Repo Notes · openclaw/acpx

acpx:让 Agent 和 Agent 说话,不再抓终端

一个 headless 命令行客户端,把 Agent Client Protocol(ACP) 变成脚本与编排器可以直接消费的接口:持久会话、一次性运行、权限策略、机器可读输出——对 22 个内置编码 Agent 一视同仁。

状态 pre-1.0,接口仍在演化 运行时 Node ≥ 22.13 npm i -g acpx@latest License MIT 文档站 acpx.sh
signature · 一次 acpx 会话在 ACP 线上的样子(JSON-RPC over stdio)

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 存在的前提:

Fig.1 · topology · ACP 把 N×M 私接变成 N+M 标准线
WITHOUT ACP Zed JetBrains CLI 工具 Codex Claude Code Gemini CLI 3 × 3 = 9 套私有集成,每对客户端/Agent 各写一遍胶水 WITH ACP ACP Zed JetBrains acpx 等客户端 Codex Claude Code Gemini CLI 3 + 3 = 6 条标准线,换任何一侧都不动另一侧
读法:左边是 ACP 之前的世界——每个编辑器为每个 Agent 写一套私有适配;右边是 ACP 之后——客户端实现一次 client 侧,Agent 实现一次 adapter 侧,任意组合即插即用。acpx 就是「client 侧」的一个通用实现,跑在命令行里。

Positioning · VISION定位:agent 优先的 CLI,同时是可复用的后端

VISION.md 给 acpx 的自我定义非常克制:「最小的有用 ACP 客户端」——让一个 Agent 通过 ACP 与另一个 Agent 对话,不要 PTY 抓终端,不要 adapter 专用胶水。它刻意同时服务两种身份:

  • agent-first CLI——主要用户是另一个 Agent、编排器或 harness,人类当然也能直接敲,但那是次要约束;
  • 可复用后端——别的工具想要 ACP 会话、结构化输出、队列和持久化,不必自己重造,委托给 acpx 即可(它导出 acpx/runtime 与 acpx/flows 两个内嵌库)。

六条设计原则决定了这个项目的形状:

01 · Interoperability first

标准是 ACP,不是某一家 Agent 的怪癖。线级行为贴近协议,兼容性差异做稳妥的归一化,保留结构化数据让下游自己决策,拒绝锁定单一 harness 的特性。

02 · Keep the core small

只做强 ACP 客户端该做的事:连接 Agent、管会话、安全排队、处理权限与生命周期、渲染结构化响应。对核心没贡献的功能不属于核心。

03 · Robust by default

面向长时间、自动化、多轮次场景:默认会话连续性、安全排队、清晰的失败模式、可恢复的生命周期。无聊但到处能用的特性,胜过聪明但只在一处能跑的。

04 · Conventions are API surface

数据模型、配置键、flag、输出形状都是产品表面,反复推敲才准入。文档里点名例子:定名 claude 而非 claude-code,是有意为之的约定,不是随手缩写。

05 · Fully customizable

静态 JSON 覆盖常见场景;不够用时允许以受控方式程序化扩展本地配置(精神上类似 Pi)。定制的目的是不 fork 就能适配环境,不是把核心做大。

06 · Backend-friendly

终端用户永远不直接敲 acpx 的工具也要能用它:会话持久化、队列所有权、prompt 串行化、adapter 进程管理、权限策略,这些脏活它替你扛。

Feature test 新特性的唯一检验标准:它是否让 acpx 更互通、更稳健、或更适合做一个轻量 ACP 后端?不是,就该活在核心之外。VISION 同时列了四条「不成为」:大杂烩自动化框架、替代所有 harness、套壳 UI 产品、无核心的 Agent 特例堆。

Architecture · 数据路径架构:一条不做终端抓取的直连线

architecture 文档把数据路径写成一行:CLI command → AcpClient → ndjson/stdio → ACP adapter → coding agent。CLI 全程只说结构化的 ACP JSON-RPC 消息,从不解析交互式 UI 的终端转义序列——这是它与「套壳 pexpect」类工具的本质区别。

CLIsrc/cli.ts 入口 · cli-core.ts 命令装配与输出策略
↓
AcpClientsrc/acp/client.ts · 拉起 adapter 进程,ClientSideConnection + ndJsonStream 接管传输
↓ ndjson / stdio
ACP adapter每家 Agent 一个适配进程(codex-acp、claude-agent-acp…)
↓
coding agent真正的模型与工具执行层

§其余核心组件

  • src/session/session.ts —— 会话持久化、resume/create、队列所有权、软关闭生命周期、超时/中断与清理;
  • src/permissions.ts —— 权限策略(approve-all / approve-reads / deny-all)与交互式回退;
  • src/cli/output/output.ts —— text / json / quiet 三种流式输出格式。

§一次 prompt 的协议流(顺序即语义)

initialize握手;客户端声明 fs.readTextFile / fs.writeTextFile / terminal 三项能力,协商协议版本。
session/new 或 loadSession本地有已存会话且 adapter 支持就先尝试 loadSession;遇到 not-found 类错误自动回退 newSession。
session/prompt提交这一轮的内容块。
流式 session/update 直到完成thinking、tool 调用、diff、文本、完成状态都以结构化通知流回,经格式化器输出,而非终端转义序列。

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」:

首个进程抢到会话锁,成为队列 owner它持有与 adapter 的活跃连接和本轮 turn。
并发调用经本地 IPC 把 prompt 递给 owner 排队--no-wait 即投即走(fire-and-forget),不阻塞脚本。
队列排空后,owner 以 idle TTL 驻留等新活默认 300 秒(--ttl 可调),期间随时接续下一轮。
TTL 到期:体面退场关停 owner、释放 socket/lock、清掉队列 owner 记录。sessions close / sessions new 做软关闭——终止相关进程但保留会话 JSON,之后仍可显式恢复。
cancel ≠ SIGINT 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 完成度最直接的一份文档:

已支持(spec 方法 → acpx 特性)
分组ACP 方法acpx 侧
会话核心initialize · authenticate握手与能力协商;auth 握手兼容靠 ACP 认证的 adapter
session/new · session/loadsessions new;崩溃恢复/重连
session/prompt · session/updateprompt / exec / 隐式 prompt;流式输出(thinking、tools、text、diffs)
session/cancel · session/request_permission协作式取消;三档权限模式
session/set_mode · set_config_option · set_modelset-mode、set 命令;set_model 仅限声明了 legacy models 元数据的 adapter
客户端权威 fsfs/read_text_file · fs/write_text_fileagent 请求读写时由客户端执行;默认按 cwd 沙箱限定路径
客户端权威 terminalterminal/create · output · wait_for_exit · kill · release完整终端生命周期:进程跟踪、输出缓冲、按 terminal id 清理
未支持(规格标记 unstable,语义未稳)
方法用途
session/fork unstable把一段对话分叉成并行替代路径
session/list unstableadapter 侧会话清单(当前只有 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——等各家语义稳定了再进后续版本。
为什么这一节值得单独读 协议生态最常见的死法是「各说各话的兼容」。把互操作性收敛成 21 个可自动执行、带超时与 transcript 的断言,是 acpx 从「又一个客户端」走向「生态基础设施」的分水岭——也呼应了它自己的 VISION 第一条。

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 的既有行为。

五种节点:运行时管秩序,agent 管推理
节点职责
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"],
  ],
});
Fig.2 · process · triage flow 的图执行(triangle = decision,圆角 = acp)
classify decision fix acp · implement + verify write_doc acp · draft docs entry 结果持久化 runs/<runId>/ 结果持久化 replay 可回放 input edge 标注:bug|feat → fix · doc → write_doc(类型化 decisionEdge)
分工:运行时拥有图执行、步序、存活与超时、ACP 会话生命周期、持久化与回放、decision 路由;agent 只在 acp/decision 节点内做推理与工具调用。flow 文件只声明工作流,不实现引擎。

§工程细节里见态度

  • 权限前置声明,快速失败: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 …

built-in registry(docs/agents.md,2026-09 快照)
名字adapter 命令包装的上游
codex ★默认npx -y @agentclientprotocol/codex-acpCodex CLI
claudenpx -y @agentclientprotocol/claude-agent-acpClaude Code
geminigemini --acpGemini CLI
cursorcursor-agent acpCursor CLI
copilotcopilot --acp --stdioGitHub Copilot CLI
droiddroid exec --output-format acpFactory Droid(factory-droid 亦解析到此)
pinpx pi-acpPi Coding Agent
openclawopenclaw acpOpenClaw ACP bridge
opencodenpx -y opencode-ai acpOpenCode
qwenqwen --acpQwen Code
kimikimi acpKimi CLI(MoonshotAI)
iflowiflow --experimental-acpiFlow CLI
traetraecli acp serveTrae CLI
kirokiro-cli-chat acpKiro CLI
qoderqodercli --acpQoder CLI
kilocodenpx -y @kilocode/cli acpKilocode
mcodemcode acpMiniMax Code
muxmux acp(ACPX 维护的 npm range)Mux
grok-buildgrok agent stdioxAI Grok Build
fast-agentuvx fast-agent-mcp acpfast-agent
poolpool acpPoolside
zeroclawzeroclaw acpZeroClaw(原生 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三句话带走

  1. 协议层价值:ACP 把「编辑器×Agent」的 N×M 私接变成 N+M 标准插座;acpx 证明了这套插座在纯命令行、纯自动化场景里已经够用。
  2. 工程层样本:显式会话作用域、队列 owner + IPC、软关闭、退出码绑定权限统计、conformance 用例判兼容——一份「如何把协议客户端做成可靠后端」的参考实现。
  3. 生态层信号:22 个 agent 已经能被同一个 CLI 驱动、横评、编排进 flow;「Agent 调 Agent」的基础设施正在从愿景变成 npm install 就能拿到的东西——尽管它自己还 pre-1.0。
时效提示 本文基于 openclaw/acpx 仓库主分支文档快照整理(2026-09);项目 pre-1.0,CLI 约定、flag 与覆盖面都可能变化,以 acpx.sh 与仓库文档为准。