概要
2025年9月に公開した「Code Mode」では、エージェントがツール呼び出しを行うのではなく、APIを呼び出すコードを書くことでタスクを実行するというアイデアを紹介しました。MCPサーバーをTypeScript APIに変換するだけでトークン使用量を81%削減できることを示しました。Code ModeはMCPサーバーの前ではなく背後で動作させることもでき、Cloudflare MCPサーバーではわずか2つのツール、1,000トークン未満でCloudflare API全体を公開できることを実証しました。
しかし、もしエージェント(またはMCPサーバー)がAIによってオン・ザ・フライで生成されたコードを実行してタスクを行うなら、そのコードはどこかで実行される必要があり、その場所は安全でなければなりません。AI生成コードをアプリで直接eval()するわけにはいきません。悪意のあるユーザーは簡単にAIに脆弱性を注入させることができます。必要なのはサンドボックスです:アプリケーションや外部環境から隔離され、コードに与えたい特定の能力だけを許可する実行場所です。
サンドボックス化はAI業界で重要なテーマです。多くの人はコンテナに頼っています。Linuxベースのコンテナを使えば、任意のコード実行環境を立ち上げられます(Cloudflareもこの用途のためにコンテナランタイムやSandbox SDKを提供しています)。しかしコンテナは遅く高価で、起動に数百ミリ秒、メモリは数百MBを消費します。遅延を避けるためにウォームスタートを維持する必要があり、複数タスクでコンテナを再利用するとセキュリティを損なう恐れがあります。消費者規模のエージェント(各エンドユーザーがエージェントを持ち、エージェントがコードを書くようなケース)をサポートするには、コンテナだけでは不十分です。より軽量な仕組みが必要です。
Dynamic Worker Loader:軽量なサンドボックス
昨年のCode Modeの記事の中で、実験的機能として「Dynamic Worker Loader API」を発表しました。このAPIにより、Cloudflare Workerがランタイム時に指定されたコードで新しいWorkerを、そのWorkerごとのサンドボックス内にオンザフライでインスタンス化できます。Dynamic Worker Loaderは現在オープンベータで、すべての有料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);
100x高速
Dynamic Workersは、Cloudflare Workersプラットフォームがローンチ以来採用してきた同じサンドボックス機構、すなわち「isolate」を利用しています。isolateはV8 JavaScript実行エンジンのインスタンスで、Google Chromeが使用するのと同じエンジンです。isolateは起動に数ミリ秒、メモリは数メガバイトしか使いません。これは典型的なコンテナに比べておおよそ100x高速で、メモリ効率は10x–100xに相当します。したがって、各ユーザーリクエストごとにオンデマンドで新しいisolateを起動し、1スニペットを実行して破棄する、という運用が現実的になります。
無制限のスケーラビリティ
多くのコンテナベースのサンドボックスプロバイダは、同時グローバルサンドボックス数やサンドボックス作成レートに制限を設けます。Dynamic Worker Loaderにはそのような制限がありません。というのも、このAPIは私たちのプラットフォームをこれまで支えてきた技術(Workersが何百万リクエスト/秒にシームレスにスケールできる仕組み)そのものへのインターフェースだからです。もし毎リクエストごとに別々のDynamic Workerサンドボックスを同時に実行して1秒間に100万リクエストを処理したい場合でも問題ありません。
ゼロレイテンシ
ワンオフのDynamic Workerは通常、作成元のWorkerと同じマシン、同じスレッドで動作します。ウォームサンドボックスを探して遠隔地と通信する必要はありません。isolateは非常に軽量なので、リクエストが到着した場所でそのまま実行できます。Dynamic WorkersはCloudflareの数百あるロケーションすべてでサポートされています。
全てJavaScript
コンテナと比べた唯一の制約は、エージェントがJavaScriptを書く必要がある点です。技術的にはWorkers(Dynamic Workersを含む)はPythonやWebAssemblyも利用できますが、エージェントがオンデマンドで書くような短いスニペットではJavaScriptの読み込みと実行が遥かに高速です。
人間は言語に強い好みを持ちますが、AI相手なら話は別です。LLMは主要言語すべてに精通しており、JavaScriptの学習データは膨大です。Webにおける性質上、JavaScriptはサンドボックス化が前提の言語であり、この用途に最適です。
TypeScriptで定義されたツール
エージェントに外部APIを呼ばせるには、どのAPIにアクセスできるかを伝える必要があります。MCPはフラットなツール呼び出しのスキーマを定義しますが、プログラミングAPIの表現には向いていません。OpenAPIはREST APIを表現する手段を提供しますが、スキーマ自体も呼び出し用コードも冗長になりがちです。JavaScript向けのAPIを公開する場合、明白な解はTypeScriptです。エージェントはTypeScriptを理解しますし、TypeScriptは簡潔にAPIの意味を伝えられます。
次はチャットルームと対話するためのTypeScriptインターフェースの例です:
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: >
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
私たちはTypeScript APIの方が優れていると考えています。トークン数が少なくて済み、理解しやすい(エージェント側にも人間側にも)からです。Dynamic Worker Loaderを使えば、自分のWorker内でこのようなTypeScript APIを実装し、メソッド引数または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インターフェースをエージェントに与えることも完全にサポートされています。worker loader APIのglobalOutboundオプションを使うと、外向きHTTPリクエストごとに呼び出されるコールバックを登録できます。そのコールバック内でリクエストを検査・書き換え・認証情報注入・直接応答・ブロックなどを行えます。
例えば、エージェントが認証が必要なサービスにHTTPリクエストを送る際に、送信側で資格情報を追加(トークン注入)することができます。こうすれば、エージェント自身は秘密情報を知ることがなく、漏洩のリスクを低減できます。
ただし、互換性が要求されない限り、TypeScript RPCインターフェースはHTTPより優れています:
- TypeScriptインターフェースはHTTPインターフェースよりも少ないトークンで記述できる。
- エージェントがTypeScriptインターフェースを呼ぶコードは、同等のHTTP呼び出しに比べてはるかに少ないトークンで書ける。
- TypeScriptインターフェースでは、公開する機能を狭く限定できるため、単純さとセキュリティの面で有利。
HTTP経由だと、既存APIに対するリクエストをフィルタリングする必要が生じやすく、各APIコールの意味を完全に解釈して許可するか判断するのは難しいです。結果として、必要な関数のみを実装したTypeScriptラッパーを書く方が簡単です。
堅牢なセキュリティ
isolateベースのサンドボックスを堅牢化するのは難しく、ハードウェア仮想マシンよりも攻撃対象が複雑です。すべてのサンドボックス機構にバグは存在しますが、V8におけるセキュリティバグは一般的なハイパーバイザのバグより頻繁に見つかる傾向があります。isolateを使って悪意ある可能性のあるコードを分離実行する場合、追加の多層防御が重要です。
Google Chromeはこの理由で厳格なプロセス分離を導入していますが、それが唯一の解決策ではありません。私たちはisolateベースのプラットフォームをほぼ10年にわたって運用・保護してきました。システムはV8のセキュリティパッチを数時間以内に本番へ自動展開します—これはChrome自体より速い場合もあります。セキュリティアーキテクチャには、リスク評価に基づく動的テナント隔離を行うカスタムな第2層サンドボックスを採用しており、V8サンドボックスを拡張してMPKなどのハードウェア機能を活用しています。Spectreに対する新しい防御策を開発するために外部研究者と協力・採用も行っています。さらに、コードを走査して悪意あるパターンを検出し、自動的にブロックしたり追加のサンドボックス化を適用するシステム等、多数の防御が組み合わされています。
Dynamic WorkersをCloudflare上で使えば、これらすべてが自動的に適用されます。
ヘルパーライブラリ
ヘルパーライブラリをいくつか構築しました。
(ここでの記事は原文の掲載箇所で切れています。)