A2A:跨服务的 Agent 协作协议
A2A(Agent2Agent Protocol)是独立 Agent 服务之间的通信协议。 它规定如何声明能力、发送消息、跟踪任务、请求补充输入和交付结果。双方可以使用不同的模型和框架,也不需要暴露各自的内部状态、提示词和工具实现。
例如,周报 Agent 负责撰写报告,数据 Agent 负责分析销售表格。周报 Agent 把分析目标和文件交给数据 Agent,后者自行安排分析步骤;遇到币种不明确时请求补充,完成后返回摘要。A2A 为这段协作提供统一的交互方式:
周报 Agent 数据 Agent
│ ── 读取能力与接口说明 ────────────→ │
│ ── 发送分析目标和销售表格 ────────→ │
│ ←─ 返回任务 task-42,正在执行 ──── │
│ ←─ task-42 需要确认统计币种 ────── │
│ ── 为 task-42 补充币种要求 ───────→ │
│ ←─ task-42 完成,返回分析摘要 ──── │拆任务和验收结果仍由周报 Agent 所在的应用负责。A2A 解决的是远程协作的接口问题;主管该委派什么、什么时候转交控制权,分别见主管与子 Agent和路由与 Handoff。
从 Agent Card 确认对方能接什么任务
Agent Card 是远程 Agent 的能力与接入说明,常见发现地址是:
https://data-agent.example.com/.well-known/agent-card.json周报 Agent 读取 Card 后,需要确认下面这些信息,才能决定是否把表格交给对方。
| 要确认什么 | 主要字段 | 在销售分析任务中的用途 |
|---|---|---|
| 支持哪些工作 | skills | 是否提供销售趋势和异常分析 |
| 使用哪个接口 | supportedInterfaces | 选择双方都支持的地址、协议绑定和版本 |
| 能否推送进度 | capabilities | 判断是否支持流式更新、Webhook |
| 接受什么内容 | defaultInputModes、技能的 inputModes | 确认能否读取 Excel 文件 |
| 返回什么内容 | defaultOutputModes、技能的 outputModes | 确认能否返回 Markdown 摘要或结构化结果 |
| 怎样接入 | securitySchemes、securityRequirements | 获取并使用接口要求的凭据 |
supportedInterfaces 按服务端的偏好排序,客户端选择其中第一个自己支持的接口。每个接口包含 URL、protocolBinding 和 protocolVersion;同一 Agent 可以同时提供 HTTP+JSON、JSONRPC 或 GRPC 接口。
如果接口声明了 tenant,客户端应把这个值原样填入请求的 tenant 字段,并按所选绑定映射到请求中。它用于服务端路由,访问权限仍需另外检查。本文后续使用没有 tenant 的 HTTP+JSON 接口。
Card 中的 skills 描述远程能力,例如“销售异常分析”。本地 Agent Skills 中的 SKILL.md 则描述任务执行规则,两者用途不同。
用 Message 提交目标和材料
假设数据 Agent 支持 Excel 输入和 Markdown 输出,周报 Agent 可以发送以下请求:
POST /message:send
Content-Type: application/a2a+json
A2A-Version: 1.0
Authorization: Bearer <access-token>
{
"message": {
"messageId": "msg-1001",
"role": "ROLE_USER",
"parts": [
{
"text": "分析 8 月销售数据,找出三个异常点,并返回一页摘要。"
},
{
"url": "https://files.example.com/sales/august.xlsx",
"filename": "august.xlsx",
"mediaType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}
]
},
"configuration": {
"acceptedOutputModes": ["text/markdown"],
"returnImmediately": true
}
}Message 表示一次通信,parts 把内容分成文本、文件或结构化数据等内容块。这里第一块给出目标,第二块提供文件地址。文件还需要满足接收方的访问条件;A2A 消息中出现 URL,不会自动赋予数据 Agent 下载权限。
ROLE_USER 表示这条消息从客户端发往服务端,所以周报 Agent 作为调用方也使用这个角色。服务端发回的消息使用 ROLE_AGENT。
messageId 由消息创建方生成,用于标识这一次通信。重试同一条消息时保留原 ID,可以让支持去重的服务端识别重复请求;A2A 允许 SendMessage 实现幂等,但没有规定所有服务端必须去重,也不保证任务只执行一次。 对于会产生业务副作用的操作,双方还需要约定业务幂等键及重复请求的处理方式。
SendMessage 的响应可以包含一条直接回答的 Message,也可以包含一个需要跟踪的 Task。例如,询问支持的文件格式可以直接回答;分析销售表格则可能创建任务:
{
"task": {
"id": "task-42",
"contextId": "ctx-sales-8",
"status": {
"state": "TASK_STATE_WORKING"
}
}
}task-42 标识这项分析工作,contextId 将相关的 Message 和 Task 归入同一组交互。一个 context 可以包含多个 Task,例如数据清洗、异常分析和后续报告修改。
请求中的 returnImmediately: true 要求在创建 Task 后尽快返回,后续状态由客户端查询或订阅。默认值为 false,此时 SendMessage 等待任务进入终态或需要补充输入、授权的中断态。这个字段不影响直接 Message 响应、流式操作或 Webhook 推送。
Task 怎样暂停、继续和结束
Task 保存工作状态和交付物,客户端根据状态决定下一步动作。
| 状态 | 含义 | 客户端下一步 |
|---|---|---|
TASK_STATE_SUBMITTED | 已受理,尚未开始执行 | 跟踪进度 |
TASK_STATE_WORKING | 正在执行 | 跟踪进度 |
TASK_STATE_INPUT_REQUIRED | 缺少业务输入 | 补充信息,或说明无法补充 |
TASK_STATE_AUTH_REQUIRED | 执行需要额外授权 | 处理授权请求并继续跟踪 |
TASK_STATE_COMPLETED | 成功结束 | 获取并验收交付物 |
TASK_STATE_FAILED | 执行失败 | 读取失败信息,决定是否发起新任务 |
TASK_STATE_CANCELED | 已取消 | 停止等待这项任务完成 |
TASK_STATE_REJECTED | 服务端拒绝执行 | 调整请求或更换执行方 |
前两种是进行中的状态,中间两种是中断态,最后四种是终态。任务不必依次经过表格中的每个状态。
缺少业务信息时,给原 Task 补一条消息
数据 Agent 发现表格同时包含人民币和美元,无法确定汇总口径,可以把 Task 更新为:
{
"id": "task-42",
"contextId": "ctx-sales-8",
"status": {
"state": "TASK_STATE_INPUT_REQUIRED",
"message": {
"messageId": "msg-data-1",
"taskId": "task-42",
"contextId": "ctx-sales-8",
"role": "ROLE_AGENT",
"parts": [
{
"text": "表格包含人民币和美元,汇总时使用哪种币种和汇率?"
}
]
}
}
}周报 Agent 得到统计要求后,再调用 SendMessage,把新消息关联到同一个 taskId:
{
"message": {
"messageId": "msg-1002",
"taskId": "task-42",
"role": "ROLE_USER",
"parts": [
{
"text": "统一换算成人民币,使用财务系统当天汇率。"
}
]
}
}msg-1002 是新增的补充信息,因而使用新的消息 ID;task-42 保持不变,数据 Agent 可以继续原任务。只传 taskId 时,服务端从 Task 推断 contextId;两个字段都传时,必须与已有任务匹配。
缺少授权时,按授权请求处理
如果读取财务汇率还需要用户授权,数据 Agent 可以进入 TASK_STATE_AUTH_REQUIRED,并通过状态消息说明需要什么授权;已通过带外约定或扩展说明过的细节可以省略。
这里要区分两层权限:Agent Card 声明的是客户端怎样接入数据 Agent,任务内的授权请求则可能涉及数据 Agent 怎样访问财务系统,或者某项操作是否获得人工批准。客户端需要按任务中的请求和双方约定处理,不能只重复发送接入 A2A 服务时使用的令牌。
凭据默认通过带外安全通道交付,除非双方已通过带外约定或扩展协商了带内传递方式。收到授权后,数据 Agent 可以自行继续执行,不一定需要客户端再发一条“继续”消息。因此,客户端处理授权的同时仍要保留订阅、轮询或 Webhook 跟踪,直到观察到后续状态。
终态任务不能直接恢复为进行中
COMPLETED、FAILED、CANCELED 和 REJECTED 都是终态。向这些 Task 继续发送消息会得到 UnsupportedOperationError。例如分析完成后又要求换一种统计口径,应发起新任务,并按需用原 contextId 和 referenceTaskIds 关联之前的工作。
取消也需要向服务端提出请求;关闭页面或断开流式连接不等于取消任务。服务端可能因任务已完成等原因拒绝取消,客户端应以返回的 Task 状态或错误为准。父任务如何处理远程失败、取消和迟到结果,见子任务生命周期与失败恢复。
从 Artifact 获取并验收结果
数据 Agent 完成分析后,Task 的结果放入 artifacts:
{
"id": "task-42",
"contextId": "ctx-sales-8",
"status": {
"state": "TASK_STATE_COMPLETED"
},
"artifacts": [
{
"artifactId": "artifact-summary",
"name": "august-sales-summary",
"parts": [
{
"text": "## 8 月销售异常摘要\n\n1. 华东退款率较上月增加……",
"mediaType": "text/markdown"
}
]
}
]
}Artifact 表示任务产物,内部同样由 Part 组成,所以既能返回摘要,也能返回 JSON 结果或文件。artifactId 在当前 Task 内唯一,可用于后续产物更新。
Message 承载对话,例如请求和追问;Artifact 承载任务产出。简单请求直接返回 Message 即可,不必为了统一形式创建 Task。进入 Task 流程后,客户端应从 Artifact 获取正式结果,不能只把最后一条状态说明当作分析报告。
周报 Agent 拿到摘要后,还要确认它是否满足任务要求:是否给出三个异常、使用了约定的币种、结论是否有数据支持。A2A 能表示“数据 Agent 报告任务完成”,业务验收仍由调用方完成。对文件或结构化产物,还需检查类型、大小、Schema 和文件来源,再交给后续步骤。
怎样跟踪进度和处理断线
拿到 task-42 后,客户端可以根据服务端能力选择跟踪方式。
| 方式 | 用途 | 需要处理的问题 |
|---|---|---|
GetTask 轮询 | 定期读取当前状态和交付物 | 轮询间隔、任务保留期 |
SubscribeToTask | 为已有的非终态任务接收持续更新 | 断线、重复更新、任务在重连前结束 |
| Webhook | 服务端向配置的回调地址发送更新 | 回调认证、重复投递、接收端不可用 |
HTTP+JSON 绑定下,查询当前 Task 使用 GET /tasks/task-42,订阅已有任务使用 GET /tasks/task-42:subscribe。如果提交时就需要流式更新,可以调用 POST /message:stream。HTTP 流式响应使用 SSE;gRPC 绑定使用服务端流。
SubscribeToTask 先返回订阅时的 Task 快照,然后发送状态或产物更新。客户端可以据此恢复当前视图,但不能假定它会重放断线期间的每一个事件。断线后已经知道任务 ID 时,可以先用 GetTask 读取状态:
- 任务仍在进行中或处于中断态,且服务端支持流式能力,可以重新订阅。订阅的首个 Task 快照覆盖查询与订阅之间的状态变化。
- 任务已经进入终态,直接读取最终 Task 和 Artifact。终态任务不能再订阅;若查询与订阅之间恰好完成,订阅可能报错,此时再查询结果。
- 服务端返回
TaskNotFoundError,需要检查访问权限和任务保留期,不能据此断定它从未执行。
如果第一次提交就超时,连 Task ID 都没拿到,客户端需要依据服务端约定的消息去重、业务请求标识或查询能力确认执行情况。仅重复发送相同 messageId 不能代替这项约定。
Webhook 的载荷也是 Task、Message、状态更新或产物更新之一,同一 Task 会产生多次合法通知。因此接收方不能只按 taskId 去重,否则收到“正在执行”后就会丢掉“已完成”。应以双方约定的事件标识或版本区分更新,并让业务处理可以重复执行;协议未提供通用的必填事件 ID。
产物更新还可能使用 append 追加内容。此时重复处理同一块会把报告拼接两次;如果没有可靠的事件去重约定,可以把通知作为重新调用 GetTask 的触发信号,用当前快照校准本地结果。
A2A 的边界:编排、工具和信任
同一应用内的 Agent 如果共享运行时和状态,通常可以直接通过函数、框架节点或子 Agent 调用协作。跨团队、跨部署的 Agent 需要独立声明能力、跟踪长任务和交换产物时,A2A 才有较明确的接入价值。
| 周报助手的需求 | 对应方式 |
|---|---|
调用本地 calculate_growth() | Function Calling / Tool Loop |
| 通过标准接口读取数据库或文件 | MCP |
| 委派独立数据 Agent 完成分析 | A2A |
| 组织写作、审查和修改的执行顺序 | 本地多 Agent 编排 |
这些方式可以同时存在。数据 Agent 可以用 MCP 访问数据库,通过 A2A 接受委派;周报 Agent 在本地编排中等待分析结果,再调用写作和审查步骤。A2A 不要求调用方知道远程 Agent 内部用了几个 Agent。
跨服务协作还增加了几个需要明确处理的边界。Agent Card 应从受信任入口获取,必要时验证签名;能力声明不能替代访问授权或结果验收。任务查询、取消和订阅都要验证调用者的权限,知道 Task ID 或 tenant 值不能成为访问依据。
Webhook 接收方按配置的认证方案验证来源,若方案使用签名和时间戳,还应验证签名与有效窗口;不能假定协议自动提供统一的签名头。远程 Message、Artifact 和文件内容作为任务数据处理,其中的文字不能提升为本地系统指令,也不能改变本地工具权限。
版本与参考资料
本文于 2026-09-28 核对 A2A v1.0.1 的 Release、规范正文和 Proto 定义。协议协商使用主版本与次版本,因此示例填写 A2A-Version: 1.0,不填写补丁号。
示例采用该版本的 HTTP+JSON 绑定、camelCase JSON 字段和 TASK_STATE_* 枚举值。HTTP+JSON 请求与普通响应优先使用 application/a2a+json;JSON-RPC 绑定使用 application/json,两者不能混用端点和请求封装。
该版本规范正文的 HTTP 端点列表与 Proto 对 SubscribeToTask 的动词存在差异,本文按 Proto 的 GET /tasks/{id}:subscribe 编写。接入具体服务时,应锁定 SDK 和服务端版本,并核对其实际支持的绑定。