agents 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-DE5zmCQ8.d.ts} +1038 -394
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-C0XJqWzB.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/browser/ai.js +4 -1
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
- package/dist/callable-decorator.d.ts +18 -0
- package/dist/callable-decorator.js +71 -0
- package/dist/callable-decorator.js.map +1 -0
- package/dist/capability-BjSKYpzg.js.map +1 -1
- package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
- package/dist/channel-Bnm4S7T2.d.ts +491 -0
- package/dist/channels/ai-sdk.d.ts +46 -0
- package/dist/channels/ai-sdk.js +120 -0
- package/dist/channels/ai-sdk.js.map +1 -0
- package/dist/channels/email.d.ts +95 -0
- package/dist/channels/email.js +323 -0
- package/dist/channels/email.js.map +1 -0
- package/dist/channels/index.d.ts +233 -0
- package/dist/channels/index.js +608 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/slack.d.ts +140 -0
- package/dist/channels/slack.js +614 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/tanstack-ai.d.ts +39 -0
- package/dist/channels/tanstack-ai.js +17 -0
- package/dist/channels/tanstack-ai.js.map +1 -0
- package/dist/channels/telegram.d.ts +106 -0
- package/dist/channels/telegram.js +427 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/voice.d.ts +45 -0
- package/dist/channels/voice.js +122 -0
- package/dist/channels/voice.js.map +1 -0
- package/dist/chat/index.d.ts +2325 -2019
- package/dist/chat/index.js +888 -518
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +7 -7
- package/dist/chat-sdk/index.js +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/context/index.d.ts +216 -0
- package/dist/context/index.js +454 -0
- package/dist/context/index.js.map +1 -0
- package/dist/{current-agent-CuMErtly.d.ts → current-agent-Da_C9a3b.d.ts} +90 -107
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
- package/dist/diagnostics-BzvaX2UT.js.map +1 -0
- package/dist/diagnostics-C4jcz3VK.js +360 -0
- package/dist/diagnostics-C4jcz3VK.js.map +1 -0
- package/dist/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-BVVgDSdq.d.ts +1 -0
- package/dist/index-XDkuQ7zm.d.ts +89 -0
- package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
- package/dist/index.d.ts +91 -82
- package/dist/index.js +3 -2
- package/dist/ingress-BfetZbMO.js +83 -0
- package/dist/ingress-BfetZbMO.js.map +1 -0
- package/dist/internal-CYlgHl1l.js +59 -0
- package/dist/internal-CYlgHl1l.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +35 -17
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle-CMRGjZdw.js +1299 -0
- package/dist/lifecycle-CMRGjZdw.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/react.d.ts +4 -4
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +13 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +137 -0
- package/dist/routing/index.js +244 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sanitize-D9TujEK8.js +79 -0
- package/dist/sanitize-D9TujEK8.js.map +1 -0
- package/dist/scheduler-DD9NdYbF.js +665 -0
- package/dist/scheduler-DD9NdYbF.js.map +1 -0
- package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-Dwh85ZGl.d.ts} +21 -22
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +441 -0
- package/dist/sessions/index.js +2063 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/skills/index.d.ts +99 -0
- package/dist/skills/index.js +254 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/{src-5W6JNKVb.js → src-DlSHshb2.js} +1460 -1110
- package/dist/src-DlSHshb2.js.map +1 -0
- package/dist/streams/index.d.ts +120 -0
- package/dist/streams/index.js +107 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams-D6tJ0NN9.d.ts +370 -0
- package/dist/streams-DZKgAj9b.js +709 -0
- package/dist/streams-DZKgAj9b.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/surface-bZZJqBka.js +17 -0
- package/dist/surface-bZZJqBka.js.map +1 -0
- package/dist/tasks/index.d.ts +64 -0
- package/dist/tasks/index.js +2 -0
- package/dist/tasks-BRJ5zgya.d.ts +517 -0
- package/dist/tasks-ylZgBjhj.js +1656 -0
- package/dist/tasks-ylZgBjhj.js.map +1 -0
- package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
- package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
- package/dist/text-stream-CpdiKrJB.js +272 -0
- package/dist/text-stream-CpdiKrJB.js.map +1 -0
- package/dist/tokens-nHAKcN6M.js +52 -0
- package/dist/tokens-nHAKcN6M.js.map +1 -0
- package/dist/tool-schema-CBjGPrsQ.js +31 -0
- package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
- package/dist/types-B7LojTe4.d.ts +202 -0
- package/dist/types-_Faxb570.d.ts +439 -0
- package/dist/voice/client.d.ts +226 -0
- package/dist/voice/client.js +932 -0
- package/dist/voice/client.js.map +1 -0
- package/dist/voice/errors.d.ts +43 -0
- package/dist/voice/errors.js +41 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/index.d.ts +271 -0
- package/dist/voice/index.js +1812 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/react.d.ts +167 -0
- package/dist/voice/react.js +234 -0
- package/dist/voice/react.js.map +1 -0
- package/dist/voice/sfu.d.ts +71 -0
- package/dist/voice/sfu.js +157 -0
- package/dist/voice/sfu.js.map +1 -0
- package/dist/voice/text.d.ts +6 -0
- package/dist/voice/text.js +2 -0
- package/dist/voice/types.d.ts +58 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/workers-ai.d.ts +136 -0
- package/dist/voice/workers-ai.js +568 -0
- package/dist/voice/workers-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +192 -0
- package/dist/websockets/index.js +2 -0
- package/dist/websockets-DUfRHPRq.js +502 -0
- package/dist/websockets-DUfRHPRq.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +21 -21
- package/dist/workflows.js +1 -1
- package/docs/agent-class.md +2 -2
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +6 -13
- package/docs/context.md +131 -0
- package/docs/index.md +15 -12
- package/docs/lifecycle.md +102 -55
- package/docs/long-running-agents.md +2 -2
- package/docs/mcp-servers.md +5 -1
- package/docs/resumable-streaming.md +1 -1
- package/docs/routing.md +105 -0
- package/docs/sessions.md +237 -871
- package/docs/streams.md +213 -0
- package/docs/sub-agents.md +184 -124
- package/docs/tasks.md +246 -0
- package/docs/voice.md +745 -0
- package/package.json +115 -13
- package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
- package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
- package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
- package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
- package/dist/diagnostics-CaBjfz4J.js.map +0 -1
- package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
- package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
- package/dist/experimental/memory/session/index.d.ts +0 -671
- package/dist/experimental/memory/session/index.js +0 -2379
- package/dist/experimental/memory/session/index.js.map +0 -1
- package/dist/experimental/memory/utils/index.d.ts +0 -96
- package/dist/experimental/memory/utils/index.js +0 -79
- package/dist/experimental/memory/utils/index.js.map +0 -1
- package/dist/scheduler-CR9RHGos.js +0 -857
- package/dist/scheduler-CR9RHGos.js.map +0 -1
- package/dist/src-5W6JNKVb.js.map +0 -1
- package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
- package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
package/docs/sub-agents.md
CHANGED
|
@@ -1,40 +1,57 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Dynamic agents (facets)
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
|
33
|
-
async
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
|
|
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
|
|
47
|
-
const
|
|
63
|
+
// Connect to the supervisor:
|
|
64
|
+
const supervisor = useAgent({ agent: "Supervisor", name: userId });
|
|
48
65
|
|
|
49
|
-
// Connect to a specific
|
|
50
|
-
const
|
|
51
|
-
agent: "
|
|
66
|
+
// Connect to a specific child:
|
|
67
|
+
const runner = useAgent({
|
|
68
|
+
agent: "Supervisor",
|
|
52
69
|
name: userId,
|
|
53
|
-
sub: [{ agent: "
|
|
70
|
+
sub: [{ agent: "JobRunner", name: runId }]
|
|
54
71
|
});
|
|
55
72
|
```
|
|
56
73
|
|
|
57
|
-
The resulting URL for the
|
|
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
|
-
│
|
|
64
|
-
│ -
|
|
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
|
-
|
|
71
|
-
- own
|
|
72
|
-
- own
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
111
|
+
Dynamic agents can schedule their own callbacks with `this.schedule()` and `this.scheduleEvery()`:
|
|
87
112
|
|
|
88
113
|
```typescript
|
|
89
|
-
export class
|
|
114
|
+
export class JobRunner extends Agent {
|
|
90
115
|
async onStart() {
|
|
91
|
-
await this.scheduleEvery(60, "
|
|
116
|
+
await this.scheduleEvery(60, "checkpoint");
|
|
92
117
|
}
|
|
93
118
|
|
|
94
|
-
async
|
|
95
|
-
// Runs inside the
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
129
|
-
await
|
|
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 `
|
|
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.
|
|
184
|
+
### `this.dynamicAgents.delete(Cls, name)`
|
|
149
185
|
|
|
150
|
-
Abort a running
|
|
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.
|
|
189
|
+
await this.dynamicAgents.delete(JobRunner, "run-abc");
|
|
154
190
|
```
|
|
155
191
|
|
|
156
|
-
### `this.
|
|
192
|
+
### `this.dynamicAgents.abort(Cls, name, reason?)`
|
|
157
193
|
|
|
158
|
-
Forcefully abort a running
|
|
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.
|
|
197
|
+
this.dynamicAgents.abort(JobRunner, "run-abc", new Error("quota exceeded"));
|
|
162
198
|
```
|
|
163
199
|
|
|
164
|
-
### `this.
|
|
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.
|
|
205
|
+
if (!this.dynamicAgents.has(JobRunner, id)) {
|
|
170
206
|
return new Response("not found", { status: 404 });
|
|
171
207
|
}
|
|
172
208
|
```
|
|
173
209
|
|
|
174
|
-
### `this.
|
|
210
|
+
### `this.dynamicAgents.list(Cls?)`
|
|
175
211
|
|
|
176
|
-
List spawned
|
|
212
|
+
List spawned children, optionally filtered by class. Returns `{ className, name, createdAt }` rows in creation order.
|
|
177
213
|
|
|
178
214
|
```typescript
|
|
179
|
-
const
|
|
180
|
-
// → [{ className: "
|
|
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
|
|
232
|
+
export class Supervisor extends Agent {
|
|
197
233
|
override async onBeforeSubAgent(_req, { className, name }) {
|
|
198
|
-
// Strict-registry gate: only allow clients to reach
|
|
199
|
-
// have actually been created
|
|
200
|
-
if (!this.
|
|
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
|
|
254
|
+
// Inside a JobRunner that was spawned by a Supervisor:
|
|
219
255
|
this.parentPath;
|
|
220
|
-
// → [{ className: "
|
|
256
|
+
// → [{ className: "Supervisor", name: "user-123" }]
|
|
221
257
|
|
|
222
258
|
this.selfPath;
|
|
223
|
-
// → [{ className: "
|
|
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 `
|
|
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
|
|
234
|
-
await
|
|
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
|
|
263
|
-
agent: "
|
|
298
|
+
const runner = useAgent({
|
|
299
|
+
agent: "Supervisor",
|
|
264
300
|
name: userId,
|
|
265
|
-
sub: [{ agent: "
|
|
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/
|
|
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: "
|
|
286
|
-
{ className: "
|
|
321
|
+
{ className: "Supervisor", name: userId },
|
|
322
|
+
{ className: "JobRunner", name: runId }
|
|
287
323
|
],
|
|
288
324
|
{ leafPath: "/callbacks/job" }
|
|
289
325
|
);
|
|
290
326
|
|
|
291
|
-
// /agents/
|
|
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
|
|
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,7 +348,7 @@ 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
|
|
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
|
|
|
@@ -320,7 +356,7 @@ Root Agent names follow `routeAgentRequest`'s raw pathname-segment behavior and
|
|
|
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
|
|
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.
|
|
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/
|
|
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.
|
|
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
|
|
351
|
-
const
|
|
386
|
+
const supervisor = await getAgentByName(env.Supervisor, userId);
|
|
387
|
+
const runner = await getSubAgentByName(supervisor, JobRunner, runId);
|
|
352
388
|
|
|
353
|
-
await
|
|
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
|
-
`
|
|
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.
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
|
422
|
+
## Scheduling and durable work in dynamic agents
|
|
387
423
|
|
|
388
|
-
|
|
424
|
+
Dynamic agents can schedule their own callbacks and run durable fibers:
|
|
389
425
|
|
|
390
|
-
- `this.schedule()` / `this.scheduleEvery()` / `this.cancelSchedule()` work on a
|
|
391
|
-
- `this.getScheduleById()` / `this.listSchedules()` work on a
|
|
392
|
-
- `this.runFiber()` and Think `chatRecovery` work on a
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
476
|
+
## Examples
|
|
416
477
|
|
|
417
|
-
- `
|
|
418
|
-
- `
|
|
419
|
-
-
|
|
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`
|
|
426
|
-
- [Long-running agents](./long-running-agents.md) — how
|
|
427
|
-
- [Callable methods](./callable-methods.md) — `@callable` methods work unchanged on
|
|
428
|
-
- [Scheduling](./scheduling.md) — scheduling primitives for top-level
|
|
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
|
|