首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >银河it-协商的协议,信任的荒漠:A2A从能力发现到意图泄漏的结构性张力

银河it-协商的协议,信任的荒漠:A2A从能力发现到意图泄漏的结构性张力

原创
作者头像
用户12502707
修改于 2026-09-29 10:41:52
修改于 2026-09-29 10:41:52
1170
举报

一个被“标准化”掩盖的架构选择

2025年4月,Google发布A2A(Agent-to-Agent)协议时,官方博客给出了一段看似简单的判断:“API是刚性的、确定性的;Agent是流动的、自主的。把Agent当成API来对待,会严重限制其潜力”。这句话划定了A2A与REST API之间的本质区别,也划定了A2A与MCP之间的分工边界。

但这个判断本身隐藏着一个需要被审视的假设:“流动和自主”在协议层面到底意味着什么? 如果A2A只是把API调用包装成JSON-RPC消息,那它不过是换了个传输格式的远程过程调用。A2A的实质区别在于Task作为一等公民的设计——任务不是一次请求-响应的原子操作,而是一个有生命周期、有中间状态、可以被中断和恢复的持久实体。

这个设计选择的代价,在协议落地一年后开始显现。A2A v1.0于2026年3月发布,提供了“第一个稳定的开放标准,让Agent跨框架和供应商边界相互发现和协调工作”。但同一份指南紧接着给出了一句刺眼的限定:“规范只解决通信问题。因为v1.0.0刻意排除了内置授权,使用宽泛的OAuth作用域部署这些系统会带来下游数据泄漏和Agent冒充的风险”。

这是理解A2A当前状态的关键。它不是一个“尚未完善”的协议,而是一个在架构层面刻意选择了极简主义的协议。这个选择的后果,远比“安全性待加强”这样的模糊评价要具体和深远。

Task生命周期:状态机作为协议的核心

A2A最核心的架构决策,是把“任务”而非“消息”作为协议的基本单元。一个Task在A2A中经历八个离散状态:submitted、working、input_required、auth_required、completed、failed、canceled、rejected。其中input_required和auth_required是中断状态——任务在此处暂停,等待外部输入或授权,而不是继续消耗计算资源或保持连接。

这个设计解决了一个实际问题。在传统API调用中,如果一次请求需要用户授权或补充信息,调用方必须保持连接或轮询,而服务端必须维持会话状态。A2A的中断状态把“等待”从连接层解耦到了任务层:客户端收到input_required后可以断开,在获得用户输入后通过taskId恢复任务。TaskId成为了跨连接、跨会话的持久引用。

以下是一个简化的A2A任务状态转移的TypeScript类型定义,展示了协议层面的核心约束:

代码语言:javascript
复制
// 基于 @agentskit/core a2a.d.cts 的类型抽象
type TaskState =
  | 'submitted'      // 已提交,未开始处理
  | 'working'        // 处理中
  | 'input_required' // 中断:需要用户补充信息
  | 'auth_required'  // 中断:需要授权
  | 'completed'      // 终态:成功
  | 'failed'         // 终态:失败
  | 'canceled'       // 终态:被取消
  | 'rejected';      // 终态:被拒绝

interface Task {
  id: string;
  contextId: string;     // 用于将相关任务分组到同一对话上下文中
  state: TaskState;
  artifacts?: Artifact[]; // 任务输出
  history?: Message[];    // 对话历史
}

// 合法状态转移的约束(简化表示)
const VALID_TRANSITIONS: Record<TaskState, TaskState[]> = {
  submitted: ['working', 'failed', 'rejected', 'canceled'],
  working: ['input_required', 'auth_required', 'completed', 'failed', 'canceled'],
  input_required: ['working', 'failed', 'canceled'],
  auth_required: ['working', 'failed', 'canceled'],
  // 终态不可转移
  completed: [], failed: [], canceled: [], rejected: [],
};

这个状态机的工程意义在于:它把“任务在多个Agent之间流转”从隐式约定变成了显式契约。一个编排Agent可以可靠地知道,当它收到一个input_required的Task时,对端Agent正在等待用户输入,而不是在“假装工作”或者陷入了死循环。对于跨组织边界的Agent协作——这正是A2A的设计目标——这种可观测性是部署可靠性的前提。

Agent Card:能力发现作为攻击面

A2A的另一个核心机制是Agent Card——一个位于/.well-known/agent-card.json的JSON文档,声明Agent的身份、技能、端点URL、支持的传输绑定和安全方案。

这个设计在工程上是优雅的。编排Agent不需要预先知道另一个Agent的接口细节,只需获取其Agent Card,解析其中的skills数组,匹配任务需求,然后向声明的endpoint发送请求。能力的“协商”通过JSON Schema的输入输出声明完成,而非通过人类编写的接口文档。

但优雅的代价是发现层的信任缺失。

2026年6月发表在IEEE的一项研究首次系统性地测量了“Agent Card投毒”攻击。攻击方式极其直接:攻击者在一个Agent Card的url字段中填入自己控制的端点,编排Agent在发现阶段解析卡片后,会忠实地将后续任务路由到攻击者的服务器。研究测试了三种攻击层级——朴素攻击(纯文本覆写)、结构化攻击(JSON嵌入)和自然语言社会工程攻击——在无防护的编排Agent上,三种攻击全部成功。

正则清洗(D1)能拦截所有语法显式的尝试,但被自然语言社会工程攻击全部绕过。LLM-based的卡片审计(D3)在试点中拦截了所有9次攻击尝试,在100次良性测试中零误报,但研究明确标注这是试点规模(n=3 per tier per combo),需要更大规模的验证。

这项研究的价值不在于“发现了一个漏洞”,而在于它揭示了一个结构性问题:A2A的发现层假设Agent Card是可信的,但协议本身不提供任何密码学验证机制。Agent Card是明文JSON,通过HTTP GET获取,没有签名,没有证书绑定。在一个只涉及同一组织内部Agent的场景中,这个问题可能被网络边界防护掩盖。但A2A的整个价值主张就是“跨组织边界协作”——当编排Agent需要发现并调用另一个公司的专业Agent时,它凭什么信任那张卡片上写的url?

与MCP的分层:清晰但不对称

A2A经常被拿来与MCP对比,而两者确实形成了清晰的互补关系。一篇2026年的系统性综述给出了精确的界定:“MCP是‘工具连接的USB-C’(垂直的,Agent到工具),A2A是‘Agent协作的HTTP’(水平的,Agent到Agent)。生产系统通常同时使用两者:A2A将任务路由到正确的专业Agent;MCP为该Agent提供上下文和工具”。

这个类比在架构层面是准确的。MCP解决的是“一个Agent如何安全、标准化地调用外部工具和数据源”;A2A解决的是“一个Agent如何发现、协商并委派任务给另一个Agent”。两者不竞争,是同一栈的两层。

但“互补”的表述掩盖了一个重要的不对称。MCP在2026年7月经历了一次大规模修订,其核心变化是“从有状态的、需要握手和会话管理的协议,彻底转向了无状态核心”。这次修订的方向是降低协议的认知负荷和工程复杂度。而A2A的设计方向恰好相反:它的核心价值恰恰在于Task作为有状态、跨连接、可中断恢复的持久实体。A2A的有状态性是它的功能,不是它的负担。

这意味着,一个团队如果同时使用MCP和A2A,它需要维护两套完全不同的状态管理心智模型。MCP侧,每次调用自包含,状态由应用层管理;A2A侧,Task本身承载状态,协议层管理生命周期。这种认知切换的成本,在2026年的工程实践中尚未被充分讨论。

安全模型的结构性缺口

A2A v1.0明确将授权委托给外部机制:“A2A将认证委托给标准Web机制,主要依赖HTTP头和OAuth 2.0、OpenID Connect等既定标准”。这个选择的理由是合理的——协议不需要重新发明OAuth。但“委托”的代价是,协议层面无法对授权行为的语义施加任何约束。

2026年6月的一项研究专门分析了A2A的Token管理风险,结论是:“A2A标准没有对Token使用上下文、授权委托或Agent之间的Token传播施加严格规范,这可能导致Token泄露和重用漏洞”。

这个发现的实践含义是严重的。考虑一个典型的多Agent工作流:用户通过前端Agent发起请求,前端Agent将任务委派给专业Agent A,A又将子任务委派给专业Agent B。在这个过程中,用户的OAuth Token需要从用户→前端Agent→A→B传播。如果Token的scope是宽泛的(比如read:all),那么B获得了远超其任务需要的权限。如果Token的传播没有绑定到特定任务上下文,那么B可以将Token重用于其他目的。如果Token的lifetime没有被严格约束,那么一旦泄露,攻击窗口是开放的。

A2A的协议规范没有强制任何这些约束。它提供了auth_required状态来标记需要授权的任务中断,但授权决策本身不在协议范围内。这意味着,部署A2A的生产系统需要自行设计一套Token传播和验证机制,而协议不提供任何参考实现或规范性约束。

更隐蔽的问题是“意图泄漏”(Intent Leakage)。一篇2026年的论文识别了工具增强架构中的治理盲区,指出A2A在任务委派过程中,调用方的原始意图——包括用户的目标、约束条件、敏感偏好——会随着任务描述传播到所有下游Agent。如果下游Agent属于不同的信任域,这些意图信息就构成了跨边界的信息泄漏。协议没有提供意图的最小化披露机制。

当前的工程边界

A2A v1.0是一个通信协议,不是一个安全协议。它的设计目标是以最小的规范复杂度,解决Agent之间“如何说同一件事”的问题。它成功地做到了这一点:八个任务状态、Agent Card发现、JSON-RPC/HTTP+JSON/gRPC三种传输绑定,构成了一个足够简单以至于可以被不同框架和供应商实现的标准。

但“足够简单”的另一面是“不够安全”。Agent Card的发现层信任是明文且无验证的。Token传播没有上下文绑定。意图在委派链中全量传播。这些问题在协议层面被识别,但被有意地排除在规范范围之外——委托给OAuth、mTLS、应用层的授权策略。

对于正在部署A2A的工程师,这意味着几件具体的事。第一,Agent Card必须被签名和验证,否则发现层就是攻击者的入口。第二,Token必须绑定到任务上下文,不能允许一个Agent将Token用于它被授权范围之外的操作。第三,委派链中的意图信息需要最小化,只传递下游Agent完成任务所必需的信息,而非调用方的完整上下文。

协议不会替你解决这些问题。这是它的设计选择,也是它的工程代价。A2A让Agent之间“能说话”变得便宜,但让它们“安全地说话”仍然昂贵。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一个被“标准化”掩盖的架构选择
  • Task生命周期:状态机作为协议的核心
  • Agent Card:能力发现作为攻击面
  • 与MCP的分层:清晰但不对称
  • 安全模型的结构性缺口
  • 当前的工程边界
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档