一、一个续约问题,为什么会需要四种连接

用户在网页里问:“找出合同的自动续约条款,核对我们当前套餐,如果有争议,请供应商专家确认。” 如果只画一个 agent.run(),这句话后面至少藏着四个独立问题:

  1. 网页怎样知道 agent 已开始、正在查合同、等待确认,还是已经失败?
  2. Agent 怎样发现“合同搜索”和“CRM 套餐查询”,并以对方能理解的参数调用?
  3. 供应商专家是另一个独立 agent,我们怎样知道它会什么,又怎样跟踪一个可能运行几分钟的任务?
  4. 当 runtime 请求模型思考下一步时,历史、工具调用和流式返回怎样交给模型 provider?

这四个问题的两边都可能由不同团队、语言和产品实现。如果每次都自定义一组 JSON 和回调, 换前端、换工具服务或换远程 agent 都会重写适配层。协议要解决的,正是这种跨实现协作。

二、先给“协议”一个不绕的定义

协议是两个可以独立实现、独立升级的参与方,对“怎样发现能力、怎样发请求、怎样表示状态与结果”达成的公开约定。

Agent 框架与协议不在同一层。框架决定 runtime 内部由谁拥有 loop、session、tool gate、memory 和 graph; 协议则在某个对象需要走出这个 runtime 时,规定它怎样跨边界。同一个框架可以同时支持多种协议; 同一种协议也可以被很多框架实现。

阅读契约。 这篇只追问一件事:每个协议的两边站着谁?读完后,你应该能把同一次续约任务从前端重放到工具、远程 agent 和模型, 并说清哪个协议传的是 tool call、Task、UI event 还是 coding session update。

证据边界。 MCP、A2A、AG-UI 和 Agent Client Protocol 的语义以各自官方架构文档和已发布规范为准。 文中“边界地图”和“owner”是为了比较而做的工程归纳,不代表这些协议可以互相无损转换。

三、先画边界,再放协议名字

先不看 method 名称,只看续约助手的参与方。中间是 Agent Runtime,它拥有本次运行、会话和工具循环。 四条边界分别是:

  • 用户前端 ↔ Agent Runtime:AG-UI 传输 run、消息、工具和状态事件。
  • Agent Runtime ↔ 工具与数据服务:MCP 发现和调用 tools、resources 与 prompts。
  • Agent Runtime ↔ 远程 Agent:A2A 发现对方能力,并跟踪 Message、Task 与 Artifact。
  • 代码编辑器 ↔ Coding Agent:Agent Client Protocol 管理 coding session、流式更新和工具授权。

还有第五条边界:Agent Runtime ↔ 模型 Provider。OpenAI Responses API、Chat Completions API、Anthropic Messages API 都属于这一层 provider contract。它们当然也是 API 协议,但在 Agent 系统分层里,它们解决的是“runtime 怎样调模型”, 不是工具发现、远程 agent 协作或前端状态同步。

3.1 同一个续约任务,会得到多层身份

下面固定一件事:续约案例 RC-42。产品账本先为它保存用户与合同引用;每跨过一条协议边界, 对方再创建自己负责的身份。它们不是可以互换的五个名字,而是一条父子关联链:

RenewalCase RC-42
└─ AG-UI thread th-9
   └─ frontend run r-3
      ├─ MCP tool call tc-7
      └─ A2A task at-88
         └─ Artifact ar-5
   └─ final UI message m-12

Runtime 必须把这条关联写进自己的账本,因为单个协议只认识自己的工作单元。MCP Server 不需要看到整份合同历史, 它只收到 customer_id 与查询词;A2A 专家只收到争议条款摘要和问题;前端看到状态与可展示结果; provider 则看到 runtime 为本次模型调用投影的上下文。协议标准化的是跨边界形状,不是把内部账本全部暴露出去。

边界 谁创建身份 跨出的最小数据 调用方必须保存什么
AG-UI 产品/runtime 协商 thread 与 run 用户输入、可展示状态、typed events RC-42 ↔ th-9 ↔ r-3
MCP 调用 Client 为请求分配 id 工具名、校验后的参数 r-3 ↔ tc-7 与工具结果
A2A 远程 agent 返回 Task id 目标、必要上下文、可见附件 r-3 ↔ at-88 与最后状态
Provider API runtime 与 provider 各保留请求/响应身份 本轮模型可见投影 provider identity 或可重放历史

四、MCP:让 runtime 看见工具和数据

4.1 为什么不能只给模型一个 HTTP 地址

续约助手需要合同搜索和 CRM 查询。它不仅要知道 endpoint,还要知道对方现在暴露哪些能力、参数 schema 是什么、 返回内容是文本、图片还是资源引用。如果能力发生变化,runtime 还应该知道需要重新刷新工具表。

MCP 用 Host、Client、Server 三个角色表达这件事。续约助手是 Host;它为每个 MCP Server 维护一个 Client 连接; 合同库和 CRM 分别可以是 Server。Server 可以在本机通过 stdio 运行,也可以在远程通过 Streamable HTTP 服务。

4.2 一次 MCP 调用不是从 tools/call 开始

  1. Client 与 Server 先 initialize,交换协议版本和 capabilities。
  2. Client 通过 tools/listresources/list 等方法发现对方真正提供的能力。
  3. Runtime 把适合当前用户和会话的工具 schema 纳入模型输入。
  4. 模型选择 contract_search 后,runtime 路由到对应 Client,再发出 tools/call
  5. Server 返回结构化 content,runtime 决定它怎样进入会话、模型上下文与 UI。
{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{
  "name":"contract_search",
  "arguments":{"customer_id":"c-42","query":"automatic renewal"}
}}

这是简化后的 wire shape。关键不是 JSON-RPC 这几个字段,而是工具在调用前可发现、可协商,调用后有标准内容形状。

MCP 规定 context 交换,不接管 Agent Runtime。 官方架构文档明确说,MCP 不规定 AI 应用如何使用 LLM,也不规定如何管理已取得的 context。 哪些工具对当前用户可见、是否要过权限关口、结果是否持久化,仍然由 Host 与框架决定。

也不要把 MCP 压缩成“远程 function calling”。除了 Server 端的 tools、resources、prompts,它还定义 sampling、elicitation、logging 等 Client 能力, 并且有用于延迟结果和状态跟踪的实验性 Tasks。它的主边界仍是 runtime 与工具/数据,但边界两端可以双向协作。

五、A2A:把一件事交给不透明的远程 agent

5.1 远程 agent 不是一个大工具

CRM 查询的输入和输出很稳定,适合当工具。供应商专家不一样:它有自己的模型、工具、记忆和审批流程,也可能向人补问信息。 调用方不应该知道它内部的 prompt 或 tool chain,只需要知道它承诺什么能力、任务现在是什么状态、最后交付了什么。

A2A 正是为独立、可能不透明的 agent 系统设计的。调用方先通过 Agent Card 发现能力、支持的交互形式和安全要求, 再向对方发送 Message。简单回答可以直接返回 Message;需要持续跟踪的工作则进入 Task,最终结果放进 Artifact

5.2 Task 把长时间协作变成有身份的资源

  1. 续约助手读取 Agent Card,确认对方具有“解释供应商续约规则”的 skill。
  2. 它发送 Message,携带条款摘要和需要确认的问题。
  3. 对方返回 Task id,状态从 submitted 走向 working,中间可能进入 input-required。
  4. 调用方可以轮询、订阅流式更新,或为长任务配置 push notification;不再需要时也可以取消。
  5. 任务完成后,对方把“续约规则说明”作为 Artifact 交付,Task history 则保留这次多轮协作。
Agent Card → Message → Task: working
                         ├─ status update
                         ├─ input-required → Message
                         └─ completed → Artifact

A2A 1.0 把核心数据模型、抽象操作和具体 binding 分层。同一套 Task、Message、Agent Card 和 Artifact 语义可以映射到 JSON-RPC、gRPC 或 HTTP/REST。 因此“A2A 是不是 JSON-RPC”不是最重要的问题;更重要的是不同 binding 之上仍然共享同一份任务语义。

六、AG-UI:前端需要的不只是 token 流

6.1 一串文字无法还原一次 agent run

续约助手已经查了两个工具,又向远程 agent 发起任务。如果后端只向浏览器推送 assistant text delta,前端无法稳定回答: 这条 delta 属于哪次 run?哪个 tool call 正在等待?用户点击“批准”应该恢复哪个中断?页面重连后怎样重建状态?

AG-UI 把前端与 agent backend 之间的连接定义成双向事件协议。一次运行从 RunAgentInput 进入, 后端返回 BaseEvent 流。threadId 稳定标识会话线,runId 标识本次执行; message、tool call、state 和 lifecycle 都有自己的 typed event。

RUN_STARTED(threadId, runId)
→ TOOL_CALL_START(toolCallId, "contract_search")
→ TOOL_CALL_ARGS(toolCallId, ...)
→ TOOL_CALL_RESULT(toolCallId, ...)
→ STATE_DELTA(...)
→ TEXT_MESSAGE_START(messageId)
→ TEXT_MESSAGE_CONTENT(messageId, delta)
→ RUN_FINISHED(runId)

这个序列让 UI 知道什么是一次 run,哪些 delta 属于同一条消息或工具调用。STATE_SNAPSHOT 可以建立完整基线, STATE_DELTA 再用 JSON Patch 传递增量变化。前端不必从一串文本反推 runtime 状态。

6.2 中断是 run 的结果,不是一个没发完的回答

如果 CRM 工具要求用户确认读取敏感套餐,当前 run 可以以 interrupt outcome 结束。前端显示审批界面,用户回答后用新 run 携带 resume 信息。 这种设计保护了两个边界:前端知道上一次 run 已经到达明确终点;runtime 也不用把一个 HTTP 请求无限期悬挂等待用户。

AG-UI 翻译 runtime 事件,不会自动创造 runtime 语义。 如果框架内部没有稳定的 run id、tool-call lifecycle、interrupt 和 state owner,adapter 只能现场猜。 这就是为什么前面先读 AgentScope 的 typed event 和暂停恢复,再读前端协议。

七、ACP:先问清楚说的是哪一个 ACP

7.1 Agent Client Protocol 连接编辑器与 coding agent

把案例换成 coding agent:用户在编辑器里说“修改续约提醒逻辑,然后跑测试”。编辑器需要启动或连接 agent,建立多个 session, 把文件和用户 prompt 交给它,持续显示消息与 tool call,并在 agent 要执行命令时弹出权限询问。

这是一个旁支对照,不是 RC-42 执行到一半突然换协议。浏览器业务产品通常由 AG-UI 表达 run 与 UI state;当编辑器本身拥有 workspace、coding session 和命令授权入口时,ACP 才是更贴近产品 owner 的外层协议。

Agent Client Protocol 把这条边界标准化。对本地 agent,编辑器通常按需启动 agent 子进程, 双方通过 stdin/stdout 交换 JSON-RPC。Agent 用 notification 持续向 UI 推送 session update;又可以用双向 request 反过来请求编辑器批准工具调用。 协议尽量复用 MCP 类型,但 ACP 连接的两边仍然是编辑器和 coding agent,不是 MCP Client 与 MCP Server。

编辑器启动 agent 子进程
→ initialize
→ session/new
→ session/prompt
→ session/update ...
← session/request_permission
→ 用户决策
→ session/update ...

7.2 历史上还有 Agent Communication Protocol

搜索 ACP 时还会找到 BeeAI 的 Agent Communication Protocol。 它解决的是 agent、应用和人之间的远程通信,与前面的 A2A 更接近,不是编辑器协议。 该项目已于 2025 年归档,README 明确说明它已成为 Linux Foundation 下 A2A 的一部分。因此新文章里的 ACP 默认指 Agent Client Protocol; 遇到旧文档时,必须展开全称再判断。

八、Responses API 与 Chat Completions 为什么不和它们放在一格

AgentScope 篇已经具体比较了 OpenAI Responses API 与 Chat Completions API。 Responses 使用 typed items,可以通过 response identity 延续状态;Chat Completions 主要接收 messages 数组,工具调用和结果也有不同 wire shape。 AgentScope 需要两个 formatter 和 model adapter,再把它们归一成同一种内部 ChatResponse

这个差异很重要,但它发生在 runtime 与模型 provider 之间。MCP 不规定你用 Responses 还是 Chat Completions 调模型; A2A 对面的 agent 也可以使用完全不同的 provider;AG-UI 前端只应该消费稳定的 UI event,不该直接绑定 provider delta。 把 provider API 单独画一层,才能让每个 adapter 对自己真正拥有的语义负责。

九、把整个续约任务重放一遍

正常成功路径并不难,真正能检验协议有没有接牢的是远程 agent 补问用户。下面让 at-88 进入 input-required,同时跟踪持久记录怎样变化:

  1. 浏览器通过 AG-UI 发送 RunAgentInput,runtime 把 RC-42 关联到 th-9 / r-3
  2. 模型决定查合同;runtime 校验权限后发出 MCP 调用 tc-7,把结果写回案例账本。
  3. 遇到争议条款时,runtime 向 A2A 专家发送摘要,保存远端返回的 at-88
  4. at-88 返回 input-required。Runtime 保存父 thread、Task id、问题和 waiting_user,让 r-3 以 interrupt 结束。
  5. AG-UI 把问题投影给前端。用户回答后创建新 run r-4,而不是复活已结束的 HTTP 请求。
  6. Runtime 通过账本找到原 Task,把回答作为新 Message 发给 at-88;远端完成后交付 ar-5
  7. Runtime 保存 Artifact 引用,合并本地工具结果与远程证据,再调用模型生成消息 m-12
  8. AG-UI 发出最终文本与 RUN_FINISHED;产品账本把 RC-42 标成 completed。
before input-required
RC-42: {run: r-3, a2a_task: at-88, status: waiting_remote}

after input-required
RC-42: {run: r-3, a2a_task: at-88, question: "Which region?",
        status: waiting_user, resume_from: at-88}

after user reply
RC-42: {run: r-4, a2a_task: at-88, artifact: ar-5,
        final_message: m-12, status: completed}

失败分支也各有 owner:MCP 失败要以 tool error 回到 r-3,不能伪装成空结果; A2A 超时要保留 at-88 供查询、取消或稍后重附着;浏览器断线只中断事件交付, 重连时应从 snapshot 和后续 delta 重建 UI,不应偷偷重做工具调用。

如果入口是代码编辑器,最外层的 AG-UI 可能换成 Agent Client Protocol;里面的 coding agent 仍然可以使用 MCP 连接工具, 也可以用 A2A 委派远程 agent。这就是“主边界”的含义:协议各自有主要位置,但它们可以在一次运行里组合。

十、现在再用一张表压缩

边界 主要参与方 核心工作单元 发现与能力 长任务与恢复 谁仍然拥有内部运行
MCP AI Host/Client ↔ Tool/Data Server tool call、resource、prompt initialize + capabilities + */list notification;实验性 Tasks Host 拥有 agent loop、context 选择和权限。
A2A Agent Client ↔ 独立远程 Agent Message、Task、Artifact Agent Card + skills + interfaces status、stream、poll、push、cancel 远程 agent 保持不透明的内部实现。
AG-UI User-facing App ↔ Agent Backend RunAgentInput + BaseEvent client/agent capabilities 与 adapter run lifecycle、interrupt/resume、state snapshot/delta Backend 拥有执行;Frontend 拥有用户可见状态。
Agent Client Protocol Code Editor ↔ Coding Agent connection、session、prompt turn、update initialize + agent registry/config session identity、stream update、permission request Coding agent 拥有 loop;编辑器拥有 workspace UX 与授权入口。
Provider API Agent Runtime ↔ Model Provider response/item 或 messages/completion model 与 API capability response identity 或客户端重放历史 Runtime 拥有工具循环与产品 session;provider 拥有模型调用。

十一、选协议时不要先问“支持吗”

一个框架在 README 里写“supports MCP/A2A/AG-UI”,只能证明它有某个接入面,不能证明所有语义都是完整的。 更有用的检查顺序是:

  1. 先画参与方:这是 runtime 调工具、委派 agent、服务前端,还是编辑器启动 coding agent?
  2. 再找 identity owner:tool call id、task id、run id、session id 分别由谁创建、持久化和恢复?
  3. 再看终止语义:成功、失败、取消、询问用户和稍后恢复,是协议一等状态还是自定义文本?
  4. 最后审安全边界:连接身份、用户授权、工具参数校验、Artifact 与 resource 的可见范围由谁强制?

协议不会自动带来信任。MCP Server 暴露一个 tool,不代表当前用户应该被允许调用;A2A Agent Card 声明一项 skill,不代表调用方已验证对方身份; AG-UI 能传授权结果,不代表前端可以绕过后端策略。协议只是让安全信息有一个可互操作的载体,真正的决策仍由各边 owner 强制。

11.1 “支持协议”要用恢复场景验收

真正采用某个 adapter 前,至少要跑过五个场景:MCP Server 更新 schema 后,Host 能刷新而不是继续发送旧参数; 调用方重启后能凭 at-88 重附着 A2A Task;AG-UI 断线后能从 snapshot 重建而不重复执行; 前端传来的授权仍由后端复核;更换 provider 后,前端不需要理解一套新的 delta。

这些场景通过,只说明协议语义被验证;把 adapter 配进生产、开放给哪些用户,仍是应用 owner 的采用决定。 因此“代码里有 adapter”“恢复测试通过”“产品启用”是三个不同状态。

十二、带着这张地图继续读框架

后面几篇会反复出现这些协议名称,但每次都要回到边界。ADK 的 Agent 与 Workflow 先回答框架内部怎样运行;Agno 会把这条主线放进 AgentOS 和多种对外界面; AutoGen 与 Microsoft Agent Framework 会把 multi-agent runtime 和生产编排放在一起比较;CrewAI 让角色协作与确定性 Flow 分开;Eino 用 Go component 与 graph 表达执行; tRPC-Agent-Go 则会直接展开 MCP、A2A、AG-UI 与 OpenAI-compatible server 的不同出口。

下一篇进入 ADK Python。当协议边界已经分清,我们可以重新回到框架内部,看 Agent 的自主决策与 Workflow 的确定性步骤怎样在同一条 Runner/Event 主线上共存。

官方规范与文档