# Agent Note: ACP 作为仅面向自动化的协议

Status: implemented

[English](2026-07-23-acp-automation-only-protocol.md) | 中文

## 问题

ACP（Agent Client Protocol）桥接层已经变成第二套交互式产品 UI。它将持久事件转换为编辑器卡片、终端元数据、diff、计划、标题、推理（reasoning）、命令、模式、模型和权限选择器、会话导航以及面向人类的询问。这些职责与 TUI 和 Web 客户端重复，同时将自动化传输层与 UI 服务、持久化查询、展示策略和编辑器特定约定耦合在一起。

ACP 仍有一个有用的职责：另一个 agent（智能体）或自动化控制器可以启动 harness 进程、创建隔离会话、发送文本或范围狭窄的受支持内联图片、接收已提交的文本／图片回答、取消工作并回答权限请求。跨进程 ACP subagent 后端依赖这个标准协议边界。

快照套件使移除工作更复杂。大多数 ACP 场景测试的是组装后的 agent 后端，而不是 ACP 展示层；如果随编辑器桥接层一起删除整个套件，就会丢失大量无密钥行为测试。

## 决策

`@deepseek-ai/dsh-acp` 是位于 [`packages/acp/acp`](../../../../packages/acp/acp/README.md) 下、独立于 `ui` 包组的自动化传输层。其公开协议特意保持精简：版本协商、全新会话（每个会话最多允许一个进行中的提示词）、已提交的助手文本／图片更新、按会话取消、并发会话，以及由连接负责的资源清理。提示词按协议顺序保留文本与受支持光栅图片，资源链接则展平为方括号文本引用；桥接层会拒绝附加目录、MCP 服务器、音频、嵌入资源、格式错误或空提示词、未知会话和重叠提示词。

图片能力必须真实，而不能只看结构：只有持久附件存储存在，且配置的确切提供方／模型解析后明确支持图片输入时，`initialize` 才会公布该能力。每个图片提示词都会重新检查会话的最新确切路由、严格解码全部块，并在发布用户事件前把完整批次委托给 `AttachmentStore.saveImages()`。取消会在任何异步工作前预留并中止准入槽位，使提示词在已经启动的写入停稳后才结算，而且绝不发布迟到消息；提示词进入 Agent inbox 前既不会取消，也不会等待无关的 Agent 工作。已经完成的内容寻址写入可能保持不可达，因为对去重存储执行破坏性回滚并不正确。可由调用方修正的图片策略失败会映射为无效参数，路由查询、存储损坏和持久化失败则仍属于内部故障。

桥接层只发出已提交的 `assistant/message` 文本与图片。每个会话使用一条 Promise 链，在异步重新读取并校验助手图片引用、将其转换为 ACP base64 交付时保持块与消息顺序；对象缺失或损坏会使提示词交付失败，而不是变成占位符。推理、原始分片、工具活动、待办事项、计划、标题、重试标记、终端元数据、diff、位置和资源链接仍保留在持久会话日志或 UI 专用传输层中。它不提供会话加载、列出与删除、命令、模式、配置选择器、模型切换、plan 评审或面向人类的询问。

保留一次性 `session/request_permission`。它是为桥接层拥有的 agent 提供的机器策略通道，而不是面向人类的审批 UI：应答者只接受桥接层当前会话映射中登记的同一 agent 对象；不属于桥接层当前 agent 的请求或未关联具体调用的请求会继续委派；RPC 失败则映射为故障时默认拒绝的 `unavailable` 结果。客户端可选择允许一次、拒绝一次或取消，桥接层绝不会将该响应转换为持久授权。询问策略仍归审批 seam 及其生产者所有；[`dsh-subagent-acp`](../../../../packages/subagent/subagent-acp/README.md) 会以程序化方式使用该通道。

应用组装包含 agent 主干、持久化、检查点策略和 ACP 传输层。它不会为 ACP 挂载命令、会话查询、会话引用、plan mode、权限选择器或用户交互服务。

传输层调用 agent、会话和审批的接口服务，而不依赖具体的 agent loop（智能体循环）。工具执行仍留在 harness 内；ACP 绝不会把 shell 执行委派给编辑器。stdout 只承载分帧 JSON-RPC，因此 app 不挂载 stdout logger，桥接层也不会 monkey-patch 进程输出。

断开连接与插件 dispose（资源释放）共享同一个经记忆化的完全停稳边界。传输关闭无论成功还是失败，都会取消提示词准入和 agent、排空有序输出、将待处理提示词以已取消状态结算、dispose 每个由桥接层拥有的 agent，并等待循环和会话清理完成。创建流程如果在与关闭的竞态中落败，就会 dispose 其尚未发布的 handle。

## 快照边界

ACP 快照套件仍会启动组装后的 ACP 示例，并保留用于锁定后端行为的场景。从该套件移出的只有通过已删除的 UI 方法驱动的场景；由于 ACP 不再加载会话，语义检查点恢复通过 headless `stream-json` 示例执行。

协议与生命周期测试会锁定停止原因编解码器、版本协商、真实图片能力、新会话创建、有序文本／图片准入、资源链接展平、写入前校验全部成员、持久事件中不含内联 base64、拒绝空提示词或不受支持的提示词、基于同一 agent 对象的权限归属、多会话隔离、在有序输出后结算提示词、经过校验的助手图片交付、准入期间取消且不产生迟到 followup 或取消无关 Agent 工作、排除进入 inbox 前的无关失败、传输关闭失败、ACP 专属重载清理，以及拆卸完全停稳。组装后的无密钥快照通过可运行 ACP 示例发送一张真实内联 PNG，并在会话日志中只固定其持久引用。构建产物冒烟测试与真实 stdio 冒烟测试会拒绝混入 stdout 的额外输出。`session/new` 中在真实 stdio 关闭竞态中落败的分支仍豁免覆盖率要求，因为内存传输层无法复现这一顺序；该分支会 dispose 尚未发布的 handle，而周边 dispose 测试会锁定无遗留资源不变式。

## 考虑过的替代方案

**在 Web 达到同等能力前，继续将 ACP 作为编辑器 UI。** 不予采用，因为这会留下两套需要演进的交互约定，并使编辑器约定继续存在于自动化边界中。

**通过严格的服务边界保留早期编辑器桥接层。** 不予采用，尽管该桥接层正确使用了接口服务、工具自有的 render intent、审批与用户交互应答者、harness 自有执行，以及保持 stdout 纯净的组装。其终端卡片是经过能力门控、仅用于展示的 Zed `_meta` 投影，并提供文本回退，而非使用 ACP `terminal/create`，因此 shell 执行从未离开 harness。该投影从稳定的逐调用 id 派生每个展示用终端 id，以避免冲突；由于纯结果展示器接收的是内容块，而不是结构化退出信息，它会从渲染后的状态标记中恢复退出码或信号。标记往返测试和显式的无能力 `console` 回退测试锁定了这两项约定。这些边界保持一致，却无法让编辑器卡片、会话导航、配置选择器和面向人类的询问成为自动化协议应有的职责。

**用私有 subagent RPC 替换 ACP。** 不予采用，因为 ACP 已经提供类型化、可互操作的进程协议，并由跨进程 subagent 后端使用。

**随其他交互功能一起移除机器权限请求。** 不予采用，因为自动化父 agent 必须回答子 agent 的一次性策略决策；这是 agent 之间的控制流，而不是展示层。

**删除 ACP 快照套件，或在本次变更中迁移每个场景。** 不予采用，因为大多数场景测试后端且仍有价值，而完整的 harness 迁移是一项独立的测试变更。只有通过已删除的 UI 方法驱动的场景才离开该套件。

**只要 ACP SDK 具有图片块就公布图片支持。** 不予采用，因为协议词汇不能证明当前部署可以持久化字节，也不能证明配置的确切路由接受视觉输入。初始化时能力未知即为 false；提示词准入会重新检查实时路由。

**把内联图片和助手图片展平为标记，或把 ACP base64 持久化进会话事件。** 不予采用，因为标记会静默丢失模型／用户意图，base64 则会让持久日志变成二进制存储。ACP 在传输边界把自身协议块与现有持久 `ImageBlock` 引用相互转换。

**为 ACP、MCP 和 Web 创建通用 RichContent 服务。** 不予采用，因为核心 `ContentBlock` 与附件 seam 已经拥有共享契约。每个入口只保留协议解析、能力证明与生命周期编排；共享批次限制和图片校验留在 `AttachmentStore.saveImages()` 中。

## 结果

ACP 具有适合 agent 与自动化的精简约定，而 TUI 和 Web 拥有面向人类的交互与展示。该包注入的服务、依赖、协议分支和生命周期状态更少，也不再将自身定位为通用编辑器入口。

自动化客户端收到完整的已提交文本／图片，而不是 token 增量或结构化工具 UI。当它们需要推理、工具跟踪信息、标题或更丰富的状态时，需要查看持久日志或其他 API。只支持全新会话也意味着，需要浏览持久会话或恢复会话的调用方必须使用 host API，而不是 ACP。

因此，后端快照测试仍与 ACP 传输层耦合，尽管对于受测行为而言，该传输层只是附带因素。
