agents 0.16.2 → 0.17.1
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.
- package/README.md +11 -8
- package/dist/{agent-tool-types-CTw3UJUP.d.ts → agent-tool-types-CNyE1iz_.d.ts} +671 -138
- package/dist/agent-tool-types.d.ts +34 -18
- package/dist/agent-tool-types.js +20 -1
- package/dist/agent-tool-types.js.map +1 -0
- package/dist/agent-tools-BXlsuX0d.js +304 -0
- package/dist/agent-tools-BXlsuX0d.js.map +1 -0
- package/dist/agent-tools-CSnyGvJ2.d.ts +119 -0
- package/dist/agent-tools.d.ts +24 -18
- package/dist/agent-tools.js.map +1 -1
- package/dist/ai-chat-agent.d.ts +1 -1
- package/dist/ai-chat-agent.js +1 -2
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration.d.ts +1 -1
- package/dist/ai-chat-v5-migration.js +1 -2
- package/dist/ai-chat-v5-migration.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +1 -2
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.d.ts +1 -7
- package/dist/ai-types.js +2 -8
- package/dist/ai-types.js.map +1 -1
- package/dist/browser/ai.js +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/chat/index.d.ts +2033 -77
- package/dist/chat/index.js +1828 -245
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +602 -0
- package/dist/chat/react.js +1506 -0
- package/dist/chat/react.js.map +1 -0
- package/dist/chat-sdk/index.d.ts +4 -4
- package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
- package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
- package/dist/{client-BXJ9n2f7.js → client-BZ-B3NhC.js} +2 -2
- package/dist/client-BZ-B3NhC.js.map +1 -0
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +76 -57
- package/dist/client.js +33 -5
- package/dist/client.js.map +1 -1
- package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
- package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
- package/dist/email.js +1 -1
- package/dist/email.js.map +1 -1
- package/dist/{index-B7IbEeze.d.ts → index-BRnybD6X.d.ts} +197 -14
- package/dist/index.d.ts +95 -73
- package/dist/index.js +694 -25
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +18 -14
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/index.d.ts +30 -30
- package/dist/mcp/index.js +7 -7
- package/dist/mcp/index.js.map +1 -1
- package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
- package/dist/message-builder-BymO4N_D.js.map +1 -0
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +4 -2
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +123 -110
- package/dist/react.js +44 -11
- package/dist/react.js.map +1 -1
- package/dist/{retries-CwlpAGet.d.ts → retries-CvHJwSuh.d.ts} +15 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +44 -1
- package/dist/retries.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/skills/compile.js +1 -1
- package/dist/skills/compile.js.map +1 -1
- package/dist/skills/index.js +5 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite.js +5 -5
- package/dist/vite.js.map +1 -1
- package/dist/wire-types-nflOzNuU.js +240 -0
- package/dist/wire-types-nflOzNuU.js.map +1 -0
- package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
- package/dist/workflow-types.d.ts +25 -21
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -21
- package/dist/workflows.js +31 -7
- package/dist/workflows.js.map +1 -1
- package/docs/adding-to-existing-project.md +450 -0
- package/docs/agent-class.md +503 -0
- package/docs/agent-tools.md +552 -0
- package/docs/browse-the-web.md +430 -0
- package/docs/callable-methods.md +627 -0
- package/docs/chat-agents.md +1696 -0
- package/docs/chat-sdk.md +181 -0
- package/docs/client-sdk.md +520 -0
- package/docs/client-tools-continuation.md +177 -0
- package/docs/codemode.md +440 -0
- package/docs/configuration.md +775 -0
- package/docs/cross-domain-authentication.md +171 -0
- package/docs/durable-execution.md +537 -0
- package/docs/email.md +663 -0
- package/docs/get-current-agent.md +204 -0
- package/docs/getting-started.md +305 -0
- package/docs/http-websockets.md +668 -0
- package/docs/human-in-the-loop.md +661 -0
- package/docs/index.md +151 -0
- package/docs/long-running-agents.md +730 -0
- package/docs/mcp-client.md +620 -0
- package/docs/mcp-servers.md +526 -0
- package/docs/mcp-transports.md +308 -0
- package/docs/migration-to-ai-sdk-v5.md +96 -0
- package/docs/migration-to-ai-sdk-v6.md +163 -0
- package/docs/observability.md +261 -0
- package/docs/push-notifications.md +367 -0
- package/docs/queue.md +329 -0
- package/docs/readonly-connections.md +278 -0
- package/docs/resumable-streaming.md +127 -0
- package/docs/retries.md +444 -0
- package/docs/routing.md +749 -0
- package/docs/scheduling.md +898 -0
- package/docs/securing-mcp-servers.md +359 -0
- package/docs/server-driven-messages.md +477 -0
- package/docs/sessions.md +1024 -0
- package/docs/state.md +512 -0
- package/docs/sub-agents.md +389 -0
- package/docs/webhooks.md +604 -0
- package/docs/workflows.md +877 -0
- package/package.json +41 -14
- package/dist/agent-tools-3zLG7MgA.js.map +0 -1
- package/dist/agent-tools-DZhI5F6Q.d.ts +0 -14
- package/dist/client-BXJ9n2f7.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
|