agents 0.16.2 → 0.17.1

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 (126) hide show
  1. package/README.md +11 -8
  2. package/dist/{agent-tool-types-CTw3UJUP.d.ts → agent-tool-types-CNyE1iz_.d.ts} +671 -138
  3. package/dist/agent-tool-types.d.ts +34 -18
  4. package/dist/agent-tool-types.js +20 -1
  5. package/dist/agent-tool-types.js.map +1 -0
  6. package/dist/agent-tools-BXlsuX0d.js +304 -0
  7. package/dist/agent-tools-BXlsuX0d.js.map +1 -0
  8. package/dist/agent-tools-CSnyGvJ2.d.ts +119 -0
  9. package/dist/agent-tools.d.ts +24 -18
  10. package/dist/agent-tools.js.map +1 -1
  11. package/dist/ai-chat-agent.d.ts +1 -1
  12. package/dist/ai-chat-agent.js +1 -2
  13. package/dist/ai-chat-agent.js.map +1 -1
  14. package/dist/ai-chat-v5-migration.d.ts +1 -1
  15. package/dist/ai-chat-v5-migration.js +1 -2
  16. package/dist/ai-chat-v5-migration.js.map +1 -1
  17. package/dist/ai-react.d.ts +1 -1
  18. package/dist/ai-react.js +1 -2
  19. package/dist/ai-react.js.map +1 -1
  20. package/dist/ai-types.d.ts +1 -7
  21. package/dist/ai-types.js +2 -8
  22. package/dist/ai-types.js.map +1 -1
  23. package/dist/browser/ai.js +1 -1
  24. package/dist/browser/index.js +1 -1
  25. package/dist/chat/index.d.ts +2033 -77
  26. package/dist/chat/index.js +1828 -245
  27. package/dist/chat/index.js.map +1 -1
  28. package/dist/chat/react.d.ts +602 -0
  29. package/dist/chat/react.js +1506 -0
  30. package/dist/chat/react.js.map +1 -0
  31. package/dist/chat-sdk/index.d.ts +4 -4
  32. package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
  33. package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
  34. package/dist/{client-BXJ9n2f7.js → client-BZ-B3NhC.js} +2 -2
  35. package/dist/client-BZ-B3NhC.js.map +1 -0
  36. package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
  37. package/dist/client.d.ts +76 -57
  38. package/dist/client.js +33 -5
  39. package/dist/client.js.map +1 -1
  40. package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
  41. package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
  42. package/dist/email.js +1 -1
  43. package/dist/email.js.map +1 -1
  44. package/dist/{index-B7IbEeze.d.ts → index-BRnybD6X.d.ts} +197 -14
  45. package/dist/index.d.ts +95 -73
  46. package/dist/index.js +694 -25
  47. package/dist/index.js.map +1 -1
  48. package/dist/mcp/client.d.ts +18 -14
  49. package/dist/mcp/client.js +1 -1
  50. package/dist/mcp/index.d.ts +30 -30
  51. package/dist/mcp/index.js +7 -7
  52. package/dist/mcp/index.js.map +1 -1
  53. package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
  54. package/dist/message-builder-BymO4N_D.js.map +1 -0
  55. package/dist/observability/index.d.ts +1 -1
  56. package/dist/observability/index.js +4 -2
  57. package/dist/observability/index.js.map +1 -1
  58. package/dist/react.d.ts +123 -110
  59. package/dist/react.js +44 -11
  60. package/dist/react.js.map +1 -1
  61. package/dist/{retries-CwlpAGet.d.ts → retries-CvHJwSuh.d.ts} +15 -6
  62. package/dist/retries.d.ts +8 -6
  63. package/dist/retries.js +44 -1
  64. package/dist/retries.js.map +1 -1
  65. package/dist/serializable.d.ts +1 -1
  66. package/dist/skills/compile.js +1 -1
  67. package/dist/skills/compile.js.map +1 -1
  68. package/dist/skills/index.js +5 -5
  69. package/dist/skills/index.js.map +1 -1
  70. package/dist/sub-routing.d.ts +6 -6
  71. package/dist/utils.js +1 -1
  72. package/dist/utils.js.map +1 -1
  73. package/dist/vite.js +5 -5
  74. package/dist/vite.js.map +1 -1
  75. package/dist/wire-types-nflOzNuU.js +240 -0
  76. package/dist/wire-types-nflOzNuU.js.map +1 -0
  77. package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
  78. package/dist/workflow-types.d.ts +25 -21
  79. package/dist/workflow-types.js.map +1 -1
  80. package/dist/workflows.d.ts +22 -21
  81. package/dist/workflows.js +31 -7
  82. package/dist/workflows.js.map +1 -1
  83. package/docs/adding-to-existing-project.md +450 -0
  84. package/docs/agent-class.md +503 -0
  85. package/docs/agent-tools.md +552 -0
  86. package/docs/browse-the-web.md +430 -0
  87. package/docs/callable-methods.md +627 -0
  88. package/docs/chat-agents.md +1696 -0
  89. package/docs/chat-sdk.md +181 -0
  90. package/docs/client-sdk.md +520 -0
  91. package/docs/client-tools-continuation.md +177 -0
  92. package/docs/codemode.md +440 -0
  93. package/docs/configuration.md +775 -0
  94. package/docs/cross-domain-authentication.md +171 -0
  95. package/docs/durable-execution.md +537 -0
  96. package/docs/email.md +663 -0
  97. package/docs/get-current-agent.md +204 -0
  98. package/docs/getting-started.md +305 -0
  99. package/docs/http-websockets.md +668 -0
  100. package/docs/human-in-the-loop.md +661 -0
  101. package/docs/index.md +151 -0
  102. package/docs/long-running-agents.md +730 -0
  103. package/docs/mcp-client.md +620 -0
  104. package/docs/mcp-servers.md +526 -0
  105. package/docs/mcp-transports.md +308 -0
  106. package/docs/migration-to-ai-sdk-v5.md +96 -0
  107. package/docs/migration-to-ai-sdk-v6.md +163 -0
  108. package/docs/observability.md +261 -0
  109. package/docs/push-notifications.md +367 -0
  110. package/docs/queue.md +329 -0
  111. package/docs/readonly-connections.md +278 -0
  112. package/docs/resumable-streaming.md +127 -0
  113. package/docs/retries.md +444 -0
  114. package/docs/routing.md +749 -0
  115. package/docs/scheduling.md +898 -0
  116. package/docs/securing-mcp-servers.md +359 -0
  117. package/docs/server-driven-messages.md +477 -0
  118. package/docs/sessions.md +1024 -0
  119. package/docs/state.md +512 -0
  120. package/docs/sub-agents.md +389 -0
  121. package/docs/webhooks.md +604 -0
  122. package/docs/workflows.md +877 -0
  123. package/package.json +41 -14
  124. package/dist/agent-tools-3zLG7MgA.js.map +0 -1
  125. package/dist/agent-tools-DZhI5F6Q.d.ts +0 -14
  126. package/dist/client-BXJ9n2f7.js.map +0 -1
@@ -0,0 +1,127 @@
1
+ # Resumable Streaming
2
+
3
+ The `AIChatAgent` class provides **automatic resumable streaming** out of the box. When a client disconnects and reconnects during an active stream, the response automatically resumes from where it left off.
4
+
5
+ This is client reconnect recovery, not Durable Object eviction recovery. If the Worker process or Durable Object is evicted while the model call is in flight, enable `chatRecovery` so the turn runs inside a recoverable fiber. `Think` enables `chatRecovery` by default; plain `AIChatAgent` subclasses opt in with `override chatRecovery = true`.
6
+
7
+ ## How It Works
8
+
9
+ When you use `AIChatAgent` with `useAgentChat`:
10
+
11
+ 1. **During streaming**: All chunks are automatically persisted to SQLite
12
+ 2. **On disconnect**: The stream continues server-side, buffering chunks
13
+ 3. **On reconnect**: Client requests a resume, receives all buffered chunks, and continues streaming
14
+
15
+ No extra code is needed -- it just works. Generic client stream abort/cleanup is local-only by default, so browser navigation or React cleanup does not stop the server turn. An explicit `stop()` still cancels the server turn.
16
+
17
+ ## Example
18
+
19
+ ### Server
20
+
21
+ ```typescript
22
+ import { AIChatAgent } from "@cloudflare/ai-chat";
23
+ import { createWorkersAI } from "workers-ai-provider";
24
+ import { streamText, convertToModelMessages } from "ai";
25
+
26
+ export class ChatAgent extends AIChatAgent {
27
+ async onChatMessage() {
28
+ const workersai = createWorkersAI({ binding: this.env.AI });
29
+
30
+ const result = streamText({
31
+ model: workersai("@cf/moonshotai/kimi-k2.7-code"),
32
+ messages: await convertToModelMessages(this.messages)
33
+ });
34
+
35
+ // Automatic resumable streaming - no extra code needed
36
+ return result.toUIMessageStreamResponse();
37
+ }
38
+ }
39
+ ```
40
+
41
+ ### Client
42
+
43
+ ```tsx
44
+ import { useAgent } from "agents/react";
45
+ import { useAgentChat } from "@cloudflare/ai-chat/react";
46
+
47
+ function Chat() {
48
+ const agent = useAgent({
49
+ agent: "ChatAgent",
50
+ name: "my-chat"
51
+ });
52
+
53
+ const { messages, sendMessage, status } = useAgentChat({
54
+ agent
55
+ // resume: true is the default - streams automatically resume on reconnect
56
+ // cancelOnClientAbort: false is the default - browser cleanup does not
57
+ // cancel the server turn
58
+ });
59
+
60
+ // ... render your chat UI
61
+ }
62
+ ```
63
+
64
+ ## Under the Hood
65
+
66
+ ### Server-side (`AIChatAgent`)
67
+
68
+ - Creates SQLite tables for stream chunks and metadata on construction
69
+ - Each stream gets a unique ID and tracks chunk indices
70
+ - Chunks are batched (every 10 chunks) and flushed to SQLite for performance
71
+ - When a client sends `CF_AGENT_STREAM_RESUME_REQUEST`, the server checks for active streams and responds with `CF_AGENT_STREAM_RESUMING`
72
+ - Stale streams (older than 5 minutes) are cleaned up on restore
73
+ - Stream buffers are garbage collected from a scheduled alarm: completed or errored streams are retained for 10 minutes (a brief reconnect-and-replay grace; the assistant message itself is persisted separately), and abandoned in-flight streams are retained for 1 hour after their last chunk before being reclaimed
74
+
75
+ ### Client-side (`useAgentChat`)
76
+
77
+ - After the message handler is registered in `useEffect`, sends `CF_AGENT_STREAM_RESUME_REQUEST` to the server
78
+ - This avoids a race condition where the server's `onConnect` notification could arrive before the client's handler is ready
79
+ - On receiving `CF_AGENT_STREAM_RESUMING`, sends `CF_AGENT_STREAM_RESUME_ACK`
80
+ - Receives all buffered chunks with `replay: true` flag and applies them in a single batch
81
+ - Continues receiving live chunks as they arrive from the ongoing stream
82
+
83
+ ### The `replay` flag
84
+
85
+ Replayed chunks include `replay: true` to distinguish them from live chunks. The client uses this to batch-apply all replayed chunks before rendering, which prevents intermediate states (like reasoning "Thinking..." indicators) from flashing briefly during replay. During a live stream, chunks arrive gradually and React renders each intermediate state naturally.
86
+
87
+ ## Durable client cleanup
88
+
89
+ `resume: true` controls whether the client tries to reconnect to an active stream. `cancelOnClientAbort: false` is the default cancellation behavior: generic client stream abort/cleanup is local-only, while explicit `stop()` still cancels the server turn.
90
+
91
+ ```tsx
92
+ const { messages, stop } = useAgentChat({
93
+ agent
94
+ });
95
+ ```
96
+
97
+ If your app intentionally wants client lifecycle to own server lifecycle, opt in:
98
+
99
+ ```tsx
100
+ const { messages } = useAgentChat({
101
+ agent,
102
+ cancelOnClientAbort: true
103
+ });
104
+ ```
105
+
106
+ Use this for request-lifetime or token-saving flows. Explicit `stop()` is always
107
+ server-side cancellation regardless of `cancelOnClientAbort`.
108
+
109
+ ## Disabling Resume
110
+
111
+ If you do not want automatic resume (for example, for short responses), disable it:
112
+
113
+ ```tsx
114
+ const { messages } = useAgentChat({
115
+ agent,
116
+ resume: false // Disable automatic stream resumption
117
+ });
118
+ ```
119
+
120
+ ## Try It
121
+
122
+ See [examples/resumable-stream-chat](https://github.com/cloudflare/agents/tree/main/examples/resumable-stream-chat) for a complete working example. Start a long response, refresh the page mid-stream, and watch it resume automatically.
123
+
124
+ ## Related Docs
125
+
126
+ - [Chat Agents](./chat-agents.md) — Full `AIChatAgent` and `useAgentChat` reference
127
+ - [Client Tools Continuation](./client-tools-continuation.md) — Client-side tool execution and auto-continuation
@@ -0,0 +1,444 @@
1
+ # Retries
2
+
3
+ Retry failed operations with exponential backoff and jitter. The Agents SDK provides built-in retry support for scheduled tasks, queued tasks, and a general-purpose `this.retry()` method for your own code.
4
+
5
+ ## Overview
6
+
7
+ Transient failures are common when calling external APIs, interacting with other services, or running background tasks. The retry system handles these automatically:
8
+
9
+ - **Exponential backoff** — each retry waits longer than the last
10
+ - **Jitter** — randomized delays prevent thundering herd problems
11
+ - **Configurable** — tune attempts, delays, and caps per call site
12
+ - **Built-in** — schedule, queue, and workflow operations retry automatically
13
+
14
+ ## Quick Start
15
+
16
+ Use `this.retry()` to retry any async operation:
17
+
18
+ ```typescript
19
+ import { Agent } from "agents";
20
+
21
+ export class MyAgent extends Agent {
22
+ async fetchWithRetry(url: string) {
23
+ const response = await this.retry(async () => {
24
+ const res = await fetch(url);
25
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
26
+ return res.json();
27
+ });
28
+
29
+ return response;
30
+ }
31
+ }
32
+ ```
33
+
34
+ By default, `this.retry()` makes up to 3 attempts with jittered exponential backoff.
35
+
36
+ ## `this.retry()`
37
+
38
+ The `retry()` method is available on every `Agent` instance. It retries the provided function on any thrown error by default.
39
+
40
+ ```typescript
41
+ async retry<T>(
42
+ fn: (attempt: number) => Promise<T>,
43
+ options?: RetryOptions & {
44
+ shouldRetry?: (err: unknown, nextAttempt: number) => boolean;
45
+ }
46
+ ): Promise<T>
47
+ ```
48
+
49
+ **Parameters:**
50
+
51
+ - `fn` — the async function to retry. Receives the current attempt number (1-indexed).
52
+ - `options` — optional retry configuration (see [RetryOptions](#retryoptions) below). Options are validated eagerly — invalid values throw immediately.
53
+ - `options.shouldRetry` — optional predicate called with the thrown error and the next attempt number. Return `false` to stop retrying immediately. If not provided, all errors are retried.
54
+
55
+ **Returns:** the result of `fn` on success.
56
+
57
+ **Throws:** the last error if all attempts fail or `shouldRetry` returns `false`.
58
+
59
+ ### Examples
60
+
61
+ **Basic retry:**
62
+
63
+ ```typescript
64
+ const data = await this.retry(() => fetch("https://api.example.com/data"));
65
+ ```
66
+
67
+ **Custom retry options:**
68
+
69
+ ```typescript
70
+ const data = await this.retry(
71
+ async () => {
72
+ const res = await fetch("https://slow-api.example.com/data");
73
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
74
+ return res.json();
75
+ },
76
+ {
77
+ maxAttempts: 5,
78
+ baseDelayMs: 500,
79
+ maxDelayMs: 10000
80
+ }
81
+ );
82
+ ```
83
+
84
+ **Using the attempt number:**
85
+
86
+ ```typescript
87
+ const result = await this.retry(async (attempt) => {
88
+ console.log(`Attempt ${attempt}...`);
89
+ return await this.callExternalService();
90
+ });
91
+ ```
92
+
93
+ **Selective retry with `shouldRetry`:**
94
+
95
+ Use `shouldRetry` to stop retrying on specific errors. The predicate receives both the error and the next attempt number:
96
+
97
+ ```typescript
98
+ const data = await this.retry(
99
+ async () => {
100
+ const res = await fetch("https://api.example.com/data");
101
+ if (!res.ok) throw new HttpError(res.status, await res.text());
102
+ return res.json();
103
+ },
104
+ {
105
+ maxAttempts: 5,
106
+ shouldRetry: (err, nextAttempt) => {
107
+ // Don't retry 4xx client errors — our request is wrong
108
+ if (err instanceof HttpError && err.status >= 400 && err.status < 500) {
109
+ return false;
110
+ }
111
+ return true; // retry everything else (5xx, network errors, etc.)
112
+ }
113
+ }
114
+ );
115
+ ```
116
+
117
+ ## Retries in Schedules
118
+
119
+ Pass retry options when creating a schedule:
120
+
121
+ ```typescript
122
+ // Retry up to 5 times if the callback fails
123
+ await this.schedule(
124
+ 60,
125
+ "processTask",
126
+ { taskId: "123" },
127
+ {
128
+ retry: { maxAttempts: 5 }
129
+ }
130
+ );
131
+
132
+ // Retry with custom backoff
133
+ await this.schedule(
134
+ new Date("2026-03-01T09:00:00Z"),
135
+ "sendReport",
136
+ {},
137
+ {
138
+ retry: {
139
+ maxAttempts: 3,
140
+ baseDelayMs: 1000,
141
+ maxDelayMs: 30000
142
+ }
143
+ }
144
+ );
145
+
146
+ // Cron with retries
147
+ await this.schedule(
148
+ "0 8 * * *",
149
+ "dailyDigest",
150
+ {},
151
+ {
152
+ retry: { maxAttempts: 3 }
153
+ }
154
+ );
155
+
156
+ // Interval with retries
157
+ await this.scheduleEvery(
158
+ 30,
159
+ "poll",
160
+ { source: "api" },
161
+ {
162
+ retry: { maxAttempts: 5, baseDelayMs: 200 }
163
+ }
164
+ );
165
+ ```
166
+
167
+ If the callback throws, it is retried according to the retry options. If all attempts fail, the error is logged and routed through `onError()`. The schedule is still removed (for one-time schedules) or rescheduled (for cron/interval) regardless of success or failure.
168
+
169
+ ## Retries in Queues
170
+
171
+ Pass retry options when adding a task to the queue:
172
+
173
+ ```typescript
174
+ await this.queue(
175
+ "sendEmail",
176
+ { to: "user@example.com" },
177
+ {
178
+ retry: { maxAttempts: 5 }
179
+ }
180
+ );
181
+
182
+ await this.queue("processWebhook", webhookData, {
183
+ retry: {
184
+ maxAttempts: 3,
185
+ baseDelayMs: 500,
186
+ maxDelayMs: 5000
187
+ }
188
+ });
189
+ ```
190
+
191
+ If the callback throws, it is retried before the task is dequeued. After all attempts are exhausted, the task is dequeued and the error is logged.
192
+
193
+ ## Validation
194
+
195
+ Retry options are validated eagerly when you call `this.retry()`, `queue()`, `schedule()`, or `scheduleEvery()`. Invalid options throw immediately instead of failing later at execution time:
196
+
197
+ ```typescript
198
+ // Throws immediately: "retry.maxAttempts must be >= 1"
199
+ await this.queue("sendEmail", data, {
200
+ retry: { maxAttempts: 0 }
201
+ });
202
+
203
+ // Throws immediately: "retry.baseDelayMs must be > 0"
204
+ await this.schedule(
205
+ 60,
206
+ "process",
207
+ {},
208
+ {
209
+ retry: { baseDelayMs: -100 }
210
+ }
211
+ );
212
+
213
+ // Throws immediately: "retry.maxAttempts must be an integer"
214
+ await this.retry(() => fetch(url), { maxAttempts: 2.5 });
215
+
216
+ // Throws immediately: "retry.baseDelayMs must be <= retry.maxDelayMs"
217
+ // because baseDelayMs: 5000 exceeds the default maxDelayMs: 3000
218
+ await this.queue("sendEmail", data, {
219
+ retry: { baseDelayMs: 5000 }
220
+ });
221
+ ```
222
+
223
+ Validation resolves partial options against class-level or built-in defaults before checking cross-field constraints. This means `{ baseDelayMs: 5000 }` is caught immediately when the resolved `maxDelayMs` is 3000, rather than failing later at execution time.
224
+
225
+ ## Default Behavior
226
+
227
+ Even without explicit retry options, scheduled and queued callbacks are retried with sensible defaults:
228
+
229
+ | Setting | Default |
230
+ | ------------- | ------- |
231
+ | `maxAttempts` | 3 |
232
+ | `baseDelayMs` | 100 |
233
+ | `maxDelayMs` | 3000 |
234
+
235
+ These defaults apply to `this.retry()`, `queue()`, `schedule()`, and `scheduleEvery()`. Per-call-site options override them.
236
+
237
+ ### Class-Level Defaults
238
+
239
+ Override the defaults for your entire agent via `static options`:
240
+
241
+ ```typescript
242
+ class MyAgent extends Agent {
243
+ static options = {
244
+ retry: { maxAttempts: 5, baseDelayMs: 200, maxDelayMs: 5000 }
245
+ };
246
+ }
247
+ ```
248
+
249
+ You only need to specify the fields you want to change — unset fields fall back to the built-in defaults:
250
+
251
+ ```typescript
252
+ class MyAgent extends Agent {
253
+ // Only override maxAttempts; baseDelayMs (100) and maxDelayMs (3000) stay default
254
+ static options = {
255
+ retry: { maxAttempts: 10 }
256
+ };
257
+ }
258
+ ```
259
+
260
+ Class-level defaults are used as fallbacks when a call site does not specify retry options. Per-call-site options always take priority:
261
+
262
+ ```typescript
263
+ // Uses class-level defaults (10 attempts)
264
+ await this.retry(() => fetch(url));
265
+
266
+ // Overrides to 2 attempts for this specific call
267
+ await this.retry(() => fetch(url), { maxAttempts: 2 });
268
+ ```
269
+
270
+ To disable retries for a specific task, set `maxAttempts: 1`:
271
+
272
+ ```typescript
273
+ await this.schedule(
274
+ 60,
275
+ "oneShot",
276
+ {},
277
+ {
278
+ retry: { maxAttempts: 1 }
279
+ }
280
+ );
281
+ ```
282
+
283
+ ## RetryOptions
284
+
285
+ ```typescript
286
+ interface RetryOptions {
287
+ /** Maximum number of attempts (including the first). Must be an integer >= 1. Default: 3 */
288
+ maxAttempts?: number;
289
+ /** Base delay in milliseconds for exponential backoff. Must be > 0 and <= maxDelayMs. Default: 100 */
290
+ baseDelayMs?: number;
291
+ /** Maximum delay cap in milliseconds. Must be > 0. Default: 3000 */
292
+ maxDelayMs?: number;
293
+ }
294
+ ```
295
+
296
+ The delay between retries uses **full jitter exponential backoff**:
297
+
298
+ ```
299
+ delay = random(0, min(2^attempt * baseDelayMs, maxDelayMs))
300
+ ```
301
+
302
+ This means early retries are fast (often under 200ms), and later retries back off to avoid overwhelming a failing service. The randomization (jitter) prevents multiple agents from retrying at the exact same moment.
303
+
304
+ ## How It Works
305
+
306
+ ### Backoff Strategy
307
+
308
+ The retry system uses the "Full Jitter" strategy from the [AWS Architecture Blog](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/). Given 3 attempts with default settings:
309
+
310
+ | Attempt | Upper Bound | Actual Delay |
311
+ | ------- | ----------------------------- | ---------------- |
312
+ | 1 | min(2^1 \* 100, 3000) = 200ms | random(0, 200ms) |
313
+ | 2 | min(2^2 \* 100, 3000) = 400ms | random(0, 400ms) |
314
+ | 3 | (no retry — final attempt) | — |
315
+
316
+ With `maxAttempts: 5` and `baseDelayMs: 500`:
317
+
318
+ | Attempt | Upper Bound | Actual Delay |
319
+ | ------- | ----------------------------- | ----------------- |
320
+ | 1 | min(2 \* 500, 3000) = 1000ms | random(0, 1000ms) |
321
+ | 2 | min(4 \* 500, 3000) = 2000ms | random(0, 2000ms) |
322
+ | 3 | min(8 \* 500, 3000) = 3000ms | random(0, 3000ms) |
323
+ | 4 | min(16 \* 500, 3000) = 3000ms | random(0, 3000ms) |
324
+ | 5 | (no retry — final attempt) | — |
325
+
326
+ ### MCP Server Retries
327
+
328
+ When adding an MCP server, you can configure retry options for connection and reconnection attempts:
329
+
330
+ ```typescript
331
+ await this.addMcpServer("github", "https://mcp.github.com", {
332
+ retry: { maxAttempts: 5, baseDelayMs: 1000, maxDelayMs: 10000 }
333
+ });
334
+ ```
335
+
336
+ These options are persisted and used when:
337
+
338
+ - Restoring server connections after hibernation
339
+ - Establishing connections after OAuth completion
340
+
341
+ Default: 3 attempts, 500ms base delay, 5s max delay.
342
+
343
+ ### Internal Retries
344
+
345
+ The SDK also uses retries internally for platform operations:
346
+
347
+ - **Workflow operations** (`terminateWorkflow`, `pauseWorkflow`, `resumeWorkflow`, `restartWorkflow`, `sendEventToWorkflow`) — retried with Durable Object-aware error detection. Transient DO errors are retried; overloaded errors are not.
348
+
349
+ These internal retries use hardcoded defaults and are not configurable.
350
+
351
+ ## Patterns
352
+
353
+ ### Retry with Logging
354
+
355
+ ```typescript
356
+ class MyAgent extends Agent {
357
+ async resilientTask(payload: { url: string }) {
358
+ try {
359
+ const result = await this.retry(
360
+ async (attempt) => {
361
+ if (attempt > 1) {
362
+ console.log(`Retrying ${payload.url} (attempt ${attempt})...`);
363
+ }
364
+ const res = await fetch(payload.url);
365
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
366
+ return res.json();
367
+ },
368
+ { maxAttempts: 5 }
369
+ );
370
+ console.log("Success:", result);
371
+ } catch (e) {
372
+ console.error("All retries failed:", e);
373
+ }
374
+ }
375
+ }
376
+ ```
377
+
378
+ ### Retry with Fallback
379
+
380
+ ```typescript
381
+ class MyAgent extends Agent {
382
+ async fetchData() {
383
+ try {
384
+ return await this.retry(
385
+ () => fetch("https://primary-api.example.com/data"),
386
+ { maxAttempts: 3, baseDelayMs: 200 }
387
+ );
388
+ } catch {
389
+ // Primary failed, try fallback
390
+ return await this.retry(
391
+ () => fetch("https://fallback-api.example.com/data"),
392
+ { maxAttempts: 2 }
393
+ );
394
+ }
395
+ }
396
+ }
397
+ ```
398
+
399
+ ### Combining Retries with Scheduling
400
+
401
+ For operations that might take a long time to recover (minutes or hours), combine `this.retry()` for immediate retries with `this.schedule()` for delayed retries:
402
+
403
+ ```typescript
404
+ class MyAgent extends Agent {
405
+ async syncData(payload: { source: string; attempt?: number }) {
406
+ const attempt = payload.attempt ?? 1;
407
+
408
+ try {
409
+ // Immediate retries for transient failures (seconds)
410
+ await this.retry(() => this.fetchAndProcess(payload.source), {
411
+ maxAttempts: 3,
412
+ baseDelayMs: 1000
413
+ });
414
+ } catch (e) {
415
+ if (attempt >= 5) {
416
+ console.error("Giving up after 5 scheduled attempts");
417
+ return;
418
+ }
419
+
420
+ // Schedule a retry in 5 minutes for longer outages
421
+ const delaySeconds = 300 * attempt;
422
+ await this.schedule(delaySeconds, "syncData", {
423
+ source: payload.source,
424
+ attempt: attempt + 1
425
+ });
426
+ console.log(`Scheduled retry ${attempt + 1} in ${delaySeconds}s`);
427
+ }
428
+ }
429
+ }
430
+ ```
431
+
432
+ ## Limitations
433
+
434
+ - **No dead-letter queue.** If a queued or scheduled task fails all retry attempts, it is removed. Implement your own persistence if you need to track failed tasks.
435
+ - **Retry delays block the agent.** During the backoff delay, the Durable Object is awake but idle. For short delays (under 3 seconds) this is fine. For longer recovery times, use `this.schedule()` instead.
436
+ - **Queue retries are head-of-line blocking.** Queue items are processed sequentially. If one item is being retried with long delays, it blocks all subsequent items. If you need independent retry behavior, use `this.retry()` inside the callback rather than per-task retry options on `queue()`.
437
+ - **No circuit breaker.** The retry system does not track failure rates across calls. If a service is persistently down, each task will exhaust its retry budget independently.
438
+ - **`shouldRetry` is only available on `this.retry()`.** The `shouldRetry` predicate cannot be used with `schedule()` or `queue()` because functions cannot be serialized to the database. For scheduled/queued tasks, handle non-retryable errors inside the callback itself.
439
+
440
+ ## Related
441
+
442
+ - [Scheduling](./scheduling.md) — schedule tasks for future execution
443
+ - [Queue](./queue.md) — background task queue
444
+ - [Workflows](./workflows.md) — durable multi-step processing with automatic retries