Code Mode 是一种模型编写代码以组合工具的模式。@cloudflare/codemode 包通过隔离执行器、服务连接器与持久运行时实现该模式。
这些部分职责分离。执行器运行代码但不存储状态。连接器提供能力但不管理重放。运行时记录执行并控制审批、重放、回滚与复用。
标准设置中,模型收到名为 codemode 的外层工具。该工具接受一个字段并返回持久执行结果:
type CodeModeInput = {
code: string;
};
type PendingAction = {
executionId: string;
seq: number;
connector: string;
method: string;
args: unknown;
};
type CodeModeOutput =
| { status: "completed"; executionId: string; result: unknown; logs?: string[] }
| { status: "paused"; executionId: string; pending: PendingAction[] }
| { status: "error"; executionId: string; error: string; logs?: string[] };其描述告诉模型编写 JavaScript async 箭头函数。描述列出配置的 connector 命名空间名(如 github 或 stripe),但不包含每个 connector 方法与 schema。
模型可用一次 Code Mode execution 发现相关方法,然后在下次 execution 中使用返回的路径与类型。这使完整 tool 目录保持在初始模型上下文之外。
沙箱内,codemode 全局提供平台级 SDK:
declare const codemode: {
search(query: string): Promise<SearchOutput>;
describe(target: string): Promise<DescribeOutput>;
step<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
run(name: string, input?: unknown): Promise<unknown>;
};
type SearchOutput = {
results: Array<{
path: string;
connector: string;
method: string;
description?: string;
kind: "method" | "snippet";
score: number;
}>;
total: number;
truncated: boolean;
};
type DescribeOutput = {
path: string;
description?: string;
types: string;
kind: "connector" | "method" | "snippet";
};codemode.search() 搜索 connector 方法与已保存 snippet。返回 ranked 路径,而非完整 schema。模型可将一条路径传给 codemode.describe() 获取聚焦 TypeScript 文档。
codemode.step() 记录非确定性或有副作用的沙箱工作以供重放。codemode.run() 调用已保存 snippet。
每个配置的 connector 成为另一个沙箱全局。名为 github 的 connector 可用为 github,其方法出现在 github.list_pull_requests 等路径下。
Connector 级描述返回类似声明:
type ListPullRequestsInput = {
owner: string;
repo: string;
state?: "open" | "closed";
};
type ListPullRequestsOutput = unknown;
declare const github: {
list_pull_requests(
input: ListPullRequestsInput,
): Promise<ListPullRequestsOutput>;
};这些声明由 connector schema 生成,为示意;实际方法名、输入字段与输出类型取决于 connector。
沙箱还包含标准 JavaScript 全局。不暴露 Node.js API、host 凭证、process、require 或无限制网络访问。除非 executor 显式提供其他能力,所有外部操作通过 connector 全局进行。
执行器一次运行一块模型生成的代码。接收可调用命名空间并返回结果、错误与捕获的 console 输出。不保留执行历史。
DynamicWorkerExecutor 使用 Dynamic Worker Loader 为每次执行遍历创建隔离 Worker。恢复的执行在另一次遍历中再次运行代码。因此持久状态不能存在于沙箱内。
默认阻止外部 fetch() 与 connect()。DynamicWorkerExecutor 配置 globalOutbound: null,除非你提供其他值。可提供 Fetcher 将出站请求路由到受控服务。
连接器将宿主侧服务桥接到沙箱。可包装 Model Context Protocol (MCP) 服务器、OpenAPI 文档、AI SDK 工具集或自定义代码。
每个连接器成为全局命名空间。例如名为 github 的连接器暴露 github.list_pull_requests() 等调用。生成的代码永不接收连接器凭证或客户端对象。
连接器调用通过 Workers RPC 跨越沙箱边界。运行时在连接器执行前拦截每次调用。此拦截应用审批、日志、重放与回滚策略。
codemode 全局提供发现与 runtime 操作。codemode.search() 查找 connector 方法与已保存 snippet。codemode.describe() 返回聚焦 TypeScript 文档,而不将每个 connector schema 放入模型上下文。
运行时连接执行器与连接器。在隔离 SQLite 存储中保存执行记录、连接器调用日志、待审批项与代码片段。此状态在请求完成与 Durable Object 休眠后仍然保留。
执行器与连接器实例保持瞬时。应用在后续审批或请求处理时再次提供它们。
典型 Agent 同时创建三部分:
import {
createCodemodeRuntime,
DynamicWorkerExecutor,
} from "@cloudflare/codemode";
const runtime = createCodemodeRuntime({
ctx: this.ctx,
executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }),
connectors: [github, repoApi],
});
const tools = { codemode: runtime.tool() };import {
createCodemodeRuntime,
DynamicWorkerExecutor,
} from "@cloudflare/codemode";
const runtime = createCodemodeRuntime({
ctx: this.ctx,
executor: new DynamicWorkerExecutor({ loader: this.env.LOADER }),
connectors: [github, repoApi],
});
const tools = { codemode: runtime.tool() };Code Mode 将此状态存储在 Durable Object facet 中。Facet 是 Agent 的持久子对象,自有 SQLite 存储。createCodemodeRuntime() 与 Vite 插件管理此实现细节。你无需直接创建或寻址 facet。
多数 Agent 只需一个 Code Mode 运行时。省略 name 时运行时使用 default。
当单个 Agent 需要分离的 Code Mode 历史时设置 name。例如名为 research 与 operations 的运行时保持分离的执行记录与代码片段集合:
const researchRuntime = createCodemodeRuntime({
ctx: this.ctx,
executor,
connectors: researchConnectors,
name: "research",
});
const operationsRuntime = createCodemodeRuntime({
ctx: this.ctx,
executor,
connectors: operationsConnectors,
name: "operations",
});const researchRuntime = createCodemodeRuntime({
ctx: this.ctx,
executor,
connectors: researchConnectors,
name: "research",
});
const operationsRuntime = createCodemodeRuntime({
ctx: this.ctx,
executor,
connectors: operationsConnectors,
name: "operations",
});运行时名称标识其持久存储。不命名模型、连接器、工具或单个执行。
变更连接器集不会创建另一个运行时。每次执行记录启动时配置的所有连接器,已保存代码片段继承该列表。审批重放与代码片段执行要求所有记录的连接器仍可用,即使原始代码未调用每个连接器。
运行时为每次执行分配稳定 ID。每次连接器调用与 codemode.step() 条目获得序列号。日志记录参数、状态、重放策略及适用时的结果。
Runtime 在调用 connector 前将调用标记为 executing。记录 result 后标记为 applied。若 host 在完成 pass 前停止,execution 可保持 running。后续 expirePaused() 维护调用将该 stale execution 标记为 error 并释放资源。审批不会恢复 stale running execution。
此日志是重放 spine。也支持开发者审计视图并决定哪些 action 可回滚。不是通用对话内存,不替代 Agent 状态。
Connector 方法可要求 user approval。生成代码到达该方法时,runtime 将 action 记录为 pending 并 abort 当前 pass。Action 不会收到 provisional result。
应用可向用户展示 pending 方法与参数。审批以相同源代码与 execution ID 启动另一次 pass。已标记 applied 的调用返回记录 result 而非再次执行。已批准的 action 然后执行,代码继续直到完成或另一审批。
first pass: read ── execute ──> result
write ── pause
approval
second pass: read ── replay ───> recorded result
write ── execute ─> result
next call ────────> continue此设计允许审批等待超出请求或休眠。生成代码保持线性,不实现暂停/恢复逻辑。
仅已暂停的执行可恢复。过期审批不能复活已完成、已拒绝或已回滚的执行。拒绝操作结束执行,但不撤销较早操作。回滚是独立操作。
执行失败作为数据返回 agent 循环。因此沙箱错误与重放分歧无需作为未捕获 RPC 异常逃逸。
重放要求 connector 调用与 step 以相同顺序发生。每次 pass 上,给定序列号必须使用相同 connector、方法与参数。Mismatch 以 replay-divergence error 结束 execution。
记录的 connector result 使正常数据依赖分支稳定。然而 Date.now()、Math.random() 或其他非确定性源的值可改变控制流或 action 参数。
使用 codemode.step() 一次性捕获此类工作。Runtime 记录闭包 result 并在审批重放期间返回该值:
async () => {
const createdAt = await codemode.step("created-at", () => Date.now());
return github.create_issue({
owner: "cloudflare",
repo: "agents",
title: `Review created at ${createdAt}`,
});
};连接器调用已通过运行时,无需 step 包装。对连接器调用外的非确定性或有副作用工作使用 step。若显式允许直接网络访问,这包括审批重放期间不得重复的直接网络操作。
执行可能在暂停时按顺序发出连接器调用。主机在调用到达时分配序列号。Promise.all() 中的调用在不同遍历中可能以不同顺序到达并导致重放分歧。
部分连接器需要超出单次方法调用的资源。例如浏览器会话、数据库事务与临时工作区。连接器方法接收稳定执行 ID,可在遍历间按键关联持久资源元数据。
Code Mode 区分两种资源生命周期:
- 遍历资源 持续一个沙箱遍历。运行时在已完成、失败与已暂停的遍历后调用
onPassEnd()。 - 执行资源 持续整个执行。运行时在完成、失败、拒绝或回滚后调用
disposeExecution(),暂停后不调用。
已暂停的执行可在另一次 Worker 调用中恢复。连接器生命周期钩子不得依赖实例内存。清理也须幂等,因为已完成的执行可能稍后回滚并再次释放。
运行时为每个配置的连接器调用生命周期钩子。未分配资源的连接器应安全无操作。忽略清理错误,以免将已完成的执行变为失败。
默认 runtime 存储 connector result 并在后续 pass 重放。保留原始代码观察到的精确值。
Connector 可将调用标记为 replay: "reexecute"。Runtime 仍记录其序列与参数,但不存储 result。后续 pass 再次运行 connector 方法。
仅对大型、廉价结果的 idempotent 读取使用此策略。Result 可在 pass 间变化,生成代码须 tolerate 该变化。需要 approval 的方法不能使用 replay: "reexecute",因为重放可能多次应用已批准的副作用。
Rollback 按逆序遍历 applied connector 调用。对每个提供 revert 的 applied 方法调用 revert 实现,无论该方法是否需要 approval。
对每个 applied connector 调用,runtime 要求当前配置的 connector 运行其 revert 实现。无 revert 的方法保持 applied。缺失 connector 也跳过。Revert 失败不停止后续补偿尝试,runtime 在尝试剩余调用后报告失败。仅当至少一个调用被 revert 时 execution 移至 rolled_back。
Rollback 是补偿,非数据库事务隔离。Connector 作者为每个 action 定义 reversal 含义。外部系统在原始调用与补偿之间也可能变化。
Execution 日志是审计 trail 并随时间增长。新 run 开始时 runtime 先插入该 run 再 prune 较旧 terminal execution。maxExecutions 默认 50。因 running execution 非 terminal,completion 可能暂时留下 51 条 terminal 记录,直到另一 run 开始或调用 pruneExecutions()。
Running 与 paused execution 不自动 prune。它们可能仍需 finish 或 resume。从 recurring 维护调用 expirePaused() 回收 stale nonterminal run。Runtime 将 stale paused run 标记为 rejected,stale running run 标记为 error,然后 dispose 其 execution 资源。
也可显式删除单个执行记录或修剪终态历史。删除非终态执行会释放其执行范围资源。
每个持久重放存储的值有序列化字符限制 1,000,000。实现在序列化后检查 JavaScript 字符串长度。此限制适用于连接器参数、记录的连接器结果、步骤结果与执行源代码。
运行时不能截断这些值。截断会在重放期间提供不同数据。因此过大或不可序列化的重放值会使执行失败,并建议将数据存到别处,再传递小引用如文件路径。
最终结果行为不同,因为重放不消费它。执行可完成并将真实结果返回模型。若结果无法放入审计记录,运行时在那里存储省略消息。
transformResult 可在模型收到前重塑完成的结果。转换在运行时尝试记录原始结果后运行。审计追踪在能放下时保留原始值,模型可收到更小表示。
代码片段是来自执行的已保存源代码。代码片段将模型编写的程序变为可复用方案。跨请求与休眠仍可用。
模型不自行提升自己的代码。应用审查执行并以其执行 ID 调用 runtime.saveSnippet()。API 接受任何执行状态,保存前请验证执行成功完成。模型可用 codemode.search() 查找代码片段,用 codemode.describe() 检查,用 codemode.run() 调用。
const runs = await runtime.executions(20);
await runtime.saveSnippet("list-open-prs", {
executionId: runs[0].id,
description: "List open pull requests for a repository.",
});const runs = await runtime.executions(20);
await runtime.saveSnippet("list-open-prs", {
executionId: runs[0].id,
description: "List open pull requests for a repository.",
});Snippet 可接受 input 值。模型运行它时其 connector 调用加入当前 execution 日志。Snippet 也保留源 execution 的 connector 列表。若记录的 connector 不可用,codemode.run() 解析为带 error 属性的对象,不会自动 throw。