Files
heicode/docs/integration/heicode-am-contract.md
T
chenchenandClaude Opus 4.8 b01bba53f0 docs(agent): lock client↔agent auth to option ① (agent-local token compare)
Per the chosen design, the agent authorizes callers by comparing the request
header X-Agent-Access-Token against its env AGENT_ACCESS_TOKEN (constant-time),
no HM round-trip. AM contract §3.1 now states ① as the agreed integration with
Python pseudo-code; the /agent-access/verify endpoint is demoted to an optional
fallback. Client API §6 spells out the client's job: send X-Agent-Access-Token
on every direct-connect request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:23:41 +08:00

12 KiB
Raw Blame History

HM ↔ AM 接口契约(模板 Agent 模型)— 给 agent_management 团队

更新时间:2026-06-04 角色:HM = Heicode Manager(网关/控制面/模型网关)。AM = agent_management(云端 agent 运行时,你们)。 现状(2026-06-04 真实联调):创建 agent 已打通(HM→POST /agents→AM 返回 subdomain);剩 3 条阻断在 AM 侧(删除 500 bug / 缺 stop 端点 / agent 不就绪),见 §0.1。HM 调 AM 的代码全部隔离在 heicode/controller/agent_template_runtime.go,字段/路径可按本文对齐;路径都支持环境变量覆盖(见末尾)。


0. 模型一句话

用户在 HM 网页台选「资源 + agent 模板」部署 → HM 把 agent 定义(.md)+ 解好的资源 env 交给 AM 启动 → AM 起一个常驻 agent、分配唯一子域名 返回 → 桌面客户端从 HM 拿到地址后直连 agent(A2A)对话。HM 不在对话回路;agent 用模型走 HM /v1/*。


0.1 联调结果(2026-06-04 真实测试,账号在生产实跑)

接口 结果 需 AM
创建 POST /agents ✅ 通:返回 namespace + access_info.domain,HM 已解析成 subdomain;agent 记录建立 —
删除 DELETE /agents/{id} ❌ HTTP 500:{"detail":"cannot access local variable 'd...'"}(Python UnboundLocalError)。端点存在、id 正确,是你们删除函数的 bug 🔴 修删除函数
停止 POST /agents/{id}/stop ❌ 404:你们没有这个端点 🔴 补 stop 端点(或确认只支持删除)
取状态 GET /agents/{id} ✅ 可达(HM 拉到 status) 确认字段/路径
agent 直连(A2A) ⚠️ 连不上:agent 一直 Pending、子域名 *.taijiagnet.com 外网 /health 超时/TLS 失败 🔴 排查 agent 为何不就绪 / 公网可达性
模型调用 ✅ HM 现签的 OPENAI_API_KEY 实测能打通 /v1(gpt-5.4) —

AM 侧待办(三条阻断完整链路):① 修 DELETE /agents/{id} 的 500;② 提供 stop 端点;③ 让 agent 真正起来、子域名外网可达。HM 侧已就绪,这三条 OK 后端到端即通。


1. AM 需要提供的 4 个接口

HM 用 Authorization: Bearer <service_token> 调 AM。统一响应信封建议 { "success": true, "data": {...} }(HM 会从 data 取值;失败给 { "success": false, "message": "..." })。

1.1 启动 agent(核心,已对齐你们的 POST /agents)

POST {AM}/agents

请求体(HM 实际发送的,已按你们 CODING_A2A §2 对齐):

{
  "name": "dep-b5fab27e9255",                 // 由 HM 部署 id 派生(DNS 安全)
  "template": "coding_a2a_agent",
  "framework": "A2A",
  "config": {
    "user_id": "22",
    "manager_deployment_id": "dep_b5fab27e9255",
    "callback_url": "https://code.xinghanlab.com/api/agent/callbacks/runtime-events"
  },
  "env": {
    "AGENT_ROLE_NAME": "architect",
    "AGENT_INSTRUCTION_TEXT": "---\nname: architect\n---\n<Agent_Prompt>...</Agent_Prompt>",
    "OPENAI_BASE_URL": "https://code.xinghanlab.com/v1",
    "OPENAI_API_KEY": "sk-xxxx",
    "MODEL_NAME": "gpt-5.4",
    "AGENT_ACCESS_TOKEN": "550e8400-e29b-...",   // 客户端↔agent 访问令牌(见 §3.1)
    "HEICODE_AGENT_ID": "dep_b5fab27e9255",      // 回 HM verify 时带这个
    "GIT_PROVIDER": "github", "GIT_REPO_URL": "...", "GIT_TOKEN": "…",
    "MYSQL_HOST": "...", "MYSQL_PASSWORD": "…"
  }
}

响应(HM 解析你们返回的字段):

  • runtime_id ← runtime_id/agent_id/id/name/namespace(取到的实例标识,用于 status/stop/delete)。
  • subdomain ← subdomain 或 access_info.domain / access_info.external_ip(agent 直连地址)。
  • access_token:HM 自己现签并下发(不取你们返回值),即注入 env 的 AGENT_ACCESS_TOKEN,客户端用它做访问鉴权(见 §3.1)。
  • status ← status/runtime_status(取不到默认 running)。

**AM 启动时做的(你们已实现):**用 env.AGENT_INSTRUCTION_TEXT+AGENT_ROLE_NAME 设角色;注入 env;agent 用模型走 OPENAI_BASE_URL(=HM /v1)。

⚠️ provisioning 同步:HM 超时默认 60s(AGENT_RUNTIME_START_TIMEOUT_SECONDS)。

⚠️ 下面 1.2–1.4 的路径是 HM 的推测(你们文档只写了创建 + agent 自身 /health + A2A 调用,没写 agent-manager 的生命周期接口)。实测:创建 POST /agents ✅ 通,但 stop/delete 用 /api/agent/agents/{id}/... 返回 404。 HM 已把默认改成与创建同命名空间的 /agents/{id}。请 AM 确认真实的 状态/停止/删除 端点(方法 + 路径 + 用哪个 id),不一致我们改 env 覆盖即可。

1.2 取 agent 状态

GET {AM}/agents/{runtime_id} → data: { "status": "running" }(HM 接受 status/runtime_status/state)。

1.3 停止 agent

POST {AM}/agents/{runtime_id}/stop → 2xx 即可。

1.4 删除 agent

DELETE {AM}/agents/{runtime_id} → 2xx 即可。

runtime_id = HM 从创建响应取到的实例标识(优先 runtime_id/agent_id/id/name/namespace)。实测创建时 HM 发的 name="dep-xxxx",若你们生命周期用别的 id,请在响应里明确返回 runtime_id。


2. env 命名约定(HM↔AM 必须一致,agent 模板按此读)

HM 把用户绑定的资源按资源类型 + provider解成固定 env 名(代码 controller/agent_template_env.go)。AM 的 agent 模板按这些名字读 env;用户在 HM 只填业务字段、看不到 env 名。

2026-06-04 已对齐 AM 的 coding_a2a_agent:env 名按你们文档实现。当前只支持 AM 已支持的 4 类(git / mysql / postgres / azure blob);vm / redis / mongo / 对象桶等 HM 暂不下发,待 AM 支持再加。

资源类型 provider 注入的 env(非密 + 密钥)
git github / gitea / gitlab GIT_PROVIDER GIT_REPO_URL GIT_DEFAULT_BRANCH · GIT_TOKEN(密)
database mysql MYSQL_HOST MYSQL_PORT MYSQL_DATABASE MYSQL_USER · MYSQL_PASSWORD(密)
database postgres POSTGRES_HOST POSTGRES_PORT POSTGRES_DATABASE POSTGRES_USER · POSTGRES_PASSWORD(密)
storage azure blob AZURE_BLOB_ACCOUNT_NAME AZURE_BLOB_CONTAINER · AZURE_BLOB_ACCOUNT_KEY(密)

另外随启动注入(模型网关,3 个都传):

  • OPENAI_BASE_URL = HM /v1(https://code.xinghanlab.com/v1)。

  • MODEL_NAME = 默认 gpt-5.4(可用 env AGENT_RUNTIME_DEFAULT_MODEL 覆盖;模板 frontmatter 里的 opus/sonnet 只是角色提示,不会当作网关模型传)。

  • OPENAI_API_KEY = HM 为该用户现签的一把 new-api sk(sk-…),计费到该用户账上;删 agent 时自动吊销。已实测:用它打 /v1/chat/completions(gpt-5.4)能真实返回、计入用户用量。客户端在 A2A 请求里也可带自己的 api_key(你们文档 §7);请求级会覆盖启动级。

  • 缺失的可选字段不会出现在 env 里(agent 自行容错)。

  • 同类型资源 HM 限制只挂一个(避免 env 名冲突)。

  • 角色/指令注入:HM 把模板 .md 放进 env.AGENT_INSTRUCTION_TEXT、模板 key 放进 env.AGENT_ROLE_NAME(对齐你们 §4)。


3. 客户端 ↔ agent 直连(A2A,已对齐你们文档)

客户端从 HM 拿到 subdomain 后直连 agent,走你们的 A2A 协议(POST {subdomain}/message/send 同步、POST {subdomain}/message/stream 流式、GET {subdomain}/.well-known/agent.json、GET {subdomain}/health)。HM 不在回路。HM 已据此写好客户端文档 heicode-desktop-client-api.md §6。

3.1 客户端↔agent 访问鉴权(HM 已提供;接入方式 = ① 本地校验)

解决「agent 公网可达、谁有有效 sk 都能驱动别人的 agent、读走其挂载的 git token / db 密码 / blob key」的按用户隔离问题。HM 侧已落地;双方约定 AM 用 ① 本地校验接入(见下,一行字符串比较)。

  • 部署 agent 时,HM 给每个 agent 现签一把随机访问令牌(per-agent access token,UUID),并:
    1. 注入进 agent 的 env:AGENT_ACCESS_TOKEN(令牌本身)、HEICODE_AGENT_ID(该 agent 的 agent_id,即 dep-xxx)。
    2. 返回给客户端:HM 的 agent 列表/详情里 access_token 字段就是这把令牌(只有部署它的那个用户拿得到)。
  • 客户端直连 agent 时,把这把令牌放进请求头 X-Agent-Access-Token。
  • agent 鉴权方式 = ① 本地校验(已选定):
    • 比较 请求头 X-Agent-Access-Token == 自己 env 里的 AGENT_ACCESS_TOKEN(常量时间比较,建议 hmac.compare/secrets.compare_digest)。两者是 HM 启动时注入的同一把密钥,相等即放行,不等返回 401/403。
    • 不用回 HM、零额外依赖、零网络往返。这是双方约定的正式接入方式。
    • 缺失或为空(老 agent / 未注入)时如何处理由 AM 决定(建议:env 里有 AGENT_ACCESS_TOKEN 就强制校验,没有则放行以兼容)。
  • 可选兜底:HM 权威校验端点(AM 不需要、默认不用;仅当 AM 想让 HM 当权威、或想顺带拿 user_id 时):POST {HM}/api/heicode/agent-access/verify,body {"agent_id":"<HEICODE_AGENT_ID>","access_token":"<X-Agent-Access-Token>"} → {"success":true,"data":{"valid":true,"user_id":22,"template_id":"..."}};不符只回 {"valid":false}。公开端点、常量时间比较。
  • 只有部署该 agent 的用户拿到令牌 ⇒ 只有他能调 = 「这个 agent 这个用户能调」。
  • 通道加密 = TLS:agent 子域名请走 HTTPS,令牌与对话内容由 TLS 加密;HM 不在回路,无需 HM 中转解密。

AM 侧伪代码(本地校验):

# coding_a2a_agent 收到 A2A 请求时
expected = os.environ.get("AGENT_ACCESS_TOKEN", "")
got = request.headers.get("X-Agent-Access-Token", "")
if expected and not secrets.compare_digest(expected, got):
    raise HTTPException(401, "agent access denied")
# 通过 → 正常处理(api_key 仍用于打 HM /v1 计费)

模型鉴权(打 HM /v1/*)仍是 A2A 请求里的 api_key(模型 sk-),与上面的访问令牌职责分离:api_key 管「用谁的额度跑模型」,AGENT_ACCESS_TOKEN 管「谁有权调这个 agent」。

若 AM 完全不接:退化为旧状态——任一有效 sk 都能驱动任一 agent,存在跨用户读资源风险。HM 已把机制备好,接入成本极低(本地校验仅一行字符串比较)。


4. 安全要求

  • env 里含明文密钥(vm/db 密码、blob key、git token)。AM 的启动接口必须 HTTPS 或私网;HM 检测到明文密钥发往非 HTTPS AM 会打告警。
  • agent 绝不能把 .env/密钥回显到对话、日志、或提交进 git——由 AM 保证。
  • 生产密钥只到 agent 运行环境,不要再外传。

5. 回调(可选,lifecycle)

HM 提供 POST /api/agent/callbacks/runtime-events(HMAC 签名,callback_url 已随启动下发)。AM 可上报 agent 生命周期(started/stopped/unhealthy)。当前 HM 主要用主动拉取(§1.2)判状态,回调非必须;若 AM 走回调推送,与我们对齐事件格式即可。


6. 路径覆盖(联调期改路径无需改 HM 代码)

HM 这些路径都可用环境变量覆盖(默认值见 §1),AM 若用别的路径,运维改 env 即可:

env 默认 用途
AGENT_RUNTIME_BASE_URL — AM 基址(必须配,HTTPS/私网)
AGENT_RUNTIME_SERVICE_TOKEN — HM 调 AM 的 Bearer
AGENT_RUNTIME_AGENT_START_PATH /agents 启动(你们的 create)
AGENT_RUNTIME_AGENT_PATH /api/agent/agents/{agent_id} 取状态(GET)/删除(DELETE)
AGENT_RUNTIME_AGENT_STOP_PATH /api/agent/agents/{agent_id}/stop 停止
AGENT_RUNTIME_START_TIMEOUT_SECONDS 60 启动超时

字段名若需调整(如 subdomain→别的),只改 heicode/controller/agent_template_runtime.go 中 amStartTemplateAgent/amGetAgentStatus 的解析键一处即可,其余 HM 代码不动。


7. 联调自检清单

  • POST /agents/start 收到 agent_definition+env+callback_url,起 agent、注入 env、返回 {runtime_id, subdomain, access_token, status}。
  • agent 用模型打到 HM /v1/*(带用户身份),计费正常。
  • agent 子域名按 §3.1 ① 本地比对 X-Agent-Access-Token == env AGENT_ACCESS_TOKEN(常量时间);不符返回 401/403,非该 agent 所有者的调用被拒。
  • GET /agents/{id} 返回真实 status;stop/delete 生效。
  • agent 不泄露 env/密钥;启动接口 HTTPS/私网。
  • 把 §3 的 SSE 直连契约回给 HM 补进客户端文档。