Openclaw智能体终止机制

0 阅读3分钟

在 OpenClaw 架构中,由于 Node.js 采用单线程异步事件循环模型(无真实 OS 线程),对子智能体(Subagent)的终止(Kill)并非系统级的“物理拔电”,而是结合 AbortController全局 Map 注册表 实现的运行时协作式取消(Cooperative Cancellation)

整个 Kill 链路在代码层面被精心拆分为控制层(Control Plane)执行层(Execution Plane) 。结合详细的源码,它的执行链路分为以下几个阶段:

1. 双层标识符映射与全局运行表

系统内部存在双层标识符:

  • 控制层标识:主智能体对外的控制抓手是 childSessionKeyrunId
  • 执行层标识:真正分配给协程底层的唯一 UUID 是 sessionId

src/agents/pi-embedded-runner/runs.ts 中,维护着一个底层的 embedded runner 活跃运行表(全局变量 Map):

const ACTIVE_EMBEDDED_RUNS = new Map<string, EmbeddedPiQueueHandle>();

它把执行层的 sessionId 映射到一个“句柄”(EmbeddedPiQueueHandle)上。这个句柄暴露了对“异步协程”的控制权,包括塞入新消息(queueMessage)和强制终止(abort)。注:该表是通用的 Embedded Runner 活跃运行表,子智能体仅仅是该运行器的一个使用场景。

2. 启动时的信号量绑定

当创建一个子智能体并进入其核心控制循环(src/agents/pi-embedded-runner/run/attempt.ts)时,代码会进行以下绑定:

  1. 实例化一个 const runAbortController = new AbortController();
  2. 初始化处理消息流的 activeSession Generator。
  3. 声明一个物理切断逻辑 abortRun()
  4. 在核心代码第 1287 行左右,将包含这个切断逻辑的句柄登记到上面的活跃运行表中:
const queueHandle: EmbeddedPiQueueHandle = {
  queueMessage: async (text: string) => { await activeSession.steer(text); },
  // 暴露出切断代码的入口
  abort: abortRun, 
  // ...
};
setActiveEmbeddedRun(params.sessionId, queueHandle, params.sessionKey);

3. 主智能体的 Kill 指令传导与状态终结

当主智能体决定终止某个受控的子智能体时:

  1. 它调用了 subagents 里面的 action: "kill" 工具,带着对应的 childSessionKeyrunId
  2. 请求流转到 src/agents/subagent-control.tskillSubagentRun 函数。
  3. 代码首先从 session entry 里取出 sessionId,调用 abortEmbeddedPiRun(sessionId) 执行底层打断;随后清理后续任务和通道队列(Followup & Lane queues),将 session store 里的 abortedLastRun 置位;最后才调用 markSubagentRunTerminated 更新 Subagent Registry 任务账本,记录为终止状态。
  4. 并且,如果该智能体还派生了后代智能体,系统会一并递归级联发送 Kill 指令。

执行层拦截的 abortEmbeddedPiRun 会直接去全局表里“捞人”(位于 runs.ts):

export function abortEmbeddedPiRun(sessionId: string) {
  const run = ACTIVE_EMBEDDED_RUNS.get(sessionId);
  if (run) {
    run.abort();
  }
}

4. 协作式取消(代码层面的截断)

调用 .abort() 以后,最终触发的是 attempt.ts 里声明的 abortRun 函数(代码约在 1196 行):

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. 核心逻辑时序图

image.png

💡 核心总结

OpenClaw 对 Subagent 的 Kill 操作采用“两层机制”:控制层先通过 childSessionKeyrunId 定位目标 run,下发中断指令,执行状态清理并级联后代;执行层如果该 session 正由 embedded runner 执行,则取出 sessionIdACTIVE_EMBEDDED_RUNS 获取对应句柄,调用 abortRun,最终通过 AbortControlleractiveSession.abort 触发优雅的协作式取消。