agents 0.21.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 (258) hide show
  1. package/README.md +21 -1
  2. package/dist/{agent-tool-types-CzGGB-20.d.ts → agent-routing-DE5zmCQ8.d.ts} +1844 -1336
  3. package/dist/agent-routing.d.ts +14 -0
  4. package/dist/agent-routing.js +187 -0
  5. package/dist/agent-routing.js.map +1 -0
  6. package/dist/agent-tool-types.d.ts +26 -26
  7. package/dist/{agent-tools-zR2d5uij.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
  8. package/dist/agent-tools.d.ts +21 -21
  9. package/dist/agent-tools.js +2 -1
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/browser/ai.js +6 -2
  12. package/dist/browser/ai.js.map +1 -1
  13. package/dist/browser/tanstack-ai.js.map +1 -1
  14. package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
  15. package/dist/callable-decorator.d.ts +18 -0
  16. package/dist/callable-decorator.js +71 -0
  17. package/dist/callable-decorator.js.map +1 -0
  18. package/dist/capability-BjSKYpzg.js +42 -0
  19. package/dist/capability-BjSKYpzg.js.map +1 -0
  20. package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
  21. package/dist/channel-Bnm4S7T2.d.ts +491 -0
  22. package/dist/channels/ai-sdk.d.ts +46 -0
  23. package/dist/channels/ai-sdk.js +120 -0
  24. package/dist/channels/ai-sdk.js.map +1 -0
  25. package/dist/channels/email.d.ts +95 -0
  26. package/dist/channels/email.js +323 -0
  27. package/dist/channels/email.js.map +1 -0
  28. package/dist/channels/index.d.ts +233 -0
  29. package/dist/channels/index.js +608 -0
  30. package/dist/channels/index.js.map +1 -0
  31. package/dist/channels/slack.d.ts +140 -0
  32. package/dist/channels/slack.js +614 -0
  33. package/dist/channels/slack.js.map +1 -0
  34. package/dist/channels/tanstack-ai.d.ts +39 -0
  35. package/dist/channels/tanstack-ai.js +17 -0
  36. package/dist/channels/tanstack-ai.js.map +1 -0
  37. package/dist/channels/telegram.d.ts +106 -0
  38. package/dist/channels/telegram.js +427 -0
  39. package/dist/channels/telegram.js.map +1 -0
  40. package/dist/channels/voice.d.ts +45 -0
  41. package/dist/channels/voice.js +122 -0
  42. package/dist/channels/voice.js.map +1 -0
  43. package/dist/chat/index.d.ts +2328 -2015
  44. package/dist/chat/index.js +891 -521
  45. package/dist/chat/index.js.map +1 -1
  46. package/dist/chat/react.d.ts +14 -1
  47. package/dist/chat/react.js +82 -52
  48. package/dist/chat/react.js.map +1 -1
  49. package/dist/chat/transport.js +1 -1
  50. package/dist/chat-sdk/index.d.ts +7 -7
  51. package/dist/chat-sdk/index.js +1 -1
  52. package/dist/{client-zqKcsyFa.js → client-jagG8a9_.js} +129 -37
  53. package/dist/client-jagG8a9_.js.map +1 -0
  54. package/dist/client.d.ts +1 -1
  55. package/dist/client.js +1 -1
  56. package/dist/{cloudflare-BduZwmYK.js → cloudflare-Dzvc7V2N.js} +10 -3
  57. package/dist/{cloudflare-BduZwmYK.js.map → cloudflare-Dzvc7V2N.js.map} +1 -1
  58. package/dist/context/index.d.ts +216 -0
  59. package/dist/context/index.js +454 -0
  60. package/dist/context/index.js.map +1 -0
  61. package/dist/current-agent-Da_C9a3b.d.ts +266 -0
  62. package/dist/current-agent-DhoDkSnH.js +51 -0
  63. package/dist/current-agent-DhoDkSnH.js.map +1 -0
  64. package/dist/diagnostics-BzvaX2UT.js +45 -0
  65. package/dist/diagnostics-BzvaX2UT.js.map +1 -0
  66. package/dist/diagnostics-C4jcz3VK.js +360 -0
  67. package/dist/diagnostics-C4jcz3VK.js.map +1 -0
  68. package/dist/{do-oauth-client-provider-VTZj2VtM.d.ts → do-oauth-client-provider-Tmf1vgKz.d.ts} +2 -2
  69. package/dist/{email-CL27preh.d.ts → email-7TatiTnl.d.ts} +38 -9
  70. package/dist/email-send.d.ts +15 -0
  71. package/dist/email-send.js +32 -0
  72. package/dist/email-send.js.map +1 -0
  73. package/dist/email.d.ts +14 -10
  74. package/dist/email.js.map +1 -1
  75. package/dist/{handler-stateless-C_bo-Ytq.d.ts → handler-stateless-DxYpJ_XF.d.ts} +3 -3
  76. package/dist/{handler-stateless-CIkKPETH.js → handler-stateless-VvrWSAVA.js} +5 -5
  77. package/dist/handler-stateless-VvrWSAVA.js.map +1 -0
  78. package/dist/index-BB0kqhIz.d.ts +101 -0
  79. package/dist/index-XDkuQ7zm.d.ts +89 -0
  80. package/dist/{index-BRnybD6X.d.ts → index-YSKgfgg9.d.ts} +21 -31
  81. package/dist/index.d.ts +113 -101
  82. package/dist/index.js +11 -7234
  83. package/dist/ingress-BfetZbMO.js +83 -0
  84. package/dist/ingress-BfetZbMO.js.map +1 -0
  85. package/dist/internal-CYlgHl1l.js +59 -0
  86. package/dist/internal-CYlgHl1l.js.map +1 -0
  87. package/dist/internal_context-BlxFEWfn.d.ts +19 -0
  88. package/dist/internal_context.d.ts +10 -4
  89. package/dist/internal_context.js +1 -10
  90. package/dist/{client-invoker-BNSZxAkv.d.ts → invoker-CG0_p_Wq.d.ts} +2 -2
  91. package/dist/{client-invoker-VNZ7X0nn.js → invoker-CHMnoxIA.js} +2 -2
  92. package/dist/invoker-CHMnoxIA.js.map +1 -0
  93. package/dist/lifecycle/index.d.ts +66 -0
  94. package/dist/lifecycle/index.js +4 -0
  95. package/dist/lifecycle-CMRGjZdw.js +1299 -0
  96. package/dist/lifecycle-CMRGjZdw.js.map +1 -0
  97. package/dist/mcp/{do-oauth-client-provider.d.ts → client/do-oauth-client-provider.d.ts} +1 -1
  98. package/dist/mcp/{do-oauth-client-provider.js → client/do-oauth-client-provider.js} +1 -1
  99. package/dist/mcp/client/do-oauth-client-provider.js.map +1 -0
  100. package/dist/mcp/client/index.d.ts +42 -0
  101. package/dist/mcp/{client.js → client/index.js} +1 -1
  102. package/dist/mcp/{x402.d.ts → client/x402.d.ts} +2 -2
  103. package/dist/mcp/{x402.js → client/x402.js} +2 -2
  104. package/dist/mcp/client/x402.js.map +1 -0
  105. package/dist/mcp/index.d.ts +36 -36
  106. package/dist/mcp/index.js +14 -16
  107. package/dist/mcp/index.js.map +1 -1
  108. package/dist/mcp/{server.d.ts → server/index.d.ts} +1 -1
  109. package/dist/mcp/{server.js → server/index.js} +1 -1
  110. package/dist/observability/ai/index.js +50 -35
  111. package/dist/observability/ai/index.js.map +1 -1
  112. package/dist/observability/index.d.ts +4 -4
  113. package/dist/observability/index.js +3 -50
  114. package/dist/observability/index.js.map +1 -1
  115. package/dist/{protocol-Dqc2MQxo.js → protocol-B0nh6KNf.js} +19 -21
  116. package/dist/protocol-B0nh6KNf.js.map +1 -0
  117. package/dist/react.d.ts +4 -4
  118. package/dist/react.js +1 -1
  119. package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
  120. package/dist/retries.d.ts +8 -6
  121. package/dist/retries.js +13 -1
  122. package/dist/retries.js.map +1 -1
  123. package/dist/routing/index.d.ts +137 -0
  124. package/dist/routing/index.js +244 -0
  125. package/dist/routing/index.js.map +1 -0
  126. package/dist/sanitize-D9TujEK8.js +79 -0
  127. package/dist/sanitize-D9TujEK8.js.map +1 -0
  128. package/dist/schedule.d.ts +25 -94
  129. package/dist/schedule.js +1 -98
  130. package/dist/schedule.js.map +1 -1
  131. package/dist/scheduler-DD9NdYbF.js +665 -0
  132. package/dist/scheduler-DD9NdYbF.js.map +1 -0
  133. package/dist/scheduler-Dwh85ZGl.d.ts +223 -0
  134. package/dist/schedules/index.d.ts +22 -0
  135. package/dist/schedules/index.js +2 -0
  136. package/dist/schedules/parser.d.ts +79 -0
  137. package/dist/schedules/parser.js +103 -0
  138. package/dist/schedules/parser.js.map +1 -0
  139. package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
  140. package/dist/serializable.d.ts +1 -1
  141. package/dist/sessions/index.d.ts +441 -0
  142. package/dist/sessions/index.js +2063 -0
  143. package/dist/sessions/index.js.map +1 -0
  144. package/dist/skills/index.d.ts +99 -0
  145. package/dist/skills/index.js +254 -5
  146. package/dist/skills/index.js.map +1 -1
  147. package/dist/sql-error-CPY-GXyI.d.ts +12 -0
  148. package/dist/sql-error.d.ts +2 -0
  149. package/dist/sql-error.js +16 -0
  150. package/dist/sql-error.js.map +1 -0
  151. package/dist/src-DlSHshb2.js +6963 -0
  152. package/dist/src-DlSHshb2.js.map +1 -0
  153. package/dist/streams/index.d.ts +120 -0
  154. package/dist/streams/index.js +107 -0
  155. package/dist/streams/index.js.map +1 -0
  156. package/dist/streams-D6tJ0NN9.d.ts +370 -0
  157. package/dist/streams-DZKgAj9b.js +709 -0
  158. package/dist/streams-DZKgAj9b.js.map +1 -0
  159. package/dist/sub-routing.d.ts +12 -12
  160. package/dist/surface-bZZJqBka.js +17 -0
  161. package/dist/surface-bZZJqBka.js.map +1 -0
  162. package/dist/tasks/index.d.ts +64 -0
  163. package/dist/tasks/index.js +2 -0
  164. package/dist/tasks-BRJ5zgya.d.ts +517 -0
  165. package/dist/tasks-ylZgBjhj.js +1656 -0
  166. package/dist/tasks-ylZgBjhj.js.map +1 -0
  167. package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
  168. package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
  169. package/dist/text-stream-CpdiKrJB.js +272 -0
  170. package/dist/text-stream-CpdiKrJB.js.map +1 -0
  171. package/dist/tokens-nHAKcN6M.js +52 -0
  172. package/dist/tokens-nHAKcN6M.js.map +1 -0
  173. package/dist/tool-schema-CBjGPrsQ.js +31 -0
  174. package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
  175. package/dist/types-B7LojTe4.d.ts +202 -0
  176. package/dist/types-_Faxb570.d.ts +439 -0
  177. package/dist/voice/client.d.ts +226 -0
  178. package/dist/voice/client.js +932 -0
  179. package/dist/voice/client.js.map +1 -0
  180. package/dist/voice/errors.d.ts +43 -0
  181. package/dist/voice/errors.js +41 -0
  182. package/dist/voice/errors.js.map +1 -0
  183. package/dist/voice/index.d.ts +271 -0
  184. package/dist/voice/index.js +1812 -0
  185. package/dist/voice/index.js.map +1 -0
  186. package/dist/voice/react.d.ts +167 -0
  187. package/dist/voice/react.js +234 -0
  188. package/dist/voice/react.js.map +1 -0
  189. package/dist/voice/sfu.d.ts +71 -0
  190. package/dist/voice/sfu.js +157 -0
  191. package/dist/voice/sfu.js.map +1 -0
  192. package/dist/voice/text.d.ts +6 -0
  193. package/dist/voice/text.js +2 -0
  194. package/dist/voice/types.d.ts +58 -0
  195. package/dist/voice/types.js +18 -0
  196. package/dist/voice/types.js.map +1 -0
  197. package/dist/voice/workers-ai.d.ts +136 -0
  198. package/dist/voice/workers-ai.js +568 -0
  199. package/dist/voice/workers-ai.js.map +1 -0
  200. package/dist/websockets/index.d.ts +192 -0
  201. package/dist/websockets/index.js +2 -0
  202. package/dist/websockets-DUfRHPRq.js +502 -0
  203. package/dist/websockets-DUfRHPRq.js.map +1 -0
  204. package/dist/workflow-types.d.ts +25 -25
  205. package/dist/workflows.d.ts +22 -22
  206. package/dist/workflows.js +2 -1
  207. package/dist/workflows.js.map +1 -1
  208. package/dist/{ws-chat-transport-CIoOBbO7.js → ws-chat-transport-rWwta645.js} +152 -15
  209. package/dist/ws-chat-transport-rWwta645.js.map +1 -0
  210. package/docs/agent-class.md +29 -87
  211. package/docs/agent-tools.md +2 -1
  212. package/docs/channels.md +323 -0
  213. package/docs/chat-agents.md +19 -25
  214. package/docs/context.md +131 -0
  215. package/docs/durable-execution.md +1 -1
  216. package/docs/http-websockets.md +1 -11
  217. package/docs/human-in-the-loop.md +1 -1
  218. package/docs/index.md +16 -12
  219. package/docs/lifecycle.md +370 -0
  220. package/docs/long-running-agents.md +4 -6
  221. package/docs/mcp-client.md +55 -0
  222. package/docs/mcp-servers.md +5 -1
  223. package/docs/observability.md +11 -11
  224. package/docs/resumable-streaming.md +2 -2
  225. package/docs/routing.md +105 -0
  226. package/docs/scheduling.md +175 -15
  227. package/docs/server-driven-messages.md +1 -1
  228. package/docs/sessions.md +237 -871
  229. package/docs/streams.md +213 -0
  230. package/docs/sub-agents.md +185 -125
  231. package/docs/tasks.md +246 -0
  232. package/docs/voice.md +745 -0
  233. package/package.json +144 -33
  234. package/dist/cli/index.js +0 -26
  235. package/dist/cli/index.js.map +0 -1
  236. package/dist/client-invoker-VNZ7X0nn.js.map +0 -1
  237. package/dist/client-zqKcsyFa.js.map +0 -1
  238. package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
  239. package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
  240. package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
  241. package/dist/experimental/memory/session/index.d.ts +0 -670
  242. package/dist/experimental/memory/session/index.js +0 -2374
  243. package/dist/experimental/memory/session/index.js.map +0 -1
  244. package/dist/experimental/memory/utils/index.d.ts +0 -96
  245. package/dist/experimental/memory/utils/index.js +0 -79
  246. package/dist/experimental/memory/utils/index.js.map +0 -1
  247. package/dist/handler-stateless-CIkKPETH.js.map +0 -1
  248. package/dist/index.js.map +0 -1
  249. package/dist/internal_context-Dg4Cgjcu.d.ts +0 -37
  250. package/dist/internal_context.js.map +0 -1
  251. package/dist/mcp/client.d.ts +0 -42
  252. package/dist/mcp/do-oauth-client-provider.js.map +0 -1
  253. package/dist/mcp/x402.js.map +0 -1
  254. package/dist/protocol-Dqc2MQxo.js.map +0 -1
  255. package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
  256. package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
  257. package/dist/ws-chat-transport-CIoOBbO7.js.map +0 -1
  258. /package/dist/{cli/index.d.ts → index-BVVgDSdq.d.ts} +0 -0
@@ -13,6 +13,61 @@ The MCP client capability lets your agent:
13
13
 
14
14
  > **Note:** This page covers connecting to MCP servers as a client. To create your own MCP server, see [Creating MCP Servers](./mcp-servers.md).
15
15
 
16
+ ## Compose the MCP client with a Durable Object
17
+
18
+ `MCPClientManager` is a lifecycle capability. A class can extend the platform
19
+ `DurableObject` directly and install the manager without extending `Agent`:
20
+
21
+ ```typescript
22
+ import { DurableObject } from "cloudflare:workers";
23
+ import { Lifecycle } from "agents/lifecycle";
24
+ import { MCPClientManager } from "agents/mcp/client";
25
+
26
+ export class MyObject extends DurableObject<Env> {
27
+ readonly mcp = new MCPClientManager("my-object", "1.0.0");
28
+
29
+ readonly lifecycle = Lifecycle.install(this).use(this.mcp);
30
+
31
+ onRequest() {
32
+ return Response.json({ tools: this.mcp.listTools() });
33
+ }
34
+ }
35
+ ```
36
+
37
+ The lifecycle calls the manager automatically:
38
+
39
+ - `onStart()` initializes its schema and restores persisted connections before
40
+ the host handles work.
41
+ - `onRequest()` intercepts registered OAuth callback URLs before the host's
42
+ request handler.
43
+
44
+ Do not call these hooks manually. For a native Durable Object RPC method,
45
+ which bypasses `fetch`, call `await this.lifecycle.start()` before using the
46
+ manager. Explicit cleanup remains available through `mcp.dispose()`; Durable
47
+ Objects do not provide an eviction callback.
48
+
49
+ The manager does not require a particular OAuth callback route. Pass the exact
50
+ callback URL to `registerServer()` and route that request to the same named
51
+ Durable Object. The manager persists the URL and only intercepts a callback
52
+ whose origin and pathname match it.
53
+
54
+ An HTTP-only manager does not need `env`. Pass the Durable Object environment
55
+ when the catalog can contain RPC servers so persisted binding names can be
56
+ resolved after a wake:
57
+
58
+ ```ts
59
+ readonly mcp = new MCPClientManager("my-object", "1.0.0", {
60
+ env: this.env
61
+ });
62
+ ```
63
+
64
+ If an RPC row exists without `env`, startup logs a warning identifying the
65
+ server and does not recreate that connection.
66
+
67
+ `Agent` installs this same capability object directly. Existing `this.mcp`,
68
+ `addMcpServer()`, `removeMcpServer()`, and `getMcpServers()` APIs remain
69
+ available.
70
+
16
71
  ## Quick Start
17
72
 
18
73
  Install the exact MCP client peer used by this Agents release:
@@ -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
 
@@ -158,17 +158,17 @@ These events are emitted by `AIChatAgent` from `@cloudflare/ai-chat`. They track
158
158
 
159
159
  ### Chat recovery events
160
160
 
161
- | Type | Payload | When |
162
- | ------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
163
- | `chat:request:failed` | `{ requestId?, stage, messagesPersisted?, error }` | A Think chat request fails while parsing, persisting, running, or streaming |
164
- | `chat:recovery:detected` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind }` | An interrupted chat fiber is first observed |
165
- | `chat:recovery:attempt` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind }` | The framework begins a recovery attempt |
166
- | `chat:recovery:scheduled` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind }` | A retry or continuation callback is scheduled |
167
- | `chat:recovery:completed` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind }` | Recovery completed successfully |
168
- | `chat:recovery:skipped` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind, reason? }` | Recovery was skipped because the conversation changed or was no longer recoverable |
169
- | `chat:recovery:failed` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind, reason? }` | Recovery ran but failed |
170
- | `chat:recovery:exhausted` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind, reason }` | Recovery exceeded its configured attempt budget |
171
- | `chat:stream:stalled` | `{ requestId, timeoutMs }` | The inactivity watchdog fired — no stream chunk arrived within `chatStreamStallTimeoutMs`. With `chatRecovery` on (the default) the turn then routes into bounded recovery (look for `chat:recovery:*`); with recovery off it terminalizes. See [Think configuration](https://github.com/cloudflare/agents/blob/main/docs/think/index.md) |
161
+ | Type | Payload | When |
162
+ | ------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
163
+ | `chat:request:failed` | `{ requestId?, stage, messagesPersisted?, error }` | A Think chat request fails while parsing, persisting, running, or streaming |
164
+ | `chat:recovery:detected` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind }` | An interrupted chat fiber is first observed |
165
+ | `chat:recovery:attempt` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind }` | The framework begins a recovery attempt |
166
+ | `chat:recovery:scheduled` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind }` | A retry or continuation callback is scheduled |
167
+ | `chat:recovery:completed` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind }` | Recovery completed successfully |
168
+ | `chat:recovery:skipped` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind, reason? }` | Recovery was skipped because the conversation changed or was no longer recoverable |
169
+ | `chat:recovery:failed` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind, reason? }` | Recovery ran but failed |
170
+ | `chat:recovery:exhausted` | `{ incidentId, requestId, attempt, maxAttempts, recoveryKind, reason }` | Recovery exceeded its configured attempt budget |
171
+ | `chat:stream:stalled` | `{ requestId, timeoutMs }` | The inactivity watchdog fired — no stream chunk arrived within `chatStreamStallTimeoutMs`. The turn then routes into bounded recovery; look for `chat:recovery:*`. See [Think configuration](https://github.com/cloudflare/agents/blob/main/docs/think/index.md) |
172
172
 
173
173
  `recoveryKind` is `"retry"` when recovery replays an unanswered user turn and `"continue"` when it continues a partial assistant turn.
174
174
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  The `AIChatAgent` class provides **automatic resumable streaming** out of the box. When a client disconnects and reconnects during an active stream, the response automatically resumes from where it left off.
4
4
 
5
- This is client reconnect recovery, not Durable Object eviction recovery. If the Worker process or Durable Object is evicted while the model call is in flight, enable `chatRecovery` so the turn runs inside a recoverable fiber. `Think` enables `chatRecovery` by default; plain `AIChatAgent` subclasses opt in with `override chatRecovery = true`.
5
+ This is client reconnect recovery, not Durable Object eviction recovery. Durable Object eviction recovery is also always enabled for `AIChatAgent` and `Think`: every turn runs inside a recoverable fiber. Use `chatRecovery` only to tune its budgets and terminal behavior.
6
6
 
7
7
  ## How It Works
8
8
 
@@ -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:
@@ -15,6 +15,91 @@ The scheduling system supports four modes:
15
15
 
16
16
  Under the hood, scheduling uses [Durable Object alarms](https://developers.cloudflare.com/durable-objects/api/alarms/) to wake the agent at the right time. Tasks are stored in a SQLite table and executed in order.
17
17
 
18
+ ## Scheduler Lifecycle primitive
19
+
20
+ > **Experimental.** The `Scheduler` primitive and the `agents/lifecycle`
21
+ > surface it builds on may change between releases. Agent's established
22
+ > scheduling methods (`this.schedule()` and friends) are stable.
23
+
24
+ `Scheduler` is a reusable Lifecycle capability. A plain Lifecycle Object can
25
+ install it without extending `Agent`:
26
+
27
+ ```typescript
28
+ import { DurableObject } from "cloudflare:workers";
29
+ import { Lifecycle } from "agents/lifecycle";
30
+ import { Scheduler, type Schedule } from "agents/schedules";
31
+
32
+ export class ReminderObject extends DurableObject<Env> {
33
+ readonly scheduler = new Scheduler({
34
+ callbacks: {
35
+ sendReminder: (
36
+ payload: { message: string },
37
+ schedule: Schedule<{ message: string }>
38
+ ) => {
39
+ console.log(schedule.id, payload.message);
40
+ }
41
+ }
42
+ });
43
+
44
+ readonly lifecycle = Lifecycle.install(this).use(this.scheduler);
45
+
46
+ async createReminder(message: string): Promise<string> {
47
+ const schedule = await this.scheduler.set(300, "sendReminder", {
48
+ message
49
+ });
50
+ return schedule.id;
51
+ }
52
+ }
53
+ ```
54
+
55
+ Lifecycle owns the physical Durable Object alarm. Scheduler contributes its
56
+ earliest pending task or hung-interval recheck. Lifecycle selects the earliest
57
+ contribution from Scheduler, other capabilities, and the host, then rearms after
58
+ every alarm phase. A future Fiber or MCP capability can contribute its own wake
59
+ time without storing work in Scheduler or depending on it.
60
+
61
+ Scheduler's primary API is small: callbacks are registered by name in the
62
+ constructor, `set()` and `every()` create schedules typed against that
63
+ registration, and `get()`, `list()`, and `cancel()` manage them. All of these
64
+ are asynchronous and work inside routed sub-agents.
65
+
66
+ Scheduler Lifecycle hooks run without ambient host context. Registered
67
+ callbacks are user code, so they run inside the host invocation context with
68
+ the Lifecycle Object available through `getCurrentAgent()`.
69
+
70
+ ## Using Scheduler through Agent
71
+
72
+ Every `Agent` constructs and installs the same primitive at `this.scheduler`.
73
+ Existing Agent applications continue to use the established methods:
74
+
75
+ - `this.schedule()` and `this.scheduleEvery()` create schedules.
76
+ - `this.getScheduleById()` and `this.listSchedules()` read schedules.
77
+ - `this.cancelSchedule()` removes a schedule.
78
+
79
+ These methods delegate to `this.scheduler`; no setup or migration is required.
80
+ Agent registers no callbacks map — a composition-root resolver keeps
81
+ `this.schedule(60, "methodName")` dispatching to Agent methods. Agent passes
82
+ only policy options (retry defaults, hung-interval timeout, error routing) and
83
+ adapts Lifecycle's event sink, facet transport, and host invocation boundary
84
+ at its composition root — there is no Agent-specific Scheduler adapter. Scheduler contributes its next wake time to the same Lifecycle alarm
85
+ selection as Agent keep-alive, fibers, sub-agent work, and deferred
86
+ destruction.
87
+
88
+ Import `Scheduler` and runtime schedule types from the dependency-light entry
89
+ point:
90
+
91
+ ```typescript
92
+ import {
93
+ Scheduler,
94
+ type Schedule,
95
+ type ScheduleCriteria
96
+ } from "agents/schedules";
97
+ ```
98
+
99
+ Natural-language parsing helpers use Zod and live under
100
+ `agents/schedules/parser`. The previous `agents/schedule` path remains as a
101
+ deprecated compatibility alias.
102
+
18
103
  ## Quick Start
19
104
 
20
105
  ```typescript
@@ -299,9 +384,9 @@ This is the recommended approach since you cannot forget to dispose the heartbea
299
384
 
300
385
  ### How it works
301
386
 
302
- `keepAlive()` uses an in-memory reference count and the Durable Object alarm system directly. Each call increments the count; the disposer decrements it. While the count is above zero, `_scheduleNextAlarm()` ensures an alarm fires every 30 seconds, which resets the inactivity timer. No schedule rows are created and no observability events are emitted the heartbeat is invisible to `listSchedules()` and the `agents:schedule` diagnostics channel.
387
+ `keepAlive()` uses an in-memory reference count. Each call increments the count; the disposer decrements it. While the count is above zero, Agent contributes a wake time every 30 seconds to Lifecycle. No schedule rows are created and no observability events are emitted, so the heartbeat is invisible to `listSchedules()` and the scheduling diagnostics channel.
303
388
 
304
- The heartbeat does not conflict with your own schedules the alarm system multiplexes all schedules and the keepAlive heartbeat through a single alarm slot.
389
+ The heartbeat does not conflict with scheduled work. Lifecycle selects one physical alarm from both contributions.
305
390
 
306
391
  Inside sub-agents, `keepAlive()` delegates that heartbeat ref to the top-level parent because facets do not have independent alarm slots. `keepAliveWhile()` works the same way because it calls `keepAlive()` and automatically disposes the delegated ref when the scoped work completes.
307
392
 
@@ -642,14 +727,14 @@ class TimezoneAgent extends Agent {
642
727
 
643
728
  ## AI-Assisted Scheduling
644
729
 
645
- The SDK includes utilities for parsing natural language scheduling requests with AI.
730
+ The SDK includes utilities for parsing natural language scheduling requests with AI. Import them from `agents/schedules/parser`.
646
731
 
647
732
  ### getSchedulePrompt()
648
733
 
649
734
  Returns a system prompt for parsing natural language into scheduling parameters:
650
735
 
651
736
  ```typescript
652
- import { getSchedulePrompt, scheduleSchema } from "agents";
737
+ import { getSchedulePrompt, scheduleSchema } from "agents/schedules/parser";
653
738
  import { generateObject } from "ai";
654
739
  import { openai } from "@ai-sdk/openai";
655
740
 
@@ -703,7 +788,7 @@ class SmartScheduler extends Agent {
703
788
  A Zod schema for validating parsed scheduling data:
704
789
 
705
790
  ```typescript
706
- import { scheduleSchema } from "agents";
791
+ import { scheduleSchema } from "agents/schedules/parser";
707
792
 
708
793
  // The schema uses a discriminated union on `when.type`:
709
794
  // {
@@ -751,7 +836,81 @@ When using this schema with OpenAI models via the AI SDK, you must pass `provide
751
836
 
752
837
  ## API Reference
753
838
 
754
- ### schedule()
839
+ Two surfaces share these semantics: the experimental `Scheduler` primitive
840
+ (`agents/schedules`, methods on the scheduler instance) and the stable `Agent`
841
+ methods (on the Agent class, delegating to `this.scheduler`).
842
+
843
+ ### Scheduler primitive
844
+
845
+ #### `new Scheduler(options?)`
846
+
847
+ ```typescript
848
+ new Scheduler({
849
+ callbacks?,
850
+ retry?,
851
+ hungScheduleTimeoutSeconds?,
852
+ onError?
853
+ });
854
+ ```
855
+
856
+ - `callbacks` registers scheduled callbacks by name. `set()` and `every()`
857
+ type both the name and the payload against this map, and dispatch runs the
858
+ registered function — the typed scheduling surface and the runtime dispatch
859
+ target are the same object.
860
+ - Lifecycle supplies storage, readiness, startup state, alarm coordination,
861
+ the host invocation boundary, events, and routing.
862
+ - `retry` supplies callback retry defaults. The defaults are three attempts,
863
+ 100 ms base delay, and 3,000 ms maximum delay.
864
+ - `hungScheduleTimeoutSeconds` defaults to 30 seconds.
865
+ - `onError` observes terminal callback failures outside ambient host context.
866
+
867
+ Install the constructed object with `Lifecycle.use()` before use. Scheduler
868
+ starts Lifecycle automatically when its asynchronous API is entered. It
869
+ publishes `schedule:*` events through Lifecycle's best-effort event bus. A plain
870
+ Lifecycle Object writes them to the existing `agents:schedule` diagnostics
871
+ channel; `Agent` sends them through its existing observability implementation.
872
+
873
+ #### set()
874
+
875
+ ```typescript
876
+ async set(when, callback, payload?, options?): Promise<Schedule<Payload>>
877
+ ```
878
+
879
+ Create a one-shot or cron schedule. `when`, `options`, idempotency, and return
880
+ value follow [`schedule()`](#schedule) below. `callback` must be a name
881
+ registered in the constructor's `callbacks` map, and `payload` is typed
882
+ against that callback's first parameter.
883
+
884
+ #### every()
885
+
886
+ ```typescript
887
+ async every(intervalSeconds, callback, payload?, options?): Promise<Schedule<Payload>>
888
+ ```
889
+
890
+ Create a fixed-interval schedule. Semantics follow
891
+ [`scheduleEvery()`](#scheduleevery) below, and `every()` also accepts
892
+ `options.idempotent` to opt out of interval dedup.
893
+
894
+ #### get() / list() / cancel()
895
+
896
+ ```typescript
897
+ async get(id): Promise<Schedule<unknown> | undefined>
898
+ async list(criteria?): Promise<Schedule<unknown>[]>
899
+ async cancel(id): Promise<boolean>
900
+ ```
901
+
902
+ Read and cancel schedules — the primitive's equivalents of
903
+ [`getScheduleById()`](#getschedulebyid), [`listSchedules()`](#listschedules),
904
+ and [`cancelSchedule()`](#cancelschedule) below. (Internal synchronous
905
+ variants on the Scheduler back Agent's deprecated `getSchedule()` and
906
+ `getSchedules()`; they are not part of the primitive's contract.)
907
+
908
+ ### Agent methods
909
+
910
+ Methods on the `Agent` class, delegating to `this.scheduler`. Callback names
911
+ resolve to methods on the Agent.
912
+
913
+ #### schedule()
755
914
 
756
915
  ```typescript
757
916
  async schedule<T = string>(
@@ -789,7 +948,7 @@ class MyAgent extends Agent {
789
948
  }
790
949
  ```
791
950
 
792
- ### scheduleEvery()
951
+ #### scheduleEvery()
793
952
 
794
953
  ```typescript
795
954
  async scheduleEvery<T = string>(
@@ -800,7 +959,8 @@ async scheduleEvery<T = string>(
800
959
  ): Promise<Schedule<T>>
801
960
  ```
802
961
 
803
- Schedule a task to run repeatedly at a fixed interval.
962
+ Schedule a task to run repeatedly at a fixed interval. Idempotent by design
963
+ (the primitive's `every()` accepts `options.idempotent` to opt out).
804
964
 
805
965
  **Parameters:**
806
966
 
@@ -819,7 +979,7 @@ Schedule a task to run repeatedly at a fixed interval.
819
979
  - If callback throws an error, the interval continues
820
980
  - Cancel with `cancelSchedule(id)` to stop the entire interval
821
981
 
822
- ### getScheduleById()
982
+ #### getScheduleById()
823
983
 
824
984
  ```typescript
825
985
  async getScheduleById(id: string): Promise<Schedule<unknown> | undefined>
@@ -827,7 +987,7 @@ async getScheduleById(id: string): Promise<Schedule<unknown> | undefined>
827
987
 
828
988
  Get a scheduled task by ID. This method works in both top-level agents and sub-agents.
829
989
 
830
- ### listSchedules()
990
+ #### listSchedules()
831
991
 
832
992
  ```typescript
833
993
  async listSchedules(criteria?: {
@@ -839,7 +999,7 @@ async listSchedules(criteria?: {
839
999
 
840
1000
  Get scheduled tasks matching the criteria. This method works in both top-level agents and sub-agents.
841
1001
 
842
- ### getSchedule()
1002
+ #### getSchedule()
843
1003
 
844
1004
  ```typescript
845
1005
  getSchedule<T = string>(id: string): Schedule<T> | undefined
@@ -847,7 +1007,7 @@ getSchedule<T = string>(id: string): Schedule<T> | undefined
847
1007
 
848
1008
  Deprecated. Get a scheduled task by ID synchronously. This method only works in top-level agents; use `await this.getScheduleById(id)` instead.
849
1009
 
850
- ### getSchedules()
1010
+ #### getSchedules()
851
1011
 
852
1012
  ```typescript
853
1013
  getSchedules<T = string>(criteria?: {
@@ -859,7 +1019,7 @@ getSchedules<T = string>(criteria?: {
859
1019
 
860
1020
  Deprecated. Get scheduled tasks matching the criteria synchronously. This method only works in top-level agents; use `await this.listSchedules(criteria)` instead.
861
1021
 
862
- ### cancelSchedule()
1022
+ #### cancelSchedule()
863
1023
 
864
1024
  ```typescript
865
1025
  async cancelSchedule(id: string): Promise<boolean>
@@ -867,7 +1027,7 @@ async cancelSchedule(id: string): Promise<boolean>
867
1027
 
868
1028
  Cancel a scheduled task. Returns `true` if cancelled, `false` if not found.
869
1029
 
870
- ### keepAlive()
1030
+ #### keepAlive()
871
1031
 
872
1032
  ```typescript
873
1033
  async keepAlive(): Promise<() => void>
@@ -877,7 +1037,7 @@ Create an alarm-backed heartbeat that prevents the Durable Object from being evi
877
1037
 
878
1038
  See [Keeping the Agent Alive](#keeping-the-agent-alive) for usage details.
879
1039
 
880
- ### keepAliveWhile()
1040
+ #### keepAliveWhile()
881
1041
 
882
1042
  ```typescript
883
1043
  async keepAliveWhile<T>(fn: () => Promise<T>): Promise<T>
@@ -457,7 +457,7 @@ Pre-aborted signals short-circuit before any model work runs.
457
457
  ### Limitations
458
458
 
459
459
  - **Signals cannot cross Durable Object boundaries.** `AbortSignal` is not an RPC-serializable type. Construct the controller inside the DO that calls `saveMessages`. For Think child-agent orchestration, use [Agent Tools](./agent-tools.md); `runAgentTool()` bridges parent aborts into the child run. For lower-level custom RPC, return a `ReadableStream` from the child and let the parent cancel it — workerd propagates the cancel back to the source's `cancel` callback.
460
- - **Hibernation drops the listener.** The signal lives in memory. If the DO hibernates mid-turn and `chatRecovery` is enabled, the recovered turn usually calls `continueLastTurn()` internally without the original signal an abort fired after restart has no effect on the recovered turn. For pre-stream interruptions, recovery can instead retry the latest unanswered user message automatically. This is true for top-level agents and sub-agents; sub-agent recovery still works, but the original caller's in-memory signal is gone. Override `onChatRecovery` (Think) or set `chatRecovery = false` for callers that need stronger guarantees.
460
+ - **Hibernation drops the listener.** The signal lives in memory. After the DO restarts, durable recovery usually calls `continueLastTurn()` without the original signal, so an abort fired after restart has no effect. For pre-stream interruptions, recovery can instead retry the latest unanswered user message automatically. This is true for top-level agents and sub-agents. If cancellation must survive restart, persist the cancellation intent in agent state or SQL, check it in `onChatRecovery()`, and return `{ continue: false }`. Durable recovery cannot be disabled.
461
461
 
462
462
  This is the integration point for agent-tool orchestration where the parent's AI SDK abort signal needs to propagate into a child DO's `saveMessages` call. See [`cloudflare/agents#1406`](https://github.com/cloudflare/agents/issues/1406) for the original use case.
463
463