AAIニュース
最新ニュースAI日報Hacker日報週報動画AIツールトレンド企業
AAIニュース

世界中のAI最新情報を日本語で。毎時自動収集・翻訳・要約。

コンテンツ

最新ニュースAI日報週報

分析

トレンド企業動画

サイト

についてRSSお問い合わせ
© 2026 ainew.jp — All rights reserved.特定商取引法に基づく表記
ニュース一覧元記事を開く
Cloudflare Blog·2026年5月1日 22:00·約21分

テナントに追従する耐久性実行を実現する「Dynamic Workflows」の発表

#Agentic AI#Serverless Architecture#Dynamic Deployment#Cloudflare Workers
TL;DR

Cloudflare は、テナントごとの動的なワークフローコード実行を可能にする「Dynamic Workflows」を発表し、既存の堅牢な実行エンジンと動的デプロイ技術を統合した。

AI深層分析2026年5月1日 23:04
4
重要/ 5段階
深度40%
5
関連度30%
5
実用性20%
4
革新性10%
4

キーポイント

1

堅牢性と動的最適化の統合

従来の Cloudflare Workflows が持つ「失敗からの回復や長時間待機」という堅牢性(durable execution)と、Dynamic Workers や Durable Object Facets が提供する「ランタイムでの動的コード実行」機能を初めて結合した。

2

マルチテナント・SaaS への対応

各顧客やエージェントが独自のワークフロー定義を持つプラットフォーム(AI アプリ、CI/CD、エージェント SDK など)において、デプロイなしで動的にコードをバインドし実行できる課題を解決する。

3

既存機能の拡張と進化

Dynamic Workers(計算)、Durable Object Facets(ストレージ)、Artifacts(ソース管理)に続く、実行層における最後のピースとして位置づけられ、Agentic Era 向けの基盤整備が完了した。

4

単一Loaderによるテナント分離

@cloudflare/dynamic-workflowsライブラリは、1つのWorker Loaderがすべてのテナントのコードをルーティングし、実行時に適切なテナントコードへ戻す仕組みを提供します。

5

テナント開発者の透明性

テナントは通常のWorkflowバインディングを使用するだけでよく、背後で動的なディスパッチが行われていることを意識する必要はありません。

6

既存機能の完全互換性

ID管理、ステータス確認、一時停止、リトライ、休眠、永続ステップなど、従来のWorkflowのすべての機能がそのまま利用可能です。

7

動的実行の3層アーキテクチャ

ワークフローエンジン(プラットフォーム)、ワーカーローダー、テナントコード(動的ワーカー)という3層構造により、リクエストが到着した際に適切なテナントコードに動的にルーティングされます。

影響分析・編集コメントを表示

影響分析

この発表は、AI エージェントや SaaS プラットフォームが「動的なコード生成・実行」を大規模に運用する際の最大のボトルネックであった、堅牢性と柔軟性の両立問題を解消する画期的な進展です。特に Agentic Era において、各エージェントが自律的にワークフローを定義・実行する未来のアーキテクチャを支える重要なインフラとして機能し、開発者の生産性とシステムの信頼性を同時に高める可能性があります。

編集コメント

従来のサーバーレスワークフローが抱えていた「柔軟性」と「堅牢性」のトレードオフを解消する、プラットフォーム開発者にとって極めて重要な技術的マイルストーンです。AI エージェントによる自律的なコード実行を大規模に支える基盤として注目すべきニュースです。

8 年前に Workers を最初にリリースした際、それは開発者向けの直接プラットフォームでした。長年にわたり、エコシステムを拡大・拡張し、プラットフォームが Workers 上で直接構築できるだけでなく、マルチテナントアプリケーションを通じて顧客のコードを私たちに提供できるようにしました。現在、Workers では以下のようなアプリケーションが見られます:ユーザーが何を望むかを記述すると AI が実装を書くアプリケーション、各顧客のビジネスロジックがランタイムでプラットフォームがこれまで見たことのない TypeScript となるマルチテナント SaaS、自分自身のツールを書き実行するエージェント、リポジトリごとに独自のパイプラインを定義する CI/CD プロダクト。

先月、Dynamic Workers のオープンベータ版をリリースした際、計算側に対してこれらのプラットフォームにクリーンなプリミティブを提供しました:ランタイムで Workers ランタイムにコードを手渡し、同じマシン上で単一桁のミリ秒以内に隔離されたサンドボックス化された Worker を取得する仕組みです。Durable Object Facets は、この考え方をストレージにも拡張しました:動的に読み込まれる各アプリは独自の SQLite データベースを持ち、プラットフォームがスーパーバイザーとして前面に立つ中でオンデマンドで起動します。Artifacts はバージョン管理システム(ソースコントロール)に対して同様の役割を果たしました:エージェントごとに 1 つ、セッションごとに 1 つ、テナントごとに 1 つ作成できる、Git ネイティブなバージョン管理ファイルシステムです。つまり、ストレージとソースコントロールには動的デプロイが実現されました。次は何でしょうか?

本日、私たちは Durable Execution(永続的な実行)と Dynamic Deployment(動的デプロイ)を Dynamic Workflows(ダイナミックワークフロー)によって結びつけます。

永続的実行と動的実行のギャップ

Cloudflare Workflows は、当社の耐久性実行エンジンです。run(event, step) 関数をプログラムに変換し、各ステップが障害を生き延び、数時間や数日間スリープでき、外部イベントを待機し、アイソレートがリサイクルされたときに中断した場所から正確に再開できるようにします。これは、単一の要求を超えて「継続して実行する」必要があるオンボーディングフロー、ビデオトランスコーディングパイプライン、多段階課金、長期間実行されるエージェントループなどにとって最適なプリミティブです。そして、Workflows V2 以降では、アジェンティック・エラ(エージェント時代)のために再設計され、アカウントあたり最大 50,000 の並行インスタンスと秒間 300 の新規インスタンスに対応しています。

しかし、Workflows には常に一つの前提が組み込まれていました。それは、ワークフローコードはデプロイメントの一部であるという前提です。wrangler.jsonc ファイルには、「エンジンが WORKFLOWS に呼び出す際に、MyWorkflow というクラスを実行する」というブロックがあります。バインディングは一つ、クラスも一つ。デプロイごとに一つです。

すべてのコードを所有している場合や、従来のアプリケーションを実行している場合はこれで問題ありません。

しかし、顧客にワークフローを提供したい瞬間には、この仕組みは機能しなくなります。

例えば、AI が各テナントのために TypeScript を記述するアプリプラットフォームを構築しているとしましょう。あるいは、各リポジトリが独自のパイプラインを持つ CI/CD 製品を実行している場合かもしれません。また、各エージェントが独自の耐久性計画(durable plan)を記述するエージェント SDK を使用している場合もあるでしょう。これらのケースすべてにおいて、ワークフローはテナントごとに、エージェントごとに、要求ごとに異なります。バインドできる単一のクラスなど存在しません。

これは、Dynamic Workers が計算リソースに対して解決し、Durable Object Facets がストレージに対して解決したのと同じ形状の問題です。私たちはまだ、永続的な実行(durable execution)については解決していませんでした。

Dynamic Workflows

@cloudflare/dynamic-workflows は小さなライブラリです。TypeScript で約 300 行程度です。これは単一の Worker、すなわち Worker Loader が、すべての create() 呼び出しを異なるテナントのコードへルーティングすることを可能にし、さらに重要なことに、ワークフローが実際に実行される数秒後、数時間後、あるいは数日後に Workflows エンジンが run(event, step) を同じコードへディスパッチ(dispatch)できるようにします。

ここには全体のパターンがあります。Worker Loader の例です:

code
import {
  createDynamicWorkflowEntrypoint,
  DynamicWorkflowBinding,
  wrapWorkflowBinding,
} from '@cloudflare/dynamic-workflows';

// このライブラリは、cloudflare:workers エクスポート上でこのクラスを検索します。
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) },
    // テナントにとっては、これは通常の Workflow バインディングとして見えます。
    env: { WORKFLOWS: wrapWorkflowBinding({ tenantId }) },
  }));
}

// これを wrangler.jsonc の class_name として登録します。
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);

},

};

Add to your wrangler.jsonc:

"workflows": [

{

"name": "dynamic-workflow",

"binding": "WORKFLOW",

"class_name": "DynamicWorkflow"

}

]

The tenant writes plain, idiomatic Workflows code. They have no idea they're being dispatched:

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() });

return Response.json({ id: await instance.id });

},

};

That's it. The tenant calls env.WORKFLOWS.create(...) against what looks like a perfectly normal Workflow binding. Workflow IDs, .status(), .pause(), retries, hibernation, durable steps, step.sleep('24 hours'), step.waitForEvent() — everything works the way it always has.

The library handles one thing: making sure that when the Workflows engine eventually wakes up and calls run(event, step), it ends up inside the right tenant's code.

How it works

3 つのレイヤー:最上位にワークフローエンジン(プラットフォーム)、その下にワーカーローダー、さらに最下位にテナントのコード(ダイナミックワーカー)があります。

image
image

リクエストがワーカーローダーに到達すると、実行は即時に正しいダイナミックコードへルーティングされます。残りの実行プロセスは、これら 3 つのレイヤー間でのハンドオフであり、時間軸で左から右へと進行します:リクエストが入力され、エンジンまでバウンドし、永続化された後、再び下層へ戻ってきます。

フローを追う:

① → ② テナントコードへの進入。ワーカーローダーは HTTP リクエストを受け取り、それがどのテナント宛てかを特定し、そのテナントのコードをワーカーローダー経由で読み込み、リクエストをデフォルトの fetch に転送します。ワーカーローダーがテナントに渡す環境には WORKFLOWS: wrapWorkflowBinding({ tenantId }) が含まれています。テナントから見れば、これは本物のワークフローバインディングのように見え、動作します。

③ ワーカーローダーまで。テナントが env.WORKFLOWS.create({ params }) を呼び出すと、実際にはワーカーローダーに対してリモートプロシージャコール (RPC) が行われます。ここでラップされたバインディングは WorkerEntrypoint のサブクラス(DynamicWorkflowBinding)であり、ランタイムによってロード時にテナントのメタデータで特殊化されています。そのため、ワーカーローダーから { DynamicWorkflowBinding } をエクスポートする必要があります。これは、ランタイムが cloudflare:workers エクスポート内でこのクラスを検索して、テナントごとのスタブを構築するためです。Dynamic Worker の境界を越えるバインディングは RPC スタブでなければなりません。単純な { create, get } オブジェクトでは構造化クローンできず、生の Workflow バインディングもシリアライズできません。

ワーカーローダー内では、ラップされたバインディングがペイロードを透過的に書き換えます:

テナント呼び出し: create({ params: { name: 'Alice' } })

│

▼

エンジンが見るもの: create({ params: {

__workerLoaderMetadata: { tenantId: 't-42' },

params: { name: 'Alice' }

}})

④ エンジンまで。ワーカーローダーは、このエンベロープをパラメータとして実体の WORKFLOWS バインディングに対して .create() を呼び出します。ここからワークフローエンジンが引き継ぎます。エンジンは event.payload(今回はエンベロープが含まれる)を永続化し、実行をスケジュールします。後でエンジンがワークフローを起動するたびに(24 時間のスリープ後でも、クラッシュ後でも、デプロイ後でも)、メタデータはペイロードと共に付随し、実行のルーティングを待機します。

一つの帰結:メタデータを権限付与の根拠ではなく、ルーティングの手がかりとして扱うこと。テナントは instance.status() を通じてこれを再取得できます。ここに機密情報を格納しないでください。

⑤ → ⑥ エンジンは再度降下します。エンジンがあるステップを実行する準備ができると、wrangler.jsonc で登録したクラスに対して .run(event, step) を呼び出します。これは createDynamicWorkflowEntrypoint が提供したクラスです。そのクラスはエンベロープを解きほぐし、メタデータをあなたが記述した loadRunner コールバックに渡し、アンラップされたイベントをコールバックが返すランナーへ転送します。

このコールバックこそがすべて面白いことが起こる場所であり、完全にあなたのものです。R2 からテナントの最新ソースを取得します。プランティアを確認し、リージョンを選択します。テナントごとのログ用に Tail Worker をアタッチします。@cloudflare/worker-bundler で TypeScript をその場でバンドルします。一般的なケースでは、単に Worker Loader に引き渡すだけです。

const stub = env.LOADER.get(tenantId, () => loadTenantCode(tenantId));

return stub.getEntrypoint('TenantWorkflow');

The Worker Loader caches by ID, so a workflow that runs many steps over many hours reuses the same dynamic Worker across them. When the isolate eventually gets evicted, the next step.do() pulls the code again and keeps going — the tenant's workflow has no idea anything happened. A Dynamic Worker boots in single-digit milliseconds using a few megabytes of memory, so the dispatch overhead is essentially free. You can have a million tenants, each with their own distinct workflow code, each spun up lazily on the step boundary where it's needed, and none of them cost anything while idle.

The escape hatch

If you want to subclass WorkflowEntrypoint yourself — to add logging around run(), wire up per-tenant observability, or thread custom state through — the library exposes the lower-level dispatchWorkflow primitive that createDynamicWorkflowEntrypoint is built on:

import { dispatchWorkflow } from '@cloudflare/dynamic-workflows';

export class MyDynamicWorkflow extends WorkflowEntrypoint {

async run(event, step) {

return dispatchWorkflow(

{ env: this.env, ctx: this.ctx },

event,

step,

({ metadata, env }) => loadRunnerForTenant(env, metadata),

);

}

}

それ以外のすべての機能——ID、一時停止/再開、sendEvent、リトライなど——は、実体の Workflows エンジンの処理をそのまま通して実行されます。

Dynamic Workers はその基本要素です

少し立ち止まって全体像を見てみましょう。このライブラリの興味深い行のすべては、出力側では .create() のラッパーか、入力側では WorkflowEntrypoint のラッパーのいずれかに過ぎません。実際の作業——テナントコードのスピンアップ、サンドボックス化、境界を越えた RPC ルーティング、アイソレーターのキャッシュ、ステップ間のヒバーネーション——はすべて、背後で動作する Dynamic Workers によって行われています。

それが真の物語であり、Workflows という枠組みよりもはるかに大きな話です。

Dynamic Workers はすべての機能を吸収する基本要素です。Durable Object Facets は同じパターンを Durable Objects に適用したものであり、Dynamic Workflows は WorkflowEntrypoint に同じパターンを適用したものです。それぞれが、これまであなたが持っていた静的バインディングと、今や顧客に提供できる動的バージョンの間に挟まれる、ごく小さな「包み込み・展開」用の接着剤です。

そして、Workflows で終わらせるつもりはありません。Workers が現在公開しているすべてのバインディングは、動的な対応版へと向かっています。各プロデューサーが独自のハンドラーを配送するキュー、キャッシュ、データベース、オブジェクトストレージ、AI バインディング、そして各テナントが独自ツールを持ち込む MCP サーバーなどです。今日 Worker にバインドできるものはすべて、間もなく動的にバインドできるようになります。テナントごと、エージェントごと、リクエストごとにディスパッチされ、アイドル状態のコストはゼロです。

この種のプラットフォームを運用する際のユニットエコノミクスは、正直に言って非現実的です。マルチテナント製品を提供することは以前、各顧客に個別のコンテナ、個別のデータベース、個別のディスク、個別のスケジューラーを用意し、オーケストレーション用接着剤やサービスメッシュ、そして頭を痛めるような請求計算でそれらを接続することを意味していました。これらのアプリケーションの多くは、少なくとも数千、最大では数百万もの顧客をサポートする必要があります。一方、Dynamic Workers やその上で構築されるすべてのものにおいては、アイドル状態のテナントのコストはほぼゼロであり、アクティブなテナントはアイソレーションレベルのマルチテナンシーを通じて同じハードウェアを共有します。コストの下限が数桁も低下するのです。以前は数千人の有料顧客で頭打ちだったプラットフォームが、現在は合理的に数千万人をサービスできるようになりました。

What this unlocks

Agent platforms that plan like engineers

コーディングエージェント — OpenCode, Claude Code, Codex, Pi — は過去 1 年間、LLM が連続的なツール呼び出しを行うよりもはるかに優れたコード作成能力を持っていることを証明してきました。Cloudflare Agents SDK と Project Think は、この洞察を永続的実行(durable execution)へと拡張します。fibers やサブエージェント(sub-agents)といったプリミティブを用いることで、エージェントの長期にわたる計画は、ユーザーが気づかずに済むように、クラッシュやハビネーション、再デプロイに対しても耐性を持ちます。

Dynamic Workflows は、その計画を Cloudflare Workflow のファーストクラスの実装へと昇華させる要素です。エージェントが実際に記述し、プラットフォームが実際に実行するものであり、背後には完全な耐久性の仕組みが備わっています。数分前にモデルが記述した run(event, step) 関数において、すべての step.do(...) は独立して再試行可能であり、step.sleep('24 hours') は無料で休眠状態に入り、step.waitForEvent(...) は人間による次のアクションの承認を無期限に待ち続けます。エージェントがワークフローを記述し、プラットフォームがそれを実行します。事前に計画の外観を知る必要はありません。

ユーザーがロジックを提供する SDK およびフレームワーク

顧客が run(event, step) 関数を記述するフレームワーク(ワークフロービルダー UI、ビジュアル自動化ツール、テナントごとの拡張システム、非開発者向けのローコードツールなど)をリリースする場合、Dynamic Workflows は妥協なく動作させるための基盤となります。wrapWorkflowBinding({ tenantId }) を一度呼び出し、その結果を WORKFLOWS としてコードに渡すだけで、ユーザーが作成するすべてのワークフローインスタンスは自動的にタグ付けされ、戻り経路が設定され、サンドボックス内で実行されます。フレームワークが Worker Loader を管理し、ユーザーがワークフローを管理します。お互いの詳細を知る必要はありません。

プリミティブな速度での CI/CD

これが最も興奮させるユースケースです。

現在存在するすべての CI/CD プラットフォームは、本質的にはリポジトリごとの設定ファイルをディスパッチするものです。「これらのステップをこの順序で実行し、これらのシークレットを使用し、これらのディレクトリをキャッシュし、これらのアーティファクトをアップロードする」といった内容です。各リポジトリには独自のパイプラインがあり、各ブランチには独自の変種が存在します。各プルリクエストは、そのパイプラインのインスタンスを起動し、完了まで実行し、マシンのクラッシュに耐え、不安定なステップを再試行し、ログをストリーミングし、承認のために一時停止し、結果を永続化する必要があります。

それがまさに、耐久性のあるワークフローの形状です。これまで CI がそのような方式で構築されてこなかった理由は、ワークフロー自体がリポジトリごとに異なり、ランタイムでディスパッチされ、プロビジョニングコストゼロで実行できるクラウドプリミティブが存在しなかったからです。しかし今なら可能です。

CI パイプラインが、顧客がリポジトリと共に提供する単なるコードである場合(例えば .cloudflare/ci.ts 内)にはどのような姿になるかを見てみましょう。ワークフロー自体は実際に存在します。以下に示す runInSandbox() / summarise() / GitHub binding helpers はプラットフォーム提供の接着剤であり、ディスパッチャーで一度だけ実装して配布する類のものです:

import { WorkflowEntrypoint } from 'cloudflare:workers';

export class CIPipeline extends WorkflowEntrypoint {

async run(event, step) {

const { repo, sha, branch, pr } = event.payload;

// このコミット時点のリポジトリの孤立したコピーをフォークします。数秒、数分ではありません。

const workspace = await step.do('checkout', () =>

this.env.ARTIFACTS.fork(repo, { sha })

);

await step.do('install', () => runInSandbox(workspace, ['pnpm', 'install']));

// 各並列ステップは独立して再試行可能です。

const [lint, test, build] = await Promise.all([

step.do('lint', () => runInSandbox(workspace, ['pnpm', 'lint'])),

step.do('test', () => runInSandbox(workspace, ['pnpm', 'test'])),

step.do('build', () => runInSandbox(workspace, ['pnpm', 'build'])),

]);

if (pr) {

await step.do('comment', () =>

this.env.GITHUB.commentOnPR(repo, pr, summarise({ lint, test, build }))

);

}

// ワークフローは承認が来るまで待機(hibernate)します。VM は解放されません。

if (branch === 'main') {

await step.waitForEvent('approval', { type: 'deploy-approval', timeout: '24 hours' });

await step.do('deploy', () => runInSandbox(workspace, ['pnpm', 'deploy']));

}

}

}

プラットフォームがディスパッチャーを所有しています。プラットフォームは Webhook を取り込み、それがどのリポジトリから来たかを特定し、そのリポジトリの CIPipeline クラスを動的ワーカーとして読み込んで実行を引き渡します。プラットフォームがパイプライン内に何が含まれているかを知る必要はありません。それは顧客のリポジトリに存在する耐久性のある関数を実行しているだけだからです。

各ステップが実際に何を行うかを並べてみましょう:

Artifacts は、Cloudflare のグローバル分散ネットワーク上に存在する、Git ネイティブでバージョン管理されたファイルシステムをすべてのリポジトリに提供します。ArtifactFS はツリーを遅延読み込み(lazy hydration)するため、数 GB に及ぶリポジトリでも数秒以内に作業準備が整います。また fork() により、各 CI 実行ごとに独立したコピーが生成され、git clone のオーバーヘッドは発生しません。

Dynamic Workers は、リポジトリのデータと同じマシン上で、ミリ秒単位で起動するサンドボックス化されたアイソレート内で、各軽量ステップ(lint, format, typecheck, bundle)を実行します。仮想マシンのプロビジョニングも、イメージのプルも、コールドスタートも不要です。

Dynamic Workflows は実行全体を統合して管理します。各ステップは再試行可能で耐久性があります。承認待ちの間は実行は無料でヒバーネート(休止)状態になります。ステータスと進捗状況は、デプロイ、エビクション(強制終了)、クラッシュの後も維持されます。

サンドボックスは重いタスクを処理します。Docker ビルドが必要なステップや、Postgres の実行が必要な統合スイート、8 コアが必要となる Rust コンパイルなどです。R2 へのスナップショットにより、これらのステップも数秒でウォームスタートできます。

中規模の JS リポジトリにおける従来の CI 実行は、以下のような流れになります:VM の割り当て(15-30 秒)→ ベースイメージのプル(10 秒)→ git clone(10 秒)→ npm ci(30-60 秒)→ テストの実行(実際の作業)→ tear down。最初のテストが実行されるまでに数分間の準備が必要で、その間ずっと VM の料金を支払うことになります。

同じパイプラインをこのスタックで実行すると、以下のようになります:リポジトリのエッジフォーク(数秒)→ 各ステップが新しいアイソレートまたはスナップショット復元されたサンドボックスをミリ秒単位で起動 → 実際の作業を実行 → ヒバーネート。コールドスタートする必要も、事前のプロビジョニングも、ウォーム状態の維持も不要です。リポジトリは移動しません。計算資源がリポジトリのもとへやってきます。

CI がこれほど高速になったことはかつてありませんでした。その理由は、これらのプリミティブがこれまで一度に一つの場所で存在していなかったからです。しかし今、それが実現しました。

Try it

@cloudflare/dynamic-workflows は MIT ライセンスで、本日 npm で利用可能です:

npm install @cloudflare/dynamic-workflows

これは Dynamic Workers 上で動作します。Dynamic Workers は現在、Workers Paid プラン向けにオープンベータ版として提供されています。リポジトリには動作する例が含まれており、インタラクティブなブラウザプレイグラウンドです。ここでは TenantWorkflow クラスを記述し、「Run」ボタンをクリックすると、各ステップの do() コミットに応じて点灯していくチェックリストと共に、ライブストリーミングログでステップの実行状況を確認できます。これをクローンしてデプロイし、同僚に見せてみてください。

もしあなたがプラットフォーム、SDK、フレームワーク、または CI/CD 製品を構築している方で、自社のプロセス内で顧客のコードを実行することなく、顧客に独自ワークフローを提供したいと考えているなら:これがあなたのために構築したプリミティブです。耐久性のあるプランを作成するエージェントを構築中の方には、それらのプランを実際のワークフローとして機能させるためのプリミティブがこれです。単にこれらを見守っているだけで、これをベースに構築するのが楽しそうだと思われる方にも、ぜひあなたが何を作るか見てみたいと思っています。

Fi

原文を表示

When we first launched Workers eight years ago, it was a direct-to-developers platform. Over the years, we have expanded and scaled the ecosystem so that platforms could not only build on Workers directly, but they could also enable their customers to ship code to us through many multi-tenant applications. We now see on Workers: Applications where users describe what they want, and the AI writes the implementation. Multi-tenant SaaS where every customer's business logic is, at runtime, some TypeScript the platform has never seen before. Agents that write and run their own tools. CI/CD products where every repo defines its own pipeline.

Last month, when we shipped the Dynamic Workers open beta, we gave those platforms a clean primitive for the compute side: hand the Workers runtime some code at runtime, get back an isolated, sandboxed Worker, on the same machine, in single-digit milliseconds. Durable Object Facets extended the same idea to storage — each dynamically-loaded app can have its own SQLite database, spun up on demand, with the platform sitting in front, as a supervisor. Artifacts did the same for source control: a Git-native, versioned filesystem you can create by the tens of millions, one per agent, one per session, one per tenant. So, we have dynamic deployment for storage and source control. What’s next?

Today, we are bridging durable execution and dynamic deployment with Dynamic Workflows.

The gap between durable and dynamic execution

Cloudflare Workflows is our durable execution engine. It turns a run(event, step) function into a program where every step survives failures, can sleep for hours or days, can wait for external events, and resumes exactly where it left off when the isolate is recycled. It's the right primitive for anything that has to "keep going" past a single request: onboarding flows, video transcoding pipelines, multi-stage billing, long-running agent loops, and — as of Workflows V2 — up to 50,000 concurrent instances and 300 new instances per second per account, redesigned for the agentic era.

But Workflows has always had one assumption baked in: the workflow code is part of your deployment. Your wrangler.jsonc has a block that says "when the engine calls into WORKFLOWS, run the class called MyWorkflow." One binding, one class. Per deploy.

That works fine if you own all the code. It's fine if you're running a traditional application.

It stops working the moment you want to let your customer ship their workflow.

Say you're building an app platform where the AI writes TypeScript for every tenant. Say you're running a CI/CD product where each repository has its own pipeline. Say you're using an agents SDK where each agent writes its own durable plan. In every one of these cases, the workflow is different for every tenant, every agent, every request. There is no single class to bind.

This is the same shape of problem that Dynamic Workers solved for compute and that Durable Object Facets solved for storage. We just hadn't solved it for durable execution yet.

Dynamic Workflows

@cloudflare/dynamic-workflows is a small library. Roughly 300 lines of TypeScript. It lets a single Worker — the Worker Loader — route every create() call to a different tenant's code, and, critically, have the Workflows engine dispatch run(event, step) back to that same code when the workflow actually executes, seconds or hours or days later.

Here's the whole pattern. A Worker Loader:

import {

createDynamicWorkflowEntrypoint,

DynamicWorkflowBinding,

wrapWorkflowBinding,

} from '@cloudflare/dynamic-workflows';

// The library looks this class up on cloudflare:workers exports.

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 tenant sees this as a normal Workflow binding.

env: { WORKFLOWS: wrapWorkflowBinding({ tenantId }) },

}));

}

// Register this as class_name in wrangler.jsonc.

export const DynamicWorkflow = createDynamicWorkflowEntrypoint<Env>(

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);

},

};

Add to your wrangler.jsonc:

"workflows": [

{

"name": "dynamic-workflow",

"binding": "WORKFLOW",

"class_name": "DynamicWorkflow"

}

]

The tenant writes plain, idiomatic Workflows code. They have no idea they're being dispatched:

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() });

return Response.json({ id: await instance.id });

},

};

That's it. The tenant calls env.WORKFLOWS.create(...) against what looks like a perfectly normal Workflow binding. Workflow IDs, .status(), .pause(), retries, hibernation, durable steps, step.sleep('24 hours'), step.waitForEvent() — everything works the way it always has.

The library handles one thing: making sure that when the Workflows engine eventually wakes up and calls run(event, step), it ends up inside the right tenant's code.

How it works

Three layers: the Workflows engine (platform) on top, your Worker Loader in the middle, your tenant's code (a Dynamic Worker) on the bottom.

imageimage

When a request reaches the Worker Loader, it routes the execution to the correct dynamic code on the fly. The rest of the execution is a handoff between these three layers, left-to-right in time: the request enters, bounces up to the engine, is persisted, and later bounces back down again.

Walking the flow:

① → ② Entering the tenant's code. The Worker Loader receives an HTTP request, figures out which tenant it's for, loads that tenant's code via the Worker Loader, and forwards the request to its default.fetch. The env it hands the tenant contains WORKFLOWS: wrapWorkflowBinding({ tenantId }). As far as the tenant is concerned, that looks and acts like a real Workflow binding.

③ Up to the Worker Loader. When the tenant calls env.WORKFLOWS.create({ params }), it's actually making a Remote Procedure Call (RPC) into the Worker Loader — the wrapped binding is a WorkerEntrypoint subclass (DynamicWorkflowBinding) that the runtime specialized with the tenant's metadata at load time. That's why you have to export { DynamicWorkflowBinding } from your Worker Loader: the runtime builds per-tenant stubs by looking the class up in cloudflare:workers exports. Bindings that cross the Dynamic Worker boundary have to be RPC stubs — a plain { create, get } object can't be structured-cloned, and the raw Workflow binding isn't serializable either.

Inside the Worker Loader, the wrapped binding transparently rewrites the payload:

tenant calls: create({ params: { name: 'Alice' } })

│

▼

engine sees: create({ params: {

__workerLoaderMetadata: { tenantId: 't-42' },

params: { name: 'Alice' }

}})

④ Up to the engine. The Worker Loader then calls .create() on the real WORKFLOWS binding with the envelope as the params. From here the Workflows engine takes over. It persists event.payload — which now includes the envelope — and schedules the run. Every time the engine later wakes up the workflow (whether that’s after a 24-hour sleep, a crash, or a deploy), the metadata rides along with the payload, waiting to route the run.

One implication: treat the metadata as a routing hint, not as authorization. The tenant can read it back via instance.status(). Don't put secrets in there.

⑤ → ⑥ The engine comes back down. When the engine is ready to run a step, it calls .run(event, step) on the class you registered in wrangler.jsonc — the one createDynamicWorkflowEntrypoint gave you. That class unwraps the envelope, hands the metadata to the loadRunner callback you wrote, and forwards the unwrapped event through to whatever runner the callback returns.

The callback is where everything interesting happens, and it's entirely yours. Fetch the tenant's latest source from R2. Check their plan tier and pick a region. Attach a tail Worker for per-tenant logging. Bundle TypeScript on the fly with @cloudflare/worker-bundler. In the common case, you just hand off to the Worker Loader:

const stub = env.LOADER.get(tenantId, () => loadTenantCode(tenantId));

return stub.getEntrypoint('TenantWorkflow');

The Worker Loader caches by ID, so a workflow that runs many steps over many hours reuses the same dynamic Worker across them. When the isolate eventually gets evicted, the next step.do() pulls the code again and keeps going — the tenant's workflow has no idea anything happened. A Dynamic Worker boots in single-digit milliseconds using a few megabytes of memory, so the dispatch overhead is essentially free. You can have a million tenants, each with their own distinct workflow code, each spun up lazily on the step boundary where it's needed, and none of them cost anything while idle.

The escape hatch

If you want to subclass WorkflowEntrypoint yourself — to add logging around run(), wire up per-tenant observability, or thread custom state through — the library exposes the lower-level dispatchWorkflow primitive that createDynamicWorkflowEntrypoint is built on:

import { dispatchWorkflow } from '@cloudflare/dynamic-workflows';

export class MyDynamicWorkflow extends WorkflowEntrypoint {

async run(event, step) {

return dispatchWorkflow(

{ env: this.env, ctx: this.ctx },

event,

step,

({ metadata, env }) => loadRunnerForTenant(env, metadata),

);

}

}

Everything else — IDs, pause/resume, sendEvent, retries — falls through to the real Workflows engine untouched.

Dynamic Workers are the primitive

Step back from the specifics for a second. Every interesting line of this library is either a wrapper around .create() on the outbound side or a wrapper around WorkflowEntrypoint on the inbound side. The actual work — spinning up the tenant's code, sandboxing it, routing RPC across the boundary, caching the isolate, hibernating between steps — is all done by Dynamic Workers underneath.

That's the real story, and it's a lot bigger than Workflows

Dynamic Workers is the primitive that swallows everything. Durable Object Facets is the same pattern applied to Durable Objects. Dynamic Workflows is that same pattern applied to WorkflowEntrypoint. Each one is the same small amount of envelope-and-unwrap glue between the static binding you've always had and the dynamic version you can now hand to your customers.

And we're not stopping at Workflows. Every binding that Workers currently exposes is heading for a dynamic counterpart — queues where each producer ships its own handler, caches, databases, object stores, AI bindings, and MCP servers where every tenant brings their own tools. Whatever you bind to a Worker today, you will soon be able to bind dynamically: dispatched per tenant, per agent, per request, at zero idle cost.

The unit economics of running a platform like this are, frankly, absurd. Shipping a multi-tenant product used to mean giving every customer their own container, their own database, their own disk, their own scheduler, and stitching it together with orchestration glue, service meshes, and hair-pulling billing math. Many of these applications have to support thousands of customers at the very least; millions, at the most. On Dynamic Workers and everything composing on top of them, idle tenants cost approximately nothing and active tenants share the same hardware through isolate-level multi-tenancy. The floor drops several orders of magnitude. A platform that used to cap out at thousands of paying customers can now reasonably serve tens of millions.

What this unlocks

Agent platforms that plan like engineers

Coding agents — OpenCode, Claude Code, Codex, Pi — have been proving for the past year that LLMs are far better at writing code than at making sequential tool calls. The Cloudflare Agents SDK and Project Think extend that insight into durable execution: with primitives like fibers and sub-agents, an agent's long-running plan can survive crashes, hibernation, and redeploys without the user noticing.

Dynamic Workflows is the piece that lets that plan be a first-class Cloudflare Workflow — something the agent literally writes and the platform literally runs, with the full durability machinery behind it. A run(event, step) function the model wrote a minute ago, where every step.do(...) is independently retryable, every step.sleep('24 hours') hibernates for free, and every step.waitForEvent(...) waits indefinitely for the human to approve the next action. The agent writes the workflow; the platform runs it; neither has to know ahead of time what the plan looks like.

SDKs and frameworks where the user brings the logic

If you're shipping a framework where your customer writes the run(event, step) function — a workflow builder UI, a visual automation tool, a per-tenant extension system, a low-code tool for non-developers — Dynamic Workflows is now the primitive that makes it work without compromise. You call wrapWorkflowBinding({ tenantId }) once, hand the result to their code as WORKFLOWS, and every workflow instance they create is automatically tagged, routed back, and executed in their sandbox. The framework owns the Worker Loader; the user owns the workflow; neither has to care about the other.

CI/CD at primitive speed

Here's the use case that's been getting us most excited.

Every CI/CD platform in existence is, underneath, a dispatcher of per-repo configuration files: "run these steps, in this order, with these secrets, cache these directories, upload these artifacts." Each repo has its own pipeline. Each branch might have its own variant. Each pull request spawns an instance of that pipeline that has to run to completion, survive a machine crash, retry a flaky step, stream logs, pause for approvals, and persist results.

That's exactly the shape of a durable workflow. The reason CI hasn't been built that way until now is that nobody had a cloud primitive where the workflow itself is different for every repo, dispatched at runtime, at zero provisioning cost. Now you do.

Here's what a CI pipeline looks like when it's just code your customer ships with their repo — say, in .cloudflare/ci.ts. The workflow itself is real; the runInSandbox() / summarise() / GitHub binding helpers below are platform-provided glue, the kind of thing you'd ship once in your dispatcher:

import { WorkflowEntrypoint } from 'cloudflare:workers';

export class CIPipeline extends WorkflowEntrypoint {

async run(event, step) {

const { repo, sha, branch, pr } = event.payload;

// Fork an isolated copy of the repo at this commit. Seconds, not minutes.

const workspace = await step.do('checkout', () =>

this.env.ARTIFACTS.fork(repo, { sha })

);

await step.do('install', () => runInSandbox(workspace, ['pnpm', 'install']));

// Each parallel step is independently retryable.

const [lint, test, build] = await Promise.all([

step.do('lint', () => runInSandbox(workspace, ['pnpm', 'lint'])),

step.do('test', () => runInSandbox(workspace, ['pnpm', 'test'])),

step.do('build', () => runInSandbox(workspace, ['pnpm', 'build'])),

]);

if (pr) {

await step.do('comment', () =>

this.env.GITHUB.commentOnPR(repo, pr, summarise({ lint, test, build }))

);

}

// Workflow hibernates until approval arrives. No VM held open.

if (branch === 'main') {

await step.waitForEvent('approval', { type: 'deploy-approval', timeout: '24 hours' });

await step.do('deploy', () => runInSandbox(workspace, ['pnpm', 'deploy']));

}

}

}

The platform owns the dispatcher. It ingests a webhook, figures out which repo it came from, loads that repo's CIPipeline class as a Dynamic Worker, and hands the run-off to Dynamic Workflows. The platform doesn't know what's in the pipeline. It doesn't need to. It's running a durable function that happens to live in the customer's repo.

Now line up what each step actually does:

Artifacts gives every repo a Git-native, versioned filesystem that lives on Cloudflare's globally distributed network. ArtifactFS hydrates the tree lazily, so even a multi-GB repo is ready to work within single-digit seconds — and fork() gives each CI run its own isolated copy, with no git clone tax.

Dynamic Workers run each lightweight step (lint, format, typecheck, bundle) in a sandboxed isolate that boots in milliseconds, on the same machine as the repo's data. No VM provisioning, no image pull, no cold start.

Dynamic Workflows holds the whole run together. Steps are retryable and durable. The run hibernates for free while waiting on approvals. State and progress survive deploys, evictions, and crashes.

Sandboxes handle the heavy corners — the step that needs docker build, the integration suite that needs Postgres running, the Rust compile that needs 8 cores. Snapshots to R2 mean even those warm-start in a couple of seconds.

A traditional CI run for a mid-sized JS repo looks something like: allocate VM (15-30s) → pull base image (10s) → git clone (10s) → npm ci (30-60s) → run tests (actual work) → tear down. Several minutes of ceremony before the first test runs, and you pay for the whole VM the whole time.

The same pipeline on this stack looks like: edge fork of the repo (seconds) → each step boots a fresh isolate or snapshot-restored sandbox in milliseconds → runs the actual work → hibernates. Nothing has to cold-start. Nothing has to be provisioned ahead of time. Nothing has to be kept warm. The repo doesn't move — the compute comes to it.

CI has never been this fast, and the reason it hasn't is that none of these primitives have existed together in one place. Now they do.

Try it

@cloudflare/dynamic-workflows is MIT-licensed and on npm today:

npm install @cloudflare/dynamic-workflows

It runs on top of Dynamic Workers, which is in open beta on the Workers Paid plan. The repo includes a working example — an interactive browser playground where you write a TenantWorkflow class, hit Run, and watch the steps execute with live-streaming logs and a per-step checklist that lights up as each step.do() commits. Clone it, deploy it, show it to a coworker.

If you're a platform, an SDK, a framework, or a CI/CD product, and you want to give your customers their own workflows without running their code in your own process: this is the primitive we built for you. If you're building agents that write durable plans, this is the primitive that makes those plans real Workflows. If you're just watching all of this, and it looks fun to build on top of: we'd love to see what you make.

Fi

この記事をシェア

関連記事

AWS Machine Learning Blog重要度42026年6月26日 01:35

AWS で現代的なデータメッシュ戦略を用いたエージェント型 AI アプリケーションの構築

LangChain Blog重要度42026年6月25日 23:53

最高の AI エージェントはシンプルである:Sierra の Zack Reneau-Wedeen が語る、Max Agency Podcast での議論

Cloudflare Blog2026年6月25日 22:00

Cloudflare Workflows のサガロールバック機能の構築方法について

今日のまとめ

AI日報で今日の重要ニュースをまとめ読み

ニュース一覧に戻る元記事を読む