A2A 是什么,不是什么

A2A 是 Agent-to-Agent 的缩写,为一个 Agent 服务请求另一个 Agent 服务完成工作提供互操作词汇。Google 在 2025 年公开宣布了这一协议,项目当前将 1.0.0 作为最新正式规范。1.0 把 Protocol Buffer 模型提升为规范数据定义,把抽象操作与传输 Binding 分离,并为独立实现的服务定义兼容规则。

生产环境应把 1.0.0、选定 Binding、SDK 版本和 Agent Card 中声明的 Interface 视为一组经过测试的兼容档案。“支持 A2A”本身不是足够精确的契约。

A2A 可以帮助实现:

  • 通过 Agent Card 发现能力;
  • 结构化 Message 和 Artifact;
  • 短任务或长任务的生命周期;
  • 状态更新,以及版本支持时的 Streaming 或 Push Delivery;
  • 在独立运营的 Agent 服务间进行委派。

它不能提供业务授权、Tenant 隔离、可信目录、安全 Tool 执行、准确模型输出或只发生一次的副作用。这些仍是应用职责。

先确定边界,再选择协议

如果工作由同一服务拥有且契约很窄、可确定,优先使用 Local Function。只有远程服务具有独立生命周期、Policy 或能力边界时,才考虑 A2A。

问题 合理答案
谁拥有 Task State? 一个有持久存储和 Retention Policy 的明确服务。
谁认证 Caller? 远程服务,使用可信身份机制。
谁授权数据和副作用? 拥有对象和动作的服务。
Retry Identity 是什么? 应用生成的 Idempotency Key,而不是模型语句。
断连时发生什么? 明确 Polling、Callback、Resume 或 Cancellation 契约。
保留什么证据? 脱敏的生命周期 Event、Policy Decision、Artifact Reference 和 Error Class。

不要只是为了让线性 Workflow 看起来“自治”而引入 Agent 协议。

A2A 与 MCP

两个协议面对不同边界,但“A2A 是 Agent-to-Agent、MCP 是 Agent-to-Tool”的口号并不完整。

边界 常见目的 协议外仍需完成
A2A 向远程 Agent 服务委派 Task 服务身份、Task/Object 授权、Budget
MCP 为 AI 应用暴露 Tool、Resource、Prompt User 身份、Object 授权、副作用 Policy
Application Workflow 协调本地与远程工作 Approval、补偿、Audit、业务 Invariant

A2A Delegate 可以在内部使用 MCP;MCP Host 也可以不使用 A2A 而调用远程服务。两种安排都不会授予模型读取对象或执行外部写入的权限。

固定规范表面

实现前记录已测试的精确输入:

json
{
  "a2a_specification": "1.0.0",
  "canonical_data_model": "spec/[email protected]",
  "abstract_operations": ["SendMessage", "GetTask", "CancelTask"],
  "transport_binding": "HTTP+JSON/REST",
  "agent_interface_protocol_version": "1.0",
  "agent_card_location": "/.well-known/agent-card.json",
  "sdk": "package-and-version",
  "authentication": "deployment-specific",
  "task_retention": "policy-reference",
  "artifact_store": "approved-store"
}

在 A2A 1.0 中,a2a.proto 是 Canonical Data Model 的权威来源,Abstract Operation 先定义行为,再由 Binding 映射到线上协议。标准 Binding 包括 JSON-RPC、gRPC 和 HTTP+JSON/REST。Agent Card 可以声明多个 Interface,每个 Interface 独立携带 Protocol Version,因此迁移不必要求所有调用方一次性切换。

语义等价不代表线上字节可以互换。JSON 字段命名、Enum 表示、错误、Header、Streaming 和认证仍由选定 Binding 约束。应对每个公开 Interface 运行同一组 Conformance Case;如果响应声称的 Protocol Version 与协商 Interface 不一致,应直接拒绝。

Agent Card 是发现 Metadata

Agent Card 可能描述服务名称、Endpoint、Skill、输入输出 Mode 和 Capability。它适合 Routing,但在系统验证来源前均不可信。

需要验证:

  1. Transport Endpoint 是否来自 Allowlist 或可信 Registry;
  2. 使用 Token 时的服务身份、Issuer、Audience、Expiry 和 Key Rotation;
  3. Card Schema 与 Version;
  4. Requested Skill 是否被本地 Policy 允许;
  5. 每个 Task 的 Caller、Tenant 和 Target Object;
  6. Result Size、Artifact Type 与 Callback Destination。

Description、Skill Name、URL 和 Example 都可能包含 Prompt Injection 或误导声明。不能将 Card Text 变成可执行 Policy、自动安装或 Tool Permission。

Task 生命周期是状态机

规范使用 Task 表示有状态工作单元,使用 Message 表示一次交互,完成后的交付物进入 Artifact。A2A 1.0 还定义了 SendMessage、SendStreamingMessage、GetTask、ListTasks、CancelTask、SubscribeToTask 和 Push Notification 配置等抽象操作。生产服务应把这些操作映射到自己的持久化状态机。

text
accepted -> running -> waiting_for_input -> running -> terminal
                         |                         |
                         +-> canceled              +-> succeeded | failed | rejected

同时记录 Protocol-facing Status 与 Application Status。GetTask、ListTasks、Cancellation 和 Subscription 必须执行相同的 Caller 与 Tenant 可见性规则;持有 Task ID 不等于获得访问权限。Terminal Response 应区分:被 Policy 拒绝、作用前失败、作用结果不确定,或带证据完成。

幂等与取消

Retry、重复 Message、Timeout 和 Callback 都是分布式系统的常见行为。对于会产生副作用的 Task:

  • 将 Idempotency Key 绑定 Principal、Tenant、Action 与规范化参数;
  • 执行副作用前持久化 Decision;
  • 让 Cancellation 可协作且感知 State;
  • 无法证明副作用时返回“结果不确定”;
  • 仅在业务操作支持时使用 Compensation。

不能将模型拒答或 completed 标签视为付款、删除或通知只执行一次的证据。

Message、Artifact 与不可信内容

Message Part 和 Artifact 可以承载 Text、Structured Data、File、Reference 或 URL。包括其他 Agent 输出的内容在内,都应视为不可信输入。

限制:

  • Byte、Item Count、Nesting Depth 和 Decompression;
  • MIME Allowlist 与 Parser 行为;
  • Artifact Storage Location、Expiry、Access Scope 和 Deletion;
  • Outbound Fetch Destination 与 Redirect Count;
  • Telemetry Field 与原始 Payload 留存。

比起将大量敏感内容嵌入 Task Message,更适合使用经过认证访问的 Reference Artifact。File Reference 不是访问许可,Retrieval 时仍须验证。

Streaming 与 Push Delivery

Streaming 和异步通知是传递选择,并不代替 Task Durability。A2A 1.0 在抽象操作层定义 SendStreamingMessage 与 SubscribeToTask,再由各 Binding 决定如何承载有序的 Stream Response。Push Notification 使用已注册的 Task 通知配置,并向 Callback 传递同一类 StreamResponse。无论选择哪种 Binding,都应定义:

  • Reconnect 与 Resume;
  • Event Ordering 与 Deduplication;
  • Token Expiry 与重新认证;
  • Callback 注册与 Destination Allowlist;
  • Timeout、Backpressure 与 Result Size Budget;
  • 谁可以观察或取消 Task。

Callback URL 是 SSRF 边界。必须验证 Destination Ownership,将配置绑定到具体 Task 和 Tenant,为通知签名,拒绝私网与 Link-local 地址,限制 Redirect,并让接收方去重。不能让模型或远程 Agent 选择无限制 Callback Target。

授权逐 Task、逐对象执行

OAuth、Bearer Token、mTLS、Workload Identity、RBAC 和 ABAC 都是可能的部署控制。它们并不都是 A2A 的必选功能;已经认证的 Caller 也不自动拥有全部 Skill 权限。

go
package main

import "fmt"

type verdict string

const (
	allow   verdict = "allow"
	deny    verdict = "deny"
	confirm verdict = "confirm"
)

type taskRequest struct {
	Skill    string
	ObjectID string
}

type authenticatedContext struct {
	TenantID    string
	PrincipalID string
}

type objectAccess func(tenantID, principalID, objectID string) bool

func authorize(
	context authenticatedContext,
	request taskRequest,
	allowedSkills map[string]struct{},
	canAccessObject objectAccess,
	effectClass string,
) verdict {
	if _, ok := allowedSkills[request.Skill]; !ok {
		return deny
	}
	if request.ObjectID != "" &&
		!canAccessObject(context.TenantID, context.PrincipalID, request.ObjectID) {
		return deny
	}
	if effectClass == "external_write" || effectClass == "destructive" {
		return confirm
	}
	return allow
}

func main() {
	context := authenticatedContext{TenantID: "tenant-a", PrincipalID: "user-42"}
	request := taskRequest{Skill: "invoice.read", ObjectID: "invoice-7"}
	allowed := map[string]struct{}{"invoice.read": {}}
	canRead := func(tenantID, principalID, objectID string) bool {
		return tenantID == "tenant-a" && principalID == "user-42" && objectID == "invoice-7"
	}
	fmt.Println(authorize(context, request, allowed, canRead, "read"))
}

这段可运行的 Go 代码是应用层 Policy 片段,不是 A2A SDK 实现。可信 Caller Context 必须来自 Authentication Middleware,不能来自模型生成的 Task Field。effectClass 应来自服务端 Skill Registry,而不是请求体;真实的 confirm 结果还需要明确的审批和审计流程。

最小生产架构

text
caller
  -> authenticate and authorize
  -> validate task shape and budget
  -> persist idempotency + lifecycle event
  -> remote A2A service
       -> independently authenticate, authorize, and validate
       -> use bounded local tools and data access
       -> store artifacts in an approved scoped store
  -> collect redacted result evidence
  -> apply local approval / compensation / user response

每个服务都应重新检查 Policy,不能只相信上游执行。跨 Tenant Test 必须证明:Tenant A 的有效 Token 和 Task ID 无法读取或修改 Tenant B 数据。

测试失败路径

测试 应证明什么
Card Validation Unknown Issuer、Endpoint、Schema Revision、Skill 被拒绝
Task Contract Malformed Part、Oversized Artifact、Unsupported Mode 安全失败
Authorization Tenant、Object、Action Policy 独立于 Description 执行
Lifecycle 重复投递、Restart、Timeout、Cancellation、Late Callback 可确定处理
Side Effects Retry 不重复执行;不确定性会暴露
Streaming Reconnect、Replay、Ordering、Expiry、Backpressure 有边界
Abuse 阻止注入、投毒 Artifact、SSRF Callback、Artifact Exfiltration
Operations 可观察延迟、Queue Depth、Task Cost、Retention 与 Deletion

Policy 和副作用优先用确定性 Oracle。语言模型 Judge 可以审查答案质量,但不能证明授权、来源或财务正确性。

常见失败模式

  • 将 Agent Card、Registry Listing、Badge 或 SDK Adapter 当作信任决策;
  • 假设所有 A2A 实现共享同一个发现路径或 Streaming Transport;
  • 接受 Task Content 中提供的 Caller Identity;
  • 将 Task Completion 当作副作用只执行一次的证据;
  • 未验证地转发远程 Artifact 或 Callback URL;
  • 将远程 Agent 输出当作指令或授权;
  • 无期限保留原始 Task Payload;
  • 未测试固定 Version/Transport 就宣传 Framework 支持;
  • 本地确定性 Workflow 更清晰时仍调用远程 Agent。

实现清单

  • [ ] 固定 Protocol、Binding、SDK 与 Agent Card Schema Revision。
  • [ ] 获取或信任 Card 前验证远程 Endpoint 来源。
  • [ ] 在部署需要时双向认证。
  • [ ] 在拥有服务处授权每个 Skill、Tenant、Object 和副作用。
  • [ ] 持久化 Idempotency、生命周期迁移和不确定状态。
  • [ ] 限制 Message、Artifact、Callback、Streaming、Retry 与 Telemetry。
  • [ ] 将远程 Message、Artifact 与 Card Content 视为不可信。
  • [ ] 测试 Restart、重复投递、Cancellation、Expiry、跨 Tenant 与 SSRF。
  • [ ] 定义 Task、Artifact、Index、Cache、Export 的 Retention 与 Deletion。
  • [ ] 使用明确 Scope、Budget 与 Rollback Control 逐步发布。

总结

A2A 可以使独立 Agent 服务实现互操作,但它不是自治的信任网络。固定运行的版本,将发现视为不可信 Metadata,使 Task State 可持久化,并在模型外执行身份、对象访问和副作用控制。最安全的引入方式是从一个窄小、只读的委派 Task 开始,只有生命周期、授权和失败证据充分时再扩展。

延伸阅读

一手来源