14. 进阶(三):A2A 协议与外部智能体互操作

本章解决一个问题:如何让 omicOS 与其他 AI 智能体互相调用。外部智能体(Claude Code、Codex、你自己写的 agent)把分析任务委托给 omicOS;反过来,omicOS 在分析中调用外部智能体。

版本要求:A2A 功能在 0.3.29 之后的版本提供。功能默认关闭,且需要 Pro / Lab / Enterprise 档位。

预期输出说明:本章输出的结构和字段名与实现一致;其中的 id、时间戳、密钥是示意值,每次运行都不同。

14.1 三十秒概念

A2A(Agent2Agent)是 Linux Foundation 的智能体互操作协议,基于 HTTP + JSON-RPC 2.0。核心对象只有三个:

  • Agent Card:/.well-known/agent-card.json,智能体的名片(能干什么、怎么鉴权)
  • Task:一次任务。状态机 SUBMITTED → WORKING → (INPUT_REQUIRED ⇄) → COMPLETED / FAILED / CANCELED;contextId 把多个 task 串成一个会话
  • Message / Part:消息由 parts 组成,每个 part 是文本、文件(base64 或 URL)、结构化数据三选一

omicOS 两个方向都支持:

  • 对外提供服务:把 omicOS 生信智能体暴露给外部 agent 调用(§14.2–14.3)
  • 调用外部服务:把外部 A2A agent 接进 omicOS,变成一个工具(§14.4)

14.2 开启服务端

omicos a2a enable

预期输出:

A2A enabled in /Users/you/.omicos/a2a.json.
It takes effect the next time omicos serves. Run `omicos a2a status` to see
whether the endpoint will actually mount.
omicos a2a key create --label "claude-code"

预期输出(明文只显示这一次,妥善保存):

A2A API key created.

  key id : 1a2b3c4d
  label  : claude-code
  created: 2026-08-15T19:30:00Z

  omak_1a2b3c4d_Zm9vYmFyYmF6cXV4MTIzNDU2Nzg5MGFiY2RlZmdo

This is the ONLY time the key is shown. Copy it now — it is stored hashed
and cannot be displayed again. Lost keys are replaced, not recovered:
  omicos a2a key revoke 1a2b3c4d

The peer sends it as:  Authorization: Bearer <key>
omicos a2a status

预期输出(Pro 档、已发 key):

A2A endpoint status
  config      : /Users/you/.omicos/a2a.json
  keys        : /Users/you/.omicos/a2a_keys.json
  enabled     : true
  exposed agent: omicverse_omni
  plan        : pro (A2A requires pro or higher — ok)
  active keys : 1
  file parts  : inline limit 100 MiB; url parts disabled
  artifacts   : files up to 1024 KiB ship inline; larger ones as /a2a/v1/artifacts URLs
  bind host   : 127.0.0.1 (loopback)

  endpoint    : mounted at /a2a/v1, authentication REQUIRED.
                Peers send `Authorization: Bearer <key>`.

  discovery   : /.well-known/agent-card.json (always anonymous)

挂载规则(启动时和每个请求都会重新校验):

场景 结果
本机联调(127.0.0.1)+ 零 key 挂载,免鉴权(日志有警告)
任意绑定 + ≥1 把 key 挂载,强制 Bearer 鉴权
非 loopback 绑定 + 零 key 拒绝挂载(无鉴权的端口不允许上网)
档位 < Pro 拒绝挂载;已挂载后降档或过期 → 每请求 403

重启 omicos 生效。以下示例默认 http://127.0.0.1:5055 且已发 key。

14.3 外部智能体驱动 omicOS(以 Claude Code 为例)

Claude Code、Codex 等编码智能体没有原生 A2A 客户端——但不需要:A2A 就是 HTTP + JSON,curl 即是客户端。

发现

curl -s http://127.0.0.1:5055/.well-known/agent-card.json | jq

预期输出(节选):

{
  "name": "OmicOS",
  "description": "Omics analysis agent: single-cell and multi-omics QC, clustering, trajectory, differential expression.",
  "supportedInterfaces": [
    { "url": "http://127.0.0.1:5055/a2a/v1", "protocolVersion": "1.0" }
  ],
  "capabilities": { "streaming": true, "pushNotifications": true },
  "skills": [
    { "id": "single-cell-analysis", "name": "single-cell analysis" }
  ],
  "securitySchemes": { "bearer": { "httpAuthSecurityScheme": { "scheme": "bearer" } } }
}

发起分析

curl -s http://127.0.0.1:5055/a2a/v1 \
  -H "Authorization: Bearer omak_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "SendMessage",
    "params": {
      "message": {
        "messageId": "m-001",
        "role": "ROLE_USER",
        "parts": [{"text": "对当前工作区的 scRNA-seq 数据做质控和聚类,给出 UMAP 图"}]
      }
    }
  }' | jq

预期输出——60 秒内完成(终态,带产物):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "task": {
      "id": "turn_9f3a1c2e8b7d4e5f",
      "contextId": "turn_20260815193012_4c1d9e2f8a7b3c6d",
      "status": { "state": "TASK_STATE_COMPLETED", "timestamp": "2026-08-15T19:31:05Z" },
      "artifacts": [
        { "artifactId": "turn_9f3a1c2e8b7d4e5f-answer", "name": "answer",
          "parts": [{"text": "QC 后保留 8,214 个细胞,Leiden 聚出 12 个簇……"}] },
        { "artifactId": "turn_9f3a1c2e8b7d4e5f-file-0", "name": "umap_clusters.png",
          "parts": [{"raw": "iVBORw0KGgo…", "filename": "umap_clusters.png", "mediaType": "image/png"}] }
      ]
    }
  }
}

预期输出——超过 60 秒未完成(记下 id 与 contextId,转轮询):

{
  "jsonrpc": "2.0", "id": 1,
  "result": { "task": {
    "id": "turn_9f3a1c2e8b7d4e5f",
    "contextId": "turn_20260815193012_4c1d9e2f8a7b3c6d",
    "status": { "state": "TASK_STATE_WORKING" } } }
}

轮询、追问、取消

# 轮询(result 直接是 Task 对象,没有 task 包装层)
curl -s http://127.0.0.1:5055/a2a/v1 -H "Authorization: Bearer omak_..." \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"GetTask","params":{"id":"turn_9f3a1c2e8b7d4e5f"}}'

预期输出(任务不存在时):

{ "jsonrpc": "2.0", "id": 2,
  "error": { "code": -32001, "message": "task not found: turn_deadbeef" } }

同一会话追问:复用 contextId 再发 SendMessage,会开一个新 task,上下文延续。turn 还在跑时带 taskId 再发 = 中途补充指示,不会报冲突。取消用 CancelTask(params 同 GetTask),返回 TASK_STATE_CANCELED 的 Task;对已结束的任务取消会得到 -32002 错误。

审批(omicOS 在等你确认)

执行类操作默认走审批。对端会收到 TASK_STATE_INPUT_REQUIRED,status message 里带 {"kind":"tool_approval","summary":"…"}。回帖:

curl -s http://127.0.0.1:5055/a2a/v1 -H "Authorization: Bearer omak_..." \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"SendMessage","params":{"message":{
       "messageId":"m-002","taskId":"turn_9f3a1c2e8b7d4e5f","role":"ROLE_USER",
       "parts":[{"data":{"decision":"allow_once"}}]}}}'

预期输出:task 回到 TASK_STATE_WORKING 继续执行。decision 取值 allow_once / allow_session / deny;不回帖任务会一直等,退出用 CancelTask。

传文件与收产物

上行文件用 raw part(base64,解码后默认上限 100MB):

"parts": [
  {"text": "这是我的计数矩阵,做标准流程"},
  {"raw": "<base64>", "filename": "counts.h5ad", "mediaType": "application/octet-stream"}
]

下行产物在终态 task 的 artifacts[]:小图内联 base64;.h5ad 等大文件给 URL:

curl -sL -H "Authorization: Bearer omak_..." \
  http://127.0.0.1:5055/a2a/v1/artifacts/turn_9f3a1c2e8b7d4e5f/1 -o processed.h5ad

预期行为:下载成功;不是你创建的 task 或序号越界,一律 404。

Webhook(长任务:提交完就走)

curl -s http://127.0.0.1:5055/a2a/v1 -H "Authorization: Bearer omak_..." \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":4,"method":"CreateTaskPushNotificationConfig",
       "params":{"taskId":"turn_9f3a1c2e8b7d4e5f","url":"https://your-host/hook","token":"随机串"}}'

预期输出:

{ "jsonrpc": "2.0", "id": 4,
  "result": { "id": "pnc_5e6f7a8b", "taskId": "turn_9f3a1c2e8b7d4e5f",
              "url": "https://your-host/hook", "token": "随机串" } }

任务到终态(或进入等待审批)时 omicOS 会 POST 你的 URL,带 X-A2A-Notification-Token 头回显 token。分析里启动的后台长任务(可能数小时)会让 task 保持 WORKING,真正结束才推送——「提交组学分析、挂上 webhook、下班走人」。webhook 地址必须是公网地址(内网/环回会在注册时被拒,-32602)。

让 Claude Code 学会这些:CLAUDE.md 片段

在 Claude Code 的项目里放一段说明,它就能把 omicOS 当远程生信专家用:

## OmicOS A2A endpoint

本机 omicOS 暴露了 A2A 端点,可把生信分析(质控/聚类/轨迹/差异表达等)委托给它:

- Endpoint: POST http://127.0.0.1:5055/a2a/v1(JSON-RPC 2.0)
- Auth: `Authorization: Bearer $OMICOS_A2A_KEY`
- 发任务: method "SendMessage",message.parts=[{text:"..."}];同一会话复用 contextId
- TASK_STATE_WORKING 就每 30 秒 "GetTask" 轮询,期间可做别的事
- TASK_STATE_INPUT_REQUIRED 时向我转述 summary,确认后用 {"decision":"allow_once"} 回帖
- 产物在终态 task 的 artifacts[];url 型用同一个 Bearer 下载

Codex CLI 同理(放 AGENTS.md)。

14.4 omicOS 调用外部 A2A 智能体

反方向:把一个外部 A2A 服务(例如文献检索 agent)接进 omicOS。

curl -s -X POST http://127.0.0.1:5055/api/a2a/agents -H "Content-Type: application/json" \
  -d '{"id":"litsearch","name":"文献检索","url":"https://lit.example.com",
       "api_key":"对端发的key","enabled":true}'

预期输出(api_key 永远打码显示;把打码值原样提交回来不会覆盖真实 key):

{ "id": "litsearch",
  "agents": [ { "id": "litsearch", "name": "文献检索", "url": "https://lit.example.com",
                "api_key": "••••…f3k9", "enabled": true, "status": "pending" } ] }
curl -s -X POST http://127.0.0.1:5055/api/a2a/agents/litsearch/test

预期输出(成功;失败为 {"ok":false,"error":"…"}):

{ "ok": true, "name": "LitSearch", "description": "PubMed literature agent",
  "version": "1.0", "protocol_versions": ["1.0"], "skill_count": 2 }

之后 omicOS 的智能体自动获得一个工具 a2a__litsearch,会在合适时自行调用;对端任务未完成时不会卡住当前分析,omicOS 稍后自行查询进度。

注意事项:

  • 对端 URL 默认必须是公网地址;本机联调在条目里加 "allow_private": true
  • 指回 omicOS 自己端点的配置会被拒绝(防自我递归)
  • 对端返回的内容按「不可信外部数据」处理,其中夹带的指令不会被执行

14.5 Claude Code ↔ Codex:三种拓扑

Claude Code 和 Codex 原生都是 MCP 生态,都不自带 A2A 服务端。三种做法按推荐排序:

  1. omicOS 当中枢(推荐):Claude Code 用 §14.3 的方式调 omicOS;omicOS 通过 a2a__codex 工具委托给套壳的 Codex。鉴权、限流、审计、webhook 都由 omicOS 统一承担,两端零改造。
  2. 官方 SDK 套壳:用 a2a-sdk(Python,约 40 行)把 codex exec --full-auto 包成 A2A 服务端,注册进 omicOS(本机记得 allow_private: true)。同样的壳换个命令即可包 claude -p,反方向也就通了。骨架代码见 A2A 官方样例。
  3. 不用 A2A:只是两个本机 CLI 互调的话,MCP 是更短的路。A2A 的价值在跨信任边界的长任务——鉴权名片、任务状态机、webhook、断线续传。

14.6 安全须知

  1. key 是唯一凭证,omicos a2a key revoke 即时生效;每个协作方发独立 key
  2. 暴露到非 loopback 前必须有 key(程序强制);公网部署建议再套 TLS 反向代理
  3. 对端拿不到你的智能体/技能内部内容:名片是白名单投影、事件流只有粗粒度进度、每个 A2A 会话自动注入防泄漏指令——但你在消息里主动写的内容不在保护范围
  4. A2A 会话与你自己的会话相互隔离,对端无法枚举你的聊天记录
  5. 执行类操作默认走审批(INPUT_REQUIRED),不确认不执行

results matching ""

    No results matching ""