最近由于 Pi 通过 codemode 引入了 MCP 的调用,再次引发了关于 codemode 的讨论。其实在过去一段时间,随着基模能力的提升,codemode 已经成为了生产级 coding agent 的事实标配。
Codemode 本身也是一个 LLM 可以看到的 tool,但它是一个万能的元工具:可以让 LLM 写一段程序,通过程序调用工具、处理数据,再把需要的结果交回模型。
Codemode 把写代码这个动作本身做成了一个工具,它把过去 agent 常用的 Read、Write、Edit,甚至 MCP,都收敛进一个单一工具,收敛成 codemode 里的函数调用,通过代码编排实现工具的串并行,并对输入、输出进行修改。
一、什么是 codemode
假设我们希望 Agent 完成这个任务:查询所有活跃项目的任务,找出逾期超过 7 天、尚未完成的任务,再按负责人统计数量。
如果使用普通 Tool Calling,模型先调用"查询项目"工具,读到项目 ID,再调用"查询任务"工具。拿到结果后,它决定继续查询、筛选或汇总,直到获得足够的信息。
Codemode 给模型一个执行代码的入口。模型可以提交一段 JavaScript 代码,遍历、传递 ID 和统计都在程序里完成,模型最终只需要读到一个小对象:
const projects = await tools.list_projects({ status: "active" });
const counts = new Map();
for (const project of projects) {
const tasks = await tools.list_tasks({ project_id: project.id });
for (const task of tasks) {
if (!task.completed && task.overdue_days > 7) {
const owner = task.owner ?? "未分配";
counts.set(owner, (counts.get(owner) ?? 0) + 1);
}
}
}
text(Object.fromEntries(counts));
代码仍然调用了多次工具。查询项目和任务的网络请求也照常发生。不过,遍历、传递 ID 和统计都在程序里完成,模型最终只需要读到一个小对象:{"张三": 4, "李四": 2, "未分配": 1}。
关键在于:项目和任务列表先进入执行器,程序处理后,再选择输出什么给模型。

二、codemode 的优势
代码对循环、分支和精确计算有现成的表达方式。工具只需要提供基础操作,模型就能用 for、if、await 把它们组合起来,不必为每种组合再设计一个专用工具。
减少模型调用
在前面的任务中,"拿到项目 ID 后查询任务"可以预先表达。执行器获得项目结果以后,直接进入下一次调用,不必把结果交回模型,让模型再生成相同的决定。对于调用耗时较短、步骤很多的任务,这可以减少模型往返。
实现工具编排与串并行
数组中的计数、排序、过滤和连接,可以在代码里完成。模型负责确定处理目标和规则,执行器负责按规则计算。"逾期超过 7 天"能够写成清楚的条件;"哪条任务最值得优先处理"可能还需要业务判断。
只让必要结果进入上下文
工具可能返回上千条记录,最终回答只需要几个统计数字。Codemode 可以让这些记录留在执行器内,通过 text 输出需要的内容。
三、程序运行在哪里
理解 Codemode,需要先认识 Harness 和 Execution Environment。
Harness 是组织 Agent 运行的软件。Execution Environment 则是具体操作发生的环境:本地进程、容器、远端沙箱、浏览器,或者某个外部服务。Harness 组织任务和决策,codemode 的执行环境承担具体工作。
Codemode 的 JavaScript 执行器也有自己的边界。在 Pi 和 Codex 实现中,裸 JavaScript 都没有直接的文件系统和网络入口。代码访问外部世界,需要调用宿主提供的接口。

codemode 引擎仅仅负责编排工具,工具的具体执行,比如 MCP client、bash、read/write/edit 等工具的实现,均在 harness 层,在 codemode 引擎之外。
这可以回答一个很常见的问题:沙箱是否能看到当前目录的文件?codemode 沙箱本身不具备文件权限,但代码可以调用 tools.read 或 tools.exec_command,让对应工具读取文件,再获得返回内容。
四、Pi:QuickJS、WASM 与工具桥接
Pi 提供一个名为 codemode 的工具。底层参数定义是 { code: string },支持对应采样形式的模型可以直接提交原始 JavaScript。
Pi 使用编译后的 quickjs.wasm,由宿主加载并创建 QuickJS VM。QuickJS 是一个用 C 实现的 JavaScript 引擎。模型每次生成的 JavaScript,由这个引擎执行,无须每次重新编译成 WASM。
在工具调用方面,tools.mcp__server__method 是宿主注入的包装函数。调用它以后,QuickJS 发送包含工具名字、参数和调用 ID 的消息。宿主找到对应工具,执行后把结果送回来。
五、Codex:V8 isolate 如何编排工具
Codex 的 Code Mode 同样通过宿主接口调用工具,但使用 V8 执行 JavaScript。
模型所能看到的 tools 是 functions.exec 与 functions.wait。exec 是 Freeform Tool,输入是原始 JavaScript,可用首行 pragma 配置交回控制权的时间和输出预算。
每次 exec 建立新的 V8 isolate 和 Context。可以把 isolate 理解成一个独立的 JavaScript 运行实例,具有自己的 JS 堆与上下文。
Pi 通过 worker 消息桥接,Codex 通过 V8 回调和运行时事件桥接。两者共同的设计是:引擎执行 JavaScript,宿主负责把工具请求送到真实执行入口。
六、安全、失败与恢复
把多个动作写入一个脚本,会让任务执行更紧凑,也让失败处理更值得提前设计。
- 沙箱需要配合工具授权:执行器没有直接文件和网络入口,可以缩小暴露面。但宿主仍应检查每个工具调用:参数是否合法,账户和资源是否在授权范围内,是否需要审批。
- 脚本失败可能留下部分成功:假设代码先创建任务,再写入说明,最后输出结果。第二步出错,并不会自动删除第一步创建的任务。
- 保存数据不等于恢复执行:Pi 成功脚本的 store 写入会保存为 codemode-store 会话条目。Codex 的 store/load 在检查的运行库中使用宿主会话内存保存值。这些能力适合留下对象 ID、游标或摘要。
七、设计自己的 Codemode
从 Pi 和 Codex 的调用链,可以提炼出几个职责:
- 工具目录:保存稳定的工具 ID、说明、输入输出 schema 和执行入口
- 代码执行器:创建受限运行实例,注入明确的 helpers,管理 Promise、输出预算和终止
- 宿主/harness:工具请求必须经过宿主执行,在这里完成参数验证、授权、审批、并发控制和记录
- 持久化:需要跨重启继续的任务,还要增加工作流日志、稳定步骤和幂等协议
对于只有几个工具、步骤简单的任务,直接 Tool Calling 可以足够。涉及多个工具的依赖、批量数据处理和大型目录时,Codemode 更有价值。