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 服务端。三种做法按推荐排序:
- omicOS 当中枢(推荐):Claude Code 用 §14.3 的方式调 omicOS;omicOS 通过
a2a__codex工具委托给套壳的 Codex。鉴权、限流、审计、webhook 都由 omicOS 统一承担,两端零改造。 - 官方 SDK 套壳:用
a2a-sdk(Python,约 40 行)把codex exec --full-auto包成 A2A 服务端,注册进 omicOS(本机记得allow_private: true)。同样的壳换个命令即可包claude -p,反方向也就通了。骨架代码见 A2A 官方样例。 - 不用 A2A:只是两个本机 CLI 互调的话,MCP 是更短的路。A2A 的价值在跨信任边界的长任务——鉴权名片、任务状态机、webhook、断线续传。
14.6 安全须知
- key 是唯一凭证,
omicos a2a key revoke即时生效;每个协作方发独立 key - 暴露到非 loopback 前必须有 key(程序强制);公网部署建议再套 TLS 反向代理
- 对端拿不到你的智能体/技能内部内容:名片是白名单投影、事件流只有粗粒度进度、每个 A2A 会话自动注入防泄漏指令——但你在消息里主动写的内容不在保护范围
- A2A 会话与你自己的会话相互隔离,对端无法枚举你的聊天记录
- 执行类操作默认走审批(INPUT_REQUIRED),不确认不执行