agents 0.22.0 → 0.23.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 (186) hide show
  1. package/README.md +20 -1
  2. package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-DE5zmCQ8.d.ts} +1038 -394
  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-DtXMTDGM.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.map +1 -1
  15. package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
  16. package/dist/channel-Bnm4S7T2.d.ts +491 -0
  17. package/dist/channels/ai-sdk.d.ts +46 -0
  18. package/dist/channels/ai-sdk.js +120 -0
  19. package/dist/channels/ai-sdk.js.map +1 -0
  20. package/dist/channels/email.d.ts +95 -0
  21. package/dist/channels/email.js +323 -0
  22. package/dist/channels/email.js.map +1 -0
  23. package/dist/channels/index.d.ts +233 -0
  24. package/dist/channels/index.js +608 -0
  25. package/dist/channels/index.js.map +1 -0
  26. package/dist/channels/slack.d.ts +140 -0
  27. package/dist/channels/slack.js +614 -0
  28. package/dist/channels/slack.js.map +1 -0
  29. package/dist/channels/tanstack-ai.d.ts +39 -0
  30. package/dist/channels/tanstack-ai.js +17 -0
  31. package/dist/channels/tanstack-ai.js.map +1 -0
  32. package/dist/channels/telegram.d.ts +106 -0
  33. package/dist/channels/telegram.js +427 -0
  34. package/dist/channels/telegram.js.map +1 -0
  35. package/dist/channels/voice.d.ts +45 -0
  36. package/dist/channels/voice.js +122 -0
  37. package/dist/channels/voice.js.map +1 -0
  38. package/dist/chat/index.d.ts +2325 -2019
  39. package/dist/chat/index.js +888 -518
  40. package/dist/chat/index.js.map +1 -1
  41. package/dist/chat-sdk/index.d.ts +7 -7
  42. package/dist/chat-sdk/index.js +1 -1
  43. package/dist/client.d.ts +1 -1
  44. package/dist/context/index.d.ts +216 -0
  45. package/dist/context/index.js +454 -0
  46. package/dist/context/index.js.map +1 -0
  47. package/dist/{current-agent-CuMErtly.d.ts → current-agent-Da_C9a3b.d.ts} +90 -107
  48. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  49. package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
  50. package/dist/diagnostics-BzvaX2UT.js.map +1 -0
  51. package/dist/diagnostics-C4jcz3VK.js +360 -0
  52. package/dist/diagnostics-C4jcz3VK.js.map +1 -0
  53. package/dist/index-BB0kqhIz.d.ts +101 -0
  54. package/dist/index-BVVgDSdq.d.ts +1 -0
  55. package/dist/index-XDkuQ7zm.d.ts +89 -0
  56. package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
  57. package/dist/index.d.ts +91 -82
  58. package/dist/index.js +3 -2
  59. package/dist/ingress-BfetZbMO.js +83 -0
  60. package/dist/ingress-BfetZbMO.js.map +1 -0
  61. package/dist/internal-CYlgHl1l.js +59 -0
  62. package/dist/internal-CYlgHl1l.js.map +1 -0
  63. package/dist/internal_context.d.ts +1 -1
  64. package/dist/lifecycle/index.d.ts +35 -17
  65. package/dist/lifecycle/index.js +1 -1
  66. package/dist/lifecycle-CMRGjZdw.js +1299 -0
  67. package/dist/lifecycle-CMRGjZdw.js.map +1 -0
  68. package/dist/mcp/client/index.d.ts +20 -20
  69. package/dist/mcp/index.d.ts +35 -35
  70. package/dist/mcp/index.js +1 -1
  71. package/dist/observability/index.d.ts +1 -1
  72. package/dist/observability/index.js +1 -1
  73. package/dist/react.d.ts +4 -4
  74. package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
  75. package/dist/retries.d.ts +8 -6
  76. package/dist/retries.js +13 -1
  77. package/dist/retries.js.map +1 -1
  78. package/dist/routing/index.d.ts +137 -0
  79. package/dist/routing/index.js +244 -0
  80. package/dist/routing/index.js.map +1 -0
  81. package/dist/sanitize-D9TujEK8.js +79 -0
  82. package/dist/sanitize-D9TujEK8.js.map +1 -0
  83. package/dist/scheduler-DD9NdYbF.js +665 -0
  84. package/dist/scheduler-DD9NdYbF.js.map +1 -0
  85. package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-Dwh85ZGl.d.ts} +21 -22
  86. package/dist/schedules/index.d.ts +1 -1
  87. package/dist/schedules/index.js +1 -1
  88. package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
  89. package/dist/serializable.d.ts +1 -1
  90. package/dist/sessions/index.d.ts +441 -0
  91. package/dist/sessions/index.js +2063 -0
  92. package/dist/sessions/index.js.map +1 -0
  93. package/dist/skills/index.d.ts +99 -0
  94. package/dist/skills/index.js +254 -5
  95. package/dist/skills/index.js.map +1 -1
  96. package/dist/{src-5W6JNKVb.js → src-DlSHshb2.js} +1460 -1110
  97. package/dist/src-DlSHshb2.js.map +1 -0
  98. package/dist/streams/index.d.ts +120 -0
  99. package/dist/streams/index.js +107 -0
  100. package/dist/streams/index.js.map +1 -0
  101. package/dist/streams-D6tJ0NN9.d.ts +370 -0
  102. package/dist/streams-DZKgAj9b.js +709 -0
  103. package/dist/streams-DZKgAj9b.js.map +1 -0
  104. package/dist/sub-routing.d.ts +12 -12
  105. package/dist/surface-bZZJqBka.js +17 -0
  106. package/dist/surface-bZZJqBka.js.map +1 -0
  107. package/dist/tasks/index.d.ts +64 -0
  108. package/dist/tasks/index.js +2 -0
  109. package/dist/tasks-BRJ5zgya.d.ts +517 -0
  110. package/dist/tasks-ylZgBjhj.js +1656 -0
  111. package/dist/tasks-ylZgBjhj.js.map +1 -0
  112. package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
  113. package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
  114. package/dist/text-stream-CpdiKrJB.js +272 -0
  115. package/dist/text-stream-CpdiKrJB.js.map +1 -0
  116. package/dist/tokens-nHAKcN6M.js +52 -0
  117. package/dist/tokens-nHAKcN6M.js.map +1 -0
  118. package/dist/tool-schema-CBjGPrsQ.js +31 -0
  119. package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
  120. package/dist/types-B7LojTe4.d.ts +202 -0
  121. package/dist/types-_Faxb570.d.ts +439 -0
  122. package/dist/voice/client.d.ts +226 -0
  123. package/dist/voice/client.js +932 -0
  124. package/dist/voice/client.js.map +1 -0
  125. package/dist/voice/errors.d.ts +43 -0
  126. package/dist/voice/errors.js +41 -0
  127. package/dist/voice/errors.js.map +1 -0
  128. package/dist/voice/index.d.ts +271 -0
  129. package/dist/voice/index.js +1812 -0
  130. package/dist/voice/index.js.map +1 -0
  131. package/dist/voice/react.d.ts +167 -0
  132. package/dist/voice/react.js +234 -0
  133. package/dist/voice/react.js.map +1 -0
  134. package/dist/voice/sfu.d.ts +71 -0
  135. package/dist/voice/sfu.js +157 -0
  136. package/dist/voice/sfu.js.map +1 -0
  137. package/dist/voice/text.d.ts +6 -0
  138. package/dist/voice/text.js +2 -0
  139. package/dist/voice/types.d.ts +58 -0
  140. package/dist/voice/types.js +18 -0
  141. package/dist/voice/types.js.map +1 -0
  142. package/dist/voice/workers-ai.d.ts +136 -0
  143. package/dist/voice/workers-ai.js +568 -0
  144. package/dist/voice/workers-ai.js.map +1 -0
  145. package/dist/websockets/index.d.ts +192 -0
  146. package/dist/websockets/index.js +2 -0
  147. package/dist/websockets-DUfRHPRq.js +502 -0
  148. package/dist/websockets-DUfRHPRq.js.map +1 -0
  149. package/dist/workflow-types.d.ts +25 -25
  150. package/dist/workflows.d.ts +21 -21
  151. package/dist/workflows.js +1 -1
  152. package/docs/agent-class.md +2 -2
  153. package/docs/agent-tools.md +2 -1
  154. package/docs/channels.md +323 -0
  155. package/docs/chat-agents.md +6 -13
  156. package/docs/context.md +131 -0
  157. package/docs/index.md +15 -12
  158. package/docs/lifecycle.md +102 -55
  159. package/docs/long-running-agents.md +2 -2
  160. package/docs/mcp-servers.md +5 -1
  161. package/docs/resumable-streaming.md +1 -1
  162. package/docs/routing.md +105 -0
  163. package/docs/sessions.md +237 -871
  164. package/docs/streams.md +213 -0
  165. package/docs/sub-agents.md +184 -124
  166. package/docs/tasks.md +246 -0
  167. package/docs/voice.md +745 -0
  168. package/package.json +115 -13
  169. package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
  170. package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
  171. package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
  172. package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
  173. package/dist/diagnostics-CaBjfz4J.js.map +0 -1
  174. package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
  175. package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
  176. package/dist/experimental/memory/session/index.d.ts +0 -671
  177. package/dist/experimental/memory/session/index.js +0 -2379
  178. package/dist/experimental/memory/session/index.js.map +0 -1
  179. package/dist/experimental/memory/utils/index.d.ts +0 -96
  180. package/dist/experimental/memory/utils/index.js +0 -79
  181. package/dist/experimental/memory/utils/index.js.map +0 -1
  182. package/dist/scheduler-CR9RHGos.js +0 -857
  183. package/dist/scheduler-CR9RHGos.js.map +0 -1
  184. package/dist/src-5W6JNKVb.js.map +0 -1
  185. package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
  186. package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
package/docs/lifecycle.md CHANGED
@@ -26,7 +26,7 @@ export class MyObject extends DurableObject<Env> {
26
26
  }
27
27
 
28
28
  onAlarm(): void {
29
- // Runs after lifecycle capabilities process the alarm.
29
+ // Runs once per alarm invocation, after due jobs are driven.
30
30
  }
31
31
  }
32
32
  ```
@@ -65,24 +65,34 @@ export default {
65
65
  The default URL shape is `/agents/:binding/:name`. Direct
66
66
  `env.MY_OBJECT.getByName(name).fetch(request)` calls work as well.
67
67
 
68
- `Agent` already constructs this lifecycle. Existing Agent classes continue to
69
- override `onStart`, `onRequest`, `onConnect`, `onMessage`, `onClose`, and
70
- `onError` normally.
68
+ `Agent` already constructs this lifecycle (and installs the `WebSockets`
69
+ capability for its connections). Existing Agent classes continue to override
70
+ `onStart`, `onRequest`, `onConnect`, `onMessage`, `onClose`, and `onError`
71
+ normally.
71
72
 
72
73
  ## Request call path
73
74
 
75
+ The lifecycle-installed `fetch` is the request handler. It offloads each
76
+ request to the installed capabilities, which act as middleware: the first
77
+ capability registered that matches the request handles it by returning a
78
+ `Response`. A capability that returns `undefined` passes the request on to
79
+ the next capability, and a request no capability claims falls through to the
80
+ host's `onRequest`.
81
+
74
82
  ```text
75
83
  routeAgentRequest(request)
76
84
  └─ named Durable Object stub.fetch(request)
77
85
  └─ lifecycle-installed fetch
78
86
  ├─ lifecycle startup capabilities
79
87
  ├─ host onStart
80
- ├─ lifecycle request capabilities
81
- │ └─ first Response wins
88
+ ├─ capability middleware, in registration order
89
+ │ └─ first Response handles the request
82
90
  └─ host onRequest
83
91
  ```
84
92
 
85
- A warm object skips startup but still offers every request to its capabilities.
93
+ A warm object skips startup but still offers every request to its middleware.
94
+ There is no `next()` today: a capability either handles a request or declines
95
+ it, and cannot wrap or observe a downstream response.
86
96
 
87
97
  ## Reusable capabilities
88
98
 
@@ -107,10 +117,6 @@ class AuditLog implements DurableObjectCapability {
107
117
  return new Response("ok");
108
118
  }
109
119
  }
110
-
111
- onAlarm(): void {
112
- this.storage.sql.exec("DELETE FROM audit_log");
113
- }
114
120
  }
115
121
  ```
116
122
 
@@ -127,12 +133,19 @@ export class MyObject extends DurableObject<Env> {
127
133
  }
128
134
  ```
129
135
 
130
- Capabilities run in registration order. Startup and alarms run every hook
131
- sequentially. Request handling stops at the first returned `Response`. A phase
136
+ Capabilities run in registration order. Startup runs every hook
137
+ sequentially. Request handling is middleware dispatch: it stops at the first
138
+ returned `Response`, and returning `undefined` passes the request on. A phase
132
139
  failure propagates, and failed startup can be retried.
133
140
 
141
+ A capability installed with `{ fallback: true }` dispatches after every
142
+ non-fallback capability, whenever it was installed. This is for a host's
143
+ catch-all: `Agent` installs its WebSockets capability as a fallback, so a
144
+ subclass that installs request or upgrade middleware from its own
145
+ constructor still runs first, even though `Agent`'s constructor ran earlier.
146
+
134
147
  Capabilities extending `LifecycleCapability` receive one standard service
135
- surface: storage, readiness, startup state, alarm coordination, a host
148
+ surface: storage, readiness, startup state, the job queue, a host
136
149
  invocation boundary, best-effort events, and capability routing.
137
150
  Host-specific bindings, authentication, and protocol adapters remain explicit
138
151
  constructor dependencies. Lifecycle never grants a capability the complete
@@ -143,46 +156,59 @@ Capability hooks run outside host context, but user callbacks run through
143
156
  Scheduler dispatches its registered callbacks through this boundary, and a
144
157
  future capability that calls user code should do the same.
145
158
 
146
- ## Shared alarm ownership
159
+ ## The job queue
147
160
 
148
- Lifecycle owns the Durable Object's single physical alarm. A capability that
149
- needs a future wake-up keeps its work in its own durable storage and implements
150
- `getNextAlarm()`:
161
+ Lifecycle owns the Durable Object's queue of durable work and its single
162
+ physical alarm. A job is a serialisable callback address the owning
163
+ capability plus a function name — with a due time and a payload. A capability
164
+ that needs future work pushes a job and implements `onJob()`:
151
165
 
152
166
  ```ts
153
- import { LifecycleCapability, type AlarmContribution } from "agents/lifecycle";
167
+ import {
168
+ LifecycleCapability,
169
+ type LifecycleJobContext
170
+ } from "agents/lifecycle";
154
171
 
155
172
  class Cleanup extends LifecycleCapability {
156
173
  constructor() {
157
174
  super("cleanup");
158
175
  }
159
176
 
160
- async getNextAlarm(): Promise<AlarmContribution> {
161
- return (await this.lifecycle.storage.get<number>("cleanup:next")) ?? null;
177
+ async scheduleCleanup(time: number): Promise<void> {
178
+ await this.lifecycle.jobs.push({ id: "cleanup", fn: "sweep", time });
162
179
  }
163
180
 
164
- async onAlarm(): Promise<void> {
165
- const next = await this.lifecycle.storage.get<number>("cleanup:next");
166
- if (next === undefined || next > Date.now()) return;
167
- await this.lifecycle.storage.delete("cleanup:next");
168
- }
169
-
170
- async scheduleCleanup(time: number): Promise<void> {
171
- await this.lifecycle.storage.put("cleanup:next", time);
172
- await this.lifecycle.alarms.rearm();
181
+ async onJob({ job }: LifecycleJobContext): Promise<void> {
182
+ // job.fn === "sweep"; returning nothing completes the job.
183
+ await this.lifecycle.storage.delete("cleanup:marker");
173
184
  }
174
185
  }
175
186
  ```
176
187
 
177
- Lifecycle selects the earliest contribution from every capability and the
178
- host. It runs all capability `onAlarm()` hooks, then host `onAlarm()`, then
179
- recalculates the physical alarm. Capabilities do not depend on Scheduler or on
180
- each other merely to receive alarm wakes.
181
- A contribution can be `{ time, exclusive: true }` when its wake time must
182
- replace ordinary wake candidates, such as a pending teardown. This changes only
183
- which physical alarm is armed; when that alarm fires, normal capability and host
184
- hook order still applies. Hosts can implement `getNextAlarm()` for alarm work
185
- that has not yet been extracted into a capability.
188
+ The queue is ordered by timestamp, and every queue mutation re-arms the
189
+ physical alarm automatically there is no explicit rearm call. When the
190
+ alarm fires, Lifecycle drives due jobs in due order as an event loop, then
191
+ runs host `onAlarm()`, then re-arms from queue state. Before driving any job
192
+ it arms a deadman pre-alarm so an isolate death mid-drive still wakes the
193
+ object to resume.
194
+
195
+ A job's drive result decides what happens next: returning nothing completes
196
+ and deletes it, `{ rescheduleAt }` suspends it until a future time, and
197
+ `"yield"` leaves it due so the object wakes again immediately. Lifecycle also
198
+ owns dispatch retries: a job's `retry` options bound in-process attempts,
199
+ platform-class failures (a superseded isolate after a deploy, a memory-limit
200
+ reset) preserve the job for a fresh invocation, and a terminal application
201
+ failure reaches the owner's `onJobError()`, whose result decides advancement.
202
+
203
+ A job pushed with `exclusive: true` suppresses ordinary alarm candidates
204
+ while it is pending — Agent's deferred destroy uses this so a condemned
205
+ object cannot be kept alive by other work. A `singleflight` job is skipped
206
+ while a previous run is still in flight, until it crosses its hung timeout.
207
+ The host pushes jobs through `lifecycle.jobs` and implements the same
208
+ `onJob()` hook (a host job's terminal failure completes it; the host
209
+ re-derives its jobs from durable state). Capabilities do not depend on
210
+ Scheduler or
211
+ on each other merely to receive wakes.
186
212
 
187
213
  ## Capability events
188
214
 
@@ -217,7 +243,7 @@ dispatch. A host with child objects supplies the transport internally.
217
243
 
218
244
  Agent uses this for facet schedules: Scheduler sends owner-scoped CRUD to the
219
245
  root Scheduler and routes due callbacks back to the matching facet Scheduler.
220
- Existing rows remain in the root `cf_agents_schedules` table. Scheduler does not
246
+ Facet schedules live as jobs in the root's queue. Scheduler does not
221
247
  implement facet traversal, and Agent exposes only one internal generic Lifecycle
222
248
  route aperture.
223
249
 
@@ -267,33 +293,54 @@ Host context values follow the invocation:
267
293
 
268
294
  - `onStart` and `onAlarm`: object;
269
295
  - `onRequest`: object and request;
270
- - `onConnect`: object, connection, and upgrade request;
271
- - `onMessage`, `onClose`, and `onError`: object and connection.
296
+ - `WebSockets` capability handlers `onConnect`: object, connection, and
297
+ upgrade request;
298
+ - `WebSockets` capability handlers `onMessage`, `onClose`, and `onError`:
299
+ object and connection.
272
300
 
273
301
  `getConnectionTags(connection, { request })` remains argument-driven because it
274
302
  already receives both values explicitly. The root `agents` package continues
275
303
  to export `getCurrentAgent()` for the `Agent` class as a compatibility alias.
276
304
 
277
- ## WebSockets always hibernate
278
-
279
- The lifecycle always uses Cloudflare's WebSocket Hibernation API. Idle clients
280
- remain connected while the Durable Object can leave memory. When a message
281
- wakes the object, its constructor and lifecycle startup run again before
282
- `onMessage`.
305
+ ## WebSockets are an opt-in capability
283
306
 
284
- State needed after a wake must be stored durably or through connection state:
307
+ Lifecycle itself does not model WebSockets. Hosts that want connections
308
+ install the `WebSockets` capability, which owns the subsystem end to end —
309
+ it claims upgrades, accepts hibernating sockets, dispatches handlers inside
310
+ the host invocation boundary, and answers `getConnections()`:
285
311
 
286
312
  ```ts
287
- onConnect(connection: Connection): void {
288
- connection.setState({ authenticated: true });
289
- }
313
+ import { WebSockets } from "agents/websockets";
290
314
 
291
- onMessage(connection: Connection<{ authenticated: boolean }>): void {
292
- console.log(connection.state?.authenticated);
315
+ export class MyObject extends DurableObject<Env> {
316
+ readonly webSockets = new WebSockets({
317
+ handlers: {
318
+ onConnect: (connection) => {
319
+ connection.setState({ authenticated: true });
320
+ },
321
+ onMessage: (connection, message) => {
322
+ connection.send(`echo:${message}`);
323
+ }
324
+ }
325
+ });
326
+ readonly lifecycle = Lifecycle.install(this).use(this.webSockets);
293
327
  }
294
328
  ```
295
329
 
296
- There is no non-hibernating mode.
330
+ Without the capability installed, WebSocket upgrades are declined.
331
+
332
+ The capability can also serve remote methods: pass an `RpcTarget` as
333
+ `callables` and its prototype methods become the complete remote interface,
334
+ served over a Cap'n Web session (`?__agents_rpc=capnweb`). An `Agent` adds
335
+ no new surface for this — its `@callable()`-decorated methods are its
336
+ interface, served on every wire: natively over the legacy JSON RPC protocol
337
+ and, through the decorator-derived target, over the Cap'n Web endpoint.
338
+
339
+ Connections use Cloudflare's WebSocket Hibernation API. Idle clients remain
340
+ connected while the Durable Object can leave memory; when a message wakes the
341
+ object, its constructor and lifecycle startup run again before `onMessage`.
342
+ State needed after a wake must be stored durably or through
343
+ `connection.setState()`. There is no non-hibernating mode.
297
344
 
298
345
  ## Native RPC
299
346
 
@@ -505,7 +505,7 @@ This pattern has several advantages for long-running agents:
505
505
 
506
506
  ## Delegating to sub-agents
507
507
 
508
- A project manager does not do everything itself. It delegates specialized work to sub-agents — child Durable Objects (facets) spawned under the parent. Each facet has its own isolated SQLite state and runs in parallel, but stays colocated on the same machine as the parent.
508
+ A project manager does not do everything itself. It delegates specialized work to sub-agents — child Durable Objects (facets) spawned under the parent. Each facet has its own isolated SQLite state and runs in parallel, but stays colocated on the same machine as the parent. This per-run, parent-supervised delegation is the use case facets are for; see [When to use dynamic agents](./sub-agents.md#when-to-use-dynamic-agents) before reaching for one to model long-lived independent peers.
509
509
 
510
510
  ```typescript
511
511
  export class ProjectManager extends Agent<Env, ProjectState> {
@@ -623,7 +623,7 @@ The [Session API](./sessions.md) addresses this directly:
623
623
 
624
624
  - **Compaction** — automatically summarizes older messages when the estimated token count exceeds a threshold. The summary replaces the middle of the conversation as a non-destructive overlay. Original messages remain in SQLite for audit.
625
625
  - **Context blocks** — persistent structured sections injected into the system prompt (identity, memory, learned facts). The agent or the LLM can write to these blocks, and they survive hibernation and eviction.
626
- - **Multi-session management** — `SessionManager` provides a registry of named sessions within a single agent, with forking, cross-session search, and `compactAndSplit` for splitting long conversations into linked continuations.
626
+ - **Conversation isolation** — use one Durable Object per user-facing conversation and keep the conversation directory in a parent Durable Object. Sessions can still fork paths or use named handles for local drafts and namespaces.
627
627
 
628
628
  For simpler cases: keep only the last N messages in the active context (sliding window), or selectively retain messages that contain decisions and approvals while pruning routine exchanges.
629
629
 
@@ -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
 
@@ -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/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: