AIエージェントのサンドボックス化、100倍高速に
Cloudflareは、AIエージェントが生成したコードを安全かつ高速に実行するための軽量サンドボックス「Dynamic Worker Loader」を公開ベータとして提供し、コンテナに依存しないエージェント実行環境の実現を目指す。
キーポイント
Code Modeの進化とトークン削減効果
エージェントがツール呼び出しではなくコード生成を行う「Code Mode」により、MCPサーバー統合でトークン使用量を81%削減可能であることを再確認。
コンテナベース実行のボトルネック
従来のLinuxコンテナは起動に数百ミリ秒、メモリに数百MBを要し、消費規模のエージェント普及にはレイテンシとコストの面で不向き。
Dynamic Worker Loaderの仕組み
ランタイムで指定されたコードをCloudflare Worker内で隔離・実行するAPIを提供し、ネットワークアクセスの遮断やRPCスタブ連携によりセキュリティを確保。
公開ベータと実装例の提供
全有料Workersユーザー向けに公開ベータを開始し、LLM生成コードの読み込みからエントリポイント呼び出しまでの具体的な実装コードを公開。
高速起動と低メモリ消費
V8エンジン由来の「isolates」は数ミリ秒で起動し数MBのメモリしか使用せず、コンテナ比で100倍高速・最大100倍のメモリ効率を実現する。
無制限のスケーラビリティとゼロレイテンシ
並列サンドボックス数や作成速度に制限がなく、同一マシン・スレッド上で実行されるため、世界中の数百箇所でのミリ秒単位の応答が可能。
JavaScript中心の設計とAI活用
スニペット実行にはJavaScriptが最適だが、LLMは多言語を扱えるためAIエージェントの生成コードとして適合し、Webサンドボックス化にも標準対応している。
影響分析・編集コメントを表示
影響分析
CloudflareのDynamic Worker Loaderは、AIエージェントの実行環境におけるコンテナ依存というボトルネックを解決し、低レイテンシ・高セキュリティな実行基盤を提供する。これにより、エンドユーザー規模のエージェント普及が現実化し、CloudflareのWorkersエコシステムがAIエージェント実行層での標準インフラとなる可能性が高い。
編集コメント
Cloudflareはコンテナオーケストレーション市場とは一線を画し、エッジコンピューティングの特性を活かした「サーバーレス型エージェントランタイム」で差別化を図っている。技術的な革新性よりも、既存インフラとの統合コストをいかに下げるかという実装視点が強いが、消費規模のエージェント普及には不可欠な基盤となるだろう。
昨年9月、私たちはCode Modeを発表しました。これは、エージェントがツール呼び出しを行うのではなく、APIを呼び出すコードを書くことでタスクを実行するという考え方です。MCPサーバーをTypeScript APIに変換するだけでトークン使用量を81%削減できることを示しました。また、Code ModeはMCPサーバーの前ではなくその背後で動作することもでき、2つのツールと1,000トークン未満でCloudflare API全体を公開する新しいCloudflare MCPサーバーを作成したことも実証しています。
しかし、エージェント(またはMCPサーバー)がAIによってオンザフライで生成されたコードを実行してタスクを遂行する場合、そのコードはどこかで実行されなければならず、その場所は安全である必要があります。悪意のあるユーザーが簡単にAIに脆弱性を注入させるプロンプトを与える可能性があるため、アプリ内でAI生成のコードを直接eval()することはできません。
必要なのはサンドボックスです。これは、アプリケーションや世界の他の部分から隔離された場所であり、コードがアクセスするべき特定の機能のみを利用できるようにした実行環境です。
サンドボックス化はAI業界でホットなトピックです。このタスクには、多くの人がコンテナ(container)を採用しています。Linuxベースのコンテナを使用すれば、任意のコード実行環境を起動できます。Cloudflareもその目的のために、コンテナランタイムとSandbox SDKを提供しています。
しかし、コンテナは高価であり、起動に時間がかかります。起動には数百ミリ秒を要し、実行には数百メガバイトのメモリが必要です。遅延を避けるために常時稼働状態(ウォームアップ)に保つ必要がある場合が多く、またセキュリティが損なわれるリスクがあるにもかかわらず、既存のコンテナを複数のタスクで再利用したくなる誘惑に駆られるかもしれません。
エンドユーザー全員がエージェント(あるいは多数のエージェント)を持ち、すべてのエージェントがコードを書くような消費者規模のエージェントをサポートしたいのであれば、コンテナだけでは不十分です。より軽量な何かが必要なのです。
そして、それがあります。
Dynamic Worker Loader: 軽量なサンドボックス
9 月の Code Mode 記事に埋め込まれていたのは、新しい実験的機能である「Dynamic Worker Loader API」の発表でした。この API を使用すると、Cloudflare Worker がランタイムで指定されたコードを用いて、独自のサンドボックス内で新しい Worker を即時にインスタンス化できます。
Dynamic Worker Loader は現在オープンベータ版として提供されており、すべての有料 Workers ユーザーが利用可能です。
詳細はドキュメントをご覧ください。ここでは概要を示します:
// LLM に以下のようなコードを生成させます。
let agentCode: string = `
export default {
async myAgent(param, env, ctx) {
// ...
}
}
`;
// エージェントがアクセスできるべき API を表す RPC スタブを取得します。(これは定義した任意の Workers RPC API で構いません。)
let chatRoomRpcStub = ...;
// ワーカーローダーバインディングを使用して、コードを実行するワーカーを読み込みます。
let worker = env.LOADER.load({
// コードを指定します。
compatibilityDate: "2026-03-01",
mainModule: "agent.js",
modules: { "agent.js": agentCode },
必ず JSON 形式で返してください。`translation フィールドのみ。他のフィールド (technical_terms 等)は一切追加しないこと — 余計なフィールドを書こうとして本文翻訳がトークン上限で打ち切られる事故を防ぐため:
{"translation": "翻訳全文"}
// エージェントにチャットルーム API のアクセス権を付与します。
env: { CHAT_ROOM: chatRoomRpcStub },
// インターネットへのアクセスをブロックします。(必要に応じてインターセプトも可能です。)
globalOutbound: null,
});
// エージェントコードによってエクスポートされた RPC メソッドを呼び出します。
await worker.getEntrypoint().myAgent(param);
これで完了です。
100 倍高速
Dynamic Workers は、8 年前のローンチ以来 Cloudflare Workers プラットフォーム全体が構築されてきた基盤となるサンドボックス化メカニズム、すなわち「isolates(アイソレート)」を同じく使用しています。アイソレートとは、Google Chrome で採用されている V8 JavaScript 実行エンジン(V8)のインスタンスです。これが Workers の動作原理となっています。
アイソレートの起動には数ミリ秒しか要さず、使用するメモリも数メガバイト程度です。これは一般的なコンテナと比較して約 100 倍高速であり、メモリ効率においても 10 倍から 100 倍の優位性があります。
つまり、ユーザーリクエストごとにオンデマンドで新しいアイソレートを起動し、コードスニペットを実行した後に破棄するという運用も可能です。
無制限のスケーラビリティ
コンテナベースのサンドボックスプロバイダーの多くは、グローバルな並列サンドボックス数やサンドボックス作成レートに制限を設けています。しかし Dynamic Worker Loader にはそのような制限がありません。それは単なる API に過ぎず、プラットフォーム全体を支えてきた同じ技術へのアクセス手段だからです。この技術により、Workers は常に毎秒数百万件のリクエストに対してシームレスにスケールできる能力を持っています。
1 秒あたり 100 万件のリクエストを処理し、それぞれが個別の Dynamic Worker サンドボックスを読み込み、すべてが並列で実行されるような状況でも、問題ありません!
Zero latency (ゼロレイテンシ)
単発の Dynamic Worker は通常、それを作成した Worker と同じマシン、場合によっては同じスレッド上で動作します。世界中に散らばったウォームなサンドボックスを探すために通信する必要はありません。Isolates(分離環境)は非常に軽量であるため、リクエストが着信した場所にそのまま実行できます。Dynamic Worker は、Cloudflare の世界中の数百か所のすべてのロケーションでサポートされています。
It's all JavaScript (すべて JavaScript)
コンテナとの唯一の違いは、エージェントが JavaScript を記述する必要がある点です。
技術的には、Workers(動的なものを含む)は Python や WebAssembly も使用できますが、エージェントによってオンデマンドで書かれるような小さなコードスニペットの場合、JavaScript の方が読み込みと実行がはるかに高速です。
私たち人間にはプログラミング言語に対する強い好みがありますが、多くの人が JavaScript を愛する一方で、Python、Rust、あるいは無数の他の言語を好む人もいます。
しかし、ここでは人間について話しているのではありません。AI について話しています。AI はあなたが望むあらゆる言語を書くことができます。LLM(大規模言語モデル)は主要なすべての言語における専門家です。JavaScript に関するトレーニングデータは膨大な量に及びます。
JavaScript は、そのウェブ上での性質上、サンドボックス化されるように設計されています。これがこのタスクに適した言語です。
Tools defined in TypeScript (TypeScript で定義されたツール)
エージェントに何でも有用なことを実行させるためには、外部 API と通信できる必要があります。そのエージェントがアクセス可能な API をどのようにして教えるのでしょうか?
MCP はフラットなツール呼び出しのスキーマを定義していますが、プログラミング API については定義していません。OpenAPI は REST API を表現する手段を提供しますが、スキーマ自体も、それを呼び出すために記述しなければならないコードも冗長です。
JavaScript に公開された API に対しては、明白で単一の答えがあります:TypeScript です。
エージェントは TypeScript を知っています。TypeScript は簡潔に設計されています。非常に少ないトークン数で、エージェントに API の正確な理解を与えることができます。
// チャットルームと対話するためのインターフェース。
interface ChatRoom {
// チャットログの最後の limit 件のメッセージを取得する。
getHistory(limit: number): Promise;
// 新しいメッセージを購読する。返されたオブジェクトを破棄すると購読解除される。
subscribe(callback: (msg: Message) => void): Promise;
// チャットにメッセージを投稿する。
post(text: string): Promise;
}
type Message = {
author: string;
time: Date;
text: string;
}
これを、等価な OpenAPI スペックと比較してください(非常に長いため、すべてを見るにはスクロールする必要があります):
openapi: 3.1.0
info:
description: >
チャットルームと対話するためのインターフェース。
version: 1.0.0
paths:
/messages:
get:
operationId: getHistory
summary: Get recent chat history
description: Returns the last limit messages from the chat log, newest first.
parameters:
- name: limit
in: query
required: true
schema:
type: integer
minimum: 1
responses:
"200":
description: A list of messages.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Message"
post:
operationId: postMessage
summary: Post a message to the chat room
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- text
properties:
text:
type: string
responses:
"204":
description: Message posted successfully.
必ず JSON 形式で返してください。translation フィールドのみ。他のフィールド (technical_terms 等) は一切追加しないこと — 余計なフィールドを書こうとして本文翻訳がトークン上限で打ち切られる事故を防ぐため:
{"translation": "翻訳全文"}
/messages/stream:
get:
operationId: subscribeMessages
summary: SSE を介した新着メッセージの購読
description: >
Server-Sent Events (SSE) ストリームを開きます。各イベントは 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 の方が優れていると考えています。トークン数が少なく、エージェントと人間の両者にとって理解しやすいからです。
Dynamic Worker Loader を使用すれば、このような TypeScript API を独自で実装した Worker に簡単に組み込み、メソッドパラメータとしてまたは env オブジェクトに渡して Dynamic Worker へ引き渡すことができます。Workers Runtime は自動的にサンドボックスとハーンチスコードの間に Cap'n Web RPC ブリッジを設定するため、エージェントはセキュリティ境界を越えて API を呼び出すことが可能になります。その際、ローカルライブラリを使用しているかのように認識されるため、境界を越えていることを意識することはありません。
つまり、エージェントは以下のようなコードを書くことができます:
// 思考:ユーザーはアリスの最近のチャットメッセージを要約するよう私に依頼しました。
// 私はコード内で最近のメッセージ履歴をフィルタリングし、関連するメッセージのみを読み込むようにします。
let history = await env.CHAT_ROOM.getHistory(1000);
return history.filter(msg => msg.author == "alice");
HTTP フィルタリングと認証情報の注入
もしエージェントに HTTP API を提供したい場合は、それも完全にサポートされています。ワーカーローダー API の globalOutbound オプションを使用すると、すべての HTTP リクエストに対して呼び出されるコールバックを登録できます。これにより、リクエストの検査、書き換え、認証キーの注入、直接応答、ブロック、またはその他好きな処理を行うことができます。
例えば、この機能を使用して認証情報の注入(トークン注入)を実装できます。エージェントが認可が必要なサービスに HTTP リクエストを送信する際、外部へ送信されるリクエストに認証情報を追加します。これにより、エージェント自体は秘密の認証情報を一切知らず、したがって漏洩することもありません。
エージェントがトレーニングセットに含まれるよく知られた API と対話している場合や、REST API を基盤としたライブラリを使用させたい場合は、単純な HTTP インターフェースを採用することが望ましい場合があります(このライブラリはエージェントのサンドボックス内で実行されます)。
その一方で、互換性の要件がない限り、TypeScript RPC インターフェースの方が HTTP より優れています:
上記に示す通り、HTTP インターフェースを記述するよりも、TypeScript インターフェースの方がはるかに少ないトークン数で済みます。
エージェントは、同等の HTTP リクエストと比較してはるかに少ないトークン数で、TypeScript インターフェースを呼び出すコードを記述できます。
TypeScript インターフェースを使用する場合、そもそも自分でラッパーインターフェースを定義するわけですから、シンプルさとセキュリティの両面から、エージェントに提供したい機能を正確に暴露するようにインターフェースを狭めることが容易になります。一方、HTTP の場合、既存の API に対して行われるリクエストのフィルタリングを実装することになる可能性が高く、これは困難です。なぜなら、プロキシは各 API 呼び出しの意味を完全に解釈して許可するかどうかを適切に判断する必要があり、HTTP リクエストにはヘッダーや他のパラメータが多く含まれており、それらすべてが意味を持つ可能性があるからです。結局のところ、許可したい関数だけを実装した TypeScript ラッパーを書く方が簡単になります。
戦闘経験豊富なセキュリティ
イソレートベースのサンドボックスを強化するのは難しい作業です。なぜなら、ハードウェア仮想マシンよりも攻撃対象領域が複雑だからです。すべてのサンドボックス化メカニズムにバグは存在しますが、V8 におけるセキュリティバグは、一般的なハイパーバイザーにおけるセキュリティバグよりも一般的です。おそらく悪意のあるコードをサンドボックス化する際にイソレートを使用する場合、追加の多層防御(ディフェンス・イン・デプス)レイヤーを持つことが重要です。例えば Google Chrome はこの理由から厳格なプロセス分離を実装していますが、それが唯一の解決策ではありません。
私たちは、アイソレートベースのプラットフォームを保護する経験がほぼ10年に及びます。私たちのシステムは、V8 のセキュリティパッチを生産環境に数時間以内に自動的に展開します——これは Chrome 自体よりも迅速です。当社のセキュリティアーキテクチャには、リスク評価に基づいてテナントを動的に分離するカスタム第2層サンドボックス(sandbox)が特徴として備わっています。また、MPK(Memory Protection Keys)のようなハードウェア機能を活用するために、V8 サンドボックスそのものを拡張しました。Spectre 攻撃に対する新たな防御策を開発するため、主要な研究者たちと提携し、彼らを雇用しています。さらに、コードをスキャンして悪意のあるパターンを検出し、自動的にブロックするか、追加のサンドボックス層を適用するシステムも備えています。その他にも多くの機能があります。
Cloudflare の Dynamic Workers を利用すれば、これらすべての機能が自動的に提供されます。
ヘルパーライブラリ
Dynamic Workers で作業する際に役立つと考えるいくつかのライブラリを構築しました:
コードモード
@cloudflare/codemode は、Dynamic Workers を使用して AI ツールに対してモデル生成されたコードを実行することを簡素化します。その核心には DynamicWorkerExecutor() があり、これは一般的なフォーマットエラーを処理するためのコード正規化と、サンドボックス内の fetch() 動作を制御するグローバルアウトバウンドフェッチャーへの直接アクセスを組み合わせた目的特化型のサンドボックスを構築します。これを null に設定すると完全な分離が実現され、Fetcher バインディングを渡すことで、サンドボックスからのアウトバウンドリクエストのルーティング、インターセプト、またはエンリッチメントが可能になります。
const executor = new DynamicWorkerExecutor({
loader: env.LOADER,
globalOutbound: null, // fully isolated
});
const codemode = createCodeTool({
tools: myTools,
executor,
});
return generateText({
model,
messages,
tools: { codemode },
});
The Code Mode SDK also provides two server-side utility functions. codeMcpServer({ server, executor }) wraps an existing MCP Server, replacing its tool surface with a single code() tool. openApiMcpServer({ spec, executor, request }) goes further: given an OpenAPI spec and an executor, it builds a complete MCP Server with search() and execute() tools as used by the Cloudflare MCP Server, and better suited to larger APIs.
In both cases, the code generated by the model runs inside Dynamic Workers, with calls to external services made over RPC bindings passed to the executor.
Learn more about the library and how to use it.
Bundling
Dynamic Workers expect pre-bundled modules. @cloudflare/worker-bundler handles that for you: give it source files and a package.json, and it resolves npm dependencies from the registry, bundles everything with esbuild, and returns the module map the Worker Loader expects.
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);
It also supports full-stack apps via createApp — bundle a server Worker, client-side JavaScript, and static assets together, with built-in asset serving that handles content types, ETags, and SPA routing.
Learn more about the library and how to use it.
File manipulation
@cloudflare/shell gives your agent a virtual filesystem inside a Dynamic Worker. Agent code calls typed methods on a state object — read, write, search, replace, diff, glob, JSON query/update, archive — with structured inputs and outputs instead of string parsing.
ストレージは永続的なワークスペース(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, // 任意の DO の SqlStorage、D1、またはカスタム SQL バックエンドと動作します
r2: this.env.MY_BUCKET, // 大規模ファイルは自動的に R2 にスプールされます
name: () => this.name // レイジー評価 — 必要になったときに解決され、構築時には解決されません
});
// コードはネットワークアクセスのない隔離されたワーカーサンドボックス内で実行されます
const executor = new DynamicWorkerExecutor({ loader: env.LOADER });
// LLM がこのコードを記述します。state.* の呼び出しは RPC を経由してホストへ戻されます
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 の型宣言とシステムプロンプトテンプレートも同梱されており、LLM コンテキストに完全な状態 API を数トークンで組み込むことができます。
ライブラリとその使用方法の詳細についてはこちらをご覧ください。
人々はどのようにこれを利用しているのでしょうか?
コードモード
開発者は、エージェントが一度に一つのツール呼び出しを行うのではなく、ツール API に対してコードを記述して実行することを望んでいます。Dynamic Workers を用いると、LLM は複数の API 呼び出しを連鎖させる単一の TypeScript 関数を生成し、それを Dynamic Worker で実行して最終結果をエージェントへ返します。その結果、コンテキストウィンドウに格納されるのは出力のみであり、各中間ステップは含まれません。これによりレイテンシとトークン使用量の両方が削減され、特にツール表面積が大きい場合に優れた結果が得られます。
当社の Cloudflare MCP サーバーもまさにこのように構築されています:エージェントが数百の個別のツール定義をナビゲートするのではなく、型付き API に対してコードを記述するため、検索と実行というたった二つのツールを通じて Cloudflare API の全体を 1,000 トークン未満で公開しています。
カスタム自動化の構築
開発者は Dynamic Workers を活用して、エージェントがその場でカスタム自動化を構築できるようにしています。例えば Zite は、ユーザーがチャットインターフェースを通じて対話するアプリプラットフォームを構築中ですが、LLM が裏側で TypeScript を記述して CRUD アプリを構築し、Stripe、Airtable、Google Calendar などのサービスに接続し、バックエンドロジックを実行します。ユーザーは一切コードを見ることはありません。すべての自動化は独自の Dynamic Worker で実行され、エンドポイントが必要とする特定のサービスとライブラリのみがアクセス可能です。
「Zite の LLM 生成アプリにサーバーサイドコードを有効化するためには、瞬時に起動し、隔離され、かつ安全な実行レイヤーが必要でした。Cloudflare の Dynamic Workers はこの三つの要件すべてに見事に合致し、速度とライブラリサポートの観点で他プラットフォームを凌駕するパフォーマンスを発揮しました。NodeJS 互換ランタイムは Zite のすべてのワークフローをサポートし、起動時間の犠牲なく数百ものサードパーティ製統合を実現しています。現在、Zite はミリ秒単位でサービスを提供しています
原文を表示
Last September we introduced Code Mode, the idea that agents should perform tasks not by making tool calls, but instead by writing code that calls APIs. We've shown that simply converting an MCP server into a TypeScript API can cut token usage by 81%. We demonstrated that Code Mode can also operate behind an MCP server instead of in front of it, creating the new Cloudflare MCP server that exposes the entire Cloudflare API with just two tools and under 1,000 tokens.
But if an agent (or an MCP server) is going to execute code generated on-the-fly by AI to perform tasks, that code needs to run somewhere, and that somewhere needs to be secure. You can't just eval() AI-generated code directly in your app: a malicious user could trivially prompt the AI to inject vulnerabilities.
You need a sandbox: a place to execute code that is isolated from your application and from the rest of the world, except for the specific capabilities the code is meant to access.
Sandboxing is a hot topic in the AI industry. For this task, most people are reaching for containers. Using a Linux-based container, you can start up any sort of code execution environment you want. Cloudflare even offers our container runtime and our Sandbox SDK for this purpose.
But containers are expensive and slow to start, taking hundreds of milliseconds to boot and hundreds of megabytes of memory to run. You probably need to keep them warm to avoid delays, and you may be tempted to reuse existing containers for multiple tasks, compromising the security.
If we want to support consumer-scale agents, where every end user has an agent (or many!) and every agent writes code, containers are not enough. We need something lighter.
And we have it.
Dynamic Worker Loader: a lean sandbox
Tucked into our Code Mode post in September was the announcement of a new, experimental feature: the Dynamic Worker Loader API. This API allows a Cloudflare Worker to instantiate a new Worker, in its own sandbox, with code specified at runtime, all on the fly.
Dynamic Worker Loader is now in open beta, available to all paid Workers users.
Read the docs for full details, but here's what it looks like:
// Have your LLM generate code like this.
let agentCode: string = `
export default {
async myAgent(param, env, ctx) {
// ...
}
}
`;
// Get RPC stubs representing APIs the agent should be able
// to access. (This can be any Workers RPC API you define.)
let chatRoomRpcStub = ...;
// Load a worker to run the code, using the worker loader
// binding.
let worker = env.LOADER.load({
// Specify the code.
compatibilityDate: "2026-03-01",
mainModule: "agent.js",
modules: { "agent.js": agentCode },
// Give agent access to the chat room API.
env: { CHAT_ROOM: chatRoomRpcStub },
// Block internet access. (You can also intercept it.)
globalOutbound: null,
});
// Call RPC methods exported by the agent code.
await worker.getEntrypoint().myAgent(param);
That's it.
100x faster
Dynamic Workers use the same underlying sandboxing mechanism that the entire Cloudflare Workers platform has been built on since its launch, eight years ago: isolates. An isolate is an instance of the V8 JavaScript execution engine, the same engine used by Google Chrome. They are how Workers work.
An isolate takes a few milliseconds to start and uses a few megabytes of memory. That's around 100x faster and 10x-100x more memory efficient than a typical container.
That means that if you want to start a new isolate for every user request, on-demand, to run one snippet of code, then throw it away, you can.
Unlimited scalability
Many container-based sandbox providers impose limits on global concurrent sandboxes and rate of sandbox creation. Dynamic Worker Loader has no such limits. It doesn't need to, because it is simply an API to the same technology that has powered our platform all along, which has always allowed Workers to seamlessly scale to millions of requests per second.
Want to handle a million requests per second, where every single request loads a separate Dynamic Worker sandbox, all running concurrently? No problem!
Zero latency
One-off Dynamic Workers usually run on the same machine — the same thread, even — as the Worker that created them. No need to communicate around the world to find a warm sandbox. Isolates are so lightweight that we can just run them wherever the request landed. Dynamic Workers are supported in every one of Cloudflare's hundreds of locations around the world.
It's all JavaScript
The only catch, vs. containers, is that your agent needs to write JavaScript.
Technically, Workers (including dynamic ones) can use Python and WebAssembly, but for small snippets of code — like that written on-demand by an agent — JavaScript will load and run much faster.
We humans tend to have strong preferences on programming languages, and while many love JavaScript, others might prefer Python, Rust, or countless others.
But we aren't talking about humans here. We're talking about AI. AI will write any language you want it to. LLMs are experts in every major language. Their training data in JavaScript is immense.
JavaScript, by its nature on the web, is designed to be sandboxed. It is the correct language for the job.
Tools defined in TypeScript
If we want our agent to be able to do anything useful, it needs to talk to external APIs. How do we tell it about the APIs it has access to?
MCP defines schemas for flat tool calls, but not programming APIs. OpenAPI offers a way to express REST APIs, but it is verbose, both in the schema itself and the code you'd have to write to call it.
For APIs exposed to JavaScript, there is a single, obvious answer: TypeScript.
Agents know TypeScript. TypeScript is designed to be concise. With very few tokens, you can give your agent a precise understanding of your API.
// Interface to interact with a chat room.
interface ChatRoom {
// Get the last limit messages of the chat log.
getHistory(limit: number): Promise<Message[]>;
// Subscribe to new messages. Dispose the returned object
// to unsubscribe.
subscribe(callback: (msg: Message) => void): Promise<Disposable>;
// Post a message to chat.
post(text: string): Promise<void>;
}
type Message = {
author: string;
time: Date;
text: string;
}
Compare this with the equivalent OpenAPI spec (which is so long you have to scroll to see it all):
openapi: 3.1.0
info:
title: ChatRoom API
description: >
Interface to interact with a chat room.
version: 1.0.0
paths:
/messages:
get:
operationId: getHistory
summary: Get recent chat history
description: Returns the last limit messages from the chat log, newest first.
parameters:
- name: limit
in: query
required: true
schema:
type: integer
minimum: 1
responses:
"200":
description: A list of messages.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Message"
post:
operationId: postMessage
summary: Post a message to the chat room
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- text
properties:
text:
type: string
responses:
"204":
description: Message posted successfully.
/messages/stream:
get:
operationId: subscribeMessages
summary: Subscribe to new messages via SSE
description: >
Opens a Server-Sent Events stream. Each event carries a JSON-encoded
Message object. The client unsubscribes by closing the connection.
responses:
"200":
description: An SSE stream of new messages.
content:
text/event-stream:
schema:
description: >
Each SSE data field contains a JSON-encoded Message object.
$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
We think the TypeScript API is better. It's fewer tokens and much easier to understand (for both agents and humans).
Dynamic Worker Loader makes it easy to implement a TypeScript API like this in your own Worker and then pass it in to the Dynamic Worker either as a method parameter or in the env object. The Workers Runtime will automatically set up a Cap'n Web RPC bridge between the sandbox and your harness code, so that the agent can invoke your API across the security boundary without ever realizing that it isn't using a local library.
That means your agent can write code like this:
// Thinking: The user asked me to summarize recent chat messages from Alice.
// I will filter the recent message history in code so that I only have to
// read the relevant messages.
let history = await env.CHAT_ROOM.getHistory(1000);
return history.filter(msg => msg.author == "alice");
HTTP filtering and credential injection
If you prefer to give your agents HTTP APIs, that's fully supported. Using the globalOutbound option to the worker loader API, you can register a callback to be invoked on every HTTP request, in which you can inspect the request, rewrite it, inject auth keys, respond to it directly, block it, or anything else you might like.
For example, you can use this to implement credential injection (token injection): When the agent makes an HTTP request to a service that requires authorization, you add credentials to the request on the way out. This way, the agent itself never knows the secret credentials, and therefore cannot leak them.
Using a plain HTTP interface may be desirable when an agent is talking to a well-known API that is in its training set, or when you want your agent to use a library that is built on a REST API (the library can run inside the agent's sandbox).
With that said, in the absence of a compatibility requirement, TypeScript RPC interfaces are better than HTTP:
As shown above, a TypeScript interface requires far fewer tokens to describe than an HTTP interface.
The agent can write code to call TypeScript interfaces using far fewer tokens than equivalent HTTP.
With TypeScript interfaces, since you are defining your own wrapper interface anyway, it is easier to narrow the interface to expose exactly the capabilities that you want to provide to your agent, both for simplicity and security. With HTTP, you are more likely implementing filtering of requests made against some existing API. This is hard, because your proxy must fully interpret the meaning of every API call in order to properly decide whether to allow it, and HTTP requests are complicated, with many headers and other parameters that could all be meaningful. It ends up being easier to just write a TypeScript wrapper that only implements the functions you want to allow.
Battle-hardened security
Hardening an isolate-based sandbox is tricky, as it is a more complicated attack surface than hardware virtual machines. Although all sandboxing mechanisms have bugs, security bugs in V8 are more common than security bugs in typical hypervisors. When using isolates to sandbox possibly-malicious code, it's important to have additional layers of defense-in-depth. Google Chrome, for example, implemented strict process isolation for this reason, but it is not the only possible solution.
We have nearly a decade of experience securing our isolate-based platform. Our systems automatically deploy V8 security patches to production within hours — faster than Chrome itself. Our security architecture features a custom second-layer sandbox with dynamic cordoning of tenants based on risk assessments. We've extended the V8 sandbox itself to leverage hardware features like MPK. We've teamed up with (and hired) leading researchers to develop novel defenses against Spectre. We also have systems that scan code for malicious patterns and automatically block them or apply additional layers of sandboxing. And much more.
When you use Dynamic Workers on Cloudflare, you get all of this automatically.
Helper libraries
We've built a number of libraries that you might find useful when working with Dynamic Workers:
Code Mode
@cloudflare/codemode simplifies running model-generated code against AI tools using Dynamic Workers. At its core is DynamicWorkerExecutor(), which constructs a purpose-built sandbox with code normalisation to handle common formatting errors, and direct access to a globalOutbound fetcher for controlling fetch() behaviour inside the sandbox — set it to null for full isolation, or pass a Fetcher binding to route, intercept or enrich outbound requests from the sandbox.
const executor = new DynamicWorkerExecutor({
loader: env.LOADER,
globalOutbound: null, // fully isolated
});
const codemode = createCodeTool({
tools: myTools,
executor,
});
return generateText({
model,
messages,
tools: { codemode },
});
The Code Mode SDK also provides two server-side utility functions. codeMcpServer({ server, executor }) wraps an existing MCP Server, replacing its tool surface with a single code() tool. openApiMcpServer({ spec, executor, request }) goes further: given an OpenAPI spec and an executor, it builds a complete MCP Server with search() and execute() tools as used by the Cloudflare MCP Server, and better suited to larger APIs.
In both cases, the code generated by the model runs inside Dynamic Workers, with calls to external services made over RPC bindings passed to the executor.
Learn more about the library and how to use it.
Bundling
Dynamic Workers expect pre-bundled modules. @cloudflare/worker-bundler handles that for you: give it source files and a package.json, and it resolves npm dependencies from the registry, bundles everything with esbuild, and returns the module map the Worker Loader expects.
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);
It also supports full-stack apps via createApp — bundle a server Worker, client-side JavaScript, and static assets together, with built-in asset serving that handles content types, ETags, and SPA routing.
Learn more about the library and how to use it.
File manipulation
@cloudflare/shell gives your agent a virtual filesystem inside a Dynamic Worker. Agent code calls typed methods on a state object — read, write, search, replace, diff, glob, JSON query/update, archive — with structured inputs and outputs instead of string parsing.
Storage is backed by a durable Workspace (SQLite + R2), so files persist across executions. Coarse operations like searchFiles, replaceInFiles, and planEdits minimize RPC round-trips — the agent issues one call instead of looping over individual files. Batch writes are transactional by default: if any write fails, earlier writes roll back automatically.
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, // Works with any DO's SqlStorage, D1, or custom SQL backend
r2: this.env.MY_BUCKET, // large files spill to R2 automatically
name: () => this.name // lazy — resolved when needed, not at construction
});
// Code runs in an isolated Worker sandbox with no network access
const executor = new DynamicWorkerExecutor({ loader: env.LOADER });
// The LLM writes this code; state.* calls dispatch back to the host via RPC
const result = await executor.execute(
`async () => {
// Search across all TypeScript files for a pattern
const hits = await state.searchFiles("src/**/*.ts", "answer");
// Plan multiple edits as a single transaction
const plan = await state.planEdits([
{ kind: "replace", path: "/src/app.ts",
search: "42", replacement: "43" },
{ kind: "writeJson", path: "/src/config.json",
value: { version: 2 } }
]);
// Apply atomically — rolls back on failure
return await state.applyEditPlan(plan);
}`,
[resolveProvider(stateTools(workspace))]
);
The package also ships prebuilt TypeScript type declarations and a system prompt template, so you can drop the full state API into your LLM context in a handful of tokens.
Learn more about the library and how to use it.
How are people using it?
Code Mode
Developers want their agents to write and execute code against tool APIs, rather than making sequential tool calls one at a time. With Dynamic Workers, the LLM generates a single TypeScript function that chains multiple API calls together, runs it in a Dynamic Worker, and returns the final result back to the agent. As a result, only the output, and not every intermediate step, ends up in the context window. This cuts both latency and token usage, and produces better results, especially when the tool surface is large.
Our own Cloudflare MCP server is built exactly this way: it exposes the entire Cloudflare API through just two tools — search and execute — in under 1,000 tokens, because the agent writes code against a typed API instead of navigating hundreds of individual tool definitions.
Building custom automations
Developers are using Dynamic Workers to let agents build custom automations on the fly. Zite, for example, is building an app platform where users interact through a chat interface — the LLM writes TypeScript behind the scenes to build CRUD apps, connect to services like Stripe, Airtable, and Google Calendar, and run backend logic, all without the user ever seeing a line of code. Every automation runs in its own Dynamic Worker, with access to only the specific services and libraries that the endpoint needs.
“To enable server-side code for Zite’s LLM-generated apps, we needed an execution layer that was instant, isolated, and secure. Cloudflare’s Dynamic Workers hit the mark on all three, and out-performed all of the other platforms we benchmarked for speed and library support. The NodeJS compatible runtime supported all of Zite’s workflows, allowing hundreds of third party integrations, without sacrificing on startup time. Zite now services milli
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み