Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ACP — 代理客户端协议

ACP is a JSON-RPC 2.0 protocol over stdio that lets editors and IDEs drive a running ZeroClaw agent as a session host. Newline-delimited JSON — lightweight, streamable, easy to wire to a subprocess.

Think of it as “LSP for agents”: the editor launches zeroclaw acp, sends prompts over stdin, and receives session updates on stdout.

你打算用它做什么

  • 一个提供“向代理询问此文件”命令的编辑器扩展
  • 集成终端复用器,打开带有代理会话的侧边窗格
  • 一个通过编程方式驱动代理的 CI 运行器,无需完整的网关设置
  • 任何希望在不使用 HTTP 且无需绑定端口的情况下使用代理会话的功能

Protocol shape — v1

All messages are JSON-RPC 2.0 (newline-delimited). ZeroClaw implements protocol version 1.

initialize

握手。返回服务器能力。

→ {`jsonrpc`:"2.0","id":1,方法:初始化}
← {`jsonrpc`:"2.0","id":1,"结果":{
    "protocolVersion": 1,
    "agentCapabilities": {
      "loadSession": true,
      "promptCapabilities": {"image": false, "audio": false, "embeddedContext": false},
      "mcpCapabilities": {"http": false, "sse": false},
      "sessionCapabilities": {"resume": {}, "close": {}}
    },
    "agentInfo": {
      "name": "zeroclaw-acp",
      "title": "ZeroClaw ACP",
      "version": "0.7.x"
    },
    "authMethods": [],
    "_meta": {
      "zeroclaw": {
        "默认模型": "anthropic/claude-sonnet-4.6",
        "maxSessions": 10,
        "sessionTimeoutSecs": 3600
      }
    }
  }}

loadSession: true and sessionCapabilities: {"resume": {}, "close": {}} indicate that session persistence is active. If the SQLite store could not be opened at startup, all three are absent or false and session/load, session/resume, and session/close will return SESSION_NOT_FOUND errors.

_meta.zeroclaw carries ZeroClaw-specific extension fields not in the base ACP spec. Clients that only implement the base spec can ignore this object.

The server always responds protocolVersion: 1. If you send a client-side protocolVersion: 0, you still get 1 back — v0 clients will see parse errors on the new message shapes; see version compatibility below.

session/new

打开一个隔离的代理会话。

agentAlias names which configured [agents.<alias>] entry to use. It is required when more than one agent is configured; when exactly one agent exists, it is auto-selected and the field may be omitted. The alias accepts the camelCase agentAlias, the snake_case agent_alias, or the short agent form.

The optional cwd parameter (aliases: workspaceDir, workspace_dir) pins the per-session file-access boundary — it becomes the workspace_dir inside the SecurityPolicy that all file tools enforce. The agent’s persistent data directory (memory, identity, cron) remains the daemon-level workspace_dir from config.

→ {`jsonrpc`:"2.0","id":2,方法:"session/new","参数":{
    "agentAlias": "myagent",
    当前工作目录: "/path/to/project"
  }}
← {`jsonrpc`:"2.0","id":2,"结果":{
    "会话ID": "s-ab12cd",
    "workspaceDir": "/path/to/project"
  }}

cwd is canonicalized on intake — ../ traversal cannot escape the intended root. If cwd is omitted, the server uses the daemon’s launch directory.

session/prompt

Send a prompt. The response is a sequence of session/update notifications streaming back, terminated by the session/prompt result.

→ {`jsonrpc`:"2.0","id":3,方法:"会话/提示","参数":{
    "会话ID": "s-ab12cd",
    "提示": 总结最后一次提交中的更改。
  }}
← {`jsonrpc`:"2.0",方法:"session/update","参数":{
    "会话ID": "s-ab12cd",
    "update": {"sessionUpdate": "agent_message_chunk", "内容": {类型:"文本","文本":“最后一次提交...”}}
  }}
← {`jsonrpc`:"2.0",方法:"session/update","参数":{
    "会话ID": "s-ab12cd",
    "update": {"sessionUpdate": "tool_call", "toolCallId": "tc-1", "title": "shell",
               类型: "execute", 状态: "pending", "rawInput": {...}}
  }}
← {`jsonrpc`:"2.0",方法:"session/update","参数":{
    "会话ID": "s-ab12cd",
    "update": {"sessionUpdate": "tool_call_update", "toolCallId": "tc-1",
               状态: 已完成, "rawOutput": "..."}
  }}
← {`jsonrpc`:"2.0","id":3,"结果":{
    "会话ID": "s-ab12cd",
    "stopReason": "end_turn",
    "内容": "The last commit introduces..."
  }}

stopReason is "end_turn" on normal completion. The ACP completion signal is stopReason; ZeroClaw also includes the current final content string for existing clients.

session/update notifications (agent → client)

ZeroClaw sends four kinds of session/update notification during a prompt turn. The discriminant is the sessionUpdate field inside update:

sessionUpdate valueWhen emittedKey fields
agent_message_chunkEach streaming text tokencontent.type = "text", content.text
agent_thought_chunkInternal reasoning tokens (when enabled)content.type = "text", content.text
tool_callTool call initiatedtoolCallId, title, kind, status: "pending", rawInput
tool_call_updateTool call completedtoolCallId, status: "finished", rawOutput, content[]

toolCallId on tool_call and tool_call_update are stable and correlated — the update completing a call carries the same toolCallId as the one that opened it.

The name field on tool_call_update is a ZeroClaw extension (not required by the base ACP spec). Clients can use it for display; it’s safe to ignore.

session/request_permission (agent → client, outbound request)

When a tool requires user approval (via always_ask in the autonomy config, or the ask_user/escalate_to_human tools), ZeroClaw issues a JSON-RPC request from agent to client. The client must reply with a result before the tool call proceeds.

← {`jsonrpc`:"2.0","id":"zc-out-0",方法:"session/request_permission","参数":{
    "会话ID": "s-ab12cd",
    "options": [
      {"optionId": "allow-once",  "name": "Allow once",  类型: "allow_once"},
      {"optionId": "allow-always","name": "Always allow",类型: "allow_always"},
      {"optionId": "reject-once", "name": "Reject",      类型: "reject_once"}
    ],
    "toolCall": {
      "toolCallId": "approval-...",
      "title": "Approve shell?",
      类型: "execute",
      状态: "pending",
      "rawInput": {"tool": "shell", "summary": "git status --short"},
      "内容": [{类型: "内容", "内容": {类型: "文本", "文本": "git status --short"}}]
    }
  }}
→ {`jsonrpc`:"2.0","id":"zc-out-0","结果":{
    "outcome": {"outcome": "selected", "optionId": "allow-once"}
  }}

The server-issued id ("zc-out-N") is always a string prefixed zc-out- — disjoint from any integer or string ids the client uses for its own requests.

Response shape:

  • {"outcome": {"outcome": "selected", "optionId": "<id>"}} — user picked an option
  • {"outcome": {"outcome": "cancelled"}} — user dismissed the prompt

If the client never replies (crash, network drop, user closes IDE), the request times out after sessionTimeoutSecs and the tool call is denied.

ask_user uses the same session/request_permission mechanism, mapping the question’s choices to permission options. Free-form (no-choices) ask_user is not supported until the ACP elicitation RFD lands. Calling ask_user without choices on an ACP session fast-fails with a clear error.

session/cancel (ZeroClaw extension)

Abort an in-flight session/prompt turn. This method is a ZeroClaw extension, not part of the base ACP spec. If ACP later standardizes a conflicting session/cancel, ZeroClaw will move its extension to _meta/session/cancel.

Cancel vs. stop: session/cancel aborts an in-flight prompt turn and returns stopReason: "cancelled" with any streamed text accumulated up to the interrupt point. session/stop gracefully ends the session after the current turn completes — it waits for the turn to finish rather than interrupting it.

The canonical parameter is sessionId; session_id is accepted as a compatibility alias.

→ {`jsonrpc`:"2.0",方法:"session/cancel","参数":{"会话ID":"s-ab12cd"}}
← {`jsonrpc`:"2.0",方法:"session/update","参数":{
    "会话ID": "s-ab12cd",
    "update": {"sessionUpdate": "agent_message_chunk", "内容": {类型:"文本","文本":"partial..."}}
  }}
← {`jsonrpc`:"2.0","id":3,"结果":{
    "会话ID": "s-ab12cd",
    "stopReason": "cancelled",
    "内容": "partial...\n\n[interrupted by user]"
  }}

Only one session/prompt may be active for a session at a time. A second prompt for the same session is rejected until the active turn completes or is cancelled.

If no turn is active for the session, the cancel is a noop — it succeeds silently without error. This follows ACP notification semantics: notifications must not produce errors.

session/stop (ZeroClaw extension)

Cleanly end a session. Not in the base ACP spec — ZeroClaw-specific. If a future ACP spec revision adds session/stop with different semantics, this will be renamed _meta/session/stop.

→ {`jsonrpc`:"2.0","id":4,方法:"session/stop","参数":{"会话ID":"s-ab12cd"}}
← {`jsonrpc`:"2.0","id":4,"结果":{已停止:true}}

session/update (client → server) (ZeroClaw extension)

ZeroClaw also accepts inbound session/update (and the legacy session/event alias) notifications from the client for custom event injection. Not in the base ACP spec — ZeroClaw-specific. If the ACP spec later defines an inbound session/update with different semantics, this will be renamed _meta/session/update.

Session persistence

ZeroClaw automatically persists ACP sessions to SQLite. No configuration is required — the store opens at <workspace_dir>/sessions/acp-sessions.db whenever zeroclaw acp starts or a gateway WebSocket ACP connection is accepted. If the file cannot be created (read-only filesystem, bad permissions), the server falls back to in-memory-only sessions and loadSession reports false in the initialize response.

What is persisted:

  • Session metadata: sessionId, workspaceDir, created_at, last_activity
  • Full conversation history: every ConversationMessage written after each completed session/prompt turn, in one atomic transaction per turn

Sessions survive process restarts. A session created in one zeroclaw acp invocation can be loaded or resumed in a later one, as long as the same workspace_dir is in use (and therefore the same acp-sessions.db file).

Sessions are not automatically deleted. Use session/close to deactivate a session without deleting it, then session/load or session/resume to bring it back.

session/load (ZeroClaw extension)

Restore a previously persisted session with full history replay. The server seeds the agent with the stored conversation history, then streams that history back to the client as a sequence of session/update notifications before returning. The client receives the same update stream it would have seen had the session never ended.

→ {`jsonrpc`:"2.0","id":5,方法:"session/load","参数":{"会话ID":"s-ab12cd"}}
← {`jsonrpc`:"2.0",方法:"session/update","参数":{
    "会话ID": "s-ab12cd",
    "update": {"sessionUpdate": "agent_message_chunk", "内容": {类型:"文本","文本":“最后一次提交...”}}
  }}
← ... (remaining stored messages replayed as session/update notifications)
← {`jsonrpc`:"2.0","id":5,"结果":{}}

After session/load returns, the session is active and ready to accept session/prompt calls.

session_id is accepted as a snake_case alias for sessionId.

Errors:

代码含义
-32000 SESSION_NOT_FOUNDNo record exists for the given sessionId in the store
-32001 SESSION_LIMIT_REACHEDmax_sessions active sessions already in flight
-32602 INVALID_PARAMSSession is already active — call session/close first
-32603 INTERNAL_ERRORSQLite read failure

session/resume (ZeroClaw extension)

Restore a previously persisted session without history replay. The agent is seeded with the stored conversation history so it has full context for the next turn, but no session/update notifications are emitted. Use this when the client already has the history from a previous connection and only needs the agent state restored.

→ {`jsonrpc`:"2.0","id":5,方法:"session/resume","参数":{"会话ID":"s-ab12cd"}}
← {`jsonrpc`:"2.0","id":5,"结果":{}}

After session/resume returns, the session is active and ready to accept session/prompt calls. Same errors as session/load.

Load vs. resume: use session/load when reconnecting after an unexpected disconnect and the client needs to rebuild its UI from the stored history. Use session/resume when the client already has the history (e.g., it stored it locally) and only needs the server-side agent state restored.

session/close (ZeroClaw extension)

Deactivate an active session: cancels any in-flight turn, removes the session from the in-memory active set, and unregisters the ACP back-channel. The session record in the SQLite store is not deleted — the session can still be restored with session/load or session/resume later.

→ {`jsonrpc`:"2.0","id":6,方法:"session/close","参数":{"会话ID":"s-ab12cd"}}
← {`jsonrpc`:"2.0","id":6,"结果":{}}

session_id is accepted as a snake_case alias for sessionId.

Returns SESSION_NOT_FOUND (-32000) if the session is not currently active (it may still exist in the store).

Close vs. stop: session/close deactivates the session while preserving its persistent record for later reload. session/stop also removes the session from memory but has the same effect on the store. Neither deletes the SQLite record.

配置

[acp]
# Which agent to use when session/new omits agentAlias.
# Falls back to auto-select when exactly one agent is configured.
default_agent = "myagent"

max_sessions = 10
session_timeout_secs = 3600       # idle sessions killed after 1 hour

All three fields are optional. default_agent is consulted when session/new omits agentAlias and more than one agent is configured; if it is absent and exactly one [agents.<alias>] entry exists, that agent is auto-selected.

When running zeroclaw acp as a subprocess, the command starts the server unconditionally. When running as a daemon, the gateway exposes ACP over WebSocket at /acp with no additional config required.

运行中

作为子进程(典型的 IDE 集成):

zeroclaw acp

该二进制文件读取标准输入,写入标准输出,在遇到文件末尾(EOF)时退出。

Via the daemon gateway (remote or same-host):

Start the daemon normally. The gateway always exposes ACP over WebSocket at /acp — no extra config flag is required. Clients connect directly, or through zeroclaw-acp-bridge, which bridges the stdio ACP protocol to the gateway WebSocket:

zeroclaw-acp-bridge

The bridge reads the gateway address and auth token from the same config.toml as the daemon. When the daemon runs with a non-default config directory (e.g. --config-dir /tmp/zeroclaw), point the bridge at the same directory:

zeroclaw-acp-bridge --config-dir /tmp/zeroclaw
# or equivalently:
zeroclaw-acp-bridge --config-dir=/tmp/zeroclaw

You can also supply the bearer token directly via ZEROCLAW_ACP_BRIDGE_TOKEN if you prefer not to rely on the cached token file.

Version compatibility

ACP v0 clients (using the flat {streaming, maxSessions, ...} initialize response and kind: "text"|"tool_call" session/update shape) will see deserialization errors on connecting to a v1 server. The discriminants and envelope shapes changed in a breaking way. Upgrade steps:

  • Use sessionUpdate (not kind) to discriminate session/update notifications.
  • Parse session/prompt results as {sessionId, stopReason, content} (not {finished, usage}).
  • Implement session/request_permission response handling — the approval mechanism moved from a server notification to a client-answered RPC.
  • Drop the systemPrompt param from session/new — it is not read.

安全

ACP inherits the running config’s autonomy level. When [autonomy] level = "supervised", medium-risk tool calls trigger approval via the ACP back-channel — a session/request_permission outbound request the client must acknowledge. In full mode, tool calls execute without approval and workspace_only is implicitly disabled (the agent can reach paths outside the session cwd); forbidden_paths still apply.

The cwd from session/new becomes the SecurityPolicy workspace boundary used by all file and shell tools for that session. Note: the agent’s system prompt currently reflects the daemon’s global workspace_dir rather than the session cwd — this does not affect enforcement, only the directory the model believes it is working in.

代码参考

  • ACP server: crates/zeroclaw-channels/src/orchestrator/acp_server.rs
  • ACP back-channel: crates/zeroclaw-channels/src/acp_channel.rs
  • Session store (SQLite): crates/zeroclaw-infra/src/acp_session_store.rs
  • Gateway ACP-over-WebSocket endpoint: crates/zeroclaw-gateway/src/acp.rs
  • Per-session path enforcement: crates/zeroclaw-config/src/policy.rs (SecurityPolicy::from_config), crates/zeroclaw-runtime/src/agent/agent.rs (from_config_with_session_cwd_and_mcp)
  • OS-level sandbox detection/backends: crates/zeroclaw-runtime/src/security/detect.rs, landlock.rs, bubblewrap.rs, seatbelt.rs

另见