Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

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_alphaagent_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。它有三项技能,zeroclawskill-creatorchangelog-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_skillsexposed_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 让代理彼此委派。