はじめに:なぜAIエージェント専用のサンドボックスが必要か
AIエージェントが単なるチャットボットを超え、コードを書き、テストを実行し、開発サーバーを立ち上げる「仮想開発者」としての役割を担い始めています。しかし、そのようなエージェントに「安全な作業空間」を提供することは、想像以上に難しい問題です。
例えば、エージェントがGitHubリポジトリをクローンし、npm installを実行し、開発サーバーを起動する必要があるとします。従来はVMを立ち上げたりコンテナを直接管理する必要があり、以下のような課題に直面します。
- バースト性(Burstiness):セッションごとに個別のサンドボックスが必要で、リクエストが急増すると迅速に多数のインスタンスを生成しなければなりません。しかし、待機中のアイドルコンピューティングコストを支払いたくはありません。
- 高速な状態復元:各セッションが素早く起動し、以前の状態をそのまま引き継げる必要があります。
- セキュリティ:エージェントがサービスにアクセスできる必要がありますが、機密性の高い認証情報(credential)をエージェントに直接預けることはできません。
- 制御:サンドボックスのライフサイクル、コマンド実行、ファイル処理などをプログラムで簡単に制御できる必要があります。
- 人間工学(Ergonomics):人間とエージェントの両方が共通の操作を簡単に行えるシンプルなインターフェースが必要です。
Cloudflareはこれらの問題を解決するため、昨年6月にサンドボックスのベータ版をリリースし、今回**GA(General Availability)**を発表しました。Figmaなどのパートナーがすでにプロダクションで使用しており、今回のGAではセキュアな認証情報注入、PTYサポート、永続コードインタプリタ、スナップショットなど大幅に改善された機能を利用できます。

主要機能の解説
1. セキュアな認証情報注入(Secure Credential Injection)
エージェントワークロードで最も厄介な問題の1つは**認証(Authentication)**です。エージェントが内部サービスにアクセスする必要がある場合でも、生の認証情報をエージェントに完全に信頼させることはできません。
Cloudflare SandboxesはプログラマブルなEgressプロキシを介してネットワークレベルで認証情報を注入します。エージェントは決して認証情報にアクセスできず、認証ロジックを完全にカスタマイズできます。
class OpenCodeInABox extends Sandbox {
static outboundByHost = {
"my-internal-vcs.dev": (request, env, ctx) => {
const headersWithAuth = new Headers(request.headers);
headersWithAuth.set("x-auth-token", env.SECRET);
return fetch(request, { headers: headersWithAuth });
}
}
}
2. 本物のターミナル(PTYサポート)
初期のエージェントシステムはシェルアクセスをリクエスト-レスポンスのループとしてモデル化していました。コマンド実行 → 出力収集 → プロンプトに再投入。この方法は機能しますが、開発者が実際にターミナルを使う方法とはかけ離れています。
Cloudflareは**PTY(Pseudo-Terminal)**をWebSocket経由でプロキシし、xterm.jsと互換性のあるリアルタイムターミナルセッションを提供します。
// Worker: WebSocket接続をライブターミナルセッションにアップグレード
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
if (url.pathname === "/terminal") {
const sandbox = getSandbox(env.Sandbox, "my-session");
return sandbox.terminal(request, { cols: 80, rows: 24 });
}
return new Response("Not found", { status: 404 });
},
};
// ブラウザ: xterm.jsをサンドボックスシェルに接続
import { Terminal } from "xterm";
import { SandboxAddon } from "@cloudflare/sandbox/xterm";
const term = new Terminal();
const addon = new SandboxAddon({
getWebSocketUrl: ({ origin }) => `${origin}/terminal`,
});
term.loadAddon(addon);
term.open(document.getElementById("terminal-container")!);
addon.connect({ sandboxId: "my-session" });
3. 永続コードインタプリタ(Persistent Code Interpreter)
データ分析、スクリプティング、探索的ワークフローのために永続的なコード実行コンテキストを提供します。キーワードは「永続的(Persistent)」です。一般的なコードインタプリタは各スニペットを隔離された状態で実行するため、呼び出し間で状態が失われます。
SandboxesはJupyterノートブックのように、変数やインポートが呼び出し間で維持されるコンテキストを作成できます。
// Pythonコンテキストを作成。状態は有効期間中保持されます。
const ctx = await sandbox.createCodeContext({ language: "python" });
// 最初の実行:データの読み込み
await sandbox.runCode(`
import pandas as pd
df = pd.read_csv('/workspace/sales.csv')
df['margin'] = (df['revenue'] - df['cost']) / df['revenue']
`, { context: ctx });
// 2回目の実行:dfはまだ存在
const result = await sandbox.runCode(`
df.groupby('region')['margin'].mean().sort_values(ascending=False)
`, { context: ctx, onStdout: (line) => console.log(line.text) });
4. バックグラウンドプロセスとライブプレビューURL
エージェントが開発サーバーを起動し、準備状態を確認し、ユーザーにライブリンクを共有できます。
// バックグラウンドプロセスでdevサーバーを起動
const server = await sandbox.startProcess("npm run dev", {
cwd: "/workspace",
});
// サーバーが実際に準備できるまで待機 — 単なるsleepはしない
await server.waitForLog(/Local:.*localhost:(\d+)/);
// 実行中のサービスを公開URLとして公開
const { url } = await sandbox.exposePort(3000);
console.log(`Preview: ${url}`);
5. ファイルシステム監視(Filesystem Watching)
sandbox.watch()はLinuxカーネルメカニズムinotifyをベースにしたSSE(Server-Sent Events)ストリームを返します。エージェントがファイル変更をリアルタイムで検出し、即座にリビルドやテスト再実行をトリガーできます。
import { parseSSEStream, type FileWatchSSEEvent } from '@cloudflare/sandbox';
const stream = await sandbox.watch('/workspace/src', {
recursive: true,
include: ['*.ts', '*.tsx']
});
for await (const event of parseSSEStream(stream)) {
if (event.type === 'modify' && event.path.endsWith('.ts')) {
await sandbox.exec('npx tsc --noEmit', { cwd: '/workspace' });
}
}
6. スナップショット(Snapshots)— 近日公開予定
開発者がノートPCを開け閉めするように、エージェントも作業を中断して再開できる必要があります。スナップショットはコンテナのディスク全体の状態(OS設定、インストール済み依存関係、変更されたファイル、データファイルなど)を保存し、後で迅速に復元できるようにします。
class AgentDevEnvironment extends Sandbox {
sleepAfter = "5m";
persistAcrossSessions = {type: "disk"}; // 個別ディレクトリ指定も可能
}
プログラムでスナップショットを作成し、手動で復元することも可能です。チェックポイント作成やセッションのフォークに役立ちます。
async function forkDevEnvironment(baseId: string, numberOfForks: number) {
const baseInstance = await getSandbox(baseId);
const snapshotId = await baseInstance.snapshot();
const forks = Array.from({ length: numberOfForks }, async (_, i) => {
const newInstance = await getSandbox(`${baseId}-fork-${i}`);
return newInstance.start({ snapshot: snapshotId });
});
await Promise.all(forks);
}
スナップショットはアカウント内のR2に保存され、耐久性とロケーション非依存性を保証します。R2の階層型キャッシングシステムにより、世界中のすべてのリージョン(Region: Earth)で高速復元が可能です。
補足: スナップショット正式リリース前でも、
backupとrestoreメソッドを使用できます。これらのメソッドもR2を使ってディレクトリを保存・復元しますが、真のVMレベルスナップショットほどのパフォーマンスはありません。それでも、'axios'リポジトリをクローンしてnpm installするのに30秒かかっていた作業が、バックアップ復元で2秒で完了します。

料金体系と制限の変更(Active CPU Pricing)
GAに伴い、料金体系も変更されました。**アクティブCPUサイクル(Active CPU cycles)**に対してのみ課金されます。つまり、エージェントがLLMの応答を待っているアイドル状態のCPU時間には課金されません。
| インスタンスタイプ | 同時実行可能インスタンス数(標準プラン) |
|---|---|
| Lite | 15,000 |
| Basic | 6,000 |
| 大規模インスタンス | 1,000+ |
さらに多くのインスタンスが必要な場合は、Cloudflareに問い合わせてください。
この技術の限界と注意点
- スナップショットのメモリ状態非保存(現在):現在のスナップショットはディスク状態のみを保存し、実行中のプロセスのメモリ状態は保存されません。近日中にライブメモリスナップショットがリリースされる予定ですが、それまではスナップショット後に実行中のプロセスは再起動されます。
- ネットワーク依存:すべてのサンドボックスはCloudflareネットワークを介して通信するため、インターネット接続が不安定な環境ではパフォーマンスが低下する可能性があります。
- コスト管理:Active CPU Pricingはアイドル時間のコストを削減しますが、同時実行インスタンスが多い場合は全体のコストを監視する必要があります。

まとめ:エージェントも本物の開発環境を使う時代へ
9ヶ月前、Cloudflareはコマンド実行とファイルシステムアクセスが可能な基本的なサンドボックスをリリースしました。現在のサンドボックスはまったく異なるレベルです。
- ブラウザに接続できるターミナル
- 状態が維持されるコードインタプリタ
- ライブプレビューURLを提供するバックグラウンドプロセス
- リアルタイムファイル変更イベントを発行するファイルシステム
- 安全な認証情報注入のためのEgressプロキシ
- ほぼ瞬時のウォームスタートを可能にするスナップショット
これらの機能が組み合わさることで、エージェントが実際のエンジニアリング作業を行うフィードバックループが完成します。リポジトリクローン → インストール → テスト実行 → 失敗を読む → コード修正 → テスト再実行。人間の開発者を効果的にするタイトなフィードバックループを、今やエージェントも享受できるのです。
SDKは現在v0.8.9で、以下のコマンドですぐに始められます。
npm i @cloudflare/sandbox@latest
合わせて読みたい記事
本記事はCloudflare公式ブログの内容を基に、日本の開発者コミュニティ向けに再構成しました。