Dynamic Worker の中で Workflow を実行すると、実行時に読み込んだコードに耐久実行を付けられます。Workflow の各ステップは障害を乗り越え、数時間または数日スリープでき、外部イベントを待てます。isolate がステップ間でリサイクルされても、中断した場所から正確に再開します。
Dynamic Workers はオンデマンドで作成されるため、各 Workflow を事前登録したり、個別に管理したりする必要はありません。必要なときにコードを読み込めば、Workflows エンジンが永続化とリトライを裏側で処理します。一度きりの実行にも、長時間かかる複数ステップの処理にも同じように使えます。
たとえば、次のような用途が考えられます。
- SaaS プラットフォームで、テナントごとにオンボーディング、承認チェーン、請求のリトライなど独自の自動化を定義し、顧客ごとに別の Workflow をデプロイせずに耐久実行したい。
- AI エージェントフレームワークで、エージェントが実行時に複数ステップの計画を生成・実行し、再起動を乗り越え、ツール呼び出しのあいだスリープし、人の承認を待つ必要がある。
- マルチテナントのジョブシステムで、顧客ごとにデータ変換、webhook チェーン、スケジュールタスクなどの処理ロジックを提出し、自前のオーケストレーターを作らずに各ステップの進捗を永続化し、失敗時にリトライしたい。
@cloudflare/dynamic-workflows ライブラリは、Worker Loader を Workflows エンジンに接続します。配線を自分で実装しなくても、各 Dynamic Worker が耐久ステップ(step.do()、step.sleep()、step.waitForEvent())を使えます。
このガイドでは、@cloudflare/dynamic-workflows ライブラリを使い、Worker Loader を用意し、耐久ステップ付きの Dynamic Worker を書き、Workflow インスタンスを起動します。
この構成は 3 つの部分からなります。
- Worker Loader: デプロイするメインの Worker です。リクエストを受け取り、読み込む Dynamic Worker を決め、Workflow インスタンスを作成します。このコードは自分で書きます。
- Dynamic Worker: Workflow が実際に行うこと(ステップ、スリープ、イベント待ち)を定義する、テナントごとのコードです。各 Dynamic Worker は実行時にオンデマンドで読み込まれます。
- DynamicWorkflow クラス: ライブラリが作る Workflow のエントリポイントです。Workflows エンジンがステップを実行する必要があるとき、このクラスはそのインスタンス向けの正しい Dynamic Worker を読み込み、その中でステップを実行します。
連携の流れは次のとおりです。
- Worker Loader がリクエストを受け取り、テナントの Dynamic Worker を読み込み、テナント ID でタグ付けした Workflow バインディングを渡します。
- Dynamic Worker が
env.WORKFLOWS.create()を呼び出し、新しい Workflow インスタンスを開始します。テナント ID はインスタンスに自動で保存されます。 - Workflows エンジンは、Dynamic Worker で定義したステップ(
step.do()、step.waitForEvent()、step.sleep())を実行します。各ステップは耐久的です。結果は永続化され、成功したあとは再実行されません。 - isolate がステップ間でリサイクルされた場合(スリープ中やイベント待ちなど)、エンジンはインスタンスからテナント ID を読み戻し、Worker Loader 経由で同じ Dynamic Worker を再読み込みし、中断した場所から再開します。
ライブラリは、Worker Loader と Workflows エンジンをつなぐ 2 つの関数を提供します。リクエストへのタグ付け、ペイロードの解析、自前の WorkflowEntrypoint サブクラス作成は不要です。
wrapWorkflowBinding:{ tenantId }のようなメタデータでタグ付けした Workflow バインディングを作り、Dynamic Worker に渡します。ライブラリはそのメタデータを、Dynamic Worker が作るすべてのインスタンスに付けます。エンジンは各インスタンスを正しいテナントに戻せます。createDynamicWorkflowEntrypoint: エンジンが再開するときに正しい Dynamic Worker を再読み込みする DynamicWorkflow クラスを作ります。メタデータを受け取りテナントの Workflow クラスを返すコールバックを渡し、ステップの実行が必要になるたびにライブラリがそのコールバックを呼びます。
ライブラリが Worker Loader と Workflows エンジンの配線を担当します。リクエストへのタグ付け、ペイロードの解析、自前の WorkflowEntrypoint サブクラス作成は不要です。
npm i @cloudflare/dynamic-workflowsyarn add @cloudflare/dynamic-workflowspnpm add @cloudflare/dynamic-workflowsbun add @cloudflare/dynamic-workflowsWorker Loader には 2 つの バインディング が必要です。
- 実行時に Dynamic Workers を読み込む Worker Loader バインディング(
LOADER)。 DynamicWorkflowクラスを指す Workflow バインディング(WORKFLOWS)。Workflows エンジンが各インスタンスを正しい Dynamic Worker へルーティングするためのエントリポイントです。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-worker-loader",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-09-20",
"worker_loaders": [
{
"binding": "LOADER"
}
],
"workflows": [
{
"name": "dynamic-workflow",
"binding": "WORKFLOWS",
"class_name": "DynamicWorkflow"
}
]
}name = "my-worker-loader"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-09-20"
[[worker_loaders]]
binding = "LOADER"
[[workflows]]
name = "dynamic-workflow"
binding = "WORKFLOWS"
class_name = "DynamicWorkflow"Worker Loader で、Dynamic Workers を Workflows エンジンに接続します。このファイルでは次を定義します。
-
テナントのコードの読み込み方: テナント ID を受け取り、そのコードを取得し、Workflow バインディングを渡す関数です。バインディングは
wrapWorkflowBindingで作ります。すべての Workflow インスタンスにテナント ID をタグ付けし、あとでエンジンが正しいコードへ戻れるようにします。 -
エンジンが Workflow を再開する方法:
createDynamicWorkflowEntrypointを使い、ステップ実行が必要になるたびにエンジンが呼ぶコールバックを定義します。コールバックはインスタンスのメタデータからテナント ID を受け取り、そのテナントの Workflow クラスを返します。isolate 再起動をまたいだ耐久実行は、この仕組みで成り立ちます。エンジンは正しいコードの再読み込み方を知っています。
import {
createDynamicWorkflowEntrypoint,
DynamicWorkflowBinding,
wrapWorkflowBinding,
} from "@cloudflare/dynamic-workflows";
// Required: re-exporting puts the class on cloudflare:workers exports,
// which is how wrapWorkflowBinding builds per-tenant RPC stubs.
export { DynamicWorkflowBinding };
function loadTenant(env, tenantId) {
return env.LOADER.get(tenantId, async () => ({
compatibilityDate: "2026-01-01",
mainModule: "index.js",
modules: { "index.js": await fetchTenantCode(tenantId) },
// The Dynamic Worker uses this exactly like a real Workflow binding;
// every create() is tagged with { tenantId } automatically.
env: { WORKFLOWS: wrapWorkflowBinding({ tenantId }) },
}));
}
// The entrypoint name must match `class_name` in the workflows binding of your Wrangler config file.
export const DynamicWorkflow = createDynamicWorkflowEntrypoint(
async ({ env, metadata }) => {
const stub = loadTenant(env, metadata.tenantId);
return stub.getEntrypoint("TenantWorkflow");
},
);
export default {
fetch(request, env) {
const tenantId = request.headers.get("x-tenant-id");
return loadTenant(env, tenantId).getEntrypoint().fetch(request);
},
};import {
createDynamicWorkflowEntrypoint,
DynamicWorkflowBinding,
wrapWorkflowBinding,
type WorkflowRunner,
} from "@cloudflare/dynamic-workflows";
// Required: re-exporting puts the class on cloudflare:workers exports,
// which is how wrapWorkflowBinding builds per-tenant RPC stubs.
export { DynamicWorkflowBinding };
interface Env {
WORKFLOWS: Workflow;
LOADER: WorkerLoader;
}
function loadTenant(env: Env, tenantId: string) {
return env.LOADER.get(tenantId, async () => ({
compatibilityDate: "2026-01-01",
mainModule: "index.js",
modules: { "index.js": await fetchTenantCode(tenantId) },
// The Dynamic Worker uses this exactly like a real Workflow binding;
// every create() is tagged with { tenantId } automatically.
env: { WORKFLOWS: wrapWorkflowBinding({ tenantId }) },
}));
}
// The entrypoint name must match `class_name` in the workflows binding of your Wrangler config file.
export const DynamicWorkflow = createDynamicWorkflowEntrypoint<Env>(
async ({ env, metadata }) => {
const stub = loadTenant(env, metadata.tenantId as string);
return stub.getEntrypoint("TenantWorkflow") as unknown as WorkflowRunner;
},
);
export default {
fetch(request: Request, env: Env) {
const tenantId = request.headers.get("x-tenant-id")!;
return loadTenant(env, tenantId).getEntrypoint().fetch(request);
},
};リクエスト到着時の流れは次のとおりです。
fetchハンドラーがリクエストヘッダーからテナント ID を読み取ります。loadTenantがenv.LOADER.get()を呼び、そのテナント向けの Dynamic Worker を読み込みます(または再利用します)。Dynamic Worker はWORKFLOWS: wrapWorkflowBinding({ tenantId })をバインディングとして受け取ります。見た目も動作も通常の Workflow バインディングと同じです。- リクエストは Dynamic Worker の
fetchハンドラーへ転送されます。そこからenv.WORKFLOWS.create()を呼んで Workflow インスタンスを開始できます。
その Workflow インスタンスが後からステップを実行する必要があるとき(step.sleep() のあとや、別の isolate が引き継いだときなど)、Workflows エンジンは DynamicWorkflow クラスの run() を呼びます。ライブラリはインスタンスに保存されたメタデータから tenantId を読み戻し、createDynamicWorkflowEntrypoint に渡したコールバックを呼び出します。そのコールバックは該当テナントの Dynamic Worker を読み込み、TenantWorkflow クラスを返すので、エンジンは元のコードで次のステップを実行できます。
Dynamic Worker はユーザーが書くコードであり、ルーティング層を知る必要はありません。通常どおり step.do()、step.sleep()、step.waitForEvent() を使う標準の Workflow です。env.WORKFLOWS は通常の Workflow バインディングとして見えます。
import { WorkflowEntrypoint } from "cloudflare:workers";
export class TenantWorkflow extends WorkflowEntrypoint {
async run(event, step) {
return step.do("greet", async () => `Hello, ${event.payload.name}!`);
}
}
export default {
async fetch(request, env) {
const instance = await env.WORKFLOWS.create({
params: await request.json(),
});
// instance is an RPC stub — .id is an RpcPromise, so await it.
return Response.json({ id: await instance.id });
},
};import { WorkflowEntrypoint } from "cloudflare:workers";
export class TenantWorkflow extends WorkflowEntrypoint {
async run(event, step) {
return step.do("greet", async () => `Hello, ${event.payload.name}!`);
}
}
export default {
async fetch(request, env) {
const instance = await env.WORKFLOWS.create({
params: await request.json(),
});
// instance is an RPC stub — .id is an RpcPromise, so await it.
return Response.json({ id: await instance.id });
},
};通常の Workflows の動作はそのままです。Workflow ID、.status()、.pause()、リトライ、ハイバネーション、耐久ステップは、このアーキテクチャの影響を受けません。ライブラリが追加するのは、Worker Loader と Dynamic Worker のあいだのルーティングだけです。
テナント ID ヘッダーと JSON ペイロードを付けて、Worker Loader へ POST リクエストを送ります。Worker Loader は対応する Dynamic Worker を読み込み、その Worker が env.WORKFLOWS.create() を呼んで新しいインスタンス ID を返します。
curl -X POST http://localhost:8787/ \
-H "x-tenant-id: tenant-42" \
-H "Content-Type: application/json" \
-d '{"name": "Alice"}'直前のリクエストで返ったインスタンス ID を使い、Workflow のステータスを確認します。status API の詳細は Workers API リファレンス を参照してください。
curl "http://localhost:8787/api/status?instanceId=YOUR_INSTANCE_ID"