Code Mode(代码模式)
Code Mode(代码模式) 是一种工具使用模式:模型不再逐个请求工具调用,而是写一段代码当作「紧凑计划」,在沙箱里组合多次调用、处理中间结果,只把回答需要的结构化结果交回上下文。Cloudflare Agents 的 @cloudflare/codemode、Vercel AI SDK 的 @ai-sdk/code-mode、Python 生态 FastMCP 的 CodeMode transform 和 Pi 内置的 Codemode 都用这个名字;Programmatic Tool Calling(程序化工具调用)是 OpenAI 在 Responses API 里对同族模式的托管命名。
它与 Model Context Protocol(模型上下文协议) 的「代码执行式 MCP」是同一类思路:把工具调用从「模型逐个发起」变成「代码批量编排」。区别在于承载位置——Anthropic 那篇讲的是把 MCP server 生成成代码 API 放进执行环境,Code Mode 更强调 harness 侧一个专门的编排沙箱。
它解决的两个规模化问题
FastMCP 的文档把直连工具调用的成本写得很直接:工具目录里每个工具都要在模型读到用户请求之前进入上下文,几百个工具就是几万 token;每次工具调用又都是一次往返——模型调一个、等结果、再推理、再调下一个,只为喂给下一步的中间结果也要流过模型。
代码模式同时改掉这两件事:工具面从「整个目录」缩成「少量元工具 + 一个执行入口」,中间逻辑从「模型上下文」搬进「可执行代码」。一个计划里可以串起有依赖的多步调用、对结果集合循环、过滤和转换返回数据、按前面的结果分支、再塑形最终返回值。
flowchart LR
U["用户请求"] --> M["模型"]
M -->|"写一段程序"| S["Code Mode 沙箱(harness 侧)"]
S -->|"tool call 1..n"| T["工具 / MCP server / connector"]
T -->|"结果留在沙箱"| S
S -->|"结构化摘要"| M
M --> A["最终回答"]
沙箱在哪一侧
Pi 在解释 Codemode 时给了一个有用的分侧:执行工具有两侧,一侧是 bash 运行的地方,一侧是 harness agent loop 运行的地方,两侧信任等级完全不同——harness loop 通常跑在受信环境里,而它执行的工具跑在不那么受信的沙箱里。Code Mode 跑在 harness 这一侧,因此它本身不是「不可信工具的执行环境」,而是编排和协调工具调用的机制;也正因为如此,它的状态可以保存在会话 transcript 里而不是文件系统里。
语言选择上理论上任何语言都行,JavaScript 的实际优势是小体积运行时可以编译成 WASM 二进制,在沙箱里提供合理程度的保护。几家实现都落在同一形态:AI SDK 用 QuickJS,FastMCP 用 pydantic-monty。
这个沙箱和 Agent Sandbox(Agent 沙箱) 里装载工具与不可信代码的沙箱不是一回事,威胁模型也不同:前者约束「模型生成的编排代码不能乱来」,后者约束「工具和被注入的内容能碰到什么」。Cloudflare 的实现把这条边界拆得最清楚——executor 只负责跑一段代码且不保存状态,connector 提供能力但不管理重放,runtime 记录执行并控制审批、重放、回滚和复用;沙箱默认阻断 fetch() / connect(),出站只能经 connector 越过边界。
在 Pi 里的样子
Pi 里配置 MCP 后 Codemode 会自动加载,也可以作为默认工具写进配置。Earendil 给出的示例能说明它和直连调用的手感差别:模型先取 Linear 的 open issues,再在沙箱里对每条 issue 的评论跑一次分类模型,用四个 worker 并发,把逐条结论 store() 起来,最后只返回汇总。
const { issues } = await tools.mcp__linear__list_issues({ team: "Pi", state: "open", limit: 250 });
const jev = await models.getModelOfType("classifier", "cloudflare-workers-ai", "typesafe/jev");
// ...对每条 issue 取评论、调用 models.classify(jev, ...),4 个 worker 并发
store("frustration", results); // 逐条结论留在会话里,后续追问不必重新取数
return { total, counts, flagged }; // 只有摘要回到模型上下文
回放数据(这篇公告附带的可重放 trace)把成本算得更清楚:整次会话一共发出 335 次调用——1 次列 issue、167 次取评论、167 次分类,墙上时间 76 秒,而模型上下文里只留下一个小结:167 个 open issue 中 156 个语气中性、11 个轻度烦躁、0 个高度烦躁。值得注意的两点:tools.mcp__linear__* 和模型接口(这里是 Jev 分类器)在沙箱里是同级可调用的;store() 让沙箱状态进入会话 transcript,因此「哪些 issue 被判定为烦躁」在后续轮次仍可查,但当时的评论不必再取一遍。
工具可见性:哪些给模型,哪些只给沙箱
代码模式一进 harness,就立刻带出一个配置问题:某个工具是直接暴露给模型,还是只对 Code Mode 沙箱可见?Pi 把这点讲得最明确——写 Codemode 之前需要先决定工具属于 LLM 还是只属于 LLM 的 codemode 部分,而当时第三方 MCP extension 拿不到 Pi 工具加载层足够的元数据来做这件事,所以核心里得先让工具可以声明为「延迟」或「Codemode 专用」。
同类机制在其他实现里也能看到:
| 实现 | 可见性控制 | 发现方式 |
|---|---|---|
| AI SDK | experimental_toolCallers 逐工具声明允许的调用方(code_mode / DIRECT_TOOL_CALL) | toolSearch() + deferLoading: true;toolDiscovery: 'conversation' 把目录放进 user message |
| Cloudflare | 模型只看到一个外层 codemode 工具,描述里仅列 connector 命名空间 | 沙箱内 codemode.search() 返回排名路径,codemode.describe() 返回单个目标的 TypeScript 说明 |
| FastMCP | server 侧 transform 决定客户端能看到哪些元工具 | 默认 search → get_schema → execute 三阶段,也可配成两阶段或单阶段 |
| Pi | 工具可标记为延迟加载或 Codemode 专用 | MCP 工具在沙箱里以 tools.mcp__<server>__<tool> 形式出现 |
AI SDK 的 toolDiscovery: 'conversation' 值得单独留意:把工具目录放进 user message、让 provider 可见的工具定义保持不变,是为了Prompt Caching(提示缓存)复用;否则每次编入沙箱的工具集合一变,工具定义就得重写。这与 MCP 词条里「工具列表顺序或内容一变就破坏前缀缓存」是同一个约束。
FastMCP 还给了三个 detail 级别的取舍样本:brief 只回工具名和一行描述,detailed 回紧凑的参数 markdown,full 回完整 JSON schema。发现阶段默认用最便宜的那一级,把「需要多少 schema 才够写代码」交给模型按次决定。
持久执行、审批与重放
有些动作必须人批,但生成代码是线性写的、不该自己实现 pause/resume。Cloudflare 的 runtime 做法是:遇到需要审批的 connector 方法就把动作记为 pending、中止本次 pass,把 { status: "paused", pending } 交回;人批准后用同一段源码和同一个 executionId 再跑一遍,已经 applied 的调用直接返回记录结果而不重复执行。
sequenceDiagram
participant M as 模型
participant R as Code Mode runtime
participant C as Connector
M->>R: 执行(读几条 + 写一条)
R->>C: read(无需审批)
C-->>R: 结果写入执行日志
R->>R: 遇到需审批的 write → 记 pending,中止本 pass
R-->>M: status: paused + pending actions
M->>R: 人批准后重跑同一段代码(同一 executionId)
R->>R: 已 applied 的调用重放记录结果
R->>C: write 真正执行
R-->>M: status: completed + result
这个设计有两个代价。一是重放要求每次 pass 的调用顺序、connector、方法与参数完全一致,Promise.all() 里到达顺序不稳定的调用会触发 replay-divergence;Date.now()、Math.random() 这类不确定值要用 codemode.step() 包一次,让 runtime 记录并在重放时返回同一个值。二是回滚必须显式实现——rollback 按相反顺序调用 connector 的 revert,是补偿(compensation) 而非数据库事务隔离,没有 revert 的方法在执行被回滚后仍然是 applied。
还有一个治理细节值得记住:模型不能自己把某次执行提升为可复用 recipe,应用审查执行记录后调用 runtime.saveSnippet() 保存,模型之后才能 search() / describe() / run() 它。把「什么固化为能力」留在人这一侧的批准上,和 Agent Guardrails(Agent 护栏)的思路一致。
边界与代价
适合交给代码模式的阶段有明确数据流:并行查询后过滤、关联、去重、排序、聚合、校验,或后续调用参数可由前一次结构化结果确定的链路。每一步观察都需要模型重新做语义判断、单次查询、依赖逐项核验的任务,仍应走直接工具调用;审批边界也不会因为换了调用方式而消失。更完整的判断标准见 Programmatic Tool Calling(程序化工具调用)的「适用边界」。
沙箱本身要独立治理:FastMCP 的默认沙箱给的是 30 秒执行上限、100 MB 内存上限,并对单次 execute 里的 call_tool() 次数设上限(默认 50),因为一段循环可以在一次请求里放大成大量后端操作。Cloudflare 侧还有执行记录条数上限(默认保留 50 条终态执行)与单个重放值的序列化长度上限(1,000,000 字符,超限直接失败并建议改存引用)。
代码模式缓解但不消除 MCP「难组合」的问题。Pi 明确说这到今天仍是 MCP 最大的问题,代码模式只能部分解决,剩下的更多取决于 server 和 harness 两端怎么配合:很多 server 仍按「把工具一股脑塞进上下文、返回文本省 token」的 harness 来设计,而他们希望 MCP 更接近带智能工具发现的 OpenAPI——工具返回结构化数据、靠文档和描述被发现。
生态与命名
| 实现 | 运行位置 | 要点 |
|---|---|---|
Cloudflare Agents @cloudflare/codemode | 托管 runtime(Durable Object facet + Dynamic Worker executor) | 执行日志、审批重放、补偿式回滚、snippet 复用;沙箱默认无直接网络 |
Vercel AI SDK @ai-sdk/code-mode | Node.js 进程内 QuickJS 沙箱 | 实验特性,要求 Node 22+;experimental_toolCallers 治理每个工具能否被直接调用 |
| FastMCP CodeMode | Python,pydantic-monty 沙箱 | server 侧 transform,工具函数不用改;发现工具与 detail 级别可配 |
| Pi Codemode | harness 侧 JS/WASM 沙箱 | 配置 MCP 后自动加载;可调 tools.mcp__* 与模型接口,状态进会话 transcript |
| OpenAI Programmatic Tool Calling | 托管 V8 runtime | Responses API 原生工具,输出项为 program / program_output |
「代码模式」是 Cloudflare 官方中文博客与 FastMCP 中文文档采用的译法,可以视为这个模式目前较稳定的中文名;但各家产品里的工具名和配置项并不统一(Cloudflare 的 codemode、AI SDK 的 code_mode),跨实现比较时应看具体接口而不是名字。Programmatic Tool Calling 是同一模式在模型 API 侧的命名,差别主要在沙箱由谁提供、执行记录是否持久化。
相关概念
- Programmatic Tool Calling(程序化工具调用) — OpenAI 对同族模式的托管实现与更细的适用边界
- Model Context Protocol(模型上下文协议) — 代码模式常被用来解决 MCP 工具目录膨胀与中间结果回流
- Agent Sandbox(Agent 沙箱) — Code Mode 沙箱位于 harness 侧,与工具执行沙箱是两道不同的边界
- Context Engineering(上下文工程) — 工具定义延迟加载与中间结果不进上下文所属的工程面
- Prompt Caching(提示缓存) — 工具目录动态变化会破坏前缀缓存,是工具发现方式的设计约束
- 面向 Agent 的工具设计 — 工具返回结构化数据而非文本,是代码模式能组合的前提
- Agent-Computer Interface(智能体计算机接口) — 元工具与沙箱 SDK 也是 Agent 的操作界面
- Pi — 把 Codemode 内置进核心,并用它接入 MCP 的 harness 案例
- Agent Guardrails(Agent 护栏) — 审批、重放与 snippet 提升由运行时和人来裁决