@trigger.dev/sdk 4.6.4 → 4.7.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 (119) hide show
  1. package/dist/commonjs/v3/ai.d.ts +5 -2
  2. package/dist/commonjs/v3/ai.js +189 -266
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-client.js +7 -0
  5. package/dist/commonjs/v3/chat-client.js.map +1 -1
  6. package/dist/commonjs/v3/chat-server.d.ts +1 -0
  7. package/dist/commonjs/v3/chat-server.js +8 -0
  8. package/dist/commonjs/v3/chat-server.js.map +1 -1
  9. package/dist/commonjs/v3/chat.d.ts +28 -4
  10. package/dist/commonjs/v3/chat.js +41 -9
  11. package/dist/commonjs/v3/chat.js.map +1 -1
  12. package/dist/commonjs/v3/chatRouteWait.d.ts +21 -0
  13. package/dist/commonjs/v3/chatRouteWait.js +43 -0
  14. package/dist/commonjs/v3/chatRouteWait.js.map +1 -0
  15. package/dist/commonjs/v3/compactionResponse.js +5 -0
  16. package/dist/commonjs/v3/compactionResponse.js.map +1 -1
  17. package/dist/commonjs/v3/concurrency-shared.d.ts +13 -0
  18. package/dist/commonjs/v3/concurrency-shared.js +35 -0
  19. package/dist/commonjs/v3/concurrency-shared.js.map +1 -0
  20. package/dist/commonjs/v3/concurrencyLimits.d.ts +73 -0
  21. package/dist/commonjs/v3/concurrencyLimits.js +166 -0
  22. package/dist/commonjs/v3/concurrencyLimits.js.map +1 -0
  23. package/dist/commonjs/v3/index.d.ts +2 -1
  24. package/dist/commonjs/v3/index.js +3 -1
  25. package/dist/commonjs/v3/index.js.map +1 -1
  26. package/dist/commonjs/v3/managedChatResponse.d.ts +44 -0
  27. package/dist/commonjs/v3/managedChatResponse.js +233 -0
  28. package/dist/commonjs/v3/managedChatResponse.js.map +1 -0
  29. package/dist/commonjs/v3/queues.d.ts +31 -0
  30. package/dist/commonjs/v3/queues.js +31 -0
  31. package/dist/commonjs/v3/queues.js.map +1 -1
  32. package/dist/commonjs/v3/shared.d.ts +18 -1
  33. package/dist/commonjs/v3/shared.js +137 -47
  34. package/dist/commonjs/v3/shared.js.map +1 -1
  35. package/dist/commonjs/v3/steeringContext.d.ts +41 -0
  36. package/dist/commonjs/v3/steeringContext.js +118 -0
  37. package/dist/commonjs/v3/steeringContext.js.map +1 -0
  38. package/dist/commonjs/v3/transcriptStorage.d.ts +4 -1
  39. package/dist/commonjs/v3/transcriptStorage.js +51 -4
  40. package/dist/commonjs/v3/transcriptStorage.js.map +1 -1
  41. package/dist/commonjs/version.js +1 -1
  42. package/dist/esm/v3/ai.d.ts +5 -2
  43. package/dist/esm/v3/ai.js +189 -266
  44. package/dist/esm/v3/ai.js.map +1 -1
  45. package/dist/esm/v3/chat-client.js +7 -0
  46. package/dist/esm/v3/chat-client.js.map +1 -1
  47. package/dist/esm/v3/chat-server.d.ts +1 -0
  48. package/dist/esm/v3/chat-server.js +8 -0
  49. package/dist/esm/v3/chat-server.js.map +1 -1
  50. package/dist/esm/v3/chat.d.ts +28 -4
  51. package/dist/esm/v3/chat.js +41 -9
  52. package/dist/esm/v3/chat.js.map +1 -1
  53. package/dist/esm/v3/chatRouteWait.d.ts +21 -0
  54. package/dist/esm/v3/chatRouteWait.js +40 -0
  55. package/dist/esm/v3/chatRouteWait.js.map +1 -0
  56. package/dist/esm/v3/compactionResponse.js +5 -0
  57. package/dist/esm/v3/compactionResponse.js.map +1 -1
  58. package/dist/esm/v3/concurrency-shared.d.ts +13 -0
  59. package/dist/esm/v3/concurrency-shared.js +31 -0
  60. package/dist/esm/v3/concurrency-shared.js.map +1 -0
  61. package/dist/esm/v3/concurrencyLimits.d.ts +73 -0
  62. package/dist/esm/v3/concurrencyLimits.js +158 -0
  63. package/dist/esm/v3/concurrencyLimits.js.map +1 -0
  64. package/dist/esm/v3/index.d.ts +2 -1
  65. package/dist/esm/v3/index.js +2 -1
  66. package/dist/esm/v3/index.js.map +1 -1
  67. package/dist/esm/v3/managedChatResponse.d.ts +44 -0
  68. package/dist/esm/v3/managedChatResponse.js +228 -0
  69. package/dist/esm/v3/managedChatResponse.js.map +1 -0
  70. package/dist/esm/v3/queues.d.ts +31 -0
  71. package/dist/esm/v3/queues.js +31 -0
  72. package/dist/esm/v3/queues.js.map +1 -1
  73. package/dist/esm/v3/shared.d.ts +18 -1
  74. package/dist/esm/v3/shared.js +136 -47
  75. package/dist/esm/v3/shared.js.map +1 -1
  76. package/dist/esm/v3/steeringContext.d.ts +41 -0
  77. package/dist/esm/v3/steeringContext.js +113 -0
  78. package/dist/esm/v3/steeringContext.js.map +1 -0
  79. package/dist/esm/v3/transcriptStorage.d.ts +4 -1
  80. package/dist/esm/v3/transcriptStorage.js +51 -4
  81. package/dist/esm/v3/transcriptStorage.js.map +1 -1
  82. package/dist/esm/version.js +1 -1
  83. package/docs/ai-chat/client-protocol.mdx +3 -1
  84. package/docs/ai-chat/error-handling.mdx +44 -76
  85. package/docs/ai-chat/fast-starts.mdx +1 -1
  86. package/docs/ai-chat/frontend.mdx +27 -21
  87. package/docs/ai-chat/patterns/branching-conversations.mdx +95 -230
  88. package/docs/ai-chat/patterns/human-in-the-loop.mdx +166 -164
  89. package/docs/ai-chat/patterns/tool-result-auditing.mdx +28 -27
  90. package/docs/ai-chat/patterns/version-upgrades.mdx +4 -4
  91. package/docs/ai-chat/pending-messages.mdx +19 -5
  92. package/docs/ai-chat/quick-start.mdx +26 -20
  93. package/docs/ai-chat/reference.mdx +21 -3
  94. package/docs/ai-chat/sessions.mdx +1 -1
  95. package/docs/ai-chat/testing.mdx +16 -4
  96. package/docs/concurrency.mdx +384 -0
  97. package/docs/database-connections.mdx +3 -3
  98. package/docs/deploy-environment-variables.mdx +6 -0
  99. package/docs/deployment/atomic-deployment.mdx +416 -132
  100. package/docs/deployment/overview.mdx +2 -2
  101. package/docs/github-actions.mdx +2 -2
  102. package/docs/github-integration.mdx +2 -2
  103. package/docs/idempotency.mdx +43 -5
  104. package/docs/introduction.mdx +1 -1
  105. package/docs/limits.mdx +16 -6
  106. package/docs/observability/query.mdx +25 -0
  107. package/docs/queues.mdx +271 -0
  108. package/docs/reports.mdx +1 -1
  109. package/docs/runs/priority.mdx +2 -25
  110. package/docs/self-hosting/env/webapp.mdx +7 -0
  111. package/docs/tasks/overview.mdx +3 -5
  112. package/docs/troubleshooting-alerts.mdx +124 -1
  113. package/docs/troubleshooting.mdx +12 -0
  114. package/docs/vercel-integration.mdx +6 -7
  115. package/docs/versioning.mdx +1 -1
  116. package/docs/writing-tasks-introduction.mdx +2 -1
  117. package/package.json +2 -2
  118. package/docs/deployment/version-skew-protection.mdx +0 -492
  119. package/docs/queue-concurrency.mdx +0 -358
@@ -0,0 +1,384 @@
1
+ ---
2
+ title: "Concurrency"
3
+ description: "Limit how many runs execute at once: per task, per tenant, or shared across tasks."
4
+ ---
5
+
6
+ When you trigger a task, the [run](/runs) waits in a [queue](/queues) and runs start in trigger order as capacity allows. Concurrency is what decides how much capacity there is.
7
+
8
+ By default, concurrency is only limited by your environment concurrency limit. If you need more control (for example, to limit concurrency or share limits across multiple tasks), you can set a concurrency limit on the task, or declare a named limit and share it, as described below.
9
+
10
+ Controlling concurrency is useful when you have a task that can't be run concurrently, or when you want to limit the number of runs to avoid overloading a resource.
11
+
12
+ It's important to note that only actively executing runs count towards concurrency limits. Runs that are delayed or waiting in a queue do not consume concurrency slots until they begin execution.
13
+
14
+ ## Use cases
15
+
16
+ - **Limit how many runs of a task execute at once**: [Setting task concurrency](#setting-task-concurrency)
17
+ - **Share one limit across several tasks**: [Sharing a limit between tasks](#sharing-a-limit-between-tasks)
18
+ - **Give each tenant its own separate concurrency**: [Concurrency keys and per-tenant limits](#concurrency-keys-and-per-tenant-limits)
19
+ - **Per-tenant limits with a ceiling on the total**: [Per-key and total limits together](#per-key-and-total-limits-together)
20
+ - **Cap a tenant across every task they run**: [A per-tenant cap across multiple tasks](#a-per-tenant-cap-across-multiple-tasks)
21
+ - **Cap a shared resource, like an external API, across tasks and tenants**: [A global cap for a shared resource](#a-global-cap-for-a-shared-resource)
22
+ - **Switch a run's limits when you trigger it**: [Setting limits when you trigger a run](#setting-limits-when-you-trigger-a-run)
23
+
24
+ ## Default concurrency
25
+
26
+ By default, all tasks have an unbounded concurrency limit, limited only by the overall concurrency limits of your environment.
27
+
28
+ <Note>
29
+ Your environment has a base concurrency limit and a burstable limit (default burst factor of 2.0x
30
+ the base limit). Individual tasks and limits are capped by the base concurrency limit, not the
31
+ burstable limit. For example, if your base limit is 10, your environment can burst up to 20
32
+ concurrent runs, but any single limit can allow at most 10 concurrent runs. If you're a paying
33
+ customer you can request higher burst limits by [contacting us](https://www.trigger.dev/contact).
34
+ </Note>
35
+
36
+ ## Setting task concurrency
37
+
38
+ Set the `concurrency` option on a task to limit how many of its runs execute at once. `{ total: n }` caps the task outright:
39
+
40
+ ```ts /trigger/one-at-a-time.ts
41
+ import { task } from "@trigger.dev/sdk";
42
+
43
+ // This task will only run one at a time
44
+ export const oneAtATime = task({
45
+ id: "one-at-a-time",
46
+ concurrency: { total: 1 },
47
+ run: async (payload) => {
48
+ //...
49
+ },
50
+ });
51
+ ```
52
+
53
+ This is useful if you need to control access to a shared resource, like a database or an API that has rate limits.
54
+
55
+ There are two ways to bound a task, and you can combine them:
56
+
57
+ - `total` caps every run of the task together, whether or not runs use a `concurrencyKey`.
58
+ - `perKey` caps each `concurrencyKey` pool separately; runs triggered without a key share one pool.
59
+
60
+ ```ts /trigger/per-user.ts
61
+ import { task } from "@trigger.dev/sdk";
62
+
63
+ export const generateReport = task({
64
+ id: "generate-report",
65
+ // each user runs at most 1 at a time, and at most 10 run in total
66
+ concurrency: { perKey: 1, total: 10 },
67
+ run: async (payload) => {
68
+ //...
69
+ },
70
+ });
71
+ ```
72
+
73
+ <Note>
74
+ The `queue: {"{ concurrencyLimit: n }"}` option keeps working but is deprecated in favor of
75
+ `concurrency`. Its single number means "per key when runs pass a `concurrencyKey`, whole queue
76
+ when they don't" — the `concurrency` shape says which you mean explicitly.
77
+ </Note>
78
+
79
+ ## Sharing a limit between tasks
80
+
81
+ Declare a named limit with `concurrencyLimit()` and put it in each task's `concurrency`. Every task holding the limit draws from the same pools:
82
+
83
+ ```ts /trigger/limits.ts
84
+ import { concurrencyLimit, task } from "@trigger.dev/sdk";
85
+
86
+ // at most 25 concurrent runs across every task that holds this limit
87
+ export const openaiLimit = concurrencyLimit({ name: "openai", total: 25 });
88
+
89
+ export const generateSummary = task({
90
+ id: "generate-summary",
91
+ concurrency: openaiLimit,
92
+ run: async (payload) => {
93
+ // ...
94
+ },
95
+ });
96
+
97
+ export const generateTitle = task({
98
+ id: "generate-title",
99
+ concurrency: openaiLimit,
100
+ run: async (payload) => {
101
+ // ...
102
+ },
103
+ });
104
+ ```
105
+
106
+ A task's `concurrency` takes a single item or an array: at most one inline shape (which caps that task alone) plus up to two named limits. A run starts only when every limit it holds has capacity, and it occupies a slot in each while it executes:
107
+
108
+ ```ts /trigger/summarize.ts
109
+ import { concurrencyLimit, task } from "@trigger.dev/sdk";
110
+
111
+ export const openaiLimit = concurrencyLimit({ name: "openai", total: 25 });
112
+
113
+ export const summarizeThread = task({
114
+ id: "summarize-thread",
115
+ // this task runs at most 5 at once, and also counts towards the shared openai limit
116
+ concurrency: [{ total: 5 }, openaiLimit],
117
+ run: async (payload) => {
118
+ // ...
119
+ },
120
+ });
121
+ ```
122
+
123
+ <Note>
124
+ Names you declare with `concurrencyLimit()` are 1-122 characters using only letters, numbers,
125
+ underscores and hyphens.
126
+ </Note>
127
+
128
+ ## Setting limits when you trigger a run
129
+
130
+ The trigger-time `concurrency` option takes limit names and replaces the task's declared **named** limits for that run. The task's inline limit always applies:
131
+
132
+ ```ts app/api/report/route.ts
133
+ import { generateReport } from "~/trigger/reports";
134
+
135
+ // this run counts towards "priority" instead of the task's declared named limits
136
+ await generateReport.trigger(data, { concurrency: ["priority"] });
137
+ ```
138
+
139
+ Pass an empty array to run with only the task's inline limit.
140
+
141
+ ## Concurrency keys and per-tenant limits
142
+
143
+ If you're building an application where you want to run tasks for your users, you might want a separate limit for each of your users (or orgs, projects, etc.).
144
+
145
+ You can do this by passing a `concurrencyKey` when you trigger. Each unique key value gets its own pool under every `perKey` bound the run holds:
146
+
147
+ ```ts app/api/report/route.ts
148
+ import { generateReport } from "~/trigger/per-user";
149
+
150
+ export async function POST(request: Request) {
151
+ const data = await request.json();
152
+
153
+ const handle = await generateReport.trigger(data, {
154
+ // every user gets their own concurrency pool
155
+ concurrencyKey: data.userId,
156
+ });
157
+
158
+ return Response.json(handle);
159
+ }
160
+ ```
161
+
162
+ ## Per-key and total limits together
163
+
164
+ `perKey` on its own lets total concurrency grow with the number of active keys: ten active users under `perKey: 5` can run 50 at once. Add `total` to bound everything as a group. Each key still gets at most `perKey`, and all runs together — keyed or not — never exceed `total`:
165
+
166
+ ```ts /trigger/per-user-capped.ts
167
+ import { task } from "@trigger.dev/sdk";
168
+
169
+ export const processUpload = task({
170
+ id: "process-upload",
171
+ // each user runs at most 1 at a time, and at most 10 run in total
172
+ concurrency: { perKey: 1, total: 10 },
173
+ run: async (payload) => {
174
+ //...
175
+ },
176
+ });
177
+ ```
178
+
179
+ ## A per-tenant cap across multiple tasks
180
+
181
+ A named limit's `perKey` bound follows each run's own `concurrencyKey`, so one declaration caps each tenant across every task holding the limit:
182
+
183
+ ```ts /trigger/webhooks.ts
184
+ import { concurrencyLimit, task } from "@trigger.dev/sdk";
185
+
186
+ // each tenant runs at most 10 at once across every task that holds this limit
187
+ export const tenantLimit = concurrencyLimit({ name: "tenant", perKey: 10 });
188
+
189
+ export const processWebhook = task({
190
+ id: "process-webhook",
191
+ // webhooks themselves are capped at 2 per tenant, within the tenant's overall 10
192
+ concurrency: [{ perKey: 2 }, tenantLimit],
193
+ run: async (payload) => {
194
+ //...
195
+ },
196
+ });
197
+ ```
198
+
199
+ ```ts app/api/webhook/route.ts
200
+ // the run counts towards this tenant's pool in both limits
201
+ await processWebhook.trigger(payload, { concurrencyKey: tenantId });
202
+ ```
203
+
204
+ ## A global cap for a shared resource
205
+
206
+ To cap something global, like total traffic to an external API, across many tasks and all tenants: declare a limit with only a `total` and share it. It counts every run holding it, whether or not the run has a `concurrencyKey`:
207
+
208
+ ```ts /trigger/sync.ts
209
+ import { concurrencyLimit, task } from "@trigger.dev/sdk";
210
+
211
+ // at most 10 concurrent provider calls across every task and every tenant
212
+ export const providerApiLimit = concurrencyLimit({ name: "provider-api", total: 10 });
213
+
214
+ export const syncToProvider = task({
215
+ id: "sync-to-provider",
216
+ concurrency: [{ total: 20 }, providerApiLimit],
217
+ run: async (payload) => {
218
+ //...
219
+ },
220
+ });
221
+ ```
222
+
223
+ Runs with and without a `concurrencyKey` share the same `total`, so this works even when only some of your triggers have a natural key.
224
+
225
+ ## Concurrency and subtasks
226
+
227
+ When you trigger a task that has subtasks, the subtasks will not inherit the parent's limits. Unless otherwise specified, subtasks run under their own task's configuration:
228
+
229
+ ```ts /trigger/subtasks.ts
230
+ export const parentTask = task({
231
+ id: "parent-task",
232
+ run: async (payload) => {
233
+ //trigger a subtask
234
+ await subtask.triggerAndWait(payload);
235
+ },
236
+ });
237
+
238
+ // This subtask runs under its own limits
239
+ export const subtask = task({
240
+ id: "subtask",
241
+ run: async (payload) => {
242
+ //...
243
+ },
244
+ });
245
+ ```
246
+
247
+ ## Waits and concurrency
248
+
249
+ With our [task checkpoint system](/how-it-works#the-checkpoint-resume-system), tasks can wait at various waitpoints (like waiting for subtasks to complete, delays, or external events). The way this system interacts with the concurrency system is important to understand.
250
+
251
+ Concurrency is only released when a run reaches a waitpoint and is checkpointed. When a run is checkpointed, it transitions to the `WAITING` state and releases its concurrency slots back to every limit it holds and the environment, allowing other runs to execute or resume.
252
+
253
+ This means that:
254
+
255
+ - Only actively executing runs count towards concurrency limits
256
+ - Runs in the `WAITING` state (checkpointed at waitpoints) do not consume concurrency slots
257
+ - You can have more runs in the `WAITING` state than a limit allows to execute
258
+ - When a waiting run resumes (e.g., when a subtask completes), it must re-acquire its slots
259
+
260
+ For example, if a task has `concurrency: { total: 1 }`:
261
+
262
+ - You can only have exactly 1 run executing at a time
263
+ - You may have multiple runs in the `WAITING` state for that task
264
+ - When the executing run reaches a waitpoint and checkpoints, it releases its slot
265
+ - The next queued run can then begin execution
266
+
267
+ ### Short time-based waits keep their slot
268
+
269
+ Checkpointing takes time, so a run doesn't checkpoint the moment it reaches a waitpoint. For [`wait.for()`](/wait-for) and [`wait.until()`](/wait-until) it happens 60 seconds into the wait, so anything shorter stays `EXECUTING` and holds its slots for the whole wait. If you're polling in a loop, use an interval comfortably above 60 seconds so the slots are actually released between polls.
270
+
271
+ ### Waiting for a subtask
272
+
273
+ When a parent task triggers and waits for a subtask, the parent task will checkpoint and release its concurrency slots once it reaches the wait point. This prevents environment deadlocks where all concurrency slots would be occupied by waiting tasks.
274
+
275
+ ```ts /trigger/waiting.ts
276
+ export const parentTask = task({
277
+ id: "parent-task",
278
+ concurrency: { total: 1 },
279
+ run: async (payload) => {
280
+ //trigger a subtask and wait for it to complete
281
+ await subtask.triggerAndWait(payload);
282
+ // The parent task checkpoints here and releases its concurrency slots
283
+ // allowing other tasks to execute while waiting
284
+ },
285
+ });
286
+
287
+ export const subtask = task({
288
+ id: "subtask",
289
+ run: async (payload) => {
290
+ //...
291
+ },
292
+ });
293
+ ```
294
+
295
+ When the parent task reaches the `triggerAndWait` call, it checkpoints and transitions to the `WAITING` state, releasing its slots. Once the subtask completes, the parent task will resume and re-acquire them.
296
+
297
+ ## Managing concurrency limits with the SDK
298
+
299
+ The `concurrencyLimits` namespace manages your named limits at runtime (anonymous inline limits appear under derived `task/<task-id>` names):
300
+
301
+ ```ts
302
+ import { concurrencyLimits } from "@trigger.dev/sdk";
303
+ ```
304
+
305
+ ### Listing limits
306
+
307
+ ```ts
308
+ import { concurrencyLimits } from "@trigger.dev/sdk";
309
+
310
+ // List all limits (returns paginated results)
311
+ const allLimits = await concurrencyLimits.list();
312
+
313
+ // With pagination options
314
+ const pagedLimits = await concurrencyLimits.list({
315
+ page: 1,
316
+ perPage: 20,
317
+ });
318
+ ```
319
+
320
+ ### Retrieving a limit
321
+
322
+ Retrieve a limit by its name to see its bounds and live counts:
323
+
324
+ ```ts
325
+ import { concurrencyLimits } from "@trigger.dev/sdk";
326
+
327
+ const limit = await concurrencyLimits.retrieve("openai");
328
+ ```
329
+
330
+ The limit object contains each bound plus live counts:
331
+
332
+ ```ts
333
+ {
334
+ id: "climit_1234",
335
+ name: "openai",
336
+ perKey: {
337
+ current: null, // Enforced right now (null = no per-key bound)
338
+ base: null, // Declared in your code
339
+ override: null, // Override value (if set)
340
+ overriddenAt: null, // When the override was applied
341
+ },
342
+ total: {
343
+ current: 25,
344
+ base: 25,
345
+ override: null,
346
+ overriddenAt: null,
347
+ },
348
+ running: 14, // Runs executing that hold this limit
349
+ queued: 100, // Runs queued that must clear this limit to execute
350
+ paused: false, // Whether the limit is paused
351
+ }
352
+ ```
353
+
354
+ ### Overriding a limit
355
+
356
+ Overrides change only the fields you pass; the declared values are kept and restored by `reset`:
357
+
358
+ ```ts
359
+ import { concurrencyLimits } from "@trigger.dev/sdk";
360
+
361
+ // Raise the shared cap
362
+ await concurrencyLimits.override("openai", { total: 50 });
363
+
364
+ // Back to the values declared in your code
365
+ await concurrencyLimits.reset("openai");
366
+ ```
367
+
368
+ Overrides survive deploys: redeploying your code keeps an active override until you reset it.
369
+
370
+ ### Pausing a limit
371
+
372
+ Pause a limit to stop every run holding it from being dequeued; runs that are already executing continue to completion. The configured bounds are kept, and resuming restores them:
373
+
374
+ ```ts
375
+ import { concurrencyLimits } from "@trigger.dev/sdk";
376
+
377
+ // Pause the limit: nothing holding it can start
378
+ await concurrencyLimits.pause("openai");
379
+
380
+ // Resume the limit under its configured bounds
381
+ await concurrencyLimits.resume("openai");
382
+ ```
383
+
384
+ You can also pause and resume a limit from the Concurrency page in the dashboard, exactly like a queue. Overriding `total` to `0` also blocks every run holding the limit, but pause is the first-class way to do this and leaves your configured bounds untouched.
@@ -68,7 +68,7 @@ Set the pool small. A task usually runs its queries in sequence, so one connecti
68
68
  | Drizzle (node-postgres) | 10 (the underlying `pg` pool) |
69
69
  | [MongoDB driver](https://www.mongodb.com/docs/drivers/node/current/connect/connection-options/connection-pools/) | 100 (`maxPoolSize`) |
70
70
 
71
- Keep `concurrent runs × pool size` under your provider's connection limit, and cap how many runs execute at once with [concurrency limits](/queue-concurrency) so runs queue instead of overrunning the database. Direct connection limits for common Postgres providers:
71
+ Keep `concurrent runs × pool size` under your provider's connection limit, and cap how many runs execute at once with [concurrency limits](/concurrency) so runs queue instead of overrunning the database. Direct connection limits for common Postgres providers:
72
72
 
73
73
  | Provider | Direct connection limit |
74
74
  | --- | --- |
@@ -197,7 +197,7 @@ export const myChat = chat.agent({
197
197
 
198
198
  ## Troubleshooting
199
199
 
200
- `too many connections` or connection refused: `concurrent runs × pool size` is over your provider's limit. Lower the pool size, cap [concurrency](/queue-concurrency), or connect through a pooler.
200
+ `too many connections` or connection refused: `concurrent runs × pool size` is over your provider's limit. Lower the pool size, cap [concurrency](/concurrency), or connect through a pooler.
201
201
 
202
202
  The worker crashes right after resuming from a wait: an idle connection that closed during the suspend emitted an unhandled `error` event. Attach `pool.on("error", ...)` on a `pg` pool (node-postgres or Drizzle); Prisma and the MongoDB driver handle this internally.
203
203
 
@@ -208,6 +208,6 @@ When a task waits, the runtime can [checkpoint](/how-it-works#the-checkpoint-res
208
208
  ## See also
209
209
 
210
210
  - [Wait](/wait) for the primitives that trigger a checkpoint.
211
- - [Concurrency and queues](/queue-concurrency) to cap how many runs execute at once.
211
+ - [Concurrency](/concurrency) to cap how many runs execute at once.
212
212
  - [Lifecycle functions](/tasks/overview#onwait-and-onresume-functions) for global `tasks.onWait` and `tasks.onResume`.
213
213
  - [Chat agent lifecycle hooks](/ai-chat/lifecycle-hooks) for `onChatSuspend` and `onChatResume`.
@@ -43,6 +43,12 @@ When creating an environment variable, you can mark it as a **Secret**. Secret v
43
43
 
44
44
  You can edit an environment variable's values. You cannot edit the key name, you must delete and create a new one.
45
45
 
46
+ <Note>
47
+ An empty string is a valid value. Saving a variable with an empty value stores it as an empty
48
+ string (your task sees the variable set to `""`), which is different from deleting the variable
49
+ (where your task does not see it at all).
50
+ </Note>
51
+
46
52
  <Steps>
47
53
 
48
54
  <Step title="Press the action button on a variable">