代理注册与队列API规范
本文档提供将AI代理(或任意主机)注册为GIIP逻辑机器、轮询其自身CQE任务队列并报告执行结果的技术规范 — 与生产环境中giipAgentWin/giipAgentLinux实际使用的机制相同。
📋 概述
此API使主机 — 运行giipAgentWin/giipAgentLinux的物理/虚拟机器,或没有自身持久硬件身份的AI编程代理会话 — 能够将自己注册为tLSvr中的逻辑机器(lssn),然后轮询该lssn自身的CQE(Centralized Queue Engine,集中式队列引擎)任务队列并报告结果。这三种操作都通过同一个端点完成。
🔐 认证 — 与REST风格不同的单一调度端点
这与问题管理API的API形态不同。 那个API是使用
x-api-key请求头和真实HTTP状态码的普通REST端点(/api/giipIssues)。此API是giipApiSk2— 一个表单编码的通用调度端点,始终返回HTTP 200。
基础URL
https://giipfaw.azurewebsites.net/api/giipApiSk2
请求格式(始终是相同的三个字段)
| 字段 | 内容 |
|---|---|
text | 存储过程"命令"名称加其参数名称,以空格分隔(例如AgentAutoRegister hostname jsondata)— 而非参数值 |
token | SK,作为普通表单字段发送。不是HTTP请求头。 |
jsondata | 保存实际参数值的JSON对象 |
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"}'
HTTP状态码几乎不能说明任何问题
此端点能够解析的所有请求都返回200 OK。 无效SK没有401,不存在的lssn没有404,权限问题没有403。真实结果始终位于JSON主体的data[0].RstVal中(已通过审查giipfaw/giipApiSk2/run.ps1源代码及实际测试确认,2026-09-06):
{"data":[{"Proc_MSG":"401|Unauthorized - Invalid SK","RstVal":401,"RstMsg":"Unauthorized - Invalid Secret Key"}]}
上述响应仍然是HTTP 200。判断giipApiSk2调用是否真正成功,务必始终查看data[0].RstVal,而不是HTTP状态码。
SK — 与Issue API相同的密钥类型,不同的传输方式
- 与Issue API使用的相同、在
/svclist按csn签发的项目SK — 但此处以POST主体中的token=<sk>发送,而非x-api-key请求头。 - csn由SK在服务端推导得出(
dbo.lwGetCSNbysk(@sk))— 您不会在请求中直接发送csn,AgentAutoRegister/CQEQueueGet根本不存在可被静默钳制的客户端指定csn字段(已通过审查giipdb/SP/pApiAgentAutoRegisterBySK.sql确认,2026-09-06)。KVSPut有相关但不同的所有权检查 — 见下文。
端点
AgentAutoRegister — 注册逻辑机器,或对现有机器发送心跳
请求:
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}]
}
只有hostname是必需的,其余字段均为尽力而为、可以省略。
身份键是(hostname, csn)。 存储过程(pApiAgentAutoRegisterBySK)以LSHostname = hostname AND CSn = <从sk推导>条件查找tLSvr:
| 存在现有行? | 操作 | 响应 |
|---|---|---|
| 否 | 插入新tLSvr行,lssn = SCOPE_IDENTITY() | {"data":[{"lssn":<new>,"action":"new","RstVal":200,...}]} |
| 是 | 更新现有行(规格、网络/软件/服务清单、LSLastHeartbeat) | {"data":[{"lssn":<same>,"action":"update","RstVal":200,...}]} |
已实际验证(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 # 重复调用
{"success": true, "hostname": "Lowy-DP01-claude-code", "lssn": 71290, "action": "update"}
强制性主机名规则: <物理主机名>-<tool-slug>。同一主机上不同的AI工具必须各自使用自己的主机名注册(因此获得各自的lssn)— 在同一台电脑上运行的Claude Code、Codex和Antigravity是三台不同的逻辑机器(例如Lowy-DP01-claude-code、Lowy-DP01-codex、Lowy-DP01-antigravity)。这是文档/客户端层面的约定,而非服务器强制的约束 — 服务器会照单全收你发送的任何主机名,因此这种自律必须由调用方来保证。
无效的SK不会返回401,而是在data[0]中表现为RstVal: 401({"Proc_MSG":"401|Unauthorized - Invalid SK",...})。
CQEQueueGet — 轮询此lssn的队列一次
请求:
text=CQEQueueGet lssn hostname os op
token=<sk>
jsondata={"lssn":71290,"hostname":"Lowy-DP01-claude-code","os":"Windows 10","op":"op"}
响应,有任务:
{"data":[{"RstVal":"200","ms_body":"<script or payload text>","mslsn":8032,"script_type":"sh","mssn":123}]}
响应,队列为空(实际验证,2026-09-06)— 这是成功,不是错误:
{"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含义(已通过审查giipdb/SP/pApiCQEQueueGetbySK.sql确认,2026-09-06):
RstVal | 含义 |
|---|---|
200 | 队列中有任务;ms_body/mslsn/mssn/script_type均已填充 |
201 | 对全新lssn的首次轮询触发了隐式重新注册路径 — 应视为"尚无任务",稍后再次轮询 |
0或404 | 已检查,当前没有排队任务 — 正常,非错误 |
| 其他值 | 真正的错误 |
推荐轮询间隔:60秒。 与实际giipAgentLinux安装通过cron使用的间隔(* * * * *)一致。此API本身不对轮询进行速率限制,但没有理由轮询得更快 — 队列是按GIIP自身的计划填充的。
错误的lssn与"无任务"表现相同。 如果轮询的lssn不属于该SK的csn,查找待处理任务的连接查询(tMgmtScriptList ms inner join tLSvr ls on ... where ls.csn = @csn and ms.lssn = @lssn)根本找不到任何行,返回的RstVal: 404与空队列相同。这里没有单独的"权限拒绝"或"lssn不存在"响应 — 如果怀疑轮询了错误的lssn,请重新运行register并比对。
KVSPut(kFactor=cqeresult)— 报告结果
没有专门的"完成/确认"命令。结果是通过KVSPut写入的,这是用于GIIP所有代理遥测数据的同一通用键值机制。
请求:
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必须是字符串字面量"lssn",kKey必须是结果所属的lssn(字符串形式)。
与register/poll不同,此调用会检查所有权并干净利落地失败(已通过审查giipdb/SP/pApiKVSPutbySk.sql确认,2026-09-06):SP在写入前会检查EXISTS(SELECT 1 FROM tLSvr WHERE LSsn = @kKey AND CGSn = <从sk推导>)。如果lssn不属于该SK的密钥组:
{"data":[{"RstVal":411,"RstMsg":"..."}]}
对于report,任何非200的RstVal都应视为严重失败 — 不要将其当作心跳重试,也不要悄悄丢弃该结果。
没有注销(unregister/deregister)端点
没有用于删除逻辑机器的调用。停止发送心跳的机器只会逐渐"过期"(LSLastHeartbeat不再更新)— 在API层面没有"告别"机制。
🔍 响应标准
无论调用三个命令中的哪一个,giipApiSk2调用始终将结果包装在data数组中:
{ "data": [ { "RstVal": 200, ... } ] }
- 成功:
RstVal为200(在AgentAutoRegister/CQEQueueGet的边缘情况下偶尔为201)。RstVal根据命令不同可能是数字或数字字符串——本规范的客户端脚本将其作为字符串比较以同时处理这两种情况。 - poll的"无任务":
RstVal为0或404— 成功,不是失败。 - 失败:其他任何
RstVal,并附带说明原因的RstMsg/Proc_MSG/ProcName。 - 传输层失败(网络错误、超时,或非200的HTTP状态):意味着请求根本没有到达存储过程 — 这与
RstVal失败是完全不同的失败模式。
🤖 AI代理的完整流程(注册 → 轮询 → 报告)
export GIIP_API_KEY="<the SK>"
# 1. 注册(幂等 — 首次调用创建lssn,之后使用相同hostname的
# 调用都是对该lssn的心跳)
python scripts/giip_agent.py register --tool-slug claude-code
# {"success": true, "hostname": "Lowy-DP01-claude-code", "lssn": 71290, "action": "new"}
# 2. 轮询一次。按自己的计划(约60秒)重复 -- 不要在内部循环。
python scripts/giip_agent.py poll --lssn 71290 --tool-slug claude-code
# {"success": true, "lssn": 71290, "has_work": false}
# 3. 若has_work为true,处理ms_body后报告结果。
python scripts/giip_agent.py report --lssn 71290 --mslsn 8032 --mssn 123 \
--status success --exit-code 0
⚠️ 重要说明
- 这是与问题管理API不同的API系列。 请勿混淆两者:
giip-issue的客户端将SK作为x-api-key请求头发送给具有真实HTTP状态码的REST端点;giip-agent的客户端将SK作为token表单字段发送给始终返回HTTP 200的单一调度端点。 - 主机名规则是客户端层面的约束,而非服务器约束。 没有任何机制阻止您为两个不同的工具都注册
foo两次 — 但这样做会产生两个相互混淆队列历史的lssn。请严格遵循<host>-<tool-slug>规则。 - 即使系统信息负载发生变化,重复的
register仍然是心跳 — 不是新机器。决定身份的只有hostname字符串。
故障排查
| 症状 | 原因 | 解决方法 |
|---|---|---|
AgentAutoRegister的data[0].RstVal为401 | SK无效或已重新签发 | 在/svclist重新核实SK |
poll始终返回RstVal 404/has_work: false | 要么确实没有排队任务,要么lssn属于与此SK不同的csn | 重新运行register,比较返回的lssn |
report返回RstVal 411 | lssn不属于此SK的密钥组(cgsn) | 仅针对自己register调用返回的lssn进行报告 |
| HTTP状态本身不是200 | 传输层失败(网络问题,或路由器本身拒绝了格式错误的请求) | 与RstVal失败不同 — 在确认连接后重试是合理的(RstVal失败则不应重试) |
原以为是一台机器,却出现两个不同的lssn | 主机名构建不一致(使用了不同的--tool-slug,或使用了裸主机名覆盖) | 主机名应始终按<host>-<tool-slug>构建,并在各次调用间保持稳定 |
版本:1.0
最后更新:2026-09-06(在giipfaw生产环境实际验证,csn 47,结果lssn 71290)
源文件:giipv3/public/help/giip-agent-api.zh-CN.md
v1.0(2026-09-06,giip #2081):首次发布。基于对
giipdb/SP/pApiAgentAutoRegisterBySK.sql、pApiCQEQueueGetbySK.sql、pApiKVSPutbySk.sql、giipfaw/giipApiSk2/run.ps1的源代码审查,以及giipAgentLinux(scripts/giip-auto-discover.sh、lib/cqe.sh、lib/kvs.sh、cqe/giipCQE.sh)的生产客户端行为,记录了giipApiSk2调度端点上的AgentAutoRegister、CQEQueueGet和KVSPut(kFactor=cqeresult)。实际验证:一次register(新建lssn 71290)、一次重复register(心跳,相同lssn)、一次poll(无任务)。
相关文档: