当我们将一个复杂的业务拆解成多个专职智能体(Agent)时,一个随之而来的问题是:这些 Agent 如何知道彼此的存在?如何找到对方、理解对方的能力并发起调用?
Google 主导提出的 A2A(Agent-to-Agent)协议为此提供了标准答案。它通过 AgentCard 让每个智能体对外自描述能力与接入方式,通过 服务发现 让调用方动态感知可用 Agent 的全貌。然而,协议本身只定义了规范,要将其投入生产环境,还需要一套完整的管理体系——注册、隔离、权限控制、多环境管理,缺一不可。
AgentRun 在 A2A 协议之上构建了这套生产级的管理体系。本文将从原理出发,以「希希咖啡厅」多 Agent 系统为例,完整演示如何在 AgentRun 中完成从工作空间搭建、发现端点配置,到使用 Go SDK 拉取 AgentCard、并与 Agent 完成一轮对话的全链路流程。
A2A 协议原理
AgentCard:智能体的自我介绍
在 A2A 协议中,每个智能体都通过一份 AgentCard 对外声明自己的身份和能力。AgentCard 是一份标准的 JSON 文档,描述了以下信息:
- 是谁:Agent 的名称、描述、版本、提供方;
- 能做什么:技能列表(Skills),每个技能有 ID、名称、描述和示例问法;
- 怎么访问:服务地址(URL)、支持的传输协议(JSON-RPC / gRPC);
- 有什么限制:认证方式(OAuth2、API Key 等)、是否支持流式响应。
按照 A2A 标准,AgentCard 默认托管在 /.well-known/agent-card.json 路径下。客户端只需知道 Agent 的基础 URL,就能获取这份自描述文档,进而决定如何与之通信。
一个典型的 AgentCard 结构如下:
{
"name": "coffee_agent",
"description": "希希咖啡店智能服务,可以帮助点咖啡和查询订单",
"url": "https://agent.example.com/agent",
"version": "0.0.1",
"capabilities": {
"streaming": true,
"pushNotifications": true,
"stateTransitions": ["submitted", "working", "completed", "failed"]
},
"skills": [
{
"id": "coffee_agent-get_products",
"name": "获取商品列表",
"description": "获取当前可点的咖啡饮品列表",
"examples": ["有什么咖啡推荐", "看看菜单"]
},
{
"id": "coffee_agent-create_order",
"name": "创建订单",
"description": "帮用户下单点咖啡",
"examples": ["我要一杯拿铁", "下单两杯美式"]
}
]
}
服务发现:谁在这个网络里?
有了 AgentCard 规范,还缺少一个关键问题的答案:我怎么知道有哪些 Agent 可以调用?
A2A 协议本身并未定义中心化的注册表。在实际项目中,通常需要一个「发现层」来管理 Agent 的注册和查询。发现层的职责是:接受一个查询(例如“给我这个环境里所有可用的 Agent”),返回每个 Agent 的 AgentCard URL。随后,调用方再逐一拉取 AgentCard 来完成能力感知。
通信机制:JSON-RPC 2.0 + Task 模型
A2A 协议的核心是 JSON-RPC 2.0 消息格式,配合 Task 任务模型来实现 Agent 间的通信。
消息格式
A2A 采用标准的 JSON-RPC 2.0 请求/响应模式:
// 请求
{
"jsonrpc": "2.0",
"id": 1,
"method": "tasks/sendMessage",
"params": {
"message": {
"role": "user",
"parts": [{ "type": "text", "text": "我要一杯拿铁" }]
},
"taskId": null
}
}
// 响应
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"id": "task-123",
"status": { "state": "completed" },
"messages": [...],
"artifacts": [...]
}
}
核心方法包括:
tasks/sendMessage:发送消息(创建新 Task 或继续已有 Task);
tasks/get:查询 Task 状态;
tasks/cancel:取消执行中的 Task。
Task 生命周期
每个与 Agent 的交互都被建模为一个 Task,Task 有明确的状态流转:
submitted → working → completed
↓ ↓
failed canceled
- submitted:任务已提交,等待处理;
- working:Agent 正在处理,可能通过
messageDelta 事件推送中间结果;
- completed:任务完成,返回最终结果;
- failed:执行失败;
- canceled:任务被取消。
流式响应:Server-Sent Events
A2A 支持通过 Server-Sent Events(SSE)实现流式输出。当 AgentCard 中 capabilities.streaming: true 时,客户端可以订阅任务的事件流:
// SSE 事件流
event: messageDelta
data: {"messageId": "msg-1", "parts": [{"type": "text", "text": "正在为您"}]}
event: messageDelta
data: {"messageId": "msg-1", "parts": [{"type": "text", "text": "下单..."}]}
event: taskStatusUpdate
data: {"taskId": "task-123", "status": {"state": "completed"}}
这种模式特别适合长文本生成、多步骤推理等耗时操作,用户无需等待完整响应即可看到中间进展。
推拉模式:主动通知
除了客户端主动拉取(polling),A2A 还支持服务端主动推送。当 capabilities.pushNotifications: true 时,Agent 可以在任务状态变化时主动通知客户端:
// 客户端在初始请求中携带 pushNotification URL
{
"params": {
"message": {...},
"pushNotification": {
"url": "https://client.example.com/callback",
"token": "optional-auth-token"
}
}
}
这样,Agent 处理完任务后可以直接回调客户端,无需客户端轮询。
技能调用:Skills 机制
AgentCard 中的 skills 数组定义了 Agent 可以执行的原子能力。每个 Skill 包含:
id:唯一标识符;
name:人类可读名称;
description:功能描述;
examples:示例问法(帮助 LLM 理解何时应该调用这个 Skill)。
调用方在拿到 AgentCard 后,可以:
- 将 Skills 告诉主控 Agent(Orchestrator),让它根据用户意图选择合适的 Skill;
- 在
tasks/sendMessage 时通过 skillId 参数直接指定调用某个 Skill;
- 在简单场景下,让 Agent 自行判断使用哪个 Skill(依赖 LLM 的推理能力)。
AgentRun 的多 Agent 管理:注册、发现与隔离
AgentRun 在 A2A 协议基础上,提供了一套生产级的多 Agent 管理体系,其核心围绕三个概念展开。
工作空间(Workspace):逻辑隔离的 Agent 集合
工作空间是 AgentRun 中组织 Agent 的基本单位,类似于一个「项目空间」或「命名空间」。不同业务域、不同团队的 Agent 可以分属不同的工作空间,从而实现互相隔离和独立的权限管理。一个 Agent Runtime 在归属于某个工作空间后,该工作空间就成为其对外可被发现的范围边界。
发现端点(Discovery Endpoint):按环境隔离的发现入口
一个工作空间内可以配置多个发现端点,典型的用法是按部署环境进行区分。例如:

每个发现端点维护了一张映射表,记录了“哪个 Agent”对应“哪个访问地址”。同一个 Agent 在不同的端点中可以配置不同的地址(例如开发环境地址 vs 生产环境自定义域名)。
平台托管 vs 外部 Agent:统一的发现体验
AgentRun 支持两类 Agent 共存于同一工作空间:
| 类型 |
部署方式 |
注册方式 |
状态流转 |
| 平台托管 Agent |
AgentRun 负责部署到运行时 |
通过平台创建注册 |
CREATING → READY |
| 外部 Agent |
自行部署在任意位置 |
手动注册到指定工作空间 |
直接 READY |
这两类 Agent 在发现端点中的表现完全一致——调用方拿到的都是标准的 a2aAgentCardUrl,无需关心 Agent 实际部署在哪里。
凭证安全保护:谁可以发现这些 Agent?
服务发现信息本身就属于敏感信息,暴露工作空间内有哪些 Agent 及其位置可能为攻击者提供侦察入口。因此,AgentRun 在发现端点上内置了完整的凭证验证体系,支持多种行业标准的认证方式,可按场景灵活选择。
支持的凭证类型
API Key
这是最轻量的接入方式,适合服务端对服务端的调用场景。平台颁发一个 Key,调用方在请求头中携带即可。
# 使用自定义 Header(推荐,更语义化)
curl -H ‘X-API-Key: <your-api-key>‘ \
‘https://.../workspaces/<workspace-name>/discovery/agents?discoveryEndpointName=default‘
# 或使用标准 Authorization Bearer 方式
curl -H ‘Authorization: Bearer <your-api-key>‘ \
‘https://.../workspaces/<workspace-name>/discovery/agents?discoveryEndpointName=default‘
Header 名称(如 X-API-Key)和 prefix(如 Bearer)均可在凭证配置中自定义。此外,Key 也可以通过 URL 查询参数 ?Authorization=<key> 传递,方便在不便设置请求头的环境中使用。
HTTP Basic Auth
兼容标准 HTTP Basic 认证,适合对接遗留系统或工具链中不便使用 Token 的场景。
curl -u ‘username:password‘ \
‘https://.../workspaces/my-workspace/discovery/agents?discoveryEndpointName=default‘
凭证与工作空间的绑定关系
凭证配置与工作空间是解耦的。你可以在平台统一管理凭证,然后将某个凭证绑定到指定工作空间的服务发现配置上。绑定之后:
- 未携带有效凭证的请求会被拒绝(401 / 403);
- 更换凭证时只需在平台重新绑定,无需修改任何 Agent 的代码;
- 不同工作空间可以绑定不同的凭证,实现精细化的访问控制。
实战体验
准备工作:创建工作空间并注册 Agent
第一步:部署模版多 Agent 系统
本文以「希希咖啡厅」多 Agent 系统作为演示对象。在 AgentRun 控制台的 Agent 模版 页面找到该模版,点击一键部署,平台会自动创建并启动以下两个 Agent:
- coffee_agent:希希咖啡店智能服务,负责点单、查看菜单、查询订单;
- delivery_agent:送了么配送助手,负责安排配送和查询配送状态。
等待两个 Agent 状态变为 Ready 后进行下一步。

第二步:创建工作空间
在 AgentRun 控制台新建一个工作空间(Workspace)。工作空间是组织和隔离 Agent 的基本单元,后续的服务发现都以工作空间为范围。

第三步:将 Agent 注册到工作空间
有两种方式将 Agent 纳入工作空间:
- 方式一:创建新 Agent 时指定工作空间:在创建 Agent Runtime 时,填写
workspaceId 字段,Agent 会直接归属到该工作空间。
- 方式二:将已有 Agent 移入工作空间:在 Agent 详情页或工作空间管理页面,将已有的 Agent Runtime 迁移到目标工作空间。
外部 Agent 也可接入:如果你有运行在 AgentRun 平台外部的 A2A 兼容服务(自建服务、第三方 Agent),同样可以注册进来。注册时勾选「外部 Agent」选项并填写对外服务地址即可,平台不会尝试部署,其状态立即变为 Ready,后续的发现和调用方式与平台托管 Agent 完全一致。
配置发现端点
Agent 注册好之后,还需要在工作空间中配置「发现端点」,才能让外部调用方通过统一入口发现这些 Agent。
在工作空间管理页面,进入「服务发现」或「Discovery」配置,添加一个发现端点(例如命名为 default),然后将工作空间内的 Agent 逐一配置进来,填写每个 Agent 的访问地址。



配置访问凭证
为了防止发现端点被未授权访问,可以为工作空间绑定一个 API Key 凭证。配置后,所有对该工作空间发现端点的请求都需要在请求头中携带 X-API-Key。
在工作空间设置中,选择或创建一个凭证,将其关联到该工作空间的服务发现配置上。
调用发现端点
配置完成后,可以用下面的方式验证发现端点是否正常工作:
curl -s \
-H ‘X-API-Key: <your-api-key>‘ \
‘https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com/workspaces/<workspace-name>/discovery/agents?discoveryEndpointName=default‘
响应示例:
{
“code“: “SUCCESS“,
“data“: {
“items“: [
{
“agentRuntimeName“: “coffee-agent“,
“protocolConfiguration“: {
“protocolSettings“: [
{
“name“: “A2A“,
“a2aAgentCardUrl“: “https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/coffee-agent/endpoints/Default/invocations/.well-known/agent-card.json“
}
]
}
}
],
“pageNumber“: 1,
“pageSize“: 1,
“total“: 1
}
}
每个 Agent 的 a2aAgentCardUrl 就是接下来使用 A2A SDK 发起连接的入口。
基于 a2a-go SDK 的发现 Demo
以下是一个完整的 Go Demo,展示如何从发现端点解析出 AgentCard URL,再使用 a2a-go SDK 获取 AgentCard 并与 Agent 通信。完整代码位于 examples/a2a-discovery-demo/。
package main
import (
“context“
“encoding/json“
“flag“
“fmt“
“io“
“log“
“net/http“
“os“
“github.com/a2aproject/a2a-go/a2a“
“github.com/a2aproject/a2a-go/a2aclient“
“github.com/a2aproject/a2a-go/a2aclient/agentcard“
)
// DiscoveryResponse 是 AgentRun 发现端点的响应结构
// 实际响应格式:{“code“:“SUCCESS“,“data“:{“items“:[...]}}
type DiscoveryResponse struct {
Code string `json:“code“`
Data *DiscoveryData `json:“data,omitempty“`
AgentRuntimes []AgentRuntime `json:“agentRuntimes,omitempty“` // 兼容旧版格式
}
type DiscoveryData struct {
Items []AgentRuntime `json:“items“`
PageNumber int `json:“pageNumber“`
PageSize int `json:“pageSize“`
Total int `json:“total“`
}
type AgentRuntime struct {
AgentRuntimeName string `json:“agentRuntimeName“`
ProtocolConfiguration *ProtocolConfiguration `json:“protocolConfiguration,omitempty“`
}
type ProtocolConfiguration struct {
ProtocolSettings []ProtocolSetting `json:“protocolSettings“`
}
type ProtocolSetting struct {
Name string `json:“name“`
A2AAgentCardURL string `json:“a2aAgentCardUrl,omitempty“`
}
// Config 保存运行时的所有端点配置
type Config struct {
DiscoveryBaseURL string // AgentRun 平台基础地址,格式:https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com
WorkspaceID string // 工作空间名称
DiscoveryEndpointName string // 发现端点名称,通常为 “default“
APIKey string // API Key 凭证
}
func (c *Config) discoveryURL() string {
return fmt.Sprintf(
“%s/workspaces/%s/discovery/agents?discoveryEndpointName=%s“,
c.DiscoveryBaseURL, c.WorkspaceID, c.DiscoveryEndpointName,
)
}
func main() {
var cfg Config
flag.StringVar(&cfg.DiscoveryBaseURL, “base-url“,
envOrDefault(“AGENTRUN_BASE_URL“, ““), “AgentRun 平台基础地址“)
flag.StringVar(&cfg.WorkspaceID, “workspace-id“,
envOrDefault(“AGENTRUN_WORKSPACE_ID“, ““), “工作空间名称“)
flag.StringVar(&cfg.DiscoveryEndpointName, “endpoint-name“,
envOrDefault(“AGENTRUN_ENDPOINT_NAME“, “default“), “发现端点名称“)
flag.StringVar(&cfg.APIKey, “api-key“,
envOrDefault(“AGENTRUN_API_KEY“, ““), “API Key 凭证“)
flag.Parse()
ctx := context.Background()
// ── 第一步:请求发现端点,获取 Agent 列表 ─────────────────────────────
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, cfg.discoveryURL(), nil)
req.Header.Set(“X-API-Key“, cfg.APIKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf(“请求发现端点失败: %v“, err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var discovery DiscoveryResponse
if err := json.Unmarshal(body, &discovery); err != nil {
log.Fatalf(“解析发现响应失败: %v“, err)
}
// ── 第二步:从响应中提取 A2AAgentCardURL ─────────────────────────────
// 响应嵌套在 data.items 中(旧版在顶层 agentRuntimes)
runtimes := discovery.AgentRuntimes
if discovery.Data != nil && len(discovery.Data.Items) > 0 {
runtimes = discovery.Data.Items
}
var firstCardURL string
for _, runtime := range runtimes {
if runtime.ProtocolConfiguration == nil {
continue
}
for _, ps := range runtime.ProtocolConfiguration.ProtocolSettings {
if ps.Name == “A2A“ && ps.A2AAgentCardURL != ““ {
fmt.Printf(“发现 Agent: %s\n AgentCard URL: %s\n\n“,
runtime.AgentRuntimeName, ps.A2AAgentCardURL)
if firstCardURL == ““ {
firstCardURL = ps.A2AAgentCardURL
}
}
}
}
if firstCardURL == ““ {
log.Fatal(“未发现任何 A2A Agent“)
}
// ── 第三步:用 agentcard.Resolver 拉取 AgentCard ──────────────────────
// A2AAgentCardURL 已是完整 URL,WithPath(““) 跳过默认路径拼接
card, err := agentcard.DefaultResolver.Resolve(ctx, firstCardURL, agentcard.WithPath(““))
if err != nil {
log.Fatalf(“获取 AgentCard 失败: %v“, err)
}
fmt.Printf(“AgentCard 信息:\n“)
fmt.Printf(“ 名称: %s\n“, card.Name)
fmt.Printf(“ 描述: %s\n“, card.Description)
fmt.Printf(“ 版本: %s\n“, card.Version)
fmt.Printf(“ 服务地址: %s\n“, card.URL)
fmt.Printf(“ 支持流式: %v\n“, card.Capabilities.Streaming)
for _, skill := range card.Skills {
fmt.Printf(“ - [%s] %s: %s\n“, skill.ID, skill.Name, skill.Description)
}
// ── 第四步:建立 A2A 客户端,向 Agent 发送消息 ───────────────────────
// NewFromCard 根据 AgentCard 声明的 preferredTransport 自动选择传输协议
client, err := a2aclient.NewFromCard(ctx, card)
if err != nil {
log.Fatalf(“创建 A2A 客户端失败: %v“, err)
}
defer client.Destroy()
msg := a2a.NewMessage(a2a.MessageRoleUser, a2a.TextPart{Text: “你好,请介绍一下你能做什么?“})
result, err := client.SendMessage(ctx, &a2a.MessageSendParams{Message: msg})
if err != nil {
log.Fatalf(“发送消息失败: %v“, err)
}
// SendMessageResult 是 interface,实际类型为 *a2a.Task 或 *a2a.Message
switch r := result.(type) {
case *a2a.Task:
fmt.Printf(“\nAgent 回复 (Task, 状态: %s):\n“, r.Status.State)
for _, artifact := range r.Artifacts {
for _, part := range artifact.Parts {
if tp, ok := part.(a2a.TextPart); ok {
fmt.Printf(“ %s\n“, tp.Text)
}
}
}
case *a2a.Message:
fmt.Printf(“\nAgent 回复 (Message):\n“)
for _, part := range r.Parts {
if tp, ok := part.(a2a.TextPart); ok {
fmt.Printf(“ %s\n“, tp.Text)
}
}
}
}
func envOrDefault(key, defaultVal string) string {
if v := os.Getenv(key); v != ““ {
return v
}
return defaultVal
}
运行方式
通过 run_demo.sh 脚本运行,所有端点参数通过环境变量传入:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=“$(cd “$(dirname “${BASH_SOURCE[0]}”)” && pwd)“
# ── 端点配置(可通过环境变量覆盖) ─────────────────────────────────────────
BASE_URL=“${AGENTRUN_BASE_URL:?请设置 AGENTRUN_BASE_URL,例如 https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com}“
WORKSPACE_ID=“${AGENTRUN_WORKSPACE_ID:?请设置 AGENTRUN_WORKSPACE_ID,即工作空间名称}“
ENDPOINT_NAME=“${AGENTRUN_ENDPOINT_NAME:-default}“
API_KEY=“${AGENTRUN_API_KEY:?请设置 AGENTRUN_API_KEY}“
cd “$SCRIPT_DIR“
echo “▶ 运行 A2A Discovery Demo“
echo “ base-url: $BASE_URL“
echo “ workspace-id: $WORKSPACE_ID“
echo “ endpoint-name: $ENDPOINT_NAME“
echo ““
go run main.go \
--base-url “$BASE_URL“ \
--workspace-id “$WORKSPACE_ID“ \
--endpoint-name “$ENDPOINT_NAME“ \
--api-key “$API_KEY“
执行命令:
export AGENTRUN_BASE_URL=https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com
export AGENTRUN_WORKSPACE_ID=<workspace-name>
export AGENTRUN_API_KEY=<your-api-key>
./examples/a2a-discovery-demo/run_demo.sh
执行结果
▶ 运行 A2A Discovery Demo
base-url: https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com
workspace-id: <workspace-name>
endpoint-name: default
=== A2A Discovery Demo ===
发现端点: https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com/workspaces/<workspace-name>/discovery/agents?discoveryEndpointName=default
发现 2 个 A2A Agent:
- buy-me-a-coffeev-<workspace-name>_coffee_agent
AgentCard URL: https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/buy-me-a-coffeev-<workspace-name>_coffee_agent/endpoints/Default/invocations/.well-known/agent-card.json
- buy-me-a-coffeev-<workspace-name>_delivery_agent
AgentCard URL: https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/buy-me-a-coffeev-<workspace-name>_delivery_agent/endpoints/Default/invocations/.well-known/agent-card.json
正在获取 AgentCard: https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/buy-me-a-coffeev-<workspace-name>_coffee_agent/endpoints/Default/invocations/.well-known/agent-card.json
AgentCard 信息:
名称: coffee_agent
描述: 希希咖啡店智能服务,可以帮助点咖啡和查询订单
版本: 0.0.1
服务地址: https://<uid>.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/buy-me-a-coffeev-<workspace-name>_coffee_agent/endpoints/Default/invocations
支持流式: false
技能列表:
- [coffee_agent] model: 希希咖啡店智能服务,可以帮助点咖啡和查询订单
- [coffee_agent-get_products_api_coffee_products_get] get_products_api_coffee_products_get: 获取商品列表
- [coffee_agent-create_order_api_coffee_orders_post] create_order_api_coffee_orders_post: 创建订单
- [coffee_agent-get_order_api_coffee_orders__order_id__get] get_order_api_coffee_orders__order_id__get: 获取订单详情
...(共 9 个技能)
发送消息: “你好,请介绍一下你能做什么?“
Agent 回复 (Task, 状态: completed):
你好!我是希希咖啡的智能服务助手,可以帮你做以下几件事情哦:
- 点咖啡:为你推荐饮品、帮助下单。
- 查询订单:查看你的订单状态或获取最近的订单信息。
- 提供门店基础信息:比如地址、营业时间等。
有什么我可以帮到你的吗?😊☕️
总结
本文完整地走通了在 AgentRun 平台上基于 A2A 协议实现多智能体服务发现与通信的全链路流程。从理解 A2A 协议的核心概念(AgentCard、服务发现、Task模型),到在 AgentRun 中实践工作空间管理、发现端点配置,再到使用官方的 Go SDK 进行集成与调用,每一步都力求清晰。
A2A 的价值远不止于此演示的单轮对话。一旦你的智能体网络通过服务发现机制互相可见,主控(Orchestrator)Agent 就能在运行时动态地拼装调用链,将复杂的任务分发给最合适的专职 Agent 去执行——这正是实现多智能体高效协作的核心模式。
AgentRun 通过其云原生的工作空间与发现端点机制,为这套先进的协作模式提供了生产环境所需的管理性、扩展性与可审计性,让构建复杂的 多智能体系统 变得切实可行。