去年 9 月,我们介绍了 Code Mode,其核心理念是:智能体执行任务时,不应通过调用工具(tool calls)来完成,而应通过编写调用 API 的代码来完成。我们已经展示过,只需将一个 MCP 服务器转换为 TypeScript API,就可以▶ 将 token 使用量削减 81%。我们还演示了,Code Mode 也可以运行在 MCP 服务器的后端而不是前端,从而构建出全新的 Cloudflare MCP 服务器:它仅用两个工具、不到 1,000 个 token,就暴露了整个 Cloudflare API。
但如果一个智能体(或一个 MCP 服务器)要执行由 AI 动态生成的代码来完成任务,那么这些代码总得有个地方运行,而且这个地方必须是安全的。你不能直接在应用中对 AI 生成的代码执行 eval():恶意用户很容易通过提示词诱导 AI 注入漏洞。
你需要一个沙箱(sandbox):一个与应用本身、与外部世界相隔离的代码执行环境,只开放这段代码确实需要访问的特定能力。
沙箱是 AI 行业的热门话题。对于这类任务,大多数人会选择容器(container)。借助基于 Linux 的容器,你可以启动几乎任何你想要的代码执行环境。Cloudflare 甚至还为此提供了容器运行时和Sandbox SDK。
但容器代价高、启动慢:启动往往要花数百毫秒,运行还要占用数百 MB 内存。为了避免延迟,你大概率需要让它们保持热启动(warm),而这又会让你倾向于在多个任务之间复用已有容器,从而损害安全性。
如果我们想支持面向消费者规模的智能体,也就是每个终端用户都有一个智能体(甚至很多个!),并且每个智能体都会写代码,那么容器并不够。我们需要更轻量的方案。
去年 9 月那篇 Code Mode 文章里,还夹带发布了一个新的实验性功能:Dynamic Worker Loader API。这个 API 允许一个 Cloudflare Worker 在运行时动态指定代码,并即时实例化出一个运行在独立沙箱中的新 Worker。
Dynamic Worker Loader 现已进入公开测试版(open beta),对所有付费版 Workers 用户开放。
完整细节请参阅文档,下面是它的使用方式:
let agentCode: string = `
export default {
async myAgent(param, env, ctx) {
// ...
}
}
`;
let chatRoomRpcStub = ...;
let worker = env.LOADER.load({
compatibilityDate: "2026-03-01",
mainModule: "agent.js",
modules: {
"agent.js": agentCode
},
env: {
CHAT_ROOM: chatRoomRpcStub
},
globalOutbound: null,
});
await worker.getEntrypoint().myAgent(param);
就是这么简单。
Dynamic Worker 使用的底层沙箱机制,正是整个 Cloudflare Workers 平台自 8 年前发布以来一直采用的技术:isolate(隔离实例)。isolate 是 V8 JavaScript 执行引擎的一个实例,而 V8 也正是 Google Chrome 使用的引擎。它们就是 Workers 的运行基础。
一个 isolate 启动只需几毫秒,内存占用也只需几 MB。相比典型容器,这大约快 100 倍,且内存效率高 10 到 100 倍。
这意味着,如果你想为每个用户请求按需启动一个全新的 isolate,运行一小段代码后再将其丢弃,你完全可以这么做。
许多基于容器的沙箱服务商都会限制全局并发沙箱数量,以及沙箱创建速率。Dynamic Worker Loader 没有这些限制。因为它本质上只是一个 API,调用的是一直以来支撑我们平台的同一套技术,而这套技术本来就支持 Workers 平滑扩展到每秒数百万请求。
想要处理每秒一百万个请求,并且每一个请求都加载一个独立的 Dynamic Worker 沙箱、全部并发运行?完全没问题!
一次性使用的 Dynamic Worker 通常运行在创建它的 Worker 所在的同一台机器上——甚至是同一个线程里。你无需在全球范围内寻找一个热启动的沙箱,也无需跨地域通信。isolate 轻量到我们可以直接在请求落地的地方运行它。Dynamic Workers 已在 Cloudflare 全球数百个节点全部支持。
与容器相比,唯一的限制是:你的智能体需要编写 JavaScript。
严格来说,Workers(包括动态 Workers)也支持 Python 和 WebAssembly,但对于小段代码——比如由智能体按需生成的那种——JavaScript 的加载和运行速度会快得多。
我们人类往往对编程语言有强烈偏好。有人喜欢 JavaScript,也有人更偏爱 Python、Rust 或其他无数语言。
但这里讨论的不是人类,而是 AI。AI 可以编写你想要的任何语言。LLM 对所有主流语言都很擅长,而它们在 JavaScript 上的训练数据尤其庞大。
JavaScript 因其 Web 天生属性,就是为沙箱环境而设计的。它是这项工作的正确语言。
如果我们希望智能体能够做点真正有用的事情,它就必须能和外部 API 通信。那我们该如何告诉它自己可以访问哪些 API 呢?
MCP 定义的是扁平化工具调用(flat tool calls)的模式,而不是编程 API。OpenAPI 提供了表达 REST API 的方式,但它非常冗长——无论是规范本身,还是你为调用它而编写的代码,都是如此。
对于暴露给 JavaScript 的 API,答案显而易见,而且只有一个:TypeScript。
智能体懂 TypeScript。TypeScript 本身就是为简洁表达而设计的。只用很少的 token,你就能让智能体精确理解你的 API。
interface ChatRoom {
getHistory(limit: number): Promise < Message[] > ;
subscribe(callback: (msg: Message) => void): Promise < Disposable > ;
post(text: string): Promise < void > ;
}
type Message = {
author: string;
time: Date;
text: string;
}
再看看等价的 OpenAPI 规范(长到你得滚动才能看完):
openapi: 3.1.0
info:
title: ChatRoom API
description: >
与聊天室交互的接口。
version: 1.0.0
paths:
/messages:
get:
operationId: getHistory
summary: 获取最近的聊天记录
description: 按时间从新到旧返回最近 `limit` 条消息。
parameters:
- name: limit
in: query
required: true
schema:
type: integer
minimum: 1
responses:
"200":
description: 消息列表。
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Message"
post:
operationId: postMessage
summary: 向聊天室发送消息
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- text
properties:
text:
type: string
responses:
"204":
description: 消息发送成功。
/messages/stream:
get:
operationId: subscribeMessages
summary: 通过 SSE 订阅新消息
description: >
打开一个服务器发送事件(Server-Sent Events)流。每个事件携带一个
JSON 编码的 Message 对象。客户端通过关闭连接来取消订阅。
responses:
"200":
description: 新消息的 SSE 流。
content:
text/event-stream:
schema:
description: >
每个 SSE `data` 字段都包含一个 JSON 编码的 Message 对象。
$ref: "#/components/schemas/Message"
components:
schemas:
Message:
type: object
required:
- author
- time
- text
properties:
author:
type: string
time:
type: string
format: date-time
text:
type: string
我们认为 TypeScript API 更好。它所需 token 更少,也更容易理解(无论对智能体还是对人类都是如此)。
Dynamic Worker Loader 让你可以很方便地在自己的 Worker 中实现这样的 TypeScript API,然后将其作为方法参数或通过 env 对象传给 Dynamic Worker。Workers Runtime 会自动在沙箱与你的宿主代码(harness code)之间建立一座 Cap'n Web RPC 桥,这样智能体就能跨越安全边界调用你的 API,而且完全不会意识到自己并不是在使用本地库。
这意味着你的智能体可以编写如下代码:
let history = await env.CHAT_ROOM.getHistory(1000);
return history.filter(msg => msg.author == "alice");
如果你更愿意给智能体提供 HTTP API,也完全支持。通过 worker loader API 的 globalOutbound 选项,你可以注册一个回调函数,让它在每次 HTTP 请求发生时被调用;你可以在其中检查请求、改写请求、注入认证密钥、直接返回响应、阻止请求,或做任何你想做的事。
例如,你可以用它来实现凭据注入(token injection):当智能体向一个需要授权的服务发起 HTTP 请求时,你在请求发出前为其添加认证凭据。这样一来,智能体本身永远不会知道这些秘密凭据,因此也就无法泄露它们。
当智能体要访问一个众所周知、可能已包含在训练集中的 API 时,或者当你希望智能体使用某个基于 REST API 构建的库时,采用纯 HTTP 接口可能是合适的(该库可以运行在智能体的沙箱内)。
不过,在没有兼容性要求的前提下,TypeScript RPC 接口优于 HTTP:
加固基于 isolate 的沙箱并不容易,因为它的攻击面比硬件虚拟机更复杂。尽管所有沙箱机制都会有漏洞,但 V8 中的安全漏洞通常比典型 hypervisor(虚拟机监控器)中的安全漏洞更常见。因此,当你用 isolate 来隔离可能带有恶意的代码时,必须有额外的纵深防御层(defense-in-depth)。例如,Google Chrome 就因此实现了严格的进程隔离,但这并不是唯一的解决方案。
在保护基于 isolate 的平台方面,我们已有近十年的经验。我们的系统会在数小时内自动将 V8 安全补丁部署到生产环境——比 Chrome 自己还快。我们的安全架构包含一层定制的第二层沙箱,并能基于风险评估动态隔离租户。我们还扩展了 V8 沙箱本身,以利用 MPK 等硬件特性。我们与顶尖研究人员合作(并将其中一些人招入团队),开发了针对 Spectre 的新型防御机制。我们还有系统会扫描代码中的恶意模式,并自动阻断或施加额外的沙箱层。以及更多。
当你在 Cloudflare 上使用 Dynamic Workers 时,这一切都会自动随之而来。
我们构建了一些你在使用 Dynamic Workers 时可能会觉得很有帮助的库:
@cloudflare/codemode 简化了通过 Dynamic Workers 运行模型生成代码并访问 AI 工具的过程。其核心是 DynamicWorkerExecutor(),它会构建一个专门设计的沙箱,包含代码规范化(code normalisation)能力,用于处理常见格式错误;同时还能直接访问 globalOutbound fetcher,以控制沙箱内 fetch() 的行为——将其设为 null 可实现完全隔离,或传入一个 Fetcher 绑定以对沙箱发出的出站请求进行路由、拦截或增强。
const executor = new DynamicWorkerExecutor({
loader: env.LOADER,
globalOutbound: null,
});
const codemode = createCodeTool({
tools: myTools,
executor,
});
return generateText({
model,
messages,
tools: {
codemode
},
});
Code Mode SDK 还提供了两个服务端工具函数。codeMcpServer({ server, executor }) 可以包装现有的 MCP Server,将其工具表面(tool surface)替换为一个单独的 code() 工具。openApiMcpServer({ spec, executor, request }) 更进一步:给定一个 OpenAPI 规范和一个 executor,它会构建一个完整的 MCP Server,并提供与 Cloudflare MCP Server 一样的 search() 和 execute() 工具,更适合较大的 API。
在这两种情况下,模型生成的代码都运行在 Dynamic Workers 中,而对外部服务的调用则通过传递给 executor 的 RPC 绑定完成。
Dynamic Workers 需要预先打包好的模块。 @cloudflare/worker-bundler 可以帮你处理这件事:你只需提供源码文件和一个 package.json,它就会从 registry 解析 npm 依赖,使用 esbuild 完成打包,并返回 Worker Loader 所需的模块映射。
import {
createWorker
} from "@cloudflare/worker-bundler";
const worker = env.LOADER.get("my-worker", async () => {
const {
mainModule,
modules
} = await createWorker({
files: {
"src/index.ts": `
import { Hono } from 'hono';
import { cors } from 'hono/cors';
const app = new Hono();
app.use('*', cors());
app.get('/', (c) => c.text('Hello from Hono!'));
app.get('/json', (c) => c.json({ message: 'It works!' }));
export default app;
`,
"package.json": JSON.stringify({
dependencies: {
hono: "^4.0.0"
}
})
}
});
return {
mainModule,
modules,
compatibilityDate: "2026-01-01"
};
});
await worker.getEntrypoint().fetch(request);
它还通过 createApp 支持全栈应用——可将服务端 Worker、客户端 JavaScript 和静态资源一起打包,并内置资源分发能力,处理内容类型、ETag 和 SPA 路由。
@cloudflare/shell 为你的智能体在 Dynamic Worker 中提供了一个虚拟文件系统。智能体代码通过 state 对象上的类型化方法来执行操作——读取、写入、搜索、替换、diff、glob、JSON 查询/更新、归档——使用结构化输入输出,而不是解析字符串。
存储后端由一个持久化的 Workspace 支撑(SQLite + R2),因此文件可以跨执行持久保存。像 searchFiles、replaceInFiles 和 planEdits 这样的粗粒度操作可最大限度减少 RPC 往返次数——智能体只需发起一次调用,而不必循环处理单个文件。批量写入默认具有事务性:如果任意一次写入失败,之前的写入会自动回滚。
import {
Workspace
} from "@cloudflare/shell";
import {
stateTools
} from "@cloudflare/shell/workers";
import {
DynamicWorkerExecutor,
resolveProvider
} from "@cloudflare/codemode";
const workspace = new Workspace({
sql: this.ctx.storage.sql,
r2: this.env.MY_BUCKET,
name: () => this.name
});
const executor = new DynamicWorkerExecutor({
loader: env.LOADER
});
const result = await executor.execute(
`async () => {
// 在所有 TypeScript 文件中搜索某个模式
const hits = await state.searchFiles("src/**/*.ts", "answer");
// 将多处编辑规划为单个事务
const plan = await state.planEdits([
{ kind: "replace", path: "/src/app.ts",
search: "42", replacement: "43" },
{ kind: "writeJson", path: "/src/config.json",
value: { version: 2 } }
]);
// 原子性应用——失败时回滚
return await state.applyEditPlan(plan);
}`,
[resolveProvider(stateTools(workspace))]
);
这个包还附带预构建的 TypeScript 类型声明和一个系统提示模板,因此你只需很少几个 token,就能把完整的 state API 放进 LLM 上下文里。
开发者希望智能体通过针对工具 API 编写并执行代码来完成任务,而不是一次只进行一个串行工具调用。借助 Dynamic Workers,LLM 可以生成一个单独的 TypeScript 函数,把多个 API 调用串联起来,在 Dynamic Worker 中运行,并将最终结果返回给智能体。这样,进入上下文窗口的只有输出结果,而不是每个中间步骤。这既降低了延迟,也减少了 token 使用量,同时还能带来更好的效果,特别是在工具表面较大的情况下。
我们自己的 Cloudflare MCP server 就是完全按这种方式构建的:它仅通过两个工具——search 和 execute——就在不到 1,000 个 token 中暴露了整个 Cloudflare API,因为智能体面对的是类型化 API 编程,而不是在数百个单独工具定义中摸索。
开发者正在使用 Dynamic Workers 让智能体即时构建自定义自动化。Zite 就是一个例子:它正在打造一个应用平台,用户通过聊天界面进行交互——LLM 在幕后编写 TypeScript,用来构建 CRUD 应用、连接 Stripe、Airtable、Google Calendar 等服务,并运行后端逻辑,而用户从头到尾都不会看到一行代码。每个自动化都运行在其各自独立的 Dynamic Worker 中,并且只能访问该端点所需的特定服务和库。
“为了让 Zite 的 LLM 生成应用支持服务端代码,我们需要一个具备即时性、隔离性和安全性的执行层。Cloudflare 的 Dynamic Workers 在这三方面都达到了要求,而且在我们针对速度和库支持做的基准测试中,表现优于所有其他平台。其兼容 NodeJS 的运行时支持了 Zite 的全部工作流,使我们能够接入数百个第三方集成,同时不牺牲启动时间。借助 Dynamic Workers,Zite 现在每天可处理数百万次执行请求。” —— Antony Toron,Zite 联合创始人兼 CTO
开发者正在构建这样的平台:由 AI 生成完整应用,既可面向他们的客户,也可供内部团队搭建原型。借助 Dynamic Workers,每个应用都可以按需启动,然后在再次调用前回到冷存储。快速的启动时间让活跃开发过程中的变更预览变得非常容易。平台还可以拦截或阻止生成代码发出的任何网络请求,从而确保 AI 生成应用的运行安全。
动态加载的 Workers 的定价为:每个每天加载的唯一 Worker 收费 0.002 美元(截至本文发布时),此外还要加上常规 Workers 的 CPU 时间和调用次数费用。
对于 AI 生成的 “code mode” 使用场景,即每个 Worker 都是一次性的唯一实例,这意味着每加载一个 Worker 的价格是 0.002 美元(另加 CPU 和调用费用)。与生成代码所需的推理成本相比,这项成本通常可以忽略不计。
在 Beta 测试期间,0.002 美元的收费将被免除。由于定价可能发生变化,请始终查看我们的 Dynamic Workers 定价页面 以获取最新信息。
如果你使用的是 Workers 付费计划,今天就可以开始使用 Dynamic Workers。
使用这个“hello world”入门模板,即可部署一个能够加载并执行 Dynamic Workers 的 Worker。
你也可以部署 Dynamic Workers Playground,在那里你可以编写或导入代码,使用 @cloudflare/worker-bundler 在运行时打包,通过 Dynamic Worker 执行,查看实时响应和执行日志。

Dynamic Workers 快速、可扩展且轻量。欢迎在 Discord 上联系我们,如果你有任何问题。我们非常期待看到你构建出的作品!