@trigger.dev/sdk 4.8.1 → 4.8.2

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 (89) hide show
  1. package/dist/commonjs/v3/ai-shared.d.ts +6 -0
  2. package/dist/commonjs/v3/ai-shared.js +7 -1
  3. package/dist/commonjs/v3/ai-shared.js.map +1 -1
  4. package/dist/commonjs/v3/ai.d.ts +45 -0
  5. package/dist/commonjs/v3/ai.js +110 -69
  6. package/dist/commonjs/v3/ai.js.map +1 -1
  7. package/dist/commonjs/v3/auth.d.ts +2 -1
  8. package/dist/commonjs/v3/auth.js +18 -12
  9. package/dist/commonjs/v3/auth.js.map +1 -1
  10. package/dist/commonjs/v3/chat-client.d.ts +11 -2
  11. package/dist/commonjs/v3/chat-client.js +28 -10
  12. package/dist/commonjs/v3/chat-client.js.map +1 -1
  13. package/dist/commonjs/v3/chat-react.d.ts +21 -0
  14. package/dist/commonjs/v3/chat-react.js +86 -30
  15. package/dist/commonjs/v3/chat-react.js.map +1 -1
  16. package/dist/commonjs/v3/chat.d.ts +1 -0
  17. package/dist/commonjs/v3/chat.js +4 -1
  18. package/dist/commonjs/v3/chat.js.map +1 -1
  19. package/dist/commonjs/v3/managedChatResponse.d.ts +13 -5
  20. package/dist/commonjs/v3/managedChatResponse.js +36 -18
  21. package/dist/commonjs/v3/managedChatResponse.js.map +1 -1
  22. package/dist/commonjs/v3/steeringContext.d.ts +11 -0
  23. package/dist/commonjs/v3/steeringContext.js +44 -3
  24. package/dist/commonjs/v3/steeringContext.js.map +1 -1
  25. package/dist/commonjs/v3/subAgentStream.d.ts +50 -0
  26. package/dist/commonjs/v3/subAgentStream.js +154 -0
  27. package/dist/commonjs/v3/subAgentStream.js.map +1 -0
  28. package/dist/commonjs/v3/transcriptStorage.js +3 -0
  29. package/dist/commonjs/v3/transcriptStorage.js.map +1 -1
  30. package/dist/commonjs/v3/uiMessageChunks.js +5 -1
  31. package/dist/commonjs/v3/uiMessageChunks.js.map +1 -1
  32. package/dist/commonjs/version.js +1 -1
  33. package/dist/esm/v3/ai-shared.d.ts +6 -0
  34. package/dist/esm/v3/ai-shared.js +6 -0
  35. package/dist/esm/v3/ai-shared.js.map +1 -1
  36. package/dist/esm/v3/ai.d.ts +45 -0
  37. package/dist/esm/v3/ai.js +113 -72
  38. package/dist/esm/v3/ai.js.map +1 -1
  39. package/dist/esm/v3/auth.d.ts +2 -1
  40. package/dist/esm/v3/auth.js +19 -13
  41. package/dist/esm/v3/auth.js.map +1 -1
  42. package/dist/esm/v3/chat-client.d.ts +11 -2
  43. package/dist/esm/v3/chat-client.js +29 -11
  44. package/dist/esm/v3/chat-client.js.map +1 -1
  45. package/dist/esm/v3/chat-react.d.ts +21 -0
  46. package/dist/esm/v3/chat-react.js +81 -30
  47. package/dist/esm/v3/chat-react.js.map +1 -1
  48. package/dist/esm/v3/chat.d.ts +1 -0
  49. package/dist/esm/v3/chat.js +1 -0
  50. package/dist/esm/v3/chat.js.map +1 -1
  51. package/dist/esm/v3/managedChatResponse.d.ts +13 -5
  52. package/dist/esm/v3/managedChatResponse.js +36 -18
  53. package/dist/esm/v3/managedChatResponse.js.map +1 -1
  54. package/dist/esm/v3/steeringContext.d.ts +11 -0
  55. package/dist/esm/v3/steeringContext.js +44 -4
  56. package/dist/esm/v3/steeringContext.js.map +1 -1
  57. package/dist/esm/v3/subAgentStream.d.ts +50 -0
  58. package/dist/esm/v3/subAgentStream.js +149 -0
  59. package/dist/esm/v3/subAgentStream.js.map +1 -0
  60. package/dist/esm/v3/transcriptStorage.js +3 -0
  61. package/dist/esm/v3/transcriptStorage.js.map +1 -1
  62. package/dist/esm/v3/uiMessageChunks.js +5 -1
  63. package/dist/esm/v3/uiMessageChunks.js.map +1 -1
  64. package/dist/esm/version.js +1 -1
  65. package/docs/ai-chat/background-injection.mdx +2 -0
  66. package/docs/ai-chat/client-protocol.mdx +1 -0
  67. package/docs/ai-chat/frontend.mdx +15 -0
  68. package/docs/ai-chat/overview.mdx +1 -1
  69. package/docs/ai-chat/patterns/sub-agents.mdx +81 -55
  70. package/docs/ai-chat/pending-messages.mdx +2 -0
  71. package/docs/ai-chat/quick-start.mdx +1 -1
  72. package/docs/ai-chat/reference.mdx +22 -0
  73. package/docs/ai-chat/server-chat.mdx +11 -7
  74. package/docs/config/config-file.mdx +2 -2
  75. package/docs/how-to-reduce-your-spend.mdx +4 -0
  76. package/docs/limits.mdx +41 -0
  77. package/docs/observability/query.mdx +29 -0
  78. package/docs/self-hosting/docker.mdx +62 -0
  79. package/docs/self-hosting/env/supervisor.mdx +3 -0
  80. package/docs/self-hosting/env/webapp.mdx +14 -0
  81. package/docs/self-hosting/kubernetes.mdx +2 -0
  82. package/docs/troubleshooting-alerts.mdx +39 -0
  83. package/docs/webhooks/deliveries.mdx +4 -0
  84. package/docs/webhooks/overview.mdx +8 -0
  85. package/docs/webhooks/sources.mdx +3 -1
  86. package/docs/webhooks/waiters.mdx +3 -3
  87. package/package.json +2 -2
  88. package/skills/trigger-chat-agent-advanced/SKILL.md +17 -7
  89. package/skills/trigger-cost-savings/SKILL.md +1 -0
@@ -565,6 +565,7 @@ All methods available on the `chat` object from `@trigger.dev/sdk/ai`.
565
565
  | `chat.isStopped()` | Check if the current turn was stopped by the user |
566
566
  | `chat.cleanupAbortedParts(message)` | Remove incomplete parts from a stopped response message |
567
567
  | `chat.response.write(chunk)` | Write a data part that streams to the frontend AND persists in `onTurnComplete`'s `responseMessage` |
568
+ | `chat.forwardSubAgent(stream, { toolCallId, flushIntervalMs? })` | Stream a sub-agent's reply through a parent tool call as transient `data-subagent` parts, and resolve with its final `UIMessage`. Call it inside a tool's `execute`. See [Sub-agents](/ai-chat/patterns/sub-agents) |
568
569
  | `chat.stream` | Raw chat output stream — use `.writer()`, `.pipe()`, `.append()`, `.read()`. Chunks are NOT accumulated into the response. |
569
570
  | `chat.history.all()` | Read the current accumulated UI messages (returns a copy). See [chat.history](/ai-chat/backend#chat-history) |
570
571
  | `chat.history.set(messages)` | Replace all accumulated messages (same as `chat.setMessages()`) |
@@ -837,6 +838,27 @@ const transport = useTriggerChatTransport({
837
838
 
838
839
  No-op when `BroadcastChannel` is unavailable (SSR, Node.js). See [Multi-tab coordination](/ai-chat/frontend#multi-tab-coordination).
839
840
 
841
+ ## useSubAgentMessages
842
+
843
+ React hook that rebuilds sub-agents streamed with [`chat.forwardSubAgent`](/ai-chat/patterns/sub-agents) while they run. Import from `@trigger.dev/sdk/chat/react`.
844
+
845
+ ```tsx
846
+ import { useSubAgentMessages } from "@trigger.dev/sdk/chat/react";
847
+
848
+ const subAgents = useSubAgentMessages();
849
+ const { messages } = useChat({ transport, onData: subAgents.onData });
850
+
851
+ const message = subAgents.get(part.toolCallId) ?? part.output;
852
+ ```
853
+
854
+ | Option | Type | Default | Description |
855
+ | ------------ | -------- | ------- | ------------------------------------------------------------------------ |
856
+ | `throttleMs` | `number` | `100` | Minimum time between rebuilds of a sub-agent's message while chunks stream in. |
857
+
858
+ **Returns:** `{ onData, get }`. Pass `onData` to `useChat`. `get(toolCallId)` returns the sub-agent's `UIMessage` so far, or `undefined` before its first chunk. Batches a resumed stream replays are skipped, so a reload mid-turn does not duplicate content.
859
+
860
+ `createSubAgentMessages({ throttleMs?, onChange? })` from `@trigger.dev/sdk/chat` is the same store without React: call its `onData` with every data part you receive, read with `get(toolCallId)`, and `dispose()` when you are done.
861
+
840
862
  ### Trigger configuration
841
863
 
842
864
  Trigger config (machine, queue, tags, maxAttempts, idleTimeoutInSeconds) lives server-side in `chat.createStartSessionAction(taskId, options?)`. The transport doesn't accept these options directly — pass them when wrapping the action:
@@ -161,21 +161,25 @@ export async function POST(req: Request) {
161
161
 
162
162
  ## Sub-agent tool pattern
163
163
 
164
- `AgentChat` can be used inside an AI SDK tool to delegate work to a durable sub-agent. The sub-agent's response streams as preliminary tool results:
164
+ `AgentChat` can be used inside an AI SDK tool to delegate work to a durable sub-agent. `chat.forwardSubAgent` streams the sub-agent's reply to the frontend inside the tool call and resolves with its final message:
165
165
 
166
166
  ```ts
167
167
  import { tool } from "ai";
168
+ import { chat } from "@trigger.dev/sdk/ai";
168
169
  import { AgentChat } from "@trigger.dev/sdk/chat";
169
170
  import { z } from "zod";
170
171
 
171
172
  const researchTool = tool({
172
173
  description: "Delegate research to a specialist agent.",
173
174
  inputSchema: z.object({ topic: z.string() }),
174
- execute: async function* ({ topic }, { abortSignal }) {
175
- const chat = new AgentChat({ agent: "research-agent" });
176
- const stream = await chat.sendMessage(topic, { abortSignal });
177
- yield* stream.messages();
178
- await chat.close();
175
+ execute: async ({ topic }, { toolCallId, abortSignal }) => {
176
+ const researcher = new AgentChat({ agent: "research-agent" });
177
+ try {
178
+ const stream = await researcher.sendMessage(topic, { abortSignal });
179
+ return await chat.forwardSubAgent(stream, { toolCallId });
180
+ } finally {
181
+ await researcher.close();
182
+ }
179
183
  },
180
184
  toModelOutput: ({ output: message }) => {
181
185
  const lastText = message?.parts?.findLast(
@@ -254,6 +258,6 @@ const stream = await chat.reconnect();
254
258
  |---|---|---|
255
259
  | `text()` | `Promise<string>` | Consume stream, return accumulated text |
256
260
  | `result()` | `Promise<ChatStreamResult>` | Consume stream, return `{ text, toolCalls, toolResults }` |
257
- | `messages()` | `AsyncGenerator<UIMessage>` | Yield accumulated UIMessage snapshots (sub-agent pattern) |
261
+ | `messages()` | `AsyncGenerator<UIMessage>` | Yield the accumulated UIMessage after each chunk. For sub-agents, pass the stream to `chat.forwardSubAgent` instead |
258
262
  | `[Symbol.asyncIterator]` | `UIMessageChunk` | Iterate over typed stream chunks |
259
263
  | `.stream` | `ReadableStream<UIMessageChunk>` | Raw stream for AI SDK utilities |
@@ -329,7 +329,7 @@ After each call we restore the few attributes we use to link a span to its run,
329
329
 
330
330
  If you don't set a `runtime`, your tasks run on the current Node.js LTS. Setting `runtime: "node"` does the same thing — see the Node.js versions section below to pin a specific version instead.
331
331
 
332
- We currently only officially support the `node` runtime, but you can try our experimental `bun` runtime by setting the `runtime` option in your config file:
332
+ We currently only officially support the `node` runtime, but you can try our experimental Bun runtimes by setting the `runtime` option in your config file:
333
333
 
334
334
  ```ts trigger.config.ts
335
335
  import { defineConfig } from "@trigger.dev/sdk";
@@ -341,7 +341,7 @@ export default defineConfig({
341
341
  });
342
342
  ```
343
343
 
344
- See our [Bun guide](/guides/frameworks/bun) for more information.
344
+ `bun` runs on Bun 1.3 with a Node.js binary alongside it. `bun-1.4` runs on a Bun-only image with no Node.js binary. See our [Bun guide](/guides/frameworks/bun) for the differences.
345
345
 
346
346
  ### Node.js versions
347
347
 
@@ -190,6 +190,10 @@ export const waitpointTask = task({
190
190
 
191
191
  Waiting saves you compute, but it doesn't always free up concurrency — see [Wait](/wait).
192
192
 
193
+ ## Filter webhooks before they start runs
194
+
195
+ Give each [webhook](/webhooks/overview) subscriber a [filter](/webhooks/filters) so it only runs for the events it handles. A delivery that no subscriber's filter matches starts no run for any subscriber, so you aren't charged for it. Filters only apply to subscribers: a run [waiting](/webhooks/waiters) for a matching webhook still resumes and uses compute. Receiving webhooks has no separate charge.
196
+
193
197
  ## Use debounce to consolidate multiple triggers
194
198
 
195
199
  When a task might be triggered multiple times in quick succession, use debounce to consolidate them into a single run. This is useful for document indexing, webhook aggregation, cache invalidation, and real-time sync scenarios.
package/docs/limits.mdx CHANGED
@@ -178,6 +178,47 @@ All of an environment's endpoints also share a per-environment limit, counted in
178
178
  | Pro | 3,000 every 10 sec |
179
179
  | Enterprise | Set per organization |
180
180
 
181
+ ## Webhook waiters
182
+
183
+ A [waiter](/webhooks/waiters) is a run paused until a matching webhook arrives. The per-endpoint limit is also the most waiting runs one delivery can resume.
184
+
185
+ | Pricing tier | Live waiters per environment | Live waiters per endpoint |
186
+ | :----------- | :--------------------------- | :------------------------ |
187
+ | Free | 500 | 100 |
188
+ | Hobby | 2,000 | 1,000 |
189
+ | Pro | 25,000 | 10,000 |
190
+ | Enterprise | Set per organization | Set per organization |
191
+
192
+ A waiter can wait for up to 90 days.
193
+
194
+ ## Webhook processing concurrency
195
+
196
+ This limit controls how many webhook deliveries and waiter resumes are processed at once per environment. Additional deliveries wait their turn. It is not a limit on the runs those deliveries start, which use your normal [concurrency limits](#concurrency-limits).
197
+
198
+ | Pricing tier | Limit |
199
+ | :----------- | :----------------------- |
200
+ | Free | 5 concurrent deliveries |
201
+ | Hobby | 10 concurrent deliveries |
202
+ | Pro | 50 concurrent deliveries |
203
+ | Enterprise | Set per organization |
204
+
205
+ ## Webhook delivery retention
206
+
207
+ How long [deliveries](/webhooks/deliveries) stay visible in the dashboard and the API.
208
+
209
+ | Pricing tier | Limit |
210
+ | :----------- | :--------------------------- |
211
+ | Free | 3 days |
212
+ | Hobby | 7 days |
213
+ | Pro | 30 days |
214
+ | Enterprise | 90, 180 or 365 days |
215
+
216
+ By default, deliveries are stored for at least 30 days, so upgrading your plan shows older deliveries that are still stored. An organization can ask us to store deliveries only for its retention period instead. Deliveries past that period are then deleted and can't be shown again after an upgrade.
217
+
218
+ ## Webhook subscribers
219
+
220
+ An endpoint can have up to 25 subscribers (tasks, agent sessions and channels) on every plan.
221
+
181
222
  ## Log retention
182
223
 
183
224
  | Pricing tier | Limit |
@@ -57,6 +57,35 @@ ORDER BY minute
57
57
  LIMIT 1000
58
58
  ```
59
59
 
60
+ #### Platform limit metrics
61
+
62
+ The `metrics` table also records how close your environment is to platform limits. These are the metrics behind [Limits alerts](/troubleshooting-alerts#limits-alerts):
63
+
64
+ | Metric | What it shows |
65
+ | ------------------------------- | ------------------------------------------------ |
66
+ | `queue.size.queued` | Runs currently queued in the environment |
67
+ | `queue.size.limit` | The environment's maximum queued runs |
68
+ | `webhook.processing.in_flight` | Webhooks being processed now |
69
+ | `webhook.processing.limit` | The webhook processing concurrency limit |
70
+ | `webhook.ingress.denied` | Incoming webhooks rejected by a rate limit |
71
+ | `webhook.ingress.remaining_min` | The lowest remaining rate limit allowance seen |
72
+ | `webhook.ingress.limit.tokens` | The rate limit's allowance per window |
73
+ | `webhook.waiters.live` | Runs currently waiting for a webhook |
74
+ | `webhook.waiters.limit.env` | The environment's maximum webhook waiters |
75
+ | `webhook.waiters.refused` | New webhook waiters that were refused |
76
+
77
+ ```sql
78
+ SELECT
79
+ timeBucket(),
80
+ maxIf(metric_value, metric_name = 'queue.size.queued') AS queued,
81
+ maxIf(metric_value, metric_name = 'queue.size.limit') AS queue_limit
82
+ FROM metrics
83
+ WHERE metric_name LIKE 'queue.size.%'
84
+ GROUP BY timeBucket
85
+ ORDER BY timeBucket
86
+ LIMIT 1000
87
+ ```
88
+
60
89
  ### `prettyFormat()`
61
90
 
62
91
  Use `prettyFormat()` to format metric values for display:
@@ -389,6 +389,68 @@ EVENT_REPOSITORY_DEFAULT_STORE=clickhouse_v2
389
389
 
390
390
  This only affects new runs; existing runs continue to read from wherever their events were originally stored.
391
391
 
392
+ ### Move from the legacy clickhouse store
393
+
394
+ The legacy `clickhouse` store (the `task_events_v1` table) is retired. If you set `EVENT_REPOSITORY_DEFAULT_STORE=clickhouse`, your webapp now writes and reads `clickhouse_v2` instead and logs a warning at startup. Events recorded in `task_events_v1` stay there but no longer show in the dashboard until you copy them over.
395
+
396
+ <Steps titleSize="h4">
397
+ <Step title="Switch to clickhouse_v2">
398
+ Switch the store in your `.env` and restart the webapp:
399
+
400
+ ```bash .env
401
+ EVENT_REPOSITORY_DEFAULT_STORE=clickhouse_v2
402
+ ```
403
+ </Step>
404
+ <Step title="List the days to copy">
405
+ List the days that hold legacy events. The bundled ClickHouse container already sets `CLICKHOUSE_USER` and `CLICKHOUSE_PASSWORD`, and `clickhouse-client` reads them, so no credentials are needed:
406
+
407
+ ```bash
408
+ docker compose exec clickhouse clickhouse-client -q \
409
+ "SELECT toDate(start_time) AS day, count() FROM trigger_dev.task_events_v1 GROUP BY day ORDER BY day"
410
+ ```
411
+ </Step>
412
+ <Step title="Save the copy query">
413
+ Save the copy query as `copy-task-events-v1.sql`. It copies one day and skips events already in `task_events_v2`, so it's safe to run again if it's interrupted:
414
+
415
+ ```sql copy-task-events-v1.sql
416
+ INSERT INTO trigger_dev.task_events_v2
417
+ (environment_id, organization_id, project_id, task_identifier, run_id, start_time,
418
+ trace_id, span_id, parent_span_id, message, kind, status, attributes_text,
419
+ metadata, duration, expires_at, inserted_at)
420
+ SELECT
421
+ environment_id, organization_id, project_id, task_identifier, run_id, start_time,
422
+ trace_id, span_id, parent_span_id, message, kind, status, attributes_text,
423
+ metadata, duration, expires_at, toDateTime64(start_time, 3)
424
+ FROM trigger_dev.task_events_v1
425
+ WHERE toDate(start_time) = {day:Date}
426
+ AND (trace_id, span_id, start_time, kind, status) NOT IN (
427
+ SELECT trace_id, span_id, start_time, kind, status
428
+ FROM trigger_dev.task_events_v2
429
+ WHERE inserted_at >= {day:Date} AND inserted_at < {day:Date} + INTERVAL 8 DAY
430
+ AND toDate(start_time) = {day:Date}
431
+ );
432
+ ```
433
+ </Step>
434
+ <Step title="Copy each day">
435
+ Run it once per day from step 2, oldest first:
436
+
437
+ ```bash
438
+ docker compose exec -T clickhouse clickhouse-client --param_day=2026-08-01 \
439
+ --multiquery < copy-task-events-v1.sql
440
+ ```
441
+ </Step>
442
+ <Step title="Check a copied run">
443
+ Open a run from one of the copied days and check its trace and logs show.
444
+ </Step>
445
+ </Steps>
446
+
447
+ Copying one day at a time keeps memory use bounded on large tables. A future release removes `task_events_v1`, so copy anything you want to keep before then.
448
+
449
+ <Note>
450
+ `EVENT_REPOSITORY_V1_REROUTE_ENABLED=0` keeps the legacy store while you migrate. It's
451
+ temporary and will be removed in a future release.
452
+ </Note>
453
+
392
454
  ## Realtime streams
393
455
 
394
456
  Realtime streams power AI-agent token streaming and run streams. They default to **v2**, backed by the bundled `s2` service — [s2-lite](https://s2.dev), the open-source, self-hostable S2 server. It stores stream data in a persistent volume and is preconfigured in the webapp compose file, so no setup is required.
@@ -33,6 +33,9 @@ mode: "wide"
33
33
  | `TRIGGER_DEQUEUE_INTERVAL_MS` | No | 250 | Dequeue interval (ms). |
34
34
  | `TRIGGER_DEQUEUE_IDLE_INTERVAL_MS` | No | 1000 (1s) | Dequeue idle interval (ms). |
35
35
  | `TRIGGER_DEQUEUE_MAX_CONSUMER_COUNT` | No | 1 | Max dequeue consumer count. |
36
+ | `RELEASE_UNSTARTED_RUNS_ENABLED` | No | false | Set to true to requeue a run straight away when its container or pod can't be created, rather than after the pending execution timeout. Limited by `RUN_RELEASES_PER_MINUTE`. |
37
+ | `RELEASE_FAILED_POD_RUNS_ENABLED` | No | false | Set to true to requeue a run straight away when its pod fails before the run starts, rather than after the pending execution timeout. Independent of `RELEASE_UNSTARTED_RUNS_ENABLED`; the two share `RUN_RELEASES_PER_MINUTE`. Needs `FAILED_POD_HANDLER_ENABLED`. |
38
+ | `RUN_RELEASES_PER_MINUTE` | No | 5 | Different runs released within a minute, by either release flag, past which every release waits for the timeout again until a slot frees, a run already released included. 0 withholds every release; blank means the default. |
36
39
  | **Docker settings** | | | |
37
40
  | `DOCKER_API_VERSION` | No | v1.41 | Docker API version. You should probably not touch this. |
38
41
  | `DOCKER_STRIP_IMAGE_DIGEST` | No | true | Strip image digest in Docker. Turning off can cause issues. |
@@ -88,6 +88,11 @@ mode: "wide"
88
88
  | `API_RATE_LIMIT_METRICS_MAX_ENTRIES` | No | 10000 | Cap on distinct (environment, bucket) entries held between flushes. |
89
89
  | `API_RATE_LIMIT_METRICS_WAIT_FOR_ASYNC_INSERT` | No | 0 | Wait for ClickHouse to write each async insert of API rate limit metrics, surfacing write failures. |
90
90
  | `API_RATE_LIMIT_METRICS_INSERT_BUSY_TIMEOUT_MS` | No | 10000 | How long ClickHouse buffers API rate limit metric inserts before writing a part. |
91
+ | `WEBHOOK_METRICS_FLUSH_INTERVAL_MS` | No | 10000 | How often aggregated webhook limit metrics are flushed to ClickHouse. |
92
+ | `WEBHOOK_METRICS_MAX_ENTRIES` | No | 10000 | Cap on distinct (environment, bucket) entries held between flushes. |
93
+ | `WEBHOOK_METRICS_MAX_ENDPOINTS` | No | 500 | Cap on endpoints with their own webhook metric rows per environment and bucket; environment totals still count all. |
94
+ | `QUEUE_SIZE_METRICS_INTERVAL_SEC` | No | 30 | Queue size sampling interval in seconds; must divide 60. |
95
+ | `QUEUE_SIZE_METRICS_MAX_ENVS` | No | 2000 | Most environments sampled per interval. |
91
96
  | `DEPLOYMENT_RATE_LIMIT_REFILL_INTERVAL` | No | 10s | Deployment endpoints rate limit refill interval. |
92
97
  | `DEPLOYMENT_RATE_LIMIT_MAX` | No | 1500 | Deployment endpoints rate limit max. |
93
98
  | `DEPLOYMENT_RATE_LIMIT_REFILL_RATE` | No | 500 | Deployment endpoints rate limit refill rate. |
@@ -123,6 +128,14 @@ mode: "wide"
123
128
  | `ARTIFACTS_OBJECT_STORE_ACCESS_KEY_ID` | No | — | Optional artifacts store access key. |
124
129
  | `ARTIFACTS_OBJECT_STORE_SECRET_ACCESS_KEY` | No | — | Optional artifacts store secret key. |
125
130
  | `ARTIFACTS_OBJECT_STORE_REGION` | No | — | Optional artifacts store region. |
131
+ | `DEPLOYMENT_INDEX_OBJECT_STORE_BASE_URL` | No | — | Optional separate store for deployment index snapshots. If not set, uses main object store. |
132
+ | `DEPLOYMENT_INDEX_OBJECT_STORE_BUCKET` | No | — | Deployment index store bucket. Required together with the base URL. |
133
+ | `DEPLOYMENT_INDEX_OBJECT_STORE_ACCESS_KEY_ID` | No | — | Deployment index store access key. |
134
+ | `DEPLOYMENT_INDEX_OBJECT_STORE_SECRET_ACCESS_KEY` | No | — | Deployment index store secret key. |
135
+ | `DEPLOYMENT_INDEX_OBJECT_STORE_REGION` | No | — | Deployment index store region. |
136
+ | `DEPLOYMENT_INDEX_OBJECT_STORE_SERVICE` | No | s3 | Deployment index store service. |
137
+ | `DEPLOYMENT_INDEX_UPLOAD_MAX_ATTEMPTS` | No | 4 | Attempts to store a deployment's index before the deploy fails. |
138
+ | `DEPLOYMENT_INDEX_CAPTURE_ENABLED` | No | false | Set to true to store each deployment's full index in object storage when it deploys. |
126
139
  | **Alerts** | | | |
127
140
  | `ORG_SLACK_INTEGRATION_CLIENT_ID` | No | — | Slack client ID. Required for Slack alerts. |
128
141
  | `ORG_SLACK_INTEGRATION_CLIENT_SECRET` | No | — | Slack client secret. Required for Slack alerts. |
@@ -160,6 +173,7 @@ mode: "wide"
160
173
  | `SERVER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` | No | 8192 | OTel span attribute value length limit. |
161
174
  | **Task events** | | | |
162
175
  | `EVENT_REPOSITORY_DEFAULT_STORE` | No | postgres | Where to store task events. Set to `clickhouse_v2` to store in ClickHouse (recommended for production). |
176
+ | `EVENT_REPOSITORY_V1_REROUTE_ENABLED` | No | 1 | Routes the retired `clickhouse` store to `clickhouse_v2`, for new events and for reads. Set `EVENT_REPOSITORY_DEFAULT_STORE=clickhouse_v2` and [copy older events](/self-hosting/docker#move-from-the-legacy-clickhouse-store) to keep them. `0` keeps the legacy store temporarily and will be removed. |
163
177
  | `EVENT_REPOSITORY_POSTGRES_WRITES_DISABLED` | No | 0 | Skip all PostgreSQL task-event writes (set to `1`). Only enable when `EVENT_REPOSITORY_DEFAULT_STORE` is `clickhouse_v2`, otherwise task events are lost. |
164
178
  | **Realtime** | | | |
165
179
  | `REALTIME_BACKEND_NATIVE_ENABLED` | No | 0 | Set to `1` to enable native realtime run subscriptions and change notifications. When `0`, subscriptions use Electric. The Helm chart sets this to `1`. |
@@ -679,6 +679,8 @@ webapp:
679
679
 
680
680
  This only affects new runs; existing runs continue to read from wherever their events were originally stored.
681
681
 
682
+ If you set `EVENT_REPOSITORY_DEFAULT_STORE` to the legacy `clickhouse` store, switch it to `clickhouse_v2`. To keep events recorded before the switch, follow [Move from the legacy clickhouse store](/self-hosting/docker#move-from-the-legacy-clickhouse-store) and run the queries with `kubectl exec` against your ClickHouse pod.
683
+
682
684
  ### Realtime streams
683
685
 
684
686
  Realtime streams power AI-agent token streaming and run streams. They default to **v2**, backed by the bundled `s2` deployment — [s2-lite](https://s2.dev), the open-source, self-hostable S2 server. The chart deploys it with a persistent volume, so no extra services are required.
@@ -3,6 +3,8 @@ title: "Alerts"
3
3
  description: "Get alerted when runs or deployments fail, or when deployments succeed."
4
4
  ---
5
5
 
6
+ import { PrivateBeta } from "/snippets/feature-status.jsx";
7
+
6
8
  We support receiving alerts for the following events:
7
9
  - Run fails
8
10
  - Deployment fails
@@ -44,6 +46,43 @@ Click on the triple dot menu on the right side of the table row and select "Disa
44
46
  </Steps>
45
47
 
46
48
 
49
+ ## Limits alerts
50
+
51
+ <PrivateBeta flag="alertsEnabled" />
52
+
53
+ A **Limits** alert watches limits that can interrupt or degrade runtime flow. Pick one limit for a single alert, or several (or "Watch all limits") for one combined alert.
54
+
55
+ | Group | What you can watch |
56
+ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
57
+ | Platform | API rate limit running out, Environment concurrency filling up, Queue size nearing its limit |
58
+ | Webhooks | Webhook processing saturating, Webhook ingress getting rate-limited, Webhook endpoint getting rate-limited, Webhook waiters running out |
59
+
60
+ ### Signals
61
+
62
+ Each limit has up to two signals. Choose one or both:
63
+
64
+ - **Limit reached**: the limit is already affecting traffic, for example requests are being rejected.
65
+ - **Approaching the limit**: usage has reached a share of the limit. The default is 80%, and you can change it.
66
+
67
+ Queue size only has the approaching signal.
68
+
69
+ ### Any or all
70
+
71
+ When you watch more than one limit, choose when the alert raises:
72
+
73
+ - **Any**: raise when at least one selected limit is true.
74
+ - **All**: raise only when every selected limit is true at the same time.
75
+
76
+ ### Queue metrics
77
+
78
+ Environment concurrency and queue size need queue metrics for your environment. If they aren't available, they aren't offered when you create the alert.
79
+
80
+ ### No data
81
+
82
+ A limit shows "No data" until its metrics exist, for example before your environment has made any API requests or received any webhooks. You can query these metrics yourself, see [Platform limit metrics](/observability/query#platform-limit-metrics).
83
+
84
+ You can also create these alerts with the [API](/management/alerts/create).
85
+
47
86
  ## Error group alerts
48
87
 
49
88
  Error group alerts are **issue-based**, not run-based. They are created from the **Errors** page in the dashboard (the "Configure alerts…" button), not from the New alert modal on the Alerts page. Once created they show up in the Alerts table alongside your other alerts, labelled "Error group".
@@ -70,6 +70,10 @@ The [MCP server](/mcp-introduction) has `list_webhook_deliveries` (filter by end
70
70
  | [`GET /api/v1/webhooks/deliveries/{deliveryId}`](/management/webhooks/retrieve-delivery) | Reads a delivery, with a `targets` entry per subscriber and one for waiting runs. |
71
71
  | [`POST /api/v1/webhooks/deliveries/{deliveryId}/replay`](/management/webhooks/replay-delivery) | Replays it, as above. The key needs permission to trigger every task the replay starts, and to write sessions if it reaches an agent's subscriber. |
72
72
 
73
+ ## Retention
74
+
75
+ Deliveries stay visible for 3 days on Free, 7 days on Hobby, 30 days on Pro, and 90, 180 or 365 days on Enterprise. See [Limits](/limits#webhook-delivery-retention).
76
+
73
77
  ## Deduplication
74
78
 
75
79
  Trigger.dev uses the endpoint and idempotency key to deduplicate incoming events. Delivery records and task runs have separate deduplication windows:
@@ -83,6 +83,14 @@ The CLI finds your endpoints and subscribers during `trigger dev` and deployment
83
83
 
84
84
  A request must pass verification before anything else happens. Provider handshakes are answered directly and create no delivery. A verified request is checked for duplicates, recorded, and then routed: each subscriber's filter is checked, matching subscribers run, and waiting runs whose match fits the event are resumed. The delivery records a result per subscriber.
85
85
 
86
+ ## Pricing and limits
87
+
88
+ Webhooks have no separate charge on Trigger.dev Cloud. Receiving, verifying, filtering and recording deliveries is included in every plan. The runs a delivery starts or resumes are billed like any other run: a per-run invocation cost plus compute time. See [pricing](https://trigger.dev/pricing).
89
+
90
+ A delivery that no subscriber's filter matches starts no run for any subscriber, so it costs nothing. A run already waiting for that webhook still resumes. A run [waiting](/webhooks/waiters) for a webhook is suspended, so it uses no compute until the webhook arrives.
91
+
92
+ Each plan sets limits on the ingest rate, waiting runs, processing concurrency and how long deliveries are kept. See [Limits](/limits#webhook-ingest-rate-limits).
93
+
86
94
  ## Connect it
87
95
 
88
96
  Every endpoint can produce a setup prompt for an AI coding agent. It holds the URL, the events your subscribers filter on, and the steps for registering the endpoint and its signing secret with the provider. Copy it from the endpoints list or the endpoint's page, or read it with the MCP server's `get_webhook_endpoint_details` tool. See [Connect a provider](/webhooks/connect).
@@ -106,6 +106,8 @@ Supabase database webhooks aren't signed, so `supabase()` checks a shared secret
106
106
 
107
107
  Slack also ships as a chat channel in `@trigger.dev/slack`.
108
108
 
109
+ For a complete project built on `resend()`, see the [Resend email agent example](https://github.com/triggerdotdev/examples/tree/main/resend-slack-email-agent). It drafts replies to a Resend inbox and waits for your approval in Slack before sending them.
110
+
109
111
  ### Hookdeck
110
112
 
111
113
  `hookdeck()` verifies requests that a [Hookdeck](https://hookdeck.com) connection forwards to the endpoint. Hookdeck forwards the original provider's body unchanged, so pass that provider's event type:
@@ -242,7 +244,7 @@ Custom sources support these schemes:
242
244
  | `hmac` | Computes an HMAC over the raw body or a signing template and compares it with a request header. |
243
245
  | `shared-secret` | Compares a static token supplied in a header, bearer token, basic authentication, or request body. |
244
246
  | `url-secret` | Compares a token in a URL query parameter. Hosted endpoints don't support path placement. |
245
- | `asymmetric` | Verifies an Ed25519, ECDSA, or RSA signature using the provider's public key. |
247
+ | `asymmetric` | Verifies an Ed25519, ECDSA (P-256 with SHA-256, P-384 with SHA-384), or RSA-SHA256 signature using the provider's public key. Set `prehash: "sha256"` when the provider signs a SHA-256 digest of the signing string instead of the string itself. |
246
248
 
247
249
  <Warning>
248
250
  Query parameters can appear in access logs, proxies, and traces. Prefer a header-based scheme when
@@ -178,13 +178,13 @@ if (!result.cancelled) {
178
178
 
179
179
  | Limit | Default |
180
180
  | --- | --- |
181
- | Live waiters per endpoint | 10,000. This is also the most one delivery can resume. |
182
- | Live waiters per environment | 1,000,000 on self-hosted installs. |
181
+ | Live waiters per endpoint | Depends on your plan on Trigger.dev Cloud, 10,000 on self-hosted installs. This is also the most one delivery can resume. |
182
+ | Live waiters per environment | Depends on your plan on Trigger.dev Cloud, 1,000,000 on self-hosted installs. |
183
183
  | Distinct sets of `match` paths per endpoint | 25 |
184
184
  | Paths per `match` | 5 |
185
185
  | Tags per waiter | 10 |
186
186
  | Timeout | 24 hours by default, 90 days at most |
187
187
 
188
- On Trigger.dev Cloud the per-endpoint and per-environment limits depend on your plan. Self-hosted installs can change them with `WEBHOOK_WAITER_MAX_PER_ENDPOINT`, `WEBHOOK_WAITER_MAX_PER_ENVIRONMENT` and `WEBHOOK_WAITER_MAX_SHAPES`.
188
+ On Trigger.dev Cloud the per-endpoint and per-environment limits depend on your plan: see [Limits](/limits#webhook-waiters). Self-hosted installs can change them with `WEBHOOK_WAITER_MAX_PER_ENDPOINT`, `WEBHOOK_WAITER_MAX_PER_ENVIRONMENT` and `WEBHOOK_WAITER_MAX_SHAPES`.
189
189
 
190
190
  A delivery that resumes waiters shows one **Waiting runs** row on its [delivery page](/webhooks/deliveries#where-a-delivery-went) with how many matched, resumed and failed. A delivery that arrives while runs are waiting but matches none of them, and no subscriber takes, is `UNMATCHED`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trigger.dev/sdk",
3
- "version": "4.8.1",
3
+ "version": "4.8.2",
4
4
  "description": "trigger.dev Node.JS SDK",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -69,7 +69,7 @@
69
69
  "dependencies": {
70
70
  "@opentelemetry/api": "1.9.1",
71
71
  "@opentelemetry/semantic-conventions": "1.41.1",
72
- "@trigger.dev/core": "4.8.1",
72
+ "@trigger.dev/core": "4.8.2",
73
73
  "uncrypto": "^0.1.3"
74
74
  },
75
75
  "devDependencies": {
@@ -95,22 +95,27 @@ mutate with `sessions.update`, end with `sessions.close` (terminal, idempotent).
95
95
 
96
96
  ### 2. Durable sub-agent as a streaming tool
97
97
 
98
- `AgentChat` inside an AI SDK `tool()` delegates to a durable sub-agent; its response streams as
99
- preliminary tool results. Give the tool a `toModelOutput` so the model sees a compact summary.
98
+ `AgentChat` inside an AI SDK `tool()` delegates to a durable sub-agent; `chat.forwardSubAgent`
99
+ streams its reply to the frontend and returns the final message as the tool output. Give the tool a
100
+ `toModelOutput` so the model sees a compact summary.
100
101
 
101
102
  ```ts
102
103
  import { tool } from "ai";
104
+ import { chat } from "@trigger.dev/sdk/ai";
103
105
  import { AgentChat } from "@trigger.dev/sdk/chat";
104
106
  import { z } from "zod";
105
107
 
106
108
  const researchTool = tool({
107
109
  description: "Delegate research to a specialist agent.",
108
110
  inputSchema: z.object({ topic: z.string() }),
109
- execute: async function* ({ topic }, { abortSignal }) {
110
- const chat = new AgentChat({ agent: "research-agent" });
111
- const stream = await chat.sendMessage(topic, { abortSignal });
112
- yield* stream.messages(); // UIMessage snapshots become preliminary tool results
113
- await chat.close();
111
+ execute: async ({ topic }, { toolCallId, abortSignal }) => {
112
+ const researcher = new AgentChat({ agent: "research-agent" });
113
+ try {
114
+ const stream = await researcher.sendMessage(topic, { abortSignal });
115
+ return await chat.forwardSubAgent(stream, { toolCallId }); // chunks out once, final UIMessage back
116
+ } finally {
117
+ await researcher.close();
118
+ }
114
119
  },
115
120
  toModelOutput: ({ output: message }) => {
116
121
  const lastText = message?.parts?.findLast((p: { type: string }) => p.type === "text") as
@@ -121,6 +126,11 @@ const researchTool = tool({
121
126
  });
122
127
  ```
123
128
 
129
+ On the frontend, `useSubAgentMessages()` from `@trigger.dev/sdk/chat/react` rebuilds each sub-agent
130
+ while it runs: pass its `onData` to `useChat` and render `subAgents.get(part.toolCallId) ?? part.output`.
131
+ Do not `yield* stream.messages()` from a tool: every snapshot repeats the whole message, so the bytes
132
+ written grow with the square of the reply length.
133
+
124
134
  For a subtask exposed via `execute: ai.toolExecute(task)`, stream progress to the agent's run with
125
135
  `chat.stream.writer({ target: "root" })`. `target` accepts `"self" | "parent" | "root" | <runId>`.
126
136
  Inside the subtask, read context with `ai.toolCallId()` and `ai.chatContextOrThrow<typeof myChat>()`
@@ -105,6 +105,7 @@ Larger machines cost proportionally more per second of compute:
105
105
  ## Key principles
106
106
 
107
107
  - **Waits > 5 seconds are free** — checkpointed, no compute charge.
108
+ - **Webhook filters skip runs** — a webhook delivery starts no run when no subscriber's filter matches, so it costs no compute (a run already waiting for that webhook still resumes). Receiving webhooks has no separate charge.
108
109
  - **Start small, scale up** — the default `small-1x` is right for most tasks.
109
110
  - **I/O-bound tasks don't need big machines** — API calls and DB queries wait on the network.
110
111
  - **Debounce saves the most on high-frequency tasks** — it consolidates bursts into single runs.