Skip to content

附录 D|code-mode:模型写 JS 编排工具,V8 里没有 Node ​

先别把 code-mode 当成第三条 run_turn ​

B2 的 ToolExposure::CodeModeOnly 和 finalize_tool_router 里对 exec / wait 的撞名处理,是这条旁路的接口。没有附录会以为 Codex 在沙箱里跑 Node,或以为 JS 里 tools.exec_command 绕过了 Orchestrator。

判断先说清楚:code-mode 换的是模型怎么点工具,不是换循环。 模型少打一堆 JSON function call,改写一段 JS;isolate 里 没有 Node、没有文件系统、没有网络、没有 console(code-mode-protocol/src/description.rs:16-20)。真正改仓库、跑命令、调 MCP,仍是 await tools.exec_command(...) 这类 嵌套 tool,落到原来的 Handler / Runtime / Orchestrator。主循环、审批、沙箱、rollout 一条都不少。

F3 第一周不做它。feature code_mode 默认 关、阶段 UnderDevelopment(features/src/lib.rs:116, 1017-1020)。没有它,Direct 工具表照样能干活。


1. 模型看见的入口只有 exec 和 wait ​

协议常量(code-mode-protocol/src/lib.rs:51-52):PUBLIC_TOOL_NAME = "exec",WAIT_TOOL_NAME = "wait"。ToolMode 有 Direct / CodeMode / CodeModeOnly(protocol/src/openai_models.rs:346-349)。

finalize_tool_router 在 CodeMode / CodeModeOnly 下若 registry 里已有同名工具,先 remove 并 record_collision(spec_plan.rs:361-370),再注册 code-mode 自己的 executor。避免 MCP 或插件也叫 exec 把入口抢走。

CodeModeOnly(feature code_mode_only,默认关):模型可见工具收成这两个入口(feature 注释 :125)。其它工具 CodeModeOnly 暴露:JS 里能调,prompt 的 tools 数组里没有。这是比 tool_search 更狠的渐进披露——目录几乎清空,编排语言换成 JS。

普通 CodeMode:Direct 工具仍可出现在模型眼前,同时 JS 也能嵌套调。配置里 code_mode.direct_only_tool_namespaces 可把某命名空间排除出 code-mode / deferred(spec_plan.rs:236-242)。

模型没在 metadata 里声明 Code Mode、配置却开了:session 警告性能会降,让你关 feature 或换模型(session/code_mode_warning.rs:5-21)。不要对不会写 exec 源码的模型开这条旁路。


2. V8 isolate:编排器,不是万能 shell ​

exec 的说明书(description.rs:16-25):

  • 在 新的 V8 isolate 里当 async module 求值
  • 嵌套工具在全局 tools 上,名字是合法 JS 标识符(如 tools.mcp__ologs__get_profile)
  • 输入是 裸 JS 源码,不是 JSON、不是 markdown 围栏
  • 可选首行 pragma // @exec: {"yield_time_ms": 10000, "max_output_tokens": 1000}
  • 求值结束 isolate 就销毁,没 await 的 Promise 静默丢掉

辅助:text / image / audio / store / load / notify / setTimeout(超时 不会 独自让 exec 保活)/ yield_control / exit。ALL_TOOLS 是嵌套工具的 name+description。延迟加载的嵌套工具可能不在这段 description 里,但仍在 tools 和 ALL_TOOLS 上(:11-12)。

wait:exec 若 yield 成「Script running with cell ID …」,用 wait 按 cell_id 续、或 terminate: true 停掉(:40-47)。这和附录 B 的 PTY write_stdin 是同一哲学:长任务可跨多次 tool call,对象换成 JS cell,不是操作系统进程。

运行时:code-mode-runtime 的 InProcessCodeModeSession 包 V8(lib.rs:10-12,service.rs)。生产路径还可以走 独立 host 进程(Feature::CodeModeHost 默认开、Stable;code_mode 本身仍默认关)。code-mode crate 提供 gRPC / process-owned session。JS 崩了不该拖垮 Session 的 tokio 运行时——这是拆 host 的理由,不是再实现一套沙箱。

没有「isolate 无 I/O」会怎样?模型在 JS 里 fetch 外网、fs.writeFile 绕过 apply_patch。说明书把 I/O 关死,副作用必须走 tools.*,才能继续过 B3。


3. 嵌套调用仍进原来的四层 ​

CodeModeExecuteHandler 带着 nested_tool_specs: Vec<(ToolSpec, Option<Arc<dyn CoreToolRuntime>>)>(execute_handler.rs:19-24)。JS 里 await tools.exec_command(...) 变成 CodeModeNestedToolCall,delegate 回 core 的 runtime——审批、沙箱、ExecPolicy、并行门闩、patch 拦截,一条不少。

MCP handler 会缓存 code-mode 用的 ToolDefinition(handlers/mcp.rs:54, 262-266),把 schema 渲成 TypeScript 类型塞进 exec 说明书。模型在 JS 里看到类型,call 时仍是 sanitised 名 → raw MCP 名(D3)。

CodeModeInterrupt(默认关):turn 被 Interrupt 时是否杀掉还在跑的 cell。默认关则 JS 编排可能活过这一枪 wait,类似 PTY 默认不随 Interrupt 死(附录 B)。开了才「取消 turn 也取消脚本」。

Prewarm(CodeModePrewarm,默认关):session 启动就连 host。B1 的 ModelClient prewarm 是 WS;这里是 V8 host。失败不该把 turn 循环打挂。


4. 什么时候才需要它 ​

要做的事用
P0 能改文件、跑命令Direct exec_command / apply_patch(F3)
MCP 太多撑 prompttool_search + Deferred(B2),不必上 code-mode
模型要用循环/分支把十个工具编在一次脚本里code-mode exec
只让模型看见两个入口code_mode_only

抄 harness:先不要 V8。code-mode 是 工具表面的语法糖 + 进程隔离的脚本运行时。没有它,SQ/EQ、Turn、Orchestrator 仍然完整。有了它,禁止把 isolate 当成第三种沙箱——沙箱仍在嵌套 tool 上。


5. 结语:带走一句话 ​

code-mode 让模型用一段无 I/O 的 JS 编排已经存在的工具:exec/wait 是入口,tools.* 仍过 Orchestrator——它不是 Node、不是新循环,默认关掉也不妨碍做出最小 agent。