源码分析基于 OpenAI Codex 提交
d1e3f9dfe3d5f6105b7e4c3958b9bf2ac6c32818,不代表所有版本或某个桌面安装包。A2A 以本文核对的 1.0.0 规范为准。图中角色和任务均为通用示例;架构建议没有经过生产或互通测试。
给几个 Agent 分别起名“架构师”“开发”“测试”,再让它们互相发消息,就能协作了吗?
真正需要回答的是:实例由谁创建?名字在哪里登记?消息发到哪里?等待期间程序在做什么?接收失败以后,谁知道任务没完成?
理解这些问题,也就能分清 Codex 的内部协作、A2A 协议和官方 SDK 各自负责什么。
1. 先分清:协议、SDK 与协作运行时
| 层次 | 主要职责 | 本文中的例子 |
|---|---|---|
| 通信协议 | 约定独立系统交换什么信息、如何交互 | A2A |
| 协议 SDK | 用代码实现客户端、服务端和传输适配 | A2A Java SDK |
| 协作运行时 | 创建或调用 Agent,组织上下文、消息和执行 | Codex 内部协作、各类编排框架 |
A2A 规定独立 Agent 系统之间的交互契约。Codex 的 collaboration 则是本文所分析的一套内部协作机制。“Codex 有具体实现”不等于“Codex 实现了 A2A”。 前面这条内部链路使用自身的注册与消息机制。A2A 规范、Codex 协作控制源码
类似地,A2A SDK 可以替应用处理协议通信,具体任务如何完成仍要由应用实现。它并不要求内部必须采用某一种 Agent 框架。
2. Agent 地址从哪里来?运行时维护路径与实例
角色配置与运行实例要分开看。“测试角色”描述职责和配置;一次真正创建出来的测试 Agent,还需要自己的运行上下文、标识和状态。配置一个角色,不等于已经启动了一个常驻服务。Codex 子 Agent 配置与编排
在本文的源码快照中,同一根任务树共享协作控制与注册信息。调用方指定子任务名,运行时结合父路径构造完整路径,并在创建过程中登记实例。
例如:
- 当前调用方是
/root,子任务名为testing,得到/root/testing。 - 当前调用方是
/root/architect,同名子任务得到/root/architect/testing。
这些是运行时中的逻辑地址,不是文件夹,也不是 HTTP 地址。注册表维护路径、实例元数据及反向查询关系。路径构造、Agent 注册表
下面是据固定源码快照整理的简化逻辑图,展示创建操作涉及的对象,不展开内部执行顺序和错误分支:
所以,“架构师怎么知道测试 Agent 的地址”有具体的信息来源:创建工具的返回结果,以及查询已有实例的工具。并不是模型凭角色名称猜出一个地址。这一版本的 spawn 返回结果包含规范化后的 task_name。创建工具实现
3. target 负责路由,message 提供任务内容
下面是一次消息工具调用的参数示意,假设目标实例已经存在:
{
"target": "/root/testing",
"message": "只读检查订单分页的边界行为,返回失败场景和依据;不要修改代码。"
}
target 让运行时找到接收者;message 是提供给接收 Agent 的内容。示例中的限制属于任务约定,实际文件和工具访问仍受运行环境约束。
消息投递成功,不代表测试已经执行。接收者需要结合上下文理解任务、请求工具执行,再根据真实结果作出判断。任务约定不能单独改变运行环境权限。
常用协作操作的区别可以这样理解:
| 操作 | 作用 |
|---|---|
spawn_agent | 创建一个新的子 Agent |
send_message | 向已有实例投递消息 |
followup_task | 继续分配任务,并可让空闲实例开始新的执行轮次 |
wait_agent | 等待消息或状态变化,不等于指定任务必然成功 |
运行中的 Agent 会在合适的处理边界接收消息;不能把“已投递”理解为“立刻打断所有正在执行的工具”。消息工具实现
事件驱动,为什么源码里仍然能看到循环?
接收循环负责反复处理事件;是否轮询,要看没有事件时怎样等待。
在这条源码链路中,操作通过异步通道交给会话处理器,消息写入 mailbox 队列,再通过 watch 通知状态变化。等待端使用异步等待和超时机制。会话事件处理、邮箱与通知、等待工具
下面是据固定源码快照整理的简化逻辑图,展示消息与通知的关系,不表示真实线程拓扑:
队列保存消息,通知帮助等待者知道发生了变化,两者职责不同。没有消息时,等待端不持续主动检查队列,而是等待变化通知或超时等条件。
Java 开发者可以类比 BlockingQueue.take():队列为空时等待元素,而不是循环调用 isEmpty()。但它与 Rust 的异步任务等待并非同一种线程模型,也不能因此推断“每个 Agent 对应一个独占线程”。Java BlockingQueue
4. 接收失败与执行失败,需要分别处理
“在线程里 catch 住异常”只能说明异常得到了某种处理,还要继续确认任务状态是否正确、调用方是否获知结果。
| 发生的情况 | 应当确认什么 |
|---|---|
| 目标不存在或投递失败 | 调用方收到错误了吗,任务是否真的交出? |
| 接收通道关闭 | 等待是否结束,实例是否仍可运行? |
| 工具或业务执行失败 | 是否形成失败结果,下一步由谁决定? |
| 提交中断或取消 | 执行是否已经停止,已有副作用是什么? |
在本文快照中,可以看到投递错误的处理和终态通知路径;但不能据此宣称所有失败都会自动重试、所有实例都会自动重启。通知本身也不等于一套持久化、保证重投的消息系统。协作错误处理、完成通知
中断同样要看实际状态:发出了中断请求,不足以证明正在进行的操作已经全部停止。中断处理
5. 换成 A2A 后,通信边界发生了变化
假设一个 Java 服务负责需求分析,另一个 Python 服务负责代码检查。双方希望交换任务和结果,同时各自保留内部实现,就进入了 A2A 解决的问题域。
下面是根据协议整理的边界示意。两个服务可以部署在同一台机器,也可以独立部署:
A2A 的几个核心对象是:
| 对象 | 用途 |
|---|---|
| Agent Card | 描述能力、服务接口和认证要求 |
| Message | 一次交流,承载文本、文件引用或结构化数据等内容 |
| Task | 有标识、有状态、可跟踪的工作单元 |
| Artifact | 任务产生的报告、文件或结构化结果 |
| contextId | 关联一组相关交互,不等于双方共享模型上下文 |
发送消息后,服务端可以直接返回 Message,也可以返回 Task。对端内部用了几个 Agent、怎样选模型,属于对端的实现。A2A 核心概念、任务生命周期
A2A 有没有注册中心?
A2A 标准化 Agent Card。调用方可以通过已知地址、配置、目录服务,或约定路径 /.well-known/agent-card.json 获取卡片。目录服务可以集中管理卡片,但当前规范没有规定统一的目录 API。
这与 Codex 在创建实例时登记本地路径不同。A2A 调用方仍需先知道一个域名、卡片地址或可查询的目录。Agent 发现机制
A2A 是轮询还是事件驱动?
协议支持多种更新方式:查询任务状态;在服务支持时接收流式更新;或者配置支持的 Webhook 推送。JSON-RPC 和 HTTP+JSON 绑定的流式更新使用 SSE,gRPC 则使用服务端流式 RPC。异步与流式交互、gRPC 流式规范
这些方式都不规定服务内部必须使用什么线程池或队列。协议中的取消是请求取消,也不能直接推导出业务已回滚。取消语义
6. 放在一张表里,区别更清楚
| 维度 | Codex 本文分析的内部协作 | A2A |
|---|---|---|
| 面向对象 | 同一根任务树中的实例 | 独立 Agent 系统 |
| 定位方式 | 任务路径及实例注册信息 | Agent Card 中的服务接口 |
| 工作表达 | 协作工具参数、任务上下文 | 标准化消息、任务和产物 |
| 运行责任 | Codex 的运行时与执行配置 | 各服务自行实现 |
| 更新机制 | 异步通道、邮箱、变化通知 | 查询、流式或推送 |
| 互通条件 | 遵守该运行时的内部机制 | 双方支持兼容的协议与能力 |
因此,不能给 Codex 套上一个 HTTP 接口,就认为它已经支持 A2A。若做适配,还需要处理消息转换、任务状态、结果与错误映射,以及身份和权限边界。这是集成设计要求,本文没有实现这样的适配器。
同时,内部协作使用本地机制,也不意味着 Codex 只能通过界面使用。它另有 SDK 和 App Server 等程序化集成入口。Codex SDK、App Server
7. 官方 Java SDK 能用于企业服务吗?
可以。官方 A2A Java SDK 已提供客户端和服务端支持,包含 JSON-RPC、gRPC、REST 传输,要求 Java 17+,采用 Apache-2.0 许可。协议规范版本与 SDK 发布版本需要分别核对。A2A Java SDK
一个初期企业 Agent 服务可以采用下面的建议架构。它不是 SDK 内部类图,也不表示图中能力安装依赖后就自动就绪:
SDK 负责协议接入。应用负责定义能力、执行业务和产生结果;生产运行还需要结合已有设施解决:
- 身份与资源权限:认证调用者,并校验其对具体任务、数据和动作的权限。
- 任务状态与恢复:保存必要状态,明确服务重启、执行超时和多实例之间如何协调。
- 执行与重试边界:限制并发和等待时间;有写入副作用的业务必须考虑幂等,避免重试造成重复操作。
- 运行证据:关联任务标识、业务请求和追踪信息,能够区分“请求已收到”“任务已执行”“结果已确认”。
这些是企业接入职责,不是 A2A 单独替应用完成的功能。官方企业指南也强调与既有身份、授权、网关和可观测性体系结合。A2A 企业接入指南
例如,一个“文档分析 Agent”可以先由普通 Java 代码接收任务、读取调用者有权访问的文档、调用模型并返回报告。只有内部步骤和角色变复杂时,才需要进一步评估编排框架。
8. 还有哪些开源实现值得看?
以下是不同侧重点的源码入口,不是性能或成熟度排名:
| 项目 | 适合观察的实现 | 许可 |
|---|---|---|
| Deep Agents | 子 Agent 委派、上下文隔离、文件和工具能力组合 | MIT |
| Google ADK Java | Java Agent 层级与执行编排,可接入 A2A;与协议层的 A2A Java SDK 是不同项目 | Apache-2.0 |
| LangGraph | 有状态执行图、检查点、持久化与人工介入 | MIT |
| CrewAI | 角色、任务、协作团队与事件流程 | MIT |
| Microsoft Agent Framework | 顺序、并发、任务移交和群组编排 | MIT |
这些是可复用的实现,但没有义务采用 Codex 相同的注册表或邮箱模型。Microsoft 官方把 Agent Framework 定位为 AutoGen 与 Semantic Kernel 的直接继任者,阅读旧教程时应注意项目演进。官方定位
另两个常见协议也需要放对位置:MCP 主要连接 AI 应用与工具、资源;Agent Client Protocol 主要连接客户端或编辑器与编码 Agent。MCP 服务能力、Agent Client Protocol
对 Java 开发者,一条便于理解的阅读路线是:先通过 ADK Java 看任务如何在服务内部执行,再通过 A2A Java SDK 看这些能力如何对外提供。若重点研究子 Agent 的上下文隔离与委派,可以补看 Deep Agents。
建设系统时也可以采用同样的分层:先确定任务如何可靠完成,再确定哪些能力需要跨服务互通。内部编排与对外协议各自清楚,后续更换模型、框架或部署方式时,边界才容易保持稳定。