Deep Dive · 2026

Agent 到底
是怎么互相通信的?

从进程、Session、Run、邮箱、队列和模型唤醒开始,逐层拆解 Pi、Claude Code、OpenClaw、Hermes、Codex 与标准 A2A。重点不是记 API 名称,而是看清消息真正经过了什么。

Pi MessengerPi A2AClaude Agent TeamsOpenClaw GatewayHermes KanbanCodex App Server
Conclusion first

先给最终结论

“多个 Agent 可以协作”与“实现了标准 A2A Protocol”是两个层次。前者只要求系统能投递消息;后者要求不同厂商的 Agent 按同一网络协议互操作。

目前的核心判断:Pi、Claude Code、OpenClaw、Hermes、Codex 都有自己的协作机制,但它们的核心机制不是标准 A2A。Pi 生态的 pi-a2a 扩展明确走 Agent Card、HTTP/JSON-RPC、Task 和 Push Notification 路线,属于面向标准 A2A 的实现;是否完整兼容仍需互操作测试。
01

本机协作

文件邮箱、内存队列、数据库任务表都足够。优点是简单、低延迟;缺点是不能天然跨框架、跨机器。

02

远程互操作

需要 Agent Card、认证、标准 Task 状态、结果 Artifact、流式或异步回调。这才是标准 A2A 解决的问题。

03

真正的“唤醒”

消息到达并不等于模型已经处理。必须由 Runtime 把消息装进 Session,再调度一次新的模型 Run。

First principles

先分清六个经常混淆的对象

最常见的误解,是把 Agent、Session、Run、进程和模型当成同一个东西。它们其实分别属于配置层、状态层、调度层和操作系统层。

概念准确含义Java 类比它是否一直运行
LLM / 模型收到上下文后生成下一段输出;本身不会监听邮箱一次远程方法调用否
Agent 配置系统提示词、模型、工具、权限、技能、工作目录BeanDefinition / 配置模板否
Session消息历史、状态、路由、模型选择等持久化上下文数据库聚合根 / 会话记录否
Run / Turn针对一个 Session 执行一次模型—工具循环一个 Runnable / Callable只在执行期间
Runtime / Gateway保存 Session、执行工具、调用模型、处理队列Spring Boot 服务通常是
OS Process真正由操作系统调度的程序实例JVM 进程进程存活期间
尤其注意:Codex 文档里的 Thread 通常表示“会话线程”,不是 Java Thread;OpenClaw 的 Session 也不是 Node 子进程。一个 Gateway 进程可以承载很多 Session,每个 Session 又可以先后产生很多 Run。

Agent 通信系统的最小组成

地址簿+消息载体+调度器+上下文装配+回执关联+持久化

地址簿

名字、Agent ID、SessionKey、注册文件、mDNS 或 Agent Card。没有地址,就不知道消息该投给谁。

消息载体

JSON 文件、进程内队列、SQLite 行、HTTP 请求。API 名字相似,底层介质可能完全不同。

调度器

目标空闲时启动 Run;目标繁忙时决定 steer、follow-up、合并、等待或中断。

上下文装配

加载目标 Session 的历史、目标 Agent 的提示词、工具权限,再把新消息加入模型输入。

关联返回

通过 requestId、taskId、contextId、父子关系或 replyTo,把结果交回正确请求者。

持久化

决定进程崩溃后消息、任务和完成事件能不能恢复,也决定是否会重复投递。

Universal flow

一次 Agent 通信的完整生命周期

点击“逐步播放”,观察一条“请审查认证模块”的消息,从 A 的模型输出一路走到 B 的模型,再返回 A。

Agent A Session
A Runtime / 通道
B Runtime / Session
Agent B 模型
A 的模型生成工具调用。例如 send(to=B, message=...)。模型没有自己建立连接。
A Runtime 截获工具调用。检查权限、查找 B 的地址,并生成 messageId/requestId。
把消息放入传输介质。可能是写 JSON、队列入队、SQLite INSERT,也可能是 HTTP POST。
B Runtime 收到事件。文件监听、队列消费者、Dispatcher 或 HTTP Handler 被触发。
B Runtime 取得 Session 执行权。同一 Session 通常只允许一个活跃 Run,避免历史并发写乱。
装配 B 的模型输入。系统提示词 + B 的历史 + Agent 间消息 + 工具定义。
真正调用 B 的模型。B 调用工具、读代码、运行测试,直到产生完成结果。
保存结果并关联原请求。使用 taskId、replyTo、父 Session ID 或 runId。
返回 A。同步工具结果、反向邮箱、Completion Event、Announce、Push 或轮询 GetTask。
A 再次被调度。A 的新 Run 读取 B 的结果,综合后回复用户;不是原来那次模型调用“突然恢复意识”。
Protocol boundary

A2A、MCP、ACP 到底分别管什么

三个协议经常一起出现,但服务对象不同。判断一个项目是否“使用 A2A”,不能仅凭文档出现了 agent-to-agent 字样。

协议/接口两端是谁解决的问题不负责什么
A2A ProtocolAgent Runtime ↔ Agent Runtime发现能力、发送 Message、跟踪 Task、接收 Artifact、流式/异步通知不规定内部如何启动模型、进程和工具
MCPAgent/模型宿主 ↔ 工具与资源服务列出和调用工具、读取资源、获取 Prompt不是完整的远程 Agent 任务生命周期
ACP / Codex App ServerIDE/Gateway/宿主 ↔ Coding Agent Harness创建会话、发送 Turn、流式接收事件、审批工具操作不是任意 Agent 之间的标准发现协议
内部 send API同一产品内的 Session ↔ Session依赖中央 Gateway 或本地文件快速协作通常不能让别的厂商直接接入

标准 A2A 的网络过程

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 不规定。
非常重要的分层:完全可以在 OpenClaw、Claude Code 或 Codex 外面增加一个 A2A Adapter。外部使用标准 A2A,Adapter 收到请求后再调用内部的 sessions_send、SendMessage 或编排器 API。
Pi ecosystem

Pi:三个项目,三套完全不同的发动机

Pi Subagents:父进程派工,子 Pi 返回结果

父 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 Messenger:共享目录就是消息总线

~/.pi/agent/messenger/
├── registry/
│   ├── GoldFalcon.json       ← 身份、PID、Session、心跳
│   └── BlueTiger.json
└── inbox/
    ├── GoldFalcon/           ← 只由 GoldFalcon 监听
    └── BlueTiger/            ← 只由 BlueTiger 监听

发送与唤醒的精确过程

  1. A 调用 pi_messenger({action:"send", to:"B"})。
  2. A Extension 把消息写成 inbox/B/时间戳-随机数.json。
  3. 操作系统向 B 进程注册的 fs.watch(inboxOfB) 发目录变化事件。
  4. B 的回调读取所有待处理 JSON,解析后删除消息文件。
  5. B Extension 调用自己的 pi.sendMessage(..., {triggerTurn:true, deliverAs:"steer"})。
  6. Pi Runtime 把消息写进 B Session;空闲则开启新 Turn,繁忙则尝试 steering。
不是所有 Session 都判断一次。每个 Pi 进程只 watch 自己的 inbox 目录;给 B 写文件时,正常情况下只有 B 的 watcher 收到事件。

不是标准 A2A 它没有服务器和标准 Task;优点是极简单,缺点是通常要求共享同一个文件系统。

Pi A2A:每个 Pi 都同时是 HTTP Client 和 Server

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 的互操作测试确认。

Claude Code

Claude Code:Subagent 与 Agent Team 不是一回事

Subagent

Main Claude
  │ Agent tool
  ▼
Child context
  │ final summary
  ▼
Main Claude

层级委派。子 Agent 只向调用者汇报,通常不直接与兄弟 Agent 通信。

Agent Teams

Lead ───── Teammate A
 │  ╲       ╱
 │   Mailbox
 │  ╱       ╲
 └────── Teammate B
      Shared Tasks

每个 teammate 是独立 Claude Code Session,可以按名字直接互发消息并共享任务表。

Agent Teams 的公开底层结构

~/.claude/teams/{team-name}/
├── config.json                  ← members、agent ID、session ID、运行信息
└── inboxes/
    ├── lead.json                ← lead 的邮箱
    ├── researcher.json          ← researcher 的邮箱
    └── reviewer.json

~/.claude/tasks/{team-name}/     ← 持久任务列表
  1. Lead 创建 teammate 时,Claude Runtime 建立独立 Session,并把成员写入 config.json。
  2. Teammate 加载自己的系统上下文、项目 CLAUDE.md、技能和 MCP;不会自动继承 Lead 的完整对话历史。
  3. SendMessage(to="reviewer") 根据成员名定位目标邮箱。
  4. 消息落到目标 JSON mailbox,Claude Runtime 自动投递给目标 Session。
  5. 目标的下一次 Run 把消息标记为“来自另一个 Claude Session”,而不是用户授权。
  6. 回复仍通过 Mailbox 反向发送;完成或空闲时也会通知 Lead。
公开边界:官方公开了 JSON Mailbox 与自动投递行为,但没有公开内部采用文件监听还是定时轮询。因此不能无证据地写成 fs.watch。

共享任务的“认领”使用文件锁避免两个 teammate 同时拿到一张任务卡;它只解决任务状态竞态,并不锁住源代码文件。

不是标准 A2A 这是 Claude Code 私有的本机 Team Mailbox 协议。

OpenClaw

OpenClaw:所有路由都经过 Gateway

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 的完整过程

Session A
Gateway
Session B Queue
B Model Run
A 模型生成 sessions_send(sessionKey=B) 工具调用。
Gateway 检查 A 对 B 的 visibility、工具策略和权限。
Gateway 通过 sessionKey 找到 B 的本地 Session;目标不是外部网络地址。
生成 isUser=false 的 inter-session message,保存来源信息。
消息进入 session:<key> 的 FIFO lane;同一 Session 最多一个活跃 Run。
B 空闲则启动 Run;B 繁忙则按 steer / followup / collect / interrupt 处理。
B Runtime 加载 B 历史和消息,真正调用模型。
若设置等待超时,回复成为 A 的工具结果;若 timeout=0,则 A 立即得到“已入队”。
配置允许时,Gateway 还能运行有次数上限的 A↔B reply-back loop。

sessions_spawn 与 Announce

Parent 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。

Hermes

Hermes:短任务用线程池,长期协作用数据库

delegate_task

Parent AIAgent
  │ submit
  ▼
ThreadPoolExecutor
  ├─ Child AIAgent 1
  ├─ Child AIAgent 2
  └─ Child AIAgent 3
        │ completion
        ▼
state.db → fresh-turn queue → Parent

子 Agent 有隔离上下文,只收到 goal/context。顶层先返回 handle;完成事件先持久化,再触发父 Session 的新回合。

Kanban

Orchestrator / Human
  │ INSERT task
  ▼
SQLite Kanban DB
  │ Dispatcher atomic claim
  ▼
Named Profile OS Process
  │ heartbeat / comment / complete
  ▼
SQLite events + downstream task

它是持久消息队列和状态机。每个 handoff 都是一行记录,适合重启恢复、人工介入和跨小时工作。

为什么 Kanban 的 Comment 也算通信

因为接收方不一定正在运行。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

Codex:公开 App Server 与团队编排器要分层看

公开的 Codex App Server

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发送新任务;若目标空闲则触发一个新 Turnqueue.offer + executor.submit
wait_agent挂起父 Agent 的等待,直到邮箱更新、子 Agent 完成、用户输入或超时CompletableFuture.anyOf(...).get(timeout)
公开边界:这些是当前运行时保证的工具语义;Mailbox 究竟落在内存、数据库还是远程控制服务中没有公开,不能凭空断言。

Codex App 的多 Agent 界面通常让不同 Agent 使用独立 Thread,并可用 Git Worktree 隔离代码修改;当前团队运行时则可能共享同一工作目录,这两种部署形态不能混为一谈。

不是标准 A2A App Server JSON-RPC 是客户端—Harness 协议;团队工具是内部 Orchestrator 协议。

Scheduling

目标 Agent 正忙时,会发生什么

系统忙时处理主请求是否等待关键风险
Pi MessengerdeliverAs: steer 尝试注入运行中的 Turn;否则由 Pi Runtime 后续处理发送工具很快返回Steering 改变目标当前计划
Pi A2AfollowUp 排到当前 Turn 后HTTP 先返回 Task需要 Push/GetTask 确保最终结果
Claude Teams消息留在 Mailbox,Runtime 自动投递发送方不必轮询公开文档未说明具体调度算法
OpenClawsteer / followup / collect / interrupt 四种策略可等待,也可 timeout=0错误的 interrupt 会丢掉当前工作
Hermes Delegation受 ThreadPool 并发上限控制,多余批次可能直接报错顶层先拿 handle父进程崩溃时运行中的子任务不能恢复
Hermes KanbanTask 留在 ready/running 状态,由 Dispatcher 和并发槽控制创建者 fire-and-forget必须正确 heartbeat 与 complete
Codex Orchestrator消息进入目标邮箱;followup 可触发空闲目标父可继续或 wait共享工作目录可能产生写冲突
Reliability

消息送达、进程崩溃与重复执行

真正成熟的 Agent 协作,不只要“能发消息”,还要回答:写成功但没处理怎么办?处理完成但回执丢了怎么办?重启后会不会重复执行?

至少一次投递

消息可能重试,因此接收方可能看到两次。需要 idempotency key、taskId 或去重表。

至多一次投递

处理后立即删消息可以避免重复,但进程在“删除后、执行前”崩溃时可能永久丢失。

完成事件持久化

Hermes 先把 completion 写进 state.db,再投 fresh-turn,可恢复“已完成但尚未交付”的事件。

Task 状态查询

A2A/pi-a2a 使用 Task + Push;Push 丢失后再 GetTask,把通知通道和真相来源分离。

Pi Messenger 的可靠性边界
消息 JSON 在目标读取前是持久的;读取、解析并交给 deliverFn 后会删除。解析或投递异常时源码也会删除,避免无限重试。因此它偏向简单本机协作,不是严格消息队列。
OpenClaw Announce 为什么需要幂等
子任务完成、父 Session 繁忙、Gateway 重启、频道投递失败都可能跨越多个状态。完成事件需要携带子 Session、运行状态和幂等标识,才能避免同一个结果多次唤醒父 Agent。
Hermes Kanban 为什么更适合长期任务
任务、尝试、PID、heartbeat、comment、result 都是数据库记录。Dispatcher 能发现 worker PID 消失、回收 stale claim,再交给另一个 Worker;代价是实现复杂度和调度延迟更高。
Code conflicts

多个 Agent 同时修改一个文件,通信系统不会自动救你

任务消息没有等于代码隔离。即便 Agent 能完美通信,只要它们写同一工作目录,最后写入者仍可能覆盖前一个 Agent 的修改。
系统默认/典型工作区冲突机制建议
Pi Messenger共享目录reserve 是合作式约定,不是 OS 强制锁按目录分工;修改前 reserve;最终统一 review
Claude Teams共享项目目录任务 claim 有文件锁,代码文件没有;官方警告会覆盖每人拥有不同文件集合
OpenClaw取决于 spawn 的 cwd可对可见 Session 开 managed worktree编码 Agent 使用独立 worktree
Hermes Kanbanscratch、共享 dir 或 worktreeworktree 类型隔离 Git 工作区工程流水线优先 worktree
Codex App内置 Worktree 支持Agent 各自在隔离副本工作审查后选择性合并
当前 Codex 团队运行时可能共享工作目录没有自动代码文件锁父 Agent 明确分配文件所有权
Final comparison

底层实现总表

系统执行单位发现/寻址消息介质唤醒目标结果返回A2A
Pi Subagents子 Pi 进程父创建stdout / 文件RunnerTool Result / 通知否
Pi Messenger独立 Pi Session/进程registry JSONinbox JSONfs.watch → pi.sendMessage反向邮箱否
Pi A2APi + HTTP ListenermDNS + Agent CardHTTP/JSON-RPCsendUserMessage(followUp)Push / GetTask子集路线
Claude Subagent子 Context父创建内部任务调用Claude Runtime最终摘要否
Claude Teams独立 Sessionteam membersJSON MailboxClaude Runtime反向 Mailbox否
OpenClaw send同 Gateway Session RunSession Registry进程内 lane 队列Gateway Scheduler内联或异步否
OpenClaw spawnChild Session Run父子树subagent laneGateway SchedulerAnnounce否
Hermes delegateChild AIAgent父创建Future + state.dbThreadPool / fresh-turn父 Session 新消息否
Hermes KanbanWorker OS ProcessProfile/assigneeSQLite 行DispatcherResult/Comment/下游任务否
CodexAgent Thread/ContextOrchestrator Tree内部 MailboxOrchestratorMailbox/Final Event否
标准 A2A任意远程 RuntimeAgent Card/RegistryHTTP/RPC/gRPC接收方自行实现Task/Artifact/Stream/Push是

按架构而不是项目名来记

父子函数调用
Pi Subagents · Claude Subagents · Hermes delegate_task
共享文件邮箱
Pi Messenger · Claude Code Agent Teams
中央编排器队列
OpenClaw Gateway · Codex Orchestrator
持久工作队列
Hermes Kanban
标准网络协议
A2A Protocol · 面向 A2A 的 pi-a2a Adapter

该怎么选

同一台机器,几个 Pi 互相聊天

优先 Pi Messenger。它的文件邮箱结构最容易观察、调试和接入 Web UI。

主 Agent 派几个短任务并汇总

使用 Subagent / delegate_task。它像并行函数调用,认知成本最低。

跨小时、可恢复、有人类审批

选择数据库任务板/工作流,例如 Hermes Kanban,而不是只靠实时消息。

跨机器、跨框架、跨厂商

在各 Runtime 外增加 A2A Adapter;内部继续使用产品原生 Session API。

Primary sources

资料来源与事实边界

页面明确区分三类内容:官方文档确认的行为、开源代码直接验证的实现、没有公开而必须保留未知的内部细节。尤其没有把 Claude Mailbox 推断成 fs.watch,也没有猜测 Codex Mailbox 的具体存储介质。