2026年6月,balaji bal发表的《A2A Is a Nonsense Protocol》把A2A批得很直接:没有把消息总线当成一等公民,导致智能体强耦合、缺少背压(下游处理不过来时,无法协调上游减速、暂停或削峰)、可观测性碎片化,而且消息无法回放。

图:原作者对“直接A2A”与“消息流优先”架构的观点示意,图片来自原文。这里不代表本文已接受其结论。
这些批评并非全无道理,但它们混合了三个不同层次的问题:协议语义、传输模型,以及生产环境的治理能力。要判断A2A到底是不是“胡扯协议”,不能只看架构观点,还要看协议为什么这样演进,以及真实报文经过网关时到底发生了什么。
本文先讲清A2A解决的问题,再追溯两个容易被误读的破坏性改动:Agent Card为什么删除根层url,以及tasks/send为什么改成message/send。最后用Wireshark抓包验证一次真实的双Agent委托链路。
A2A不是另一个MCP
MCP解决的是智能体的“垂直调用”:一个Agent怎样访问工具、数据库和API。A2A解决的是“水平协作”:当一个Agent没有权限、知识或职责边界去完成整件事时,它怎样发现另一个Agent、了解其能力,并把任务委托出去。
二者可以同时出现在一套系统中:上层Agent通过A2A发现和委托另一个Agent;每个Agent再通过MCP访问自己的工具、数据库和API。二者分工不同,不是互相替代。

没有A2A时,不同框架通常只能互相硬编码私有接口。A2A尝试把发现、消息、任务状态、流式更新和交付物抽象为框架无关的协议契约。
A2A 1.0规范把协议拆成三层:最上层是所有实现共同理解的数据模型;中间是与传输无关的抽象操作;底层才是JSON-RPC、gRPC、HTTP+JSON/REST以及自定义协议绑定。这样,SendMessage等操作的语义不必随底层传输变化,新增协议绑定时也不需要重写Task、Message、Artifact等核心对象。
graph TB
subgraph L1 ["A2A Data Model"]
direction LR
A[Task] ~~~ B[Message] ~~~ C[AgentCard] ~~~ D[Part] ~~~ E[Artifact] ~~~ F[Extension]
end
subgraph L2 ["A2A Operations"]
direction LR
G[Send Message] ~~~ H[Send Streaming Message] ~~~ I[Get Task] ~~~ J[List Tasks] ~~~ K[Cancel Task] ~~~ L[Get Agent Card]
end
subgraph L3 ["Protocol Bindings"]
direction LR
M[JSON-RPC Methods] ~~~ N[gRPC RPCs] ~~~ O[HTTP/REST Endpoints] ~~~ P[Custom Bindings]
end
L1 --> L2
L2 --> L3
style A fill:#e1f5fe
style B fill:#e1f5fe
style C fill:#e1f5fe
style D fill:#e1f5fe
style E fill:#e1f5fe
style F fill:#e1f5fe
style G fill:#f3e5f5
style H fill:#f3e5f5
style I fill:#f3e5f5
style J fill:#f3e5f5
style K fill:#f3e5f5
style L fill:#f3e5f5
style M fill:#e8f5e8
style N fill:#e8f5e8
style O fill:#e8f5e8
style L1 fill:#f0f8ff,stroke:#333,stroke-width:2px
style L2 fill:#faf0ff,stroke:#333,stroke-width:2px
style L3 fill:#f0fff0,stroke:#333,stroke-width:2px
图:A2A 1.0规范的三层结构。数据模型和抽象操作保持稳定,各协议绑定负责提供具体的方法名、端点和传输行为。
时间线上有几个关键节点:Google于2025年4月9日发布A2A;Linux Foundation于2025年6月23日接收并托管该项目;A2A v1.0.0则在2026年3月12日发布。

A2A 1.0为什么重构Agent Card
每个A2A Agent都可以在/.well-known/agent-card.json暴露Agent Card。它不仅描述名字和技能,也告诉客户端“应该到哪里、用什么协议、按哪个版本访问我”。
A2A 1.0对这张”名片”做了不向前兼容的重构。最显眼的变化,是删除根层url、preferredTransport、additionalInterfaces这三个字段,并把protocolVersion从根层迁移到每个接口项,统一改成有序的supportedInterfaces[]:
A2A 1.0中的核心结构是一个有序数组:
1 | { |
第一个接口是首选项。一个Agent若同时支持不同传输或不同协议版本,可以发布多个接口项。url没有消失,只是从Card根层移进了每个接口项。
为什么删除根层url
早期Card把”首选接口”和”其他接口”拆成两种结构:根层url配合preferredTransport描述首选接口,additionalInterfaces[]再描述其余接口。客户端既要读两处数据,还要理解它们之间的优先级;新增协议绑定时也容易出现重复或冲突。
这两套结构要怎么合并才不至于越改越乱?1.0把它们合并为一个有序数组:supportedInterfaces[0]就是首选接口,后续元素是备选接口。所有接口都采用同一种AgentInterface结构,地址、协议绑定和版本放在一起,不再需要根层字段表达特例。这是删除url最直接的原因。
具体过程如下:2025年11月20号合入的PR #1160先把旧字段标记为弃用,对应commit可以直接看到三个根层字段的弃用标记;随后11月24号的Issue #1227提出趁1.0清理旧字段,2026年1月7号的PR #1301完成删除并保留旧字段号、防止被重新使用。它是数据模型去重和协议绑定重构的结果,不能单独归因于Issue #1104(这里提出了url表达上存在的问题)。
为什么协议版本也放进每个接口
生产环境中常见的问题是升级节奏:服务端可能已经支持新版本,但不可能让所有客户端同一时间升级。那如果Card根层只能写一个protocolVersion会怎样?服务端切换版本后,旧客户端可能直接失联。
Issue #1104公开提出了这个多版本兼容问题。社区讨论过以下几种方案:
| 方案 | 为什么没有成为最终方案 |
|---|---|
根层保留protocolVersion,再增加版本数组 |
两个字段会产生“以谁为准”和如何同步的问题 |
直接把根层protocolVersion改成数组 |
会立刻破坏旧Card的结构兼容性 |
| 用不同子域名区分版本 | 需要额外部署和DNS管理,而且Card仍无法完整表达能力 |
把版本放进每个AgentInterface |
同时说明“这个地址、这种绑定支持哪个版本”,语义最完整 |
经过根层版本数组的过渡实现PR #1259以及兼容性讨论#1416后,PR #1401最终选择了最后一种;最终数据结构中,每个AgentInterface声明一个必填的protocolVersion。如果同一个Agent同时支持0.3和1.0,就发布两个接口项,可以使用相同或不同URL。客户端先从Card选择合适的接口,请求时再通过A2A-Version明确所用版本。服务端不支持时返回VersionNotSupportedError。
因此,supportedInterfaces[]是一张明确的兼容性清单:每一项完整回答“去哪个地址、用哪种协议绑定、按哪个A2A版本访问”。
为什么1.0可以接受不向前兼容
这些字段在1.0中直接删除,意味着只认识旧字段的0.3客户端不能读懂新Card。A2A能够接受这一代价,一方面因为1.0是允许清理历史设计的主版本升级,另一方面因为继续保留两套表达会让每个后续版本都背负歧义。
那些还没升级的0.3客户端怎么办?过渡期仍需要实现层双栈:例如同时支持0.3与1.0版本、根据A2A-Version分发请求,或者给旧客户端提供兼容Card。协议本身只是把版本边界和协商方式明确化了。
相关改动经过公开的问题与设计讨论、代码评审逐步落地。PR #1160的投票结果与PR #1401的投票结果都可直接查阅,后者还保留了代码审查批准记录。这里体现开放治理的不是某位提案人的身份背景,而是设计理由、替代方案、审查和表决结果都可追溯。
Signed Agent Card为什么加入JWS
Agent Card挂在公开URL上,任何客户端都能读取;同样地,如果缺少完整性与来源校验,攻击者也可能篡改Card,替换接口地址、能力或安全要求,把请求引向错误的Agent。A2A 1.0因此加入Signed Agent Card,并按签名规范使用JWS为Card签名。

生成签名时,签名方先移除Card里的signatures字段,再按照JCS(RFC 8785)对JSON做确定性规范化,最后生成JWS签名。验证方使用可信公钥验证签名,就能判断Card内容签发后是否被改动,以及签名者是否持有对应私钥。
但JWS不能证明Card里的自我描述是真的,也不能证明Agent的输出可靠。它证明的是“这份Card由谁签过、签后有没有被改”,信任仍取决于公钥来源和组织的信任策略。
tasks/send为什么改成message/send
这里先澄清版本:tasks/send -> message/send不是A2A 1.0才发生的,而是2025年5月、v0.1向v0.2演进时的改动。
这项改动最早出现在Phil Stephens(GitHub账号pstephengoogle)的提交4921066,由Issue #432跟踪设计,再通过PR #415“Protocol updates: Support stateless transactions”实现。PR于2025年5月7日合入next分支,随后经PR #599进入v0.2.0。

资源所有权的改变
旧的tasks/send默认每次交互都围绕Task展开,客户端需要预先提供Task ID。这对长任务合理,对一句问答却很别扭:Agent即使只想直接返回一条Message,也被迫创建Task。
PR #415把模型改成“消息优先”:
- 消息创建者生成
messageId; - 客户端发送Message,而不是预先创建Task;
- 服务端判断是否需要持久任务;
- 需要追踪时由服务端创建并拥有
taskId; - 不需要状态管理时可以直接返回Message;
sessionId同步重构为服务端创建的contextId;- 流式入口相应从
tasks/sendSubscribe迁到message/stream。
所以方法名换成message/send是在表达:输入对象始终是Message,Task只是服务端可能创建的处理结果。
依旧不向前兼容
对照v0.1规范与v0.2规范,可以看到它不保证新旧版本之间的请求与响应格式兼容性。
PR #415合入时,旧方法曾短暂保留并标记为弃用,给实现者一个迁移窗口;随后提交41437f7从示例实现中移除了旧方法,PR #459继续同步规范、Schema和测试,最终v0.2.0只保留新方法。更麻烦的是,变化不仅是路由名,还包括Task ID归属和返回类型。服务端即便给旧方法设置别名,也不能自动消除所有语义差异。
这类破坏性修改之所以被接受,主要有两个背景:其一,项目当时仍处于0.x草案阶段;其二,新模型解决了真实的无状态交互问题,而不是纯命名偏好。
到1.0版本,A2A把核心操作与具体传输绑定分开(感受下当下的AI相关领域迭代有多快吧),命名又发生变化:
| 阶段 | JSON-RPC/核心操作 | HTTP+JSON映射 |
|---|---|---|
| v0.1 | tasks/send |
当时未形成1.0式映射 |
| v0.2 / v0.3 | message/send |
以JSON-RPC为主 |
| v1.0 | SendMessage |
POST /message:send |
根据1.0迁移说明和方法映射表,当前1.0规范的JSON-RPC方法是SendMessage,gRPC方法也是SendMessage,REST绑定则是POST /message:send。统一的是抽象操作语义,各传输绑定负责把它映射到自己的报文格式。
A2A真正提供了什么
前面的字段变化不是目的,A2A要提供的核心价值仍然是下面四项:
- 框架无关的互操作契约:双方不必使用同一种Agent框架,只要实现相同版本和绑定,就能发现能力并交换消息。
- 任务生命周期管理:除了即时Message,还可以创建可查询、可订阅、可取消并具有终态的Task,承载长耗时和人工介入流程。
- 对等委托:Agent可以根据能力边界自主寻找另一个Agent,不必把对方降格为本地工具。
- 与MCP互补:MCP解决“怎样调用工具、数据库和API”,A2A解决“应该找哪个Agent协作”。
网关不是A2A运行的强制依赖,两个Agent可以点对点通信。但进入规模化环境后,Agent发现、路由、凭证、策略和观测通常需要一个集中治理角色,这才是A2A网关或者说是AI网关的价值。
真实双Agent抓包验证
实验拓扑包含一个协调Agent和一个数据分析Agent,二者都位于agentgateway之后。客户端先经3000端口访问协调Agent;协调Agent收到任务后,再从1176端口建立一条独立TCP连接,经3001端口把分析任务委托给数据分析Agent:
注意:这次实验仅验证网关转发、Agent Card处理和双Agent委托链路。
下面Wireshark中的HTTP请求/响应总览。前八条是两个Agent Card分别经过网关的请求与响应;随后是四段POST /message/send,其中帧44开始的是协调Agent自主发起的第二条连接。

关键帧如下:
| 帧号 | 相对时间 | 源端口 -> 目标端口 | 内容 |
|---|---|---|---|
| 13 | 0.027537s | 9998 -> 1172 | Orchestrator原始Agent Card |
| 15 | 0.027972s | 3000 -> 1171 | 网关转发后的Orchestrator Card |
| 29 | 0.059025s | 9999 -> 1174 | Data Analyst原始Agent Card |
| 31 | 0.059171s | 3001 -> 1173 | 网关转发后的Data Analyst Card |
| 35 | 0.059879s | 1171 -> 3000 | 客户端发给协调Agent |
| 37 | 0.060099s | 1172 -> 9998 | 网关转发给协调Agent |
| 44 | 0.285413s | 1176 -> 3001 | 协调Agent建立新连接并委托 |
| 46 | 0.285535s | 1174 -> 9999 | 网关转发给分析Agent |
| 115 | 89.367648s | 1171 -> 3000 | 兼容性测试:/tasks/send |
| 117 | 89.367830s | 1172 -> 9998 | 网关继续转发 |
Message与Artifact的差异
其实可以看到Agent的响应同时包含message和artifacts:
1 | { |
message是对话中的即时回复,artifacts是任务生成的正式交付物,例如报告、文件。二者并行存在,不能把响应里的所有文本都当成Artifact。
这次返回的“14%环比增长”“电子产品增长22%”并非真实经营数据。测试Agent没有联网,也没有连接真实数据库,这些数字是模型生成的。实验验证的是CrewAI确实执行了模型调用,以及A2A委托链路真实发生;它没有验证报告内容的事实性。
网关修改了什么
网关转发Agent Card时到底动没动内容?逐字节比较响应后发现,JSON Body只差一个字符:
1 | - "url": "http://127.0.0.1:3001" |
同样的尾斜杠归一化也出现在3000端口的Card中。这表明网关至少对Agent Card做了结构化解析与重新序列化,而不是逐字节盲转发。
另一方面,本次抓包中message/send请求和响应的Body经过网关后完全一致,HTTP响应头也未见网关注入自己的server或链路追踪字段。就这组样本而言,agentgateway对Card做轻量协议感知,对业务消息保持透传。
这次拓扑也有明确边界:配置文件中每个网关端口只对应一个固定backend,网关根本不需要在多个候选Agent之间做技能路由或负载均衡。因此,本次实验不能用来证明agentgateway的动态路由、限流或熔断能力。
agentgateway官方资料还把以下能力定位为网关层职责,但它们没有在本次抓包中验证:
- 凭证代理:真实凭证由网关在转发时注入,避免Agent长期持有密钥。
- 策略执行与急停:在多Agent间的异常调用扩散时集中执行访问策略,必要时切断链路。
- 统一可观测性:汇总原本分散的Agent间调用、指标和追踪数据。
回看开头文章的四条批评
原文的四项批评可以分成两组来看:前两项讨论点对点调用的耦合与流量控制,后两项讨论跨节点观测与历史留存。
“没有消息总线,所以强耦合”
这是架构偏好,不是充分的协议缺陷证明。A2A选择点对点委托,HTTP和gRPC同样如此。消息总线适合需要持久日志、消费组和重放的场景,但它也会引入Broker治理、消息顺序和投递语义等问题。
“没有背压”
背压是指:当下游的消费速度低于上游的生产速度时,下游能够通过一套协调机制让上游减速、暂停、排队或丢弃部分负载,避免请求持续堆积直至内存、连接池或线程池耗尽。超时只是“等不下去了”,重试甚至可能放大拥塞,它们都不等于背压。
这项批评有一部分合理,但不能只看单次HTTP连接。A2A通过异步Task、查询、订阅和Push Notification减少调用方持续占用连接等待结果的压力,缓解了同步等待耦合。
“可观测性碎片化”
点对点调用确实不像消息总线那样天然经过一个中心关口。但这通常由网关、Service Mesh、OpenTelemetry和策略执行点补齐,不一定硬塞进应用协议本身。
“消息无法回放”
这条批评容易被误读,需要先排除一种不成立的理解方式,再谈真正站得住的部分。
先说清楚”回放”不是什么:如果”回放”指的是”把同一条请求重新丢给Agent,期待它重新生成一模一样的内容”,这个诉求本身在生成式AI场景下就不成立——LLM的输出是概率采样的结果,同一个问题问两次,措辞、数字、结论都可能不一样。这不是A2A协议的缺陷,是生成式模型的本质决定的,换成任何协议都解决不了,除非在应用层做语义缓存把结果锁死(而语义缓存本身要解决”两条措辞不同但语义相同的请求算不算同一件事”,是个更难的问题,跟协议设计没关系)。所以如果批评的靶子是”A2A没法保证重放出相同内容”,这是个无效指控,不该算在协议头上。
真正值得讨论的,是”回放”这个词底下压着的另外两件事,它们都跟”内容是否可重新生成”无关:
第一件事:安全重试。客户端发了一条消息,网络抖动导致不确定服务端有没有收到、有没有处理完,能不能放心再发一次,而不用担心产生第二次真实副作用(比如再触发一次计费、再生成一个重复Task)。A2A规范提到messageId可以用于这个场景:”Send Message operations MAY be idempotent. Agents may utilize the messageId to detect duplicate messages.”——工作原理不是”重新算一遍指望算出一样的结果”,而是”记住第一次算出来的结果,第二次看到同一个messageId直接把存好的结果原样吐回去,根本不会再调用一次LLM”。这跟LLM的不确定性完全无关,是个纯粹的缓存查找问题。
但这个安全网是有有效期的:服务端不可能永久保留每一个messageId对应的结果,这几乎肯定是个有TTL的短期缓存(几分钟到几小时量级),不然等于自己维护了一份持久化信息。在去重窗口内,重放同一个messageId能拿回原来那个结果,跟LLM的非确定性无关;过了去重窗口,服务端已经不再识别这个messageId了,会当成一条全新消息,真的重新调用一次LLM——这时候两次结果确实会不一样,”回放”这个词在这里已经名不副实。而且这个机制还只是MAY,不是MUST,协议没有强制要求任何Agent实现它。
第二件事:审计留痕。这跟”以后能不能重新生成同样内容”完全无关,要求的是”当时到底发生了什么、系统当时确实产出过什么内容,事后能不能查”——不管这个内容是不是能被同一个模型再生成一遍,它作为历史记录的价值不受影响。这才是原文批评真正对准的目标:A2A定义了Message、Task、Artifact的交换语义,但没有承诺一份可以长期查询、支持多消费者独立回放的持久化事件日志。这一层能力,Kafka这类消息总线协议是把它做成协议核心(分区日志+offset,本来就是为持久化和重放设计的),FIX协议是在协议层显式定义了Resend Request和序列号补发机制(金融交易的合规要求逼出来的)。A2A没有这么做,这一点和HTTP、gRPC等大多数RPC式协议保持一致,不算特别异常,但如果业务确实需要审计重演或事件溯源,仍然要在A2A之外自己引入持久化系统。
所以更精确的结论是:A2A能做到“短期内安全重试”,做不到“长期审计回放”——这条批评说的是这一层,跟”LLM是概率生成的”这件事不矛盾,也没有被生成式模型的不确定性反驳掉。
结论
A2A不是消息总线,也不试图成为消息总线。它解决的是独立Agent之间的发现、消息交换、任务管理和结果交付。批评它时,应把“协议没有定义”“协议通过异步模型部分缓解”和“生产治理层应承担”这三种情况分开。后续我们再来分析真实的A2A网关到底怎样在生产治理层中解决这些问题的。
参考资料
- Google:Announcing the Agent2Agent Protocol,2025-04-09
- Linux Foundation:Launches the Agent2Agent Protocol Project,2025-06-23
- A2A v1.0.0 Release
- A2A 1.0官方迁移说明
- A2A 1.0规范
- A2A Issue #997:WebRTC / WebTransport实时媒体绑定提案
- A2A PR #1975:SendLiveMessage与实时媒体数据模型提案
- A2A Issue #1995:双向流与流语义候选议题
- W3C:WebRTC
- W3C:WebTransport
- A2A 1.0规范:Agent Card Signing
- Issue #1104:Advertising support of multiple protocol versions
- PR #1160:协议层与传输绑定重构
- Issue #1227:删除1.0已废弃的Agent Card字段
- PR #1301:删除根层
url等字段 - Discussion #1416:A2A backward compatibility
- PR #1401:接口级协议版本与向后兼容
- PR #415:Support stateless transactions
- agentgateway:A2A路由文档
- agentgateway:可观测性文档
- agentgateway:Credential Injection Patterns
- agentgateway:Kill Switch
- 本文抓包证据来自本地实测文件
a2a_capture_v5.pcap,不是公开网络样本。