Code Mode は、モデルがコードを書いてツールを組み合わせるパターンです。@cloudflare/codemode パッケージは、分離されたエグゼキューター、サービスコネクター、耐久性のあるランタイムで、このパターンを実装します。
各部分の役割は分かれています。エグゼキューターはコードを実行しますが、状態は持ちません。コネクターは能力を提供しますが、リプレイは管理しません。ランタイムは実行を記録し、承認、リプレイ、ロールバック、再利用を制御します。
標準構成では、モデルは codemode という外側のツールを 1 つ受け取ります。このツールはフィールドを 1 つ受け取り、耐久性のある実行結果を返します。
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 アロー関数を書くよう指示します。github や stripe のような、設定済みコネクターの名前空間名は列挙しますが、コネクターの全メソッドとスキーマは含めません。
モデルは、1 回の Code Mode 実行で関連メソッドを見つけ、返されたパスと型を次の実行で使えます。これにより、完全なツールカタログを初期のモデルコンテキストから外せます。
サンドボックス内では、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() は、コネクターメソッドと保存済みスニペットを検索します。返すのは順位付きパスであり、完全なスキーマではありません。モデルはそのパスを codemode.describe() に渡し、焦点を絞った TypeScript ドキュメントを取得できます。
codemode.step() は、非決定的または副作用のあるサンドボックス作業を、リプレイ用に記録します。codemode.run() は保存済みスニペットを呼び出します。
設定した各コネクターは、別のサンドボックスグローバルになります。github という名前のコネクターは github として使え、メソッドは github.list_pull_requests のようなパスに現れます。
コネクターレベルの説明は、次のような宣言を返します。
type ListPullRequestsInput = {
owner: string;
repo: string;
state?: "open" | "closed";
};
type ListPullRequestsOutput = unknown;
declare const github: {
list_pull_requests(
input: ListPullRequestsInput,
): Promise<ListPullRequestsOutput>;
};これらの宣言は、コネクタースキーマから生成されます。説明用です。実際のメソッド名、入力フィールド、出力型はコネクターに依存します。
サンドボックスには、標準の JavaScript グローバルも含まれます。Node.js API、ホストの認証情報、process、require、制限のないネットワークアクセスは公開しません。外部操作は、エグゼキューターが別の能力を明示的に提供しない限り、コネクターグローバル経由です。
エグゼキューターは、モデルが生成したコードブロックを 1 回実行します。呼び出し可能な名前空間を受け取り、結果、エラー、捕捉したコンソール出力を返します。実行履歴は保持しません。
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 グローバルは、発見とランタイム操作を提供します。codemode.search() はコネクターメソッドと保存済みスニペットを見つけます。codemode.describe() は、すべてのコネクタースキーマをモデルコンテキストに置かず、焦点を絞った TypeScript ドキュメントを返します。
ランタイムは、エグゼキューターとコネクターをつなぎます。実行記録、コネクター呼び出しログ、保留中の承認、スニペットを、分離された SQLite ストレージに保存します。この状態は、リクエスト完了と Durable Object のハイバネーションを生き延びます。
エグゼキューターとコネクターのインスタンスは一時的です。後の承認やリクエストを処理するときに、アプリケーションが再び提供します。
典型的な Agent は、3 つの部分をまとめて作ります。
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 のファセットに保存します。ファセットは、独自の SQLite ストレージを持つ、Agent の耐久性のある子です。createCodemodeRuntime() と Vite プラグインが、この実装詳細を管理します。ファセットを直接作成したり、アドレス指定したりする必要はありません。
ほとんどの Agent に必要な Code Mode ランタイムは 1 つです。name を省略すると、ランタイム名は default になります。
1 つの 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() エントリには、シーケンス番号が付きます。ログは、引数、状態、リプレイ方針、該当する場合は結果を記録します。
ランタイムは、コネクターを呼び出す前に呼び出しを executing とマークします。結果を記録したあと、applied とマークします。パス完了前にホストが止まると、実行は running のままになることがあります。後の expirePaused() メンテナンス呼び出しは、その滞留実行をエラーとマークし、リソースを解放します。承認では、滞留した running 実行は再開しません。
このログはリプレイの背骨です。開発者向けの監査ビューも支え、どの操作をロールバックできるかを決めます。一般的な会話メモリではなく、Agent 状態の代わりにもなりません。
コネクターメソッドは、ユーザー承認を要求できます。生成コードがそのメソッドに到達すると、ランタイムは操作を保留として記録し、現在のパスを中断します。操作は仮の結果を受け取りません。
アプリケーションは、保留中のメソッドと引数をユーザーに見せられます。承認すると、同じソースコードと実行 ID で別のパスが始まります。すでに applied とマークされた呼び出しは、再実行せず記録済み結果を返します。承認された操作が実行され、完了または次の承認までコードが続きます。
first pass: read ── execute ──> result
write ── pause
approval
second pass: read ── replay ───> recorded result
write ── execute ─> result
next call ────────> continueこの設計により、承認はリクエストやハイバネーションを超えて待てます。生成コードは線形のままで、一時停止や再開のロジックを実装しません。
再開できるのは、一時停止中の実行だけです。滞留した承認は、完了、拒否、ロールバック済みの実行を復活できません。操作を拒否すると実行は終わりますが、それ以前の操作は取り消しません。ロールバックは別操作です。
実行失敗は、エージェントループへデータとして返されます。そのため、サンドボックスエラーとリプレイ分岐は、捕捉されない RPC 例外として漏れ出る必要がありません。
リプレイでは、コネクター呼び出しとステップが同じ順序で起きる必要があります。すべてのパスで、あるシーケンス番号は同じコネクター、メソッド、引数を使わなければなりません。不一致は、リプレイ分岐エラーで実行を終了します。
記録済みのコネクター結果により、通常のデータ依存分岐は安定します。ただし Date.now()、Math.random()、その他の非決定的な値は、制御フローや操作引数を変えられます。
そのような作業は、codemode.step() で一度だけ捕捉します。ランタイムはクロージャの結果を記録し、承認リプレイ時にその値を返します。
async () => {
const createdAt = await codemode.step("created-at", () => Date.now());
return github.create_issue({
owner: "cloudflare",
repo: "agents",
title: `Review created at ${createdAt}`,
});
};コネクター呼び出しはすでにランタイムを通るため、ステップのラッパーは不要です。ステップは、コネクター呼び出しの外にある、非決定的または副作用のある作業に使います。直接のネットワークアクセスを明示的に許可している場合、承認リプレイ中に繰り返してはいけない直接のネットワーク操作もここに含まれます。
実行が一時停止する可能性があるときは、コネクター呼び出しを順次発行してください。ホストは、呼び出しが届いたときにシーケンス番号を割り当てます。Promise.all() 内の呼び出しは、パスごとに到着順が変わり、リプレイ分岐の原因になります。
一部のコネクターは、1 回のメソッド呼び出しを超えるリソースを必要とします。例は、ブラウザーセッション、データベーストランザクション、一時ワークスペースです。コネクターメソッドは安定した実行 ID を受け取り、パスをまたいで耐久リソースのメタデータをキーにできます。
Code Mode は、2 つのリソース寿命を区別します。
- パスリソースは、1 回のサンドボックスパスのあいだ続きます。ランタイムは、完了、失敗、一時停止のあとで
onPassEnd()を呼び出します。 - 実行リソースは、実行全体のあいだ続きます。ランタイムは、完了、失敗、拒否、ロールバックのあとで
disposeExecution()を呼び出します。一時停止のあとでは呼びません。
一時停止した実行は、別の Worker 呼び出しで再開できます。コネクターのライフサイクルフックは、インスタンスメモリに依存してはいけません。クリーンアップは冪等でもある必要があります。完了した実行は、後でロールバックされ、再度 dispose されることがあるためです。
ランタイムは、設定済みのすべてのコネクターにライフサイクルフックを呼びます。リソースを割り当てていないコネクターは、何もしないのが安全です。クリーンアップエラーは無視されるため、終わった実行を失敗扱いにしません。
デフォルトでは、ランタイムはコネクター結果を保存し、後のパスでリプレイします。これにより、元のコードが見た値そのものが保たれます。
コネクターは、呼び出しに replay: "reexecute" を付けられます。ランタイムはシーケンスと引数は記録しますが、結果は保存しません。後のパスでは、コネクターメソッドを再実行します。
この方針は、大きく安価で冪等な読み取りにだけ使います。結果はパス間で変わり得るため、生成コードはその変化を許容する必要があります。承認必須のメソッドは replay: "reexecute" を使えません。リプレイが、承認済みの副作用を複数回適用する可能性があるためです。
ロールバックは、applied なコネクター呼び出しを逆順にたどります。承認が必要だったかにかかわらず、revert 実装を持つ applied メソッドすべてに対して、その実装を呼び出します。
各 applied コネクター呼び出しについて、ランタイムは現在設定されているコネクターに revert 実装の実行を求めます。revert がないメソッドは applied のままです。存在しないコネクターもスキップされます。失敗した revert は、後続の補償試行を止めません。ランタイムは、残りの呼び出しを試したあとに失敗を報告します。少なくとも 1 件が revert されたときだけ、実行は rolled_back になります。
ロールバックは補償であり、データベースのトランザクション分離ではありません。各操作の取り消しの意味は、コネクター作者が定義します。外部システムは、元の呼び出しとその補償のあいだに変わることもあります。
実行ログは監査証跡であり、時間とともに増えます。新しい実行が始まると、ランタイムはまずその実行を挿入し、そのあと古い終端実行を刈り込みます。maxExecutions のデフォルトは 50 です。実行中の実行は終端ではないため、完了直後は一時的に終端記録が 51 件になることがあります。次の実行が始まるか、pruneExecutions() を呼ぶまで続きます。
実行中と一時停止中の実行は、自動では刈り込みません。まだ完了または再開が必要なことがあるためです。滞留した非終端実行を回収するには、定期メンテナンスから expirePaused() を使います。ランタイムは、滞留した一時停止実行を拒否、滞留した実行中実行をエラーとマークし、その実行リソースを dispose します。
個別の実行記録を削除したり、終端履歴を明示的に刈り込んだりもできます。非終端実行を削除すると、その実行スコープのリソースも dispose されます。
耐久リプレイ用に保存する各値には、シリアライズ後 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.",
});スニペットは入力値を受け取れます。そのコネクター呼び出しは、モデルが実行したときに、現在の実行ログへ加わります。スニペットは、元の実行のコネクター一覧も保持します。記録されたコネクターが使えない場合、codemode.run() は error プロパティを持つオブジェクトに解決します。自動では例外を投げません。