agents 0.23.0 → 0.25.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.
- package/dist/{agent-routing-DE5zmCQ8.d.ts → agent-routing-0DR0Tk2v.d.ts} +270 -190
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-routing.js.map +1 -1
- package/dist/agent-tool-types.d.ts +13 -7
- package/dist/agent-tool-types.js +23 -1
- package/dist/agent-tool-types.js.map +1 -1
- package/dist/{agent-tools-y7zLfw4Q.js → agent-tools-CdJC-8Dc.js} +72 -9
- package/dist/agent-tools-CdJC-8Dc.js.map +1 -0
- package/dist/{agent-tools-DtXMTDGM.d.ts → agent-tools-YrATZJu2.d.ts} +29 -15
- package/dist/agent-tools.d.ts +6 -4
- package/dist/agent-tools.js.map +1 -1
- package/dist/ai-fm7xx1BK.js +312 -0
- package/dist/ai-fm7xx1BK.js.map +1 -0
- package/dist/base64-redaction-C1Uu29t5.js +59 -0
- package/dist/base64-redaction-C1Uu29t5.js.map +1 -0
- package/dist/browser/ai-sdk.d.ts +90 -0
- package/dist/browser/ai-sdk.js +51 -0
- package/dist/browser/ai-sdk.js.map +1 -0
- package/dist/browser/ai.d.ts +4 -4
- package/dist/browser/ai.js +1 -347
- package/dist/browser/index.d.ts +81 -79
- package/dist/browser/index.js +361 -2
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/tanstack-ai.d.ts +96 -1
- package/dist/browser/tanstack-ai.js +62 -2
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/browser-DE3d-RLk.d.ts +275 -0
- package/dist/browser-tool-CQ10e6Kj.js +480 -0
- package/dist/browser-tool-CQ10e6Kj.js.map +1 -0
- package/dist/browser-tool-DuNdlwZ9.d.ts +73 -0
- package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
- package/dist/capability-B4WbF81e.js.map +1 -0
- package/dist/{capability-runner-Be_-PLR1.d.ts → capability-runner-DBAnGr6L.d.ts} +45 -15
- package/dist/chat/index.d.ts +341 -232
- package/dist/chat/index.js +602 -72
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +42 -2
- package/dist/chat/react.js +191 -35
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/transport.d.ts +1 -1
- package/dist/chat/transport.js +1 -1
- package/dist/chat-sdk/index.d.ts +17 -3
- package/dist/chat-sdk/index.js +28 -2
- package/dist/chat-sdk/index.js.map +1 -1
- package/dist/client-4OlMIJx-.js +591 -0
- package/dist/client-4OlMIJx-.js.map +1 -0
- package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
- package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
- package/dist/client-je3koD9Z.d.ts +446 -0
- package/dist/client.d.ts +29 -283
- package/dist/client.js +2 -274
- package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
- package/dist/connector-58XDGvc3.js.map +1 -0
- package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
- package/dist/context/index.d.ts +29 -0
- package/dist/context/index.js +64 -17
- package/dist/context/index.js.map +1 -1
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- package/dist/current-agent-vAk_5JMt.d.ts +93 -0
- package/dist/{current-agent-Da_C9a3b.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +26 -127
- package/dist/harnesses/pi/index.d.ts +348 -0
- package/dist/harnesses/pi/index.js +754 -0
- package/dist/harnesses/pi/index.js.map +1 -0
- package/dist/images-iiTDSyRu.js +1631 -0
- package/dist/images-iiTDSyRu.js.map +1 -0
- package/dist/index-BreL0YrQ.d.ts +78 -0
- package/dist/index-kbSUIer3.d.ts +343 -0
- package/dist/index.d.ts +43 -41
- package/dist/index.js +2 -2
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +19 -17
- package/dist/lifecycle/index.js +2 -2
- package/dist/{lifecycle-CMRGjZdw.js → lifecycle-CFu6OV03.js} +73 -29
- package/dist/lifecycle-CFu6OV03.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/client/x402.js +21 -19
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +2 -2
- package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
- package/dist/message-builder-DAl9AhHH.js.map +1 -0
- package/dist/models/ai-sdk/index.d.ts +353 -0
- package/dist/models/ai-sdk/index.js +2443 -0
- package/dist/models/ai-sdk/index.js.map +1 -0
- package/dist/models/pi-ai/index.d.ts +313 -0
- package/dist/models/pi-ai/index.js +1795 -0
- package/dist/models/pi-ai/index.js.map +1 -0
- package/dist/observability/ai/index.js +20 -4
- package/dist/observability/ai/index.js.map +1 -1
- package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
- package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
- package/dist/queue/index.d.ts +123 -0
- package/dist/queue/index.js +2 -0
- package/dist/queue-BB-zfIYF.js +406 -0
- package/dist/queue-BB-zfIYF.js.map +1 -0
- package/dist/react.d.ts +31 -13
- package/dist/react.js +190 -71
- package/dist/react.js.map +1 -1
- package/dist/{retries-D9Ds-1lz.d.ts → retries-CV6AkXmQ.d.ts} +23 -8
- package/dist/retries.d.ts +12 -8
- package/dist/retries.js +19 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +2 -2
- package/dist/routing/index.js +1 -1
- package/dist/{scheduler-DD9NdYbF.js → scheduler-DZ3xQfeK.js} +4 -11
- package/dist/scheduler-DZ3xQfeK.js.map +1 -0
- package/dist/{scheduler-Dwh85ZGl.d.ts → scheduler-RdACNXDP.d.ts} +6 -6
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +70 -3
- package/dist/sessions/index.js +276 -67
- package/dist/sessions/index.js.map +1 -1
- package/dist/skills/index.d.ts +29 -314
- package/dist/socket-address.d.ts +16 -0
- package/dist/socket-address.js +19 -0
- package/dist/socket-address.js.map +1 -0
- package/dist/{src-DlSHshb2.js → src-BZeu-SCD.js} +541 -500
- package/dist/src-BZeu-SCD.js.map +1 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.js +137 -0
- package/dist/state/index.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-D6tJ0NN9.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
- package/dist/{streams-DZKgAj9b.js → streams-DW-UCOUc.js} +9 -2
- package/dist/streams-DW-UCOUc.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-ylZgBjhj.js → tasks-68DESxf0.js} +3 -3
- package/dist/{tasks-ylZgBjhj.js.map → tasks-68DESxf0.js.map} +1 -1
- package/dist/{tasks-BRJ5zgya.d.ts → tasks-9YxYtByh.d.ts} +5 -5
- package/dist/transport-PfFnt-Ld.d.ts +525 -0
- package/dist/transport-protocol-BxyngQ_R.js +25 -0
- package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
- package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
- package/dist/types-BA4anrjI.d.ts +70 -0
- package/dist/voice/index.d.ts +2 -2
- package/dist/websockets/index.d.ts +195 -69
- package/dist/websockets/index.js +3 -2
- package/dist/websockets-Ci-LZFHH.js +1030 -0
- package/dist/websockets-Ci-LZFHH.js.map +1 -0
- package/dist/wire-types-CnMt6_HR.js.map +1 -1
- package/dist/wire-types-CrjKvDyi.d.ts +300 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +21 -21
- package/dist/workflows.js +1 -1
- package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
- package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
- package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
- package/docs/agent-class.md +2 -2
- package/docs/agent-tools.md +29 -4
- package/docs/browse-the-web.md +87 -5
- package/docs/chat-agents.md +124 -16
- package/docs/client-sdk.md +30 -0
- package/docs/context.md +27 -0
- package/docs/durable-execution.md +2 -0
- package/docs/harnesses/pi.md +91 -0
- package/docs/human-in-the-loop.md +4 -0
- package/docs/index.md +4 -1
- package/docs/lifecycle.md +114 -23
- package/docs/models-pi-ai.md +325 -0
- package/docs/models.md +504 -0
- package/docs/queue.md +135 -241
- package/docs/resumable-streaming.md +2 -0
- package/docs/retries.md +1 -1
- package/docs/routing.md +13 -13
- package/docs/sessions.md +36 -1
- package/docs/state.md +1 -0
- package/docs/sub-agents.md +12 -3
- package/package.json +51 -3
- package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
- package/dist/browser/ai.js.map +0 -1
- package/dist/capability-BjSKYpzg.js.map +0 -1
- package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
- package/dist/client.js.map +0 -1
- package/dist/connector-CptFKzRh.js.map +0 -1
- package/dist/lifecycle-CMRGjZdw.js.map +0 -1
- package/dist/message-builder-BymO4N_D.js.map +0 -1
- package/dist/scheduler-DD9NdYbF.js.map +0 -1
- package/dist/src-DlSHshb2.js.map +0 -1
- package/dist/streams-DZKgAj9b.js.map +0 -1
- package/dist/websockets-DUfRHPRq.js +0 -502
- package/dist/websockets-DUfRHPRq.js.map +0 -1
- package/dist/ws-chat-transport-rWwta645.js.map +0 -1
package/docs/queue.md
CHANGED
|
@@ -1,218 +1,157 @@
|
|
|
1
|
-
# Queue
|
|
1
|
+
# Queue
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Durable background work for an Agent or any Lifecycle Object. A queued item
|
|
4
|
+
persists a callback name and a payload, runs from the Durable Object's alarm
|
|
5
|
+
loop one item at a time in push order, retries when the callback throws, and
|
|
6
|
+
survives the object leaving memory.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## Queue Lifecycle capability
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
> **Experimental.** The `Queue` capability and the `agents/lifecycle` surface
|
|
11
|
+
> it builds on may change between releases. Agent's established queue methods
|
|
12
|
+
> (`this.queue()` and friends) are stable.
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
`Queue` is a reusable Lifecycle capability. A plain Lifecycle Object can
|
|
15
|
+
install it without extending `Agent`:
|
|
10
16
|
|
|
11
17
|
```typescript
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
callback: keyof Agent<unknown>; // Name of the method to call
|
|
16
|
-
created_at: number; // Timestamp when the task was created
|
|
17
|
-
retry?: RetryOptions; // Retry options (if configured)
|
|
18
|
-
};
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Core Methods
|
|
22
|
-
|
|
23
|
-
### queue()
|
|
24
|
-
|
|
25
|
-
Adds a task to the queue for future execution.
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
async queue<T = unknown>(
|
|
29
|
-
callback: keyof this,
|
|
30
|
-
payload: T,
|
|
31
|
-
options?: { retry?: RetryOptions }
|
|
32
|
-
): Promise<string>
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**Parameters:**
|
|
36
|
-
|
|
37
|
-
- `callback`: The name of the method to call when processing the task
|
|
38
|
-
- `payload`: Data to pass to the callback method
|
|
39
|
-
- `options.retry`: Optional retry configuration. See [Retries](./retries.md) for details.
|
|
40
|
-
|
|
41
|
-
**Returns:** The unique ID of the queued task
|
|
42
|
-
|
|
43
|
-
**Example:**
|
|
18
|
+
import { DurableObject } from "cloudflare:workers";
|
|
19
|
+
import { Lifecycle } from "agents/lifecycle";
|
|
20
|
+
import { Queue, type QueueItem } from "agents/queue";
|
|
44
21
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
22
|
+
export class UploadObject extends DurableObject<Env> {
|
|
23
|
+
readonly queue = new Queue({
|
|
24
|
+
callbacks: {
|
|
25
|
+
processUpload: (
|
|
26
|
+
payload: { fileName: string },
|
|
27
|
+
item: QueueItem<{ fileName: string }>
|
|
28
|
+
) => {
|
|
29
|
+
console.log(item.id, payload.fileName);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
// Queue an email processing task
|
|
54
|
-
const taskId = await this.queue("processEmail", {
|
|
55
|
-
email: "user@example.com",
|
|
56
|
-
subject: "Welcome!"
|
|
57
|
-
});
|
|
34
|
+
readonly lifecycle = Lifecycle.install(this).use(this.queue);
|
|
58
35
|
|
|
59
|
-
|
|
36
|
+
async enqueueUpload(fileName: string): Promise<string> {
|
|
37
|
+
const item = await this.queue.push("processUpload", { fileName });
|
|
38
|
+
return item.id;
|
|
60
39
|
}
|
|
61
40
|
}
|
|
62
41
|
```
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
dequeue(id: string): void
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**Parameters:**
|
|
73
|
-
|
|
74
|
-
- `id`: The ID of the task to remove
|
|
75
|
-
|
|
76
|
-
**Example:**
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
// Remove a specific task
|
|
80
|
-
agent.dequeue("abc123def");
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### dequeueAll()
|
|
84
|
-
|
|
85
|
-
Removes all tasks from the queue.
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
dequeueAll(): void
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**Example:**
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
// Clear the entire queue
|
|
95
|
-
agent.dequeueAll();
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### dequeueAllByCallback()
|
|
99
|
-
|
|
100
|
-
Removes all tasks that match a specific callback method.
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
dequeueAllByCallback(callback: string): void
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
**Parameters:**
|
|
107
|
-
|
|
108
|
-
- `callback`: Name of the callback method
|
|
109
|
-
|
|
110
|
-
**Example:**
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// Remove all email processing tasks
|
|
114
|
-
agent.dequeueAllByCallback("processEmail");
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### getQueue()
|
|
118
|
-
|
|
119
|
-
Retrieves a specific queued task by ID.
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
getQueue(id: string): QueueItem<string> | undefined
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Parameters:**
|
|
43
|
+
Queue owns no storage of its own. Each item is one job in the Lifecycle job
|
|
44
|
+
queue, due immediately; Lifecycle owns the physical Durable Object alarm, the
|
|
45
|
+
alarm event loop, retry policy, the deadman pre-arm, and the memory-limit
|
|
46
|
+
circuit breaker. See [Durable Object lifecycle](./lifecycle.md#the-job-queue).
|
|
126
47
|
|
|
127
|
-
|
|
48
|
+
Queue's API is small: callbacks are registered by name in the constructor,
|
|
49
|
+
`push()` creates items typed against that registration, and `get()`,
|
|
50
|
+
`list()`, `cancel()`, and `cancelAll()` manage them. All of these are
|
|
51
|
+
asynchronous and work inside routed sub-agents.
|
|
128
52
|
|
|
129
|
-
|
|
53
|
+
Queue Lifecycle hooks run without ambient host context. Registered callbacks
|
|
54
|
+
are user code, so they run inside the host invocation context with the
|
|
55
|
+
Lifecycle Object available through `getCurrentAgent()`.
|
|
130
56
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
**Example:**
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
const task = agent.getQueue("abc123def");
|
|
137
|
-
if (task) {
|
|
138
|
-
console.log(`Task callback: ${task.callback}`);
|
|
139
|
-
console.log(`Task payload:`, task.payload);
|
|
140
|
-
}
|
|
141
|
-
```
|
|
57
|
+
### `new Queue(options?)`
|
|
142
58
|
|
|
143
|
-
|
|
59
|
+
| Option | Description |
|
|
60
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `callbacks` | Named callbacks `(payload, item) => unknown`. Register in a field initializer so names re-bind on every wake. |
|
|
62
|
+
| `retry` | Default retry policy: `{ maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 3000 }`. See [Retries](./retries.md). |
|
|
63
|
+
| `onError` | Observes an item's terminal failure after its attempts are exhausted. Runs without host context. |
|
|
144
64
|
|
|
145
|
-
|
|
65
|
+
### `push(callback, payload?, options?)`
|
|
146
66
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
67
|
+
Queues one item and returns the `QueueItem`. The item is durable before the
|
|
68
|
+
returned promise resolves, and it runs from the next alarm, so the caller
|
|
69
|
+
never waits for the callback.
|
|
150
70
|
|
|
151
|
-
|
|
71
|
+
- `options.retry` overrides the Queue's retry policy for this item.
|
|
72
|
+
- `options.id` sets a stable id. A push with an existing id replaces that
|
|
73
|
+
item in place, keeping its position in the queue (and superseding a
|
|
74
|
+
dispatch of it still in flight); use it for idempotent enqueues keyed by
|
|
75
|
+
your own identifier.
|
|
152
76
|
|
|
153
|
-
|
|
154
|
-
|
|
77
|
+
Once the Lifecycle has started, the item row is written synchronously before
|
|
78
|
+
`push()` returns its promise, so a push made in the same synchronous block as
|
|
79
|
+
your own SQL writes commits atomically with them.
|
|
155
80
|
|
|
156
|
-
|
|
81
|
+
### `get(id)`, `list(criteria?)`, `cancel(id)`, `cancelAll(callback?)`
|
|
157
82
|
|
|
158
|
-
|
|
83
|
+
`list()` returns pending items in push order; `{ callback }` filters by
|
|
84
|
+
callback name. `cancel()` returns whether an item was removed; `cancelAll()`
|
|
85
|
+
returns how many were.
|
|
159
86
|
|
|
160
|
-
|
|
87
|
+
### QueueItem
|
|
161
88
|
|
|
162
89
|
```typescript
|
|
163
|
-
|
|
164
|
-
|
|
90
|
+
type QueueItem<T = unknown> = {
|
|
91
|
+
id: string; // Unique identifier
|
|
92
|
+
callback: string; // Callback name
|
|
93
|
+
payload: T; // Data passed to the callback
|
|
94
|
+
createdAt: number; // Unix timestamp in seconds
|
|
95
|
+
retry?: RetryOptions; // Resolved retry policy
|
|
96
|
+
};
|
|
165
97
|
```
|
|
166
98
|
|
|
167
|
-
##
|
|
168
|
-
|
|
169
|
-
1. **Validation**: When calling `queue()`, the method validates that the callback exists as a function on the agent
|
|
170
|
-
2. **Automatic Processing**: After queuing, the system automatically attempts to flush the queue
|
|
171
|
-
3. **FIFO Order**: Tasks are processed in the order they were created (`created_at` timestamp)
|
|
172
|
-
4. **Context Preservation**: Each queued task runs with the same agent context (connection, request, email)
|
|
173
|
-
5. **Automatic Retries**: If a callback fails, it is retried with exponential backoff (configurable per task)
|
|
174
|
-
6. **Automatic Dequeue**: Tasks are removed from the queue after successful execution or after all retry attempts are exhausted
|
|
175
|
-
7. **Error Handling**: If a callback method does not exist at execution time, an error is logged and the task is skipped
|
|
176
|
-
8. **Persistence**: Tasks are stored in the `cf_agents_queues` table and survive agent restarts
|
|
99
|
+
## Using the queue through Agent
|
|
177
100
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
When defining callback methods for queued tasks, they must follow this signature:
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
async callbackMethod(payload: unknown, queueItem: QueueItem<string>): Promise<void>
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
**Example:**
|
|
101
|
+
`Agent` installs a `Queue` and resolves callback names to its own methods, so
|
|
102
|
+
`this.queue("methodName", payload)` keeps working with no registration:
|
|
187
103
|
|
|
188
104
|
```typescript
|
|
189
105
|
class MyAgent extends Agent {
|
|
190
|
-
async
|
|
191
|
-
payload: {
|
|
192
|
-
|
|
106
|
+
async processEmail(
|
|
107
|
+
payload: { email: string; subject: string },
|
|
108
|
+
item: QueueItem<{ email: string; subject: string }>
|
|
193
109
|
) {
|
|
194
|
-
console.log(`Processing
|
|
195
|
-
console.log(
|
|
196
|
-
`Sending notification to user ${payload.userId}: ${payload.message}`
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
// Your notification logic here
|
|
200
|
-
await this.notificationService.send(payload.userId, payload.message);
|
|
110
|
+
console.log(`Processing ${item.id}: ${payload.subject}`);
|
|
201
111
|
}
|
|
202
112
|
|
|
203
|
-
async
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
message: "Welcome to our platform!"
|
|
113
|
+
async onMessage(message: string) {
|
|
114
|
+
const id = await this.queue("processEmail", {
|
|
115
|
+
email: "user@example.com",
|
|
116
|
+
subject: "Welcome!"
|
|
208
117
|
});
|
|
118
|
+
console.log(`Queued task ${id}`);
|
|
209
119
|
}
|
|
210
120
|
}
|
|
211
121
|
```
|
|
212
122
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
123
|
+
| Method | Description |
|
|
124
|
+
| ------------------------------------ | ------------------------------------------------------------------- |
|
|
125
|
+
| `queue(callback, payload, options?)` | Queue a method by name. Returns the item id. Accepts `retry`, `id`. |
|
|
126
|
+
| `dequeue(id)` | Remove a pending item. Resolves to whether one was removed. |
|
|
127
|
+
| `dequeueAll()` | Remove every pending item. Resolves to the count. |
|
|
128
|
+
| `dequeueAllByCallback(callback)` | Remove every pending item for one callback. Resolves to the count. |
|
|
129
|
+
| `getQueue(id)` | Read a pending item. |
|
|
130
|
+
| `getQueues(key, value)` | Pending items whose payload has `key` equal to `value`. |
|
|
131
|
+
|
|
132
|
+
## How queue processing works
|
|
133
|
+
|
|
134
|
+
1. **Validation**: `queue()` checks the callback exists and validates any
|
|
135
|
+
retry options. Invalid input throws immediately.
|
|
136
|
+
2. **Durable first**: the item is a row in the `cf_agents_jobs` job queue
|
|
137
|
+
before `queue()` resolves. Items created before this capability existed
|
|
138
|
+
are migrated out of the old `cf_agents_queues` table on the next start.
|
|
139
|
+
3. **Alarm-driven**: every push re-arms the Durable Object alarm. The
|
|
140
|
+
Lifecycle event loop runs due items in push order, awaiting each callback
|
|
141
|
+
before starting the next, in a fresh invocation rather than the request
|
|
142
|
+
that queued them.
|
|
143
|
+
4. **Retries**: a throwing callback is retried with exponential backoff per
|
|
144
|
+
its retry policy, emitting `queue:retry` on each extra attempt.
|
|
145
|
+
5. **Terminal failure**: after the last attempt the item is dropped, a
|
|
146
|
+
`queue:error` event is emitted, and Agent's `onError` (or the Queue's
|
|
147
|
+
`onError` option) observes the error.
|
|
148
|
+
6. **Recovery**: an isolate that dies mid-callback wakes again on the
|
|
149
|
+
Lifecycle deadman alarm and resumes the queue, so callbacks should be
|
|
150
|
+
idempotent.
|
|
151
|
+
|
|
152
|
+
## Use cases
|
|
153
|
+
|
|
154
|
+
Queue work that must not block the current request or WebSocket message:
|
|
216
155
|
|
|
217
156
|
```typescript
|
|
218
157
|
class DataProcessor extends Agent {
|
|
@@ -221,52 +160,31 @@ class DataProcessor extends Agent {
|
|
|
221
160
|
await this.notifyUser(data.userId, results);
|
|
222
161
|
}
|
|
223
162
|
|
|
224
|
-
async onDataUpload(uploadData:
|
|
225
|
-
// Queue the processing instead of doing it synchronously
|
|
163
|
+
async onDataUpload(uploadData: { id: string; userId: string }) {
|
|
226
164
|
await this.queue("processLargeDataset", {
|
|
227
165
|
datasetId: uploadData.id,
|
|
228
166
|
userId: uploadData.userId
|
|
229
167
|
});
|
|
230
|
-
|
|
231
168
|
return { message: "Data upload received, processing started" };
|
|
232
169
|
}
|
|
233
170
|
}
|
|
234
171
|
```
|
|
235
172
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
class ReminderAgent extends Agent {
|
|
240
|
-
async sendReminder(data: { userId: string; message: string }) {
|
|
241
|
-
await this.emailService.send(data.userId, data.message);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async scheduleReminder(userId: string, message: string, delayMs: number) {
|
|
245
|
-
// Note: For true delayed execution, combine with the scheduling system
|
|
246
|
-
// This example shows queueing for later processing
|
|
247
|
-
await this.queue("sendReminder", { userId, message });
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### Batch Operations
|
|
173
|
+
Split large requests into batches that run one after another:
|
|
253
174
|
|
|
254
175
|
```typescript
|
|
255
176
|
class BatchProcessor extends Agent {
|
|
256
|
-
async processBatch(data: { items:
|
|
177
|
+
async processBatch(data: { items: unknown[]; batchId: string }) {
|
|
257
178
|
for (const item of data.items) {
|
|
258
179
|
await this.processItem(item);
|
|
259
180
|
}
|
|
260
|
-
console.log(`Completed batch ${data.batchId}`);
|
|
261
181
|
}
|
|
262
182
|
|
|
263
|
-
async onLargeRequest(items:
|
|
264
|
-
// Split large requests into smaller batches
|
|
183
|
+
async onLargeRequest(items: unknown[]) {
|
|
265
184
|
const batchSize = 10;
|
|
266
185
|
for (let i = 0; i < items.length; i += batchSize) {
|
|
267
|
-
const batch = items.slice(i, i + batchSize);
|
|
268
186
|
await this.queue("processBatch", {
|
|
269
|
-
items:
|
|
187
|
+
items: items.slice(i, i + batchSize),
|
|
270
188
|
batchId: `batch-${i / batchSize + 1}`
|
|
271
189
|
});
|
|
272
190
|
}
|
|
@@ -274,56 +192,32 @@ class BatchProcessor extends Agent {
|
|
|
274
192
|
}
|
|
275
193
|
```
|
|
276
194
|
|
|
277
|
-
|
|
195
|
+
For work that should run at a specific time or on a schedule, use
|
|
196
|
+
[Scheduling](./scheduling.md) instead. Both ride the same Lifecycle job queue.
|
|
278
197
|
|
|
279
|
-
|
|
280
|
-
2. **Idempotent Operations**: Design callback methods to be safe to retry
|
|
281
|
-
3. **Error Handling**: Include proper error handling in callback methods
|
|
282
|
-
4. **Monitoring**: Use logging to track queue processing
|
|
283
|
-
5. **Cleanup**: Regularly clean up completed or failed tasks if needed
|
|
198
|
+
## Best practices
|
|
284
199
|
|
|
285
|
-
|
|
200
|
+
1. **Keep payloads small**: payloads are JSON-serialized into the job row.
|
|
201
|
+
2. **Idempotent callbacks**: an item can run again after an isolate reset.
|
|
202
|
+
3. **Bounded callbacks**: the event loop awaits each callback, so a long one
|
|
203
|
+
delays every other job on the object. Detach unbounded work and return.
|
|
204
|
+
4. **Stable ids for dedupe**: pass `id` when the same logical task may be
|
|
205
|
+
queued more than once.
|
|
286
206
|
|
|
287
|
-
|
|
207
|
+
## Error handling and retries
|
|
288
208
|
|
|
289
209
|
```typescript
|
|
290
|
-
// Retry up to 5 times with custom backoff
|
|
291
210
|
await this.queue("reliableTask", payload, {
|
|
292
211
|
retry: { maxAttempts: 5, baseDelayMs: 500 }
|
|
293
212
|
});
|
|
294
213
|
```
|
|
295
214
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
class RobustAgent extends Agent {
|
|
300
|
-
async reliableTask(payload: { data: string }, queueItem: QueueItem<string>) {
|
|
301
|
-
try {
|
|
302
|
-
await this.doSomethingRisky(payload);
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error(`Task ${queueItem.id} failed:`, error);
|
|
305
|
-
// The retry system will catch this error and retry automatically
|
|
306
|
-
throw error;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
See [Retries](./retries.md) for full documentation on retry options and patterns.
|
|
313
|
-
|
|
314
|
-
## Integration with Other Features
|
|
315
|
-
|
|
316
|
-
The queue system works seamlessly with other Agent SDK features:
|
|
317
|
-
|
|
318
|
-
- **State Management**: Access agent state within queued callbacks
|
|
319
|
-
- **Scheduling**: Combine with `schedule()` for time-based queue processing
|
|
320
|
-
- **Retries**: Built-in retry with exponential backoff. See [Retries](./retries.md).
|
|
321
|
-
- **Context**: Queued tasks maintain the original request context
|
|
322
|
-
- **Database**: Uses the same database as other agent data
|
|
215
|
+
See [Retries](./retries.md) for full documentation on retry options, and
|
|
216
|
+
[Observability](./observability.md) for the `queue:create`, `queue:retry`,
|
|
217
|
+
and `queue:error` events.
|
|
323
218
|
|
|
324
219
|
## Limitations
|
|
325
220
|
|
|
326
|
-
-
|
|
327
|
-
- No priority system (FIFO only)
|
|
328
|
-
-
|
|
329
|
-
- Failed tasks are dequeued after all retry attempts are exhausted (no dead-letter queue)
|
|
221
|
+
- Items run sequentially, not in parallel.
|
|
222
|
+
- No priority system (FIFO only).
|
|
223
|
+
- Failed items are dropped after all retry attempts (no dead-letter queue).
|
|
@@ -84,6 +84,8 @@ function Chat() {
|
|
|
84
84
|
|
|
85
85
|
Replayed chunks include `replay: true` to distinguish them from live chunks. The client uses this to batch-apply all replayed chunks before rendering, which prevents intermediate states (like reasoning "Thinking..." indicators) from flashing briefly during replay. During a live stream, chunks arrive gradually and React renders each intermediate state naturally.
|
|
86
86
|
|
|
87
|
+
Live and replayed chunks also carry a `seq` field: the chunk's position in the stream, starting at `0`. A continuation stream (for example, a tool continuation) appends to an assistant message that the client already holds, so replaying it from the start would apply the same text twice. The client remembers the highest `seq` it has applied for each request and skips replayed continuation chunks at or below it. Replays of non-continuation streams rebuild the message from the first chunk and are not filtered.
|
|
88
|
+
|
|
87
89
|
## Durable client cleanup
|
|
88
90
|
|
|
89
91
|
`resume: true` controls whether the client tries to reconnect to an active stream. `cancelOnClientAbort: false` is the default cancellation behavior: generic client stream abort/cleanup is local-only, while explicit `stop()` still cancels the server turn.
|
package/docs/retries.md
CHANGED
|
@@ -188,7 +188,7 @@ await this.queue("processWebhook", webhookData, {
|
|
|
188
188
|
});
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
If the callback throws, it is retried before the
|
|
191
|
+
If the callback throws, it is retried before the item is removed. After all attempts are exhausted, the item is dropped, a `queue:error` event is emitted, and the error is logged.
|
|
192
192
|
|
|
193
193
|
## Validation
|
|
194
194
|
|
package/docs/routing.md
CHANGED
|
@@ -777,9 +777,9 @@ The error message lists available agents. Check:
|
|
|
777
777
|
2. Check that `routeAgentRequest()` is called before your 404 handler
|
|
778
778
|
3. Ensure the response from `routeAgentRequest()` is returned (not just called)
|
|
779
779
|
|
|
780
|
-
### WebSocket
|
|
780
|
+
### WebSocket will not connect
|
|
781
781
|
|
|
782
|
-
1.
|
|
782
|
+
1. Do not modify the response from `routeAgentRequest()` for WebSocket upgrades
|
|
783
783
|
2. Ensure CORS is enabled if connecting from a different origin
|
|
784
784
|
3. Check browser dev tools for the actual error
|
|
785
785
|
|
|
@@ -798,16 +798,16 @@ The error message lists available agents. Check:
|
|
|
798
798
|
|
|
799
799
|
Routes a request to the appropriate agent.
|
|
800
800
|
|
|
801
|
-
| Parameter | Type
|
|
802
|
-
| ------------------------- |
|
|
803
|
-
| `request` | `Request`
|
|
804
|
-
| `env` | `Env`
|
|
805
|
-
| `options.cors` | `boolean \| HeadersInit`
|
|
806
|
-
| `options.props` | `
|
|
807
|
-
| `options.locationHint` | `string`
|
|
808
|
-
| `options.jurisdiction` | `string`
|
|
809
|
-
| `options.onBeforeConnect` | `Function`
|
|
810
|
-
| `options.onBeforeRequest` | `Function`
|
|
801
|
+
| Parameter | Type | Description |
|
|
802
|
+
| ------------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
|
|
803
|
+
| `request` | `Request` | The incoming request |
|
|
804
|
+
| `env` | `Env` | Environment with agent bindings |
|
|
805
|
+
| `options.cors` | `boolean \| HeadersInit` | Enable CORS headers |
|
|
806
|
+
| `options.props` | `object` | Props passed to whichever agent handles the request. Sent as JSON, so use plain data |
|
|
807
|
+
| `options.locationHint` | `string` | Preferred location for agent instances |
|
|
808
|
+
| `options.jurisdiction` | `string` | Data jurisdiction for agent instances |
|
|
809
|
+
| `options.onBeforeConnect` | `Function` | Callback before WebSocket connections |
|
|
810
|
+
| `options.onBeforeRequest` | `Function` | Callback before HTTP requests |
|
|
811
811
|
|
|
812
812
|
**Returns:** `Promise<Response \| undefined>` - Response if matched, undefined if no agent route
|
|
813
813
|
|
|
@@ -821,7 +821,7 @@ Get an agent instance by name for server-side RPC or request forwarding.
|
|
|
821
821
|
| `name` | `string` | Instance name |
|
|
822
822
|
| `options.locationHint` | `string` | Preferred location |
|
|
823
823
|
| `options.jurisdiction` | `string` | Data jurisdiction |
|
|
824
|
-
| `options.props` | `
|
|
824
|
+
| `options.props` | `object` | Initialization properties for `onStart` |
|
|
825
825
|
|
|
826
826
|
**Returns:** `Promise<DurableObjectStub<T>>` - Typed stub for calling agent methods or forwarding requests
|
|
827
827
|
|
package/docs/sessions.md
CHANGED
|
@@ -242,7 +242,23 @@ await session.addCompaction(summary, fromMessageId, toMessageId);
|
|
|
242
242
|
|
|
243
243
|
Sessions stamps each message with a token estimate when the row is written. `compactAfter()` gates on that O(1) aggregate and never reads the transcript to decide whether to compact. Auto-compaction failures are non-fatal: they log, emit `session:error`, and leave the transcript alone.
|
|
244
244
|
|
|
245
|
-
To trim a transcript before handing it to a model, `truncateOlderMessages` is exported from [`agents/chat`](./chat-agents.md), not from `agents/sessions`.
|
|
245
|
+
To trim a transcript before handing it to a model, `truncateOlderMessages` is exported from [`agents/chat`](./chat-agents.md), not from `agents/sessions`. If your tools define `toModelOutput`, a truncated tool output may no longer match the tool's output schema. In that case, pass `toolOutputs: false` to `truncateOlderMessages` and call `truncateOlderToolResults` on the result of `convertToModelMessages`, which truncates what the model reads after `toModelOutput` has run. Provider-executed tool outputs are never truncated.
|
|
246
|
+
|
|
247
|
+
With the default `keepRecent`, the truncation cutoff moves forward by one message whenever a message is added, so every turn rewrites a message near the end of the prompt and invalidates the provider's prompt cache from that point. To keep the prefix stable, move the cutoff in steps and pass the same `keepRecent` to both functions:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const step = 8;
|
|
251
|
+
const keepRecent = history.length <= 4 ? 4 : 4 + ((history.length - 4) % step);
|
|
252
|
+
const truncated = truncateOlderMessages(history, {
|
|
253
|
+
keepRecent,
|
|
254
|
+
toolOutputs: false
|
|
255
|
+
});
|
|
256
|
+
const modelMessages = truncateOlderToolResults(
|
|
257
|
+
await convertToModelMessages(truncated, { tools }),
|
|
258
|
+
truncated,
|
|
259
|
+
{ keepRecent }
|
|
260
|
+
);
|
|
261
|
+
```
|
|
246
262
|
|
|
247
263
|
## Large messages
|
|
248
264
|
|
|
@@ -345,6 +361,25 @@ const unsubscribe = sessions.subscribe(async (event) => {
|
|
|
345
361
|
|
|
346
362
|
`import` fires once per `importMessage()` and carries the row; it is deliberately not an `append`, so a projection does not patch itself per imported row during a migration — it marks itself stale and re-derives once. `compaction` fires when an overlay is stored directly through `addCompaction()`; `compact()` reports its own overlay as `compact`.
|
|
347
363
|
|
|
364
|
+
A host that keeps an in-memory transcript does not need to write that reduction itself. `session.mirror()` subscribes for one session and patches an array in place: an append or update replaces the entry with the same id (or pushes a new row), a delete filters, and a clear empties it:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
const stop = sessions.session("").mirror<UIMessage>({
|
|
368
|
+
get: () => this.messages,
|
|
369
|
+
set: (messages) => {
|
|
370
|
+
this.messages = messages;
|
|
371
|
+
},
|
|
372
|
+
transform: (message) => toCachedMessage(message),
|
|
373
|
+
intercept: async (event) => {
|
|
374
|
+
if (event.type !== "compact") return false;
|
|
375
|
+
this.messages = await reloadFromStorage();
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
`get()` is read on every event, so reassigning the array is safe. `intercept` handles an event instead of the default reduction. Use it for changes an in-place patch cannot express, such as a branch append or a compaction. `onApplied` runs after an append or update was written, with the entry it replaced.
|
|
382
|
+
|
|
348
383
|
This is a local cache-coherence feed, not a cross-object event log. Capability diagnostics are also emitted through Lifecycle observability.
|
|
349
384
|
|
|
350
385
|
## Errors
|
package/docs/state.md
CHANGED
package/docs/sub-agents.md
CHANGED
|
@@ -4,7 +4,7 @@ Dynamic agents are child Durable Objects **colocated under and supervised by** a
|
|
|
4
4
|
|
|
5
5
|
Use dynamic agents for code whose **class or lifecycle the parent owns**: dynamically-loaded or AI-generated code that has no wrangler binding, per-run tool agents, sandboxed components that need isolated storage plus supervised abort/restart. That is what the runtime built facets for.
|
|
6
6
|
|
|
7
|
-
Do **not** use dynamic agents to model an open-ended set of independent peers — many chats, documents, or sessions per user. Those want one top-level Durable Object each plus a per-user index; see [When to use dynamic agents](#when-to-use-dynamic-agents) and [`examples/next/
|
|
7
|
+
Do **not** use dynamic agents to model an open-ended set of independent peers — many chats, documents, or sessions per user. Those want one top-level Durable Object each plus a per-user index; see [When to use dynamic agents](#when-to-use-dynamic-agents) and [`examples/next/routing`](https://github.com/cloudflare/agents/tree/main/examples/next/routing).
|
|
8
8
|
|
|
9
9
|
If you want a parent chat agent to dispatch another chat-capable agent during a
|
|
10
10
|
single turn and render that child's progress inline, use [Agent Tools](./agent-tools.md).
|
|
@@ -246,6 +246,15 @@ The hook receives the **original** request with its URL intact — including the
|
|
|
246
246
|
|
|
247
247
|
WebSocket upgrade requests flow through this hook the same way as plain HTTP. If you return a mutated `Request`, keep the original `Upgrade: websocket` and `Sec-WebSocket-*` headers — cloning via `new Headers(req.headers)` and only adding or replacing entries is the safest recipe.
|
|
248
248
|
|
|
249
|
+
When the hook returns a `Response` for a WebSocket upgrade, the framework accepts the socket and immediately closes it, because browsers hide the status of a failed handshake and reconnecting clients would retry it forever. The close code carries the status:
|
|
250
|
+
|
|
251
|
+
| Returned status | Close code | Client behavior |
|
|
252
|
+
| --------------- | --------------- | --------------------------------------------------------------------- |
|
|
253
|
+
| `4xx` | `4000 + status` | Terminal: `useAgent` and `AgentClient` stop and set `connectionError` |
|
|
254
|
+
| anything else | `1011` | Retried with backoff |
|
|
255
|
+
|
|
256
|
+
The close reason is `Sub-agent connection rejected (<status>)`, so a `404` arrives as code `4404`. At the first `/sub/` hop, a returned `Response` that already carries a `webSocket` is passed through unchanged; deeper hops always close with the mapped code.
|
|
257
|
+
|
|
249
258
|
### `this.parentPath` and `this.selfPath`
|
|
250
259
|
|
|
251
260
|
Root-first ancestor chains. `parentPath` covers strict ancestors; `selfPath` includes the current agent.
|
|
@@ -471,13 +480,13 @@ await user.recordChatActivity(chatId, { title, lastMessage, seq });
|
|
|
471
480
|
|
|
472
481
|
`RoutedAgents` packages the hub side of this pattern: a durable ID-to-Agent catalog plus request and WebSocket forwarding under one route segment. See [Routing to independent Agents](./routing.md#routing-to-independent-agents).
|
|
473
482
|
|
|
474
|
-
Each chat gets its own alarms, placement, and storage budget; deletion is one `chats.delete(id)` call; and "search across all my chats" reads only the index. The example treats that index as a best-effort derived projection: a failed push leaves it stale until the next message, and a push for a deleted chat is refused. See [`examples/next/
|
|
483
|
+
Each chat gets its own alarms, placement, and storage budget; deletion is one `chats.delete(id)` call; and "search across all my chats" reads only the index. The example treats that index as a best-effort derived projection: a failed push leaves it stale until the next message, and a push for a deleted chat is refused. See [`examples/next/routing`](https://github.com/cloudflare/agents/tree/main/examples/next/routing) for the pattern built on `RoutedAgents`, with tests. Idempotency and repair belong to the production design in [`design/rfc-user-chat-durable-objects.md`](https://github.com/cloudflare/agents/blob/main/design/rfc-user-chat-durable-objects.md).
|
|
475
484
|
|
|
476
485
|
## Examples
|
|
477
486
|
|
|
478
487
|
- [`examples/next/dynamic-agents`](https://github.com/cloudflare/agents/tree/main/examples/next/dynamic-agents) — the headline use case: a supervisor stores user-submitted Durable Object code, loads it via Worker Loader, and runs it as facets with isolated storage, supervised abort, and code upgrades over stable state.
|
|
479
488
|
- [`examples/agents-as-tools`](https://github.com/cloudflare/agents/tree/main/examples/agents-as-tools) — per-run child agents as tools with inline streaming.
|
|
480
|
-
- [`examples/multi-ai-chat`](https://github.com/cloudflare/agents/tree/main/examples/multi-ai-chat) — a multi-session chat app built on facet children under one `Inbox`. It works and demonstrates the routing surface, but for many long-lived chats per user prefer the top-level-DO-per-chat pattern in [`examples/next/
|
|
489
|
+
- [`examples/multi-ai-chat`](https://github.com/cloudflare/agents/tree/main/examples/multi-ai-chat) — a multi-session chat app built on facet children under one `Inbox`. It works and demonstrates the routing surface, but for many long-lived chats per user prefer the top-level-DO-per-chat pattern in [`examples/next/routing`](https://github.com/cloudflare/agents/tree/main/examples/next/routing) — see [When to use dynamic agents](#when-to-use-dynamic-agents).
|
|
481
490
|
|
|
482
491
|
## Related
|
|
483
492
|
|