agents 0.22.0 → 0.24.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-B2XLNMxq.d.ts} +1094 -443
- 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-Dj5Mzlw7.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 → capability-B4WbF81e.js} +2 -1
- package/dist/capability-B4WbF81e.js.map +1 -0
- package/dist/capability-runner-BUBa6Ake.d.ts +484 -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 +2319 -2010
- package/dist/chat/index.js +892 -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-BxdkCuVy.d.ts +411 -0
- package/dist/client-C9DAjZIY.js +518 -0
- package/dist/client-C9DAjZIY.js.map +1 -0
- package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
- package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
- package/dist/client.d.ts +27 -283
- package/dist/client.js +2 -274
- 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-C4w86kcN.d.ts +93 -0
- 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/durable-object-lifecycle-ei7M7lqB.d.ts +157 -0
- package/dist/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-BVVgDSdq.d.ts +1 -0
- package/dist/index-Dlgtd3Mj.d.ts +78 -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 +92 -83
- package/dist/index.js +4 -3
- 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 +39 -19
- package/dist/lifecycle/index.js +2 -2
- package/dist/lifecycle-Mm_jQh7r.js +1314 -0
- package/dist/lifecycle-Mm_jQh7r.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +2 -2
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/queue/index.d.ts +123 -0
- package/dist/queue/index.js +2 -0
- package/dist/queue-BC9F5cUo.js +406 -0
- package/dist/queue-BC9F5cUo.js.map +1 -0
- package/dist/react.d.ts +34 -16
- package/dist/react.js +31 -8
- package/dist/react.js.map +1 -1
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9B2UCq3.d.ts} +23 -6
- package/dist/retries.d.ts +10 -6
- package/dist/retries.js +21 -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-DQoTGoAW.d.ts → scheduler-BGq6M5Kd.d.ts} +20 -21
- package/dist/scheduler-D_KHqxBa.js +658 -0
- package/dist/scheduler-D_KHqxBa.js.map +1 -0
- 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-BNU3ZiJM.js} +1524 -1441
- package/dist/src-BNU3ZiJM.js.map +1 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.js +137 -0
- package/dist/state/index.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-BcE8JP2X.d.ts +370 -0
- package/dist/streams-CCPRV6dt.js +709 -0
- package/dist/streams-CCPRV6dt.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-D4nLqVSI.js +1656 -0
- package/dist/tasks-D4nLqVSI.js.map +1 -0
- package/dist/tasks-V2lCT8ZV.d.ts +517 -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/transport-protocol-BxyngQ_R.js +25 -0
- package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
- package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
- package/dist/types-B7LojTe4.d.ts +202 -0
- package/dist/types-DCD6BNZH.d.ts +70 -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 +301 -0
- package/dist/websockets/index.js +3 -0
- package/dist/websockets-D7IzWbZb.js +1005 -0
- package/dist/websockets-D7IzWbZb.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 +4 -4
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +6 -13
- package/docs/client-sdk.md +30 -0
- package/docs/context.md +131 -0
- package/docs/index.md +16 -13
- package/docs/lifecycle.md +179 -57
- package/docs/long-running-agents.md +2 -2
- package/docs/mcp-servers.md +5 -1
- package/docs/queue.md +135 -241
- package/docs/resumable-streaming.md +1 -1
- package/docs/retries.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 +126 -14
- package/dist/capability-BjSKYpzg.js.map +0 -1
- package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
- package/dist/client.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/current-agent-CuMErtly.d.ts +0 -283
- 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/chat-agents.md
CHANGED
|
@@ -520,13 +520,9 @@ their cleanup and skipped continuations return immediately.
|
|
|
520
520
|
|
|
521
521
|
#### Overriding the clear handler
|
|
522
522
|
|
|
523
|
-
The SDK's built-in `CF_AGENT_CHAT_CLEAR` handler calls `resetTurnState()`
|
|
524
|
-
automatically. If your `onMessage` override intercepts `CF_AGENT_CHAT_CLEAR`
|
|
525
|
-
and returns before the SDK sees the message — for example, to scope the delete
|
|
526
|
-
to a specific workflow — the built-in handler never runs. The active stream
|
|
527
|
-
continues and queued continuations persist into the newly-cleared conversation.
|
|
523
|
+
The SDK's built-in `CF_AGENT_CHAT_CLEAR` handler calls `resetTurnState()` and clears the default Sessions handle. If an `onMessage` override intercepts this frame and returns early, the built-in handler cannot stop the active stream or clear history.
|
|
528
524
|
|
|
529
|
-
|
|
525
|
+
Perform authorization or logging, then pass the frame to the original handler:
|
|
530
526
|
|
|
531
527
|
```typescript
|
|
532
528
|
import { MessageType } from "@cloudflare/ai-chat/types";
|
|
@@ -536,19 +532,16 @@ this.onMessage = async (connection, message) => {
|
|
|
536
532
|
if (typeof message === "string") {
|
|
537
533
|
const data = JSON.parse(message);
|
|
538
534
|
if (data.type === MessageType.CF_AGENT_CHAT_CLEAR) {
|
|
539
|
-
this.
|
|
540
|
-
|
|
541
|
-
DELETE FROM cf_ai_chat_agent_messages
|
|
542
|
-
WHERE workflow_id = ${this.workflowId}
|
|
543
|
-
`;
|
|
544
|
-
await this.saveMessages([]);
|
|
545
|
-
return;
|
|
535
|
+
await this.authorizeClear(connection);
|
|
536
|
+
console.log("clearing conversation");
|
|
546
537
|
}
|
|
547
538
|
}
|
|
548
539
|
return _onMessage(connection, message);
|
|
549
540
|
};
|
|
550
541
|
```
|
|
551
542
|
|
|
543
|
+
Do not write directly to Sessions tables. Use `this.sessions.session().clearMessages()` when implementing a separate server-side history operation outside the chat protocol.
|
|
544
|
+
|
|
552
545
|
### Lifecycle Hooks
|
|
553
546
|
|
|
554
547
|
Override `onConnect` and `onClose` to add custom logic. Stream resumption and message sync are handled for you automatically — you do not need to call `super`:
|
package/docs/client-sdk.md
CHANGED
|
@@ -120,6 +120,35 @@ useAgent({
|
|
|
120
120
|
});
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
### Transport
|
|
124
|
+
|
|
125
|
+
`useAgent` speaks the Agent protocol over one of two wires. The default,
|
|
126
|
+
`"cf-websocket"`, is a hibernating WebSocket managed by PartySocket. The
|
|
127
|
+
experimental `"capnweb"` transport carries protocol frames through a single
|
|
128
|
+
[Cap'n Web](https://github.com/cloudflare/capnweb) RPC session whose root
|
|
129
|
+
also serves the host's callables natively:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
useAgent({
|
|
133
|
+
agent: "ChatAgent",
|
|
134
|
+
name: "room-123",
|
|
135
|
+
transport: "capnweb" // default: "cf-websocket"
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Identity, state sync, reconnection with backoff, and terminal close codes are
|
|
140
|
+
identical on both wires; PartySocket still owns the connection and the
|
|
141
|
+
transport only swaps the socket class it instantiates. `call()` and `stub`
|
|
142
|
+
differ by design. On `cf-websocket` they send JSON `rpc` frames. On `capnweb`
|
|
143
|
+
they invoke the host's `callables` natively on the Cap'n Web session root: a
|
|
144
|
+
method that returns an `RpcTarget` hands you a live stub you can keep
|
|
145
|
+
calling, a `ReadableStream` result streams, and chained calls pipeline.
|
|
146
|
+
`@callable()` decorators on an `Agent` are a JSON-wire feature and are not
|
|
147
|
+
available on `capnweb` unless the Agent also passes a `callables` target.
|
|
148
|
+
The trade-off is that a Cap'n Web connection does not hibernate: the Durable
|
|
149
|
+
Object stays in memory while one is open. `AgentClient` accepts the same
|
|
150
|
+
`transport` option.
|
|
151
|
+
|
|
123
152
|
### Async Query Parameters
|
|
124
153
|
|
|
125
154
|
For authentication tokens or other async data, pass a function that returns a Promise:
|
|
@@ -351,6 +380,7 @@ type UseAgentOptions<State> = {
|
|
|
351
380
|
name?: string; // Instance name (default: "default")
|
|
352
381
|
host?: string; // Custom host
|
|
353
382
|
path?: string; // Custom path prefix
|
|
383
|
+
transport?: "cf-websocket" | "capnweb"; // Wire (default: "cf-websocket")
|
|
354
384
|
|
|
355
385
|
// Query parameters
|
|
356
386
|
query?:
|
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
|
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,13 +57,13 @@ 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
|
|
|
66
|
-
- [Queue](./queue.md) -
|
|
66
|
+
- [Queue](./queue.md) - Durable background task execution
|
|
67
67
|
- [Scheduling](./scheduling.md) - Delayed, scheduled, and cron-based tasks
|
|
68
68
|
- [Retries](./retries.md) - Automatic retries with exponential backoff and jitter
|
|
69
69
|
- [Durable Execution](./durable-execution.md) - `runFiber()`, `startFiber()`, `stash()`, and crash recovery for long tasks
|
|
@@ -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
|
|
@@ -141,6 +142,8 @@ The differentiator is not "we have durable state" — it is what happens when a
|
|
|
141
142
|
## Reference
|
|
142
143
|
|
|
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)
|
|
144
147
|
- TODO: [API Reference](./api-reference.md) - Complete API documentation
|
|
145
148
|
- TODO: [FAQ / How is this different from Durable Objects?](./faq.md)
|
|
146
149
|
- TODO: [Resources & Further Reading](./resources.md)
|
package/docs/lifecycle.md
CHANGED
|
@@ -26,7 +26,7 @@ export class MyObject extends DurableObject<Env> {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
onAlarm(): void {
|
|
29
|
-
// Runs after
|
|
29
|
+
// Runs once per alarm invocation, after due jobs are driven.
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
```
|
|
@@ -65,24 +65,34 @@ export default {
|
|
|
65
65
|
The default URL shape is `/agents/:binding/:name`. Direct
|
|
66
66
|
`env.MY_OBJECT.getByName(name).fetch(request)` calls work as well.
|
|
67
67
|
|
|
68
|
-
`Agent` already constructs this lifecycle
|
|
69
|
-
|
|
70
|
-
`onError`
|
|
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.
|
|
71
72
|
|
|
72
73
|
## Request call path
|
|
73
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
|
+
|
|
74
82
|
```text
|
|
75
83
|
routeAgentRequest(request)
|
|
76
84
|
└─ named Durable Object stub.fetch(request)
|
|
77
85
|
└─ lifecycle-installed fetch
|
|
78
86
|
├─ lifecycle startup capabilities
|
|
79
87
|
├─ host onStart
|
|
80
|
-
├─
|
|
81
|
-
│ └─ first Response
|
|
88
|
+
├─ capability middleware, in registration order
|
|
89
|
+
│ └─ first Response handles the request
|
|
82
90
|
└─ host onRequest
|
|
83
91
|
```
|
|
84
92
|
|
|
85
|
-
A warm object skips startup but still offers every request to its
|
|
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.
|
|
86
96
|
|
|
87
97
|
## Reusable capabilities
|
|
88
98
|
|
|
@@ -107,10 +117,6 @@ class AuditLog implements DurableObjectCapability {
|
|
|
107
117
|
return new Response("ok");
|
|
108
118
|
}
|
|
109
119
|
}
|
|
110
|
-
|
|
111
|
-
onAlarm(): void {
|
|
112
|
-
this.storage.sql.exec("DELETE FROM audit_log");
|
|
113
|
-
}
|
|
114
120
|
}
|
|
115
121
|
```
|
|
116
122
|
|
|
@@ -127,12 +133,24 @@ export class MyObject extends DurableObject<Env> {
|
|
|
127
133
|
}
|
|
128
134
|
```
|
|
129
135
|
|
|
130
|
-
Capabilities run in registration order. Startup
|
|
131
|
-
sequentially. Request handling stops at the first
|
|
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
|
|
132
139
|
failure propagates, and failed startup can be retried.
|
|
133
140
|
|
|
141
|
+
A capability declares how it claims traffic with `claims`, which defaults to
|
|
142
|
+
`"selective"`. A `"catch-all"` capability dispatches after every other
|
|
143
|
+
capability, whenever it was installed. Catch-alls are unique per dispatch
|
|
144
|
+
hook: Lifecycle refuses a second catch-all for `onRequest` or for
|
|
145
|
+
`onWebSocketUpgrade`, since it could never be reached, but one of each
|
|
146
|
+
coexists. The `WebSockets` capability is a catch-all for upgrades only, so
|
|
147
|
+
an HTTP catch-all installs beside it, and selective HTTP capabilities that
|
|
148
|
+
target their own routes run before both. A subclass that installs request or
|
|
149
|
+
upgrade middleware from its own constructor still runs first, even though
|
|
150
|
+
`Agent`'s constructor ran earlier.
|
|
151
|
+
|
|
134
152
|
Capabilities extending `LifecycleCapability` receive one standard service
|
|
135
|
-
surface: storage, readiness, startup state,
|
|
153
|
+
surface: storage, readiness, startup state, the job queue, a host
|
|
136
154
|
invocation boundary, best-effort events, and capability routing.
|
|
137
155
|
Host-specific bindings, authentication, and protocol adapters remain explicit
|
|
138
156
|
constructor dependencies. Lifecycle never grants a capability the complete
|
|
@@ -140,49 +158,62 @@ host implicitly.
|
|
|
140
158
|
|
|
141
159
|
Capability hooks run outside host context, but user callbacks run through
|
|
142
160
|
`this.lifecycle.runInHostContext(fn)` inside the host invocation context.
|
|
143
|
-
Scheduler
|
|
144
|
-
future capability that calls user code should do the same.
|
|
161
|
+
Scheduler and Queue dispatch their registered callbacks through this
|
|
162
|
+
boundary, and a future capability that calls user code should do the same.
|
|
145
163
|
|
|
146
|
-
##
|
|
164
|
+
## The job queue
|
|
147
165
|
|
|
148
|
-
Lifecycle owns the Durable Object's
|
|
149
|
-
|
|
150
|
-
|
|
166
|
+
Lifecycle owns the Durable Object's queue of durable work and its single
|
|
167
|
+
physical alarm. A job is a serialisable callback address — the owning
|
|
168
|
+
capability plus a function name — with a due time and a payload. A capability
|
|
169
|
+
that needs future work pushes a job and implements `onJob()`:
|
|
151
170
|
|
|
152
171
|
```ts
|
|
153
|
-
import {
|
|
172
|
+
import {
|
|
173
|
+
LifecycleCapability,
|
|
174
|
+
type LifecycleJobContext
|
|
175
|
+
} from "agents/lifecycle";
|
|
154
176
|
|
|
155
177
|
class Cleanup extends LifecycleCapability {
|
|
156
178
|
constructor() {
|
|
157
179
|
super("cleanup");
|
|
158
180
|
}
|
|
159
181
|
|
|
160
|
-
async
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async onAlarm(): Promise<void> {
|
|
165
|
-
const next = await this.lifecycle.storage.get<number>("cleanup:next");
|
|
166
|
-
if (next === undefined || next > Date.now()) return;
|
|
167
|
-
await this.lifecycle.storage.delete("cleanup:next");
|
|
182
|
+
async scheduleCleanup(time: number): Promise<void> {
|
|
183
|
+
await this.lifecycle.jobs.push({ id: "cleanup", fn: "sweep", time });
|
|
168
184
|
}
|
|
169
185
|
|
|
170
|
-
async
|
|
171
|
-
|
|
172
|
-
await this.lifecycle.
|
|
186
|
+
async onJob({ job }: LifecycleJobContext): Promise<void> {
|
|
187
|
+
// job.fn === "sweep"; returning nothing completes the job.
|
|
188
|
+
await this.lifecycle.storage.delete("cleanup:marker");
|
|
173
189
|
}
|
|
174
190
|
}
|
|
175
191
|
```
|
|
176
192
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
The queue is ordered by timestamp, and every queue mutation re-arms the
|
|
194
|
+
physical alarm automatically — there is no explicit rearm call. When the
|
|
195
|
+
alarm fires, Lifecycle drives due jobs in due order as an event loop, then
|
|
196
|
+
runs host `onAlarm()`, then re-arms from queue state. Before driving any job
|
|
197
|
+
it arms a deadman pre-alarm so an isolate death mid-drive still wakes the
|
|
198
|
+
object to resume.
|
|
199
|
+
|
|
200
|
+
A job's drive result decides what happens next: returning nothing completes
|
|
201
|
+
and deletes it, `{ rescheduleAt }` suspends it until a future time, and
|
|
202
|
+
`"yield"` leaves it due so the object wakes again immediately. Lifecycle also
|
|
203
|
+
owns dispatch retries: a job's `retry` options bound in-process attempts,
|
|
204
|
+
platform-class failures (a superseded isolate after a deploy, a memory-limit
|
|
205
|
+
reset) preserve the job for a fresh invocation, and a terminal application
|
|
206
|
+
failure reaches the owner's `onJobError()`, whose result decides advancement.
|
|
207
|
+
|
|
208
|
+
A job pushed with `exclusive: true` suppresses ordinary alarm candidates
|
|
209
|
+
while it is pending — Agent's deferred destroy uses this so a condemned
|
|
210
|
+
object cannot be kept alive by other work. A `singleflight` job is skipped
|
|
211
|
+
while a previous run is still in flight, until it crosses its hung timeout.
|
|
212
|
+
The host pushes jobs through `lifecycle.jobs` and implements the same
|
|
213
|
+
`onJob()` hook (a host job's terminal failure completes it; the host
|
|
214
|
+
re-derives its jobs from durable state). Capabilities do not depend on
|
|
215
|
+
Scheduler or
|
|
216
|
+
on each other merely to receive wakes.
|
|
186
217
|
|
|
187
218
|
## Capability events
|
|
188
219
|
|
|
@@ -217,7 +248,7 @@ dispatch. A host with child objects supplies the transport internally.
|
|
|
217
248
|
|
|
218
249
|
Agent uses this for facet schedules: Scheduler sends owner-scoped CRUD to the
|
|
219
250
|
root Scheduler and routes due callbacks back to the matching facet Scheduler.
|
|
220
|
-
|
|
251
|
+
Facet schedules live as jobs in the root's queue. Scheduler does not
|
|
221
252
|
implement facet traversal, and Agent exposes only one internal generic Lifecycle
|
|
222
253
|
route aperture.
|
|
223
254
|
|
|
@@ -267,33 +298,124 @@ Host context values follow the invocation:
|
|
|
267
298
|
|
|
268
299
|
- `onStart` and `onAlarm`: object;
|
|
269
300
|
- `onRequest`: object and request;
|
|
270
|
-
- `onConnect`: object, connection, and
|
|
271
|
-
|
|
301
|
+
- `WebSockets` capability handlers `onConnect`: object, connection, and
|
|
302
|
+
upgrade request;
|
|
303
|
+
- `WebSockets` capability handlers `onMessage`, `onClose`, and `onError`:
|
|
304
|
+
object and connection.
|
|
272
305
|
|
|
273
306
|
`getConnectionTags(connection, { request })` remains argument-driven because it
|
|
274
307
|
already receives both values explicitly. The root `agents` package continues
|
|
275
308
|
to export `getCurrentAgent()` for the `Agent` class as a compatibility alias.
|
|
276
309
|
|
|
277
|
-
## WebSockets
|
|
310
|
+
## WebSockets are an opt-in capability
|
|
278
311
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
`
|
|
283
|
-
|
|
284
|
-
State needed after a wake must be stored durably or through connection state:
|
|
312
|
+
Lifecycle itself does not model WebSockets. Hosts that want connections
|
|
313
|
+
install the `WebSockets` capability, which owns the subsystem end to end —
|
|
314
|
+
it claims upgrades, dispatches handlers inside the host invocation boundary,
|
|
315
|
+
and answers `getConnections()`:
|
|
285
316
|
|
|
286
317
|
```ts
|
|
287
|
-
|
|
288
|
-
connection.setState({ authenticated: true });
|
|
289
|
-
}
|
|
318
|
+
import { WebSockets } from "agents/websockets";
|
|
290
319
|
|
|
291
|
-
|
|
292
|
-
|
|
320
|
+
export class MyObject extends DurableObject<Env> {
|
|
321
|
+
readonly webSockets = new WebSockets({
|
|
322
|
+
handlers: {
|
|
323
|
+
onConnect: (connection) => {
|
|
324
|
+
connection.setState({ authenticated: true });
|
|
325
|
+
},
|
|
326
|
+
onMessage: (connection, message) => {
|
|
327
|
+
connection.send(`echo:${message}`);
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
callables: new MyCallables(this)
|
|
331
|
+
});
|
|
332
|
+
readonly lifecycle = Lifecycle.install(this).use(this.webSockets);
|
|
293
333
|
}
|
|
294
334
|
```
|
|
295
335
|
|
|
296
|
-
|
|
336
|
+
Without the capability installed, WebSocket upgrades are declined.
|
|
337
|
+
|
|
338
|
+
### A plain host works with `useAgent`
|
|
339
|
+
|
|
340
|
+
The capability speaks the Agent protocol on every connection, so a plain
|
|
341
|
+
Durable Object is reachable from `useAgent` and `AgentClient` exactly like an
|
|
342
|
+
`Agent`:
|
|
343
|
+
|
|
344
|
+
- On connect it sends the identity frame, which resolves the client's
|
|
345
|
+
`ready` and `identified`, then the current state when `state` is set.
|
|
346
|
+
`protocol` controls this: `true` (default) for every connection, a
|
|
347
|
+
function to decide per connection — `false` marks it no-protocol, so it
|
|
348
|
+
gets no protocol text frames on connect or by broadcast but still sends
|
|
349
|
+
and receives ordinary messages and callables — or `false` to have the
|
|
350
|
+
host drive the sequence itself with `sendIdentity()` and `sendState()`.
|
|
351
|
+
`Agent` passes `false`, because it must decide whether a connection
|
|
352
|
+
belongs to a facet before any frame is sent.
|
|
353
|
+
- `readonly` decides per connection whether state writes over the wire are
|
|
354
|
+
refused; `setReadonly()` flips it later. Both flags are stored in the
|
|
355
|
+
connection's own state under `_cf_` keys, hidden from `connection.state`
|
|
356
|
+
and preserved across `setState`, so they survive hibernation. `Agent`'s
|
|
357
|
+
`isConnectionReadonly`, `setConnectionReadonly`, and
|
|
358
|
+
`isConnectionProtocolEnabled` delegate to the same storage.
|
|
359
|
+
- `callables` is an `RpcTarget` whose prototype methods are the host's
|
|
360
|
+
complete remote interface, reached through `call()` and `stub`. On the
|
|
361
|
+
`cf-websocket` wire the capability answers them as JSON `rpc` frames. On
|
|
362
|
+
the `capnweb` wire they are native Cap'n Web methods on the session root:
|
|
363
|
+
a returned `RpcTarget` arrives as a live stub the client can keep
|
|
364
|
+
calling, a `ReadableStream` streams, and chained calls pipeline. Methods
|
|
365
|
+
run through the host invocation boundary with the calling connection in
|
|
366
|
+
scope.
|
|
367
|
+
- `state` takes a `State` capability (from `agents/state`) and syncs it over
|
|
368
|
+
connections, so `useAgent().state` and `setState()` work against a plain
|
|
369
|
+
host:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
readonly state = new State({
|
|
373
|
+
initialState: { count: 0 },
|
|
374
|
+
// The state owner decides who hears about a change.
|
|
375
|
+
onChanged: (_state, source) => this.webSockets.broadcastState(source)
|
|
376
|
+
});
|
|
377
|
+
readonly webSockets = new WebSockets({ state: this.state });
|
|
378
|
+
readonly lifecycle = Lifecycle.install(this)
|
|
379
|
+
.use(this.state)
|
|
380
|
+
.use(this.webSockets);
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
The current value is pushed to each new connection after identity. A
|
|
384
|
+
client's `cf_agent_state` frame goes through the `State` capability, so
|
|
385
|
+
the host's own `validateStateChange` decides; a readonly connection is
|
|
386
|
+
refused, and a rejected change is logged server-side and answered with a
|
|
387
|
+
generic `cf_agent_state_error`. `broadcastState(source)` pushes the
|
|
388
|
+
current value to every protocol-enabled connection except `source`. This
|
|
389
|
+
is distinct from `connection.setState()`, which is per-connection and
|
|
390
|
+
never leaves the host. Without the option, state is never sent or
|
|
391
|
+
accepted over connections.
|
|
392
|
+
|
|
393
|
+
- Any other frame goes to `handlers.onMessage`.
|
|
394
|
+
|
|
395
|
+
An `Agent` adds no new surface for this: its `@callable()`-decorated methods
|
|
396
|
+
are its JSON-wire interface, answered by its own message handler. They are
|
|
397
|
+
not mirrored onto the Cap'n Web root; an Agent that wants native calls on
|
|
398
|
+
`capnweb` passes a `callables` target like any other host.
|
|
399
|
+
|
|
400
|
+
### Two wires
|
|
401
|
+
|
|
402
|
+
The client chooses how frames travel:
|
|
403
|
+
|
|
404
|
+
- **`cf-websocket`** (default) — accepted with Cloudflare's WebSocket Hibernation
|
|
405
|
+
API. Idle clients remain connected while the Durable Object leaves memory;
|
|
406
|
+
when a message wakes it, the constructor and lifecycle startup run again
|
|
407
|
+
before `onMessage`. State needed after a wake belongs in storage or
|
|
408
|
+
`connection.setState()`.
|
|
409
|
+
- **Cap'n Web** (`?__agents_transport=capnweb`, or
|
|
410
|
+
`useAgent({ transport: "capnweb" })`) — protocol frames travel through one
|
|
411
|
+
pipe method on a Cap'n Web session whose root also carries the host's
|
|
412
|
+
`callables` natively. The connection is an in-memory socket and does not
|
|
413
|
+
hibernate: the object stays pinned while it is open, and clients reconnect
|
|
414
|
+
after an eviction.
|
|
415
|
+
|
|
416
|
+
Handlers are wire-agnostic. Both kinds of connection dispatch the same
|
|
417
|
+
`onConnect`/`onMessage`/`onClose`/`onError`, appear in `getConnections()`,
|
|
418
|
+
and honour `connection.close(code, reason)`.
|
|
297
419
|
|
|
298
420
|
## Native RPC
|
|
299
421
|
|
|
@@ -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> {
|
|
@@ -623,7 +623,7 @@ The [Session API](./sessions.md) addresses this directly:
|
|
|
623
623
|
|
|
624
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.
|
|
625
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.
|
|
626
|
-
- **
|
|
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.
|
|
627
627
|
|
|
628
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.
|
|
629
629
|
|