在 OpenClaw 架构中,由于 Node.js 采用单线程异步事件循环模型(无真实 OS 线程),对子智能体(Subagent)的终止(Kill)并非系统级的"物理拔电",而是结合 AbortController 和 全局 Map 注册表 实现的运行时协作式取消(Cooperative Cancellation) 。
整个 Kill 链路在代码层面被精心拆分为控制层(Control Plane)与执行层(Execution Plane) 。结合详细的源码,它的执行链路分为以下几个阶段:
1. 双层标识符映射与全局运行表
系统内部存在双层标识符:
- 控制层标识 :主智能体对外的控制抓手是
childSessionKey和runId。 - 执行层标识 :真正分配给协程底层的唯一 UUID 是
sessionId。
在 src/agents/pi-embedded-runner/runs.ts 中,维护着一个底层的 embedded runner 活跃运行表(全局变量 Map):
typescript
const ACTIVE_EMBEDDED_RUNS = new Map<string, EmbeddedPiQueueHandle>();
它把执行层的 sessionId 映射到一个"句柄"(EmbeddedPiQueueHandle)上。这个句柄暴露了对"异步协程"的控制权,包括塞入新消息(queueMessage)和强制终止(abort)。注:该表是通用的 Embedded Runner 活跃运行表,子智能体仅仅是该运行器的一个使用场景。
2. 启动时的信号量绑定
当创建一个子智能体并进入其核心控制循环(src/agents/pi-embedded-runner/run/attempt.ts)时,代码会进行以下绑定:
- 实例化一个
const runAbortController = new AbortController(); - 初始化处理消息流的
activeSessionGenerator。 - 声明一个物理切断逻辑
abortRun()。 - 在核心代码第 1287 行左右,将包含这个切断逻辑的句柄登记到上面的活跃运行表中:
csharp
const queueHandle: EmbeddedPiQueueHandle = {
queueMessage: async (text: string) => { await activeSession.steer(text); },
// 暴露出切断代码的入口
abort: abortRun,
// ...
};
setActiveEmbeddedRun(params.sessionId, queueHandle, params.sessionKey);
3. 主智能体的 Kill 指令传导与状态终结
当主智能体决定终止某个受控的子智能体时:
- 它调用了
subagents里面的action: "kill"工具,带着对应的childSessionKey和runId。 - 请求流转到
src/agents/subagent-control.ts的killSubagentRun函数。 - 代码首先 从 session entry 里取出
sessionId,调用abortEmbeddedPiRun(sessionId)执行底层打断;随后 清理后续任务和通道队列(Followup & Lane queues),将 session store 里的abortedLastRun置位;最后 才调用markSubagentRunTerminated更新 Subagent Registry 任务账本,记录为终止状态。 - 并且,如果该智能体还派生了后代智能体,系统会一并递归级联发送 Kill 指令。
执行层拦截的 abortEmbeddedPiRun 会直接去全局表里"捞人"(位于 runs.ts):
arduino
export function abortEmbeddedPiRun(sessionId: string) {
const run = ACTIVE_EMBEDDED_RUNS.get(sessionId);
if (run) {
run.abort();
}
}
4. 协作式取消(代码层面的截断)
调用 .abort() 以后,最终触发的是 attempt.ts 里声明的 abortRun 函数(代码约在 1196 行):
ini
const abortRun = (isTimeout = false, reason?: unknown) => {
aborted = true;
// 1. 发送标准的 Abort 信号
runAbortController.abort(reason);
abortCompaction();
// 2. 切断事件循环的生成器
void activeSession.abort();
};
在这里发生了真正的"切断代码执行"(协作式取消):
runAbortController.abort():此项目的 LLM 流式对话、大模型 API 拉取、乃至某些文件 I/O,底层均接收了runAbortController.signal(Web 标准的信号量)。一旦执行abort(),底层所有的fetch网络请求和流式解析会立刻抛出DOMException: AbortError,物理阻断它和外部 API 正在进行的读写。activeSession.abort():切断处理消息流的内部执行器(基于 Async Iterators),相当于在内部无穷await ...或yield的地方强制抛错退出协程。
5. 核心逻辑时序图

💡 核心总结
OpenClaw 对 Subagent 的 Kill 操作采用"两层机制":控制层 先通过 childSessionKey 和 runId 定位目标 run,下发中断指令,执行状态清理并级联后代;执行层 如果该 session 正由 embedded runner 执行,则取出 sessionId 从 ACTIVE_EMBEDDED_RUNS 获取对应句柄,调用 abortRun,最终通过 AbortController 和 activeSession.abort 触发优雅的协作式取消。