第 38 讲:Code Mode 运行时——V8 里的 JS 执行单元

Codex 源码解析 · 第 38 讲

Codex 源码解析系列 第 38 讲:Code Mode 运行时——V8 里的 JS 执行单元 基于 OpenAI Codex 源码 · 2026-11-01 💡 本讲一句话: Code Mode 让模型不再"一条命令一个结果",而是写一段 JavaScript、在 V8 里循环调用工具——本讲拆穿它背后的 actor + 状态机:JS 一 await 线程就冻结(零 CPU),10 秒窗口一到就把中间输出交回模型,store()/load() 让状态跨 cell 存活。 一、Code Mode:从"执行命令"到"执行程序" 前几讲我们拆过 shell exec(第 17 讲):模型发一条命令,Codex 在 bwrap/Landlock 沙箱里跑完,把 stdout 交回去。这个模式有个天花板—— 一次调用只能做一件事 。"先查日志、再根据错误码改配置、最后验证"这种多步任务,模型得自己当调度器,一轮轮往返。 Code Mode( codex-rs/code-mode* ,约 17K LOC)把这件事反过来: 模型写一段 JavaScript ,Codex 在一个叫 cell(执行单元)的 V8 isolate 里跑它。JS 代码可以循环、分支、 await tools.shell({...}) 连续调多个工具,中间用 text() / notify() 随时产出内容,用 store()/load() 跨 cell 传状态。 代价是复杂度:JS 跑在进程内的 V8 里,没有沙箱进程隔离兜底;它可能死循环、可能 await 一个永远不返回的工具。所以整个 code-mode-runtime crate(约 5K LOC)的核心工作就一句话—— 把"一段不受控的 JS"约束成一台可暂停、可恢复、可强杀、交付边界清晰的状态机 。 维度 shell exec(第 17 讲) Code Mode cell(本讲) 执行体 沙箱里的子进程 V8 isolate 里的一段 JS(exec_main.mjs) 控制粒度 一条命令一个结果 一段程序:循环、分支、await 多个工具 中间输出 命令结束才有 stdout text()/notify() 随时产出,窗口内多次交付 状态共享 靠文件系统/环境变量 store()/load() 会话级 JSON,跨 cell 存活 终止方式 kill 进程树 terminate_execution() 强杀 V8 + CancellationToken 四个 crate 分工明确: code-mode-protocol (协议与 JS API 描述,含 parse_exec_source pragma 解析)、 code-mode-runtime (本讲主角:cell actor + V8 线程 + CellState 状态机)、 code-mode-host (gRPC 宿主,把 cell 暴露给远端)、 code-mode (remote_session/grpc_session 传输层)。core 侧的 tools/code_mode/ 负责把 exec 工具调用接进来、把嵌套工具派发到真实工具运行时。 二、Cell:一个 actor + 一条 V8 线程 每个 cell 的骨架在 cell_actor/mod.rs : V8 跑在一条裸 OS 线程上(不能跑 tokio),actor 主循环跑在 tokio 里,两者之间只有三条通道 ——事件、命令、控制。创建 cell 的 CellActor::prepare() (第 63-92 行)把这套接线一次做完: 📄 codex-rs/code-mode-runtime/src/cell_actor/mod.rs (第 63-92 行,节选) let (event_tx, event_rx) = mpsc::unbounded_channel(); // 事件通道:V8 线程 → actor,Started/ToolCall/Result 都从这里来 let (command_tx, command_rx) = mpsc::unbounded_channel(); // 命令通道:actor → V8 线程,工具结果回填、终止指令走这里 let (in…

阅读全文:Codex 源码解析 第 38 讲