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