本机协作
文件邮箱、内存队列、数据库任务表都足够。优点是简单、低延迟;缺点是不能天然跨框架、跨机器。
从进程、Session、Run、邮箱、队列和模型唤醒开始,逐层拆解 Pi、Claude Code、OpenClaw、Hermes、Codex 与标准 A2A。重点不是记 API 名称,而是看清消息真正经过了什么。
“多个 Agent 可以协作”与“实现了标准 A2A Protocol”是两个层次。前者只要求系统能投递消息;后者要求不同厂商的 Agent 按同一网络协议互操作。
pi-a2a 扩展明确走 Agent Card、HTTP/JSON-RPC、Task 和 Push Notification 路线,属于面向标准 A2A 的实现;是否完整兼容仍需互操作测试。
文件邮箱、内存队列、数据库任务表都足够。优点是简单、低延迟;缺点是不能天然跨框架、跨机器。
需要 Agent Card、认证、标准 Task 状态、结果 Artifact、流式或异步回调。这才是标准 A2A 解决的问题。
消息到达并不等于模型已经处理。必须由 Runtime 把消息装进 Session,再调度一次新的模型 Run。
最常见的误解,是把 Agent、Session、Run、进程和模型当成同一个东西。它们其实分别属于配置层、状态层、调度层和操作系统层。
| 概念 | 准确含义 | Java 类比 | 它是否一直运行 |
|---|---|---|---|
| LLM / 模型 | 收到上下文后生成下一段输出;本身不会监听邮箱 | 一次远程方法调用 | 否 |
| Agent 配置 | 系统提示词、模型、工具、权限、技能、工作目录 | BeanDefinition / 配置模板 | 否 |
| Session | 消息历史、状态、路由、模型选择等持久化上下文 | 数据库聚合根 / 会话记录 | 否 |
| Run / Turn | 针对一个 Session 执行一次模型—工具循环 | 一个 Runnable / Callable | 只在执行期间 |
| Runtime / Gateway | 保存 Session、执行工具、调用模型、处理队列 | Spring Boot 服务 | 通常是 |
| OS Process | 真正由操作系统调度的程序实例 | JVM 进程 | 进程存活期间 |
名字、Agent ID、SessionKey、注册文件、mDNS 或 Agent Card。没有地址,就不知道消息该投给谁。
JSON 文件、进程内队列、SQLite 行、HTTP 请求。API 名字相似,底层介质可能完全不同。
目标空闲时启动 Run;目标繁忙时决定 steer、follow-up、合并、等待或中断。
加载目标 Session 的历史、目标 Agent 的提示词、工具权限,再把新消息加入模型输入。
通过 requestId、taskId、contextId、父子关系或 replyTo,把结果交回正确请求者。
决定进程崩溃后消息、任务和完成事件能不能恢复,也决定是否会重复投递。
点击“逐步播放”,观察一条“请审查认证模块”的消息,从 A 的模型输出一路走到 B 的模型,再返回 A。
send(to=B, message=...)。模型没有自己建立连接。三个协议经常一起出现,但服务对象不同。判断一个项目是否“使用 A2A”,不能仅凭文档出现了 agent-to-agent 字样。
| 协议/接口 | 两端是谁 | 解决的问题 | 不负责什么 |
|---|---|---|---|
| A2A Protocol | Agent Runtime ↔ Agent Runtime | 发现能力、发送 Message、跟踪 Task、接收 Artifact、流式/异步通知 | 不规定内部如何启动模型、进程和工具 |
| MCP | Agent/模型宿主 ↔ 工具与资源服务 | 列出和调用工具、读取资源、获取 Prompt | 不是完整的远程 Agent 任务生命周期 |
| ACP / Codex App Server | IDE/Gateway/宿主 ↔ Coding Agent Harness | 创建会话、发送 Turn、流式接收事件、审批工具操作 | 不是任意 Agent 之间的标准发现协议 |
| 内部 send API | 同一产品内的 Session ↔ Session | 依赖中央 Gateway 或本地文件快速协作 | 通常不能让别的厂商直接接入 |
Agent A Runtime
│
├─ GET https://agent-b/.well-known/agent-card.json
│ └─ 能力、技能、接口、认证、协议版本
│
├─ POST /message:send (或 JSON-RPC / gRPC)
│ └─ Message + contextId + metadata
│
├─ 收到 Task { id, state: WORKING }
│
└─ 结果取得方式
├─ 同步 Message
├─ Streaming events
├─ Push Notification/Webhook
└─ GetTask 轮询兜底
Agent B 内部如何把 Task 变成模型 Run:A2A 不规定。
sessions_send、SendMessage 或编排器 API。
父 Pi Extension
│ 创建任务 / runId
▼
Runner(管理程序)
│ spawn("pi", ["--mode", "json", ...])
▼
子 Pi OS 进程
│ stdout / session 文件 / result JSON
▼
Runner 收集完成结果
│ 同步返回,或写结果文件后通知
▼
父 Pi Session
Java 类比是 ProcessBuilder.start() 加上异步读取 process.getInputStream()。它的通信拓扑是父 → 子 → 父;子 Agent 并不需要发现其他平级 Agent。
不是标准 A2A 原因:它是本地进程管理和父子结果归并,没有 Agent Card 与标准远程 Task 接口。
~/.pi/agent/messenger/
├── registry/
│ ├── GoldFalcon.json ← 身份、PID、Session、心跳
│ └── BlueTiger.json
└── inbox/
├── GoldFalcon/ ← 只由 GoldFalcon 监听
└── BlueTiger/ ← 只由 BlueTiger 监听
pi_messenger({action:"send", to:"B"})。inbox/B/时间戳-随机数.json。fs.watch(inboxOfB) 发目录变化事件。pi.sendMessage(..., {triggerTurn:true, deliverAs:"steer"})。不是标准 A2A 它没有服务器和标准 Task;优点是极简单,缺点是通常要求共享同一个文件系统。
Pi A Pi B │ │ ├─ mDNS 浏览 _pi-a2a._tcp ───────────►│ 广播名称、端口、workspace │◄──────────── 发现 B ─────────────────┤ │ │ ├─ GET Agent Card ────────────────────►│ ├─ JSON-RPC SendMessage(request) ─────►│ 创建 Task: WORKING │ ├─ pi.sendUserMessage(followUp) │◄────── Task ID ─────────────────────┤ │ │ 模型处理、a2a_reply │◄──── Push Notification COMPLETED ───┤ └─ 若 Push 丢失:GetTask 轮询兜底 ───►│
followUp 的业务意义是:B 正忙时不硬插入当前模型决策,而是排在当前 Turn 之后;B 空闲时立即触发新的 Turn。HTTP Handler 不需要一直阻塞等待模型完成。
面向 A2A 标准的子集实现 已实现 Agent Card、JSON-RPC、Task、Push 与 GetTask;但完整标准兼容应通过其他 A2A SDK 的互操作测试确认。
Main Claude │ Agent tool ▼ Child context │ final summary ▼ Main Claude
层级委派。子 Agent 只向调用者汇报,通常不直接与兄弟 Agent 通信。
Lead ───── Teammate A
│ ╲ ╱
│ Mailbox
│ ╱ ╲
└────── Teammate B
Shared Tasks每个 teammate 是独立 Claude Code Session,可以按名字直接互发消息并共享任务表。
~/.claude/teams/{team-name}/
├── config.json ← members、agent ID、session ID、运行信息
└── inboxes/
├── lead.json ← lead 的邮箱
├── researcher.json ← researcher 的邮箱
└── reviewer.json
~/.claude/tasks/{team-name}/ ← 持久任务列表
config.json。CLAUDE.md、技能和 MCP;不会自动继承 Lead 的完整对话历史。SendMessage(to="reviewer") 根据成员名定位目标邮箱。fs.watch。共享任务的“认领”使用文件锁避免两个 teammate 同时拿到一张任务卡;它只解决任务状态竞态,并不锁住源代码文件。
不是标准 A2A 这是 Claude Code 私有的本机 Team Mailbox 协议。
OpenClaw 不是让 Session 自己在网络上寻找其他 Session。Gateway 是中心路由器、Session 注册表和调度器。
┌─ Agent 配置 A ─ Session A1 ─ Run 1, Run 2...
外部频道 / UI ─ Gateway
├─ Agent 配置 B ─ Session B1 ─ Run 1, Run 2...
└─ Agent 配置 C ─ Subagent Session ─ Run...
sessions_send 的完整过程sessions_send(sessionKey=B) 工具调用。sessionKey 找到 B 的本地 Session;目标不是外部网络地址。isUser=false 的 inter-session message,保存来源信息。session:<key> 的 FIFO lane;同一 Session 最多一个活跃 Run。sessions_spawn 与 AnnounceParent Session │ sessions_spawn(task) ▼ Gateway 创建 childSessionKey + transcript + 权限边界 │ 放入全局 subagent lane ▼ Child Session 执行工作 Run │ ▼ Child Session 内执行 Announce Step │ 生成:状态、最终文本、耗时、Token、sessionKey ▼ Completion Event ├─ 顶层父 Session:follow-up agent call + 外部投递 └─ 嵌套父 Session:内部注入,供 orchestrator 综合
Announce 不是子函数直接 return String。它是工作 Run 结束后,由子 Session 再执行的交付步骤,然后 Gateway 把标准化完成事件送到父级。
不是标准 A2A 文档中的 “A2A follow-up” 是泛指 Session 间对话。OpenClaw 调用 Claude/Codex 外部 Harness 时主要使用 ACP,这也不是标准 A2A Protocol。
delegate_taskParent AIAgent
│ submit
▼
ThreadPoolExecutor
├─ Child AIAgent 1
├─ Child AIAgent 2
└─ Child AIAgent 3
│ completion
▼
state.db → fresh-turn queue → Parent
子 Agent 有隔离上下文,只收到 goal/context。顶层先返回 handle;完成事件先持久化,再触发父 Session 的新回合。
Orchestrator / Human │ INSERT task ▼ SQLite Kanban DB │ Dispatcher atomic claim ▼ Named Profile OS Process │ heartbeat / comment / complete ▼ SQLite events + downstream task
它是持久消息队列和状态机。每个 handoff 都是一行记录,适合重启恢复、人工介入和跨小时工作。
因为接收方不一定正在运行。Researcher 完成后把结果和备注写进任务行/评论;Writer 可能十分钟后才被 Dispatcher 启动。Writer 的系统提示词要求它首先调用 kanban_show(),把父任务交付物、此前尝试和评论线程装入自己的上下文。
Researcher Process ── kanban_complete/comment ──► SQLite
│
│ task dependency satisfied
▼
Dispatcher spawn
│
▼
Writer Process
这不是实时聊天,但从分布式系统角度,它是比聊天更耐久的异步消息传递。
不是标准 A2A delegate_task 是父子 RPC;Kanban 是单机 SQLite 协作协议。
Codex App / IDE / Web Worker │ JSON-RPC over stdio (JSONL) ▼ Long-running App Server Process ├─ stdio reader ├─ message processor ├─ thread manager ├─ Core Session / Thread A └─ Core Session / Thread B
一个 App Server 进程可以承载多个 Codex Core Session。客户端发送 Thread/Turn 请求,Server 连续产生 item started/delta/completed、turn completed 等事件。它是“客户端控制 Agent Harness”的协议,不是标准 Agent-to-Agent 网络协议。
| 工具 | 真正的业务语义 | Java 类比 |
|---|---|---|
spawn_agent | 创建子 Agent 上下文、分配 ID/任务路径、加入父子树并启动首个 Run | 创建任务记录并提交 Executor |
send_message | 向已存在目标的 Mailbox 排入消息;不承诺唤醒完全空闲的目标 | queue.offer(message) |
followup_task | 发送新任务;若目标空闲则触发一个新 Turn | queue.offer + executor.submit |
wait_agent | 挂起父 Agent 的等待,直到邮箱更新、子 Agent 完成、用户输入或超时 | CompletableFuture.anyOf(...).get(timeout) |
Codex App 的多 Agent 界面通常让不同 Agent 使用独立 Thread,并可用 Git Worktree 隔离代码修改;当前团队运行时则可能共享同一工作目录,这两种部署形态不能混为一谈。
不是标准 A2A App Server JSON-RPC 是客户端—Harness 协议;团队工具是内部 Orchestrator 协议。
| 系统 | 忙时处理 | 主请求是否等待 | 关键风险 |
|---|---|---|---|
| Pi Messenger | deliverAs: steer 尝试注入运行中的 Turn;否则由 Pi Runtime 后续处理 | 发送工具很快返回 | Steering 改变目标当前计划 |
| Pi A2A | followUp 排到当前 Turn 后 | HTTP 先返回 Task | 需要 Push/GetTask 确保最终结果 |
| Claude Teams | 消息留在 Mailbox,Runtime 自动投递 | 发送方不必轮询 | 公开文档未说明具体调度算法 |
| OpenClaw | steer / followup / collect / interrupt 四种策略 | 可等待,也可 timeout=0 | 错误的 interrupt 会丢掉当前工作 |
| Hermes Delegation | 受 ThreadPool 并发上限控制,多余批次可能直接报错 | 顶层先拿 handle | 父进程崩溃时运行中的子任务不能恢复 |
| Hermes Kanban | Task 留在 ready/running 状态,由 Dispatcher 和并发槽控制 | 创建者 fire-and-forget | 必须正确 heartbeat 与 complete |
| Codex Orchestrator | 消息进入目标邮箱;followup 可触发空闲目标 | 父可继续或 wait | 共享工作目录可能产生写冲突 |
真正成熟的 Agent 协作,不只要“能发消息”,还要回答:写成功但没处理怎么办?处理完成但回执丢了怎么办?重启后会不会重复执行?
消息可能重试,因此接收方可能看到两次。需要 idempotency key、taskId 或去重表。
处理后立即删消息可以避免重复,但进程在“删除后、执行前”崩溃时可能永久丢失。
Hermes 先把 completion 写进 state.db,再投 fresh-turn,可恢复“已完成但尚未交付”的事件。
A2A/pi-a2a 使用 Task + Push;Push 丢失后再 GetTask,把通知通道和真相来源分离。
deliverFn 后会删除。解析或投递异常时源码也会删除,避免无限重试。因此它偏向简单本机协作,不是严格消息队列。| 系统 | 默认/典型工作区 | 冲突机制 | 建议 |
|---|---|---|---|
| Pi Messenger | 共享目录 | reserve 是合作式约定,不是 OS 强制锁 | 按目录分工;修改前 reserve;最终统一 review |
| Claude Teams | 共享项目目录 | 任务 claim 有文件锁,代码文件没有;官方警告会覆盖 | 每人拥有不同文件集合 |
| OpenClaw | 取决于 spawn 的 cwd | 可对可见 Session 开 managed worktree | 编码 Agent 使用独立 worktree |
| Hermes Kanban | scratch、共享 dir 或 worktree | worktree 类型隔离 Git 工作区 | 工程流水线优先 worktree |
| Codex App | 内置 Worktree 支持 | Agent 各自在隔离副本工作 | 审查后选择性合并 |
| 当前 Codex 团队运行时 | 可能共享工作目录 | 没有自动代码文件锁 | 父 Agent 明确分配文件所有权 |
| 系统 | 执行单位 | 发现/寻址 | 消息介质 | 唤醒目标 | 结果返回 | A2A |
|---|---|---|---|---|---|---|
| Pi Subagents | 子 Pi 进程 | 父创建 | stdout / 文件 | Runner | Tool Result / 通知 | 否 |
| Pi Messenger | 独立 Pi Session/进程 | registry JSON | inbox JSON | fs.watch → pi.sendMessage | 反向邮箱 | 否 |
| Pi A2A | Pi + HTTP Listener | mDNS + Agent Card | HTTP/JSON-RPC | sendUserMessage(followUp) | Push / GetTask | 子集路线 |
| Claude Subagent | 子 Context | 父创建 | 内部任务调用 | Claude Runtime | 最终摘要 | 否 |
| Claude Teams | 独立 Session | team members | JSON Mailbox | Claude Runtime | 反向 Mailbox | 否 |
| OpenClaw send | 同 Gateway Session Run | Session Registry | 进程内 lane 队列 | Gateway Scheduler | 内联或异步 | 否 |
| OpenClaw spawn | Child Session Run | 父子树 | subagent lane | Gateway Scheduler | Announce | 否 |
| Hermes delegate | Child AIAgent | 父创建 | Future + state.db | ThreadPool / fresh-turn | 父 Session 新消息 | 否 |
| Hermes Kanban | Worker OS Process | Profile/assignee | SQLite 行 | Dispatcher | Result/Comment/下游任务 | 否 |
| Codex | Agent Thread/Context | Orchestrator Tree | 内部 Mailbox | Orchestrator | Mailbox/Final Event | 否 |
| 标准 A2A | 任意远程 Runtime | Agent Card/Registry | HTTP/RPC/gRPC | 接收方自行实现 | Task/Artifact/Stream/Push | 是 |
优先 Pi Messenger。它的文件邮箱结构最容易观察、调试和接入 Web UI。
使用 Subagent / delegate_task。它像并行函数调用,认知成本最低。
选择数据库任务板/工作流,例如 Hermes Kanban,而不是只靠实时消息。
在各 Runtime 外增加 A2A Adapter;内部继续使用产品原生 Session API。