A2A agent 发现
此部署可以发布其代理,以便另一个部署,或任何 HTTP 客户端,能够发现它们并调用它们。A2A 是一个代理与另一个代理通信的协议,就像人通过聊天应用接触机器人一样。此页面会准确展示应输入什么,以及会返回什么。
此页面上的每个响应都是真实运行中的守护进程输出。这里没有任何示例内容。
身份验证
这两个 discovery GET 都不需要认证:catalog card 和 per-alias agent card 在没有 token 的情况下也可读取,因此对等方可以在配对前发现你已发布的 surface。message/send POST 不同。它会运行完整的启用工具的 agent turn,因此和其他所有 write surface 一样,都位于 gateway 的 pairing auth 之后。启用 [gateway] require_pairing 时(默认值),请在 task POST 上传递一个由 pairing 派生的 bearer token:
curl -X POST http://localhost:42617/a2a/agent_alpha \
-H "Authorization: Bearer $ZEROCLAW_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{...}}'
未认证的 task POST 会返回 401,不会触发 agent 回合。下面的 discovery GET 不需要 header。有关如何获取 token,请参阅 gateway 配对文档。
分两次请求完成整个内容
你只需要两个 GET 请求就能发现一个 agent。
首先,询问该部署发布了哪些 agent:
curl http://localhost:42617/.well-known/agents-card.json
其次,询问其中一个代理它能做什么:
curl http://localhost:42617/a2a/agent_alpha/.well-known/agent-card.json
第一次请求会给你一组 agent URL。第二次请求会给你某个 agent 的技能以及你发送工作的 URL。这就是整个发现面。此页面的其余部分只是仔细阅读这两个响应。
列出代理程序
curl http://localhost:42617/.well-known/agents-card.json
响应:
{
"name": "ZeroClaw agents",
"描述": "用于枚举此 ZeroClaw 安装中已发布的 A2A 代理的发现目录。不是可运行的代理;下方每个条目都有其各自的 A2A card 和 endpoint。Skills 会从已发布的代理中汇总,并为每个技能标注其所属 alias。",
"supportedInterfaces": [
{
"url": "http://localhost:42617/.well-known/agents-card.json",
"protocolBinding": catalog,
protocolVersion: "1.0"
},
{
"url": "http://localhost:42617/a2a/agent_beta",
"protocolBinding": "JSONRPC",
protocolVersion: "1.0"
},
{
"url": "http://localhost:42617/a2a/agent_alpha",
"protocolBinding": "JSONRPC",
protocolVersion: "1.0"
}
],
version: "0.8.5",
"capabilities": {
"streaming": false,
pushNotifications: false,
"extendedAgentCard": false
},
"defaultInputModes": ["文本"],
"defaultOutputModes": ["文本"],
"skills": [
{
"id": "agent_beta/github-issue-triage",
"name": "github-issue-triage",
"描述": "ZeroClaw 的问题分诊和生命周期管理代理。",
"tags": ["github", issues, "triage", "agent_beta"]
},
{
"id": "agent_beta/github-pr-review-session",
"name": "github-pr-review-session",
"描述": "ZeroClaw PR 审查的人工审核协作助手。",
"tags": ["github", "pull-requests", "review", "agent_beta"]
},
{
"id": "agent_alpha/zeroclaw",
"name": zeroclaw,
"描述": "帮助用户操作并与其 ZeroClaw agent 实例交互。",
"tags": [operations, "cli", gateway, "agent_alpha"]
},
{
"id": "agent_alpha/skill-creator",
"name": "skill-creator",
"描述": "创建新技能、修改并改进现有技能,并衡量技能性能。",
"tags": ["skills", authoring, "evaluation", "agent_alpha"]
},
{
"id": "agent_alpha/changelog-generation",
"name": "changelog-generation",
"描述": "用于 ZeroClaw 发布的变更日志生成技能。",
"tags": ["changelog", release, 自动化, "agent_alpha"]
}
]
}
按这样理解。supportedInterfaces 列出了 URL。标记为 catalog 的那一项就是这个列表本身,忽略它。标记为 JSONRPC 的两个是代理:agent_alpha 和 agent_beta。它们的 URL 是你发送任务的地方。skills 汇总了每个已发布代理的技能,每个 id 都带有前缀,并通过 tags-标记为所属别名,因此一次读取就能看到整个安装的能力范围以及每个部分的归属。
检查一个代理
从列表中取一个 URL,并追加卡片路径:
curl http://localhost:42617/a2a/agent_alpha/.well-known/agent-card.json
响应:
{
"name": "agent_alpha",
"描述": "ZeroClaw agent 'agent_alpha'.",
"supportedInterfaces": [
{
"url": "http://localhost:42617/a2a/agent_alpha",
"protocolBinding": "JSONRPC",
protocolVersion: "1.0"
}
],
version: "0.8.5",
"capabilities": {
"streaming": false,
pushNotifications: false,
"extendedAgentCard": false
},
"defaultInputModes": ["文本"],
"defaultOutputModes": ["文本"],
"skills": [
{
"id": zeroclaw,
"name": zeroclaw,
"描述": "帮助用户操作并与其 ZeroClaw agent 实例交互。",
"tags": [operations, "cli", gateway]
},
{
"id": "skill-creator",
"name": "skill-creator",
"描述": "创建新技能、修改并改进现有技能,并衡量技能性能。",
"tags": ["skills", authoring, "evaluation"]
},
{
"id": "changelog-generation",
"name": "changelog-generation",
"描述": "用于 ZeroClaw 发布的变更日志生成技能。",
"tags": ["changelog", release, 自动化]
}
]
}
现在你知道三件事。该代理名为 agent_alpha。它有三项技能,zeroclaw、skill-creator 和 changelog-generation,并且都有对其功能的简要说明。单一的 JSONRPC 接口 URL,http://localhost:42617/a2a/agent_alpha,就是你向其 POST 任务的地址。
卡片 description 来自已配置的别名身份文档:AIEOS 身份的 bio 提供该行内容,否则回退为该身份中的名称。未设置身份时,卡片使用上方显示的中性默认值 ZeroClaw agent '<alias>'.。
代理选择显示的内容
代理不必发布它拥有的每一项技能。同一部署中的 agent_beta 代理发布其自己选择的一组:
curl http://localhost:42617/a2a/agent_beta/.well-known/agent-card.json
{
"name": "agent_beta",
"描述": "ZeroClaw 代理 'agent_beta'。",
"supportedInterfaces": [
{
"url": "http://localhost:42617/a2a/agent_beta",
"protocolBinding": "JSONRPC",
protocolVersion: "1.0"
}
],
version: "0.8.5",
"capabilities": {
"streaming": false,
pushNotifications: false,
"extendedAgentCard": false
},
"defaultInputModes": ["文本"],
"defaultOutputModes": ["文本"],
"skills": [
{
"id": "github-issue-triage",
"name": "github-issue-triage",
"描述": "ZeroClaw 的问题分诊和生命周期管理代理。"
},
{
"id": "github-pr-review-session",
"name": "github-pr-review-session",
"描述": "ZeroClaw PR 审查的人工审核协作助手。"
}
]
}
部署选择了要发布哪些 agent,以及每个 agent 暴露哪些技能。发布的目的就在于此:你可以按 agent 决定外部世界能看到哪些技能。
发送任务
一旦你有了代理的接口 URL 和一个技能,就将工作作为 JSON-RPC message/send POST 发送到该 URL:
curl -X POST http://localhost:42617/a2a/agent_alpha \
-H "Authorization: Bearer $ZEROCLAW_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{ "kind": "text", "text": "回复 PONG" }]
}
}
}'
代理执行该轮并以已完成的任务作答。回复是任务制品中文本部分:
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"artifacts": [
{
"artifactId": "5346ae32-1b63-40c0-9aaa-345d815c792e",
"parts": [
{
"kind": "text",
"text": "PONG"
}
]
}
],
"contextId": "a2a_agent_alpha_06cb22f5-12bf-4b26-9ebc-9c063ab520a4",
"id": "0ef19fcb-b5e4-4c26-afce-d80451c8861e",
"kind": "task",
"status": {
"state": "completed"
}
}
}
发现和任务使用相同的接口 URL;只有请求不同。该端点仅接受 message/send;任何其他 method 都会返回 JSON-RPC -32601,空消息返回 -32602,而不是 JSON-RPC 的请求体会返回 HTTP 400。
暴露与那把锋利的边缘
任务端点与卡片共享 enabled 和 published 这两个门控:只有在设置了 [a2a.server] enabled,且别名已启用并已发布时才会响应。对未发布或未知别名的 task POST 会返回 404,与其卡片相同。不过,它不共享卡片的认证策略:发现卡片仍然是公开的,而调用任务需要网关 bearer token,没有该 token 时返回 401。
需要注意的一个棘手点:接口 URL 对裸 GET 返回的是 web 仪表板,而不是 agent,因为网关会对它不识别的任何路径回退为提供仪表板:
curl -i http://localhost:42617/a2a/translator
HTTP/1.1 200 OK
content-type: text/html
Discovery(.well-known 路径)和 message/send POST 是支持的接口。对接口 URL 直接发起裸 GET 不属于该协议;请改为读取 .well-known 路径上的卡片。
代理从何处提供服务
这些卡片由 Web 网关提供服务,地址和端口与其他所有内容相同。如果你的网关是 localhost:42617,那么目录和每个代理卡片都在这里。你不需要运行第二个服务器,也不需要打开第二个端口。
如果你将此部署置于反向代理或公共主机名之后,卡片中的 URL 需要与客户端实际访问到的地址一致。已发布的 URL 按以下顺序解析:如果你设置了显式的公共基础 URL,则优先使用它;然后是你设置的 A2A 专用主机和端口覆盖;最后是网关自身的地址。该覆盖项是为代理场景存在的;如果你不在代理后面,就不要修改它,卡片会直接公布网关地址。
打开它
在你将其开启之前,Discovery 处于关闭状态,而且它以三种相互独立的方式保持关闭,因此不会有任何内容意外泄露:
- A2A 服务器默认对整个部署禁用。
- 即使服务器已开启,每个 agent 默认也未发布。
- 已发布的 agent 只暴露你命名的技能,别无其他。
您只需启用一次服务器,将希望可访问的特定 agent 标记为已发布,并列出每个 agent 暴露的技能。被禁用或未发布的 agent 不会出现在目录中,其卡片路径会返回 404。未知的 agent 名称也会返回 404。
只有当某个命名技能解析为智能体实际携带的真实技能时,它才会出现在卡片上:它必须位于智能体的某个技能包中,并且其 SKILL.md 必须具有有效的 YAML frontmatter。无法解析的名称,或位于智能体未声明的技能包中的技能,都会被静默丢弃,而不会被展示。
空的 skills: [] 数组最常见的原因,是在未声明任何 skill_bundles 的 agent 上设置了 a2a.exposed_skills。exposed_skills 只会缩小该 agent 已解析出的技能集;它本身不会加载技能。由于没有声明 bundle,过滤器没有任何可保留的内容,因此所有名称都会被丢弃,card 也就不会展示任何内容。请将所属的 bundle 添加到 agents.<alias>.skill_bundles。配置校验会将这种情况作为启动警告抛出(a2a_exposed_skills_without_bundles)。
发布实际上公开的内容
发布前请阅读此内容。一旦服务器启用且别名已发布,POST /a2a/{alias} 会为该别名执行一次完整的 agent 回合:它通过与聊天界面相同的路径调用该 agent,并使用该 agent 的整套已配置工具集(shell、file、browser,以及该别名携带的其他所有工具)。
该任务端点受网关的 bearer/pairing 认证保护,和其他所有写入面一样。调用方需要一个由 pairing 派生的 bearer token 才能调用已发布的 agent;未认证请求会返回 401,绝不会进入 agent 回合。
发现卡片不受该认证保护。目录卡片和按别名的卡片无需 token 即可读取,因此已发布的 surface 会向任何能够访问监听器的调用方公开其 agent 名称和暴露的技能。这正是 discovery 的目的:对等方在配对之前先读取卡片。这也意味着发布会将这些元数据暴露给任何能够访问网关的人,尽管调用 agent 仍然需要 token。
发布是在两个维度上的暴露决策:卡片元数据是公开的,任何持有有效 token 的人都可以使用其完整工具集调用已发布的别名。在切换开关之前:
- 限定绑定姿态。将网关绑定到私有接口,或将其置于反向代理之后,而不是将监听器直接暴露给不受信任的网络。这样也限定了谁可以读取未认证的卡片。
- 仅发布那些您愿意让任何令牌持有者调用其完整工具集、并愿意在未认证情况下公开其名称和技能的别名。将
exposed_skills收窄到互操作所需的最小集合。 - 在决定该别名携带哪些工具和技能包时,将已发布的别名视为可远程调用的执行面。
- 跨部署互操作会与调用你的对等方共享一个令牌;请像对待任何其他凭据一样对其设置作用域并轮换。
多个部署如何连接
Discovery 可跨任意数量的部署进行组合。每个部署都会在各自的地址发布自己的目录。知道多个部署地址的客户端会获取每个目录,读取 agents,并由此持有一个合并后的映射,包含所有部署中每个可达的 agent。这里没有注册中心,也没有中央服务器:客户端是唯一需要知道这些地址的实体,并且它会直接与每个部署通信。
一张示意图。你运行一个个人部署。你的团队运行一个共享部署。一个数据团队运行第三个部署。你的客户端获取这三个目录:
curl http://personal.example:42617/.well-known/agents-card.json
curl http://team.example:42617/.well-known/agents-card.json
curl http://data.example:42617/.well-known/agents-card.json
每个都返回自己的代理列表。你的客户端现在会看到,例如,personal 上有一个 notes 代理,team 上有一个 deploy 代理,data 上有一个 query 代理。要使用其中任何一个,它会获取该代理的卡片,并将任务发送到该代理的 URL,完全如上所示。每次部署都没有变化;仍然是同样的两次读取和一次 POST,只是指向了不同的主机。
用例
将部署串联起来的一些具体原因。
研究部署将文献检索交给专门的数据部署。研究代理发现数据部署的 search 代理,将查询作为任务发送给它,并把结果整合到自己的工作中。研究端从不持有数据端的凭据或索引;它只知道代理 URL。
一次值班部署会将事件分发到各团队拥有的部署。它会在每个团队的部署中发现一个 triage 代理,并把同一个事件作为任务发送给每个代理,同时收集它们的回答。每个团队控制其 triage 代理暴露的内容;值班端只读取卡片并发送任务。
个人部署无需共享登录信息即可调用公司部署中经过审核的 agent。你发现公司的 invoice agent,向它发送一份草稿请求,并收到返回结果。公司决定发布哪些 agent 和技能;你永远不会获得其部署内部的席位,只会拿到 agent 端点。
A2A 不是 MCP
这些解决的是不同的问题,而且它们是可以组合的。MCP 将一个代理连接到它的工具和上下文:它回答的是单个代理可以调用什么。A2A 将一个代理连接到其他代理,作为对等方:它回答的是它可以把工作交给哪些其他代理。你通过 A2A 访问到的代理,可能会在内部使用 MCP 工具来完成工作,而你既看不到也不需要关心;卡片展示的是技能,而不是背后的工具。使用 MCP 为代理赋予能力,使用 A2A 让代理彼此委派。