Skip to content

Agent 间协作 A2A

A2A 用于一个数字员工在处理当前用户消息时, 通过知办AI Bus 受控调用另一个数字员工完成子任务。它属于 自定义 Agent Runtime 接入能力, 不是普通租户服务端 API。

典型链路:

text
用户消息
  -> Agent A Runtime
  -> Agent A 的 LLM 判断需要协作, 并生成给 Agent B 的干净任务指令
  -> SDK A2A discover / invoke / get_edge
  -> 知办AI Bus 做权限、白名单、深度、防环、审计和转发
  -> Agent B Runtime 执行 request_payload.user_text
  -> Agent A 汇总 Agent B 结果后回复用户

端到端流程图

每个环节的输入输出示例

下面示例用“行政专员找法务顾问审阅供应商服务合同”说明完整链路。UUID 均为示例值; st_...、endpoint、内部 numeric ID 不应出现在业务 payload 里。

1. 用户消息进入 Agent A

输入: 客户端只发送给当前会话的普通用户消息, 不指定 A2A 目标。

json
{
  "conversation_id": "conv-admin-20260710-001",
  "agent_id": "agent-admin-uuid",
  "role": "user",
  "text": "帮我找法务顾问看一下这份供应商服务合同的付款节点、违约责任和保密条款风险, 然后汇总成审批前确认清单。",
  "blocks": []
}

输出: Bus 把当前轮消息派发给 Agent A Runtime。Agent A 此时还没有调用其它 Agent。

json
{
  "session": {
    "session_kind": "single",
    "tenant_id": "tenant-uuid-001",
    "user_id": "account-uuid-15011553109",
    "conversation_id": "conv-admin-20260710-001",
    "agent_id": "agent-admin-uuid"
  },
  "message": {
    "role": "user",
    "text": "帮我找法务顾问看一下这份供应商服务合同的付款节点、违约责任和保密条款风险, 然后汇总成审批前确认清单。",
    "blocks": []
  }
}

2. Agent A 生成被调方任务

输入: Agent A 的 LLM 可以读取当前用户消息、当前会话上下文、可协作员工摘要。这里是 Agent A 内部决策, 不是 Bus 接口。

json
{
  "current_user_message": "帮我找法务顾问看一下这份供应商服务合同的付款节点、违约责任和保密条款风险, 然后汇总成审批前确认清单。",
  "caller_agent": {
    "agent_id": "agent-admin-uuid",
    "name": "行政专员",
    "role": "行政协同与流程支持"
  },
  "available_agent_summaries": [
    {
      "agent_id": "agent-legal-uuid",
      "name": "法务顾问",
      "summary": "审阅合同条款、付款节点、违约责任、保密与合规风险。"
    }
  ],
  "conversation_context": [
    {
      "type": "plain",
      "text": "用户正在准备供应商服务合同审批。"
    }
  ]
}

输出: Agent A LLM 选择目标, 并生成“给 Agent B 执行”的干净任务。这个输出会进入后续 request_payload.user_text

json
{
  "target_agent_id": "agent-legal-id",
  "target_agent_name": "法务顾问",
  "request_payload": {
    "user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
    "context_blocks": [
      {
        "type": "plain",
        "text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
      }
    ]
  }
}

注意: user_text 里不应再出现“帮我找法务顾问”“联系某某”“你就是被联系的人”等路由话术。

3. discover: 查询当前可协作候选

输入: Agent A 通过 SDK 或 runtime tool 发起 discover。实际 HTTP 鉴权由 SDK 使用 agent-bound runtime access token 完成。

http
POST /zap/v1/internal/a2a/discover
Authorization: Bearer st_agent_bound_runtime_access
Content-Type: application/json
json
{
  "query": "供应商合同付款节点、违约责任、保密条款风险审阅",
  "tags": ["legal"],
  "mode": "sync",
  "limit": 20,
  "cursor": ""
}

输出: Bus 返回可发现候选。discover 只是候选提示, 不是最终授权; invoke 还会重新校验。

json
{
  "agents": [
    {
      "agent_id": "agent-legal-uuid",
      "name": "法务顾问",
      "summary": "合同、条款、合规和法律风险审阅。",
      "when_to_use": ["合同付款节点审阅", "违约责任确认", "保密条款风险评估"],
      "when_not_to_use": ["普通会议室预订", "报销单录入"],
      "examples": [
        {
          "input": "审阅供应商合同里的付款节点和保密条款",
          "expect": "输出法律风险点和审批前确认清单"
        }
      ],
      "tags": ["legal", "contract"],
      "input_schema": {
        "type": "object",
        "properties": {
          "user_text": {"type": "string"},
          "context_blocks": {"type": "array"}
        },
        "required": ["user_text"]
      },
      "supported_modes": ["sync", "async"],
      "risk_level": "medium",
      "requires_approval": false,
      "match_score": 0.91
    }
  ],
  "next_cursor": "",
  "confidence": 0.91,
  "should_clarify": false,
  "clarify_question": ""
}

4. invoke: 创建 Agent A 到 Agent B 的任务 edge

输入: Agent A 指定目标 Agent B, 并把 LLM 生成的干净任务放入 request_payload.user_text

http
POST /zap/v1/internal/a2a/invoke
Authorization: Bearer st_agent_bound_runtime_access
Content-Type: application/json
json
{
  "to_agent_id": "agent-legal-uuid",
  "invoke_kind": "sync",
  "idempotency_key": "conv-admin-20260710-001:msg-0007:tool-a2a-legal-001",
  "request_payload": {
    "user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
    "context_blocks": [
      {
        "type": "plain",
        "text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
      }
    ]
  }
}

链式调用时, 被调方如果确实需要继续调用第三个 Agent, 还要带上父 edge:

json
{
  "to_agent_id": "agent-finance-uuid",
  "invoke_kind": "sync",
  "idempotency_key": "edge-legal-uuid:tool-a2a-finance-001",
  "parent_edge_id": "edge-legal-uuid",
  "parent_edge_created_at": "2026-07-10T02:31:20.123456Z",
  "request_payload": {
    "user_text": "核对该供应商合同的付款周期和发票要求是否符合财务审批口径。",
    "context_blocks": [
      {
        "type": "plain",
        "text": "这是法务顾问在审阅合同风险时发起的财务协作。"
      }
    ]
  }
}

输出: Bus 创建 edge 并返回回执。调用方后续必须用 edge_id + edge_created_at 查询状态。

json
{
  "edge_id": "edge-legal-uuid",
  "edge_created_at": "2026-07-10T02:31:20.123456Z",
  "status": "pending",
  "approval_request_id": ""
}

approval_request_id 仅在本次调用进入人工审批时返回;普通调用为空并省略。

5. Bus 治理与 edge 记录

输入: Bus 使用 invoke 请求、agent-bound token、租户上下文和协作配置做校验。

json
{
  "tenant_id": "tenant-uuid-001",
  "from_agent_id": "agent-admin-uuid",
  "to_agent_id": "agent-legal-uuid",
  "user_id": "account-uuid-15011553109",
  "checks": {
    "from_agent_has_a2a_invoke": true,
    "to_agent_callable_by_from_agent": true,
    "same_tenant": true,
    "depth_within_limit": true,
    "chain_budget_available": true,
    "loop_guard_passed": true,
    "runtime_instance_ready": true
  },
  "request_payload": {
    "user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
    "context_blocks": [
      {
        "type": "plain",
        "text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
      }
    ]
  }
}

输出: Bus 持久化 edge, 并只把脱敏副本用于审计和管理页展示。

json
{
  "edge_id": "edge-legal-uuid",
  "status": "running",
  "invoke_kind": "sync",
  "from_agent_id": "agent-admin-uuid",
  "to_agent_id": "agent-legal-uuid",
  "root_edge_id": "edge-legal-id",
  "parent_edge_id": "",
  "depth": 1,
  "origin_conversation_id": "conv-admin-20260710-001",
  "request_payload_redacted": {
    "user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
    "context_blocks": [
      {
        "type": "plain",
        "text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
      }
    ]
  }
}

6. Bus 派发给 Agent B Runtime

输入: 对 external ZAP runtime, Bus 创建 A2A session, 再发送一条 user message。bus_backchannel_token 只给 Agent B runtime 使用, 不得进日志和回复。

http
POST /zap/v1/sessions
Authorization: Bearer runtime_dispatch_token
Content-Type: application/json
json
{
  "agent_key": "main",
  "session_kind": "a2a",
  "user": {
    "tenant_id": "tenant-uuid-001",
    "user_id": "account-uuid-15011553109"
  },
  "conversation_context_hint": {
    "tenant_id": "tenant-uuid-001",
    "account_id": "account-uuid-15011553109",
    "purpose": "a2a",
    "a2a_edge_id": "edge-legal-uuid"
  },
  "initiator": {
    "kind": "agent",
    "agent_id": "agent-admin-uuid",
    "tenant_id": "tenant-uuid-001"
  },
  "bus_backchannel_token": "st_child_secret_never_log"
}
http
POST /zap/v1/sessions/sess-a2a-legal-001/messages
Authorization: Bearer runtime_dispatch_token
Content-Type: application/json
json
{
  "role": "user",
  "text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
  "blocks": [
    {
      "type": "plain",
      "text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
    }
  ]
}

输出: Agent B handler 看到的是 A2A 上下文和当前任务。它不需要知道用户原始路由话术。

json
{
  "ctx": {
    "is_a2a": true,
    "a2a_edge_id": "edge-legal-uuid",
    "initiator": {
      "kind": "agent",
      "agent_id": "agent-admin-uuid",
      "tenant_id": "tenant-uuid-001"
    }
  },
  "inbound": {
    "role": "user",
    "text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
    "blocks": [
      {
        "type": "plain",
        "text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
      }
    ]
  }
}

OpenClaw / Hermes 被调方也是同一个语义: Bus 把 request_payload.user_text 作为当前任务文本派发给对应托管 runtime; runtime 不需要原生理解 Bus A2A 协议。

7. Agent B 返回子任务结果

输入: Agent B 执行收到的任务。

json
{
  "task": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
  "context_blocks": [
    {
      "type": "plain",
      "text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
    }
  ]
}

输出: Agent B 返回当前子任务结果。Bus 会把结果写回 edge; 入库副本会做结构化脱敏。

json
{
  "status": "done",
  "response_payload": {
    "text": "法务审阅意见:\n1. 付款节点应与明确交付物、验收标准和验收时间绑定, 避免仅按自然日期付款。\n2. 违约责任需覆盖延期交付、质量不达标、提前终止和未配合整改等场景, 并约定可执行的违约金或赔偿上限。\n3. 保密条款应明确保密信息范围、接触人员范围、保存期限、返还/销毁义务和违约责任。"
  }
}

8. Agent A 查询 edge 并汇总最终回复

输入: Agent A 用 invoke 回执里的 edge_idedge_created_at 查询结果。

http
GET /zap/v1/internal/a2a/edges/edge-legal-uuid?created_at=2026-07-10T02%3A31%3A20.123456Z
Authorization: Bearer st_agent_bound_runtime_access

输出: Bus 返回 edge 终态和脱敏后的被调方结果。

json
{
  "edge_id": "edge-legal-uuid",
  "status": "done",
  "invoke_kind": "sync",
  "from_agent_id": "agent-admin-uuid",
  "to_agent_id": "agent-legal-uuid",
  "created_at": "2026-07-10T02:31:20.123456Z",
  "updated_at": "2026-07-10T02:31:28.654321Z",
  "request_payload": {
    "user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
    "context_blocks": [
      {
        "type": "plain",
        "text": "发起方是行政专员; 用户目标是提交供应商服务合同审批前确认。"
      }
    ]
  },
  "response_payload": {
    "text": "法务审阅意见:\n1. 付款节点应与明确交付物、验收标准和验收时间绑定, 避免仅按自然日期付款。\n2. 违约责任需覆盖延期交付、质量不达标、提前终止和未配合整改等场景, 并约定可执行的违约金或赔偿上限。\n3. 保密条款应明确保密信息范围、接触人员范围、保存期限、返还/销毁义务和违约责任。"
  },
  "error_message": ""
}

Agent A 最终给用户的回复必须由 Agent A 自己生成, 不能由 Bus 拼接, 也不能把 Agent B 原文无上下文地直接当作 Agent A 正文。

json
{
  "role": "assistant",
  "text": "我已请法务顾问审阅这份供应商服务合同, 审批前建议重点确认三项:\n\n1. 付款节点要绑定明确交付物、验收标准和验收时间, 不建议只按自然日期付款。\n2. 违约责任要覆盖延期交付、质量不达标、提前终止和未配合整改等场景, 并明确违约金或赔偿上限。\n3. 保密条款要补齐保密信息范围、接触人员范围、保存期限、返还/销毁义务和违约责任。\n\n建议你在提交正式审批前, 先让采购/业务负责人补齐合同正文、报价单、验收标准和预算依据, 再发起用印流程。",
  "blocks": [
    {
      "type": "markdown",
      "text": "我已请法务顾问审阅这份供应商服务合同..."
    }
  ]
}

放在哪一层理解

层级是否负责 A2A说明
客户端客户端只发送用户消息, 不直接发起 A2A。
业务服务端 API/api/external/v1/* 用于知识库、记忆、组织、通知等能力, 不用于 Agent 间调度。
自定义 Agent RuntimeAgent A 在运行时决定是否调用 Agent B, 并通过 SDK 的 A2A helper 调 Bus。
Bus治理与转发Bus 不生成任务, 不替 Agent A 拼最终回复; 只做鉴权、配额、防环、审计和派发。
被调 Agent RuntimeAgent B 收到当前轮干净任务并执行。

调用时机

适合 A2A 的场景:

  • 行政专员需要法务顾问审阅合同风险后再汇总审批清单。
  • 客户跟进助手需要财税助手核对续费发票口径。
  • 研发工程师需要行政协同专员补会议、通知、排期准备。
  • 一个自定义 Agent 需要另一个岗位 Agent 输出专业意见, 再由自己形成最终答复。

不适合 A2A 的场景:

  • 只是查知识库、记忆、组织或发送通知, 直接用对应服务端 API。
  • 用户只是问当前 Agent 自己能做什么, 不应为了回答能力介绍而调用其它 Agent。
  • 客户端想指定两个 Agent 对话, 客户端不直接调 A2A; 应通过正常消息交给当前 Agent Runtime 判断。

核心契约

所有 A2A invoke 统一为:

  1. Agent A 的 LLM 根据当前用户消息、被调方画像和当前任务生成给 Agent B 的任务指令。
  2. 任务指令放到 request_payload.user_text
  3. 可选低敏背景放到 request_payload.context_blocks
  4. Bus 只做治理和仅泄露项清洗, 不生成任务、不清路由词、不改写业务语义。
  5. Agent B 只把 request_payload.user_text 当作当前轮要执行的任务。

推荐 payload:

json
{
  "user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
  "context_blocks": [
    {
      "type": "plain",
      "text": "来自行政专员的合同协作请求。"
    }
  ]
}

不要这样传:

json
{
  "instruction": "帮我找法务顾问看一下合同",
  "expected_output": "给我结果"
}

也不要把下面内容放进 payload:

  • session_key
  • bearer token / API key / client secret
  • runtime endpoint
  • Bus 内部 id、候选列表、权限细节
  • “你就是被联系的人”“不要再找你自己”这类元指令

Python SDK 示例

python
from zhiban_agent_sdk import ZhibanClient

with ZhibanClient.for_runtime_access(
    ZHIBAN_BASE_URL,
    ZHIBAN_CLIENT_ID,
    ZHIBAN_CLIENT_SECRET,
    tenant_id=ZHIBAN_TENANT_ID,
    agent_id=AGENT_A_UUID,
    account_id=ACCOUNT_UUID,  # 调平台托管 OpenClaw / Hermes 时用于选择当前用户实例
) as client:
    candidates = client.a2a.discover(
        limit=20,
        query="需要法务审阅供应商合同风险",
    ).agents

    legal = next(a for a in candidates if "法务" in a.name)

    ack = client.a2a.invoke(
        to_agent_id=legal.agent_id,
        invoke_kind="sync",
        idempotency_key="message-uuid:tool-call-1",
        request_payload={
            "user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
            "context_blocks": [
                {"type": "plain", "text": "来自行政专员的合同协作请求。"}
            ],
        },
    )

    edge = client.a2a.get_edge(ack.edge_id, created_at=ack.edge_created_at)
    print(edge["status"], edge.get("response_payload"))

Go SDK 示例

go
payload := json.RawMessage(`{
  "user_text": "审阅这份供应商服务合同的付款节点、违约责任和保密条款风险, 并输出审批前确认清单。",
  "context_blocks": [
    {"type": "plain", "text": "来自行政专员的合同协作请求。"}
  ]
}`)

ack, err := client.A2A().Invoke(ctx, zhibansdk.InvokeRequest{
    ToAgentUUID:    legalAgentUUID,
    InvokeKind:     "sync",
    IdempotencyKey: "message-uuid:tool-call-1",
    RequestPayload: payload,
})
if err != nil {
    return err
}

edge, err := client.A2A().GetEdge(ctx, ack.EdgeUUID, ack.EdgeCreatedAt)
if err != nil {
    return err
}
fmt.Println(edge.Status, string(edge.ResponsePayload))

discover 与 invoke 的关系

discover 只返回当前上下文中可被发现的候选 Agent, 供 Agent A 的 LLM 或规则选择目标。它不是授权结果。

invoke 会重新校验:

  • Agent A 是否有 a2a_invoke 发起能力。
  • Agent B 是否允许 Agent A 调用。
  • 双方是否在同一租户授权范围内。
  • 当前调用链是否超过深度、预算或防环限制。
  • 被调 Agent Runtime 是否有可用实例。

因此, 不要把 discover 结果缓存成永久可调用清单。可以在一次对话或短时间内缓存候选列表, 但每次 invoke 仍以 Bus 的校验为准。

错误处理

错误码含义建议处理
a2a.no_candidate没有可发现的目标 Agent向用户说明暂时找不到合适协作对象, 或让用户指定更明确岗位。
a2a.runtime_unavailable被调方 runtime 实例不可用提示稍后重试, 并让管理员检查对应托管环境实例。
a2a.auth_or_scope凭证或 scope 不满足检查 Agent SDK 凭证、runtime access token 和 agent_id 绑定。
a2a.chain_timeout等待被调方结果超时返回可理解的暂时失败, 不编造被调方结论。
a2a.awaiting_approval本次调用需要人工审批告知用户已进入审批等待。
a2a.dropped_budget / a2a.dropped_loop超预算或检测到循环停止继续调用, 汇总已有结果。

实现建议

  • Agent A 应在调用前用 LLM 生成 user_text, 规则只做兜底和敏感字段清洗。
  • idempotency_key 用当前消息和当前 tool call 生成; 同一次重试复用, 新用户消息必须换新 key。
  • 被调方如果收到 A2A 入站请求, 默认不要再自动触发泛化 A2A; 只有任务内容明确要求继续协作时才考虑链式调用。
  • 真实业务回复由 Agent A 生成: Agent B 只提供子任务结果, Bus 不替 Agent A 总结。
  • 日志里可以记录 edge、阶段、目标 Agent 名称和脱敏后的任务摘要; 不记录 token、runtime key 或完整敏感 payload。

相关文档

  • ZAP 消息协议
  • EMP 组件清单
  • 自定义 Agent Runtime 接入指南: 返回当前文档站左侧菜单的「自定义 Agent Runtime 接入 / 接入指南」。
  • 服务端 API 总览: 返回当前文档站左侧菜单的「服务端 API / 总览」。

© 知办AI Team — 开发者公开文档. 平台运维 / 内部架构文档见内部 wiki.