OpenClaw
← 总览
01
ACP · agent client protocol

ACP 会话生成

ACP(Agent Client Protocol)是 OpenClaw 与外部智能体编排框架之间的双向接口约定。核心实现集中在 src/agents/acp-spawn.ts——这是一个长达 1708 行的文件,负责把一次 ACP 会话从「策略校验」一路推进到「子进程派生」与「父子流接续」。

OpenClaw 在 ACP 这条协议上同时站在两端。一端是被集成方:它可以作为子代理被外部 harness(例如 Claude Code、Codex 这类宿主)拉起,对外暴露符合 ACP 约定的会话能力,宿主通过协议向它下达任务、接收增量输出。另一端是编排方:OpenClaw 自身也能把外部 ACP harness 当作子代理来调度。

作为编排方时,它并不是在自己进程内「内联」运行外部 harness,而是派生出一个独立子进程来承载该 harness,再让这个子进程反连(reverse-connect,「反连」)回父进程的控制面。这种「父派生、子反连」的拓扑把外部代理彻底隔离在独立进程里,既划清了崩溃域,也让父进程能以统一的会话句柄来管理任意数量的异构子代理。

acp-spawn.ts 正是承接这套拓扑的枢纽:它先经过策略闸门判定本次派生是否被允许,再向会话管理器登记句柄,随后派生子进程并建立 stdout 接续;任何一步失败都会触发回滚清理,避免资源泄漏或留下孤儿进程。

role · 01

会话生成

校验策略后向会话管理器登记句柄,派生独立子进程承载外部 ACP harness,完成一次会话的冷启动。

role · 02

父子 relay

子进程的 stdout 经 parent-stream 接续回父进程的会话流,让外部代理的增量输出在父侧统一可见。

role · 03

派生限制

以最大子会话数与最大派生深度双重约束扇出与递归,并在父进程崩溃后回收孤儿子运行。

02
imports · the dependency graph

导入链与 acp-core

acp-spawn.ts 的导入头部就是它的依赖地图:会话管理、失败清理、策略闸门、会话元数据、以及来自独立包 @openclaw/acp-core 的标识符解析与运行时类型,共同拼出整条派生链。

src/agents/acp-spawn.ts · 导入链
import { getAcpSessionManager } from "../acp/control-plane/manager.js";
import { cleanupFailedAcpSpawn, type AcpSpawnRuntimeCloseHandle } from "../acp/control-plane/spawn.js";
import { isAcpEnabledByPolicy, resolveAcpAgentPolicyError } from "../acp/policy.js";
import { readAcpSessionMeta } from "../acp/runtime/session-meta.js";
import { resolveAcpSessionCwd, resolveAcpThreadSessionDetailLines } from "@openclaw/acp-core/runtime/session-identifiers";
import type { AcpRuntimeSessionMode } from "@openclaw/acp-core/runtime/types";

这条导入链横跨两个边界。前四行落在仓库内部的 src/acp/ 子树——控制面(control-plane)、策略(policy)与运行时(runtime);后两行则越过包边界,指向独立发布的 @openclaw/acp-core。

@openclaw/acp-core 是一个独立包,把 ACP 协议中与具体宿主无关的「纯逻辑」抽离出来:会话标识符解析(session identifier resolution)、运行时类型定义(如 AcpRuntimeSessionMode),以及会话元数据的读写(session-meta.js)。把这些下沉到独立包,既让被集成方与编排方共享同一份标识符语义,也避免了仓库内部代码与协议核心相互纠缠。

manager.js

会话管理器

getAcpSessionManager() 返回进程级的会话管理器,负责对每个 ACP 会话句柄做登记、查找与回收,是父侧统一管理子代理的中枢。

spawn.js

失败清理

cleanupFailedAcpSpawn() 在派生中途失败时回滚已分配的资源;AcpSpawnRuntimeCloseHandle 是对应的关闭句柄类型。

policy.js

策略闸门

isAcpEnabledByPolicy() 判定某代理是否被允许使用 ACP;resolveAcpAgentPolicyError() 在被拒绝时给出具体的拒绝原因。

03
lifecycle · spawn sequence

会话生成流程

一次 ACP 会话的冷启动是一条线性流水线:从策略校验、句柄登记,到子进程派生,再到反连建立父子流。每一阶段都依赖上一阶段的产物,任一环节失败都会触发回滚。

图 01 · acp-spawn 派生序列
acp-spawn.ts
入口 · 1708 行
policy
isAcpEnabledByPolicy()
策略校验
manager
getAcpSessionManager()
登记句柄
spawn child
child process
独立子进程
反连
parent-stream relay
stdout 接续

第一步 · 策略校验。派生开始前,acp-spawn.ts 先调用 isAcpEnabledByPolicy() 判定发起方代理是否被允许使用 ACP。若不被允许,则用 resolveAcpAgentPolicyError() 取出具体拒绝原因并提前终止——这一步是整条链的闸门,杜绝未授权的派生。

第二步 · 登记句柄。通过校验后,调用 getAcpSessionManager() 取得进程级会话管理器,并在其中登记本次会话的句柄。此后子进程的查找、状态更新与最终回收都以这个句柄为索引。

第三步 · 派生子进程。会话管理器就位后,派生出独立的子进程来承载外部 ACP harness。resolveAcpSessionCwd() 在此前确定子进程的工作目录,AcpRuntimeSessionMode 决定其运行时模式。

第四步 · 反连接续。子进程启动后反连回父进程,由 parent-stream 把子进程的 stdout 接续进父侧的会话流,至此一次会话冷启动完成,外部代理的增量输出开始在父侧可见。

失败回滚:上述任一阶段中途失败(句柄已登记但子进程未起、或反连未建立等),都会调用 cleanupFailedAcpSpawn() 回滚已分配的资源,确保不残留半成品会话或孤儿进程。
04
relay · parent-child stream

父子进程 stdout relay

外部 ACP harness 运行在子进程里,它的 stdout 不会被父进程直接消费,而是经由 acp-spawn-parent-stream.ts 接续进父进程的会话流——这一层 relay 是「父派生、子反连」拓扑能统一可见的关键。

子进程并不知道父侧会话流的内部结构,它只管把增量输出写到自己的 stdout。relay 层负责消费这条 stdout,逐段解析后写回父进程对应的会话流。会话的标识与定位则下沉到 @openclaw/acp-core 一侧:resolveAcpThreadSessionDetailLines() 解析线程会话的明细行,readAcpSessionMeta() 读取会话元数据,二者共同把一段裸输出还原成「属于哪个会话、处于什么状态」的结构化信息。resolveAcpSessionCwd() 则负责解析子进程的工作目录。

relay 组件职责
acp-spawn-parent-stream.ts消费子进程 stdout,逐段接续回父进程会话流,是父子之间的数据通道。
resolveAcpThreadSessionDetailLines()解析线程会话的明细行,定位输出所属的会话上下文。acp-core 侧
readAcpSessionMeta()读取会话元数据(session-meta.js),还原会话的状态与身份。acp-core 侧
resolveAcpSessionCwd()解析子进程的工作目录,确保 harness 在正确的路径下运行。
为何用独立进程:把外部 harness 放进独立子进程而非内联运行,换来的是隔离与独立崩溃域——子代理的崩溃、内存膨胀或异常退出都被关在子进程边界内,不会拖垮父进程;父侧只需通过 relay 与会话句柄观察其行为,崩溃后还能凭句柄做清理与回收。
05
limits · fan-out & depth

派生限制与孤儿回收

允许子代理派生子代理,就必须防止它失控地无限繁殖。OpenClaw 在 src/config/agent-limits.ts 里用两条默认限制——扇出宽度与递归深度——共同框住派生规模,并在父进程崩溃后回收孤儿子运行。

src/config/agent-limits.ts · 派生限制
const DEFAULT_SUBAGENT_MAX_CHILDREN_PER_AGENT = ...;  // 每个 agent 最大子会话数
const DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH        = ...;  // 最大 spawn 深度(防止嵌套失控)

两条限制管的是两个不同维度。MAX_CHILDREN_PER_AGENT 限制扇出宽度——单个 agent 同时拥有的子会话数量上限;MAX_SPAWN_DEPTH 限制递归深度——子代理派生子代理、再派生孙代理的嵌套层数上限。前者防「横向爆炸」,后者防「纵向失控」,二者相乘才是一棵派生树的规模上界,缺一则约束就会被另一维度绕过。

MAX_CHILDREN_PER_AGENT

扇出宽度

限定单个 agent 同时持有的子会话数量。超出上限的派生请求会被拒绝,防止某个代理一次性拉起过多子进程。

MAX_SPAWN_DEPTH

递归深度

限定派生链的嵌套层数。子代理派生子代理时深度逐级累加,触顶即停,杜绝无限递归式的自我繁殖。

孤儿回收:当父进程崩溃,其名下的子运行会变成无人认领的孤儿。reconcileOrphanedRun() 负责在重新对账时识别并回收这些孤儿子运行——释放其占用的句柄与进程资源,让会话状态重新收敛到一致。
ACP · 会话生成
src/agents/acp-spawn.ts · 1708 行
← 返回总览 · github ↗