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.
Files changed (186) hide show
  1. package/README.md +20 -1
  2. package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-DE5zmCQ8.d.ts} +1038 -394
  3. package/dist/agent-routing.d.ts +1 -1
  4. package/dist/agent-tool-types.d.ts +26 -26
  5. package/dist/{agent-tools-C0XJqWzB.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
  6. package/dist/agent-tools.d.ts +21 -21
  7. package/dist/browser/ai.js +4 -1
  8. package/dist/browser/ai.js.map +1 -1
  9. package/dist/browser/tanstack-ai.js.map +1 -1
  10. package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
  11. package/dist/callable-decorator.d.ts +18 -0
  12. package/dist/callable-decorator.js +71 -0
  13. package/dist/callable-decorator.js.map +1 -0
  14. package/dist/capability-BjSKYpzg.js.map +1 -1
  15. package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
  16. package/dist/channel-Bnm4S7T2.d.ts +491 -0
  17. package/dist/channels/ai-sdk.d.ts +46 -0
  18. package/dist/channels/ai-sdk.js +120 -0
  19. package/dist/channels/ai-sdk.js.map +1 -0
  20. package/dist/channels/email.d.ts +95 -0
  21. package/dist/channels/email.js +323 -0
  22. package/dist/channels/email.js.map +1 -0
  23. package/dist/channels/index.d.ts +233 -0
  24. package/dist/channels/index.js +608 -0
  25. package/dist/channels/index.js.map +1 -0
  26. package/dist/channels/slack.d.ts +140 -0
  27. package/dist/channels/slack.js +614 -0
  28. package/dist/channels/slack.js.map +1 -0
  29. package/dist/channels/tanstack-ai.d.ts +39 -0
  30. package/dist/channels/tanstack-ai.js +17 -0
  31. package/dist/channels/tanstack-ai.js.map +1 -0
  32. package/dist/channels/telegram.d.ts +106 -0
  33. package/dist/channels/telegram.js +427 -0
  34. package/dist/channels/telegram.js.map +1 -0
  35. package/dist/channels/voice.d.ts +45 -0
  36. package/dist/channels/voice.js +122 -0
  37. package/dist/channels/voice.js.map +1 -0
  38. package/dist/chat/index.d.ts +2325 -2019
  39. package/dist/chat/index.js +888 -518
  40. package/dist/chat/index.js.map +1 -1
  41. package/dist/chat-sdk/index.d.ts +7 -7
  42. package/dist/chat-sdk/index.js +1 -1
  43. package/dist/client.d.ts +1 -1
  44. package/dist/context/index.d.ts +216 -0
  45. package/dist/context/index.js +454 -0
  46. package/dist/context/index.js.map +1 -0
  47. package/dist/{current-agent-CuMErtly.d.ts → current-agent-Da_C9a3b.d.ts} +90 -107
  48. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  49. package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
  50. package/dist/diagnostics-BzvaX2UT.js.map +1 -0
  51. package/dist/diagnostics-C4jcz3VK.js +360 -0
  52. package/dist/diagnostics-C4jcz3VK.js.map +1 -0
  53. package/dist/index-BB0kqhIz.d.ts +101 -0
  54. package/dist/index-BVVgDSdq.d.ts +1 -0
  55. package/dist/index-XDkuQ7zm.d.ts +89 -0
  56. package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
  57. package/dist/index.d.ts +91 -82
  58. package/dist/index.js +3 -2
  59. package/dist/ingress-BfetZbMO.js +83 -0
  60. package/dist/ingress-BfetZbMO.js.map +1 -0
  61. package/dist/internal-CYlgHl1l.js +59 -0
  62. package/dist/internal-CYlgHl1l.js.map +1 -0
  63. package/dist/internal_context.d.ts +1 -1
  64. package/dist/lifecycle/index.d.ts +35 -17
  65. package/dist/lifecycle/index.js +1 -1
  66. package/dist/lifecycle-CMRGjZdw.js +1299 -0
  67. package/dist/lifecycle-CMRGjZdw.js.map +1 -0
  68. package/dist/mcp/client/index.d.ts +20 -20
  69. package/dist/mcp/index.d.ts +35 -35
  70. package/dist/mcp/index.js +1 -1
  71. package/dist/observability/index.d.ts +1 -1
  72. package/dist/observability/index.js +1 -1
  73. package/dist/react.d.ts +4 -4
  74. package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
  75. package/dist/retries.d.ts +8 -6
  76. package/dist/retries.js +13 -1
  77. package/dist/retries.js.map +1 -1
  78. package/dist/routing/index.d.ts +137 -0
  79. package/dist/routing/index.js +244 -0
  80. package/dist/routing/index.js.map +1 -0
  81. package/dist/sanitize-D9TujEK8.js +79 -0
  82. package/dist/sanitize-D9TujEK8.js.map +1 -0
  83. package/dist/scheduler-DD9NdYbF.js +665 -0
  84. package/dist/scheduler-DD9NdYbF.js.map +1 -0
  85. package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-Dwh85ZGl.d.ts} +21 -22
  86. package/dist/schedules/index.d.ts +1 -1
  87. package/dist/schedules/index.js +1 -1
  88. package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
  89. package/dist/serializable.d.ts +1 -1
  90. package/dist/sessions/index.d.ts +441 -0
  91. package/dist/sessions/index.js +2063 -0
  92. package/dist/sessions/index.js.map +1 -0
  93. package/dist/skills/index.d.ts +99 -0
  94. package/dist/skills/index.js +254 -5
  95. package/dist/skills/index.js.map +1 -1
  96. package/dist/{src-5W6JNKVb.js → src-DlSHshb2.js} +1460 -1110
  97. package/dist/src-DlSHshb2.js.map +1 -0
  98. package/dist/streams/index.d.ts +120 -0
  99. package/dist/streams/index.js +107 -0
  100. package/dist/streams/index.js.map +1 -0
  101. package/dist/streams-D6tJ0NN9.d.ts +370 -0
  102. package/dist/streams-DZKgAj9b.js +709 -0
  103. package/dist/streams-DZKgAj9b.js.map +1 -0
  104. package/dist/sub-routing.d.ts +12 -12
  105. package/dist/surface-bZZJqBka.js +17 -0
  106. package/dist/surface-bZZJqBka.js.map +1 -0
  107. package/dist/tasks/index.d.ts +64 -0
  108. package/dist/tasks/index.js +2 -0
  109. package/dist/tasks-BRJ5zgya.d.ts +517 -0
  110. package/dist/tasks-ylZgBjhj.js +1656 -0
  111. package/dist/tasks-ylZgBjhj.js.map +1 -0
  112. package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
  113. package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
  114. package/dist/text-stream-CpdiKrJB.js +272 -0
  115. package/dist/text-stream-CpdiKrJB.js.map +1 -0
  116. package/dist/tokens-nHAKcN6M.js +52 -0
  117. package/dist/tokens-nHAKcN6M.js.map +1 -0
  118. package/dist/tool-schema-CBjGPrsQ.js +31 -0
  119. package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
  120. package/dist/types-B7LojTe4.d.ts +202 -0
  121. package/dist/types-_Faxb570.d.ts +439 -0
  122. package/dist/voice/client.d.ts +226 -0
  123. package/dist/voice/client.js +932 -0
  124. package/dist/voice/client.js.map +1 -0
  125. package/dist/voice/errors.d.ts +43 -0
  126. package/dist/voice/errors.js +41 -0
  127. package/dist/voice/errors.js.map +1 -0
  128. package/dist/voice/index.d.ts +271 -0
  129. package/dist/voice/index.js +1812 -0
  130. package/dist/voice/index.js.map +1 -0
  131. package/dist/voice/react.d.ts +167 -0
  132. package/dist/voice/react.js +234 -0
  133. package/dist/voice/react.js.map +1 -0
  134. package/dist/voice/sfu.d.ts +71 -0
  135. package/dist/voice/sfu.js +157 -0
  136. package/dist/voice/sfu.js.map +1 -0
  137. package/dist/voice/text.d.ts +6 -0
  138. package/dist/voice/text.js +2 -0
  139. package/dist/voice/types.d.ts +58 -0
  140. package/dist/voice/types.js +18 -0
  141. package/dist/voice/types.js.map +1 -0
  142. package/dist/voice/workers-ai.d.ts +136 -0
  143. package/dist/voice/workers-ai.js +568 -0
  144. package/dist/voice/workers-ai.js.map +1 -0
  145. package/dist/websockets/index.d.ts +192 -0
  146. package/dist/websockets/index.js +2 -0
  147. package/dist/websockets-DUfRHPRq.js +502 -0
  148. package/dist/websockets-DUfRHPRq.js.map +1 -0
  149. package/dist/workflow-types.d.ts +25 -25
  150. package/dist/workflows.d.ts +21 -21
  151. package/dist/workflows.js +1 -1
  152. package/docs/agent-class.md +2 -2
  153. package/docs/agent-tools.md +2 -1
  154. package/docs/channels.md +323 -0
  155. package/docs/chat-agents.md +6 -13
  156. package/docs/context.md +131 -0
  157. package/docs/index.md +15 -12
  158. package/docs/lifecycle.md +102 -55
  159. package/docs/long-running-agents.md +2 -2
  160. package/docs/mcp-servers.md +5 -1
  161. package/docs/resumable-streaming.md +1 -1
  162. package/docs/routing.md +105 -0
  163. package/docs/sessions.md +237 -871
  164. package/docs/streams.md +213 -0
  165. package/docs/sub-agents.md +184 -124
  166. package/docs/tasks.md +246 -0
  167. package/docs/voice.md +745 -0
  168. package/package.json +115 -13
  169. package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
  170. package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
  171. package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
  172. package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
  173. package/dist/diagnostics-CaBjfz4J.js.map +0 -1
  174. package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
  175. package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
  176. package/dist/experimental/memory/session/index.d.ts +0 -671
  177. package/dist/experimental/memory/session/index.js +0 -2379
  178. package/dist/experimental/memory/session/index.js.map +0 -1
  179. package/dist/experimental/memory/utils/index.d.ts +0 -96
  180. package/dist/experimental/memory/utils/index.js +0 -79
  181. package/dist/experimental/memory/utils/index.js.map +0 -1
  182. package/dist/scheduler-CR9RHGos.js +0 -857
  183. package/dist/scheduler-CR9RHGos.js.map +0 -1
  184. package/dist/src-5W6JNKVb.js.map +0 -1
  185. package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
  186. package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
@@ -1,26 +1,26 @@
1
1
  import {
2
- At as WorkflowRejectedError,
3
- Ct as WorkflowErrorCallback,
4
- Dt as WorkflowPage,
5
- Et as WorkflowInfo,
6
- Mt as WorkflowTrackingRow,
7
- Ot as WorkflowProgressCallback,
8
- St as WorkflowCompleteCallback,
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
- ft as AgentWorkflowParams,
13
- gt as DefaultProgress,
14
- ht as ApprovalEventPayload,
15
- jt as WorkflowStatus,
16
- kt as WorkflowQueryCriteria,
17
- lt as AgentWorkflowEvent,
18
- mt as AgentWorkflowStep,
19
- vt as WaitForApprovalOptions,
20
- wt as WorkflowEventCallback,
21
- xt as WorkflowCallbackType,
22
- yt as WorkflowCallback
23
- } from "./agent-routing-CnkaHb-v.js";
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-5W6JNKVb.js";
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
@@ -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 implements `getNextAlarm()` and `onAlarm()`, then calls `this.lifecycle.alarms.rearm()` after its durable state changes. See [Durable Object lifecycle](./lifecycle.md#shared-alarm-ownership) and [Scheduling](./scheduling.md).
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 `cf_agents_schedules` SQL table. Cron schedules automatically reschedule themselves after execution, while one-time schedules are deleted.
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
 
@@ -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. The child is a real sub-agent with its own Durable Object storage,
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.
@@ -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
@@ -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
- Call `this.resetTurnState()` before performing your scoped delete:
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.resetTurnState();
540
- this.sql`
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`:
@@ -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 | 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](https://github.com/cloudflare/agents/blob/main/docs/voice/index.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. |
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
- - [Sub-agents](./sub-agents.md) - Parent/child DO composition via facets, nested routing, and direct child connections
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](https://github.com/cloudflare/agents/blob/main/docs/voice/index.md) - Build voice agents with real-time speech-to-text, text-to-speech, and conversation persistence
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) - Persistent conversation storage with tree-structured messages, context blocks, compaction, and search
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)