Background · ACP 30 秒先说清楚 ACP 是什么
Agent Client Protocol(ACP)是 Zed 在 2025 年 8 月发布、9 月上线 Zed 编辑器的开放协议,现在由 Zed 与 JetBrains 共同推进(v2 草案已在路上)。它的口号是「做 Agent 界的 LSP」:把『编辑器/客户端 ↔ 编码 Agent』之间各自为政的私有对接,收敛成一个标准插座。本地 Agent 以子进程方式跑,走 JSON-RPC 2.0 over stdio;远程场景走 HTTP/WebSocket(仍在完善)。能复用 MCP 的 JSON 表示就复用,人读的文本默认 Markdown,并为 Agent 式 UX 定义了 diff 展示等专有类型。
有了协议,客户端和 Agent 的集成成本就从「每对都要拉一根线」变成「各自插一次座」。这正是 acpx 存在的前提:
Positioning · VISION定位:agent 优先的 CLI,同时是可复用的后端
VISION.md 给 acpx 的自我定义非常克制:「最小的有用 ACP 客户端」——让一个 Agent 通过 ACP 与另一个 Agent 对话,不要 PTY 抓终端,不要 adapter 专用胶水。它刻意同时服务两种身份:
- agent-first CLI——主要用户是另一个 Agent、编排器或 harness,人类当然也能直接敲,但那是次要约束;
- 可复用后端——别的工具想要 ACP 会话、结构化输出、队列和持久化,不必自己重造,委托给 acpx 即可(它导出
acpx/runtime与acpx/flows两个内嵌库)。
六条设计原则决定了这个项目的形状:
标准是 ACP,不是某一家 Agent 的怪癖。线级行为贴近协议,兼容性差异做稳妥的归一化,保留结构化数据让下游自己决策,拒绝锁定单一 harness 的特性。
只做强 ACP 客户端该做的事:连接 Agent、管会话、安全排队、处理权限与生命周期、渲染结构化响应。对核心没贡献的功能不属于核心。
面向长时间、自动化、多轮次场景:默认会话连续性、安全排队、清晰的失败模式、可恢复的生命周期。无聊但到处能用的特性,胜过聪明但只在一处能跑的。
数据模型、配置键、flag、输出形状都是产品表面,反复推敲才准入。文档里点名例子:定名 claude 而非 claude-code,是有意为之的约定,不是随手缩写。
静态 JSON 覆盖常见场景;不够用时允许以受控方式程序化扩展本地配置(精神上类似 Pi)。定制的目的是不 fork 就能适配环境,不是把核心做大。
终端用户永远不直接敲 acpx 的工具也要能用它:会话持久化、队列所有权、prompt 串行化、adapter 进程管理、权限策略,这些脏活它替你扛。
Architecture · 数据路径架构:一条不做终端抓取的直连线
architecture 文档把数据路径写成一行:CLI command → AcpClient → ndjson/stdio → ACP adapter → coding agent。CLI 全程只说结构化的 ACP JSON-RPC 消息,从不解析交互式 UI 的终端转义序列——这是它与「套壳 pexpect」类工具的本质区别。
§其余核心组件
src/session/session.ts—— 会话持久化、resume/create、队列所有权、软关闭生命周期、超时/中断与清理;src/permissions.ts—— 权限策略(approve-all / approve-reads / deny-all)与交互式回退;src/cli/output/output.ts—— text / json / quiet 三种流式输出格式。
§一次 prompt 的协议流(顺序即语义)
Sessions & Queue会话:按 (agentCommand, cwd) 作用域持久化
acpx 要求先显式建会话再提 prompt(acpx codex sessions new),避免自动化在 CI 里意外拉起对话。会话元数据落在 ~/.acpx/sessions/*.json,作用域键是 (agent 命令, cwd)——同一仓库可以并行开命名工作流:
$ acpx codex sessions new --name backend $ acpx codex sessions new --name docs $ acpx codex -s backend 'fix the checkout timeout' $ acpx codex -s docs 'draft release notes' # 一次性运行、不留任何会话记录: $ acpx codex exec 'in 5 bullets, what does this repo do?'
每条会话记录包含:稳定的文件/会话 id、ACP session id、agent 命令、cwd、可选命名、三个时间戳、可选软关闭状态、adapter 进程 pid、协议版本与能力快照。默认自动恢复对话上下文;软关闭的记录会被自动恢复跳过,除非显式点名。配套命令:sessions list/ensure/close/show/history/prune 与 status(running / idle / dead / no-session),外加跨机器的 export/import。
§队列 owner:并发 prompt 不开第二个 adapter
对同一个持久会话,acpx 的做法不是让多个进程各拉各的 adapter,而是选出唯一「队列 owner」:
--no-wait 即投即走(fire-and-forget),不阻塞脚本。--ttl 可调),期间随时接续下一轮。sessions close / sessions new 做软关闭——终止相关进程但保留会话 JSON,之后仍可显式恢复。acpx <agent> cancel 走队列路径发 session/cancel,会话保持存活,后续 prompt 照常接;进程级 SIGINT 则可能直接拆掉正在运行的 adapter。这是文档特意强调的语义分野。
Permissions & Output权限三档,输出三格式
Agent 的权限请求通过 ACP session/request_permission 回调进来,acpx 用三档模式应答,并按 (agentCommand, cwd) 设定文件系统边界(--cwd),读写文件默认做路径沙箱:
| 模式 | 行为 | 适用 |
|---|---|---|
--approve-all | 自动批准第一个 allow 选项 | 受控沙箱 / CI,流声明了该要求的 flow |
--approve-reads(默认) | 读与检索自动批,写操作转交互确认 | 人在环上的日常使用 |
--deny-all | 能拒则拒 | 纯分析、不动任何工具 |
非 TTY 场景由 --non-interactive-permissions 决定(如 fail:无人在场时宁可失败也不默默拒绝)。更细的每工具策略用 --permission-policy 传 JSON;但「允许读 src/、拒绝写 .env」这类路径/参数级规则还不支持(见下方覆盖清单)。权限统计(请求/批准/拒绝/取消)会被记入退出码行为。
§输出:text / json / quiet
- text(默认)——给人看的事件流:
[tool] Read README.md (completed)、[done] end_turn; - json —— 每行一条 ACP JSON-RPC 消息的 NDJSON;
--json-strict保证 stdout 上除了消息没有别的。接 jq 一把梭:
$ acpx --format json codex exec 'review changed files for risky patterns' \ | jq -r 'select(.method=="session/update")' # 只取最终 assistant 文本: $ SUMMARY=$(acpx --format quiet codex exec 'one-line summary of this branch')
Coverage · 参考文档 ②ACP 方法覆盖:18 个已实现,4 个观望
仓库里的 docs/2026-02-19-acp-coverage-roadmap.md 逐方法对照了 ACP 规格的落地情况——这是理解 acpx 完成度最直接的一份文档:
| 分组 | ACP 方法 | acpx 侧 |
|---|---|---|
| 会话核心 | initialize · authenticate | 握手与能力协商;auth 握手兼容靠 ACP 认证的 adapter |
session/new · session/load | sessions new;崩溃恢复/重连 | |
session/prompt · session/update | prompt / exec / 隐式 prompt;流式输出(thinking、tools、text、diffs) | |
session/cancel · session/request_permission | 协作式取消;三档权限模式 | |
session/set_mode · set_config_option · set_model | set-mode、set 命令;set_model 仅限声明了 legacy models 元数据的 adapter | |
| 客户端权威 fs | fs/read_text_file · fs/write_text_file | agent 请求读写时由客户端执行;默认按 cwd 沙箱限定路径 |
| 客户端权威 terminal | terminal/create · output · wait_for_exit · kill · release | 完整终端生命周期:进程跟踪、输出缓冲、按 terminal id 清理 |
| 方法 | 用途 |
|---|---|
session/fork unstable | 把一段对话分叉成并行替代路径 |
session/list unstable | adapter 侧会话清单(当前只有 acpx 本地存储清单) |
session/resume unstable | 区别于 load 的「暂停后恢复」语义,不带回放 |
$/cancel_request unstable | 会话级 cancel 之外的传输层 JSON-RPC 取消 |
§ACP 之外的相邻规划
- ✔ 已做 —— 会话 export/import,跨机器搬会话;
- ◻ 未做 —— 路径/参数级权限规则;多 Agent 编排(Agent A 经 acpx 调 Agent B,会话桥接);webhooks 回调(CI/CD 管道);watch mode(文件变更重跑);cost/token 统计透出。
Conformance · 参考文档 ①acp-core-v1:给「兼容 ACP」一个可执行的判据
「支持 ACP」是一句空话,直到有人把它变成测试。acpx 仓库里的 conformance/spec/v1.md(草案)定义了最小而稳定的一致性画像 acp-core-v1,配 21 个 JSON 用例(001-initialize-handshake 到 021-prompt-post-success-drain)和一个 runner,可以横着验证所有 adapter。
- 传输假设:JSON-RPC 2.0 over stdio;请求带客户端生成的唯一
id,通知不带 id; - 必测方法:
initialize、session/new、session/prompt、session/cancel,且 prompt 执行期间必须发session/update通知; - 硬合同(节选):initialize 响应必须带协议版本;session id 非空;非空 prompt 至少先有一条 update 再进入终态;取消必须落到 cancelled 终态;非法参数 → JSON-RPC 错误(通常
-32602);未知 session 显式报错而非静默成功;权限拒绝必须是带机器可读 code/message 的显式失败; - 用例结构:每例声明 steps / checks / 每阶段超时预算(request / update / settle),超时即失败并留存 transcript;
- 判定模型:全部断言过 → 用例过;全部必测用例过 → profile 过;可选用例单独报告不拖累 profile;
- v1 明确不管:session/fork、session/list、session/resume、
$/cancel_request——等各家语义稳定了再进后续版本。
Compare · 横评compare:同一个 prompt,并发地审一遍各家 Agent
协议打通的直接红利:换 Agent 不用换编排。acpx compare 把同一条一次性 prompt 依次喂给多个 ACP 兼容 Agent,汇总成一张对照表:
$ acpx compare pi codex claude 'summarize this checkout' agent status wall_ms input output permissions stop_reason pi ok 1420 980 210 0/0 end_turn codex ok 1240 1200 340 0/0 end_turn claude ok 1610 1042 287 1/0 end_turn
每个 agent 走与 exec 相同的临时会话路径,串行执行在目标工作区里——有写权限的 prompt 不会并发改同一份代码。输出列含状态(ok / cancelled / permission_denied / error)、耗时、usage token、权限拒绝统计、ACP stop reason、前 200 字符的最终回答;--format json 输出行数组,方便程序化对比。全局执行控件(--cwd、权限模式、超时、--model、--no-fs/--no-terminal 等)全部通用。
Flows · 多步工作流flows:别把工作流塞进一个巨型 prompt
一条 prompt 搞不定的事——需要确定性分支、需要 shell/GitHub API 这些非模型步骤、需要每步可检查可回放、工作流固定而输入变化——就写一个 flow。flow 是 TypeScript 模块,由 acpx/flows 运行时逐步执行,状态持久化在 ~/.acpx/flows/runs/<runId>/。目前是实验性的 opt-in 层,不改变会话与 prompt 的既有行为。
| 节点 | 职责 |
|---|---|
acp | 模型形状的一步:对某个 agent 会话跑一轮 ACP turn |
action | 运行时自有的确定性一步:shell 命令、HTTP 调用 |
compute | 纯本地函数:整形输入、路由、格式化、推导 |
decision | 受限选择的 ACP 分支:acp + parse + switch 的类型化封装,配 decisionEdge() 产出显式可回放的路由 |
checkpoint | 暂停点:等运行时之外的东西(人工评审、外部触发) |
官方的 PR 分诊示例几行就能看懂整个模型:
import { defineFlow, acp, decision } from "acpx/flows"; export default defineFlow({ id: "triage", input: { task: "string" }, steps: { classify: decision({ agent: "codex", prompt: ({ task }) => `Classify: ${task}\nLabels: bug | feat | doc`, choices: ["bug", "feat", "doc"], }), fix: acp({ prompt: ({ task }) => `Implement and verify: ${task}` }), write_doc: acp({ prompt: ({ task }) => `Draft docs entry for: ${task}` }), }, edges: [ ["classify", "fix", (out) => out === "bug" || out === "feat"], ["classify", "write_doc", (out) => out === "doc"], ], });
§工程细节里见态度
- 权限前置声明,快速失败:flow 可声明权限要求;漏掉
--approve-all就在第一步之前报错并打印该加的 flag——对会真实改动世界的 flow(如 pr-triage 会评论/关闭真实 GitHub PR)是硬护栏; - 工作区隔离:acp 节点可钉住每步 cwd,让
git worktree add之类的 action 先备好隔离树,agent 在树内作业; - 运行产物不可变:每步的图状态、ACP transcript、artifacts、错误全部落盘,终态后即冻结,作为回放输入;
- 超时纪律:未设
--timeout时每活动步默认 15 分钟;超时/中断会取消活动 shell、等进程组与输出流清理完才回报,清理失败会如实上报;超时后已 resolve 的 executor 不可能再拉起新进程; - replay viewer:仓库内置一个只读浏览器应用(React Flow 图、逐节点状态、WebSocket 实时刷新进行中的 run、时间轴 scrub),
pnpm viewer即起。
Agent Registry内置 22 个友好名,一个逃生舱
每个友好名解析为一条 ACP adapter 命令;名字不认识就当裸命令执行,还不够就用 --agent '<command>' 自定义。顶层命令(如 acpx exec)的默认 agent 是 codex。所有内置 agent 共享同一命令面:prompt / exec / cancel / set-mode / set / status / sessions …
| 名字 | adapter 命令 | 包装的上游 |
|---|---|---|
codex ★默认 | npx -y @agentclientprotocol/codex-acp | Codex CLI |
claude | npx -y @agentclientprotocol/claude-agent-acp | Claude Code |
gemini | gemini --acp | Gemini CLI |
cursor | cursor-agent acp | Cursor CLI |
copilot | copilot --acp --stdio | GitHub Copilot CLI |
droid | droid exec --output-format acp | Factory Droid(factory-droid 亦解析到此) |
pi | npx pi-acp | Pi Coding Agent |
openclaw | openclaw acp | OpenClaw ACP bridge |
opencode | npx -y opencode-ai acp | OpenCode |
qwen | qwen --acp | Qwen Code |
kimi | kimi acp | Kimi CLI(MoonshotAI) |
iflow | iflow --experimental-acp | iFlow CLI |
trae | traecli acp serve | Trae CLI |
kiro | kiro-cli-chat acp | Kiro CLI |
qoder | qodercli --acp | Qoder CLI |
kilocode | npx -y @kilocode/cli acp | Kilocode |
mcode | mcode acp | MiniMax Code |
mux | mux acp(ACPX 维护的 npm range) | Mux |
grok-build | grok agent stdio | xAI Grok Build |
fast-agent | uvx fast-agent-mcp acp | fast-agent |
pool | pool acp | Poolside |
zeroclaw | zeroclaw acp | ZeroClaw(原生 JSON-RPC stdio,ACP v1) |
几个有代表性的 per-agent 注记:claude 的 adapter 版本被 acpx 钉住,--system-prompt 经 ACP _meta.systemPrompt 注入并持久进会话选项;codex 的模型选择走广告出来的 config option(legacy adapter 才退回 set_model);zeroclaw 多 agent 配置必须设 acp.default_agent,且仅支持文本 prompt;任何内置都可整体覆盖:
{ "agents": {
"codex": { "command": "/usr/local/bin/codex-acp", "args": ["--profile", "ci"] }
} }
CLI flag 永远赢过配置文件;配置分层为全局与项目级 JSON(~/.acpx/config.json 等)。
Takeaways三句话带走
- 协议层价值:ACP 把「编辑器×Agent」的 N×M 私接变成 N+M 标准插座;acpx 证明了这套插座在纯命令行、纯自动化场景里已经够用。
- 工程层样本:显式会话作用域、队列 owner + IPC、软关闭、退出码绑定权限统计、conformance 用例判兼容——一份「如何把协议客户端做成可靠后端」的参考实现。
- 生态层信号:22 个 agent 已经能被同一个 CLI 驱动、横评、编排进 flow;「Agent 调 Agent」的基础设施正在从愿景变成 npm install 就能拿到的东西——尽管它自己还 pre-1.0。