agents 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-DE5zmCQ8.d.ts} +1038 -394
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-C0XJqWzB.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/browser/ai.js +4 -1
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
- package/dist/callable-decorator.d.ts +18 -0
- package/dist/callable-decorator.js +71 -0
- package/dist/callable-decorator.js.map +1 -0
- package/dist/capability-BjSKYpzg.js.map +1 -1
- package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
- package/dist/channel-Bnm4S7T2.d.ts +491 -0
- package/dist/channels/ai-sdk.d.ts +46 -0
- package/dist/channels/ai-sdk.js +120 -0
- package/dist/channels/ai-sdk.js.map +1 -0
- package/dist/channels/email.d.ts +95 -0
- package/dist/channels/email.js +323 -0
- package/dist/channels/email.js.map +1 -0
- package/dist/channels/index.d.ts +233 -0
- package/dist/channels/index.js +608 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/slack.d.ts +140 -0
- package/dist/channels/slack.js +614 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/tanstack-ai.d.ts +39 -0
- package/dist/channels/tanstack-ai.js +17 -0
- package/dist/channels/tanstack-ai.js.map +1 -0
- package/dist/channels/telegram.d.ts +106 -0
- package/dist/channels/telegram.js +427 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/voice.d.ts +45 -0
- package/dist/channels/voice.js +122 -0
- package/dist/channels/voice.js.map +1 -0
- package/dist/chat/index.d.ts +2325 -2019
- package/dist/chat/index.js +888 -518
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +7 -7
- package/dist/chat-sdk/index.js +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/context/index.d.ts +216 -0
- package/dist/context/index.js +454 -0
- package/dist/context/index.js.map +1 -0
- package/dist/{current-agent-CuMErtly.d.ts → current-agent-Da_C9a3b.d.ts} +90 -107
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
- package/dist/diagnostics-BzvaX2UT.js.map +1 -0
- package/dist/diagnostics-C4jcz3VK.js +360 -0
- package/dist/diagnostics-C4jcz3VK.js.map +1 -0
- package/dist/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-BVVgDSdq.d.ts +1 -0
- package/dist/index-XDkuQ7zm.d.ts +89 -0
- package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
- package/dist/index.d.ts +91 -82
- package/dist/index.js +3 -2
- package/dist/ingress-BfetZbMO.js +83 -0
- package/dist/ingress-BfetZbMO.js.map +1 -0
- package/dist/internal-CYlgHl1l.js +59 -0
- package/dist/internal-CYlgHl1l.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +35 -17
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle-CMRGjZdw.js +1299 -0
- package/dist/lifecycle-CMRGjZdw.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/react.d.ts +4 -4
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +13 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +137 -0
- package/dist/routing/index.js +244 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sanitize-D9TujEK8.js +79 -0
- package/dist/sanitize-D9TujEK8.js.map +1 -0
- package/dist/scheduler-DD9NdYbF.js +665 -0
- package/dist/scheduler-DD9NdYbF.js.map +1 -0
- package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-Dwh85ZGl.d.ts} +21 -22
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +441 -0
- package/dist/sessions/index.js +2063 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/skills/index.d.ts +99 -0
- package/dist/skills/index.js +254 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/{src-5W6JNKVb.js → src-DlSHshb2.js} +1460 -1110
- package/dist/src-DlSHshb2.js.map +1 -0
- package/dist/streams/index.d.ts +120 -0
- package/dist/streams/index.js +107 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams-D6tJ0NN9.d.ts +370 -0
- package/dist/streams-DZKgAj9b.js +709 -0
- package/dist/streams-DZKgAj9b.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/surface-bZZJqBka.js +17 -0
- package/dist/surface-bZZJqBka.js.map +1 -0
- package/dist/tasks/index.d.ts +64 -0
- package/dist/tasks/index.js +2 -0
- package/dist/tasks-BRJ5zgya.d.ts +517 -0
- package/dist/tasks-ylZgBjhj.js +1656 -0
- package/dist/tasks-ylZgBjhj.js.map +1 -0
- package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
- package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
- package/dist/text-stream-CpdiKrJB.js +272 -0
- package/dist/text-stream-CpdiKrJB.js.map +1 -0
- package/dist/tokens-nHAKcN6M.js +52 -0
- package/dist/tokens-nHAKcN6M.js.map +1 -0
- package/dist/tool-schema-CBjGPrsQ.js +31 -0
- package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
- package/dist/types-B7LojTe4.d.ts +202 -0
- package/dist/types-_Faxb570.d.ts +439 -0
- package/dist/voice/client.d.ts +226 -0
- package/dist/voice/client.js +932 -0
- package/dist/voice/client.js.map +1 -0
- package/dist/voice/errors.d.ts +43 -0
- package/dist/voice/errors.js +41 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/index.d.ts +271 -0
- package/dist/voice/index.js +1812 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/react.d.ts +167 -0
- package/dist/voice/react.js +234 -0
- package/dist/voice/react.js.map +1 -0
- package/dist/voice/sfu.d.ts +71 -0
- package/dist/voice/sfu.js +157 -0
- package/dist/voice/sfu.js.map +1 -0
- package/dist/voice/text.d.ts +6 -0
- package/dist/voice/text.js +2 -0
- package/dist/voice/types.d.ts +58 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/workers-ai.d.ts +136 -0
- package/dist/voice/workers-ai.js +568 -0
- package/dist/voice/workers-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +192 -0
- package/dist/websockets/index.js +2 -0
- package/dist/websockets-DUfRHPRq.js +502 -0
- package/dist/websockets-DUfRHPRq.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +21 -21
- package/dist/workflows.js +1 -1
- package/docs/agent-class.md +2 -2
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +6 -13
- package/docs/context.md +131 -0
- package/docs/index.md +15 -12
- package/docs/lifecycle.md +102 -55
- package/docs/long-running-agents.md +2 -2
- package/docs/mcp-servers.md +5 -1
- package/docs/resumable-streaming.md +1 -1
- package/docs/routing.md +105 -0
- package/docs/sessions.md +237 -871
- package/docs/streams.md +213 -0
- package/docs/sub-agents.md +184 -124
- package/docs/tasks.md +246 -0
- package/docs/voice.md +745 -0
- package/package.json +115 -13
- package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
- package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
- package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
- package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
- package/dist/diagnostics-CaBjfz4J.js.map +0 -1
- package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
- package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
- package/dist/experimental/memory/session/index.d.ts +0 -671
- package/dist/experimental/memory/session/index.js +0 -2379
- package/dist/experimental/memory/session/index.js.map +0 -1
- package/dist/experimental/memory/utils/index.d.ts +0 -96
- package/dist/experimental/memory/utils/index.js +0 -79
- package/dist/experimental/memory/utils/index.js.map +0 -1
- package/dist/scheduler-CR9RHGos.js +0 -857
- package/dist/scheduler-CR9RHGos.js.map +0 -1
- package/dist/src-5W6JNKVb.js.map +0 -1
- package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
- package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
package/dist/workflows.d.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Tt as WorkflowEventPayload,
|
|
10
|
-
_t as RunWorkflowOptions,
|
|
2
|
+
Ct as WorkflowProgressCallback,
|
|
3
|
+
Dt as WorkflowTrackingRow,
|
|
4
|
+
Et as WorkflowStatus,
|
|
5
|
+
St as WorkflowPage,
|
|
6
|
+
Tt as WorkflowRejectedError,
|
|
7
|
+
_t as WorkflowCompleteCallback,
|
|
8
|
+
bt as WorkflowEventPayload,
|
|
11
9
|
c as Agent,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
dt as DefaultProgress,
|
|
11
|
+
ft as RunWorkflowOptions,
|
|
12
|
+
gt as WorkflowCallbackType,
|
|
13
|
+
it as AgentWorkflowEvent,
|
|
14
|
+
lt as AgentWorkflowStep,
|
|
15
|
+
mt as WorkflowCallback,
|
|
16
|
+
pt as WaitForApprovalOptions,
|
|
17
|
+
st as AgentWorkflowParams,
|
|
18
|
+
ut as ApprovalEventPayload,
|
|
19
|
+
vt as WorkflowErrorCallback,
|
|
20
|
+
wt as WorkflowQueryCriteria,
|
|
21
|
+
xt as WorkflowInfo,
|
|
22
|
+
yt as WorkflowEventCallback
|
|
23
|
+
} from "./agent-routing-DE5zmCQ8.js";
|
|
24
24
|
import { WorkflowEntrypoint, WorkflowEvent } from "cloudflare:workers";
|
|
25
25
|
|
|
26
26
|
//#region src/workflows.d.ts
|
package/dist/workflows.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isInternalJsStubProp } from "./utils.js";
|
|
2
2
|
import { getAgentByName } from "./agent-routing.js";
|
|
3
|
-
import "./src-
|
|
3
|
+
import "./src-DlSHshb2.js";
|
|
4
4
|
import { WorkflowRejectedError } from "./workflow-types.js";
|
|
5
5
|
import { WorkflowEntrypoint } from "cloudflare:workers";
|
|
6
6
|
//#region src/workflows.ts
|
package/docs/agent-class.md
CHANGED
|
@@ -82,7 +82,7 @@ export class MyDurableObject extends DurableObject {
|
|
|
82
82
|
|
|
83
83
|
`Lifecycle` owns the Agent's physical Durable Object alarm because schedules, keep-alive, fibers, sub-agents, and other capabilities share the same alarm slot. Do not override `alarm()` or call `this.ctx.storage.setAlarm()` from an Agent feature; one caller could overwrite another feature's wake-up.
|
|
84
84
|
|
|
85
|
-
Use `this.schedule()` for named Agent callbacks. A reusable capability with its own durable work
|
|
85
|
+
Use `this.schedule()` for named Agent callbacks. A reusable capability with its own durable work pushes jobs through `this.lifecycle.jobs` and implements `onJob()`; Lifecycle drives due jobs and re-arms the alarm from queue state. See [Durable Object lifecycle](./lifecycle.md#the-job-queue) and [Scheduling](./scheduling.md).
|
|
86
86
|
|
|
87
87
|
### `this.ctx`
|
|
88
88
|
|
|
@@ -278,7 +278,7 @@ class MyAgent extends Agent {
|
|
|
278
278
|
}
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
-
Schedules are stored in the `
|
|
281
|
+
Schedules are stored as jobs in the `cf_agents_jobs` SQL table, the Lifecycle-owned job queue. Cron schedules automatically reschedule themselves after execution, while one-time schedules are deleted.
|
|
282
282
|
|
|
283
283
|
### `this.mcp` and friends
|
|
284
284
|
|
package/docs/agent-tools.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Agent Tools
|
|
2
2
|
|
|
3
3
|
Agent tools let one chat agent dispatch another chat-capable sub-agent as part
|
|
4
|
-
of its work
|
|
4
|
+
of its work — per-run, parent-supervised delegation is the flagship use case
|
|
5
|
+
for facet-backed dynamic agents. The child is a real sub-agent with its own Durable Object storage,
|
|
5
6
|
messages, tools, resumable stream, and drill-in URL. The parent keeps a small
|
|
6
7
|
run registry so clients can render the child timeline, replay it after refresh,
|
|
7
8
|
and clean it up later.
|
package/docs/channels.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# `agents/channels`
|
|
2
|
+
|
|
3
|
+
`agents/channels` gives an agent one interface for sending and receiving
|
|
4
|
+
messages across different platforms. Use a Channel directly, expose it as an AI
|
|
5
|
+
tool, or register it with a durable `ChannelHost` that owns routing and delivery
|
|
6
|
+
recovery.
|
|
7
|
+
|
|
8
|
+
> [!NOTE]
|
|
9
|
+
> Channels is experimental. Its interface _will_ change before the package
|
|
10
|
+
> reaches a stable release.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install agents
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Create channels
|
|
19
|
+
|
|
20
|
+
Each adapter turns provider configuration into the same `Channel` interface. A
|
|
21
|
+
`ChannelHost` holds them, keyed by names you choose:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { ChannelHost, routes } from "agents/channels";
|
|
25
|
+
import { email } from "agents/channels/email";
|
|
26
|
+
import { slack } from "agents/channels/slack";
|
|
27
|
+
import { telegram } from "agents/channels/telegram";
|
|
28
|
+
|
|
29
|
+
const host = new ChannelHost({
|
|
30
|
+
channels: {
|
|
31
|
+
slack: slack({
|
|
32
|
+
botToken: env.SLACK_BOT_TOKEN,
|
|
33
|
+
webhook: {
|
|
34
|
+
signingSecret: env.SLACK_SIGNING_SECRET,
|
|
35
|
+
botUserId: env.SLACK_BOT_USER_ID
|
|
36
|
+
},
|
|
37
|
+
route: routes.perThread
|
|
38
|
+
}),
|
|
39
|
+
telegram: telegram({
|
|
40
|
+
botToken: env.TELEGRAM_BOT_TOKEN,
|
|
41
|
+
webhook: { secretToken: env.TELEGRAM_WEBHOOK_SECRET },
|
|
42
|
+
route: routes.perThread
|
|
43
|
+
}),
|
|
44
|
+
email: email({
|
|
45
|
+
binding: env.EMAIL,
|
|
46
|
+
from: "agent@example.com",
|
|
47
|
+
route: routes.perThread
|
|
48
|
+
})
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
async onMessage({ route, dispatchId, message }) {
|
|
52
|
+
await conversationFor(route).receive(dispatchId, message);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 1. Send a message
|
|
58
|
+
|
|
59
|
+
A **surface** is a destination for an outbound message.
|
|
60
|
+
You get one from an inbound message's reply field, or by constructing one from a raw channel identifier through the host.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const surface = host.contactSurface({
|
|
64
|
+
channelKey: "slack",
|
|
65
|
+
scope: "T123",
|
|
66
|
+
subject: "U456"
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await host.deliver(surface, {
|
|
70
|
+
title: "Import needs attention",
|
|
71
|
+
markdown: "The customer import stopped after **1,240 records**."
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
An identity's `channelKey` names the configured Channel that observed it. Its
|
|
76
|
+
optional `scope` names a tenant within that Channel and defaults to `"default"`;
|
|
77
|
+
for example, one configured Slack app can observe the same user ID in several
|
|
78
|
+
workspaces. The Host stamps `channelKey` on inbound identities because an adapter
|
|
79
|
+
does not know the key it was configured under.
|
|
80
|
+
|
|
81
|
+
The same human observed through two configured Channels on one platform is two
|
|
82
|
+
Channel identities, just as the same human on Slack and email is. Applications
|
|
83
|
+
that know they are the same person link those identities explicitly.
|
|
84
|
+
|
|
85
|
+
Compose destinations with `fallback()` and `fanout()`:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { fallback, fanout } from "agents/channels";
|
|
89
|
+
|
|
90
|
+
await host.deliver(fallback([slackSurface, emailSurface]), message);
|
|
91
|
+
await host.deliver(fanout([slackSurface, emailSurface]), message);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`fallback()` tries destinations in order, advancing only after a _confirmed_
|
|
95
|
+
failure, so it can never duplicate a delivery. For a stream, it advances only
|
|
96
|
+
when that failure happens before the destination starts reading; replaying an
|
|
97
|
+
arbitrarily large consumed prefix would require an unbounded buffer. `fanout()`
|
|
98
|
+
sends to all destinations; a partial or uncertain result is reported as
|
|
99
|
+
`uncertain` for the same reason.
|
|
100
|
+
The Host installs both policies as ordinary Channels under reserved keys. You
|
|
101
|
+
can register another composite policy as an ordinary Channel under your own key
|
|
102
|
+
and pair it with a surface constructor that writes that key; inject only the
|
|
103
|
+
outbound resolution capability the policy needs, as the exported built-in
|
|
104
|
+
policy Channels do.
|
|
105
|
+
|
|
106
|
+
## 2. Give a destination to a model
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { generateText, stepCountIs } from "ai";
|
|
110
|
+
import { createWorkersAI } from "workers-ai-provider";
|
|
111
|
+
import { createSendMessageTool } from "agents/channels/ai-sdk";
|
|
112
|
+
|
|
113
|
+
const workersai = createWorkersAI({ binding: env.AI });
|
|
114
|
+
|
|
115
|
+
await generateText({
|
|
116
|
+
model: workersai("@cf/moonshotai/kimi-k2.7-code"),
|
|
117
|
+
prompt: "An import stopped after 1,240 records. Notify support.",
|
|
118
|
+
tools: {
|
|
119
|
+
contactSupport: createSendMessageTool(
|
|
120
|
+
host,
|
|
121
|
+
fallback([slackSurface, emailSurface]),
|
|
122
|
+
{ description: "Contact support when a person needs to intervene" }
|
|
123
|
+
)
|
|
124
|
+
},
|
|
125
|
+
stopWhen: stepCountIs(2)
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The model writes the message; you chose the destination. TanStack AI exports the
|
|
130
|
+
same `createSendMessageTool(host, surface, options)` from
|
|
131
|
+
`agents/channels/tanstack-ai`.
|
|
132
|
+
|
|
133
|
+
## 3. Receive messages
|
|
134
|
+
|
|
135
|
+
One entry point covers every configured Channel's webhook, and Workers Email
|
|
136
|
+
arrives the same way:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
export default {
|
|
140
|
+
async fetch(request: Request): Promise<Response> {
|
|
141
|
+
const response = await host.handleRequest(request);
|
|
142
|
+
if (response) return response;
|
|
143
|
+
return new Response("Not found", { status: 404 });
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
async email(message: ForwardableEmailMessage): Promise<void> {
|
|
147
|
+
await host.handleEmail(message);
|
|
148
|
+
}
|
|
149
|
+
} satisfies ExportedHandler<Env>;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Each Channel authenticates its own input and declines what isn't its business,
|
|
153
|
+
so the Host asks them in configuration order and the first to claim it wins.
|
|
154
|
+
|
|
155
|
+
### Routing
|
|
156
|
+
|
|
157
|
+
A Channel's `route` turns one normalized event into an opaque application
|
|
158
|
+
string — a Durable Object name, a queue key, a database id — or `null` to ignore
|
|
159
|
+
the event entirely. The key is used to identify a common destination for
|
|
160
|
+
messages -- i.e. typically a single conversation:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
telegram({
|
|
164
|
+
// …credentials…
|
|
165
|
+
route(event) {
|
|
166
|
+
return event.thread.isDirectMessage === true ? event.thread.id : null;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Channels exposes builtin helpers for the common mappings — `routes.perThread`
|
|
172
|
+
and `routes.perEvent`, which namespace their routes as `thread:…` and
|
|
173
|
+
`event:…`.
|
|
174
|
+
|
|
175
|
+
Deciding whether an event is relevant at all is a different question, and one
|
|
176
|
+
only your application can answer, because the answer usually depends on state
|
|
177
|
+
you hold. Write that in your own `route` and return `null` to ignore the event:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
route(event) {
|
|
181
|
+
// Slack shows a bot every message in every channel it belongs to. Answer
|
|
182
|
+
// when addressed, and otherwise only join threads we are already in.
|
|
183
|
+
const thread = routes.perThread(event);
|
|
184
|
+
const addressed =
|
|
185
|
+
event.thread.isDirectMessage === true || event.message.isMention === true;
|
|
186
|
+
if (addressed) return thread;
|
|
187
|
+
return (await myConversations.knows(thread)) ? thread : null;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
A lookup like that runs for every event reaching it, so put it behind a cheaper
|
|
192
|
+
check, keep it read-only, and do not let it create the state it is testing for —
|
|
193
|
+
otherwise the first stray message conjures the thing the check is looking for.
|
|
194
|
+
|
|
195
|
+
### Link identities
|
|
196
|
+
|
|
197
|
+
Personal agents often wwant to resolve users regardless of the channel they messaged on. Your application can explicitly record connections between channel identities and expose them to the Host for messages to be routed on:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const host = new ChannelHost({
|
|
201
|
+
channels,
|
|
202
|
+
findUser: (identity) => users.findUser(identity),
|
|
203
|
+
onMessage,
|
|
204
|
+
onApprovalResponse
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ...
|
|
208
|
+
// route to a user's central conversaion if one exists, else start a new
|
|
209
|
+
// conversation for each thread:
|
|
210
|
+
route: routes.byUser(routes.perThread);
|
|
211
|
+
|
|
212
|
+
// or prefer the linked person, then the sender we recognise, then a new
|
|
213
|
+
// conversation per event:
|
|
214
|
+
route: routes.byUser(routes.byIdentity(routes.perEvent));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`byIdentity` groups events carrying the _same_ identity. It never infers that
|
|
218
|
+
two different identities belong to one person — that stays an explicit
|
|
219
|
+
application decision, which `byUser` then exposes to routing. Omit its fallback
|
|
220
|
+
to ignore events that carry no identity at all.
|
|
221
|
+
|
|
222
|
+
If your application does not already store user identities, `createUserIdentityStore(storage)` creates a Durable Object SQL store of the right shape. Your application can call `store.link` to connect multiple identities together.
|
|
223
|
+
|
|
224
|
+
### Ask for approval
|
|
225
|
+
|
|
226
|
+
### Request approval
|
|
227
|
+
|
|
228
|
+
The Host exposes a utility for durably correlating inbound approvals to outbound requests:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
await host.requestApproval({
|
|
232
|
+
interactionId: "deploy-42",
|
|
233
|
+
request: {
|
|
234
|
+
title: "Production deployment",
|
|
235
|
+
summary: "Deploy version 2026.08.17 to production?",
|
|
236
|
+
input: {
|
|
237
|
+
version: "2026.08.17",
|
|
238
|
+
environment: "production"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
await host.requestApproval(surface, {
|
|
243
|
+
interactionId: crypto.randomUUID(),
|
|
244
|
+
request: {
|
|
245
|
+
title: "Production deployment",
|
|
246
|
+
summary: "Deploy version 2026.08.17 to production?",
|
|
247
|
+
input: { version: "2026.08.17" }
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Users can respond to approval requests through native surfaces (e.g. Telegram
|
|
253
|
+
buttons) or HTTP inbound URLs, resolved by the Channel Host itself for your application to settle.
|
|
254
|
+
|
|
255
|
+
## Custom channels
|
|
256
|
+
|
|
257
|
+
Any transport can become a Channel:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
import { matchesPath, routes, type Channel } from "agents/channels";
|
|
261
|
+
|
|
262
|
+
const supportForm: Channel = {
|
|
263
|
+
route: routes.perEvent,
|
|
264
|
+
ingress: {
|
|
265
|
+
async receive(request) {
|
|
266
|
+
if (!matchesPath(request, "/support")) return null;
|
|
267
|
+
|
|
268
|
+
const raw = await request.json<{ message: string; email: string }>();
|
|
269
|
+
const eventId = crypto.randomUUID();
|
|
270
|
+
return {
|
|
271
|
+
events: [
|
|
272
|
+
{
|
|
273
|
+
raw,
|
|
274
|
+
event: {
|
|
275
|
+
type: "message",
|
|
276
|
+
eventId,
|
|
277
|
+
thread: { id: eventId, isDirectMessage: true },
|
|
278
|
+
actor: {
|
|
279
|
+
id: raw.email,
|
|
280
|
+
// The Host stamps the configured `channelKey`.
|
|
281
|
+
identity: { subject: raw.email }
|
|
282
|
+
},
|
|
283
|
+
message: { id: eventId, text: raw.message }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
],
|
|
287
|
+
response: Response.json({ accepted: true }, { status: 202 })
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Returning `null` declines the request so another Channel can claim it.
|
|
295
|
+
|
|
296
|
+
## Durability contract
|
|
297
|
+
|
|
298
|
+
Channels holds no state: no outbox, no retries, no deduplication, no scheduler.
|
|
299
|
+
Durability is a property of how your application uses it. A caller-supplied
|
|
300
|
+
`deliveryId` is correlation metadata, not an idempotency guarantee; an adapter
|
|
301
|
+
may map it to a provider primitive when one exists.
|
|
302
|
+
|
|
303
|
+
| Channels guarantees | Your application must |
|
|
304
|
+
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
305
|
+
| A `dispatchId` stable across redelivery and unaffected by routing | Deduplicate on it before starting any side effect |
|
|
306
|
+
| The Host awaits your callback before the provider is acknowledged | Hand off durably before returning — a DO RPC, queue send, or workflow start |
|
|
307
|
+
| One outbound attempt per `deliver()` or `stream()`, reported honestly | Decide whether to retry; `uncertain` may duplicate a real delivery |
|
|
308
|
+
| Surfaces are plain JSON you can persist | Keep configured channel keys stable |
|
|
309
|
+
| Decisions arrive as normalized events carrying your own `interactionId` | Own settlement; an interaction id is not an authorization credential |
|
|
310
|
+
|
|
311
|
+
## Future work
|
|
312
|
+
|
|
313
|
+
- [ ] Approval-link ingress: signing, verification, and a confirmation page, so
|
|
314
|
+
link approvals return through the same normalized path as Slack buttons
|
|
315
|
+
- [ ] Reader-initiated stream cancellation: Slack's `message_stream_stopped`
|
|
316
|
+
and Telegram's `stopped_message_generation` should reach the running
|
|
317
|
+
generation as ordinary ingress, so aborting it errors the stream and
|
|
318
|
+
each Channel finalizes on the path it already has
|
|
319
|
+
- [ ] More built-in channels
|
|
320
|
+
- [ ] Rendering templates (pretty emails)
|
|
321
|
+
- [ ] Automatic webhook registration
|
|
322
|
+
- [ ] Security review of approval flows
|
|
323
|
+
- [ ] Conformance tests of adapters
|
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/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,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
|
|
@@ -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)
|