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
@@ -6,10 +6,15 @@ This document tries to bridge that gap, empowering any developer aiming to get s
6
6
 
7
7
  # What is the Agent?
8
8
 
9
- The `Agent` class is an extension of `DurableObject`. That is to say, they _are_ **Durable Objects**. If you're not familiar with Durable Objects, it is highly recommended that you read ["What are Durable Objects"](https://developers.cloudflare.com/durable-objects/) but at their core, Durable Objects are globally addressable (each instance has a unique ID) single-threaded compute instances with long term storage (KV/SQLite).
10
- That being said, `Agent` does **not** extend `DurableObject` directly but instead `Server`. `Server` is a class provided by [PartyKit](https://github.com/cloudflare/partykit/tree/main/packages/partyserver).
9
+ `Agent` directly extends Cloudflare's `DurableObject`, so every Agent is a globally addressable, single-threaded compute instance with durable KV/SQLite storage. If you are unfamiliar with the platform primitive, start with [What are Durable Objects](https://developers.cloudflare.com/durable-objects/).
11
10
 
12
- You can visualize the logic as a Matryoshka doll: **DurableObject** -> **Server** -> **Agent**.
11
+ Each Agent composes a `Lifecycle` instance. The lifecycle installs request, alarm, and hibernating WebSocket entry points while the Agent supplies semantic callbacks and higher-level features:
12
+
13
+ ```text
14
+ DurableObject
15
+ └── Agent
16
+ └── owns Lifecycle
17
+ ```
13
18
 
14
19
  ## Layer 0: Durable Object
15
20
 
@@ -73,11 +78,11 @@ export class MyDurableObject extends DurableObject {
73
78
  }
74
79
  ```
75
80
 
76
- ### `alarm()`
81
+ ### Alarms
77
82
 
78
- HTTP and RPC requests are not the only entrypoints for a DO. Alarms allow developers to schedule an event to trigger at a later time. Whenever the next alarm is due, the runtime will call the `alarm()` method, which is left to the developer to implement.
83
+ `Lifecycle` owns the Agent's physical Durable Object alarm because schedules, keep-alive, fibers, sub-agents, and other capabilities share the same alarm slot. Do not override `alarm()` or call `this.ctx.storage.setAlarm()` from an Agent feature; one caller could overwrite another feature's wake-up.
79
84
 
80
- To schedule an alarm, you can use the `this.ctx.storage.setAlarm()` method. For more information, check [the documentation](https://developers.cloudflare.com/durable-objects/api/alarms/).
85
+ Use `this.schedule()` for named Agent callbacks. A reusable capability with its own durable work pushes jobs through `this.lifecycle.jobs` and implements `onJob()`; Lifecycle drives due jobs and re-arms the alarm from queue state. See [Durable Object lifecycle](./lifecycle.md#the-job-queue) and [Scheduling](./scheduling.md).
81
86
 
82
87
  ### `this.ctx`
83
88
 
@@ -102,104 +107,43 @@ const token = kv.get("someToken");
102
107
 
103
108
  Lastly, it's worth mentioning that the DO also has the Worker `Env` in `this.env`. Read more [here](https://developers.cloudflare.com/workers/runtime-apis/bindings).
104
109
 
105
- ## Layer 1: Partykit `Server`
106
-
107
- Now that you've seen what Durable Objects come with out-of-the-box, what [PartyKit](https://github.com/cloudflare/partykit)'s `Server` (package `partyserver`) implements will be clearer. It's an **opinionated `DurableObject` wrapper that improves DX by hiding away DO primitives in favor of more developer friendly callbacks**.
108
-
109
- An important note is that `Server` **does NOT persist to the DO storage** so you will not see extra storage operations by using it.
110
-
111
- ### Addressing
110
+ ## Layer 1: lifecycle composition
112
111
 
113
- `partyserver` exposes helper to address your DOs instead of manually through your bindings. This allows `partyserver` to implement several improvements, including a unique URL routing scheme for your DOs (e.g. `<your-worker>/servers/:durableClass/:durableName`).
114
-
115
- Compare this to the DO addressing [example above](#rpc).
112
+ `Agent` uses `Lifecycle.install(this)`, an explicit side-effect-named factory that constructs the lifecycle and installs the platform-facing `fetch`, `alarm`, `webSocketMessage`, `webSocketClose`, and `webSocketError` handlers. Agent subclasses implement semantic callbacks instead of a second base class:
116
113
 
117
114
  ```ts
118
- // Note the await here!
119
- const stub = await getServerByName(env.MY_DO, "foo");
120
-
121
- // We can still call RPC methods.
122
- await stub.bar();
123
- ```
124
-
125
- Since we have a URL addressing scheme, we also get access to `routePartykitRequest()`.
126
-
127
- ```ts
128
- async fetch(request: Request, env: Env, ctx: ExecutionContext) {
129
- // Behind the scenes, PartyKit normalizes your DO binding names
130
- // and tries to do some pattern matching.
131
- const res = await routePartykitRequest(request, env);
132
-
133
- if (res) return res;
134
-
135
- return Response("Not found", { status: 404 });
136
- }
137
- ```
138
-
139
- You can have a look at [the implementation](https://github.com/cloudflare/partykit/blob/main/packages/partyserver/src/index.ts#L122) if you're interested.
140
-
141
- ### `onStart`
142
-
143
- The extra plumbing that `Server` includes on addressing allows it to expose an `onStart` callback that is **executed every time the DO starts up** (the DO was evicted, hibernated or never created at all) and **before any `fetch` or RPC**.
144
-
145
- ```ts
146
- class MyServer extends Server {
115
+ class MyAgent extends Agent {
147
116
  onStart() {
148
- // Some initialization logic that you wish
149
- // to run every time the DO is started up.
150
- const sql = this.ctx.storage.sql;
151
- sql.exec(`...`);
117
+ // Runs once per in-memory lifetime before work is handled.
152
118
  }
153
- }
154
- ```
155
-
156
- ### `onRequest` and `onConnect`
157
119
 
158
- `Server` already implements `fetch` for the underlying Durable Object and exposes 2 different callbacks that developers can make use of, `onRequest` and `onConnect` for HTTP requests and incoming WS connections, respectively (**WebSocket connections are accepted by default**).
159
-
160
- ```ts
161
- class MyServer extends Server {
162
- async onRequest(request: Request) {
163
- const url = new URL(request.url);
164
-
165
- return new Response(`Hello from ${url.origin}!`);
120
+ onRequest(request: Request) {
121
+ return new Response(`Hello from ${request.url}`);
166
122
  }
167
123
 
168
- async onConnect(conn, ctx) {
169
- const { request } = ctx;
170
- const url = new URL(request.url);
171
-
172
- // Connections are a WebSocket wrapper
173
- conn.send(`Hello from ${url.origin}!`);
124
+ onConnect(connection: Connection) {
125
+ connection.send("connected");
174
126
  }
175
127
  }
176
128
  ```
177
129
 
178
- ### WebSockets
179
-
180
- Just as `onConnect` is the callback for every new connection, `Server` also provides wrappers on top of the default callbacks from the `DurableObject` class: `onMessage`, `onClose` and `onError`.
181
-
182
- There's also `this.broadcast` that sends a WS message to all connected clients (no magic, just a loop over `this.getConnections()`!).
183
-
184
- ### `this.name`
130
+ Lifecycle WebSockets always use Cloudflare's Hibernation API. Idle clients stay connected while the Durable Object can leave memory; constructor fields and `onStart` run again when a message wakes it. Persist anything needed across wakes in storage or `connection.state`.
185
131
 
186
- Since [2026-03-15](https://developers.cloudflare.com/changelog/post/2026-03-15-durable-object-id-name/), the Workers runtime populates `ctx.id.name` inside a Durable Object addressed via `idFromName()` or `getByName()`, including in alarm handlers. Constructor-time availability isn't spelled out in the docs, but workerd's own tests pin it ([workerd#6421](https://github.com/cloudflare/workerd/pull/6421)), as do `partyserver`'s runtime-contract tests. `partyserver` reads `ctx.id.name` first, so for named access `this.name` resolves natively with no extra machinery.
132
+ Reusable capabilities can be installed through `this.lifecycle.use(capability)`. Capabilities start before Agent startup, can intercept requests before `onRequest`, and process alarms before `onAlarm`. See [Durable Object lifecycle](./lifecycle.md).
187
133
 
188
- `ctx.id.name` is still `undefined` in these cases (see [the DO id docs](https://developers.cloudflare.com/durable-objects/api/id/#name)):
134
+ ### Identity
189
135
 
190
- - the object is addressed via `idFromString()` (even if the id was originally created with `idFromName()`) or `newUniqueId()` deliberate design, not a gap;
191
- - the name is longer than 1,024 bytes;
192
- - the alarm firing was scheduled before 2026-03-15, or was scheduled from a context that itself had no name (reschedule it from a `fetch()` or RPC handler where the name is available).
136
+ Since [2026-03-15](https://developers.cloudflare.com/changelog/post/2026-03-15-durable-object-id-name/), Workers exposes the name used by `idFromName()` or `getByName()` as `ctx.id.name`, including in alarm handlers. Agents and Agent facets use named IDs, and `this.name` projects that native identity.
193
137
 
194
- For those cases `partyserver` falls back to a legacy name record in storage (written automatically during named-access initialization, or by the `setName()` bootstrap for raw-id DOs), and `this.name` throws if no name can be resolved at all.
138
+ For migration, lifecycle can read an existing `__ps_name` value written by an older release. It never writes a duplicate name. Raw IDs, `idFromString()`, and names over 1,024 bytes do not provide native identity. Alarms created before 2026-03-15 must be rescheduled from a named fetch or RPC handler.
195
139
 
196
140
  ## Layer 2: Agent
197
141
 
198
- Now finally, the `Agent` class. `Agent` extends `Server` and provides opinionated primitives for stateful, schedulable, and observable agents that can communicate via RPC, WebSockets, and (even!) email.
142
+ The `Agent` class directly extends `DurableObject`, composes the lifecycle above, and provides opinionated primitives for stateful, schedulable, and observable agents that can communicate via RPC, WebSockets, and (even!) email.
199
143
 
200
144
  ### `this.state` and `this.setState()`
201
145
 
202
- One of the core features of `Agent` is **automatic state persistence**. Developers define the shape of their state via the generic parameter and `initialState` (which is only used if no state exists in storage), and the Agent handles loading, saving, and broadcasting state changes (check `Server`'s `this.broadcast()` above).
146
+ One of the core features of `Agent` is **automatic state persistence**. Developers define the shape of their state via the generic parameter and `initialState` (which is only used if no state exists in storage), and the Agent handles loading, saving, and broadcasting state changes (using its lifecycle-managed WebSocket connections).
203
147
 
204
148
  `this.state` is a getter that lazily loads state from storage (SQL). **State is persisted across DO evictions** when it's updated with `this.setState()`, which automatically serializes the state and writes it back to storage.
205
149
  There's also `this.onStateChanged` that you can override to react to state changes.
@@ -334,7 +278,7 @@ class MyAgent extends Agent {
334
278
  }
335
279
  ```
336
280
 
337
- Schedules are stored in the `cf_agents_schedules` SQL table. Cron schedules automatically reschedule themselves after execution, while one-time schedules are deleted.
281
+ Schedules are stored as jobs in the `cf_agents_jobs` SQL table, the Lifecycle-owned job queue. Cron schedules automatically reschedule themselves after execution, while one-time schedules are deleted.
338
282
 
339
283
  ### `this.mcp` and friends
340
284
 
@@ -419,7 +363,7 @@ function someUtilityFunction() {
419
363
 
420
364
  ### `this.onError`
421
365
 
422
- `Agent` extends `Server`'s `onError` so it can be used to handle errors that are not necessarily WebSocket errors. It is called with a `Connection` or `unknown` error.
366
+ `Agent.onError` handles both WebSocket errors and other Agent errors. It is called with a `Connection` or `unknown` error.
423
367
 
424
368
  ```ts
425
369
  class MyAgent extends Agent {
@@ -495,14 +439,12 @@ application; return a recovery result to update the retained status record.
495
439
 
496
440
  ### Routing
497
441
 
498
- The `Agent` class re-exports PartyKit's [addressing helpers](#addressing) as `getAgentByName` and `routeAgentRequest`.
442
+ Use `getAgentByName` for named RPC stubs and `routeAgentRequest` for `/agents/:class/:name` HTTP and WebSocket routing.
499
443
 
500
444
  ```ts
501
- // Same API as getServerByName
502
445
  const stub = await getAgentByName(env.MY_DO, "foo");
503
446
  // ...
504
447
 
505
- // Same API as routeServerRequest
506
448
  const res = await routeAgentRequest(request, env);
507
449
 
508
450
  if (res) return res;
@@ -1,7 +1,8 @@
1
1
  # Agent Tools
2
2
 
3
3
  Agent tools let one chat agent dispatch another chat-capable sub-agent as part
4
- of its work. The child is a real sub-agent with its own Durable Object storage,
4
+ of its work per-run, parent-supervised delegation is the flagship use case
5
+ for facet-backed dynamic agents. The child is a real sub-agent with its own Durable Object storage,
5
6
  messages, tools, resumable stream, and drill-in URL. The parent keeps a small
6
7
  run registry so clients can render the child timeline, replay it after refresh,
7
8
  and clean it up later.
@@ -0,0 +1,323 @@
1
+ # `agents/channels`
2
+
3
+ `agents/channels` gives an agent one interface for sending and receiving
4
+ messages across different platforms. Use a Channel directly, expose it as an AI
5
+ tool, or register it with a durable `ChannelHost` that owns routing and delivery
6
+ recovery.
7
+
8
+ > [!NOTE]
9
+ > Channels is experimental. Its interface _will_ change before the package
10
+ > reaches a stable release.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install agents
16
+ ```
17
+
18
+ ## Create channels
19
+
20
+ Each adapter turns provider configuration into the same `Channel` interface. A
21
+ `ChannelHost` holds them, keyed by names you choose:
22
+
23
+ ```typescript
24
+ import { ChannelHost, routes } from "agents/channels";
25
+ import { email } from "agents/channels/email";
26
+ import { slack } from "agents/channels/slack";
27
+ import { telegram } from "agents/channels/telegram";
28
+
29
+ const host = new ChannelHost({
30
+ channels: {
31
+ slack: slack({
32
+ botToken: env.SLACK_BOT_TOKEN,
33
+ webhook: {
34
+ signingSecret: env.SLACK_SIGNING_SECRET,
35
+ botUserId: env.SLACK_BOT_USER_ID
36
+ },
37
+ route: routes.perThread
38
+ }),
39
+ telegram: telegram({
40
+ botToken: env.TELEGRAM_BOT_TOKEN,
41
+ webhook: { secretToken: env.TELEGRAM_WEBHOOK_SECRET },
42
+ route: routes.perThread
43
+ }),
44
+ email: email({
45
+ binding: env.EMAIL,
46
+ from: "agent@example.com",
47
+ route: routes.perThread
48
+ })
49
+ },
50
+
51
+ async onMessage({ route, dispatchId, message }) {
52
+ await conversationFor(route).receive(dispatchId, message);
53
+ }
54
+ });
55
+ ```
56
+
57
+ ## 1. Send a message
58
+
59
+ A **surface** is a destination for an outbound message.
60
+ You get one from an inbound message's reply field, or by constructing one from a raw channel identifier through the host.
61
+
62
+ ```typescript
63
+ const surface = host.contactSurface({
64
+ channelKey: "slack",
65
+ scope: "T123",
66
+ subject: "U456"
67
+ });
68
+
69
+ await host.deliver(surface, {
70
+ title: "Import needs attention",
71
+ markdown: "The customer import stopped after **1,240 records**."
72
+ });
73
+ ```
74
+
75
+ An identity's `channelKey` names the configured Channel that observed it. Its
76
+ optional `scope` names a tenant within that Channel and defaults to `"default"`;
77
+ for example, one configured Slack app can observe the same user ID in several
78
+ workspaces. The Host stamps `channelKey` on inbound identities because an adapter
79
+ does not know the key it was configured under.
80
+
81
+ The same human observed through two configured Channels on one platform is two
82
+ Channel identities, just as the same human on Slack and email is. Applications
83
+ that know they are the same person link those identities explicitly.
84
+
85
+ Compose destinations with `fallback()` and `fanout()`:
86
+
87
+ ```typescript
88
+ import { fallback, fanout } from "agents/channels";
89
+
90
+ await host.deliver(fallback([slackSurface, emailSurface]), message);
91
+ await host.deliver(fanout([slackSurface, emailSurface]), message);
92
+ ```
93
+
94
+ `fallback()` tries destinations in order, advancing only after a _confirmed_
95
+ failure, so it can never duplicate a delivery. For a stream, it advances only
96
+ when that failure happens before the destination starts reading; replaying an
97
+ arbitrarily large consumed prefix would require an unbounded buffer. `fanout()`
98
+ sends to all destinations; a partial or uncertain result is reported as
99
+ `uncertain` for the same reason.
100
+ The Host installs both policies as ordinary Channels under reserved keys. You
101
+ can register another composite policy as an ordinary Channel under your own key
102
+ and pair it with a surface constructor that writes that key; inject only the
103
+ outbound resolution capability the policy needs, as the exported built-in
104
+ policy Channels do.
105
+
106
+ ## 2. Give a destination to a model
107
+
108
+ ```typescript
109
+ import { generateText, stepCountIs } from "ai";
110
+ import { createWorkersAI } from "workers-ai-provider";
111
+ import { createSendMessageTool } from "agents/channels/ai-sdk";
112
+
113
+ const workersai = createWorkersAI({ binding: env.AI });
114
+
115
+ await generateText({
116
+ model: workersai("@cf/moonshotai/kimi-k2.7-code"),
117
+ prompt: "An import stopped after 1,240 records. Notify support.",
118
+ tools: {
119
+ contactSupport: createSendMessageTool(
120
+ host,
121
+ fallback([slackSurface, emailSurface]),
122
+ { description: "Contact support when a person needs to intervene" }
123
+ )
124
+ },
125
+ stopWhen: stepCountIs(2)
126
+ });
127
+ ```
128
+
129
+ The model writes the message; you chose the destination. TanStack AI exports the
130
+ same `createSendMessageTool(host, surface, options)` from
131
+ `agents/channels/tanstack-ai`.
132
+
133
+ ## 3. Receive messages
134
+
135
+ One entry point covers every configured Channel's webhook, and Workers Email
136
+ arrives the same way:
137
+
138
+ ```typescript
139
+ export default {
140
+ async fetch(request: Request): Promise<Response> {
141
+ const response = await host.handleRequest(request);
142
+ if (response) return response;
143
+ return new Response("Not found", { status: 404 });
144
+ },
145
+
146
+ async email(message: ForwardableEmailMessage): Promise<void> {
147
+ await host.handleEmail(message);
148
+ }
149
+ } satisfies ExportedHandler<Env>;
150
+ ```
151
+
152
+ Each Channel authenticates its own input and declines what isn't its business,
153
+ so the Host asks them in configuration order and the first to claim it wins.
154
+
155
+ ### Routing
156
+
157
+ A Channel's `route` turns one normalized event into an opaque application
158
+ string — a Durable Object name, a queue key, a database id — or `null` to ignore
159
+ the event entirely. The key is used to identify a common destination for
160
+ messages -- i.e. typically a single conversation:
161
+
162
+ ```typescript
163
+ telegram({
164
+ // …credentials…
165
+ route(event) {
166
+ return event.thread.isDirectMessage === true ? event.thread.id : null;
167
+ }
168
+ });
169
+ ```
170
+
171
+ Channels exposes builtin helpers for the common mappings — `routes.perThread`
172
+ and `routes.perEvent`, which namespace their routes as `thread:…` and
173
+ `event:…`.
174
+
175
+ Deciding whether an event is relevant at all is a different question, and one
176
+ only your application can answer, because the answer usually depends on state
177
+ you hold. Write that in your own `route` and return `null` to ignore the event:
178
+
179
+ ```typescript
180
+ route(event) {
181
+ // Slack shows a bot every message in every channel it belongs to. Answer
182
+ // when addressed, and otherwise only join threads we are already in.
183
+ const thread = routes.perThread(event);
184
+ const addressed =
185
+ event.thread.isDirectMessage === true || event.message.isMention === true;
186
+ if (addressed) return thread;
187
+ return (await myConversations.knows(thread)) ? thread : null;
188
+ }
189
+ ```
190
+
191
+ A lookup like that runs for every event reaching it, so put it behind a cheaper
192
+ check, keep it read-only, and do not let it create the state it is testing for —
193
+ otherwise the first stray message conjures the thing the check is looking for.
194
+
195
+ ### Link identities
196
+
197
+ Personal agents often wwant to resolve users regardless of the channel they messaged on. Your application can explicitly record connections between channel identities and expose them to the Host for messages to be routed on:
198
+
199
+ ```typescript
200
+ const host = new ChannelHost({
201
+ channels,
202
+ findUser: (identity) => users.findUser(identity),
203
+ onMessage,
204
+ onApprovalResponse
205
+ });
206
+
207
+ // ...
208
+ // route to a user's central conversaion if one exists, else start a new
209
+ // conversation for each thread:
210
+ route: routes.byUser(routes.perThread);
211
+
212
+ // or prefer the linked person, then the sender we recognise, then a new
213
+ // conversation per event:
214
+ route: routes.byUser(routes.byIdentity(routes.perEvent));
215
+ ```
216
+
217
+ `byIdentity` groups events carrying the _same_ identity. It never infers that
218
+ two different identities belong to one person — that stays an explicit
219
+ application decision, which `byUser` then exposes to routing. Omit its fallback
220
+ to ignore events that carry no identity at all.
221
+
222
+ If your application does not already store user identities, `createUserIdentityStore(storage)` creates a Durable Object SQL store of the right shape. Your application can call `store.link` to connect multiple identities together.
223
+
224
+ ### Ask for approval
225
+
226
+ ### Request approval
227
+
228
+ The Host exposes a utility for durably correlating inbound approvals to outbound requests:
229
+
230
+ ```typescript
231
+ await host.requestApproval({
232
+ interactionId: "deploy-42",
233
+ request: {
234
+ title: "Production deployment",
235
+ summary: "Deploy version 2026.08.17 to production?",
236
+ input: {
237
+ version: "2026.08.17",
238
+ environment: "production"
239
+ }
240
+ }
241
+ });
242
+ await host.requestApproval(surface, {
243
+ interactionId: crypto.randomUUID(),
244
+ request: {
245
+ title: "Production deployment",
246
+ summary: "Deploy version 2026.08.17 to production?",
247
+ input: { version: "2026.08.17" }
248
+ }
249
+ });
250
+ ```
251
+
252
+ Users can respond to approval requests through native surfaces (e.g. Telegram
253
+ buttons) or HTTP inbound URLs, resolved by the Channel Host itself for your application to settle.
254
+
255
+ ## Custom channels
256
+
257
+ Any transport can become a Channel:
258
+
259
+ ```typescript
260
+ import { matchesPath, routes, type Channel } from "agents/channels";
261
+
262
+ const supportForm: Channel = {
263
+ route: routes.perEvent,
264
+ ingress: {
265
+ async receive(request) {
266
+ if (!matchesPath(request, "/support")) return null;
267
+
268
+ const raw = await request.json<{ message: string; email: string }>();
269
+ const eventId = crypto.randomUUID();
270
+ return {
271
+ events: [
272
+ {
273
+ raw,
274
+ event: {
275
+ type: "message",
276
+ eventId,
277
+ thread: { id: eventId, isDirectMessage: true },
278
+ actor: {
279
+ id: raw.email,
280
+ // The Host stamps the configured `channelKey`.
281
+ identity: { subject: raw.email }
282
+ },
283
+ message: { id: eventId, text: raw.message }
284
+ }
285
+ }
286
+ ],
287
+ response: Response.json({ accepted: true }, { status: 202 })
288
+ };
289
+ }
290
+ }
291
+ };
292
+ ```
293
+
294
+ Returning `null` declines the request so another Channel can claim it.
295
+
296
+ ## Durability contract
297
+
298
+ Channels holds no state: no outbox, no retries, no deduplication, no scheduler.
299
+ Durability is a property of how your application uses it. A caller-supplied
300
+ `deliveryId` is correlation metadata, not an idempotency guarantee; an adapter
301
+ may map it to a provider primitive when one exists.
302
+
303
+ | Channels guarantees | Your application must |
304
+ | ----------------------------------------------------------------------- | --------------------------------------------------------------------------- |
305
+ | A `dispatchId` stable across redelivery and unaffected by routing | Deduplicate on it before starting any side effect |
306
+ | The Host awaits your callback before the provider is acknowledged | Hand off durably before returning — a DO RPC, queue send, or workflow start |
307
+ | One outbound attempt per `deliver()` or `stream()`, reported honestly | Decide whether to retry; `uncertain` may duplicate a real delivery |
308
+ | Surfaces are plain JSON you can persist | Keep configured channel keys stable |
309
+ | Decisions arrive as normalized events carrying your own `interactionId` | Own settlement; an interaction id is not an authorization credential |
310
+
311
+ ## Future work
312
+
313
+ - [ ] Approval-link ingress: signing, verification, and a confirmation page, so
314
+ link approvals return through the same normalized path as Slack buttons
315
+ - [ ] Reader-initiated stream cancellation: Slack's `message_stream_stopped`
316
+ and Telegram's `stopped_message_generation` should reach the running
317
+ generation as ordinary ingress, so aborting it errors the stream and
318
+ each Channel finalizes on the path it already has
319
+ - [ ] More built-in channels
320
+ - [ ] Rendering templates (pretty emails)
321
+ - [ ] Automatic webhook registration
322
+ - [ ] Security review of approval flows
323
+ - [ ] Conformance tests of adapters
@@ -520,13 +520,9 @@ their cleanup and skipped continuations return immediately.
520
520
 
521
521
  #### Overriding the clear handler
522
522
 
523
- The SDK's built-in `CF_AGENT_CHAT_CLEAR` handler calls `resetTurnState()`
524
- automatically. If your `onMessage` override intercepts `CF_AGENT_CHAT_CLEAR`
525
- and returns before the SDK sees the message — for example, to scope the delete
526
- to a specific workflow — the built-in handler never runs. The active stream
527
- continues and queued continuations persist into the newly-cleared conversation.
523
+ The SDK's built-in `CF_AGENT_CHAT_CLEAR` handler calls `resetTurnState()` and clears the default Sessions handle. If an `onMessage` override intercepts this frame and returns early, the built-in handler cannot stop the active stream or clear history.
528
524
 
529
- Call `this.resetTurnState()` before performing your scoped delete:
525
+ Perform authorization or logging, then pass the frame to the original handler:
530
526
 
531
527
  ```typescript
532
528
  import { MessageType } from "@cloudflare/ai-chat/types";
@@ -536,19 +532,16 @@ this.onMessage = async (connection, message) => {
536
532
  if (typeof message === "string") {
537
533
  const data = JSON.parse(message);
538
534
  if (data.type === MessageType.CF_AGENT_CHAT_CLEAR) {
539
- this.resetTurnState();
540
- this.sql`
541
- DELETE FROM cf_ai_chat_agent_messages
542
- WHERE workflow_id = ${this.workflowId}
543
- `;
544
- await this.saveMessages([]);
545
- return;
535
+ await this.authorizeClear(connection);
536
+ console.log("clearing conversation");
546
537
  }
547
538
  }
548
539
  return _onMessage(connection, message);
549
540
  };
550
541
  ```
551
542
 
543
+ Do not write directly to Sessions tables. Use `this.sessions.session().clearMessages()` when implementing a separate server-side history operation outside the chat protocol.
544
+
552
545
  ### Lifecycle Hooks
553
546
 
554
547
  Override `onConnect` and `onClose` to add custom logic. Stream resumption and message sync are handled for you automatically — you do not need to call `super`:
@@ -590,17 +583,11 @@ If you do not pass `abortSignal` to `streamText`, the LLM call will continue run
590
583
 
591
584
  ### Stream Recovery
592
585
 
593
- When a Durable Object is evicted mid-stream (code update, inactivity timeout, resource limit), the LLM connection is severed permanently and the in-memory streaming state is lost. `chatRecovery` wraps each chat turn in a [`runFiber()`](./durable-execution.md), providing automatic `keepAlive` during streaming and a recovery hook on restart.
594
-
595
- ```typescript
596
- export class ChatAgent extends AIChatAgent {
597
- override chatRecovery = true;
598
- }
599
- ```
586
+ When a Durable Object is evicted mid-stream (code update, inactivity timeout, resource limit), the LLM connection is severed permanently and the in-memory streaming state is lost. Durable recovery wraps every `AIChatAgent` and `Think` chat turn in a [`runFiber()`](./durable-execution.md), providing automatic `keepAlive` during streaming and a recovery hook on restart.
600
587
 
601
- When enabled, every `onChatMessage` call runs inside a fiber. If the agent is evicted mid-stream, the fiber row survives in SQLite. On the next activation, the framework detects the interrupted fiber, reconstructs the partial response from buffered stream chunks, and calls `onChatRecovery`.
588
+ If the agent is evicted mid-stream, the fiber row survives in SQLite. On the next activation, the framework detects the interrupted fiber, reconstructs the partial response from buffered stream chunks, and calls `onChatRecovery`.
602
589
 
603
- `AIChatAgent` defaults `chatRecovery` to `false` so existing chat agents only get client reconnect/resumable-stream behavior. `Think` defaults it to `true`.
590
+ Durable recovery is always enabled. Use `chatRecovery` only to tune its budgets and terminal behavior.
604
591
 
605
592
  > **Assign `chatRecovery` as a class field or in the constructor — never in `onStart()`.** On every wake the SDK evaluates recovery budgets (and may seal an interrupted turn, firing `onExhausted`) _before_ your `onStart()` body runs. A config produced inside `onStart()` is therefore read as the built-in defaults at the moment recovery decides, so your `maxRecoveryWork` / `shouldKeepRecovering` / `onExhausted` silently never apply to the recovery that matters. The SDK logs a one-time warning if it detects `chatRecovery` being assigned during `onStart()`.
606
593
 
@@ -610,8 +597,6 @@ Override to implement provider-specific recovery. The default behavior persists
610
597
 
611
598
  ```typescript
612
599
  export class ChatAgent extends AIChatAgent {
613
- override chatRecovery = true;
614
-
615
600
  override async onChatRecovery(
616
601
  ctx: ChatRecoveryContext
617
602
  ): Promise<ChatRecoveryOptions> {
@@ -659,7 +644,16 @@ Settled work is never dropped: `persist: false` only suppresses persistence of a
659
644
 
660
645
  When recovery happens before any stream chunks were written, there is no partial assistant message to continue. If the latest persisted message is still the unanswered user message from the interrupted turn, the framework retries that turn automatically unless `continue` is `false`.
661
646
 
662
- `chatRecovery` can also be configured with budgets and terminal behavior:
647
+ #### Controlling automatic continuation
648
+
649
+ Durable bookkeeping stays enabled even when automatic continuation is not appropriate:
650
+
651
+ - **Retries or side effects are unsafe:** override `onChatRecovery()` and return `{ continue: false }`. Persist idempotency keys or completion records before external side effects so a recovered turn can tell whether work already happened.
652
+ - **Cancellation must survive eviction:** an `AbortSignal` only cancels the current in-memory turn. Also persist cancellation intent in agent state or SQL, read it in `onChatRecovery()`, and return `{ continue: false }` when cancellation was requested.
653
+ - **Cost must be bounded:** set `maxAttempts`, `noProgressTimeoutMs`, `maxRecoveryWork`, and `maxOomRetries`. Use `shouldKeepRecovering` with durable spend data to stop later attempts. The predicate is not bound to the agent instance, so read spend from a store keyed by `ctx.recoveryRootRequestId`.
654
+ - **A provider can resume without a new model call:** use `this.stash()` to save its response ID, retrieve that response in `onChatRecovery()`, and return `{ persist: false, continue: false }`.
655
+
656
+ `chatRecovery` can be configured with budgets and terminal behavior:
663
657
 
664
658
  ```typescript
665
659
  override chatRecovery = {