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
@@ -1,40 +1,57 @@
1
- # Sub-agents
1
+ # Dynamic agents (facets)
2
2
 
3
- Sub-agents are child Durable Objects colocated under a parent agent. Each sub-agent has its own isolated SQLite storage and its own WebSocket connections, but shares the parent's machine and is spawned, listed, and torn down through the parent. Clients reach a sub-agent directly via a nested URL; inside an agent, sub-agents are typed RPC stubs.
3
+ Dynamic agents are child Durable Objects **colocated under and supervised by** a parent agent, built on the runtime's facet primitive. Each child runs in its **own isolate** with its **own SQLite database**, but lives inside the parent's Durable Object: the parent spawns it, can abort or delete it, and is the only way to reach it. Inside an agent they are typed RPC stubs reached via `this.dynamicAgents`; clients reach one directly via a nested URL.
4
4
 
5
- Use sub-agents when a single user or entity owns an open-ended set of long-lived agents chats, documents, sessions, shards, projects and you want each one to run in parallel with its own state while keeping one parent agent as the coordinator.
5
+ Use dynamic agents for code whose **class or lifecycle the parent owns**: dynamically-loaded or AI-generated code that has no wrangler binding, per-run tool agents, sandboxed components that need isolated storage plus supervised abort/restart. That is what the runtime built facets for.
6
+
7
+ Do **not** use dynamic agents to model an open-ended set of independent peers — many chats, documents, or sessions per user. Those want one top-level Durable Object each plus a per-user index; see [When to use dynamic agents](#when-to-use-dynamic-agents) and [`examples/next/chats`](https://github.com/cloudflare/agents/tree/main/examples/next/chats).
6
8
 
7
9
  If you want a parent chat agent to dispatch another chat-capable agent during a
8
10
  single turn and render that child's progress inline, use [Agent Tools](./agent-tools.md).
9
- Agent tools are built on sub-agents, but add a parent-side run registry,
11
+ Agent tools are built on dynamic agents, but add a parent-side run registry,
10
12
  streaming `agent-tool-event` frames, replay, cancellation, and cleanup.
11
13
 
14
+ > **Naming**: dynamic agents were previously called **sub-agents**. The
15
+ > `subAgent()` / `hasSubAgent()` / `listSubAgents()` / `abortSubAgent()` /
16
+ > `deleteSubAgent()` methods still work and now delegate to the same
17
+ > `this.dynamicAgents` capability; they are deprecated in place. The `/sub/`
18
+ > URL segment, `useAgent({ sub })`, and the `onBeforeSubAgent` hook are
19
+ > unchanged.
20
+
12
21
  ## Overview
13
22
 
14
23
  ```typescript
15
24
  import { Agent, callable } from "agents";
16
25
 
17
- export class Inbox extends Agent {
26
+ export class Supervisor extends Agent {
27
+ @callable()
28
+ async runJob(runId: string, input: string) {
29
+ // Spawn (or reattach to) an isolated child for this run. The child
30
+ // gets its own isolate and its own SQLite database, colocated with
31
+ // and supervised by this agent.
32
+ const worker = await this.dynamicAgents.get(JobRunner, runId);
33
+ return worker.execute(input);
34
+ }
35
+
18
36
  @callable()
19
- async createChat() {
20
- const id = crypto.randomUUID();
21
- // Spawn a child Chat Durable Object under this Inbox.
22
- await this.subAgent(Chat, id);
23
- return id;
37
+ cancelJob(runId: string) {
38
+ // Stops the child immediately; its storage survives for inspection.
39
+ this.dynamicAgents.abort(JobRunner, runId, new Error("cancelled"));
24
40
  }
25
41
 
26
42
  @callable()
27
- listChats() {
28
- return this.listSubAgents(Chat);
43
+ async cleanupJob(runId: string) {
44
+ // Wipes the child's storage and registry entry.
45
+ await this.dynamicAgents.delete(JobRunner, runId);
29
46
  }
30
47
  }
31
48
 
32
- export class Chat extends Agent {
33
- async say(text: string) {
34
- // Reach back up to the inbox by class the framework fills in
35
- // the parent's instance name from `this.parentPath`.
36
- const inbox = await this.parentAgent(Inbox);
37
- await inbox.recordTurn(this.name, text);
49
+ export class JobRunner extends Agent {
50
+ async execute(input: string) {
51
+ // Runs in the child's own isolate; this.sql is the child's own DB.
52
+ // Reach back up by class when needed:
53
+ const supervisor = await this.parentAgent(Supervisor);
54
+ // ...
38
55
  }
39
56
  }
40
57
  ```
@@ -43,90 +60,107 @@ export class Chat extends Agent {
43
60
  // Client
44
61
  import { useAgent } from "agents/react";
45
62
 
46
- // Connect to the inbox for the sidebar:
47
- const inbox = useAgent({ agent: "Inbox", name: userId });
63
+ // Connect to the supervisor:
64
+ const supervisor = useAgent({ agent: "Supervisor", name: userId });
48
65
 
49
- // Connect to a specific chat child:
50
- const chat = useAgent({
51
- agent: "Inbox",
66
+ // Connect to a specific child:
67
+ const runner = useAgent({
68
+ agent: "Supervisor",
52
69
  name: userId,
53
- sub: [{ agent: "Chat", name: chatId }]
70
+ sub: [{ agent: "JobRunner", name: runId }]
54
71
  });
55
72
  ```
56
73
 
57
- The resulting URL for the chat connection is `/agents/inbox/{userId}/sub/chat/{chatId}`.
74
+ The resulting URL for the child connection is `/agents/supervisor/{userId}/sub/job-runner/{runId}`.
58
75
 
59
76
  ## Concepts
60
77
 
61
78
  ```
62
79
  ┌──────────────────────────────────────────────────────────┐
63
- Inbox (Durable Object, "user-123")
64
- │ - parent of all chats for this user
65
- │ - owns chat list + shared memory │
80
+ Supervisor (Durable Object, "user-123")
81
+ │ - owns the registry and lifecycle of its children
66
82
  │ - runs `onBeforeSubAgent` on every incoming /sub/ hop │
83
+ │ - owns the one physical alarm and the WebSockets │
67
84
  └────┬─────────────────────┬───────────────────┬───────────┘
68
85
  │ │ │
69
86
  ▼ ▼ ▼
70
- Chat ("chat-a") Chat ("chat-b") Chat ("chat-c")
71
- - own SQLite - own SQLite - own SQLite
72
- - own WS clients - own WS clients - own WS clients
73
- - runs in parallel with siblings
87
+ JobRunner ("run-a") JobRunner ("run-b") CodemodeRuntime
88
+ - own isolate - own isolate - own isolate
89
+ - own SQLite - own SQLite - own SQLite
90
+ - runs in parallel with siblings, on the same machine
74
91
  ```
75
92
 
76
- ### Colocation
93
+ ### Facet semantics
94
+
95
+ Dynamic agents are backed by workerd **facets**. The properties below are what the runtime actually provides — several of them are the reason this is an isolation primitive rather than a scale-out primitive:
77
96
 
78
- Sub-agents (also called **facets**) live on the **same machine** as their parent. They are not independent Durable Objects scattered across the edge — they are colocated with the parent for RPC latency and shared-memory patterns. Two chats belonging to the same inbox can run in parallel because each is its own single-threaded isolate, but they all share the parent's physical location.
97
+ - **Separate isolate, same machine.** Each facet runs in its own isolate (its own JS heap), colocated with the parent for cheap RPC. The whole tree shares the parent's physical placement and moves or dies with it facets never scatter across the edge.
98
+ - **Own SQLite database.** Each facet's storage is invisible to its siblings and to the parent's SQL. The runtime stores the databases together as one logical root object; do not treat a facet as an independently placed top-level storage object.
99
+ - **No independent alarms.** Facets cannot set a physical alarm; the top-level parent owns the one alarm slot and the SDK routes scheduled callbacks back into children (see [Scheduling](#scheduling)).
100
+ - **Supervised lifecycle.** The parent can abort a child transitively (storage survives), delete it (storage wiped), and restart the same storage under a _different class_ — a code upgrade on stable state. A broken facet breaks the whole actor, except when the parent itself aborted it.
101
+ - **Independent hibernation.** A facet hibernates and restarts independently of its parent, but cannot outlive the root Durable Object's placement.
102
+ - **Private addressability.** A facet is reachable only through its parent; siblings cannot see each other unless the parent passes references.
103
+ - **Bounded nesting.** Facet trees are limited in depth (currently four levels including the root).
79
104
 
80
105
  ### Independent state
81
106
 
82
- Each sub-agent has its own SQLite database and its own in-memory state. Writes from one sibling never leak into another. When a sub-agent is deleted with `deleteSubAgent()`, its storage is wiped.
107
+ Each dynamic agent has its own SQLite database and its own in-memory state. Writes from one sibling never leak into another. When a child is deleted with `this.dynamicAgents.delete()`, its storage is wiped.
83
108
 
84
109
  ### Scheduling
85
110
 
86
- Sub-agents can schedule their own callbacks with `this.schedule()` and `this.scheduleEvery()`:
111
+ Dynamic agents can schedule their own callbacks with `this.schedule()` and `this.scheduleEvery()`:
87
112
 
88
113
  ```typescript
89
- export class Chat extends Agent {
114
+ export class JobRunner extends Agent {
90
115
  async onStart() {
91
- await this.scheduleEvery(60, "compactHistory");
116
+ await this.scheduleEvery(60, "checkpoint");
92
117
  }
93
118
 
94
- async compactHistory() {
95
- // Runs inside the Chat sub-agent.
96
- // this.sql points at the Chat SQLite database.
119
+ async checkpoint() {
120
+ // Runs inside the child; this.sql points at the child's database.
97
121
  }
98
122
  }
99
123
  ```
100
124
 
101
- The top-level parent still owns the underlying Durable Object alarm because facets do not have independent alarm slots. The Agents SDK stores a logical owner path for the sub-agent schedule, wakes the parent when the alarm fires, then dispatches the callback back into the sub-agent. The callback runs with the sub-agent as `this`, so it uses the sub-agent's SQLite storage, state, `parentPath`, and `getCurrentAgent()` context.
125
+ The top-level parent still owns the underlying Durable Object alarm because facets do not have independent alarm slots. The Agents SDK stores a logical owner path for the child's schedule, wakes the parent when the alarm fires, then dispatches the callback back into the child. The callback runs with the child as `this`, so it uses the child's SQLite storage, state, `parentPath`, and `getCurrentAgent()` context.
102
126
 
103
- `cancelSchedule()`, `getScheduleById()`, and `listSchedules()` also work inside sub-agents. They are scoped to the calling sub-agent — a sub-agent cannot cancel or list a sibling's schedules by id. To clear every schedule under a sub-agent (and any of its descendants), call `parent.deleteSubAgent(Cls, name)` from the parent. The older synchronous `getSchedule()` and `getSchedules()` APIs throw inside sub-agents because scheduled rows are stored on the top-level parent.
127
+ `cancelSchedule()`, `getScheduleById()`, and `listSchedules()` also work inside dynamic agents. They are scoped to the calling child — a child cannot cancel or list a sibling's schedules by id. To clear every schedule under a child (and any of its descendants), call `this.dynamicAgents.delete(Cls, name)` from the parent. The older synchronous `getSchedule()` and `getSchedules()` APIs throw inside dynamic agents because scheduled rows are stored on the top-level parent.
104
128
 
105
- Calling `this.destroy()` inside a sub-agent delegates the same teardown back to the parent: it cancels the sub-agent's parent-owned schedules (and descendants), removes the sub-agent from the parent's registry, and asks the runtime to wipe the sub-agent's storage. Because the underlying `ctx.facets.delete` call aborts the sub-agent's isolate, treat `this.destroy()` as fire-and-forget — it may not return cleanly to the caller.
129
+ Calling `this.destroy()` inside a dynamic agent delegates the same teardown back to the parent: it cancels the child's parent-owned schedules (and descendants), removes the child from the parent's registry, and asks the runtime to wipe the child's storage. Because the underlying `ctx.facets.delete` call aborts the child's isolate, treat `this.destroy()` as fire-and-forget — it may not return cleanly to the caller.
106
130
 
107
131
  ### Durable execution and chat recovery
108
132
 
109
- Sub-agents can use `runFiber()` and Think's `chatRecovery` just like top-level agents. Fiber rows live in the sub-agent's own SQLite database, so recovery hooks run with the sub-agent as `this` and see the sub-agent's state, storage, `parentPath`, and `getCurrentAgent()` context.
133
+ Dynamic agents can use `runFiber()` and Think's `chatRecovery` just like top-level agents. Fiber rows live in the child's own SQLite database, so recovery hooks run with the child as `this` and see the child's state, storage, `parentPath`, and `getCurrentAgent()` context.
110
134
 
111
- Because facets do not have independent alarm slots, the top-level parent owns the physical alarm heartbeat for sub-agent fibers. The sub-agent still stores fiber rows and snapshots in its own SQLite database, while the parent stores a small root-side index of active facet fibers. When the parent alarm fires, it checks that index and routes recovery checks back into the owning sub-agent. Think's chat recovery can schedule its recovered continuation from inside the sub-agent; the parent owns the physical alarm and routes the continuation back to the child.
135
+ Because facets do not have independent alarm slots, the top-level parent owns the physical alarm heartbeat for child fibers. The child still stores fiber rows and snapshots in its own SQLite database, while the parent stores a small root-side index of active facet fibers. When the parent alarm fires, it checks that index and routes recovery checks back into the owning child. Think's chat recovery can schedule its recovered continuation from inside the child; the parent owns the physical alarm and routes the continuation back to the child.
112
136
 
113
- Sub-agents can also start [Workflows](./workflows.md) with `this.runWorkflow()`. Workflow tracking is local to the sub-agent's SQLite database, and `AgentWorkflow.agent` routes RPC, callbacks, state updates, and broadcasts back to the originating sub-agent. Parent agents do not automatically list or control child-started workflows. Because `SubAgentStub<T>` only exposes user-defined child methods, add child wrapper methods for controls such as `getWorkflow()`, `approveWorkflow()`, or `terminateWorkflow()`, then call those wrappers through `await this.subAgent(Child, name)`. If you pass `runWorkflow(..., { agentBinding })` from a sub-agent, use the root Agent binding name, not a child binding name.
137
+ Dynamic agents can also start [Workflows](./workflows.md) with `this.runWorkflow()`. Workflow tracking is local to the child's SQLite database, and `AgentWorkflow.agent` routes RPC, callbacks, state updates, and broadcasts back to the originating child. Parent agents do not automatically list or control child-started workflows. Because the child stub only exposes user-defined child methods, add child wrapper methods for controls such as `getWorkflow()`, `approveWorkflow()`, or `terminateWorkflow()`, then call those wrappers through `await this.dynamicAgents.get(Child, name)`. If you pass `runWorkflow(..., { agentBinding })` from a child, use the root Agent binding name, not a child binding name.
114
138
 
115
- For sub-agent workflow origins, `AgentWorkflow.agent` is RPC-only. Use it to call Agent methods, but use `routeSubAgentRequest()` or the nested `/agents/{parent}/{name}/sub/{child}/{name}` URL shape for external HTTP or WebSocket routing instead of `this.agent.fetch()`.
139
+ For child workflow origins, `AgentWorkflow.agent` is RPC-only. Use it to call Agent methods, but use `routeSubAgentRequest()` or the nested `/agents/{parent}/{name}/sub/{child}/{name}` URL shape for external HTTP or WebSocket routing instead of `this.agent.fetch()`.
116
140
 
117
141
  ### Shared identity
118
142
 
119
- Sub-agents know who their parent is via `this.parentPath` (root-first ancestor chain) and `this.parentAgent(ParentClass)` (typed stub). A sub-agent with no parent (top-level agent) has `parentPath === []`.
143
+ Dynamic agents know who their parent is via `this.parentPath` (root-first ancestor chain) and `this.parentAgent(ParentClass)` (typed stub). A child with no parent (top-level agent) has `parentPath === []`.
120
144
 
121
145
  ## Server API
122
146
 
123
- ### `this.subAgent(Cls, name)`
147
+ The capability lives at `this.dynamicAgents`. The legacy method names delegate to it and remain supported:
148
+
149
+ | Legacy (deprecated) | Capability |
150
+ | -------------------------------- | -------------------------------------- |
151
+ | `this.subAgent(Cls, name)` | `this.dynamicAgents.get(Cls, name)` |
152
+ | `this.abortSubAgent(Cls, name)` | `this.dynamicAgents.abort(Cls, name)` |
153
+ | `this.deleteSubAgent(Cls, name)` | `this.dynamicAgents.delete(Cls, name)` |
154
+ | `this.hasSubAgent(Cls, name)` | `this.dynamicAgents.has(Cls, name)` |
155
+ | `this.listSubAgents(Cls?)` | `this.dynamicAgents.list(Cls?)` |
156
+
157
+ ### `this.dynamicAgents.get(Cls, name)`
124
158
 
125
- Get or create a sub-agent. Lazy: the first call for `(Cls, name)` spawns the child; subsequent calls return the existing instance. Returns a typed RPC stub.
159
+ Get or create a dynamic agent. Lazy: the first call for `(Cls, name)` spawns the child; subsequent calls return the existing instance. Returns a typed RPC stub.
126
160
 
127
161
  ```typescript
128
- const chat = await this.subAgent(Chat, "chat-abc");
129
- await chat.ping();
162
+ const runner = await this.dynamicAgents.get(JobRunner, "run-abc");
163
+ await runner.ping();
130
164
  ```
131
165
 
132
166
  The child class must:
@@ -138,46 +172,48 @@ The child class must:
138
172
 
139
173
  The parent class also has requirements that are implicit for normal usage but worth knowing if you hit the related error:
140
174
 
141
- - Be bound as a Durable Object namespace in `wrangler.jsonc durable_objects.bindings`. (Top-level agents always are — this matters only if you try to call `subAgent()` from a class that's exported but unbound.)
175
+ - Be bound as a Durable Object namespace in `wrangler.jsonc durable_objects.bindings`. (Top-level agents always are — this matters only if you try to call `dynamicAgents.get()` from a class that's exported but unbound.)
142
176
  - Have its class name preserved by your bundler. The framework looks the parent up via `ctx.exports[this.constructor.name].idFromName(name)` to give the child its own `ctx.id.name`. If your bundler minifies class identifiers (e.g. esbuild without `keepNames: true`), `this.constructor.name` becomes a short id like `_a` and the lookup fails. The framework throws a descriptive error in that case pointing at the bundler config.
143
177
 
178
+ For code with **no static class at all** — dynamically-loaded or generated Durable Object classes from Worker Loader — mount the class as a facet directly with `ctx.facets.get`; see [`examples/next/dynamic-agents`](https://github.com/cloudflare/agents/tree/main/examples/next/dynamic-agents) for the supervised-gadget pattern.
179
+
144
180
  ### Notes for testing
145
181
 
146
182
  Tests that use `@cloudflare/vitest-pool-workers` may need to list facet classes as test-only Durable Object bindings so `ctx.exports` provides a facet-compatible class value. Keep those facet classes out of `new_sqlite_classes`; the extra binding belongs only in test `wrangler.jsonc` files and is not a production Worker requirement.
147
183
 
148
- ### `this.deleteSubAgent(Cls, name)`
184
+ ### `this.dynamicAgents.delete(Cls, name)`
149
185
 
150
- Abort a running sub-agent, cancel its pending schedules, and permanently wipe its storage. Idempotent — safe to call for a never-spawned or already-deleted child.
186
+ Abort a running child, cancel its pending schedules, and permanently wipe its storage. Idempotent — safe to call for a never-spawned or already-deleted child.
151
187
 
152
188
  ```typescript
153
- await this.deleteSubAgent(Chat, "chat-abc");
189
+ await this.dynamicAgents.delete(JobRunner, "run-abc");
154
190
  ```
155
191
 
156
- ### `this.abortSubAgent(Cls, name, reason?)`
192
+ ### `this.dynamicAgents.abort(Cls, name, reason?)`
157
193
 
158
- Forcefully abort a running sub-agent without wiping its storage. The child stops executing immediately and will be restarted on next `subAgent()` access.
194
+ Forcefully abort a running child without wiping its storage. The child stops executing immediately and will be restarted on next `dynamicAgents.get()` access.
159
195
 
160
196
  ```typescript
161
- this.abortSubAgent(Chat, "chat-abc", new Error("quota exceeded"));
197
+ this.dynamicAgents.abort(JobRunner, "run-abc", new Error("quota exceeded"));
162
198
  ```
163
199
 
164
- ### `this.hasSubAgent(Cls | className, name)`
200
+ ### `this.dynamicAgents.has(Cls | className, name)`
165
201
 
166
202
  Check whether a child has been spawned and not deleted. Backed by a framework-maintained SQLite registry.
167
203
 
168
204
  ```typescript
169
- if (!this.hasSubAgent(Chat, id)) {
205
+ if (!this.dynamicAgents.has(JobRunner, id)) {
170
206
  return new Response("not found", { status: 404 });
171
207
  }
172
208
  ```
173
209
 
174
- ### `this.listSubAgents(Cls?)`
210
+ ### `this.dynamicAgents.list(Cls?)`
175
211
 
176
- List spawned sub-agents, optionally filtered by class. Returns `{ className, name, createdAt }` rows in creation order.
212
+ List spawned children, optionally filtered by class. Returns `{ className, name, createdAt }` rows in creation order.
177
213
 
178
214
  ```typescript
179
- const chats = this.listSubAgents(Chat);
180
- // → [{ className: "Chat", name: "...", createdAt: 1700... }, ...]
215
+ const runs = this.dynamicAgents.list(JobRunner);
216
+ // → [{ className: "JobRunner", name: "...", createdAt: 1700... }, ...]
181
217
  ```
182
218
 
183
219
  ### `this.onBeforeSubAgent(req, { className, name })`
@@ -193,11 +229,11 @@ Return one of:
193
229
  | `Response` | Short-circuit: send this response to the client, do not wake the child |
194
230
 
195
231
  ```typescript
196
- export class Inbox extends Agent {
232
+ export class Supervisor extends Agent {
197
233
  override async onBeforeSubAgent(_req, { className, name }) {
198
- // Strict-registry gate: only allow clients to reach chats that
199
- // have actually been created via `createChat`.
200
- if (!this.hasSubAgent(className, name)) {
234
+ // Strict-registry gate: only allow clients to reach children that
235
+ // have actually been created by this agent.
236
+ if (!this.dynamicAgents.has(className, name)) {
201
237
  return new Response(`${className} "${name}" not found`, {
202
238
  status: 404
203
239
  });
@@ -215,23 +251,23 @@ WebSocket upgrade requests flow through this hook the same way as plain HTTP. If
215
251
  Root-first ancestor chains. `parentPath` covers strict ancestors; `selfPath` includes the current agent.
216
252
 
217
253
  ```typescript
218
- // Inside a Chat that was spawned by an Inbox:
254
+ // Inside a JobRunner that was spawned by a Supervisor:
219
255
  this.parentPath;
220
- // → [{ className: "Inbox", name: "user-123" }]
256
+ // → [{ className: "Supervisor", name: "user-123" }]
221
257
 
222
258
  this.selfPath;
223
- // → [{ className: "Inbox", name: "user-123" }, { className: "Chat", name: "chat-abc" }]
259
+ // → [{ className: "Supervisor", name: "user-123" }, { className: "JobRunner", name: "run-abc" }]
224
260
  ```
225
261
 
226
262
  `parentPath` is **root-first**, so the direct parent is always `parentPath.at(-1)`. Top-level agents have `parentPath === []`.
227
263
 
228
264
  ### `this.parentAgent(Cls)`
229
265
 
230
- Typed parent stub to the **immediate** parent, resolved from `parentPath`. Symmetric with `subAgent(Cls, name)`: one opens a stub parent→child, the other opens a stub child→parent.
266
+ Typed parent stub to the **immediate** parent, resolved from `parentPath`. Symmetric with `dynamicAgents.get(Cls, name)`: one opens a stub parent→child, the other opens a stub child→parent.
231
267
 
232
268
  ```typescript
233
- const inbox = await this.parentAgent(Inbox);
234
- await inbox.recordTurn(this.name, "...");
269
+ const supervisor = await this.parentAgent(Supervisor);
270
+ await supervisor.recordProgress(this.name, "...");
235
271
  ```
236
272
 
237
273
  The framework:
@@ -259,16 +295,16 @@ When `parentAgent()` returns a facet-parent proxy, RPC methods and normal HTTP `
259
295
  Extend any `useAgent` call with a `sub` chain to connect to a descendant facet:
260
296
 
261
297
  ```tsx
262
- const chat = useAgent({
263
- agent: "Inbox",
298
+ const runner = useAgent({
299
+ agent: "Supervisor",
264
300
  name: userId,
265
- sub: [{ agent: "Chat", name: chatId }]
301
+ sub: [{ agent: "JobRunner", name: runId }]
266
302
  });
267
303
  ```
268
304
 
269
305
  - `agent` / `name` identify the **top-level** agent (the one bound in `env`).
270
306
  - `sub` is a root-first array of `{ agent, name }` hops into descendants.
271
- - The hook builds the URL `/agents/inbox/{userId}/sub/chat/{chatId}` and opens a direct WebSocket to the `Chat` child.
307
+ - The hook builds the URL `/agents/supervisor/{userId}/sub/job-runner/{runId}` and opens a WebSocket routed to the child.
272
308
  - `.path` on the returned hook object gives you the full chain including the leaf.
273
309
 
274
310
  Every other `useAgent` feature works as usual: `state` sync, `stub.method()` calls, `@callable` RPCs, `useAgentChat` on top of the returned socket.
@@ -282,13 +318,13 @@ import { buildAgentPath } from "agents";
282
318
 
283
319
  const path = buildAgentPath(
284
320
  [
285
- { className: "Inbox", name: userId },
286
- { className: "Chat", name: chatId }
321
+ { className: "Supervisor", name: userId },
322
+ { className: "JobRunner", name: runId }
287
323
  ],
288
324
  { leafPath: "/callbacks/job" }
289
325
  );
290
326
 
291
- // /agents/inbox/{userId}/sub/chat/{chatId}/callbacks/job
327
+ // /agents/supervisor/{userId}/sub/job-runner/{runId}/callbacks/job
292
328
  ```
293
329
 
294
330
  Inside an Agent, pass `this.selfPath` directly. If the root Durable Object binding name differs from its class name, also pass `rootBinding` in the options. `buildAgentUrl()` adds a public origin, which is useful when registering callbacks, webhooks, approval URLs, or asynchronous job-completion URLs with an external system:
@@ -296,7 +332,7 @@ Inside an Agent, pass `this.selfPath` directly. If the root Durable Object bindi
296
332
  ```typescript
297
333
  import { buildAgentUrl } from "agents";
298
334
 
299
- export class Chat extends Agent<Env> {
335
+ export class JobRunner extends Agent<Env> {
300
336
  callbackUrl() {
301
337
  return buildAgentUrl(this.env.PUBLIC_ORIGIN, this.selfPath, {
302
338
  leafPath: "/callbacks/job"
@@ -312,15 +348,15 @@ export class Chat extends Agent<Env> {
312
348
  }
313
349
  ```
314
350
 
315
- The Worker must pass the incoming request to `routeAgentRequest()`. Each ancestor's `onBeforeSubAgent` hook runs before the destination receives the request. For a sub-agent destination, the nested `/sub/...` routing segments are removed during forwarding, so its pathname is the `leafPath` suffix.
351
+ The Worker must pass the incoming request to `routeAgentRequest()`. Each ancestor's `onBeforeSubAgent` hook runs before the destination receives the request. For a dynamic-agent destination, the nested `/sub/...` routing segments are removed during forwarding, so its pathname is the `leafPath` suffix.
316
352
 
317
353
  `buildAgentUrl()` accepts an HTTP(S) or WS(S) origin without a pathname, query, fragment, or credentials. Set callback query parameters through the returned URL's `searchParams`. If you use a custom routing prefix, pass the same value to both `buildAgentPath()` and `routeAgentRequest()`.
318
354
 
319
- Root Agent names follow PartyServer's existing raw pathname-segment behavior and must already be externally routable. The `sub` segment is reserved in routing prefixes, class and binding names, and root Agent names. Descendant names are URL-encoded by the helper, so names containing spaces, Unicode, `/`, or URL-reserved characters round-trip safely.
355
+ Root Agent names follow `routeAgentRequest`'s raw pathname-segment behavior and must already be externally routable. The `sub` segment is reserved in routing prefixes, class and binding names, and root Agent names. Descendant names are URL-encoded by the helper, so names containing spaces, Unicode, `/`, or URL-reserved characters round-trip safely.
320
356
 
321
357
  ### Custom routing
322
358
 
323
- For fetch handlers that do their own top-level URL parsing, use `routeSubAgentRequest` to dispatch a request into a sub-agent from an already-resolved parent stub:
359
+ For fetch handlers that do their own top-level URL parsing, use `routeSubAgentRequest` to dispatch a request into a dynamic agent from an already-resolved parent stub:
324
360
 
325
361
  ```typescript
326
362
  import { getAgentByName, routeSubAgentRequest } from "agents";
@@ -332,25 +368,25 @@ export default {
332
368
  if (!match) return new Response("Not found", { status: 404 });
333
369
 
334
370
  const [, userId, rest] = match;
335
- const parent = await getAgentByName(env.Inbox, userId);
371
+ const parent = await getAgentByName(env.Supervisor, userId);
336
372
  return routeSubAgentRequest(req, parent, { fromPath: rest });
337
373
  }
338
374
  };
339
375
  ```
340
376
 
341
- `fromPath` takes any pathname containing the sub-agent tail (something like `/sub/chat/chat-abc/...`). When the destination is already represented as a root-first Agent path, pass the result of `buildAgentPath()` directly. The helper parses the first child hop, runs the parent's `onBeforeSubAgent` hook, and forwards into the facet.
377
+ `fromPath` takes any pathname containing the sub-agent tail (something like `/sub/job-runner/run-abc/...`). When the destination is already represented as a root-first Agent path, pass the result of `buildAgentPath()` directly. The helper parses the first child hop, runs the parent's `onBeforeSubAgent` hook, and forwards into the facet.
342
378
 
343
379
  ### External typed RPC
344
380
 
345
- From inside the parent DO, `this.subAgent(Cls, name)` returns a typed stub. From **outside** the parent, use `getSubAgentByName`:
381
+ From inside the parent DO, `this.dynamicAgents.get(Cls, name)` returns a typed stub. From **outside** the parent, use `getSubAgentByName`:
346
382
 
347
383
  ```typescript
348
384
  import { getAgentByName, getSubAgentByName } from "agents";
349
385
 
350
- const inbox = await getAgentByName(env.Inbox, userId);
351
- const chat = await getSubAgentByName(inbox, Chat, chatId);
386
+ const supervisor = await getAgentByName(env.Supervisor, userId);
387
+ const runner = await getSubAgentByName(supervisor, JobRunner, runId);
352
388
 
353
- await chat.addMessage({ role: "user", content: "hi" });
389
+ await runner.execute("hi");
354
390
  ```
355
391
 
356
392
  `getSubAgentByName` returns an RPC-only Proxy — method calls work; `.fetch()` throws (use `routeSubAgentRequest` for HTTP/WS). Arguments and return values must be structured-cloneable.
@@ -359,7 +395,7 @@ await chat.addMessage({ role: "user", content: "hi" });
359
395
 
360
396
  ### Creation
361
397
 
362
- `subAgent(Cls, name)` is lazy and idempotent:
398
+ `dynamicAgents.get(Cls, name)` is lazy and idempotent:
363
399
 
364
400
  - The first call for a name triggers the child's `onStart()`.
365
401
  - Subsequent calls are no-ops and return the existing instance.
@@ -373,59 +409,83 @@ When a client connects to `/agents/{parent}/{name}/sub/{child}/{childName}`:
373
409
  2. The parent's `onBeforeSubAgent` fires.
374
410
  3. If the hook does not short-circuit, the framework resolves the facet (creating it on first access, unless the hook rejected with a `Response`).
375
411
  4. The request is forwarded to the child, which handles the WebSocket upgrade or HTTP response.
376
- 5. After the upgrade, subsequent WebSocket frames flow **directly** to the child the parent is no longer on the hot path.
412
+ 5. The **parent owns the native WebSocket for the connection's lifetime.** Every subsequent frame wakes the root parent, which forwards it to the child over serializable RPC (the parent's own gating logic does not re-run per frame); replies come back the same way. The parent stays on the hot path — a design consequence of hibernation-safe sockets, and one more reason not to fan an unbounded number of busy WebSocket sessions through one parent.
377
413
 
378
414
  ### Deletion
379
415
 
380
- `deleteSubAgent(Cls, name)` aborts any running instance, removes pending schedules for that sub-agent tree, deletes its storage, and removes its registry entry. Idempotent.
416
+ `dynamicAgents.delete(Cls, name)` aborts any running instance, removes pending schedules for that child's tree, deletes its storage, and removes its registry entry. Idempotent.
381
417
 
382
418
  ### Hibernation
383
419
 
384
- Sub-agents hibernate when idle, same as any Durable Object. `this.name` is restored automatically from the facet's `ctx.id` (the runtime carries it across eviction). `this.parentPath` is persisted during `_cf_initAsFacet` and restored on wake.
420
+ Dynamic agents hibernate when idle, same as any Durable Object. `this.name` is restored automatically from the facet's `ctx.id` (the runtime carries it across eviction). `this.parentPath` is persisted at facet init and restored on wake.
385
421
 
386
- ## Scheduling and durable work in sub-agents
422
+ ## Scheduling and durable work in dynamic agents
387
423
 
388
- Sub-agents can schedule their own callbacks and run durable fibers:
424
+ Dynamic agents can schedule their own callbacks and run durable fibers:
389
425
 
390
- - `this.schedule()` / `this.scheduleEvery()` / `this.cancelSchedule()` work on a sub-agent.
391
- - `this.getScheduleById()` / `this.listSchedules()` work on a sub-agent.
392
- - `this.runFiber()` and Think `chatRecovery` work on a sub-agent.
426
+ - `this.schedule()` / `this.scheduleEvery()` / `this.cancelSchedule()` work on a child.
427
+ - `this.getScheduleById()` / `this.listSchedules()` work on a child.
428
+ - `this.runFiber()` and Think `chatRecovery` work on a child.
393
429
 
394
- The top-level parent still owns the physical alarm because facets do not have independent alarm slots. The Agents SDK stores the child owner path with each schedule row, wakes the parent, and routes the callback back into the child. `keepAlive()` and `keepAliveWhile()` work in sub-agents by delegating their heartbeat ref to the top-level parent. `runFiber()` also works in sub-agents: fiber rows and snapshots live in the child's own SQLite database, and the parent keeps a small root-side index so alarm housekeeping can route recovery checks back into idle children.
430
+ The top-level parent still owns the physical alarm because facets do not have independent alarm slots. The Agents SDK stores the child owner path with each schedule row, wakes the parent, and routes the callback back into the child. `keepAlive()` and `keepAliveWhile()` work in dynamic agents by delegating their heartbeat ref to the top-level parent. `runFiber()` also works in dynamic agents: fiber rows and snapshots live in the child's own SQLite database, and the parent keeps a small root-side index so alarm housekeeping can route recovery checks back into idle children.
395
431
 
396
432
  ## Broadcasts
397
433
 
398
- `this.broadcast(msg)` and `setState()`-driven broadcasts work the same way inside a sub-agent as in a top-level agent — they go to the sub-agent's own WebSocket clients. Siblings do not see each other's broadcasts; reach them explicitly via RPC if needed.
434
+ `this.broadcast(msg)` and `setState()`-driven broadcasts work the same way inside a dynamic agent as in a top-level agent — they go to the child's own WebSocket clients. Siblings do not see each other's broadcasts; reach them explicitly via RPC if needed. (The frames are physically sent by the root parent, which owns the native sockets.)
435
+
436
+ ## When to use dynamic agents
437
+
438
+ The decision rule: **a facet is a child whose code or lifecycle the parent supervises and which must live inside the parent; an independent peer you address by name should be its own top-level Durable Object.**
439
+
440
+ | Situation | Dynamic agents? |
441
+ | -------------------------------------------------------------------------------- | ------------------------------------------------------ |
442
+ | Dynamically-loaded or AI-generated code needs durable, isolated storage | Yes — the only way; there is no binding to give it |
443
+ | Per-run tool agents with isolated scratch state, supervised abort, and cleanup | Yes (see [Agent Tools](./agent-tools.md)) |
444
+ | A component needs isolated storage + independent abort, colocated with the agent | Yes (codemode runtimes, sandboxes, connector wrappers) |
445
+ | The parent should control what class runs over the child's storage (upgrades) | Yes — restart the same storage under new code |
446
+ | Many chats / documents / sessions per user | **No** — one top-level DO each + a per-user index |
447
+ | The children need independent geographic placement or scaling | No — top-level DOs |
448
+ | The children need independent physical alarm slots | No — top-level DOs; revisit when facet alarms ship |
449
+ | High-fan-out busy WebSocket sessions | No — every frame wakes the one root parent |
399
450
 
400
- ## When to use sub-agents
451
+ ### The recommended many-chats pattern
452
+
453
+ One top-level Durable Object per chat, plus a per-user index DO the chats push their metadata into:
454
+
455
+ ```typescript
456
+ // ChatAgent (one DO per chat) pushes on every write, through whatever
457
+ // RPC surface the hub exposes over its RoutedAgents catalog. `seq` is a
458
+ // strictly monotonic per-chat ordinal, not a wall-clock timestamp — two
459
+ // messages sent back to back can round-trip inside one millisecond and
460
+ // a wall-clock fence would tie, silently discarding the newer push.
461
+ const user = await getAgentByName(this.env.UserAgent, ownerUserId);
462
+ await user.recordChatActivity(chatId, { title, lastMessage, seq });
463
+
464
+ // UserAgent (the hub) answers listing and cross-chat search from its
465
+ // own SQLite — no chat DO wakes up — and applies the push with
466
+ // `this.chats.setMetadata(chatId, meta)` after fencing `seq` against
467
+ // the entry's current value inside `blockConcurrencyWhile`, so a push
468
+ // delayed by a slow round-trip can't overwrite one that arrived first,
469
+ // and two concurrent pushes can't both read the same stale value.
470
+ ```
401
471
 
402
- | Situation | Sub-agents? |
403
- | ---------------------------------------------------------------------------------- | -------------------------------------------------- |
404
- | One user owns an open-ended set of long-lived contexts (chats, docs, sessions) | Yes |
405
- | You want each context to run in parallel with isolated state | Yes |
406
- | You want a single parent DO to own the index, the shared memory, and the lifecycle | Yes |
407
- | You need a worker pool, scatter/gather, or ephemeral task isolation | Often yes |
408
- | You have a single conversation per user and no need for per-context isolation | No — just use one agent |
409
- | The children need independent geographic placement | No — top-level DOs instead |
410
- | The children need their own logical scheduled callbacks or chat recovery | Yes |
411
- | The children need independent physical alarm slots | No — top-level DOs; revisit when facet alarms ship |
472
+ `RoutedAgents` packages the hub side of this pattern: a durable ID-to-Agent catalog plus request and WebSocket forwarding under one route segment. See [Routing to independent Agents](./routing.md#routing-to-independent-agents).
412
473
 
413
- ## Example
474
+ Each chat gets its own alarms, placement, and storage budget; deletion is one `chats.delete(id)` call; and "search across all my chats" reads only the index. The example treats that index as a best-effort derived projection: a failed push leaves it stale until the next message, and a push for a deleted chat is refused. See [`examples/next/chats`](https://github.com/cloudflare/agents/tree/main/examples/next/chats) for the pattern built on `RoutedAgents`, with tests. Idempotency and repair belong to the production design in [`design/rfc-user-chat-durable-objects.md`](https://github.com/cloudflare/agents/blob/main/design/rfc-user-chat-durable-objects.md).
414
475
 
415
- See [`examples/multi-ai-chat`](https://github.com/cloudflare/agents/tree/main/examples/multi-ai-chat) for a complete multi-session chat app:
476
+ ## Examples
416
477
 
417
- - `Inbox` is a top-level agent per user owns the chat list, shared memory, and the strict-registry gate.
418
- - `Chat` is an `AIChatAgent` facet. Each chat runs in parallel; storage is isolated.
419
- - Server spawns via `this.subAgent(Chat, id)`; client connects via `useAgent({ sub: [...] })`.
420
- - Shared-memory tools inside the chat use `this.parentAgent(Inbox)` to write into the parent.
478
+ - [`examples/next/dynamic-agents`](https://github.com/cloudflare/agents/tree/main/examples/next/dynamic-agents) the headline use case: a supervisor stores user-submitted Durable Object code, loads it via Worker Loader, and runs it as facets with isolated storage, supervised abort, and code upgrades over stable state.
479
+ - [`examples/agents-as-tools`](https://github.com/cloudflare/agents/tree/main/examples/agents-as-tools) per-run child agents as tools with inline streaming.
480
+ - [`examples/multi-ai-chat`](https://github.com/cloudflare/agents/tree/main/examples/multi-ai-chat) a multi-session chat app built on facet children under one `Inbox`. It works and demonstrates the routing surface, but for many long-lived chats per user prefer the top-level-DO-per-chat pattern in [`examples/next/chats`](https://github.com/cloudflare/agents/tree/main/examples/next/chats) see [When to use dynamic agents](#when-to-use-dynamic-agents).
421
481
 
422
482
  ## Related
423
483
 
424
484
  - [Think sub-agents and programmatic turns](https://github.com/cloudflare/agents/blob/main/docs/think/sub-agents.md) — Think's `chat()` RPC method for streaming from a parent to a Think-based child
425
- - [Agent Tools](./agent-tools.md) — run Think or `AIChatAgent` sub-agents as tools with inline streaming child timelines
426
- - [Long-running agents](./long-running-agents.md) — how sub-agents fit alongside `schedule`, `runFiber`, and workflows
427
- - [Callable methods](./callable-methods.md) — `@callable` methods work unchanged on sub-agents
428
- - [Scheduling](./scheduling.md) — scheduling primitives for top-level agents and sub-agents
485
+ - [Agent Tools](./agent-tools.md) — run Think or `AIChatAgent` children as tools with inline streaming child timelines
486
+ - [Long-running agents](./long-running-agents.md) — how dynamic agents fit alongside `schedule`, `runFiber`, and workflows
487
+ - [Callable methods](./callable-methods.md) — `@callable` methods work unchanged on dynamic agents
488
+ - [Scheduling](./scheduling.md) — scheduling primitives for top-level and dynamic agents
429
489
 
430
490
  ## See also
431
491