--- summary: "代码模式范围、嵌套工具执行、执行器延续和安全边界" title: "代码模式内部结构" doc-schema-version: 1 read_when: - "您需要运行时状态、范围或词汇" - "您正在审查执行程序边界、类型化工具发现或延续生命周期" - "您正在验证高风险部署的安全边界" --- ## 运行状态 | 方面 | 值 | | ------------------- | ------------------------------------------------------------------------------------------- | | 执行者 | Node(`node:vm`,默认),QuickJS-WASI(捆绑插件) | | 默认状态 | 已禁用 | | 稳定性 | 实验性的 OpenClaw 接口(Codex Code Mode 是一个独立、稳定的 Codex harness 接口) | | 目标范围 | 通用 OpenClaw 代理运行 | | 安全态势 | Node 为可信主机执行; QuickJS 提供强化的访客隔离 | | 面向用户的承诺 | 启用代码模式绝不会静默回退为广泛的直接工具暴露 | ## 范围 Code mode 拥有已准备运行的面向模型的编排形态。它不拥有模型选择、通道行为、认证、工具策略或工具实现。 范围:模型可见的 control/direct 工具定义、隐藏工具目录构建、执行器选择、search/describe/call 和会话 JSON 存储的主机回调、挂起的来宾程序的可恢复状态、output/timeout/memory/pending-call/snapshot 限制以及嵌套工具调用的 telemetry/trajectory 投影。 超出范围:提供程序本机远程代码执行、shell 执行语义、更改现有工具授权、持久用户编写的脚本、在来宾代码中打包 manager/file/network/module 访问以及直接重用 Codex 代码模式内部结构。 提供方拥有的工具(例如远程 Python 沙箱)是独立的工具。参见 [代码执行](/tools/code-execution)。 ## 术语 - **Code mode**:OpenClaw 运行时模式,会隐藏与目录兼容的模型工具,并暴露 `exec`、`wait`,以及必需的仅直接使用工具。 - **Executor**:拥有JavaScript评估及其延续的实现。内置Node; QuickJS 是一个捆绑插件。 Core 拥有目录、工具授权和运行生命周期。 - **Guest 运行时**:Node VM 上下文或 QuickJS-WASI VM 评估模型代码。 - **主机桥**:从客户代码返回到OpenClaw的窄JSON-兼容回调表面。 - **Catalog**:在常规工具策略、插件、MCP 和客户端工具解析之后,按运行范围确定的有效工具列表。 - **Nested tool call**:通过主机桥从 guest 代码发起的工具调用。 - **Snapshot**:保存的 QuickJS-WASI VM 序列化状态,使 `wait` 能够继续一个已挂起的 code-mode 运行。 - **Continuation**:暂停单元的执行者拥有的状态。 Node 保留实时工作环境; QuickJS 保留快照。 ## 嵌套工具执行 每个嵌套工具调用都会穿过主机桥并重新进入 OpenClaw,保留:活动代理 ID、会话 ID 和密钥、发送者和通道上下文、沙箱策略、批准策略、插件 `before_tool_call` 挂钩、中止信号、可用的流式更新以及 trajectory/audit 事件。 已完成的嵌套调用将保留为有界的仅显示活动,在历史记录重新加载期间保留其原始父级和调用 ID。提供者重放仅包含实际的模型调用;儿童活动不会增加合成模型回合。嵌套调用 ID 包括单元的重播标识,因此提供者可以在稍后的助理响应中重用工具调用 ID,而不会与之前的活动发生冲突。在同一运行和助理响应中重播同一调用会保留其嵌套 ID。运行时脚本视图忽略这些持久的有效负载。需要时,最终确定和代理端挂钩会从记录中读取已接受的尝试活动。如果 `before_message_write` 挂钩抑制嵌套活动行,则终结和代理端挂钩也会忽略该活动。成功的工具仍算作已完成转弯结算;抑制其显示历史记录不会撤消其执行。启动和部分更新保持暂时状态。无法从源代码或外部结果重建较早的失踪儿童历史记录。 抛出的工具错误是可以捕获的。返回值可能会报告失败。遵循工具的结果契约,并在重试之前检查可能的效果。如果访客代码未捕获错误,`exec` 或 `wait` 将返回失败的工具结果,并且代理可以正常继续。在选择另一个操作之前,请按照 [ 工具错误指南 ](/tools/code-mode/quickstart#recover-from-tool-errors) 检查可能的部分效果。网络控制的工具输出和错误保留其现有的不受信任的内容包装和清理;失败后继续不会授予新权限或重播已完成的副作用。 嵌套调用遵循每个工具的 `executionMode`。 `"sequential"` 工具等待较早的目录调用完成并阻止稍后的调用,直到其结果被接受。具有并行功能的调用可以在下一个顺序调用之前重叠。使用相同的运行目录在单元之间共享调度;单独的目录保持独立。当呼叫者或目录关闭时,排队的呼叫将被取消。 `maxPendingToolCalls` 限制了正在进行的桥接请求的大小,而不是普通 `Promise.all` 批次的大小。超出该上限的呼叫和计时器将与 [Swarm](/tools/swarm) 请求一起在来宾中等待。使用现有接受的网桥限制上限,最多可以对 128 个普通请求进行排队,与配置的正在进行的上限无关。 Swarm 启动、注释和结果等待不会消耗此普通配额;他们现有的组、内存和延续限制仍然适用。排队输入和请求身份保留 `wait`; `clearTimeout` 删除排队定时器而不启动主机定时器。排队定时器的延迟在获得桥接时隙时开始。来宾延续在等待请求重新填充可用槽之前运行,并且快速请求仍然在同一 `exec` 或 `wait` 内耗尽。 创建超出队列配额允许的普通请求会导致工作线程失败,并显示 `invalid_input` 和等待较小批次的指导。捕获即时 JavaScript 错误并不承认部分批次:不会调度来自该同步前沿的新调用。早期的工人腿可能已经有运行工具;检查它们的效果而不是重放单元格。排队不会提高内存、快照、时间或无头总工具调用限制,也不会绕过取消和策略检查。 ## 运行和快照生命周期 每个新的 `exec` 都会启动一个新的 JavaScript 上下文。变量、函数和导入永远不会在单元之间传递; `wait`延续了原来的电池。 每次 code-mode 运行都会通过一个以内存中的 `runId` 为键的映射进行跟踪(不会持久化到磁盘或数据库)。`exec`/`wait` 会返回三种结果状态之一:`completed`、`waiting` 或 `failed`。 - A `waiting` 结果保留执行程序延续、挂起的桥请求和范围元数据(代理运行 ID、会话 id/key),直到 `wait` 恢复它或过期。 - Expiry、错误会话、错误运行和 unknown/already-resuming `runId` 值不会产生不同的终端状态;它们显示为 `failed` 结果 (`code: "invalid_input"`),并带有 `code mode run is unavailable or expired.` 或 `code mode run belongs to a different session.` 等消息。 - A 运行的延续一旦稳定到 `completed` 或 `failed` 就会被释放,或者在网关关闭时被删除。重启后,延续不会继续存在;提交的会话存储值可以。 - OpenClaw 会限制每个进程中同时挂起的运行数量(64),并且会拒绝超过该上限的新挂起,提示 `too many suspended code mode runs.`。 选定的执行器在单元的生命周期内保持固定。挂起状态受上述每个进程上限和 `snapshotTtlSeconds` 的限制。在将挂起的工作交给网关之前,QuickJS 还会根据 `maxSnapshotBytes` 检查序列化的 VM 大小(包括引擎元数据)。 Node 保留了一个实时工作线程,并且没有要测量的序列化快照。这些限制和 `memoryLimitBytes` 不是网关 RSS 的总限制;工作线程开销和主机端工具值也会消耗内存。 Node 的 `memoryLimitBytes` 在老一代和年轻代之间配置尽力而为的 V8 工作堆预算,受引擎最低要求的影响。它包括工作线程运行时分配并排除外部缓冲区。延续报告的保留字节是诊断估计,而不是强制保留或聚合内存配额。 Node 的实时上下文可以使用比大小受限的 QuickJS 快照更多的总主机内存。请参阅 [ 代码模式执行器](/tools/code-mode/executors#understand-waits-and-limits)。 显式 `results.save(value)` 引用在现有承认的目录寿命中保持标准化 JSON,独立于每个单元的 VM 和输出预算。除了单元的程序数据收件箱之外,存储最多允许 64 个条目和 `min(memoryLimitBytes, maxSnapshotBytes)` 编码的 JSON 字节(默认情况下为 10 MiB)。这是逻辑数据限额,而不是进程 RSS 限制。容量错误保留现有条目;删除释放了它们的容量。加载返回的分离副本并将网络内容来源转发到接收单元的正常不受信任输出包装器中。 当字节或模型结果拟合会截断最终结构化的 JSON 时,交互式单元还会自动保留它。工作器将最终值序列化一次,最多保留显示余量和现有memory/snapshot数据余量中的较大者;只有符合条件的交互式单元才请求此捕获。在解析另一个完整的 JSON 副本以进行有界预览构建之前,目录准入会检查剩余的字节和条目。规范化的字符串移动到同一存储中。最终投影在将剩余显示空间分配给采样描述和输出之前保留可用参考;释放了无法显示的引用。失败的录取仍然是成功的部分结果,并有明确的不保留原因。无头和安全重启执行不会分配自动引用。 目录拆卸、替换、限制和承认运行的中止清除保存的数据。附加的客户端工具保留相同的结果存储生命周期,包括已经停放在 `wait` 中的单元。每个单元在执行之前都会捕获该存储,因此过时的单元无法采用替换存储或在权限更改后保留引用。保存的引用是数据快照,而不是执行权限。它们会在当前回复结束时过期,并且不会在网关重新启动后继续存在。切勿重复使用先前回合中的参考 ID;使用会话存储来存储稍后需要的小值。 ### 会话存储持久性 `await store(key, value)` 和 `await load(key)` 在两个执行器下使用与 `results` 相同的异步主桥和共享访客控制器。核心在由 `runId` 键入的单元状态中保留挂起的写入,因此它们可以在 Node 的实时延续和 QuickJS snapshot/restore 中生存,而无需扩展执行程序协议。加载覆盖单元的缓冲集并删除运行的已提交会话存储投影。 投影在第一次存储操作时延迟初始化,并共享承认的 catalog/result-store 生命周期。对于持久会话,核心使用现有的线程外 `SessionManager.openAsync` API 来读取以实时会话的当前叶结尾的完整分支。这包括早于实时管理器的有限水合作用窗口的自定义条目。不匹配的叶子会使操作失败,而不是暴露不同的分支。后续操作使用投影,应用此运行自己的提交,而无需重新扫描分支。 只有设置 `completed` 的单元才会提交其非空写入集。核心附加一个类型为 `openclaw.code-mode-store` 到 `appendCustomEntryAsync` 的 `custom` 条目,然后更新投影。条目记录设置、删除和每个密钥的网络内容来源。失败、超时、中止、过期和处置的单元会丢弃其缓冲区。如果追加失败,完成的结果会带有警告,并且投影保持不变。 副本是持久性所有者:不会创建单独的数据库表、sidecar 或磁盘缓存。重放遵循分支祖先,在压缩中幸存下来,并排除其他分支的写入。自定义条目位于模型上下文之外。加载标记值将接收单元的运行时标记为包含网络内容,从而跨轮保留正常的不受信任内容包装器。存储操作在无头或 `restartSafe` 执行中不可用,或者在没有绑定会话管理器的情况下不可用。有关数据限制,请参阅 [ 访客商店合约 ](/tools/code-mode/guest-api#session-store)。 ## QuickJS-WASI 运行时 捆绑的 `code-mode-quickjs` 执行器插件拥有其 `quickjs-wasi` 依赖项和工作器实现。选择 Node 不需要加载 WASM 运行时。 运行时职责:compile/load、QuickJS-WASI、WebAssembly模块;每次代码模式运行或恢复创建一个隔离的 VM;通过稳定名称注册主机回调;设置内存和中断限制;评估JavaScript;排出待处理的工作;快照暂停VM状态;恢复 `wait` 的快照;在终端状态后处理 VM 句柄和快照。快照缓冲区直接在工作线程和网关之间传输,无需通过存储序列化格式复制 VM 堆。 运行时在 Node.js worker 线程中执行,位于 OpenClaw 的主事件循环之外。来宾无限循环不得无限期阻塞 Gateway 进程; worker 的中断处理器会独立于来宾代码的配合,强制执行墙钟超时。 ## Node 运行时 Node 执行器在网关主事件循环之外的新 `node:vm` 上下文中评估 JavaScript。它使用与 QuickJS 相同的访客控制器和 JSON 工具桥。暂停会保留工作人员、上下文和待处理的承诺; `wait` 继续该上下文而不重播源。取消、到期、最终结果和关闭会释放保留的执行。 成功完成后,Node 最多可保留 4 个空闲工作线程 30 秒,以重复使用短突发,然后自上次完成后保留一个空闲工作线程最多 5 分钟。重用需要相同的运行时条目和堆限制。重用保留在创建网关或 CLI 命令的生命周期内;关闭该主机会导致工人退休。没有主机的独立执行程序调用会立即关闭其已完成的工作线程。每个新单元仍然会获得新的 VM 上下文。运行时条目的变化和严重的内存压力使闲置的工人退休;内存压力不会丢弃挂起的延续。失败、超时或中止的单元会导致其工作人员退休。 工作人员监督将失控的计算排除在主事件循环之外。工作线程和 `node:vm` 都不提供操作系统安全边界。使用[执行器指南](/tools/code-mode/executors)选择合适的信任模型。 ## TypeScript TypeScript-style 签名通过快速索引、目录句柄和 `API.read` 声明文件描述模型的工具输入和输出。未知输出保留为 `unknown`,并且声明不会授予对其他工具的访问权限。 可执行单元是普通的 JavaScript。代码模式不会加载 TypeScript 编译器、剥离注释或对程序进行类型检查。所选引擎直接解析并运行JavaScript。工具调用仍然使用现有的运行时输入和输出验证、策略和批准所有者。在先前的调用产生影响后,后面的调用可能会失败,因此在重试失败的单元之前请遵循 [ 恢复指南](/tools/code-mode/quickstart#recover-from-tool-errors)。 ## 安全边界 Node 是可信执行的默认执行器。其预期的全局变量和模块防护不会使 `node:vm` 成为安全边界。模型输出可能会受到即时注入的影响;当来宾必须与主机隔离时,选择捆绑的 QuickJS 执行器。 QuickJS 在 WASM 来宾中执行,没有环境文件系统、网络、子进程、环境或模块访问。它使用引擎内存和中断限制、父挂钟截止日期和有界序列化快照。 两个执行器都在主事件循环之外运行,使用相同的 JSON 工具桥,强制输出和挂起调用上限,并在超时、中止、会话结束或到期时释放延续。 Core 保留工具策略、批准、挂钩和会话所有权。对代码模式和工具搜索控制工具的递归访问被排除在来宾目录之外。这些共享工具检查不包含 Node VM 转义,并且授予强大的工具仍然会向 QuickJS 来宾授予其功能。有关面向操作员的选择和 OS 隔离指南,请参阅 [ 代码模式执行器](/tools/code-mode/executors)。