agents 0.16.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +11 -8
  2. package/dist/{agent-tool-types-NofdbL9X.d.ts → agent-tool-types-Cd1TZPfB.d.ts} +595 -137
  3. package/dist/agent-tool-types.d.ts +34 -18
  4. package/dist/agent-tool-types.js +20 -1
  5. package/dist/agent-tool-types.js.map +1 -0
  6. package/dist/agent-tools-BXlsuX0d.js +304 -0
  7. package/dist/agent-tools-BXlsuX0d.js.map +1 -0
  8. package/dist/agent-tools-_E8wxUIK.d.ts +119 -0
  9. package/dist/agent-tools.d.ts +24 -18
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/ai-chat-agent.d.ts +1 -1
  12. package/dist/ai-chat-agent.js +1 -2
  13. package/dist/ai-chat-agent.js.map +1 -1
  14. package/dist/ai-chat-v5-migration.d.ts +1 -1
  15. package/dist/ai-chat-v5-migration.js +1 -2
  16. package/dist/ai-chat-v5-migration.js.map +1 -1
  17. package/dist/ai-react.d.ts +1 -1
  18. package/dist/ai-react.js +1 -2
  19. package/dist/ai-react.js.map +1 -1
  20. package/dist/ai-types.d.ts +1 -7
  21. package/dist/ai-types.js +2 -8
  22. package/dist/ai-types.js.map +1 -1
  23. package/dist/browser/ai.js +1 -1
  24. package/dist/browser/index.js +1 -1
  25. package/dist/chat/index.d.ts +1923 -76
  26. package/dist/chat/index.js +1784 -278
  27. package/dist/chat/index.js.map +1 -1
  28. package/dist/chat/react.d.ts +602 -0
  29. package/dist/chat/react.js +1506 -0
  30. package/dist/chat/react.js.map +1 -0
  31. package/dist/chat-sdk/index.d.ts +4 -4
  32. package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
  33. package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
  34. package/dist/{client-FUizKzj2.js → client-BZ-B3NhC.js} +19 -3
  35. package/dist/client-BZ-B3NhC.js.map +1 -0
  36. package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
  37. package/dist/client.d.ts +76 -57
  38. package/dist/client.js +33 -5
  39. package/dist/client.js.map +1 -1
  40. package/dist/{compaction-helpers-DVcu5lPN.d.ts → compaction-helpers-wUz6M3us.d.ts} +20 -1
  41. package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
  42. package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
  43. package/dist/email.js +1 -1
  44. package/dist/email.js.map +1 -1
  45. package/dist/experimental/memory/session/index.d.ts +1 -1
  46. package/dist/experimental/memory/session/index.js +20 -2
  47. package/dist/experimental/memory/session/index.js.map +1 -1
  48. package/dist/experimental/memory/utils/index.d.ts +1 -1
  49. package/dist/{index-B7IbEeze.d.ts → index-CcbnKkNh.d.ts} +188 -14
  50. package/dist/index.d.ts +91 -71
  51. package/dist/index.js +562 -24
  52. package/dist/index.js.map +1 -1
  53. package/dist/mcp/client.d.ts +18 -14
  54. package/dist/mcp/client.js +1 -1
  55. package/dist/mcp/index.d.ts +30 -30
  56. package/dist/mcp/index.js +7 -7
  57. package/dist/mcp/index.js.map +1 -1
  58. package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
  59. package/dist/message-builder-BymO4N_D.js.map +1 -0
  60. package/dist/observability/index.d.ts +1 -1
  61. package/dist/observability/index.js +4 -2
  62. package/dist/observability/index.js.map +1 -1
  63. package/dist/react.d.ts +123 -110
  64. package/dist/react.js +44 -11
  65. package/dist/react.js.map +1 -1
  66. package/dist/serializable.d.ts +1 -1
  67. package/dist/skills/compile.js +1 -1
  68. package/dist/skills/compile.js.map +1 -1
  69. package/dist/skills/index.js +5 -5
  70. package/dist/skills/index.js.map +1 -1
  71. package/dist/sub-routing.d.ts +6 -6
  72. package/dist/utils.js +1 -1
  73. package/dist/utils.js.map +1 -1
  74. package/dist/vite.js +5 -5
  75. package/dist/vite.js.map +1 -1
  76. package/dist/wire-types-nflOzNuU.js +240 -0
  77. package/dist/wire-types-nflOzNuU.js.map +1 -0
  78. package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
  79. package/dist/workflow-types.d.ts +25 -21
  80. package/dist/workflow-types.js.map +1 -1
  81. package/dist/workflows.d.ts +22 -21
  82. package/dist/workflows.js +31 -7
  83. package/dist/workflows.js.map +1 -1
  84. package/docs/adding-to-existing-project.md +450 -0
  85. package/docs/agent-class.md +503 -0
  86. package/docs/agent-tools.md +552 -0
  87. package/docs/browse-the-web.md +430 -0
  88. package/docs/callable-methods.md +627 -0
  89. package/docs/chat-agents.md +1687 -0
  90. package/docs/chat-sdk.md +181 -0
  91. package/docs/client-sdk.md +520 -0
  92. package/docs/client-tools-continuation.md +177 -0
  93. package/docs/codemode.md +440 -0
  94. package/docs/configuration.md +775 -0
  95. package/docs/cross-domain-authentication.md +171 -0
  96. package/docs/durable-execution.md +537 -0
  97. package/docs/email.md +663 -0
  98. package/docs/get-current-agent.md +204 -0
  99. package/docs/getting-started.md +305 -0
  100. package/docs/http-websockets.md +668 -0
  101. package/docs/human-in-the-loop.md +661 -0
  102. package/docs/index.md +151 -0
  103. package/docs/long-running-agents.md +730 -0
  104. package/docs/mcp-client.md +620 -0
  105. package/docs/mcp-servers.md +526 -0
  106. package/docs/mcp-transports.md +308 -0
  107. package/docs/migration-to-ai-sdk-v5.md +96 -0
  108. package/docs/migration-to-ai-sdk-v6.md +163 -0
  109. package/docs/observability.md +261 -0
  110. package/docs/push-notifications.md +367 -0
  111. package/docs/queue.md +329 -0
  112. package/docs/readonly-connections.md +278 -0
  113. package/docs/resumable-streaming.md +127 -0
  114. package/docs/retries.md +444 -0
  115. package/docs/routing.md +749 -0
  116. package/docs/scheduling.md +898 -0
  117. package/docs/securing-mcp-servers.md +359 -0
  118. package/docs/server-driven-messages.md +477 -0
  119. package/docs/sessions.md +1024 -0
  120. package/docs/state.md +512 -0
  121. package/docs/sub-agents.md +389 -0
  122. package/docs/webhooks.md +604 -0
  123. package/docs/workflows.md +877 -0
  124. package/package.json +47 -15
  125. package/dist/agent-tools-3zLG7MgA.js.map +0 -1
  126. package/dist/agent-tools-DLquv-dp.d.ts +0 -14
  127. package/dist/client-FUizKzj2.js.map +0 -1
@@ -0,0 +1,877 @@
1
+ # Workflows Integration
2
+
3
+ Integrate [Cloudflare Workflows](https://developers.cloudflare.com/workflows/) with Agents for durable, multi-step background processing while Agents handle real-time communication.
4
+
5
+ ## Quick Links
6
+
7
+ - [Start a workflow](#2-start-workflow-from-agent)
8
+ - [Human approval](#human-in-the-loop-approval)
9
+ - [Pagination](#getworkflowscriteria)
10
+ - [Workflow controls](#agent-workflow-methods)
11
+
12
+ ## Introduction
13
+
14
+ ### What are Cloudflare Workflows?
15
+
16
+ Cloudflare Workflows provide durable, multi-step execution that survives failures, retries automatically, and can pause to wait for external events. They're ideal for:
17
+
18
+ - Long-running background tasks (data processing, report generation)
19
+ - Multi-step pipelines with retry logic
20
+ - Human-in-the-loop approval flows
21
+ - Tasks that shouldn't block user requests
22
+
23
+ ### Why Integrate with Agents?
24
+
25
+ Agents excel at real-time communication and state management, while Workflows excel at durable execution. Together they provide:
26
+
27
+ | Feature | Agent | Workflow | Combined |
28
+ | ---------------------- | ------- | -------- | ---------------- |
29
+ | Real-time WebSocket | ✓ | ✗ | Agent handles |
30
+ | Long-running tasks | Limited | ✓ | Workflow handles |
31
+ | State persistence | ✓ | ✓ | Both |
32
+ | Automatic retries | ✗ | ✓ | Workflow handles |
33
+ | External event waiting | ✗ | ✓ | Workflow handles |
34
+
35
+ ### When to Use What
36
+
37
+ | Use Case | Recommendation |
38
+ | ----------------------------- | ------------------------------ |
39
+ | Chat/messaging | Agent only |
40
+ | Quick API calls | Agent only |
41
+ | Background processing (< 30s) | Agent `queue()` |
42
+ | One durable Think chat turn | Think `submitMessages()` |
43
+ | Long-running tasks (> 30s) | Agent + Workflow |
44
+ | Multi-step pipelines | Workflow |
45
+ | Human approval flows | Agent + Workflow |
46
+ | Scheduled tasks | Agent `schedule()` or Workflow |
47
+
48
+ ## Quick Start
49
+
50
+ ### 1. Define Your Workflow
51
+
52
+ Create a Workflow that extends `AgentWorkflow` to get typed access to the originating Agent or sub-agent:
53
+
54
+ ```typescript
55
+ // src/workflows/processing.ts
56
+ import { AgentWorkflow } from "agents/workflows";
57
+ import type { AgentWorkflowEvent, AgentWorkflowStep } from "agents/workflows";
58
+ import type { MyAgent } from "../agent";
59
+
60
+ type TaskParams = {
61
+ taskId: string;
62
+ data: string;
63
+ };
64
+
65
+ export class ProcessingWorkflow extends AgentWorkflow<MyAgent, TaskParams> {
66
+ async run(event: AgentWorkflowEvent<TaskParams>, step: AgentWorkflowStep) {
67
+ const params = event.payload;
68
+
69
+ // Step 1: Process data
70
+ const result = await step.do("process-data", async () => {
71
+ // Durable step - will retry on failure
72
+ return processData(params.data);
73
+ });
74
+
75
+ // Report progress to Agent (non-durable, lightweight)
76
+ await this.reportProgress({
77
+ step: "process",
78
+ status: "complete",
79
+ percent: 0.5
80
+ });
81
+
82
+ // Step 2: Save results
83
+ await step.do("save-results", async () => {
84
+ // Call Agent method via RPC
85
+ await this.agent.saveResult(params.taskId, result);
86
+ });
87
+
88
+ // Broadcast to connected clients (non-durable)
89
+ this.broadcastToClients({
90
+ type: "task-complete",
91
+ taskId: params.taskId
92
+ });
93
+
94
+ // Report completion (durable via step)
95
+ await step.reportComplete(result);
96
+
97
+ return result;
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### 2. Start Workflow from Agent
103
+
104
+ Use `runWorkflow()` to start a workflow with automatic tracking:
105
+
106
+ ```typescript
107
+ // src/agent.ts
108
+ import { Agent } from "agents";
109
+
110
+ export class MyAgent extends Agent {
111
+ async startTask(taskId: string, data: string) {
112
+ // Start workflow - automatically tracked in this Agent's database
113
+ const instanceId = await this.runWorkflow("PROCESSING_WORKFLOW", {
114
+ taskId,
115
+ data
116
+ });
117
+
118
+ return { instanceId };
119
+ }
120
+
121
+ // Called when workflow reports progress (progress is typed object)
122
+ async onWorkflowProgress(
123
+ workflowName: string,
124
+ instanceId: string,
125
+ progress: unknown
126
+ ) {
127
+ // Cast to your progress type
128
+ const p = progress as { step?: string; status?: string; percent?: number };
129
+ console.log(
130
+ `Workflow ${workflowName}/${instanceId}: ${p.step} - ${p.status} (${(p.percent ?? 0) * 100}%)`
131
+ );
132
+
133
+ // Broadcast to connected clients
134
+ this.broadcast(
135
+ JSON.stringify({
136
+ type: "workflow-progress",
137
+ workflowName,
138
+ instanceId,
139
+ progress
140
+ })
141
+ );
142
+ }
143
+
144
+ // Called when workflow completes
145
+ async onWorkflowComplete(
146
+ workflowName: string,
147
+ instanceId: string,
148
+ result?: unknown
149
+ ) {
150
+ console.log(`Workflow ${workflowName}/${instanceId} completed:`, result);
151
+ }
152
+
153
+ // Method called by workflow via RPC
154
+ async saveResult(taskId: string, result: unknown) {
155
+ this
156
+ .sql`INSERT INTO results (task_id, data) VALUES (${taskId}, ${JSON.stringify(result)})`;
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### 3. Configure Wrangler
162
+
163
+ ```jsonc
164
+ // wrangler.jsonc
165
+ {
166
+ "name": "my-app",
167
+ "main": "src/index.ts",
168
+ "compatibility_date": "2025-02-11",
169
+ "durable_objects": {
170
+ "bindings": [{ "name": "MY_AGENT", "class_name": "MyAgent" }]
171
+ },
172
+ "workflows": [
173
+ {
174
+ "name": "processing-workflow",
175
+ "binding": "PROCESSING_WORKFLOW",
176
+ "class_name": "ProcessingWorkflow"
177
+ }
178
+ ],
179
+ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }]
180
+ }
181
+ ```
182
+
183
+ ## API Reference
184
+
185
+ ### `AgentWorkflow<AgentType, Params, ProgressType, Env>`
186
+
187
+ Base class for Workflows that integrate with Agents.
188
+
189
+ **Type Parameters:**
190
+
191
+ - `AgentType` - The Agent class type (for typed RPC)
192
+ - `Params` - User params passed to the workflow (optional)
193
+ - `ProgressType` - Type for progress reporting (defaults to `DefaultProgress`)
194
+ - `Env` - Environment type (defaults to `Cloudflare.Env`)
195
+
196
+ **Properties:**
197
+
198
+ - `agent` - Typed stub for calling Agent methods via RPC. For workflows started from sub-agents, this is an RPC-only stub back to the originating facet; use sub-agent routing for HTTP or WebSocket `fetch()` traffic.
199
+ - `instanceId` - The workflow instance ID
200
+ - `workflowName` - The workflow binding name
201
+ - `env` - Environment bindings
202
+
203
+ **Methods on `this` (non-durable, may repeat on retry):**
204
+
205
+ | Method | Description |
206
+ | ------------------------------ | --------------------------------------------- |
207
+ | `reportProgress(progress)` | Report typed progress object to the Agent |
208
+ | `broadcastToClients(message)` | Broadcast message to all WebSocket clients |
209
+ | `waitForApproval(step, opts?)` | Wait for approval event (throws on rejection) |
210
+
211
+ **Methods on `step` (durable, idempotent, won't repeat on retry):**
212
+
213
+ | Method | Description |
214
+ | ------------------------------- | ---------------------------------------------- |
215
+ | `step.reportComplete(result?)` | Report successful completion |
216
+ | `step.reportError(error)` | Report an error |
217
+ | `step.sendEvent(event)` | Send a custom event to the Agent |
218
+ | `step.updateAgentState(state)` | Replace Agent state (broadcasts to clients) |
219
+ | `step.mergeAgentState(partial)` | Merge into Agent state (broadcasts to clients) |
220
+ | `step.resetAgentState()` | Reset Agent state to initialState |
221
+
222
+ **DefaultProgress Type:**
223
+
224
+ ```typescript
225
+ type DefaultProgress = {
226
+ step?: string;
227
+ status?: "pending" | "running" | "complete" | "error";
228
+ message?: string;
229
+ percent?: number;
230
+ [key: string]: unknown; // extensible
231
+ };
232
+ ```
233
+
234
+ ### Agent Workflow Methods
235
+
236
+ Methods added to the `Agent` class:
237
+
238
+ #### `runWorkflow(workflowName, params, options?)`
239
+
240
+ Start a workflow and track it in the originating Agent's database.
241
+
242
+ ```typescript
243
+ const instanceId = await this.runWorkflow(
244
+ "MY_WORKFLOW",
245
+ { taskId: "123", data: "process this" },
246
+ {
247
+ id: "custom-id", // optional - auto-generated if not provided
248
+ metadata: { userId: "user-456", priority: "high" }, // optional - for querying
249
+ agentBinding: "MyAgent" // optional - auto-detected from class name if not provided
250
+ }
251
+ );
252
+ ```
253
+
254
+ **Parameters:**
255
+
256
+ - `workflowName` - Workflow binding name from `env`
257
+ - `params` - Params to pass to the workflow
258
+ - `options.id` - Custom workflow ID (auto-generated if not provided)
259
+ - `options.metadata` - Optional metadata stored for querying (not passed to workflow)
260
+ - `options.agentBinding` - Agent binding name (auto-detected from class name if not provided). When called from a sub-agent, this is the root Agent binding name.
261
+
262
+ **Returns:** Workflow instance ID
263
+
264
+ #### Sub-agents
265
+
266
+ Sub-agents can call `this.runWorkflow()` directly. The workflow is tracked in the originating sub-agent's SQLite database, and `this.agent` inside `AgentWorkflow` routes back to that same sub-agent for RPC calls, callbacks, state updates, and broadcasts.
267
+
268
+ Parent agents do not automatically list or control workflows that a sub-agent starts. `SubAgentStub<T>` only exposes user-defined methods, not inherited `Agent` methods such as `approveWorkflow()` or `getWorkflow()`. To control a child-started workflow from the parent, define small wrapper methods on the child and call those wrappers through the sub-agent stub.
269
+
270
+ ```typescript
271
+ export class ParentAgent extends Agent {
272
+ async startChildWorkflow(childName: string, task: string) {
273
+ const child = await this.subAgent(ChildAgent, childName);
274
+ return child.startWorkflow(task);
275
+ }
276
+
277
+ async approveChildWorkflow(childName: string, workflowId: string) {
278
+ const child = await this.subAgent(ChildAgent, childName);
279
+ return child.approveChildWorkflow(workflowId);
280
+ }
281
+ }
282
+
283
+ export class ChildAgent extends Agent {
284
+ async startWorkflow(task: string) {
285
+ return this.runWorkflow("CHILD_WORKFLOW", { task });
286
+ }
287
+
288
+ async approveChildWorkflow(workflowId: string) {
289
+ return this.approveWorkflow(workflowId);
290
+ }
291
+
292
+ async getChildWorkflow(workflowId: string) {
293
+ return this.getWorkflow(workflowId);
294
+ }
295
+ }
296
+ ```
297
+
298
+ For sub-agent origins, `AgentWorkflow.agent` is an RPC-only stub. Use it to call Agent methods, but use `routeSubAgentRequest()` or the `/agents/{parent}/{name}/sub/{child}/{name}` URL shape for external HTTP or WebSocket routing instead of `this.agent.fetch()`.
299
+
300
+ ##### Routing constraints
301
+
302
+ Because the originating identity is persisted durably in the workflow params and replayed on every callback, a few constraints apply to all workflows (sub-agent and top-level alike):
303
+
304
+ - **Callbacks resolve the Agent by name.** The runtime re-resolves the originating Agent with `getAgentByName(...)`. If you addressed the Agent by a raw Durable Object id (`idFromString` / `get(id)`) instead of by name, callbacks land on a different instance. Start workflows from name-addressed Agents.
305
+ - **Class names must survive bundling.** The originating path is keyed by `constructor.name`. Configure your bundler to preserve class names (esbuild `keepNames: true`) so progress, completion, and `this.agent` RPC can be routed back to the right facet.
306
+ - **`agentBinding` is the root binding.** When you pass `options.agentBinding` from a sub-agent, use the **root** Agent's Durable Object binding name, not a child binding.
307
+
308
+ #### `sendWorkflowEvent(workflowName, instanceId, event)`
309
+
310
+ Send an event to a running workflow.
311
+
312
+ ```typescript
313
+ await this.sendWorkflowEvent("MY_WORKFLOW", instanceId, {
314
+ type: "approval",
315
+ payload: { approved: true }
316
+ });
317
+ ```
318
+
319
+ #### `getWorkflowStatus(workflowName, instanceId)`
320
+
321
+ Get the status of a workflow and update tracking record.
322
+
323
+ ```typescript
324
+ const status = await this.getWorkflowStatus("MY_WORKFLOW", instanceId);
325
+ // status: { status: 'running', output: null, error: null }
326
+ ```
327
+
328
+ #### `getWorkflow(instanceId)`
329
+
330
+ Get a tracked workflow by ID.
331
+
332
+ ```typescript
333
+ const workflow = this.getWorkflow(instanceId);
334
+ // { instanceId, workflowName, status, metadata, error, createdAt, ... }
335
+ ```
336
+
337
+ #### `getWorkflows(criteria?)`
338
+
339
+ Query tracked workflows with cursor-based pagination. Returns a `WorkflowPage` with workflows, total count, and cursor for the next page.
340
+
341
+ > **Scoping:** `getWorkflows()` and `getWorkflowById()` only see workflows tracked in **this** Agent's storage. Workflows started by a sub-agent are tracked in that sub-agent's own facet storage, so a parent will not see child-started runs. To build a combined view, expose a wrapper method on each child (e.g. `listMyWorkflows()`) and aggregate the results across your sub-agents yourself.
342
+
343
+ ```typescript
344
+ // Get running workflows (default limit is 50, max is 100)
345
+ const { workflows, total } = this.getWorkflows({ status: "running" });
346
+
347
+ // Get workflows by binding name
348
+ const { workflows: processing } = this.getWorkflows({
349
+ workflowName: "PROCESSING_WORKFLOW"
350
+ });
351
+
352
+ // Filter by metadata
353
+ const { workflows: userWorkflows } = this.getWorkflows({
354
+ metadata: { userId: "user-456" }
355
+ });
356
+
357
+ // Pagination example
358
+ const page1 = this.getWorkflows({
359
+ status: ["complete", "errored"],
360
+ limit: 20,
361
+ orderBy: "desc"
362
+ });
363
+
364
+ console.log(`Showing ${page1.workflows.length} of ${page1.total} workflows`);
365
+
366
+ // Get next page using cursor
367
+ if (page1.nextCursor) {
368
+ const page2 = this.getWorkflows({
369
+ status: ["complete", "errored"],
370
+ limit: 20,
371
+ orderBy: "desc",
372
+ cursor: page1.nextCursor
373
+ });
374
+ }
375
+ ```
376
+
377
+ The `WorkflowPage` type:
378
+
379
+ ```typescript
380
+ type WorkflowPage = {
381
+ workflows: WorkflowInfo[];
382
+ total: number; // Total matching workflows
383
+ nextCursor: string | null; // null when no more pages
384
+ };
385
+ ```
386
+
387
+ #### `deleteWorkflow(instanceId)`
388
+
389
+ Delete a single workflow tracking record.
390
+
391
+ ```typescript
392
+ const deleted = this.deleteWorkflow(instanceId);
393
+ // true if deleted, false if not found
394
+ ```
395
+
396
+ #### `deleteWorkflows(criteria?)`
397
+
398
+ Delete workflow tracking records matching criteria. Useful for cleanup.
399
+
400
+ ```typescript
401
+ // Delete all completed workflows older than 7 days
402
+ const count = this.deleteWorkflows({
403
+ status: "complete",
404
+ createdBefore: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
405
+ });
406
+
407
+ // Delete all errored and terminated workflows
408
+ const count = this.deleteWorkflows({
409
+ status: ["errored", "terminated"]
410
+ });
411
+ ```
412
+
413
+ #### `terminateWorkflow(instanceId)`
414
+
415
+ Terminate a running workflow immediately.
416
+
417
+ ```typescript
418
+ await this.terminateWorkflow(instanceId);
419
+ ```
420
+
421
+ This stops the workflow and sets its status to `"terminated"`. Throws if the workflow is not found in the tracking table. Cloudflare will throw if the workflow is already completed, errored, or terminated.
422
+
423
+ #### `pauseWorkflow(instanceId)`
424
+
425
+ Pause a running workflow. The workflow can be resumed later with `resumeWorkflow()`.
426
+
427
+ ```typescript
428
+ await this.pauseWorkflow(instanceId);
429
+ ```
430
+
431
+ Throws if the workflow is not running. Cloudflare will throw if the workflow is already paused, completed, errored, or terminated.
432
+
433
+ #### `resumeWorkflow(instanceId)`
434
+
435
+ Resume a paused workflow.
436
+
437
+ ```typescript
438
+ await this.resumeWorkflow(instanceId);
439
+ ```
440
+
441
+ Throws if the workflow is not paused. Cloudflare will throw if the workflow is already running, completed, errored, or terminated.
442
+
443
+ #### `restartWorkflow(instanceId, options?)`
444
+
445
+ Restart a workflow instance from the beginning with the same ID.
446
+
447
+ ```typescript
448
+ // Reset tracking (default) - clears timestamps and error fields
449
+ await this.restartWorkflow(instanceId);
450
+
451
+ // Preserve original timestamps
452
+ await this.restartWorkflow(instanceId, { resetTracking: false });
453
+ ```
454
+
455
+ This is useful for re-running failed workflows or retrying from scratch. The `resetTracking` option (default: `true`) controls whether to reset the `created_at` timestamp and clear error fields.
456
+
457
+ ### Lifecycle Callbacks
458
+
459
+ Override these methods in your Agent to handle workflow events:
460
+
461
+ ```typescript
462
+ class MyAgent extends Agent {
463
+ // Called when workflow reports progress (progress is typed object)
464
+ async onWorkflowProgress(
465
+ workflowName: string,
466
+ instanceId: string,
467
+ progress: unknown
468
+ ) {
469
+ // Cast to your progress type
470
+ const p = progress as { step?: string; percent?: number };
471
+ }
472
+
473
+ // Called when workflow completes successfully
474
+ async onWorkflowComplete(
475
+ workflowName: string,
476
+ instanceId: string,
477
+ result?: unknown
478
+ ) {}
479
+
480
+ // Called when workflow encounters an error
481
+ async onWorkflowError(
482
+ workflowName: string,
483
+ instanceId: string,
484
+ error: string
485
+ ) {}
486
+
487
+ // Called when workflow sends a custom event
488
+ async onWorkflowEvent(
489
+ workflowName: string,
490
+ instanceId: string,
491
+ event: unknown
492
+ ) {}
493
+
494
+ // Handle all callbacks in one place (alternative)
495
+ async onWorkflowCallback(callback: WorkflowCallback) {
496
+ // Called for all callback types - callback includes workflowName
497
+ }
498
+ }
499
+ ```
500
+
501
+ ### Approval Methods
502
+
503
+ Convenience methods for human-in-the-loop approval flows:
504
+
505
+ ```typescript
506
+ class MyAgent extends Agent {
507
+ // Approve a waiting workflow
508
+ async handleApproval(instanceId: string, userId: string) {
509
+ await this.approveWorkflow(instanceId, {
510
+ reason: "Approved by admin",
511
+ metadata: { approvedBy: userId }
512
+ });
513
+ }
514
+
515
+ // Reject a waiting workflow
516
+ async handleRejection(instanceId: string, reason: string) {
517
+ await this.rejectWorkflow(instanceId, { reason });
518
+ }
519
+ }
520
+ ```
521
+
522
+ ## Workflow Tracking
523
+
524
+ Workflows started with `runWorkflow()` are automatically tracked in the originating Agent's SQLite database. If a sub-agent starts the workflow, the row lives in that sub-agent's `cf_agents_workflows` table, not in the parent's table.
525
+
526
+ ### `cf_agents_workflows` Table
527
+
528
+ | Column | Type | Description |
529
+ | --------------- | ------- | ------------------------------- |
530
+ | `id` | TEXT | Internal row ID |
531
+ | `workflow_id` | TEXT | Cloudflare workflow instance ID |
532
+ | `workflow_name` | TEXT | Workflow binding name |
533
+ | `status` | TEXT | Current status |
534
+ | `metadata` | TEXT | JSON metadata (for querying) |
535
+ | `error_name` | TEXT | Error name (if failed) |
536
+ | `error_message` | TEXT | Error message (if failed) |
537
+ | `created_at` | INTEGER | Unix timestamp |
538
+ | `updated_at` | INTEGER | Unix timestamp |
539
+ | `completed_at` | INTEGER | Unix timestamp (when done) |
540
+
541
+ Note: Workflow params and output are not stored by default. Use `metadata` to store queryable information, and store large payloads in your own tables if needed.
542
+
543
+ ### Workflow Status Values
544
+
545
+ - `queued` - Waiting to start
546
+ - `running` - Currently executing
547
+ - `paused` - Paused by user
548
+ - `waiting` - Waiting for event
549
+ - `complete` - Finished successfully
550
+ - `errored` - Failed with error
551
+ - `terminated` - Manually terminated
552
+
553
+ ## Patterns
554
+
555
+ ### Background Processing with Progress
556
+
557
+ ```typescript
558
+ // Workflow with default progress type
559
+ export class DataProcessingWorkflow extends AgentWorkflow<
560
+ MyAgent,
561
+ ProcessParams
562
+ > {
563
+ async run(event: AgentWorkflowEvent<ProcessParams>, step: AgentWorkflowStep) {
564
+ const params = event.payload;
565
+ const items = params.items;
566
+
567
+ for (let i = 0; i < items.length; i++) {
568
+ await step.do(`process-${i}`, async () => {
569
+ await processItem(items[i]);
570
+ });
571
+
572
+ // Report progress after each item (non-durable, lightweight)
573
+ await this.reportProgress({
574
+ step: `process-${i}`,
575
+ status: "complete",
576
+ percent: (i + 1) / items.length,
577
+ message: `Processed ${i + 1}/${items.length}`
578
+ });
579
+ }
580
+
581
+ await step.reportComplete({ processed: items.length });
582
+ }
583
+ }
584
+
585
+ // Agent
586
+ class MyAgent extends Agent {
587
+ async onWorkflowProgress(
588
+ workflowName: string,
589
+ instanceId: string,
590
+ progress: unknown
591
+ ) {
592
+ // Broadcast progress to all connected clients
593
+ this.broadcast(
594
+ JSON.stringify({
595
+ type: "processing-progress",
596
+ workflowName,
597
+ instanceId,
598
+ progress
599
+ })
600
+ );
601
+ }
602
+ }
603
+ ```
604
+
605
+ ### Human-in-the-Loop Approval
606
+
607
+ ```typescript
608
+ // Workflow using the built-in waitForApproval helper
609
+ export class ApprovalWorkflow extends AgentWorkflow<MyAgent, RequestParams> {
610
+ async run(event: AgentWorkflowEvent<RequestParams>, step: AgentWorkflowStep) {
611
+ const params = event.payload;
612
+
613
+ // Prepare request
614
+ const request = await step.do("prepare", async () => {
615
+ return { ...params, preparedAt: Date.now() };
616
+ });
617
+
618
+ // Wait for approval (throws WorkflowRejectedError if rejected)
619
+ await this.reportProgress({
620
+ step: "approval",
621
+ status: "pending",
622
+ percent: 0.5,
623
+ message: "Awaiting approval"
624
+ });
625
+ const approvalData = await this.waitForApproval<{ approvedBy: string }>(
626
+ step,
627
+ { timeout: "7 days" }
628
+ );
629
+
630
+ console.log("Approved by:", approvalData?.approvedBy);
631
+
632
+ // Execute approved action
633
+ const result = await step.do("execute", async () => {
634
+ return executeRequest(request);
635
+ });
636
+
637
+ await step.reportComplete(result);
638
+ return result;
639
+ }
640
+ }
641
+
642
+ // Agent using the built-in approval methods
643
+ class MyAgent extends Agent {
644
+ // Approve a waiting workflow
645
+ async handleApproval(instanceId: string, userId: string) {
646
+ await this.approveWorkflow(instanceId, {
647
+ reason: "Approved by admin",
648
+ metadata: { approvedBy: userId }
649
+ });
650
+ }
651
+
652
+ // Reject a waiting workflow
653
+ async handleRejection(instanceId: string, reason: string) {
654
+ await this.rejectWorkflow(instanceId, { reason });
655
+ }
656
+ }
657
+ ```
658
+
659
+ ### Durable Task Queue with Retries
660
+
661
+ ```typescript
662
+ // Workflow with built-in retry logic
663
+ export class ResilientTaskWorkflow extends AgentWorkflow<MyAgent, TaskParams> {
664
+ async run(event: AgentWorkflowEvent<TaskParams>, step: AgentWorkflowStep) {
665
+ const params = event.payload;
666
+
667
+ const result = await step.do(
668
+ "call-external-api",
669
+ {
670
+ retries: {
671
+ limit: 5,
672
+ delay: "10 seconds",
673
+ backoff: "exponential"
674
+ },
675
+ timeout: "5 minutes"
676
+ },
677
+ async () => {
678
+ const response = await fetch("https://api.example.com/process", {
679
+ method: "POST",
680
+ body: JSON.stringify(params)
681
+ });
682
+
683
+ if (!response.ok) {
684
+ throw new Error(`API error: ${response.status}`);
685
+ }
686
+
687
+ return response.json();
688
+ }
689
+ );
690
+
691
+ await step.reportComplete(result);
692
+ return result;
693
+ }
694
+ }
695
+ ```
696
+
697
+ ### State Synchronization
698
+
699
+ Workflows can update the Agent's state directly (durably via step), which automatically broadcasts to all connected clients:
700
+
701
+ ```typescript
702
+ // Workflow that syncs state to Agent
703
+ export class ProcessingWorkflow extends AgentWorkflow<MyAgent, TaskParams> {
704
+ async run(event: AgentWorkflowEvent<TaskParams>, step: AgentWorkflowStep) {
705
+ const params = event.payload;
706
+
707
+ // Update Agent state (durable, replaces entire state, broadcasts to clients)
708
+ await step.updateAgentState({
709
+ currentTask: {
710
+ id: params.taskId,
711
+ status: "processing",
712
+ startedAt: Date.now()
713
+ }
714
+ });
715
+
716
+ const result = await step.do("process", async () => {
717
+ return processTask(params);
718
+ });
719
+
720
+ // Merge partial state (durable, keeps existing fields, broadcasts to clients)
721
+ await step.mergeAgentState({
722
+ currentTask: {
723
+ status: "complete",
724
+ result,
725
+ completedAt: Date.now()
726
+ }
727
+ });
728
+
729
+ await step.reportComplete(result);
730
+ return result;
731
+ }
732
+ }
733
+ ```
734
+
735
+ ### Custom Progress Types
736
+
737
+ Define custom progress types for domain-specific reporting:
738
+
739
+ ```typescript
740
+ // Custom progress type for data pipeline
741
+ type PipelineProgress = {
742
+ stage: "extract" | "transform" | "load";
743
+ recordsProcessed: number;
744
+ totalRecords: number;
745
+ currentTable?: string;
746
+ };
747
+
748
+ // Workflow with custom progress type (3rd type parameter)
749
+ export class ETLWorkflow extends AgentWorkflow<
750
+ MyAgent,
751
+ ETLParams,
752
+ PipelineProgress
753
+ > {
754
+ async run(event: AgentWorkflowEvent<ETLParams>, step: AgentWorkflowStep) {
755
+ const params = event.payload;
756
+
757
+ // Report typed progress (non-durable, lightweight for frequent updates)
758
+ await this.reportProgress({
759
+ stage: "extract",
760
+ recordsProcessed: 0,
761
+ totalRecords: 1000,
762
+ currentTable: "users"
763
+ });
764
+
765
+ // ... processing
766
+ }
767
+ }
768
+
769
+ // Agent receives typed progress
770
+ class MyAgent extends Agent {
771
+ async onWorkflowProgress(
772
+ workflowName: string,
773
+ instanceId: string,
774
+ progress: unknown
775
+ ) {
776
+ const p = progress as PipelineProgress;
777
+ console.log(`Stage: ${p.stage}, ${p.recordsProcessed}/${p.totalRecords}`);
778
+ }
779
+ }
780
+ ```
781
+
782
+ ## Bidirectional Communication
783
+
784
+ ### Workflow → Agent
785
+
786
+ ```typescript
787
+ // Direct RPC call (typed)
788
+ await this.agent.updateTaskStatus(taskId, "processing");
789
+ const data = await this.agent.getData(taskId);
790
+
791
+ // Non-durable callbacks (may repeat on retry, use for frequent updates)
792
+ await this.reportProgress({
793
+ step: "process",
794
+ percent: 0.5,
795
+ message: "Halfway done"
796
+ });
797
+ this.broadcastToClients({ type: "update", data });
798
+
799
+ // Durable callbacks via step (idempotent, won't repeat on retry)
800
+ await step.reportComplete(result);
801
+ await step.reportError("Something went wrong");
802
+ await step.sendEvent({ type: "custom", data: {} });
803
+
804
+ // Durable state synchronization via step (broadcasts to clients)
805
+ await step.updateAgentState({ status: "processing" });
806
+ await step.mergeAgentState({ progress: 0.5 });
807
+ ```
808
+
809
+ If a sub-agent started the workflow, `this.agent` routes RPC back to that originating facet. It does not support `.fetch()`; use `routeSubAgentRequest()` or the nested `/agents/.../sub/...` URL shape for external HTTP or WebSocket traffic.
810
+
811
+ ### Agent → Workflow
812
+
813
+ ```typescript
814
+ // Send event to waiting workflow (generic)
815
+ await this.sendWorkflowEvent("MY_WORKFLOW", instanceId, {
816
+ type: "custom-event",
817
+ payload: { action: "proceed" }
818
+ });
819
+
820
+ // Approve/reject workflows using convenience methods
821
+ await this.approveWorkflow(instanceId, {
822
+ reason: "Approved by admin",
823
+ metadata: { approvedBy: userId }
824
+ });
825
+
826
+ await this.rejectWorkflow(instanceId, {
827
+ reason: "Request denied"
828
+ });
829
+
830
+ // The workflow waits for approval with:
831
+ const approvalData = await this.waitForApproval(step, { timeout: "7 days" });
832
+ ```
833
+
834
+ ## Best Practices
835
+
836
+ 1. **Keep workflows focused** - One workflow per logical task
837
+ 2. **Use meaningful step names** - Helps with debugging and observability
838
+ 3. **Report progress regularly** - Keeps users informed
839
+ 4. **Handle errors gracefully** - Use `reportError()` before throwing
840
+ 5. **Clean up completed workflows** - The `cf_agents_workflows` table can grow unbounded, so implement a retention policy:
841
+
842
+ ```typescript
843
+ // Option 1: Cleanup immediately on completion
844
+ async onWorkflowComplete(workflowName, instanceId, result) {
845
+ // Process result first, then delete
846
+ this.deleteWorkflow(instanceId);
847
+ }
848
+
849
+ // Option 2: Scheduled cleanup (keep recent history)
850
+ // Call this periodically via a scheduled task or cron
851
+ this.deleteWorkflows({
852
+ status: ["complete", "errored"],
853
+ createdBefore: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) // 7 days
854
+ });
855
+
856
+ // Option 3: Keep all history for compliance/auditing
857
+ // Don't call deleteWorkflows() - query historical data as needed
858
+ ```
859
+
860
+ 6. **Handle workflow binding renames carefully** - If you rename a workflow binding in `wrangler.jsonc`, existing tracked workflows will reference the old name. The agent will warn on startup if it detects this. Use `migrateWorkflowBinding()` to update them:
861
+
862
+ ```typescript
863
+ // After renaming OLD_WORKFLOW to NEW_WORKFLOW in wrangler.toml
864
+ async onStart() {
865
+ // Migrate any existing tracked workflows to the new binding name
866
+ const migrated = this.migrateWorkflowBinding('OLD_WORKFLOW', 'NEW_WORKFLOW');
867
+ // You can remove this code after all agents have migrated
868
+ }
869
+ ```
870
+
871
+ ## Limitations
872
+
873
+ - Workflows can have at most 1,024 steps
874
+ - Maximum 10MB state per workflow
875
+ - Events wait for at most 1 year
876
+ - No direct WebSocket from workflows (use `broadcastToClients()`)
877
+ - Workflow execution time: up to 30 minutes per step