Skip to content

Commit 54fbafb

Browse files
committed
Dynamic Workers and Workflows documentation
1 parent aaf5878 commit 54fbafb

3 files changed

Lines changed: 193 additions & 0 deletions

File tree

315 KB
Loading
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: Dynamic Workers and Workflows
3+
description: Route Workflow runs into dynamically loaded Worker code while keeping durable Workflow steps and state.
4+
pcx_content_type: example
5+
external_link: /workflows/examples/dynamic-workers-and-workflows/
6+
sidebar:
7+
order: 5
8+
---
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
---
2+
title: Dynamic Workflows
3+
description: Create and run Workflow logic dynamically while keeping Workflow steps durable.
4+
pcx_content_type: example
5+
sidebar:
6+
order: 3
7+
---
8+
9+
import { TypeScriptExample, WranglerConfig } from "~/components";
10+
11+
Use this pattern when your Worker needs to create and run many different Workflows dynamically, especially one-off Workflows that are generated for a single task.
12+
13+
For example, an AI agent might decide at runtime to create a Workflow with these steps:
14+
15+
1. Summarize a support ticket
16+
2. Wait for human approval
17+
3. Send a follow-up message
18+
19+
With Dynamic Workers and Workflows, the Worker Loader, which loads Dynamic Workers, can create that Workflow on the spot, run it durably, and let Workflows handle retries, sleeping, and waiting for events. You do not need to pre-register a separate Workflow class for every run.
20+
21+
## When to use this pattern
22+
23+
Use this pattern when your workflow logic is not known at deploy time.
24+
25+
This is useful when:
26+
27+
- An agent generates workflow code for a specific task
28+
- End customers create workflows in a multi-tenant application
29+
- You need many short-lived or one-off Workflows without deploying a new Worker for each one
30+
31+
If your workflow logic is fixed, a standard Workflow is generally recommended.
32+
33+
## Understand the model
34+
35+
This setup has two parts:
36+
37+
- **Worker Loader**: Worker which receives the request and creates (or loads) the Dynamic Worker.
38+
- **Workflow class**: Your registered Workflow class which receives the persisted params, reloads the same Dynamic Worker code, and calls its `run()` method with the `WorkflowStep` object.
39+
40+
![Dynamic Workers and Workflows architecture diagram](~/assets/images/workflows/dynamic-workflows.png)
41+
42+
Once the Worker Loader has [loaded the Dynamic Worker](/dynamic-workers/getting-started/#run-a-dynamic-worker), the Dynamic Worker calls `env.WORKFLOW.create()` to trigger an instance of the Workflow. The Worker Loader then stores the `workerId` of the Dynamic Worker in case the isolate spins down during the Workflow's exeuction.
43+
44+
If the instance is long-running -- for example, if it includes a step.waitForEvent() -- then the Workflow engine may hibernate, and the Dynamic Worker isolate will shut down until the event occurs. When the Workflow resumes, the Worker Loader reloads its code using the workerId of the `Dynamic Worker`.
45+
46+
Following its normal durable execution model, the workflow instance continues where it left off, and the Dynamic Worker executes the remaining steps. The instance reaches completion with all steps durably executed, and the Dynamic Worker stops running.
47+
48+
## Configure your Worker
49+
50+
Your Worker needs two things:
51+
52+
- A Worker Loader binding
53+
- A Workflow binding that points to the Workflow class
54+
55+
<WranglerConfig>
56+
57+
```jsonc
58+
{
59+
"name": "dynamic-workflow-loader",
60+
"main": "src/index.ts",
61+
"compatibility_date": "$today",
62+
"worker_loaders": [
63+
{
64+
"binding": "LOADER",
65+
},
66+
],
67+
"workflows": [
68+
{
69+
"name": "dynamic-workflow",
70+
"binding": "WORKFLOW",
71+
"class_name": "DynamicWorkflow",
72+
},
73+
],
74+
}
75+
```
76+
77+
</WranglerConfig>
78+
79+
## Run the Dynamic Worker from your Workflow class
80+
81+
Although the Workflow is not statically defined, you still deploy one normal Workflow class. Its job is to load the dynamic code for the current run and call it with the `WorkflowStep` object. This class is what makes the [Workflows API](/workflows/build/workers-api/), including `step.do()`, `step.sleep()`, and `step.waitForEvent()`, continue to work as normal.
82+
83+
Use `env.LOADER.get(id, callback)` via the [Worker Loader API](/dynamic-workers/api-reference/#load) to create the Dynamic Worker. If the Dynamic Worker has already been created, the Worker Loader uses its `workerId` to reload the code and use the same Dynamic Worker.
84+
85+
<TypeScriptExample>
86+
87+
```ts
88+
import {
89+
WorkflowEntrypoint,
90+
type WorkflowStep,
91+
type WorkflowEvent,
92+
} from "cloudflare:workers";
93+
94+
type Params = {
95+
script: string;
96+
};
97+
98+
export class DynamicWorkflow extends WorkflowEntrypoint<Env, Params> {
99+
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
100+
const { script } = event.payload;
101+
102+
const worker = this.env.LOADER.get(
103+
`dyn-wf-${event.instanceId}`,
104+
async () => ({
105+
mainModule: "index.js",
106+
modules: { "index.js": script },
107+
compatibilityDate: "2025-09-27",
108+
compatibilityFlags: ["nodejs_compat"],
109+
}),
110+
);
111+
112+
const entrypoint = worker.getEntrypoint() as unknown as {
113+
run(event: Record<string, unknown>, step: WorkflowStep): Promise<unknown>;
114+
};
115+
const result = await entrypoint.run({}, step);
116+
117+
return result;
118+
}
119+
}
120+
```
121+
122+
<TypeScriptExample>
123+
124+
```ts
125+
function loadDynamicWorkflow(env: Env, scriptId: string, script: string) {
126+
return env.LOADER.get(`dynamic-workflow:${scriptId}`, async () => ({
127+
compatibilityDate: "$today",
128+
mainModule: "index.js",
129+
modules: {
130+
"index.js": script,
131+
},
132+
globalOutbound: null,
133+
}));
134+
}
135+
```
136+
137+
</TypeScriptExample>
138+
139+
## Write the dynamic workflow code
140+
141+
The dynamic code defines the steps for one run. It is regular Worker code loaded at runtime.
142+
143+
This example implements the support-ticket flow described earlier:
144+
145+
1. Summarize the ticket
146+
2. Wait for approval
147+
3. Send a follow-up
148+
149+
<TypeScriptExample>
150+
151+
```ts
152+
import { WorkerEntrypoint } from "cloudflare:workers";
153+
154+
export class DynamicWorkflowLogic extends WorkerEntrypoint {
155+
async run(event, step) {
156+
const summary = await step.do("summarize ticket", async () => {
157+
const ticket = event.payload.ticket;
158+
return `Summary: ${ticket.subject}`;
159+
});
160+
161+
await step.waitForEvent("wait for approval", {
162+
type: "ticket-approved",
163+
timeout: "24 hours",
164+
});
165+
166+
return step.do("send follow-up", async () => {
167+
return {
168+
sent: true,
169+
message: `Approved follow-up sent for: ${summary}`,
170+
};
171+
});
172+
}
173+
}
174+
```
175+
176+
</TypeScriptExample>
177+
178+
## Related resources
179+
180+
- [Workers API](/workflows/build/workers-api/)
181+
- [Trigger Workflows](/workflows/build/trigger-workflows/)
182+
- [Events and parameters](/workflows/build/events-and-parameters/)
183+
- [Dynamic Workers getting started](/dynamic-workers/getting-started/)
184+
- [Dynamic Worker Loaders](/workers/runtime-apis/bindings/worker-loader/)
185+
- [Bindings with Dynamic Workers](/dynamic-workers/usage/bindings/)

0 commit comments

Comments
 (0)