agents 0.22.0 → 0.24.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 (215) hide show
  1. package/README.md +20 -1
  2. package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-B2XLNMxq.d.ts} +1094 -443
  3. package/dist/agent-routing.d.ts +1 -1
  4. package/dist/agent-tool-types.d.ts +26 -26
  5. package/dist/{agent-tools-C0XJqWzB.d.ts → agent-tools-Dj5Mzlw7.d.ts} +7 -7
  6. package/dist/agent-tools.d.ts +21 -21
  7. package/dist/browser/ai.js +4 -1
  8. package/dist/browser/ai.js.map +1 -1
  9. package/dist/browser/tanstack-ai.js.map +1 -1
  10. package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
  11. package/dist/callable-decorator.d.ts +18 -0
  12. package/dist/callable-decorator.js +71 -0
  13. package/dist/callable-decorator.js.map +1 -0
  14. package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
  15. package/dist/capability-B4WbF81e.js.map +1 -0
  16. package/dist/capability-runner-BUBa6Ake.d.ts +484 -0
  17. package/dist/channel-Bnm4S7T2.d.ts +491 -0
  18. package/dist/channels/ai-sdk.d.ts +46 -0
  19. package/dist/channels/ai-sdk.js +120 -0
  20. package/dist/channels/ai-sdk.js.map +1 -0
  21. package/dist/channels/email.d.ts +95 -0
  22. package/dist/channels/email.js +323 -0
  23. package/dist/channels/email.js.map +1 -0
  24. package/dist/channels/index.d.ts +233 -0
  25. package/dist/channels/index.js +608 -0
  26. package/dist/channels/index.js.map +1 -0
  27. package/dist/channels/slack.d.ts +140 -0
  28. package/dist/channels/slack.js +614 -0
  29. package/dist/channels/slack.js.map +1 -0
  30. package/dist/channels/tanstack-ai.d.ts +39 -0
  31. package/dist/channels/tanstack-ai.js +17 -0
  32. package/dist/channels/tanstack-ai.js.map +1 -0
  33. package/dist/channels/telegram.d.ts +106 -0
  34. package/dist/channels/telegram.js +427 -0
  35. package/dist/channels/telegram.js.map +1 -0
  36. package/dist/channels/voice.d.ts +45 -0
  37. package/dist/channels/voice.js +122 -0
  38. package/dist/channels/voice.js.map +1 -0
  39. package/dist/chat/index.d.ts +2319 -2010
  40. package/dist/chat/index.js +892 -518
  41. package/dist/chat/index.js.map +1 -1
  42. package/dist/chat-sdk/index.d.ts +7 -7
  43. package/dist/chat-sdk/index.js +1 -1
  44. package/dist/client-BxdkCuVy.d.ts +411 -0
  45. package/dist/client-C9DAjZIY.js +518 -0
  46. package/dist/client-C9DAjZIY.js.map +1 -0
  47. package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
  48. package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
  49. package/dist/client.d.ts +27 -283
  50. package/dist/client.js +2 -274
  51. package/dist/context/index.d.ts +216 -0
  52. package/dist/context/index.js +454 -0
  53. package/dist/context/index.js.map +1 -0
  54. package/dist/current-agent-C4w86kcN.d.ts +93 -0
  55. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  56. package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
  57. package/dist/diagnostics-BzvaX2UT.js.map +1 -0
  58. package/dist/diagnostics-C4jcz3VK.js +360 -0
  59. package/dist/diagnostics-C4jcz3VK.js.map +1 -0
  60. package/dist/durable-object-lifecycle-ei7M7lqB.d.ts +157 -0
  61. package/dist/index-BB0kqhIz.d.ts +101 -0
  62. package/dist/index-BVVgDSdq.d.ts +1 -0
  63. package/dist/index-Dlgtd3Mj.d.ts +78 -0
  64. package/dist/index-XDkuQ7zm.d.ts +89 -0
  65. package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
  66. package/dist/index.d.ts +92 -83
  67. package/dist/index.js +4 -3
  68. package/dist/ingress-BfetZbMO.js +83 -0
  69. package/dist/ingress-BfetZbMO.js.map +1 -0
  70. package/dist/internal-CYlgHl1l.js +59 -0
  71. package/dist/internal-CYlgHl1l.js.map +1 -0
  72. package/dist/internal_context.d.ts +1 -1
  73. package/dist/lifecycle/index.d.ts +39 -19
  74. package/dist/lifecycle/index.js +2 -2
  75. package/dist/lifecycle-Mm_jQh7r.js +1314 -0
  76. package/dist/lifecycle-Mm_jQh7r.js.map +1 -0
  77. package/dist/mcp/client/index.d.ts +20 -20
  78. package/dist/mcp/client/index.js +1 -1
  79. package/dist/mcp/index.d.ts +35 -35
  80. package/dist/mcp/index.js +2 -2
  81. package/dist/observability/index.d.ts +1 -1
  82. package/dist/observability/index.js +1 -1
  83. package/dist/queue/index.d.ts +123 -0
  84. package/dist/queue/index.js +2 -0
  85. package/dist/queue-BC9F5cUo.js +406 -0
  86. package/dist/queue-BC9F5cUo.js.map +1 -0
  87. package/dist/react.d.ts +34 -16
  88. package/dist/react.js +31 -8
  89. package/dist/react.js.map +1 -1
  90. package/dist/{retries-CAvxtG9d.d.ts → retries-D9B2UCq3.d.ts} +23 -6
  91. package/dist/retries.d.ts +10 -6
  92. package/dist/retries.js +21 -1
  93. package/dist/retries.js.map +1 -1
  94. package/dist/routing/index.d.ts +137 -0
  95. package/dist/routing/index.js +244 -0
  96. package/dist/routing/index.js.map +1 -0
  97. package/dist/sanitize-D9TujEK8.js +79 -0
  98. package/dist/sanitize-D9TujEK8.js.map +1 -0
  99. package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-BGq6M5Kd.d.ts} +20 -21
  100. package/dist/scheduler-D_KHqxBa.js +658 -0
  101. package/dist/scheduler-D_KHqxBa.js.map +1 -0
  102. package/dist/schedules/index.d.ts +1 -1
  103. package/dist/schedules/index.js +1 -1
  104. package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
  105. package/dist/serializable.d.ts +1 -1
  106. package/dist/sessions/index.d.ts +441 -0
  107. package/dist/sessions/index.js +2063 -0
  108. package/dist/sessions/index.js.map +1 -0
  109. package/dist/skills/index.d.ts +99 -0
  110. package/dist/skills/index.js +254 -5
  111. package/dist/skills/index.js.map +1 -1
  112. package/dist/{src-5W6JNKVb.js → src-BNU3ZiJM.js} +1524 -1441
  113. package/dist/src-BNU3ZiJM.js.map +1 -0
  114. package/dist/state/index.d.ts +6 -0
  115. package/dist/state/index.js +137 -0
  116. package/dist/state/index.js.map +1 -0
  117. package/dist/streams/index.d.ts +120 -0
  118. package/dist/streams/index.js +107 -0
  119. package/dist/streams/index.js.map +1 -0
  120. package/dist/streams-BcE8JP2X.d.ts +370 -0
  121. package/dist/streams-CCPRV6dt.js +709 -0
  122. package/dist/streams-CCPRV6dt.js.map +1 -0
  123. package/dist/sub-routing.d.ts +12 -12
  124. package/dist/surface-bZZJqBka.js +17 -0
  125. package/dist/surface-bZZJqBka.js.map +1 -0
  126. package/dist/tasks/index.d.ts +64 -0
  127. package/dist/tasks/index.js +2 -0
  128. package/dist/tasks-D4nLqVSI.js +1656 -0
  129. package/dist/tasks-D4nLqVSI.js.map +1 -0
  130. package/dist/tasks-V2lCT8ZV.d.ts +517 -0
  131. package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
  132. package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
  133. package/dist/text-stream-CpdiKrJB.js +272 -0
  134. package/dist/text-stream-CpdiKrJB.js.map +1 -0
  135. package/dist/tokens-nHAKcN6M.js +52 -0
  136. package/dist/tokens-nHAKcN6M.js.map +1 -0
  137. package/dist/tool-schema-CBjGPrsQ.js +31 -0
  138. package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
  139. package/dist/transport-protocol-BxyngQ_R.js +25 -0
  140. package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
  141. package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
  142. package/dist/types-B7LojTe4.d.ts +202 -0
  143. package/dist/types-DCD6BNZH.d.ts +70 -0
  144. package/dist/types-_Faxb570.d.ts +439 -0
  145. package/dist/voice/client.d.ts +226 -0
  146. package/dist/voice/client.js +932 -0
  147. package/dist/voice/client.js.map +1 -0
  148. package/dist/voice/errors.d.ts +43 -0
  149. package/dist/voice/errors.js +41 -0
  150. package/dist/voice/errors.js.map +1 -0
  151. package/dist/voice/index.d.ts +271 -0
  152. package/dist/voice/index.js +1812 -0
  153. package/dist/voice/index.js.map +1 -0
  154. package/dist/voice/react.d.ts +167 -0
  155. package/dist/voice/react.js +234 -0
  156. package/dist/voice/react.js.map +1 -0
  157. package/dist/voice/sfu.d.ts +71 -0
  158. package/dist/voice/sfu.js +157 -0
  159. package/dist/voice/sfu.js.map +1 -0
  160. package/dist/voice/text.d.ts +6 -0
  161. package/dist/voice/text.js +2 -0
  162. package/dist/voice/types.d.ts +58 -0
  163. package/dist/voice/types.js +18 -0
  164. package/dist/voice/types.js.map +1 -0
  165. package/dist/voice/workers-ai.d.ts +136 -0
  166. package/dist/voice/workers-ai.js +568 -0
  167. package/dist/voice/workers-ai.js.map +1 -0
  168. package/dist/websockets/index.d.ts +301 -0
  169. package/dist/websockets/index.js +3 -0
  170. package/dist/websockets-D7IzWbZb.js +1005 -0
  171. package/dist/websockets-D7IzWbZb.js.map +1 -0
  172. package/dist/workflow-types.d.ts +25 -25
  173. package/dist/workflows.d.ts +21 -21
  174. package/dist/workflows.js +1 -1
  175. package/docs/agent-class.md +4 -4
  176. package/docs/agent-tools.md +2 -1
  177. package/docs/channels.md +323 -0
  178. package/docs/chat-agents.md +6 -13
  179. package/docs/client-sdk.md +30 -0
  180. package/docs/context.md +131 -0
  181. package/docs/index.md +16 -13
  182. package/docs/lifecycle.md +179 -57
  183. package/docs/long-running-agents.md +2 -2
  184. package/docs/mcp-servers.md +5 -1
  185. package/docs/queue.md +135 -241
  186. package/docs/resumable-streaming.md +1 -1
  187. package/docs/retries.md +1 -1
  188. package/docs/routing.md +105 -0
  189. package/docs/sessions.md +237 -871
  190. package/docs/streams.md +213 -0
  191. package/docs/sub-agents.md +184 -124
  192. package/docs/tasks.md +246 -0
  193. package/docs/voice.md +745 -0
  194. package/package.json +126 -14
  195. package/dist/capability-BjSKYpzg.js.map +0 -1
  196. package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
  197. package/dist/client.js.map +0 -1
  198. package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
  199. package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
  200. package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
  201. package/dist/current-agent-CuMErtly.d.ts +0 -283
  202. package/dist/diagnostics-CaBjfz4J.js.map +0 -1
  203. package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
  204. package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
  205. package/dist/experimental/memory/session/index.d.ts +0 -671
  206. package/dist/experimental/memory/session/index.js +0 -2379
  207. package/dist/experimental/memory/session/index.js.map +0 -1
  208. package/dist/experimental/memory/utils/index.d.ts +0 -96
  209. package/dist/experimental/memory/utils/index.js +0 -79
  210. package/dist/experimental/memory/utils/index.js.map +0 -1
  211. package/dist/scheduler-CR9RHGos.js +0 -857
  212. package/dist/scheduler-CR9RHGos.js.map +0 -1
  213. package/dist/src-5W6JNKVb.js.map +0 -1
  214. package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
  215. package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
@@ -78,7 +78,11 @@ createMcpHandler(() => createServer(), {
78
78
  });
79
79
  ```
80
80
 
81
- All upstream SDK v2 handler options pass through. Use `createLegacyMcpHandler` for WorkerTransport, storage, session, and event-store options.
81
+ `createMcpHandler` supports upstream SDK v2 handler options except `bus`, which is not exposed by the Agents SDK. Supplying `bus` throws a `TypeError`.
82
+
83
+ To publish change events, use the returned handler's `notify` methods, such as `handler.notify.toolsChanged()`. Create the handler once at module scope and reuse it for requests and notifications. Notifications are isolate-local: they do not reach subscriptions in other Worker isolates. See the [handler notification API](https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/#publish-list-and-resource-changes) for the available methods.
84
+
85
+ Use `createLegacyMcpHandler` for SDK v1 WorkerTransport, storage, session, and event-store options. The SDK v2 `bus` option is not available on that handler either.
82
86
 
83
87
  The handler validates every present `Origin` header before serving the request. It rejects malformed, opaque, and non-HTTP origins. Requests without `Origin` remain valid for non-browser MCP clients.
84
88
 
package/docs/queue.md CHANGED
@@ -1,218 +1,157 @@
1
- # Queue System
1
+ # Queue
2
2
 
3
- The Agents SDK provides a built-in queue system that allows you to schedule tasks for asynchronous execution. This is particularly useful for background processing, delayed operations, and managing workloads that don't need immediate execution.
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
- ## Overview
8
+ ## Queue Lifecycle capability
6
9
 
7
- The queue system is built into the base `Agent` class. Tasks are stored in a SQLite table and processed automatically in FIFO (First In, First Out) order.
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
- ## QueueItem Type
14
+ `Queue` is a reusable Lifecycle capability. A plain Lifecycle Object can
15
+ install it without extending `Agent`:
10
16
 
11
17
  ```typescript
12
- export type QueueItem<T = string> = {
13
- id: string; // Unique identifier for the queued task
14
- payload: T; // Data to pass to the callback function
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
- ```typescript
46
- class MyAgent extends Agent {
47
- async processEmail(data: { email: string; subject: string }) {
48
- // Process the email
49
- console.log(`Processing email: ${data.subject}`);
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
- async onMessage(message: string) {
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
- console.log(`Queued task with ID: ${taskId}`);
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
- ### dequeue()
65
-
66
- Removes a specific task from the queue by ID.
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
- - `id`: The ID of the task to retrieve
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
- **Returns:** The QueueItem with parsed payload or undefined if not found
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
- **Note:** The payload is automatically parsed from JSON before being returned
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
- ### getQueues()
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
- Retrieves all queued tasks that match a specific key-value pair in their payload.
65
+ ### `push(callback, payload?, options?)`
146
66
 
147
- ```typescript
148
- getQueues(key: string, value: string): QueueItem<string>[]
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
- **Parameters:**
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
- - `key`: The key to filter by in the payload
154
- - `value`: The value to match
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
- **Returns:** Array of matching QueueItem objects
81
+ ### `get(id)`, `list(criteria?)`, `cancel(id)`, `cancelAll(callback?)`
157
82
 
158
- **Note:** This method fetches all queue items and filters them in memory by parsing each payload and checking if the specified key matches the value
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
- **Example:**
87
+ ### QueueItem
161
88
 
162
89
  ```typescript
163
- // Find all tasks for a specific user
164
- const userTasks = await agent.getQueues("userId", "12345");
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
- ## How Queue Processing Works
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
- ## Queue Callback Methods
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 sendNotification(
191
- payload: { userId: string; message: string },
192
- queueItem: QueueItem<string>
106
+ async processEmail(
107
+ payload: { email: string; subject: string },
108
+ item: QueueItem<{ email: string; subject: string }>
193
109
  ) {
194
- console.log(`Processing task ${queueItem.id}`);
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 onUserSignup(userData: any) {
204
- // Queue a welcome notification
205
- await this.queue("sendNotification", {
206
- userId: userData.id,
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
- ## Use Cases
214
-
215
- ### Background Processing
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: any) {
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
- ### Delayed Operations
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: any[]; batchId: string }) {
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: any[]) {
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: batch,
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
- ## Best Practices
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
- 1. **Keep Payloads Small**: Payloads are JSON-serialized and stored in the database
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
- ## Error Handling and Retries
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
- Queued tasks are automatically retried on failure with exponential backoff. The default is 3 attempts. You can customize this per task:
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
- If you need custom error handling in the callback:
297
-
298
- ```typescript
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
- - Tasks are processed sequentially, not in parallel
327
- - No priority system (FIFO only)
328
- - Queue processing happens during agent execution, not as separate background jobs
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).
@@ -70,7 +70,7 @@ function Chat() {
70
70
  - Chunks are batched (every 10 chunks) and flushed to SQLite for performance
71
71
  - When a client sends `CF_AGENT_STREAM_RESUME_REQUEST`, the server checks for active streams and responds with `CF_AGENT_STREAM_RESUMING`
72
72
  - Stale streams (older than 5 minutes) are cleaned up on restore
73
- - Stream buffers are garbage collected from a scheduled alarm: completed or errored streams are retained for 10 minutes (a brief reconnect-and-replay grace; the assistant message itself is persisted separately), and abandoned in-flight streams are retained for 1 hour after their last chunk before being reclaimed
73
+ - Stream buffers are deleted in the same transaction that persists the assistant message (the cutover), so a finished turn leaves nothing behind and no cleanup alarm is armed. A buffer a crash left behind is reclaimed when the next stream starts: finished streams immediately, abandoned in-flight streams after 1 hour without a chunk (recovery has until then to rebuild the message from it)
74
74
 
75
75
  ### Client-side (`useAgentChat`)
76
76
 
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 task is dequeued. After all attempts are exhausted, the task is dequeued and the error is logged.
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
@@ -601,6 +601,94 @@ Each agent is accessed via its own path:
601
601
 
602
602
  ---
603
603
 
604
+ ## Routing to independent Agents
605
+
606
+ A hub Agent often owns an open-ended set of independent peers: one Durable Object per chat, document, or session for a user. `RoutedAgents` from `agents/routing` codifies that topology as a Lifecycle capability. The hub keeps a durable catalog of public IDs mapped to opaque physical names, and forwards requests under one route segment to the selected Agent:
607
+
608
+ ```typescript
609
+ import { Agent, callable, routeAgentRequest } from "agents";
610
+ import { RoutedAgents } from "agents/routing";
611
+
612
+ export class ChatAgent extends Agent<Env> {
613
+ // An ordinary top-level Agent: its own storage, alarms, and placement.
614
+ }
615
+
616
+ export class UserAgent extends Agent<Env> {
617
+ readonly chats = new RoutedAgents<ChatAgent, { title: string }>({
618
+ namespace: this.env.ChatAgent,
619
+ route: "chats"
620
+ });
621
+
622
+ constructor(ctx: DurableObjectState, env: Env) {
623
+ super(ctx, env);
624
+ this.lifecycle.use(this.chats);
625
+ }
626
+
627
+ @callable()
628
+ createChat(title: string) {
629
+ return this.chats.create({ metadata: { title } });
630
+ }
631
+
632
+ @callable()
633
+ listChats() {
634
+ return this.chats.list();
635
+ }
636
+
637
+ @callable()
638
+ deleteChat(id: string) {
639
+ return this.chats.delete(id);
640
+ }
641
+ }
642
+
643
+ export default {
644
+ async fetch(request: Request, env: Env) {
645
+ // Routes both /agents/user-agent/{id} and the forwarded
646
+ // /agents/user-agent/{id}/chats/{id}/... paths — RoutedAgents claims
647
+ // the latter from inside UserAgent once the request reaches it.
648
+ return (
649
+ (await routeAgentRequest(request, env)) ??
650
+ new Response("Not found", { status: 404 })
651
+ );
652
+ }
653
+ } satisfies ExportedHandler<Env>;
654
+ ```
655
+
656
+ The client keeps one connection to the hub and one to the active chat, both addressed through the hub:
657
+
658
+ ```
659
+ /agents/user-agent/alice -> UserAgent "alice"
660
+ /agents/user-agent/alice/chats/{id} -> the ChatAgent behind that entry
661
+ /agents/user-agent/alice/chats/{id}/... -> same ChatAgent, suffix preserved
662
+ ```
663
+
664
+ ```tsx
665
+ const user = useAgent({ agent: "UserAgent", name: "alice" });
666
+ const chat = useAgent({
667
+ agent: "ChatAgent",
668
+ basePath: `agents/user-agent/alice/chats/${encodeURIComponent(chatId)}`
669
+ });
670
+ ```
671
+
672
+ What the capability guarantees:
673
+
674
+ - `create()`, `list()`, and `setMetadata()` touch only the hub's SQLite. No target wakes.
675
+ - `list()` orders most-recently-updated first, ties broken by write order rather than by the random entry ID. Each write scans the route's own entries to derive that order — an intentional trade for a route sized like one owner's own catalog, not a route meant to hold thousands of entries.
676
+ - `get(id)` returns an initialized, typed stub for RPC, or `null` for an unknown or deleted ID.
677
+ - A WebSocket upgrade is answered by the target, which then owns the socket. Chat frames never wake the hub. This is the same two-socket shape as connecting to the chat directly, but the hub stays the authority that resolves an ID, so it can gate, migrate, or redirect entries later.
678
+ - `delete(id)` hides the entry first, condemns the target, then removes the row. The target wipes its own storage on its next wake, moments later, and the condemned marker survives interruption. A failed call leaves a hidden row, and calling `delete` again retries.
679
+ - Physical names are random UUIDs that never leave the hub. Clients only ever see entry IDs.
680
+ - `namespace` is any `DurableObjectNamespace`, including a binding to a class exported by another Worker via `script_name`, so the hub and its targets can be deployed and scaled independently.
681
+ - Destroying the hub retries condemning every remaining entry before its own storage is wiped, but this is best-effort: the platform wipes the hub's storage right after disposal regardless of outcome, so a target that is still unreachable after retries is orphaned, with no catalog row left to retry from later.
682
+
683
+ The catalog stores existence, ownership, and application metadata. Conversation data stays in the target, and a target that needs its hub calls back with `getAgentByName(this.env.UserAgent, ownerName)`. When to prefer this over facets is covered in [Dynamic agents](./sub-agents.md#when-to-use-dynamic-agents).
684
+
685
+ Two sharp edges to design around:
686
+
687
+ - **Pick a route that can't collide.** Forwarding matches every occurrence of the route segment anywhere in the path, so if the hub's own name, class, or another one of its own routes is also literally `"chats"`, a coincidental match with no active entry behind it returns `404` instead of reaching the hub's own handler for that path.
688
+ - **A routed suffix can't address a target's own dynamic agents.** `Agent.fetch()` resolves a `/sub/{class}/{name}` marker against the _hub's_ exported classes before this capability's request handling ever runs, so `/chats/{id}/sub/{class}/{name}` is served as a facet of the hub, not forwarded to the chat. Reach a target's dynamic agents through a direct connection to that target instead of through the hub's route.
689
+
690
+ ---
691
+
604
692
  ## Routing with Authentication
605
693
 
606
694
  Check authentication before routing to agents:
@@ -737,6 +825,23 @@ Get an agent instance by name for server-side RPC or request forwarding.
737
825
 
738
826
  **Returns:** `Promise<DurableObjectStub<T>>` - Typed stub for calling agent methods or forwarding requests
739
827
 
828
+ ### `new RoutedAgents(options)` (from `agents/routing`)
829
+
830
+ A Lifecycle capability installed on a hub Agent with `this.lifecycle.use(...)`. See [Routing to independent Agents](#routing-to-independent-agents).
831
+
832
+ | Parameter | Type | Description |
833
+ | ------------------- | --------------------------- | ------------------------------------------------ |
834
+ | `options.namespace` | `DurableObjectNamespace<T>` | Target binding the entries are created in |
835
+ | `options.route` | `string` | One URL-safe path segment this capability claims |
836
+
837
+ | Method | Returns | Description |
838
+ | --------------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
839
+ | `create(options?)` | `Promise<RoutedAgentEntry<Metadata>>` | Create an entry without waking the target |
840
+ | `get(id)` | `Promise<DurableObjectStub<T> \| null>` | Resolve an active entry to an initialized stub |
841
+ | `list()` | `Promise<ReadonlyArray<RoutedAgentEntry<Metadata>>>` | Active entries, most recently updated first |
842
+ | `setMetadata(id, metadata)` | `Promise<boolean>` | Replace an active entry's metadata |
843
+ | `delete(id)` | `Promise<boolean>` | Hide the entry, condemn the target, then drop the row |
844
+
740
845
  ### `useAgent(options)` / `AgentClient` Options
741
846
 
742
847
  Client connection options: