giip
SES Proposal
7 min read

Agent Registration & Queue API Reference

This document provides the technical specification for registering an AI agent (or any host) as a GIIP logical machine, polling its own CQE task queue, and reporting execution results โ€” the same mechanism giipAgentWin/giipAgentLinux use in production.

๐Ÿ”Œ Go to the giip-agent Skill Bundle โ†’

๐Ÿ“‹ Overview

This API lets a host โ€” a physical/VM machine running giipAgentWin/giipAgentLinux, or an AI coding-agent session with no persistent hardware identity of its own โ€” register itself in tLSvr as a logical machine (lssn), then poll that lssn's own CQE (Centralized Queue Engine) task queue and report back what happened. All three operations go through one endpoint.


๐Ÿ” Authentication โ€” a single dispatcher endpoint, different from the REST-style APIs

This is not the same API shape as the Issue Management API. That API is a normal REST endpoint (/api/giipIssues) with a x-api-key header and real HTTP status codes. This API is giipApiSk2 โ€” one generic dispatcher endpoint, form-encoded, that always returns HTTP 200.

Base URL

https://giipfaw.azurewebsites.net/api/giipApiSk2

Request shape (always the same three fields)

FieldContent
textThe stored-procedure "command" name plus its parameter names, space-separated (e.g. AgentAutoRegister hostname jsondata) โ€” never parameter values
tokenThe SK, as a plain form field. Not an HTTP header.
jsondataA JSON object holding the actual parameter values
curl -s -X POST "https://giipfaw.azurewebsites.net/api/giipApiSk2" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "text=AgentAutoRegister hostname jsondata" \
  --data-urlencode "token=${GIIP_API_KEY}" \
  --data-urlencode 'jsondata={"hostname":"Lowy-DP01-claude-code","os":"Windows 10"}'

The HTTP status code tells you almost nothing

Every request this endpoint can parse at all returns 200 OK. There is no 401 for an invalid SK, no 404 for a nonexistent lssn, no 403 for a permission problem. The actual outcome is always inside the JSON body, in data[0].RstVal (verified against giipfaw/giipApiSk2/run.ps1 source and live testing, 2026-09-06):

{"data":[{"Proc_MSG":"401|Unauthorized - Invalid SK","RstVal":401,"RstMsg":"Unauthorized - Invalid Secret Key"}]}

The response above is still an HTTP 200. Always check data[0].RstVal, never the HTTP status, to learn whether a giipApiSk2 call actually succeeded.

SK โ€” same key type as the Issue API, different transport

  • Same per-csn project SK issued on /svclist as the Issue API uses โ€” but sent as token=<sk> in the POST body here, not an x-api-key header.
  • The csn is derived server-side from the SK (dbo.lwGetCSNbysk(@sk)) โ€” you never send a csn in the request, and AgentAutoRegister/CQEQueueGet have no client-supplied-csn field to silently clamp (verified against giipdb/SP/pApiAgentAutoRegisterBySK.sql, 2026-09-06). KVSPut has a related but different ownership check โ€” see below.

Endpoints

AgentAutoRegister โ€” register a logical machine, or heartbeat an existing one

Request:

text=AgentAutoRegister hostname jsondata
token=<sk>
jsondata={
  "hostname": "Lowy-DP01-claude-code",
  "os": "Windows 10",
  "cpu": "...", "cpu_cores": 8, "memory_gb": 32, "disk_gb": 512,
  "agent_version": "giip-agent-skill/1.0.0",
  "ipv4_global": "...", "ipv4_local": "...",
  "network": [{"name":"eth0","ipv4":"...","ipv6":"...","mac":"..."}],
  "software": [{"name":"...","version":"...","vendor":"...","type":"..."}],
  "services": [{"name":"...","status":"...","start_type":"...","port":80}]
}

Only hostname is required; every other field is best-effort and may be omitted.

Identity key: (hostname, csn). The stored procedure (pApiAgentAutoRegisterBySK) looks up tLSvr by LSHostname = hostname AND CSn = <derived from sk>:

Existing row?ActionResponse
NoINSERT a new tLSvr row, lssn = SCOPE_IDENTITY(){"data":[{"lssn":<new>,"action":"new","RstVal":200,...}]}
YesUPDATE the existing row (specs, network/software/service inventory, LSLastHeartbeat){"data":[{"lssn":<same>,"action":"update","RstVal":200,...}]}

Live-verified (2026-09-06, csn 47):

$ python scripts/giip_agent.py register --tool-slug claude-code
{"success": true, "hostname": "Lowy-DP01-claude-code", "lssn": 71290, "action": "new"}
$ python scripts/giip_agent.py register --tool-slug claude-code   # repeat call
{"success": true, "hostname": "Lowy-DP01-claude-code", "lssn": 71290, "action": "update"}

Mandatory hostname convention: <physical-hostname>-<tool-slug>. Each distinct AI tool on a host must register under its own hostname (and therefore get its own lssn) โ€” Claude Code, Codex, and Antigravity running on the same PC are three separate logical machines, e.g. Lowy-DP01-claude-code, Lowy-DP01-codex, Lowy-DP01-antigravity. This is a documentation/client convention, not a server-enforced constraint โ€” the server will happily create whatever hostname you send, so the discipline has to come from the caller.

An invalid SK does not 401 โ€” it returns RstVal: 401 inside data[0] ({"Proc_MSG":"401|Unauthorized - Invalid SK",...}).

CQEQueueGet โ€” poll this lssn's queue, once

Request:

text=CQEQueueGet lssn hostname os op
token=<sk>
jsondata={"lssn":71290,"hostname":"Lowy-DP01-claude-code","os":"Windows 10","op":"op"}

Response, work available:

{"data":[{"RstVal":"200","ms_body":"<script or payload text>","mslsn":8032,"script_type":"sh","mssn":123}]}

Response, nothing queued (live-verified, 2026-09-06) โ€” this is success, not an error:

{"data":[{"RstVal":"404","ProcName":"[pCQEQueueGetbySK02] no queue 2"}]}
$ python scripts/giip_agent.py poll --lssn 71290 --tool-slug claude-code
{"success": true, "lssn": 71290, "has_work": false}

RstVal semantics (verified against giipdb/SP/pApiCQEQueueGetbySK.sql, 2026-09-06):

RstValMeaning
200Work is queued; ms_body/mslsn/mssn/script_type are populated
201First-ever poll for a brand-new lssn triggered an implicit re-registration path โ€” treat like "no work yet," poll again shortly
0 or 404Checked, nothing queued right now โ€” normal, not an error
anything elseA genuine error

Recommended poll interval: 60 seconds, matching the interval real giipAgentLinux installs use via cron (* * * * *). This API does not rate-limit polling itself, but there is no reason to poll faster โ€” the queue is populated on GIIP's own schedule.

Wrong lssn looks identical to "no work." If the lssn you poll does not belong to the SK's csn, the join that looks up pending work (tMgmtScriptList ms inner join tLSvr ls on ... where ls.csn = @csn and ms.lssn = @lssn) simply finds no row and returns the same RstVal: 404 as an empty queue. There is no distinct "permission denied" or "lssn not found" response here โ€” if you suspect you're polling the wrong lssn, re-run register and compare.

KVSPut (kFactor=cqeresult) โ€” report a result

There is no dedicated "complete"/"ack" verb. Results are written via KVSPut, the same generic key-value mechanism used for all of GIIP's agent telemetry.

Request:

text=KVSPut kType kKey kFactor
token=<sk>
jsondata={
  "kType": "lssn", "kKey": "71290", "kFactor": "cqeresult",
  "kValue": {"mslsn": 8032, "mssn": 123, "lssn": 71290,
             "status": "success", "exit_code": 0,
             "stdout": "...", "stderr": ""}
}

kType must be the literal string "lssn"; kKey must be the lssn (as a string) the result belongs to.

Unlike register/poll, this call checks ownership and fails cleanly (verified against giipdb/SP/pApiKVSPutbySk.sql, 2026-09-06): the SP checks EXISTS(SELECT 1 FROM tLSvr WHERE LSsn = @kKey AND CGSn = <derived from sk>) before writing. If the lssn does not belong to the SK's key group:

{"data":[{"RstVal":411,"RstMsg":"..."}]}

Treat any RstVal other than 200 from report as a hard failure โ€” do not retry it as a heartbeat, and do not silently drop the result.

No unregister / deregister endpoint

There is no delete call for a logical machine. A machine that stops sending heartbeats simply goes stale (LSLastHeartbeat ages) โ€” there is no API-level "goodbye."


๐Ÿ” Response Standard

Every giipApiSk2 call wraps its result in a data array, regardless of which of the three commands you called:

{ "data": [ { "RstVal": 200, ... } ] }
  • Success: RstVal is 200 (occasionally 201 for AgentAutoRegister/CQEQueueGet edge cases). RstVal is a number or numeric string depending on the command โ€” this reference's client script compares it as a string to handle both.
  • "No work" on poll: RstVal 0 or 404 โ€” success, not failure.
  • Failure: any other RstVal, alongside RstMsg/Proc_MSG/ProcName describing why.
  • Transport-level failure (network error, timeout, or an HTTP status other than 200): this means the request never reached the stored procedure at all โ€” a completely different failure mode from a non-200 RstVal.

๐Ÿค– Full procedure for AI agents (register โ†’ poll โ†’ report)

export GIIP_API_KEY="<the SK>"

# 1. Register (idempotent โ€” the first call creates an lssn, every later call
#    with the same hostname is a heartbeat against it)
python scripts/giip_agent.py register --tool-slug claude-code
# {"success": true, "hostname": "Lowy-DP01-claude-code", "lssn": 71290, "action": "new"}

# 2. Poll once. Repeat this on your own schedule (~60s) -- do not loop internally.
python scripts/giip_agent.py poll --lssn 71290 --tool-slug claude-code
# {"success": true, "lssn": 71290, "has_work": false}

# 3. If has_work is true, act on ms_body, then report the outcome.
python scripts/giip_agent.py report --lssn 71290 --mslsn 8032 --mssn 123 \
  --status success --exit-code 0

โš ๏ธ Important Notes

  • This is a different API family from the Issue Management API. Do not mix the two: giip-issue's client sends the SK as an x-api-key header to REST endpoints with real HTTP status codes; giip-agent's client sends the SK as a token form field to a single dispatcher endpoint that always returns HTTP 200.
  • The hostname convention is a client-side discipline, not a server constraint. Nothing stops you from registering foo twice for two different tools โ€” but doing so creates two lssns that will confuse each other's queue history. Follow <host>-<tool-slug> strictly.
  • A repeated register with a changed system-info payload is still a heartbeat, not a new machine โ€” only the hostname string determines identity.

Troubleshooting

SymptomCauseSolution
data[0].RstVal is 401 on AgentAutoRegisterInvalid/rotated SKRe-verify the SK on /svclist
poll always returns RstVal 404/has_work: falseEither genuinely no queued work, or lssn belongs to a different csn than this SKRe-run register, compare the returned lssn
report returns RstVal 411lssn does not belong to this SK's key group (cgsn)Only report against an lssn your own register call returned
HTTP status is not 200 at allTransport-level failure (network, malformed request the router itself rejected)Distinct from an RstVal failure โ€” retry is reasonable here after checking connectivity, unlike an RstVal failure
Two different lssns appear for what you thought was one machineHostname was built inconsistently (different --tool-slug, or a bare-hostname override was used)Always derive hostname as <host>-<tool-slug>, kept stable across calls

Version: 1.0 Last Updated: 2026-09-06 (live-verified against giipfaw production, csn 47, resulting in lssn 71290) Source File: giipv3/public/help/giip-agent-api.en.md

v1.0 (2026-09-06, giip #2081): Initial release. Documents AgentAutoRegister, CQEQueueGet, and KVSPut (kFactor=cqeresult) against the giipApiSk2 dispatcher, based on source review of giipdb/SP/pApiAgentAutoRegisterBySK.sql, pApiCQEQueueGetbySK.sql, pApiKVSPutbySk.sql, giipfaw/giipApiSk2/run.ps1, and production client behavior in giipAgentLinux (scripts/giip-auto-discover.sh, lib/cqe.sh, lib/kvs.sh, cqe/giipCQE.sh). Live-verified: one register (new lssn 71290), one repeat register (heartbeat, same lssn), one poll (no work queued).


Related Documents: