RPC 套接字传输
守护进程通过本地 IPC 流暴露一个 JSON-RPC 2.0 接口,在 Unix 上使用 Unix 域套接字,在 Windows 上使用命名管道。这是 zerocode 等本地客户端的主要传输方式。HTTP/WS 网关则保留用于 webhook、Web 仪表板和远程 REST 使用方。
端点解析
每个数据目录都有自己的端点,因此同一台机器上的多个守护进程实例不会发生冲突。数据目录派生自配置目录(--config-dir / ZEROCLAW_CONFIG_DIR,或 ZEROCLAW_DATA_DIR)。
| 操作系统 | 默认端点 |
|---|---|
| Linux | <data_dir>/daemon.sock(Unix 域套接字) |
| macOS | <data_dir>/daemon.sock(Unix 域套接字) |
| Windows | \\.\pipe\zeroclaw-<hash>,其中 <hash> 派生自 data_dir |
在任意平台上均可通过 ZEROCLAW_SOCKET 环境变量进行覆盖:
sh
export ZEROCLAW_SOCKET=/tmp/my-zeroclaw.sock
zeroclaw daemon
PowerShell
$env:ZEROCLAW_SOCKET = '\\.\pipe\my-zeroclaw'
zeroclaw daemon
线路协议
NDJSON(以换行符分隔的 JSON)。每一行都是一条完整的 JSON-RPC 2.0 消息。没有 HTTP 帧,也没有长度前缀。各平台的帧格式完全一致;命名管道传输的字节流与 Unix 套接字相同。
{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1},"id":1}\n
{"jsonrpc":"2.0","result":{"protocolVersion":1,"serverVersion":"0.8.6"},"id":1}\n
连接限制
本地端点限制了单个客户端可以持有的数量:
| |Limit 限制| |-| | 值 | 客户端所看到的内容 |
|---|---|---|
| 帧大小 | 8 MiB 每行 | 一个带有 id: null 的 -32600 错误,data: {"reason": "frame_too_large", "limit_bytes": 8388608},随后流结束。超大行的剩余部分不会被读取,因此连接无法继续。 |
| 初始化 | 30 秒(连接后) | 让未完成 initialize 的连接在建立 30 秒后被直接关闭且不作回复,因此连接后不发送任何数据的客户端无法占用连接名额。该期限同样涵盖身份验证:若在期限内发送的 initialize 其身份验证仍在进行(例如耗时较长的 OIDC 检查)而期限已到,则该连接会被关闭,而不是延迟接受。 |
| 未完成的帧 | 30 秒,从帧的第一个字节开始计时 | 一旦开始接收某个帧,整行必须在收到其第一个字节后的 30 秒内全部到达,无论其余部分到达得多么稳定。超过此时限后,客户端会收到一个 -32600 错误,其中 id: null,data: {"reason": "frame_timeout", "limit_ms": 30000},随后流结束。帧与帧之间的等待时间不受限制,因此已初始化的客户端可以保持空闲状态。 |
| 写入停顿 | 30 秒/帧 | 如果客户端停止读取满 30 秒,而守护进程仍有输出排队等待发送给它,该客户端就会被断开连接。其他连接不受影响。断开连接会结束该连接发起的轮次,与其他任何断开连接的情况一致。这也包括被挂起的客户端:如果 zerocode 在轮次流式输出期间通过 Ctrl-Z 被停止,或通过 Ctrl-S 被冻结,该轮次会在 30 秒后被取消;而在此之前,它会在客户端恢复后正常完成。 |
| 打开连接 | rpc.max_local_connections,默认值 512 | 连接超过上限时,会收到一个 id 为 null、data 为 {"reason": "connection_limit", "limit": N} 的 -32004 错误,随后流结束。守护进程在开始拒绝连接时记录一条警告日志。该值在监听器启动时读取。 |
超过单帧大小的负载会以分块上传的方式传输,而不是在一行中完整发送。参见分块上传。
握手
第一个 RPC 调用必须是 initialize。在 initialize 成功之前,守护进程会拒绝所有其他方法。协议版本不匹配会产生一个错误码为 -32011 的结构化错误。
{
`jsonrpc`: "2.0",
方法: 初始化,
"参数": {
protocolVersion: 1
},
"id": 1
}
该端点不需要配对令牌。访问控制由操作系统处理:
- Unix:套接字为
0o600,父目录为0o700。 - Windows:命名管道 ACL 默认授予创建用户和
SYSTEM。
方法
| 方法 | 方向 | 描述 |
|---|---|---|
initialize | 客户端 -> 守护进程 | 验证身份并协商协议版本 |
session/new | 客户端 -> 守护进程 | 创建一个智能体会话(需要 agentAlias,可选 cwd、sessionId;如果某个 ID 已处于活动状态,则会将调用方重新绑定到该规范的内存会话,而不会替换其智能体历史记录;如果调用方在其当前授权下可使用的转发环境与该会话创建时的环境不同,则会以 FORBIDDEN 拒绝;可选的 keep_siblings 会禁止对空闲的同模式兄弟会话进行驱逐,适用于自行管理兄弟会话生命周期的多会话客户端) |
session/close | 客户端 -> 守护进程 | 删除实时会话所有者;ACP 持久化历史记录仍可恢复 |
session/kill | 客户端 -> 守护进程 | 删除实时会话,并为其 ACP 持久化历史写入墓碑记录 |
session/delete | 客户端 -> 守护进程 | 删除当前会话及其选定的持久化历史记录 |
session/prompt | 客户端 -> 守护进程 | 运行一个回合(通过 session/update 通知进行流式传输) |
session/cancel | 客户端 -> 守护进程 | 取消正在进行的回合 |
session/state | 客户端 -> 守护进程 | 读取实时会话生命周期状态、当前轮次标识以及可选的当前计划;活动或排队中的工作以 state: "running" 表示,以便恢复客户端在释放保留的工作之前确认终止状态 |
status | 客户端 -> 守护进程 | 服务器版本、协议版本、活动会话列表 |
file/upload/begin, file/upload/chunk, file/upload/commit | 客户端 -> 守护进程 | 以有序分块的方式上传一个文件;参见分块上传 |
session/update | 守护进程 -> 客户端 | 回合期间的流式通知(文本块、工具调用、审批) |
elicitation/create | 守护进程 -> 客户端 | 为 ask-user 和 poll 流程请求交互式输入 |
原子化配置写入
config/set-many 接受一个有序的 sets 数组,其中包含 1–256 个对象,每个对象都带有 prop 和 value。它使用与 config/set 相同的属性语法和值规则。例如,在创建名为 operator 的权限配置文件之后,管理员可以在一次请求中创建一个完整的用户:
{`jsonrpc`:"2.0",方法:config/set-many,"参数":{sets:[{属性:"users.example.uid",“值”:1001},{属性:users.example.permission_profiles,“值”:[operator]}]},"id":2}
成功时返回 {"props":["users.example.uid","users.example.permission_profiles"],"set":true}。所有字段都先暂存到同一个候选配置中并进行校验,然后一次性持久化提交。同一属性的后续条目会覆盖先前的条目。暂存或提交失败时,已持久化的配置和运行中的配置均保持不变;无效条目的错误信息会注明其从零开始的索引。
调用方需要具备 Config:Update 权限,并且对每个请求的路径都拥有授权。守护进程在持有配置写锁的情况下解析当前权限,然后才暂存任何条目。只要有一个路径被禁止,整个批次都会被拒绝并返回 FORBIDDEN,其中包含条目索引,即使前面的路径已被允许也是如此。在等待该锁期间权限被撤销,同样会导致整个批次被拒绝。提供商/模型视图是基于完整候选配置准备的,并在提交成功后安装。删除操作和映射键操作不属于此方法的范围。
双向请求
任一方都可以通过已建立的套接字发送请求。接收方必须使用相同的 id 进行响应,并且只能包含 result 或 error 二者之一。每个对等方都使用各自的 zc-out-<number> 序列发送出站请求;此前缀不是全局唯一的命名空间。关联仍然具有方向性:每个对等方仅根据自己的待处理请求映射匹配响应,因此相同的文本 ID 可以在相反方向上独立处于进行中状态。
{`jsonrpc`:"2.0",方法:"elicitation/create","参数":{"消息":"是否继续?"},"id":zc-out-0}
{`jsonrpc`:"2.0","结果":{action:"接受","内容":{答案:"yes"}},"id":zc-out-0}
显式的 "result": null 表示响应成功,并且仍会解析挂起的调用方。error 对象会将其解析为失败。如果对端没有响应,发起请求的 ask-user 或 poll 操作仍保持现有的超时行为。
每个帧都必须是带有 "jsonrpc": "2.0" 的 JSON 对象。请求必须包含字符串类型的 method;如果存在,params 必须是对象或数组。响应必须包含字符串、数字或 null 类型的 id,并且必须恰好包含一个响应成员。无效 JSON 会产生 -32700(解析错误)。格式错误的请求形信封会产生 -32600(无效请求);如果能够恢复出有效的请求 ID,则使用该 ID。格式错误的响应形信封会在不记录帧内容的情况下记录日志,并在不回复的情况下丢弃,因为回显其 ID 可能会在相反方向完成一个无关请求。无法判定方向的信封使用 id: null。同样会记录日志并忽略未知的有效响应 ID,而不是回复它们,从而防止响应循环。
开启流式传输
session/prompt 在回合完成时返回最终结果。在执行过程中,守护进程会发送 session/update 通知以传递增量事件:
{`jsonrpc`:"2.0",方法:"session/update","参数":{"会话ID":"...",类型:agent_message_chunk,"文本":"Hello"}}
{`jsonrpc`:"2.0",方法:"session/update","参数":{"会话ID":"...",类型:"tool_call",toolCallId:tc_1,"name":bash,rawInput:{...}}}
{`jsonrpc`:"2.0",方法:"session/update","参数":{"会话ID":"...",类型:"tool_result",toolCallId:tc_1,"name":bash,rawOutput:"..."}}
事件类型:agent_message_chunk、agent_thought_chunk、tool_call、tool_result、approval_request。
ACP 持久化生命周期
原生 RPC 将原始可见转录与保留的 provider 上下文分开存储。自动裁剪不会删除或重新编号原始转录行。仅当其保留上下文快照与所覆盖的检查点边界一起提交后,才会发送裁剪通知;写入失败时将抑制该通知。快照不包含运行时系统提示词、召回记忆注入和隐藏推理内容。中断时,恢复流程会将可见检查点进度追加一次,同时根据最新的保留快照加上之后的检查点事件恢复模型上下文。明确为空的保留快照仍然具有权威性。没有快照的会话继续使用旧版 provider 安全重放路径。
ACP 会话在到达终止状态之前可以保留持久化历史。当内存中的所有者被回收后,RPC 提示恢复只会考虑其持久化的 interaction_surface 受支持的持久行;不受支持的 surface 会保持不变,以便之后的直接恢复可以检查原始检查点。
Recovery 会为未完成的对话轮次添加一个对客户端可见的中断标记。提供方重放时会排除该合成标记,而已持久化的工具输出仍受现有转录边界的限制。
生命周期操作具有各自不同的持久化含义:
session/close可能会保留可持久恢复的历史记录。session/kill会保留历史记录,但将该行标记为墓碑(tombstoned),因此不再符合运行时重新加载(rehydration)的条件。session/delete在目标为活动 ACP 会话,或不存在活动模式且选中了 ACP 持久化行时,会先删除 ACP 行及其可恢复的检查点,然后再注销并移除活动会话。同 ID 的活动 Chat 会话与 ACP 存储保持隔离。
终止并删除信号取消操作会在执行可能失败的 SQLite 操作之前进行。如果对于空闲目标该操作失败,RPC 会报告内部错误,并保留存活的所有者和通道注册。如果硬取消已移除活动所有者,RPC 无法恢复该存活代次。存储恢复正常后,持久化行和可恢复的检查点仍然可用。
分块上传
file/attach 将整个文件放在一个帧中传输,计入 base64 编码后,这会使其实际上远低于单文件大小上限。file/upload/begin、file/upload/chunk 和 file/upload/commit 则将同一上传分块传输,并且与 file/attach 一样将文件落盘:存放在会话代理的工作区中,以其内容哈希命名,按会话去重,结果中包含相同的标记。
file/upload/begin接收session_id、size_bytes,以及可选的filename和sha256(十六进制)。返回结果包含upload_id、chunk_bytes(1 MiB)和max_bytes(每个文件 10 MB 的限制)。file/upload/chunk接口需依次传入upload_id、offset和data_b64。offset必须等于目前已接收的字节数;返回结果会报告received_bytes。若重发已接受且内容相同的分块,服务器会原样确认,因此客户端在响应丢失后可以安全地重试。file/upload/commit在所有声明的字节都已到达后,配合upload_id使用。若声明了sha256,则必须与之匹配。结果为一个file/attach文件条目。
上传文件归属于发起它的那个连接:其他连接无法引用该文件,并且在该连接关闭时会被丢弃。限制如下:
- 一个连接一次最多可同时暂存四个上传文件。
- 守护进程在所有连接中暂存的数据总量最多为 256 MiB。计入额度的包括每次上传的负载数据及其保留的元数据(会话 ID、所有者、代理、文件名、声明的哈希),因此空负载无法免费占用内存。超过 255 字节的
filename会被拒绝。 - 上传空闲超过五分钟将被丢弃。当新上传超出预算容量时,守护进程会首先回收所有空闲超过该期限的上传,即使其连接仍处于打开状态,从而确保沉默的客户端无法占用预算。
这三个方法仅在本地连接上提供服务;WSS 对端会收到 -32012。调用它们需要 files:create 授权,begin 还会检查:对于非 admin 的主体,需确认其拥有该会话的所有权,并且有权使用该会话所属的代理。
上传会绑定到发起时所针对的那个确切会话:具有该 id 的当前活动会话、其所有者及其智能体。commit 在会话锁保护下一步完成存储。它首先确认该会话仍是同一个会话,然后依据当时生效的策略检查调用方的凭据、files:create、会话所有权以及智能体权限,之后才会写入并索引文件。若上传过程中授权被撤销,或会话被关闭,或以相同 id 重新创建,则提交会在写入任何内容之前失败。检查与写入期间,会同时锁定已接受的授权策略和已配对令牌集合,因此在检查完成后到达的策略变更或取消配对,只会在文件存储完成之后生效:上传排在它们之前,而不是插在检查与写入之间。file/attach 同样通过该步骤进行存储,并具有相同的所有权要求。
A对已索引上传内容的重复上传会与磁盘上的文件进行比对。如果该文件被编辑、删除或替换为链接,则在返回之前,已上传的字节会被写回到已索引的路径。
临时模式
zeroclaw daemon --ephemeral 会跟踪已连接的客户端,并在最后一个客户端断开连接时自行终止(在 1 秒的宽限期之后)。在宽限期内重新连接将取消关闭操作。在至少有一个客户端连接之前,该守护进程不会退出。
未使用 --ephemeral 启动的守护进程会忽略客户端数量,并持续运行直到被显式停止。
安全
- Unix 套接字目录:
0o700(仅所有者) - Unix socket 文件:
0o600(仅所有者) - Windows 命名管道:默认 ACL 向创建用户和
SYSTEM授予权限 - Linux 上的
SO_PEERCRED提供连接进程的 PID 和 UID 用于审计日志;Windows 则将pipe:local记录为对端标签
快速测试
在一个终端中启动守护进程:
sh
zeroclaw daemon
在 Unix 上的第二个终端中,使用 socat 连接:
sh
socat READLINE UNIX-CONNECT:~/.zeroclaw/data/daemon.sock
请逐行粘贴:
{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":1},"id":1}
{"jsonrpc":"2.0","method":"status","params":{},"id":2}
在 Windows 上,使用任意命名管道客户端(PowerShell [System.IO.Pipes.NamedPipeClientStream]、通过 WSL 使用 nc,或直接运行 zerocode)。
内部原理
调度层位于 crates/zeroclaw-runtime/src/rpc/:
| 文件 | 角色 |
|---|---|
transport.rs | RpcTransport trait |
turn.rs | execute_turn() 共享回合执行器 |
session.rs | RpcSession、SessionStore |
dispatch.rs | RpcDispatcher 方法路由 |
local.rs | LocalTransport + 监听器(Unix 套接字 / Windows 命名管道) |
wss.rs | WSS(WebSocket Secure)传输 + TLS 接收器 |
attachments.rs | 文件上传处理、去重、标记生成 |
| upload.rs | 分块上传暂存:顺序、单连接与进程级上限 |
RpcTransport trait 的设计使得额外的传输方式(vsock、自定义 IPC)能够无缝接入,而无需改动分发或会话逻辑。local.rs 模块使用 tokio::io::split 将 Unix 和 Windows 原语封装在单个 LocalTransport struct 之后,因此读写循环可在两个平台间共享。