Coding Agent 正在快速增多,但把一个 Agent 接进编辑器,仍然不是“启动一个进程,再显示几行文字”这么简单。
编辑器需要知道 Agent 正在做什么:它是在思考、读文件,还是准备执行命令?改了哪些代码?要不要向用户申请权限?任务结束了,还是仅仅暂时没有输出?会话关闭后,下一次能不能恢复?
如果没有统一协议,每个编辑器都要为每个 Agent 单独回答这些问题。新的组合越多,适配成本就越接近一个乘法:
编辑器数量 × Agent 数量 = 需要维护的集成数量
Agent Client Protocol,简称 ACP,试图把这道乘法题改成加法。它在编辑器和编码 Agent 之间定义了一套标准接口,让双方只需要面向协议实现一次。
这很像 LSP 当年为编辑器与语言服务器做的事,但 ACP 处理的不是“跳转到定义”和“代码补全”,而是一整段由模型、工具和用户共同参与的 Agent 会话。
ACP 站在系统的哪一层
先把几个容易混淆的协议放到同一张图里。
flowchart LR
U[开发者]:::person --> E[编辑器 / IDE]:::client
E <-->|ACP<br/>会话与交互| A[Coding Agent]:::agent
A <-->|MCP<br/>工具与上下文| T[外部服务]:::tool
E <-->|LSP<br/>语言能力| L[语言服务器]:::language
classDef person fill:#fff4df,stroke:#a66a14,color:#1f2937
classDef client fill:#eaf4ff,stroke:#2877a8,color:#1f2937
classDef agent fill:#eef8f1,stroke:#37805c,color:#1f2937
classDef tool fill:#fff0ed,stroke:#b85b48,color:#1f2937
classDef language fill:#f2efff,stroke:#725db1,color:#1f2937
图 1:ACP、MCP 与 LSP 连接的是三组不同角色。
三者都可以使用 JSON-RPC,也都强调能力协商,但解决的问题不同。
| 协议 | 连接谁 | 核心对象 | 典型交互 |
|---|---|---|---|
| LSP | 编辑器与语言服务器 | 文档、位置、诊断、符号 | 补全、跳转、重构 |
| MCP | AI 应用与上下文或工具服务 | Tools、Resources、Prompts | 查数据库、调 API、读取外部上下文 |
| ACP | 编辑器与 Coding Agent | Session、Prompt、Update、Permission | 对话、计划、工具进度、Diff、授权 |
一句话概括:LSP 给编辑器接入语言能力,MCP 给 Agent 接入外部能力,ACP 给编辑器接入 Agent。
ACP 并不替代 MCP。恰恰相反,ACP 会尽量复用 MCP 的内容类型;创建会话时,客户端还可以把 MCP Server 配置交给 Agent。两者更像上下两层:ACP 负责人与 Agent 的交互界面,MCP 负责 Agent 与工具、数据之间的连接。
它标准化的不是聊天,而是任务现场
把 Agent 当成聊天机器人,只需要输入框、流式文字和停止按钮。但 Coding Agent 的真实工作现场远比聊天复杂。
一次任务可能包含:
- 用户消息和被引用的代码文件;
- Agent 动态生成的执行计划;
- 多轮模型调用和工具调用;
- 等待用户确认的高风险操作;
- 持续刷新的终端输出;
- 文件修改前后的 Diff;
- 中途取消、失败、恢复和会话切换。
ACP 的核心价值,是给这些状态一套编辑器能够稳定消费的语义。
例如,Agent 不必把“正在读取配置文件”伪装成普通文本。它可以发送一个 tool_call 更新,声明工具类型为 read、状态为 pending,随后再更新为 in_progress 或 completed。编辑器因此可以选择合适的图标、进度状态和详情视图,而不是从自然语言里猜 Agent 在干什么。
同样,代码修改也不必塞进 Markdown 代码块。ACP 为工具结果定义了 Diff 内容,编辑器可以直接复用自己的差异查看器。计划、终端和文件位置也都有对应结构。
这揭示了 ACP 最关键的设计取向:
协议传递的是可交互的任务状态,而不只是一串聊天消息。
一次 ACP 会话怎样运行
当前稳定的 ACP v1 使用 JSON-RPC 2.0。最常见的本地模式是由编辑器启动 Agent 子进程,双方通过 stdin/stdout 交换以换行分隔的 JSON-RPC 消息;日志应写入 stderr,不能污染协议数据。
一条连接可以承载多个独立 Session。典型流程分为三段。
sequenceDiagram
participant U as 用户
participant C as Client / IDE
participant A as Agent
participant M as MCP / 工具
C->>A: initialize
A-->>C: 版本、能力、认证方式
C->>A: session/new(cwd, mcpServers)
A-->>C: sessionId
U->>C: 提交任务
C->>A: session/prompt
A-->>C: session/update(plan)
A-->>C: session/update(tool_call)
A->>C: session/request_permission
C->>U: 展示授权选项
U-->>C: 允许一次
C-->>A: permission result
A->>M: 执行工具
M-->>A: 结果
A-->>C: session/update(diff / output)
A-->>C: session/prompt response(stopReason)
图 2:ACP v1 中一次典型 Prompt Turn 的消息流。
第一步:先协商能力,再创建会话
连接建立后,Client 首先调用 initialize。双方在这里交换协议版本、实现信息和能力。
这一步不能省略。客户端可能支持文件读取,却不支持终端;Agent 可能支持图片输入,却不支持音频。ACP 要求未声明的能力一律按“不支持”处理,调用方不能靠猜。
随后 Client 通过 session/new 创建会话,并传入绝对路径形式的工作目录。Agent 返回唯一的 sessionId,后续 Prompt、更新、取消和恢复都围绕它进行。
第二步:Prompt 是一次完整的任务回合
在 v1 中,session/prompt 不是一次简单的模型请求,而是一个完整 Prompt Turn 的外壳。
Agent 可以在这个请求尚未返回时,多次发送 session/update 通知,持续报告计划、文本、工具调用与执行结果。一次 Turn 内部可以经历多轮“模型决定调用工具 → 工具返回结果 → 模型继续推理”,直到 Agent 返回 stopReason。
因此,编辑器看到的不是等到最后才出现的一坨结果,而是一个可以实时呈现、跟随和打断的过程。
第三步:Agent 也可以反过来调用 Client
JSON-RPC 在 ACP 中是双向的。
Client 会调用 Agent 的 session/prompt;Agent 也可以调用 Client 的 session/request_permission,把一个待执行动作及可选决策交给界面。用户选中“仅允许本次”“始终允许”或“拒绝”后,Client 再把结果返回给 Agent。
这条反向通道很重要。权限确认不是 Agent 输出的一句“可以吗”,而是协议中的正式请求。编辑器可以用原生弹窗呈现,也可以根据用户策略自动允许或拒绝。
不过要注意:ACP 定义的是授权交互,不是完整的安全沙箱。 最终能访问哪些文件、命令和网络,仍取决于 Client、Agent、操作系统权限以及具体实现的策略。支持 ACP 不等于天然安全。
为什么不让 Agent 直接操作编辑器 API
当然可以。很多产品最初就是这样做的:为某个 Agent 写一套专用插件,通过编辑器私有 API 显示消息、执行命令、应用代码修改。
问题出在第二个 Agent 和第二个编辑器出现之后。
没有协议时,Agent 需要理解不同编辑器的会话模型和 UI API;编辑器也要适配不同 Agent 的事件格式、权限模型和工具状态。双方升级都会把兼容成本传导给所有集成。
ACP 把边界收窄为一组与具体产品无关的语义:
- Client 拥有用户界面、工作区上下文和交互控制;
- Agent 拥有模型编排、内部工具与任务执行逻辑;
- 协议只约定双方必须如何描述会话、内容、状态和请求。
这使“换 Agent 不换编辑器”和“换界面不重写 Agent”成为可能。更重要的是,编辑器可以围绕统一事件构建一次 Diff、权限、计划和终端体验,而不用为每个 Agent 再造一遍。
ACP 的边界也很明确
理解一个协议,不只要看它包含什么,也要看它刻意不管什么。
它不规定 Agent 内部怎样工作
ACP 不要求使用哪一种模型、Agent Loop、记忆系统或规划算法。两个 Agent 可以发出相同的协议事件,内部实现完全不同。
它不保证所有实现能力相同
初始化阶段的能力协商正是为差异而设计。兼容 ACP 只表示双方能按协议沟通,不表示每个 Agent 都支持会话恢复、图片、音频或相同的 MCP 传输。
它不自动带来远程部署能力
稳定规范当前仍以 stdio 为主要传输。官方文档把 Streamable HTTP 标为讨论中的草案,远程 Agent 支持也仍在推进。自定义传输可以存在,但实现者需要自行保留 JSON-RPC 语义和生命周期要求。
它不消除适配层
已经存在的 Agent 不会因为 ACP 出现就自动兼容。现实中仍可能需要 Adapter,把原有 CLI 的事件、工具调用和会话语义翻译成 ACP。协议减少的是 N 对 N 的长期耦合,不是让集成工作瞬间归零。
v2 为什么要“走出 Turn”
截至本文核验时,ACP v1 是稳定版本,v2 已发布 Draft,但官方明确提醒其内容仍可能变化,不应默认用于生产。
v2 最值得关注的变化,是把 session/prompt 从“包住整个任务的长请求”改成“Agent 已接收这条消息”的确认。后续运行、等待操作、完成和空闲状态,统一通过 session/update 通知表达。
这不是简单换个字段,而是在修正一个更深的假设:Agent 的工作不一定严格开始于一次用户输入,也不一定在一次回答后全部结束。它可能排队、被中途引导、在后台继续执行,甚至被多个 Client 观察。
v2 还把消息、计划和工具调用进一步统一为按稳定 ID 更新的对象;同时移除了 v1 的 Client 文件系统与终端执行接口,建议需要 Client 侧工具时通过 MCP Server 提供。
这组变化让 ACP 的分工更清晰:
- ACP 描述 Agent 会话及其面向人的状态;
- MCP 提供 Agent 可调用的工具和上下文;
- Agent 自己负责执行与生命周期;
- Client 专注于交互、呈现和用户控制。
但在 v2 稳定之前,实现者仍应通过版本协商同时兼容 v1,而不是提前假设生态已经完成迁移。
真正值得关注的是“交互可移植”
协议是否成功,最终不取决于它列出了多少 Agent 和编辑器,而取决于双方能否在不共享私有实现的前提下,保留足够完整的使用体验。
ACP 的设计没有停在“能发消息”这一最低标准。它把计划、工具状态、权限、Diff、终端、文件位置、取消与会话恢复都纳入协议,是因为这些能力共同构成了 Coding Agent 的产品体验。
这也是 ACP 与普通聊天 API 最大的差别:
聊天 API 让模型的回答可以被显示;ACP 试图让 Agent 的整个工作过程可以被另一种界面接管。
如果这层协议能够稳定下来,编辑器不必把未来押在某一个 Agent 上,Agent 也不必为了进入每一种开发环境重复实现 UI。开发者得到的则是更实际的选择权:保留熟悉的工作台,同时替换背后的 Agent。
ACP 现在还不是这条路的终点。远程传输仍在演进,v2 仍处于 Draft,协议兼容也不能替代权限隔离和安全设计。
但它已经把问题定义得足够清楚:Coding Agent 生态缺少的不只是更多模型和更多工具,还缺少一层能够承载完整交互的公共语言。