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
@@ -0,0 +1,131 @@
1
+ # Context
2
+
3
+ > **Experimental.** Everything exported from `agents/context` may change between releases while the API stabilizes.
4
+
5
+ `agents/context` assembles an agent's system prompt from labelled blocks. A block is a piece of prompt text with a storage provider behind it. What the provider can do decides how the block behaves and which tools the model gets for it.
6
+
7
+ Context is prompt assembly. It is not conversation storage. It composes with [`agents/sessions`](./sessions.md) rather than living inside it, so an agent can have a prompt without a transcript, or a transcript without a prompt.
8
+
9
+ ## Blocks
10
+
11
+ ```ts
12
+ import { ContextBlocks } from "agents/context";
13
+
14
+ const context = new ContextBlocks([
15
+ {
16
+ label: "soul",
17
+ provider: { get: async () => "You are a helpful assistant." }
18
+ },
19
+ {
20
+ label: "memory",
21
+ description: "Facts learned about the user",
22
+ maxTokens: 1_100,
23
+ provider: memoryProvider
24
+ }
25
+ ]);
26
+
27
+ const system = await context.freezeSystemPrompt();
28
+ const tools = await context.tools();
29
+ ```
30
+
31
+ Each block renders as a labelled section of the system prompt. The header carries the label, the description, a token-usage percentage when `maxTokens` is set, and a capability marker (`[readonly]`, `[writable]`, `[loadable]`, or `[searchable]`).
32
+
33
+ An empty read-only block is skipped. Writable, loadable, and searchable blocks always render so the model knows which tools can address them.
34
+
35
+ ## Providers
36
+
37
+ The provider decides the block's behavior. The checks are structural, not nominal.
38
+
39
+ | Provider shape | Block behavior |
40
+ | ----------------------- | -------------------------------------------- |
41
+ | `get()` | Read-only text in the prompt |
42
+ | `get()` + `set()` | Writable through the `set_context` tool |
43
+ | `get()` + `search(key)` | Summary in the prompt, `search_context` tool |
44
+
45
+ `get()` returns the block's current content, or `null` when it has none. An optional `init(label)` receives the block label before first use, so one provider class can serve several labels.
46
+
47
+ `ContextBlocks` also accepts a `defaultProvider` factory. A block declared without a `provider` is then wired to whatever that factory returns for its label, which is how a host offers durable writable blocks by label alone.
48
+
49
+ ### Durable SQLite blocks
50
+
51
+ `AgentContextProvider` stores one block per row in `cf_agents_context_blocks` in the Durable Object's own SQLite database:
52
+
53
+ ```ts
54
+ import { AgentContextProvider } from "agents/context";
55
+
56
+ const context = new ContextBlocks([
57
+ { label: "memory", provider: new AgentContextProvider(this, "memory") }
58
+ ]);
59
+ ```
60
+
61
+ The constructor takes anything with a tagged-template `sql` method, which an `Agent` already has. The label argument is optional: `init()` fills it in from the block declaration.
62
+
63
+ ### Searchable blocks
64
+
65
+ `AgentSearchProvider` backs a block with a Durable Object FTS5 table:
66
+
67
+ ```ts
68
+ import { AgentSearchProvider } from "agents/context";
69
+
70
+ const context = new ContextBlocks([
71
+ { label: "knowledge", provider: new AgentSearchProvider(this) }
72
+ ]);
73
+ ```
74
+
75
+ `get()` renders a count of indexed entries rather than the entries themselves. `search(query)` returns up to 10 ranked matches through the `search_context` tool. `set(key, content)` replaces one keyed entry.
76
+
77
+ The FTS5 table is the only store for these entries. A mirror row table would double the billed writes of every indexed entry to serve a count and a lookup the index already answers. Entries live in `cf_agents_search_fts`, namespaced by label, separate from the Sessions message index.
78
+
79
+ ## Frozen prompts
80
+
81
+ `freezeSystemPrompt()` renders once and returns the same string on every later call, so the provider's prefix cache stays warm across turns. `setBlock()` writes to the provider immediately but deliberately does not change the frozen prompt; call `refreshSystemPrompt()` to re-render from current block state.
82
+
83
+ Pass a `promptStore` (any writable provider) as the second constructor argument and the frozen prompt is persisted:
84
+
85
+ ```ts
86
+ const context = new ContextBlocks(
87
+ configs,
88
+ new AgentContextProvider(this, "_system_prompt"),
89
+ (label) => new AgentContextProvider(this, label)
90
+ );
91
+
92
+ const system = await context.freezeSystemPrompt();
93
+ ```
94
+
95
+ `freezeSystemPrompt()` returns the stored prompt when one exists, and otherwise loads providers, renders, and persists. So a cold wake reuses the exact prompt string the model already cached instead of re-rendering a subtly different one.
96
+
97
+ `refreshSystemPrompt()` reloads every provider, re-renders, and overwrites the stored prompt.
98
+
99
+ ## Tools
100
+
101
+ `tools()` returns an AI SDK `ToolSet` wired from what the blocks can do:
102
+
103
+ - `set_context` when any block is writable
104
+ - `search_context` when any block is backed by a search provider
105
+
106
+ An agent with only read-only blocks gets no tools at all.
107
+
108
+ ## Think
109
+
110
+ `Think` builds its `ContextBlocks` from `configureContext()` during startup:
111
+
112
+ ```ts
113
+ import type { ContextConfig } from "agents/context";
114
+
115
+ class MyAgent extends Think<Env> {
116
+ configureContext(): ContextConfig[] {
117
+ return [
118
+ { label: "soul", provider: { get: async () => "You are helpful." } },
119
+ { label: "memory", description: "Learned facts", maxTokens: 2_000 }
120
+ ];
121
+ }
122
+ }
123
+ ```
124
+
125
+ A block declared without a provider is auto-wired to durable per-agent SQLite. The frozen system prompt is always persisted, in `_system_prompt`, so there is nothing to opt into.
126
+
127
+ The assembled blocks are available as `this.context` after `onStart()`.
128
+
129
+ ## Related
130
+
131
+ - [Sessions](./sessions.md) - durable message trees, streamed history, compaction, and attachment offload
@@ -466,7 +466,7 @@ Key points:
466
466
 
467
467
  ### Chat recovery
468
468
 
469
- `AIChatAgent` builds on fibers for LLM streaming recovery. When `chatRecovery` is enabled, each chat turn is wrapped in a fiber automatically. The framework handles the internal recovery path and exposes `onChatRecovery` for provider-specific strategies. See [Long-Running Agents: Recovering interrupted LLM streams](./long-running-agents.md#recovering-interrupted-llm-streams) and the [`forever-chat` example](https://github.com/cloudflare/agents/tree/main/experimental/forever-chat).
469
+ `AIChatAgent` and `Think` build on fibers for LLM streaming recovery. Every chat turn is wrapped in a fiber automatically. The framework handles the internal recovery path and exposes `onChatRecovery` for provider-specific strategies. See [Long-Running Agents: Recovering interrupted LLM streams](./long-running-agents.md#recovering-interrupted-llm-streams) and the [`forever-chat` example](https://github.com/cloudflare/agents/tree/main/experimental/forever-chat).
470
470
 
471
471
  ## Concurrent fibers
472
472
 
@@ -408,17 +408,7 @@ export class MyAgent extends Agent {
408
408
 
409
409
  ## Hibernation
410
410
 
411
- Agents support hibernation - they can sleep when inactive and wake when needed. This saves resources while maintaining WebSocket connections.
412
-
413
- ### Enabling Hibernation
414
-
415
- Hibernation is enabled by default. To disable:
416
-
417
- ```typescript
418
- export class AlwaysOnAgent extends Agent {
419
- static options = { hibernate: false };
420
- }
421
- ```
411
+ Agent WebSockets always use Cloudflare's Hibernation API. An idle Agent can leave memory while its WebSocket clients remain connected; there is no non-hibernating mode.
422
412
 
423
413
  ### How Hibernation Works
424
414
 
@@ -357,7 +357,7 @@ See the complete example: [guides/human-in-the-loop/](https://github.com/cloudfl
357
357
 
358
358
  ### Surviving restarts while waiting for a human
359
359
 
360
- A Durable Object can be evicted at any time (a deploy, an inactivity timeout, a resource limit), including while a turn is paused on an approval prompt or a client-side tool call. When [`chatRecovery`](./chat-agents.md#stream-recovery) is enabled (the default for `Think`), the SDK recognizes that such a turn is _waiting on the human_, not stuck, and does **not** seal it: the no-progress window, attempt cap, `maxRecoveryWork`, and `shouldKeepRecovering` are all suspended while the interaction is pending. Recovery parks the turn instead of failing it, and the user's eventual approval or `tool_result` resumes the conversation through the normal continuation path. A user who takes minutes to respond to a prompt that was interrupted by a deploy therefore does not see a spurious "session interrupted" error.
360
+ A Durable Object can be evicted at any time (a deploy, an inactivity timeout, a resource limit), including while a turn is paused on an approval prompt or a client-side tool call. Durable [`chatRecovery`](./chat-agents.md#stream-recovery) is always enabled. The SDK recognizes that such a turn is _waiting on the human_, not stuck, and does **not** seal it: the no-progress window, attempt cap, `maxRecoveryWork`, and `shouldKeepRecovering` are all suspended while the interaction is pending. Recovery parks the turn instead of failing it, and the user's eventual approval or `tool_result` resumes the conversation through the normal continuation path. A user who takes minutes to respond to a prompt that was interrupted by a deploy therefore does not see a spurious "session interrupted" error.
361
361
 
362
362
  This protection applies to interactions only the client can resolve — `approval-requested` parts and `input-available` parts for client-side tools (those without a server `execute`). A server tool whose `execute()` was killed mid-flight is a genuine orphan and recovers through the normal transcript-repair path instead.
363
363
 
package/docs/index.md CHANGED
@@ -11,13 +11,13 @@ Build stateful AI agents on Cloudflare Workers. Every agent is a Durable Object
11
11
 
12
12
  Pick the base class that matches what you are building. They share the same Durable Object foundation, so you can start small and move up without re-platforming.
13
13
 
14
- | You are building... | Use | Why |
15
- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
16
- | Stateful backend logic, real-time sync, custom protocols | [`Agent`](./agent-class.md) | The core class: state, WebSockets, scheduling, SQL, and sub-agents. No opinions about chat or LLMs. |
17
- | A chat UI where you own the loop, the stream, and the response | [`AIChatAgent`](./chat-agents.md) | A thin chat-protocol adapter for `useAgentChat`. Bring your own agentic loop and custom streaming. |
18
- | A durable, general-purpose reasoning agent | [`Think`](https://github.com/cloudflare/agents/blob/main/docs/think/index.md) | Opinionated runtime: agentic loop, sessions, tools, memory, compaction, recovery, and multi-channel delivery built in. |
19
- | A voice agent (speech in, speech out) | [Voice mixins](https://github.com/cloudflare/agents/blob/main/docs/voice/index.md) | `withVoice` adds real-time STT/TTS, interruption and barge-in, and conversation persistence to an agent. |
20
- | Durable multi-step processes (not chat) | [Workflows](./workflows.md) | Long-running, retryable step orchestration with Cloudflare Workflows. |
14
+ | You are building... | Use | Why |
15
+ | -------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
16
+ | Stateful backend logic, real-time sync, custom protocols | [`Agent`](./agent-class.md) | The core class: state, WebSockets, scheduling, SQL, and sub-agents. No opinions about chat or LLMs. |
17
+ | A chat UI where you own the loop, the stream, and the response | [`AIChatAgent`](./chat-agents.md) | A thin chat-protocol adapter for `useAgentChat`. Bring your own agentic loop and custom streaming. |
18
+ | A durable, general-purpose reasoning agent | [`Think`](https://github.com/cloudflare/agents/blob/main/docs/think/index.md) | Opinionated runtime: agentic loop, sessions, tools, memory, compaction, recovery, and multi-channel delivery built in. |
19
+ | A voice agent (speech in, speech out) | [Voice mixins](./voice.md) | `withVoice` adds real-time STT/TTS, interruption and barge-in, and conversation persistence to an agent. |
20
+ | Durable multi-step processes (not chat) | [Workflows](./workflows.md) | Long-running, retryable step orchestration with Cloudflare Workflows. |
21
21
 
22
22
  Not sure? Start with [`Agent`](./agent-class.md) for raw building blocks, or [`Think`](https://github.com/cloudflare/agents/blob/main/docs/think/index.md) if you want a chat or reasoning agent that already handles the hard parts.
23
23
 
@@ -40,8 +40,8 @@ The differentiator is not "we have durable state" — it is what happens when a
40
40
  ## Core Concepts
41
41
 
42
42
  - [State Management](./state.md) - Managing agent state with `setState()`, `initialState`, and `onStateChanged()`
43
- - [Routing](./routing.md) - How `routeAgentRequest()` and agent naming works
44
- - [Sub-agents](./sub-agents.md) - Parent/child DO composition via facets, nested routing, and direct child connections
43
+ - [Routing](./routing.md) - How `routeAgentRequest()` and agent naming works, plus `RoutedAgents` for a hub that routes to many independent Agents
44
+ - [Dynamic agents](./sub-agents.md) - Facet-backed child agents for code the parent supervises (dynamic/generated code, per-run tool agents, sandboxes) not the recommended primitive for many independent peers like chats
45
45
  - [HTTP & WebSockets](./http-websockets.md) - Request handling and real-time connections
46
46
  - [Callable Methods](./callable-methods.md) - The `@callable` decorator and client-server method calls
47
47
  - [Readonly Connections](./readonly-connections.md) - Restricting which connections can modify state
@@ -57,9 +57,9 @@ The differentiator is not "we have durable state" — it is what happens when a
57
57
  - [Webhooks](./webhooks.md) - Receiving and sending webhook events
58
58
  - [Push Notifications](./push-notifications.md) - Browser push notifications via Web Push API and scheduled delivery
59
59
  - TODO: [SMS](./sms.md) - Text message integration (Twilio, etc.)
60
- - [Voice Agents](https://github.com/cloudflare/agents/blob/main/docs/voice/index.md) - Build voice agents with real-time speech-to-text, text-to-speech, and conversation persistence
60
+ - [Voice Agents](./voice.md) - Build voice agents with real-time speech-to-text, text-to-speech, and conversation persistence
61
+ - [Channels](./channels.md) - Send and receive messages through Slack, Telegram, email, browser voice, or custom adapters
61
62
  - [Chat SDK State](./chat-sdk.md) - Store Chat SDK subscriptions, locks, queues, and history in Agents sub-agents
62
- - TODO: [Messengers](./messengers.md) - Slack, Discord, Telegram, and other chat platforms
63
63
 
64
64
  ## Background Processing
65
65
 
@@ -80,7 +80,8 @@ The differentiator is not "we have durable state" — it is what happens when a
80
80
  - [Server-Driven Messages](./server-driven-messages.md) - Autonomous agent workflows: scheduled follow-ups, queue processing, webhooks, chained reasoning
81
81
  - TODO: [Using AI Models](./using-ai-models.md) - OpenAI, Anthropic, Workers AI, and other providers
82
82
  - TODO: [RAG (Retrieval Augmented Generation)](./rag.md) - Vector search with Vectorize
83
- - [Sessions (Experimental)](./sessions.md) - Persistent conversation storage with tree-structured messages, context blocks, compaction, and search
83
+ - [Sessions (Experimental)](./sessions.md) - Durable message trees, streamed history, compaction, search, and lossless attachment offload
84
+ - [Context (Experimental)](./context.md) - System-prompt blocks, frozen prompts, writable/searchable/loadable providers, and their tools
84
85
  - [Workspace (Experimental)](https://github.com/cloudflare/agents/blob/main/docs/shell/index.md) - Durable virtual filesystem backed by SQLite + R2
85
86
  - [Codemode (Experimental)](https://github.com/cloudflare/agents/blob/main/docs/agents/codemode.md) - LLM-generated executable code for tool orchestration
86
87
  - [Client Tools Continuation](./client-tools-continuation.md) - Handling tool calls across client/server
@@ -140,6 +141,9 @@ The differentiator is not "we have durable state" — it is what happens when a
140
141
 
141
142
  ## Reference
142
143
 
144
+ - [Durable Object Lifecycle](./lifecycle.md) - Compose reusable durable components outside the Agent base class
145
+ - [Tasks](./tasks.md) - Durable, replayable background work with journaled steps and durable sleeps (experimental)
146
+ - [Streams](./streams.md) - Durable incremental output: chunk log, cursor, replay-then-tail reads (experimental)
143
147
  - TODO: [API Reference](./api-reference.md) - Complete API documentation
144
148
  - TODO: [FAQ / How is this different from Durable Objects?](./faq.md)
145
149
  - TODO: [Resources & Further Reading](./resources.md)
@@ -0,0 +1,370 @@
1
+ # Durable Object lifecycle
2
+
3
+ > **Experimental.** Everything exported from `agents/lifecycle` — and the
4
+ > capabilities built on it, including `Scheduler` — may change between
5
+ > releases while the composition surface stabilizes.
6
+
7
+ `agents/lifecycle` lets reusable durable capabilities work in both `Agent` and a
8
+ plain Cloudflare Durable Object. It uses composition: your class extends the
9
+ platform `DurableObject`, then constructs a lifecycle with `this`.
10
+
11
+ ## Plain Durable Object
12
+
13
+ ```ts
14
+ import { DurableObject } from "cloudflare:workers";
15
+ import { Lifecycle } from "agents/lifecycle";
16
+
17
+ export class MyObject extends DurableObject<Env> {
18
+ readonly lifecycle = Lifecycle.install(this);
19
+
20
+ onStart(): void {
21
+ // Runs once per in-memory object lifetime, before work is handled.
22
+ }
23
+
24
+ onRequest(request: Request): Response {
25
+ return new Response(`Hello from ${this.lifecycle.name}: ${request.url}`);
26
+ }
27
+
28
+ onAlarm(): void {
29
+ // Runs once per alarm invocation, after due jobs are driven.
30
+ }
31
+ }
32
+ ```
33
+
34
+ The side-effect-named static factory constructs the lifecycle and installs the
35
+ runtime-facing `fetch`, `alarm`, `webSocketMessage`, `webSocketClose`, and
36
+ `webSocketError` handlers. Do not define forwarding versions of those methods.
37
+ Implement the semantic callbacks instead.
38
+
39
+ The expanded equivalent is available when useful:
40
+
41
+ ```ts
42
+ readonly lifecycle = new Lifecycle(this);
43
+
44
+ constructor(ctx: DurableObjectState, env: Env) {
45
+ super(ctx, env);
46
+ this.lifecycle.installHandlers();
47
+ }
48
+ ```
49
+
50
+ Route named objects from the outer Worker when you want URL routing:
51
+
52
+ ```ts
53
+ import { routeAgentRequest } from "agents";
54
+
55
+ export default {
56
+ async fetch(request: Request, env: Env): Promise<Response> {
57
+ return (
58
+ (await routeAgentRequest(request, env)) ??
59
+ new Response("Not found", { status: 404 })
60
+ );
61
+ }
62
+ };
63
+ ```
64
+
65
+ The default URL shape is `/agents/:binding/:name`. Direct
66
+ `env.MY_OBJECT.getByName(name).fetch(request)` calls work as well.
67
+
68
+ `Agent` already constructs this lifecycle (and installs the `WebSockets`
69
+ capability for its connections). Existing Agent classes continue to override
70
+ `onStart`, `onRequest`, `onConnect`, `onMessage`, `onClose`, and `onError`
71
+ normally.
72
+
73
+ ## Request call path
74
+
75
+ The lifecycle-installed `fetch` is the request handler. It offloads each
76
+ request to the installed capabilities, which act as middleware: the first
77
+ capability registered that matches the request handles it by returning a
78
+ `Response`. A capability that returns `undefined` passes the request on to
79
+ the next capability, and a request no capability claims falls through to the
80
+ host's `onRequest`.
81
+
82
+ ```text
83
+ routeAgentRequest(request)
84
+ └─ named Durable Object stub.fetch(request)
85
+ └─ lifecycle-installed fetch
86
+ ├─ lifecycle startup capabilities
87
+ ├─ host onStart
88
+ ├─ capability middleware, in registration order
89
+ │ └─ first Response handles the request
90
+ └─ host onRequest
91
+ ```
92
+
93
+ A warm object skips startup but still offers every request to its middleware.
94
+ There is no `next()` today: a capability either handles a request or declines
95
+ it, and cannot wrap or observe a downstream response.
96
+
97
+ ## Reusable capabilities
98
+
99
+ A capability implements only the phases it needs:
100
+
101
+ ```ts
102
+ import type { DurableObjectCapability } from "agents/lifecycle";
103
+
104
+ class AuditLog implements DurableObjectCapability {
105
+ constructor(private readonly storage: DurableObjectStorage) {}
106
+
107
+ onStart(): void {
108
+ this.storage.sql.exec(`
109
+ CREATE TABLE IF NOT EXISTS audit_log (
110
+ message TEXT NOT NULL
111
+ )
112
+ `);
113
+ }
114
+
115
+ onRequest({ request }: { request: Request }): Response | undefined {
116
+ if (new URL(request.url).pathname.endsWith("/health")) {
117
+ return new Response("ok");
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ Install it before startup:
124
+
125
+ ```ts
126
+ export class MyObject extends DurableObject<Env> {
127
+ private readonly audit = new AuditLog(this.ctx.storage);
128
+ readonly lifecycle = Lifecycle.install(this).use(this.audit);
129
+
130
+ onRequest(): Response {
131
+ return new Response("application response");
132
+ }
133
+ }
134
+ ```
135
+
136
+ Capabilities run in registration order. Startup runs every hook
137
+ sequentially. Request handling is middleware dispatch: it stops at the first
138
+ returned `Response`, and returning `undefined` passes the request on. A phase
139
+ failure propagates, and failed startup can be retried.
140
+
141
+ A capability installed with `{ fallback: true }` dispatches after every
142
+ non-fallback capability, whenever it was installed. This is for a host's
143
+ catch-all: `Agent` installs its WebSockets capability as a fallback, so a
144
+ subclass that installs request or upgrade middleware from its own
145
+ constructor still runs first, even though `Agent`'s constructor ran earlier.
146
+
147
+ Capabilities extending `LifecycleCapability` receive one standard service
148
+ surface: storage, readiness, startup state, the job queue, a host
149
+ invocation boundary, best-effort events, and capability routing.
150
+ Host-specific bindings, authentication, and protocol adapters remain explicit
151
+ constructor dependencies. Lifecycle never grants a capability the complete
152
+ host implicitly.
153
+
154
+ Capability hooks run outside host context, but user callbacks run through
155
+ `this.lifecycle.runInHostContext(fn)` inside the host invocation context.
156
+ Scheduler dispatches its registered callbacks through this boundary, and a
157
+ future capability that calls user code should do the same.
158
+
159
+ ## The job queue
160
+
161
+ Lifecycle owns the Durable Object's queue of durable work and its single
162
+ physical alarm. A job is a serialisable callback address — the owning
163
+ capability plus a function name — with a due time and a payload. A capability
164
+ that needs future work pushes a job and implements `onJob()`:
165
+
166
+ ```ts
167
+ import {
168
+ LifecycleCapability,
169
+ type LifecycleJobContext
170
+ } from "agents/lifecycle";
171
+
172
+ class Cleanup extends LifecycleCapability {
173
+ constructor() {
174
+ super("cleanup");
175
+ }
176
+
177
+ async scheduleCleanup(time: number): Promise<void> {
178
+ await this.lifecycle.jobs.push({ id: "cleanup", fn: "sweep", time });
179
+ }
180
+
181
+ async onJob({ job }: LifecycleJobContext): Promise<void> {
182
+ // job.fn === "sweep"; returning nothing completes the job.
183
+ await this.lifecycle.storage.delete("cleanup:marker");
184
+ }
185
+ }
186
+ ```
187
+
188
+ The queue is ordered by timestamp, and every queue mutation re-arms the
189
+ physical alarm automatically — there is no explicit rearm call. When the
190
+ alarm fires, Lifecycle drives due jobs in due order as an event loop, then
191
+ runs host `onAlarm()`, then re-arms from queue state. Before driving any job
192
+ it arms a deadman pre-alarm so an isolate death mid-drive still wakes the
193
+ object to resume.
194
+
195
+ A job's drive result decides what happens next: returning nothing completes
196
+ and deletes it, `{ rescheduleAt }` suspends it until a future time, and
197
+ `"yield"` leaves it due so the object wakes again immediately. Lifecycle also
198
+ owns dispatch retries: a job's `retry` options bound in-process attempts,
199
+ platform-class failures (a superseded isolate after a deploy, a memory-limit
200
+ reset) preserve the job for a fresh invocation, and a terminal application
201
+ failure reaches the owner's `onJobError()`, whose result decides advancement.
202
+
203
+ A job pushed with `exclusive: true` suppresses ordinary alarm candidates
204
+ while it is pending — Agent's deferred destroy uses this so a condemned
205
+ object cannot be kept alive by other work. A `singleflight` job is skipped
206
+ while a previous run is still in flight, until it crosses its hung timeout.
207
+ The host pushes jobs through `lifecycle.jobs` and implements the same
208
+ `onJob()` hook (a host job's terminal failure completes it; the host
209
+ re-derives its jobs from durable state). Capabilities do not depend on
210
+ Scheduler or
211
+ on each other merely to receive wakes.
212
+
213
+ ## Capability events
214
+
215
+ Capabilities publish best-effort telemetry through their standard service
216
+ surface. Lifecycle assigns the capability source from the stable ID passed to
217
+ `super()`:
218
+
219
+ ```ts
220
+ class Cleanup extends LifecycleCapability {
221
+ constructor() {
222
+ super("cleanup");
223
+ }
224
+
225
+ reportRemoval(key: string): void {
226
+ this.lifecycle.events.emit("cleanup:remove", { key });
227
+ }
228
+ }
229
+ ```
230
+
231
+ Lifecycle publishes events from a plain Lifecycle Object to the existing
232
+ `agents:*` diagnostics channels according to the event type. Delivery is
233
+ best-effort, runs outside ambient host context, and does not fail the emitting
234
+ capability when a telemetry sink throws. Persist an outbox in the capability
235
+ when delivery is part of the durable business operation.
236
+
237
+ ## Capability routing
238
+
239
+ Every `LifecycleCapability` also receives `lifecycle.routes`. `toRoot()` routes
240
+ a message to the matching capability ID on the root Lifecycle; `to(address, …)`
241
+ routes to another addressed Lifecycle. Lifecycle owns the generic envelope and
242
+ dispatch. A host with child objects supplies the transport internally.
243
+
244
+ Agent uses this for facet schedules: Scheduler sends owner-scoped CRUD to the
245
+ root Scheduler and routes due callbacks back to the matching facet Scheduler.
246
+ Facet schedules live as jobs in the root's queue. Scheduler does not
247
+ implement facet traversal, and Agent exposes only one internal generic Lifecycle
248
+ route aperture.
249
+
250
+ ## Explicit disposal
251
+
252
+ `lifecycle.dispose()` calls each capability's optional `dispose()` method in
253
+ reverse installation order. This phase releases live resources such as MCP
254
+ transports and listeners. It does not delete capability tables. An explicit
255
+ Lifecycle Object destruction disposes live resources once, then calls
256
+ `storage.deleteAll()` once for all shared durable state. Eviction calls neither.
257
+
258
+ ## Lifecycle Object context
259
+
260
+ `agents/lifecycle` exports the `LifecycleObject` interface for a
261
+ `DurableObject` with an installed `Lifecycle` and the semantic hooks Lifecycle
262
+ dispatches. This is a host type, not the batteries-included `Agent` class
263
+ exported from `agents`.
264
+
265
+ Lifecycle establishes the `getCurrentAgent()` context only while it invokes
266
+ host hooks. Capability hooks run outside that ambient context and use their own
267
+ `this`, hook arguments, and explicitly supplied dependencies.
268
+
269
+ ```ts
270
+ import { getCurrentAgent } from "agents/lifecycle";
271
+
272
+ function currentRequestOrigin(): string | undefined {
273
+ const { request } = getCurrentAgent();
274
+ return request ? new URL(request.url).origin : undefined;
275
+ }
276
+
277
+ export class MyObject extends DurableObject<Env> {
278
+ readonly lifecycle = Lifecycle.install(this);
279
+
280
+ onRequest(): Response {
281
+ return Response.json({ origin: currentRequestOrigin() });
282
+ }
283
+ }
284
+ ```
285
+
286
+ Pass the concrete host class when shared host code needs its additional APIs:
287
+
288
+ ```ts
289
+ const { agent: object } = getCurrentAgent<MyObject>();
290
+ ```
291
+
292
+ Host context values follow the invocation:
293
+
294
+ - `onStart` and `onAlarm`: object;
295
+ - `onRequest`: object and request;
296
+ - `WebSockets` capability handlers `onConnect`: object, connection, and
297
+ upgrade request;
298
+ - `WebSockets` capability handlers `onMessage`, `onClose`, and `onError`:
299
+ object and connection.
300
+
301
+ `getConnectionTags(connection, { request })` remains argument-driven because it
302
+ already receives both values explicitly. The root `agents` package continues
303
+ to export `getCurrentAgent()` for the `Agent` class as a compatibility alias.
304
+
305
+ ## WebSockets are an opt-in capability
306
+
307
+ Lifecycle itself does not model WebSockets. Hosts that want connections
308
+ install the `WebSockets` capability, which owns the subsystem end to end —
309
+ it claims upgrades, accepts hibernating sockets, dispatches handlers inside
310
+ the host invocation boundary, and answers `getConnections()`:
311
+
312
+ ```ts
313
+ import { WebSockets } from "agents/websockets";
314
+
315
+ export class MyObject extends DurableObject<Env> {
316
+ readonly webSockets = new WebSockets({
317
+ handlers: {
318
+ onConnect: (connection) => {
319
+ connection.setState({ authenticated: true });
320
+ },
321
+ onMessage: (connection, message) => {
322
+ connection.send(`echo:${message}`);
323
+ }
324
+ }
325
+ });
326
+ readonly lifecycle = Lifecycle.install(this).use(this.webSockets);
327
+ }
328
+ ```
329
+
330
+ Without the capability installed, WebSocket upgrades are declined.
331
+
332
+ The capability can also serve remote methods: pass an `RpcTarget` as
333
+ `callables` and its prototype methods become the complete remote interface,
334
+ served over a Cap'n Web session (`?__agents_rpc=capnweb`). An `Agent` adds
335
+ no new surface for this — its `@callable()`-decorated methods are its
336
+ interface, served on every wire: natively over the legacy JSON RPC protocol
337
+ and, through the decorator-derived target, over the Cap'n Web endpoint.
338
+
339
+ Connections use Cloudflare's WebSocket Hibernation API. Idle clients remain
340
+ connected while the Durable Object can leave memory; when a message wakes the
341
+ object, its constructor and lifecycle startup run again before `onMessage`.
342
+ State needed after a wake must be stored durably or through
343
+ `connection.setState()`. There is no non-hibernating mode.
344
+
345
+ ## Native RPC
346
+
347
+ Native Durable Object RPC does not pass through `fetch`. An RPC method that
348
+ requires initialized capabilities starts the lifecycle explicitly:
349
+
350
+ ```ts
351
+ async runTask(): Promise<void> {
352
+ await this.lifecycle.start();
353
+ // initialized work
354
+ }
355
+ ```
356
+
357
+ Agent's internal RPC entry points already enforce this boundary.
358
+
359
+ ## Object names
360
+
361
+ Use `idFromName()` or `getByName()`. The lifecycle reads the authoritative name
362
+ from `ctx.id.name` and exposes it as `lifecycle.name`.
363
+
364
+ For migration only, the lifecycle can read an existing `__ps_name` record
365
+ written by an older PartyServer release. It never writes that key. Deprecated
366
+ name headers and bootstrap methods are not supported.
367
+
368
+ If a name cannot be resolved, the error covers named addressing, updating local
369
+ Wrangler/workerd and the compatibility date, unsupported raw IDs and oversized
370
+ names, and rescheduling alarms created before 2026-03-15.
@@ -505,7 +505,7 @@ This pattern has several advantages for long-running agents:
505
505
 
506
506
  ## Delegating to sub-agents
507
507
 
508
- A project manager does not do everything itself. It delegates specialized work to sub-agents — child Durable Objects (facets) spawned under the parent. Each facet has its own isolated SQLite state and runs in parallel, but stays colocated on the same machine as the parent.
508
+ A project manager does not do everything itself. It delegates specialized work to sub-agents — child Durable Objects (facets) spawned under the parent. Each facet has its own isolated SQLite state and runs in parallel, but stays colocated on the same machine as the parent. This per-run, parent-supervised delegation is the use case facets are for; see [When to use dynamic agents](./sub-agents.md#when-to-use-dynamic-agents) before reaching for one to model long-lived independent peers.
509
509
 
510
510
  ```typescript
511
511
  export class ProjectManager extends Agent<Env, ProjectState> {
@@ -537,7 +537,7 @@ For chat-oriented sub-agents, [Think](https://github.com/cloudflare/agents/blob/
537
537
 
538
538
  The patterns above handle the project manager's coordination work — scheduling, delegating, polling. But the project manager also uses an LLM directly: generating plans, summarizing progress, drafting status emails. Those LLM calls stream tokens over a connection that cannot be resumed if the agent is evicted mid-response.
539
539
 
540
- For chat-oriented agents built on `AIChatAgent`, this is an even sharper problem — the user is watching the response stream in real time and sees it stop mid-sentence. `chatRecovery` wraps each chat turn in a `runFiber`, providing automatic `keepAlive` during streaming and a recovery hook when the agent restarts:
540
+ For chat-oriented agents built on `AIChatAgent` or `Think`, this is an even sharper problem — the user is watching the response stream in real time and sees it stop mid-sentence. Durable recovery wraps every chat turn in a `runFiber`, providing automatic `keepAlive` during streaming and a recovery hook when the agent restarts:
541
541
 
542
542
  ```typescript
543
543
  import { AIChatAgent } from "@cloudflare/ai-chat";
@@ -547,8 +547,6 @@ import type {
547
547
  } from "@cloudflare/ai-chat";
548
548
 
549
549
  class ProjectChat extends AIChatAgent<Env> {
550
- override chatRecovery = true;
551
-
552
550
  override async onChatRecovery(
553
551
  ctx: ChatRecoveryContext
554
552
  ): Promise<ChatRecoveryOptions> {
@@ -573,7 +571,7 @@ The right recovery strategy depends on the LLM provider:
573
571
 
574
572
  For a complete multi-provider implementation with full code for each strategy, see the [`forever-chat` example](https://github.com/cloudflare/agents/tree/main/experimental/forever-chat) and the [`forever.md` design doc](https://github.com/cloudflare/agents/tree/main/experimental/forever.md).
575
573
 
576
- [Think](https://github.com/cloudflare/agents/blob/main/docs/think/index.md) enables `chatRecovery` by default. The default path persists partial output and auto-continues or retries the turn when safe, so many apps do not need a custom hook. Override `onChatRecovery` when a provider has a better recovery strategy, or configure `chatRecovery = { maxAttempts, terminalMessage, onExhausted }` to tune the terminal user experience.
574
+ `AIChatAgent` and [Think](https://github.com/cloudflare/agents/blob/main/docs/think/index.md) always enable durable recovery. The default path persists partial output and auto-continues or retries the turn when safe, so many apps do not need a custom hook. Override `onChatRecovery` when a provider has a better recovery strategy, or configure `chatRecovery = { maxAttempts, terminalMessage, onExhausted }` to tune the terminal user experience.
577
575
 
578
576
  If the agent is interrupted before any assistant stream chunks are written, there is no partial assistant message to continue. When the latest persisted message is still the unanswered user message from that turn, chat recovery retries the turn automatically unless `onChatRecovery` returns `{ continue: false }`.
579
577
 
@@ -625,7 +623,7 @@ The [Session API](./sessions.md) addresses this directly:
625
623
 
626
624
  - **Compaction** — automatically summarizes older messages when the estimated token count exceeds a threshold. The summary replaces the middle of the conversation as a non-destructive overlay. Original messages remain in SQLite for audit.
627
625
  - **Context blocks** — persistent structured sections injected into the system prompt (identity, memory, learned facts). The agent or the LLM can write to these blocks, and they survive hibernation and eviction.
628
- - **Multi-session management** — `SessionManager` provides a registry of named sessions within a single agent, with forking, cross-session search, and `compactAndSplit` for splitting long conversations into linked continuations.
626
+ - **Conversation isolation** — use one Durable Object per user-facing conversation and keep the conversation directory in a parent Durable Object. Sessions can still fork paths or use named handles for local drafts and namespaces.
629
627
 
630
628
  For simpler cases: keep only the last N messages in the active context (sliding window), or selectively retain messages that contain decisions and approvals while pruning routine exchanges.
631
629