数週間前、当社はWorkersプラットフォームの新機能であるDynamic Workersを発表しました。これは、Workerコードをセキュアなサンドボックスにオンザフライで読み込むことができるものです。Dynamic Worker Loader APIは、Workersが基盤としてきた基本的なコンピューティング分離プリミティブ、すなわちコンテナではなくIsolateへの直接アクセスを提供します。Isolateはコンテナよりはるかに軽量なうえメモリの使用は1/10ですが、100倍速く読み込み可能です。非常に効率的で、「使い捨て」として扱うことができます。数行のコードを実行するために起動し、その後破棄するのです。eval()のセキュアバージョンのようなものだと言えるでしょう。
Dynamic Workersには多くの用途があります。最初の発表では、ツール呼び出しの代替として、AIエージェント生成コードをどのように実行するかに焦点を当てていました。このユースケースでは、AIエージェントは数行のコードを記述して実行することにより、ユーザーのリクエストに応じてアクションを実行します。このコードは使い捨てで、1つのタスクを1回実行することを目的としており、実行後すぐに破棄されます。
しかし、AIにより持続的なコードを生成させたい場合はどうでしょうか?AIに、ユーザーが対話できるカスタムUIで小さなアプリケーションを構築させたい場合はどうですか?そのアプリケーションの状態を長期間維持させる場合はどうすればよいでしょうか?もちろん、セキュアなサンドボックスで実行したいでしょう。
それを実現する方法の1つが、Dynamic Workersを使用し、単純にストレージにアクセスできるRPC APIをWorkerに提供することです。バインディングを使用すれば、Dynamic WorkerにリモートSQLデータベース(おそらくCloudflare D1がサポートするもの、またはHyperdriveを介してアクセスするPostgresデータベースという選択肢があります)を指すAPIを与えることができます。
しかし、Workersには、このユースケースに最適な、他に類を見ない、極めて高速なストレージタイプであるDurable Objectsがあります。Durable Objectは、一意の名称を持つ特殊なWorkerで、名称ごとにグローバルに1つのインスタンスを持つことができます。このインスタンスには、Durable Objectが実行されるマシンのローカルディスク上に存在するSQLiteデータベースが接続されています。これにより、ストレージへのアクセスが驚くほど高速になり遅延は事実上ゼロです。
理想としては、AIにDurable Objectのコードを書かせ、そのコードをDynamic Workerで実行させたいということでしょう。
これは厄介な問題を引き起こします。Durable Objects使用時の必要事項は、以下の通りです。
DurableObjectを拡張するクラスの記述。
Workerのメインモジュールからのエクスポート。
Wrangler設定で、このクラスにストレージをプロビジョニングするよう指定。これにより、着信リクエストを処理するクラスを指すDurable Object名前空間が作成される。
自身の名前空間を指す Durable Object 名前空間バインディングを宣言し(または ctx.exports を使用し)、これを使用してDurable Objectへのリクエストを実行。
これは、Dynamic Workersには自然には拡張されません。まず、明らかな問題があります。コードが動的であるということです。Cloudflare APIをまったく呼び出さずに実行できます。ただし、Durable ObjectストレージはAPIを介してプロビジョニングする必要があり、名前空間は実装クラスを指している必要があります。Dynamic Workerを指すことはできません。
しかし、さらに深い問題があります。何らかの方法でDurable Objectの名前空間を設定してDynamic Workerを直接指すようにすることができたとしても、それを行いたいと思うでしょうか?エージェント(またはユーザー)がDurable Objectsで完全な名前空間全体を作成できるようにしたいですか?世界中に広がる無制限のストレージを使用したいですか?
おそらく、そうではないでしょう。何らかの制御が必要になるでしょう。作成するオブジェクトの数を制限または少なくとも追跡したい場合もあるでしょう。1つのオブジェクトだけに制限したいかもしれません(バイブコード化された個人アプリには十分かもしれません)。ロギングや可観測性の追加が必要になるかもしれません。指標。課金。その他。
実際、これらすべてを行うために望むのは、これらのDurable Objectsへのリクエストが、自分自身のコードにまず送られることであり、そこで「ロジスティクス」をすべて行い、その後エージェントのコードにリクエストを転送することです。すべてのDurable Objectの一部として実行されるスーパーバイザーの作成をお望みだと思います。
ソリューション:Durable Object Facets
本日、この問題を解決する機能をオープンベータ版でリリースします。
Durable Object Facetsを使用すると、Durable Objectクラスを動的に読み込んでインスタンス化し、ストレージに使用するSQLiteデータベースの提供が可能になります。Facetsでできること:
まず、通常のDurable Object名前空間を作成し、ご自身で作成するクラスを指定します。
そのクラスでは、エージェントのコードをDynamic Workerとして読み込み、呼び出します。
Dynamic Workerのコードは、Durable Objectクラスを直接実装できます。つまり、extends DurableObjectとして宣言されたクラスを文字通りエクスポートします。
そのクラスを、自分のDurable Objectの「ファセット」としてインスタンス化しています。
このファセットは独自のSQLiteデータベースを取得し、通常のDurable ObjectストレージAPIを介して使用することができます。そしてこのデータベースは、スーパーバイザーのデータベースとは別のものですが、この2つは同じ全体的なDurable Objectの一部として一緒に保存されます。
以下は、Durable Objectクラスを動的に読み込んで実行するアプリプラットフォームのシンプルで完全な実装です。
import { DurableObject } from "cloudflare:workers";
// For the purpose of this example, we'll use this static
// application code, but in the real world this might be generated
// by AI (or even, perhaps, a human user).
const AGENT_CODE = `
import { DurableObject } from "cloudflare:workers";
// Simple app that remembers how many times it has been invoked
// and returns it.
export class App extends DurableObject {
fetch(request) {
// We use storage.kv here for simplicity, but storage.sql is
// also available. Both are backed by SQLite.
let counter = this.ctx.storage.kv.get("counter") || 0;
++counter;
this.ctx.storage.kv.put("counter", counter);
return new Response("You've made " + counter + " requests.\\n");
}
}
`;
// AppRunner is a Durable Object you write that is responsible for
// dynamically loading applications and delivering requests to them.
// Each instance of AppRunner contains a different app.
export class AppRunner extends DurableObject {
async fetch(request) {
// We've received an HTTP request, which we want to forward into
// the app.
// The app itself runs as a child facet named "app". One Durable
// Object can have any number of facets (subject to storage limits)
// with different names, but in this case we have only one. Call
// this.ctx.facets.get() to get a stub pointing to it.
let facet = this.ctx.facets.get("app", async () => {
// If this callback is called, it means the facet hasn't
// started yet (or has hibernated). In this callback, we can
// tell the system what code we want it to load.
// Load the Dynamic Worker.
let worker = this.#loadDynamicWorker();
// Get the exported class we're interested in.
let appClass = worker.getDurableObjectClass("App");
return { class: appClass };
});
// Forward request to the facet.
// (Alternatively, you could call RPC methods here.)
return await facet.fetch(request);
}
// RPC method that a client can call to set the dynamic code
// for this app.
setCode(code) {
// Store the code in the AppRunner's SQLite storage.
// Each unique code must have a unique ID to pass to the
// Dynamic Worker Loader API, so we generate one randomly.
this.ctx.storage.kv.put("codeId", crypto.randomUUID());
this.ctx.storage.kv.put("code", code);
}
#loadDynamicWorker() {
// Use the Dynamic Worker Loader API like normal. Use get()
// rather than load() since we may load the same Worker many
// times.
let codeId = this.ctx.storage.kv.get("codeId");
return this.env.LOADER.get(codeId, async () => {
// This Worker hasn't been loaded yet. Load its code from
// our own storage.
let code = this.ctx.storage.kv.get("code");
return {
compatibilityDate: "2026-04-01",
mainModule: "worker.js",
modules: { "worker.js": code },
globalOutbound: null, // block network access
}
});
}
}
// This is a simple Workers HTTP handler that uses AppRunner.
export default {
async fetch(req, env, ctx) {
// Get the instance of AppRunner named "my-app".
// (Each name has exactly one Durable Object instance in the
// world.)
let obj = ctx.exports.AppRunner.getByName("my-app");
// Initialize it with code. (In a real use case, you'd only
// want to call this once, not on every request.)
await obj.setCode(AGENT_CODE);
// Forward the request to it.
return await obj.fetch(req);
}
}
例:
AppRunnerは、プラットフォーム開発者(あなた)によって書かれた「通常の」Durable Objectです。
AppRunnerの各インスタンスは、1つのアプリケーションを管理します。アプリのコードを保存し、オンデマンドで読み込みます。
アプリケーション自体はDurable Objectクラスを実装およびエクスポートします。プラットフォームは、そのクラスがAppという名称であることを想定しています。
AppRunnerは、Dynamic Workersを使用してアプリケーションコードを読み込み、Durable Object Facetとしてコードを実行します。
AppRunnerの各インスタンスは、2つのSQLiteデータベースで構成される1つのDurable Objectです。1つは親(AppRunner自体)に属し、もう1つはファセット(App)に属します。これらのデータベースは分離されています。アプリケーションは、AppRunnerのデータベースを読み取ることができず、それ自身のデータベースのみを読み取ることができます。
例を実行するには、上記のコードをworker.jsファイルにコピーし、以下のwrangler.jsoncと組み合わせます。そして、npx wrangler dev を使用してローカルで実行します。
// wrangler.jsonc for the above sample worker.
{
"compatibility_date": "2026-04-01",
"main": "worker.js",
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": [
"AppRunner"
]
}
],
"worker_loaders": [
{
"binding": "LOADER",
},
],
}
FacetsはDynamic Workersの機能の1つであり、Workers有料プランのユーザーにすぐにベータ版でご利用いただけます。
Dynamic WorkersとFacetsの詳細については、ドキュメントをご覧ください。