@trigger.dev/sdk 4.5.0-rc.6 → 4.5.0-rc.7

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 (191) hide show
  1. package/dist/commonjs/v3/ai.d.ts +171 -5
  2. package/dist/commonjs/v3/ai.js +309 -22
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/chat-server.d.ts +8 -0
  5. package/dist/commonjs/v3/chat-server.js +32 -10
  6. package/dist/commonjs/v3/chat-server.js.map +1 -1
  7. package/dist/commonjs/v3/chat-server.test.js +51 -0
  8. package/dist/commonjs/v3/chat-server.test.js.map +1 -1
  9. package/dist/commonjs/v3/createStartSessionAction.test.js +30 -0
  10. package/dist/commonjs/v3/createStartSessionAction.test.js.map +1 -1
  11. package/dist/commonjs/v3/sessions.d.ts +3 -2
  12. package/dist/commonjs/v3/sessions.js +3 -2
  13. package/dist/commonjs/v3/sessions.js.map +1 -1
  14. package/dist/commonjs/version.js +1 -1
  15. package/dist/esm/v3/ai.d.ts +171 -5
  16. package/dist/esm/v3/ai.js +309 -22
  17. package/dist/esm/v3/ai.js.map +1 -1
  18. package/dist/esm/v3/chat-server.d.ts +8 -0
  19. package/dist/esm/v3/chat-server.js +32 -10
  20. package/dist/esm/v3/chat-server.js.map +1 -1
  21. package/dist/esm/v3/chat-server.test.js +51 -0
  22. package/dist/esm/v3/chat-server.test.js.map +1 -1
  23. package/dist/esm/v3/createStartSessionAction.test.js +30 -0
  24. package/dist/esm/v3/createStartSessionAction.test.js.map +1 -1
  25. package/dist/esm/v3/sessions.d.ts +3 -2
  26. package/dist/esm/v3/sessions.js +3 -2
  27. package/dist/esm/v3/sessions.js.map +1 -1
  28. package/dist/esm/version.js +1 -1
  29. package/docs/ai/prompts.mdx +430 -0
  30. package/docs/ai-chat/actions.mdx +115 -0
  31. package/docs/ai-chat/anatomy.mdx +71 -0
  32. package/docs/ai-chat/backend.mdx +817 -0
  33. package/docs/ai-chat/background-injection.mdx +221 -0
  34. package/docs/ai-chat/changelog.mdx +850 -0
  35. package/docs/ai-chat/chat-local.mdx +174 -0
  36. package/docs/ai-chat/client-protocol.mdx +1081 -0
  37. package/docs/ai-chat/compaction.mdx +411 -0
  38. package/docs/ai-chat/custom-agents.mdx +364 -0
  39. package/docs/ai-chat/error-handling.mdx +415 -0
  40. package/docs/ai-chat/fast-starts.mdx +672 -0
  41. package/docs/ai-chat/frontend.mdx +580 -0
  42. package/docs/ai-chat/how-it-works.mdx +230 -0
  43. package/docs/ai-chat/lifecycle-hooks.mdx +530 -0
  44. package/docs/ai-chat/mcp.mdx +101 -0
  45. package/docs/ai-chat/overview.mdx +90 -0
  46. package/docs/ai-chat/patterns/branching-conversations.mdx +284 -0
  47. package/docs/ai-chat/patterns/code-sandbox.mdx +126 -0
  48. package/docs/ai-chat/patterns/database-persistence.mdx +414 -0
  49. package/docs/ai-chat/patterns/human-in-the-loop.mdx +275 -0
  50. package/docs/ai-chat/patterns/large-payloads.mdx +169 -0
  51. package/docs/ai-chat/patterns/oom-resilience.mdx +120 -0
  52. package/docs/ai-chat/patterns/persistence-and-replay.mdx +211 -0
  53. package/docs/ai-chat/patterns/recovery-boot.mdx +230 -0
  54. package/docs/ai-chat/patterns/skills.mdx +221 -0
  55. package/docs/ai-chat/patterns/sub-agents.mdx +383 -0
  56. package/docs/ai-chat/patterns/tool-result-auditing.mdx +148 -0
  57. package/docs/ai-chat/patterns/trusted-edge-signals.mdx +337 -0
  58. package/docs/ai-chat/patterns/version-upgrades.mdx +172 -0
  59. package/docs/ai-chat/pending-messages.mdx +343 -0
  60. package/docs/ai-chat/prompt-caching.mdx +206 -0
  61. package/docs/ai-chat/quick-start.mdx +161 -0
  62. package/docs/ai-chat/reference.mdx +909 -0
  63. package/docs/ai-chat/server-chat.mdx +263 -0
  64. package/docs/ai-chat/sessions.mdx +333 -0
  65. package/docs/ai-chat/testing.mdx +682 -0
  66. package/docs/ai-chat/tools.mdx +191 -0
  67. package/docs/ai-chat/types.mdx +242 -0
  68. package/docs/ai-chat/upgrade-guide.mdx +515 -0
  69. package/docs/apikeys.mdx +54 -0
  70. package/docs/building-with-ai.mdx +261 -0
  71. package/docs/bulk-actions.mdx +49 -0
  72. package/docs/changelog.mdx +6 -0
  73. package/docs/cli-deploy-commands.mdx +9 -0
  74. package/docs/cli-dev-commands.mdx +9 -0
  75. package/docs/cli-dev.mdx +8 -0
  76. package/docs/cli-init-commands.mdx +58 -0
  77. package/docs/cli-introduction.mdx +25 -0
  78. package/docs/cli-list-profiles-commands.mdx +42 -0
  79. package/docs/cli-login-commands.mdx +33 -0
  80. package/docs/cli-logout-commands.mdx +33 -0
  81. package/docs/cli-preview-archive.mdx +59 -0
  82. package/docs/cli-promote-commands.mdx +9 -0
  83. package/docs/cli-switch.mdx +43 -0
  84. package/docs/cli-update-commands.mdx +42 -0
  85. package/docs/cli-whoami-commands.mdx +33 -0
  86. package/docs/community.mdx +6 -0
  87. package/docs/config/config-file.mdx +602 -0
  88. package/docs/config/extensions/additionalFiles.mdx +38 -0
  89. package/docs/config/extensions/additionalPackages.mdx +40 -0
  90. package/docs/config/extensions/aptGet.mdx +34 -0
  91. package/docs/config/extensions/audioWaveform.mdx +20 -0
  92. package/docs/config/extensions/custom.mdx +380 -0
  93. package/docs/config/extensions/emitDecoratorMetadata.mdx +29 -0
  94. package/docs/config/extensions/esbuildPlugin.mdx +31 -0
  95. package/docs/config/extensions/ffmpeg.mdx +45 -0
  96. package/docs/config/extensions/lightpanda.mdx +56 -0
  97. package/docs/config/extensions/overview.mdx +67 -0
  98. package/docs/config/extensions/playwright.mdx +195 -0
  99. package/docs/config/extensions/prismaExtension.mdx +1014 -0
  100. package/docs/config/extensions/puppeteer.mdx +30 -0
  101. package/docs/config/extensions/pythonExtension.mdx +182 -0
  102. package/docs/config/extensions/syncEnvVars.mdx +291 -0
  103. package/docs/context.mdx +235 -0
  104. package/docs/database-connections.mdx +213 -0
  105. package/docs/deploy-environment-variables.mdx +435 -0
  106. package/docs/deployment/atomic-deployment.mdx +172 -0
  107. package/docs/deployment/overview.mdx +257 -0
  108. package/docs/deployment/preview-branches.mdx +224 -0
  109. package/docs/errors-retrying.mdx +379 -0
  110. package/docs/github-actions.mdx +222 -0
  111. package/docs/github-integration.mdx +136 -0
  112. package/docs/github-repo.mdx +8 -0
  113. package/docs/help-email.mdx +6 -0
  114. package/docs/help-slack.mdx +11 -0
  115. package/docs/hidden-tasks.mdx +56 -0
  116. package/docs/how-it-works.mdx +454 -0
  117. package/docs/how-to-reduce-your-spend.mdx +217 -0
  118. package/docs/idempotency.mdx +504 -0
  119. package/docs/introduction.mdx +223 -0
  120. package/docs/limits.mdx +241 -0
  121. package/docs/logging.mdx +195 -0
  122. package/docs/machines.mdx +952 -0
  123. package/docs/manual-setup.mdx +632 -0
  124. package/docs/mcp-agent-rules.mdx +41 -0
  125. package/docs/mcp-introduction.mdx +385 -0
  126. package/docs/mcp-tools.mdx +273 -0
  127. package/docs/migrating-from-v3.mdx +334 -0
  128. package/docs/observability/dashboards.mdx +102 -0
  129. package/docs/observability/query.mdx +585 -0
  130. package/docs/open-source-contributing.mdx +16 -0
  131. package/docs/open-source-self-hosting.mdx +541 -0
  132. package/docs/private-networking/aws-console-setup.mdx +304 -0
  133. package/docs/private-networking/overview.mdx +144 -0
  134. package/docs/private-networking/troubleshooting.mdx +78 -0
  135. package/docs/queue-concurrency.mdx +354 -0
  136. package/docs/quick-start.mdx +97 -0
  137. package/docs/realtime/auth.mdx +208 -0
  138. package/docs/realtime/backend/overview.mdx +45 -0
  139. package/docs/realtime/backend/streams.mdx +418 -0
  140. package/docs/realtime/backend/subscribe.mdx +225 -0
  141. package/docs/realtime/how-it-works.mdx +94 -0
  142. package/docs/realtime/overview.mdx +63 -0
  143. package/docs/realtime/react-hooks/overview.mdx +73 -0
  144. package/docs/realtime/react-hooks/streams.mdx +449 -0
  145. package/docs/realtime/react-hooks/subscribe.mdx +674 -0
  146. package/docs/realtime/react-hooks/swr.mdx +87 -0
  147. package/docs/realtime/react-hooks/triggering.mdx +194 -0
  148. package/docs/realtime/react-hooks/use-wait-token.mdx +34 -0
  149. package/docs/realtime/run-object.mdx +174 -0
  150. package/docs/replaying.mdx +72 -0
  151. package/docs/request-feature.mdx +6 -0
  152. package/docs/roadmap.mdx +6 -0
  153. package/docs/run-tests.mdx +20 -0
  154. package/docs/run-usage.mdx +113 -0
  155. package/docs/runs/heartbeats.mdx +38 -0
  156. package/docs/runs/max-duration.mdx +139 -0
  157. package/docs/runs/metadata.mdx +734 -0
  158. package/docs/runs/priority.mdx +31 -0
  159. package/docs/runs.mdx +396 -0
  160. package/docs/self-hosting/docker.mdx +458 -0
  161. package/docs/self-hosting/env/supervisor.mdx +74 -0
  162. package/docs/self-hosting/env/webapp.mdx +276 -0
  163. package/docs/self-hosting/kubernetes.mdx +601 -0
  164. package/docs/self-hosting/overview.mdx +108 -0
  165. package/docs/skills.mdx +85 -0
  166. package/docs/tags.mdx +120 -0
  167. package/docs/tasks/overview.mdx +697 -0
  168. package/docs/tasks/scheduled.mdx +382 -0
  169. package/docs/tasks/schemaTask.mdx +413 -0
  170. package/docs/tasks/streams.mdx +884 -0
  171. package/docs/triggering.mdx +1320 -0
  172. package/docs/troubleshooting-alerts.mdx +385 -0
  173. package/docs/troubleshooting-debugging-in-vscode.mdx +8 -0
  174. package/docs/troubleshooting-github-issues.mdx +6 -0
  175. package/docs/troubleshooting-uptime-status.mdx +6 -0
  176. package/docs/troubleshooting.mdx +398 -0
  177. package/docs/upgrading-packages.mdx +80 -0
  178. package/docs/vercel-integration.mdx +207 -0
  179. package/docs/versioning.mdx +56 -0
  180. package/docs/video-walkthrough.mdx +23 -0
  181. package/docs/wait-for-token.mdx +540 -0
  182. package/docs/wait-for.mdx +42 -0
  183. package/docs/wait-until.mdx +53 -0
  184. package/docs/wait.mdx +18 -0
  185. package/docs/writing-tasks-introduction.mdx +33 -0
  186. package/package.json +8 -5
  187. package/skills/trigger-authoring-chat-agent/SKILL.md +296 -0
  188. package/skills/trigger-authoring-tasks/SKILL.md +254 -0
  189. package/skills/trigger-chat-agent-advanced/SKILL.md +368 -0
  190. package/skills/trigger-cost-savings/SKILL.md +116 -0
  191. package/skills/trigger-realtime-and-frontend/SKILL.md +276 -0
@@ -0,0 +1,230 @@
1
+ ---
2
+ title: "Recovery boot"
3
+ sidebarTitle: "Recovery boot"
4
+ description: "Recover from cancel-mid-stream, crashes, and OOM kills with full conversational context. The smart default Just Works; the onRecoveryBoot hook is the override path for advanced policies."
5
+ ---
6
+
7
+ import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
8
+
9
+ <RcBanner />
10
+
11
+ When a `chat.agent` run dies in the middle of streaming a response — the user cancels, the worker OOMs, or an unhandled exception kills the process — the durable streams hold what was in flight. The next run boots as a continuation, reads both stream tails, and reconstructs a chain that preserves the partial response so any follow-up (`keep going`, `actually do X instead`, a new question) has full context.
12
+
13
+ The behavior is automatic. The `onRecoveryBoot` hook is opt-in for policies that need something different.
14
+
15
+ ## The scenario
16
+
17
+ ```ts
18
+ // Turn 1 is mid-essay when the user clicks Cancel.
19
+ window.__chat.send("Write me a long essay about espresso");
20
+ // ... assistant has written 3000 characters ...
21
+ window.__chat.stop(); // OR: server-side cancel_run
22
+
23
+ // User decides what they want next.
24
+ window.__chat.send("keep going"); // OR: "what's 7+8?", or anything
25
+ ```
26
+
27
+ The cancelled run never wrote `onTurnComplete`. The snapshot is stale or absent. `session.out` has a half-written assistant message. `session.in` has the original user message (the run consumed it but never marked the turn complete) plus the new follow-up.
28
+
29
+ A naive continuation would either re-run the cancelled essay (the user already chose to stop) or drop everything (no context for the follow-up). Recovery boot handles this without either failure mode.
30
+
31
+ ## The smart default
32
+
33
+ On a continuation boot, the runtime reads:
34
+
35
+ - **Snapshot** — settled turns persisted by the last successful `onTurnComplete`.
36
+ - **`session.out` tail past the snapshot cursor** — closed assistant turns plus, optionally, a `partialAssistant` (the trailing message whose stream never received a `finish` chunk). `cleanupAbortedParts` has already stripped streaming-in-progress fragments.
37
+ - **`session.in` tail past the last `turn-complete` cursor** — user messages the dead run hadn't acknowledged.
38
+
39
+ If both `partialAssistant` and `inFlightUsers` are non-empty, the runtime splices `[firstInFlightUser, partialAssistant]` onto the chain. The remaining in-flight users dispatch as fresh turns. The model sees:
40
+
41
+ ```
42
+ [ ...settledMessages, // chain through the last completed turn
43
+ firstInFlightUser, // the question the dead run was answering
44
+ partialAssistant, // the dead run's incomplete response
45
+ followUpUser ] // the new turn the customer just sent
46
+ ```
47
+
48
+ Modern instruction-following models prioritize the latest user message. The follow-up determines the response:
49
+
50
+ | Follow-up | Model behavior |
51
+ |---|---|
52
+ | "keep going" / "continue" / "more" | Continues the partial essay from where it stopped. |
53
+ | "actually, what's 7+8?" | Answers the new question. Prior context doesn't derail it. |
54
+ | "scrap that, do something else" | Abandons the partial work and follows the new direction. |
55
+
56
+ No customer code needed for any of these.
57
+
58
+ ## When to register `onRecoveryBoot`
59
+
60
+ The hook fires when recovery state is non-empty (either `partialAssistant` is defined or there's at least one in-flight user). Register it when you need a policy different from "preserve context":
61
+
62
+ - **Drop the partial entirely.** Your UX means "cancel discards the work — start fresh from the follow-up."
63
+ - **Synthesize tool results.** The partial has tool calls in `input-available` state (HITL was mid-call when the run died). Return a chain that has fabricated `output-available` results so the model can continue.
64
+ - **Emit a recovery banner.** Write a `data-chat-recovery` UIMessage chunk via `ctx.writer` so the frontend can render "Recovering interrupted response..." before the model speaks.
65
+ - **Persist recovered state.** Use `beforeBoot` to flush the partial to your own database before the next turn starts.
66
+
67
+ ```ts
68
+ import { chat } from "@trigger.dev/sdk/ai";
69
+
70
+ export const myChat = chat.agent({
71
+ id: "my-chat",
72
+ onRecoveryBoot: async ({ partialAssistant, inFlightUsers, writer, cause, previousRunId }) => {
73
+ writer.write({
74
+ type: "data-chat-recovery",
75
+ data: { cause, previousRunId, partialPresent: partialAssistant !== undefined },
76
+ transient: true,
77
+ });
78
+ // Return nothing → fall through to smart default.
79
+ },
80
+ run: async ({ messages, signal }) =>
81
+ streamText({ model, messages, abortSignal: signal }),
82
+ });
83
+ ```
84
+
85
+ ## Hook reference
86
+
87
+ ### Fires when
88
+
89
+ The hook fires once on a continuation boot, AFTER both stream tails have been read, AND only when there's a partial assistant — the mid-stream-died signal:
90
+
91
+ ```ts
92
+ const shouldFire = partialAssistant !== undefined;
93
+ ```
94
+
95
+ In-flight users alone don't fire the hook. Graceful exits like `chat.requestUpgrade()` and `chat.endRun()` may leave an unacknowledged user on `session.in` (the message that triggered the upgrade, the next message after endRun), but no partial — that's a normal continuation, not recovery. The next message just dispatches as turn 1 on the new run via the normal session.in pump.
96
+
97
+ Skipped scenarios (where the hook does NOT fire):
98
+
99
+ - A clean continuation after `chat.endRun()` with no buffered follow-up.
100
+ - A fresh chat (no continuation, attempt 1).
101
+ - An OOM retry that booted onto a complete snapshot (no partial on the tail).
102
+ - `chat.requestUpgrade()` graceful exit — predecessor ended cleanly before processing, no partial.
103
+ - An agent with [`hydrateMessages`](/ai-chat/lifecycle-hooks#hydratemessages) registered. Customers using `hydrateMessages` own persistence — recovery decisions live in their own DB query.
104
+
105
+ ### Event shape
106
+
107
+ ```ts
108
+ type RecoveryBootEvent<TUIM extends UIMessage = UIMessage> = {
109
+ ctx: TaskRunContext;
110
+ chatId: string;
111
+ runId: string;
112
+ previousRunId: string;
113
+ cause: "cancelled" | "crashed" | "unknown";
114
+ settledMessages: TUIM[];
115
+ inFlightUsers: TUIM[];
116
+ partialAssistant: TUIM | undefined;
117
+ pendingToolCalls: Array<{
118
+ toolCallId: string;
119
+ toolName: string;
120
+ input: unknown;
121
+ partIndex: number;
122
+ }>;
123
+ writer: ChatWriter;
124
+ };
125
+ ```
126
+
127
+ <Note>
128
+ `cause` is currently always `"unknown"` — the run engine doesn't yet plumb the
129
+ real reason into the continuation payload. The enum is forward-looking; don't
130
+ branch behavior on it for now.
131
+ </Note>
132
+
133
+ ### Return shape
134
+
135
+ Every field is optional. Returning `undefined` (or nothing) accepts the smart default for every field.
136
+
137
+ ```ts
138
+ type RecoveryBootResult<TUIM extends UIMessage = UIMessage> = {
139
+ chain?: TUIM[];
140
+ recoveredTurns?: TUIM[];
141
+ beforeBoot?: () => Promise<void>;
142
+ };
143
+ ```
144
+
145
+ - **`chain`** — replaces the seed chain. Defaults to `[...settledMessages, firstInFlightUser, partialAssistant]` when both partial and in-flight users exist, otherwise `settledMessages` alone.
146
+ - **`recoveredTurns`** — user messages to dispatch as fresh turns after the chain is restored. Defaults to `inFlightUsers.slice(1)` when the smart default consumed the first user, otherwise `inFlightUsers`.
147
+ - **`beforeBoot`** — runs after the writer flushes and before the first recovered turn fires. Use for blocking persistence (write the partial to your DB so a later turn can reference it). Errors bubble — wrap your own try/catch if you want to soft-fail.
148
+
149
+ ## Examples
150
+
151
+ ### Drop the partial — strict "cancel means discard"
152
+
153
+ The customer's UX treats cancel as "throw the work away":
154
+
155
+ ```ts
156
+ onRecoveryBoot: async ({ inFlightUsers, partialAssistant }) => {
157
+ if (!partialAssistant) return; // No partial → nothing to drop
158
+ return {
159
+ chain: undefined, // Use settledMessages, don't splice partial
160
+ recoveredTurns: inFlightUsers.slice(1) // Still skip the first user (the dead run was answering it)
161
+ };
162
+ }
163
+ ```
164
+
165
+ ### Synthesize tool results for a mid-call interruption
166
+
167
+ The dead run was processing a tool call when it died. The partial has tool parts in `input-available` state with no `output-available`. Synthesize a result so the model can keep going:
168
+
169
+ ```ts
170
+ onRecoveryBoot: async ({ partialAssistant, pendingToolCalls, settledMessages, inFlightUsers }) => {
171
+ if (pendingToolCalls.length === 0) return;
172
+
173
+ // Rebuild the partial with synthetic outputs for any input-available tool call.
174
+ const repaired = {
175
+ ...partialAssistant!,
176
+ parts: partialAssistant!.parts!.map((part, i) => {
177
+ const pending = pendingToolCalls.find(p => p.partIndex === i);
178
+ if (!pending) return part;
179
+ return {
180
+ ...part,
181
+ state: "output-available" as const,
182
+ output: { interrupted: true, reason: "previous run was cancelled" },
183
+ };
184
+ }),
185
+ };
186
+
187
+ return {
188
+ chain: [...settledMessages, inFlightUsers[0]!, repaired],
189
+ recoveredTurns: inFlightUsers.slice(1),
190
+ };
191
+ }
192
+ ```
193
+
194
+ ### Persist the partial before the next turn fires
195
+
196
+ ```ts
197
+ onRecoveryBoot: async ({ chatId, partialAssistant }) => {
198
+ return {
199
+ beforeBoot: async () => {
200
+ if (partialAssistant) {
201
+ await db.partial.create({
202
+ data: { chatId, partialJson: JSON.stringify(partialAssistant) },
203
+ });
204
+ }
205
+ },
206
+ };
207
+ }
208
+ ```
209
+
210
+ ## Interaction with other features
211
+
212
+ ### `hydrateMessages`
213
+
214
+ If your agent registers [`hydrateMessages`](/ai-chat/lifecycle-hooks#hydratemessages), the runtime skips snapshot read, `session.out` replay, `session.in` replay, AND `onRecoveryBoot`. Your DB is the source of truth — recovery decisions live in your own query. To detect a cancel-recovery scenario yourself, persist a `runState: "in-progress"` flag in `onTurnStart` and check for it in `hydrateMessages`.
215
+
216
+ ### `chat.requestUpgrade()`
217
+
218
+ [`chat.requestUpgrade()`](/ai-chat/patterns/version-upgrades) is a graceful exit — the old run doesn't crash, it returns cleanly. The new continuation run boots with a clean `session.out` tail (`partialAssistant` is undefined) and the upgrade-trigger message on `session.in` (one in-flight user). The smart default doesn't splice (it requires both partial AND in-flight users), so the chain is just `settledMessages` and the in-flight user dispatches as a fresh turn. `onRecoveryBoot` still fires (there's an in-flight user) — use it to emit an "upgraded" signal to the UI if you want.
219
+
220
+ ### Hooks throwing
221
+
222
+ If the body of `onRecoveryBoot` throws (or rejects), the runtime logs a warning and falls back to the smart default — the run does not fail. Wrap your own try/catch if you want stricter handling.
223
+
224
+ `beforeBoot` is the exception: it's the contract you opted into for blocking persistence, so errors thrown there **bubble** and fail the run rather than dispatch recovered turns against half-persisted state. Wrap it yourself if you want to soft-fail.
225
+
226
+ ## See also
227
+
228
+ - [OOM resilience](/ai-chat/patterns/oom-resilience) — `oomMachine` opt-in for automatic memory-driven recovery; uses the same recovery boot path.
229
+ - [Persistence and replay](/ai-chat/patterns/persistence-and-replay) — the snapshot + dual-tail replay model that recovery boot sits on top of.
230
+ - [Lifecycle hooks](/ai-chat/lifecycle-hooks) — where `onRecoveryBoot` sits in the broader hook taxonomy.
@@ -0,0 +1,221 @@
1
+ ---
2
+ title: "Agent Skills"
3
+ sidebarTitle: "Agent Skills"
4
+ description: "Ship reusable capabilities (folders with SKILL.md + scripts) that a chat agent discovers and invokes on demand."
5
+ ---
6
+
7
+ import RcBanner from "/snippets/ai-chat-rc-banner.mdx";
8
+
9
+ <RcBanner />
10
+
11
+ Agent skills are reusable capabilities you ship as folders — a `SKILL.md` describing when and how to use them, plus optional scripts, references, and assets. The chat agent sees a short description of each skill in its system prompt, loads the full instructions on demand via a `loadSkill` tool, and invokes the bundled scripts via `bash` — all without you wiring anything up manually.
12
+
13
+ Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills). Works with any provider (OpenAI, Anthropic, Gemini, etc.) — not tied to Anthropic's server-side skills.
14
+
15
+ ## Why skills?
16
+
17
+ Compared to regular AI SDK tools:
18
+
19
+ - **Tools** are typed functions you pre-declare. Great when you know up-front exactly what capability the agent needs.
20
+ - **Skills** are folders the model discovers and reads on demand. Great when the capability is a bundle of instructions + helper scripts that would be awkward to encode as a single tool.
21
+
22
+ PDFs are the canonical example: you don't want to ask the LLM to parse PDF bytes inline. You want it to `bash scripts/extract.py report.pdf` using a bundled `pdfplumber` wrapper. A skill ships the script, the instructions, and any reference notes together.
23
+
24
+ Dashboard-editable `SKILL.md` is on the roadmap so a platform team can tighten a skill's description or "when to use" text without a redeploy. Today, skills are SDK-only — defined in your task code and shipped with each deploy.
25
+
26
+ ## Trust model
27
+
28
+ Skills are **developer-authored code**, not end-user-supplied. The same developer who writes the `chat.agent()` writes the skill bundle. The trust boundary is identical to any `tool.execute` handler the developer writes — scripts run directly in the Trigger.dev worker container, no sandboxing required.
29
+
30
+ This makes skills different from the Claude Code / end-user model where arbitrary user-provided skills need isolation. Don't accept skill paths from untrusted input.
31
+
32
+ ## Skill folder layout
33
+
34
+ A skill is a directory under your project (conventionally `trigger/skills/{id}/`):
35
+
36
+ ```
37
+ trigger/skills/time-utils/
38
+ ├── SKILL.md # Required — frontmatter + instructions
39
+ ├── scripts/
40
+ │ ├── now.sh
41
+ │ └── add.sh
42
+ ├── references/
43
+ │ └── timezones.txt
44
+ └── assets/ # Optional — templates, data files, etc.
45
+ ```
46
+
47
+ ### SKILL.md
48
+
49
+ Frontmatter is YAML-subset — only `name` and `description` are required:
50
+
51
+ ```md
52
+ ---
53
+ name: time-utils
54
+ description: Compute and format dates/times in arbitrary timezones. Use when the user asks "what time is it", timezone conversions, or date math.
55
+ ---
56
+
57
+ # Time utilities
58
+
59
+ ## When to use
60
+
61
+ - The user asks for the current time in a timezone
62
+ - The user wants date math ("3 days from now")
63
+
64
+ ## Scripts
65
+
66
+ ### `scripts/now.sh [TZ]`
67
+ Prints the current time in the given IANA timezone (default `UTC`).
68
+
69
+ ### `scripts/add.sh DAYS [TZ]`
70
+ Prints a date `DAYS` days from now.
71
+
72
+ ## Tips
73
+ - IANA timezone names only (`America/New_York`, not `EST`).
74
+ - See `references/timezones.txt` for a cheat-sheet.
75
+ ```
76
+
77
+ The **description** is what the model sees in its system prompt — write it like you're explaining to the agent when to reach for the skill.
78
+
79
+ The **body** is loaded on demand via the `loadSkill` tool when the agent decides to use the skill. Write it like documentation for the agent.
80
+
81
+ ## Defining and using a skill
82
+
83
+ ```ts trigger/chat.ts
84
+ import { chat } from "@trigger.dev/sdk/ai";
85
+ import { skills } from "@trigger.dev/sdk";
86
+ import { streamText, stepCountIs } from "ai";
87
+ import { anthropic } from "@ai-sdk/anthropic";
88
+
89
+ const timeUtilsSkill = skills.define({
90
+ id: "time-utils",
91
+ path: "./skills/time-utils",
92
+ });
93
+
94
+ export const agent = chat.agent({
95
+ id: "docs-chat",
96
+ onChatStart: async () => {
97
+ chat.skills.set([await timeUtilsSkill.local()]);
98
+ },
99
+ run: async ({ messages, signal }) => {
100
+ return streamText({
101
+ model: anthropic("claude-sonnet-4-5"),
102
+ messages,
103
+ abortSignal: signal,
104
+ ...chat.toStreamTextOptions(),
105
+ stopWhen: stepCountIs(15),
106
+ });
107
+ },
108
+ });
109
+ ```
110
+
111
+ `skills.define({ id, path })` does two things:
112
+
113
+ 1. Registers the skill with the Trigger.dev build system so the CLI **automatically bundles the folder** into your deploy image at `/app/.trigger/skills/{id}/`. No `trigger.config.ts` changes, no build extension — it just works.
114
+ 2. Returns a `SkillHandle` you use at runtime.
115
+
116
+ `skill.local()` reads the bundled `SKILL.md` from disk and returns a `ResolvedSkill` with the parsed frontmatter + body + on-disk path.
117
+
118
+ `chat.skills.set([...])` stores the resolved skills for the current run. `chat.toStreamTextOptions()` spreads them into `streamText` automatically:
119
+
120
+ - The frontmatter `description` lands in the system prompt under "Available skills:".
121
+ - Three tools are added: `loadSkill`, `readFile`, `bash` — scoped per skill.
122
+
123
+ ## What gets auto-injected
124
+
125
+ When you spread `chat.toStreamTextOptions()` with skills set, the AI SDK call receives three tools:
126
+
127
+ ### `loadSkill({ name })`
128
+
129
+ Returns the full `SKILL.md` body for the named skill. The model calls this first when it decides a skill is relevant, to load the full instructions.
130
+
131
+ ### `readFile({ skill, path })`
132
+
133
+ Reads a file inside the skill's bundled folder. Paths are relative to the skill's root and are rejected if they attempt to escape via `..` or absolute paths. Output is capped at 1 MB per call.
134
+
135
+ Use for reference files and templates that the model should read literally:
136
+
137
+ ```
138
+ readFile({ skill: "time-utils", path: "references/timezones.txt" })
139
+ ```
140
+
141
+ ### `bash({ skill, command })`
142
+
143
+ Runs a bash command with `cwd` set to the skill's root. Stdout and stderr are captured and returned (each capped at 64 KB per call, with tail truncation). The turn's abort signal propagates — cancelling the run kills the child process.
144
+
145
+ Use to invoke the skill's bundled scripts:
146
+
147
+ ```
148
+ bash({ skill: "time-utils", command: "bash scripts/now.sh America/Los_Angeles" })
149
+ ```
150
+
151
+ Script runtime expectations are yours to manage. If your skill uses `extract.py`, your deploy image needs Python — add it via your build config the same way you would for any other task dependency.
152
+
153
+ ## How discovery works in the model
154
+
155
+ The model sees a short preamble appended to your system prompt:
156
+
157
+ ```
158
+ Available skills (call `loadSkill` to read the full instructions before using one):
159
+ - time-utils: Compute and format dates/times in arbitrary timezones...
160
+ - pdf-processing: Extract text from PDFs, fill forms...
161
+ ```
162
+
163
+ When the user asks something that matches a description, the model calls `loadSkill({ name: "time-utils" })` to load the body, then follows the body's instructions — typically by calling `bash` or `readFile` on the bundled scripts.
164
+
165
+ This is **progressive disclosure**: each skill costs ~100 tokens up front (its one-line description), and only the ones the model actually uses pay the full context cost.
166
+
167
+ ## Mixing skills with custom tools
168
+
169
+ If you also define your own AI SDK tools, pass them through `chat.toStreamTextOptions()` so the merge is explicit:
170
+
171
+ ```ts
172
+ return streamText({
173
+ model: anthropic("claude-sonnet-4-5"),
174
+ messages,
175
+ abortSignal: signal,
176
+ ...chat.toStreamTextOptions({
177
+ tools: {
178
+ webFetch, // your tool
179
+ deepResearch, // your tool
180
+ },
181
+ }),
182
+ stopWhen: stepCountIs(15),
183
+ });
184
+ ```
185
+
186
+ Your tools win on name conflicts. (Pick names that don't collide with `loadSkill` / `readFile` / `bash` to keep things predictable.)
187
+
188
+ Also declare those same tools on the agent's [`tools`](/ai-chat/tools) config. `toStreamTextOptions` merges them with the skill tools for the model call, while the config option threads them into history re-conversion so any `toModelOutput` survives across turns. The auto-injected skill tools (`loadSkill` / `readFile` / `bash`) don't define `toModelOutput`, so they don't need to be on the config.
189
+
190
+ ## Bundling
191
+
192
+ Bundling is **built-in to the CLI** — there's no extension to import. When you run `trigger deploy` or `trigger dev`:
193
+
194
+ 1. esbuild bundles your task code as usual.
195
+ 2. The CLI forks the indexer locally against the bundled output, collects every `skills.define({ path })` registration.
196
+ 3. Each skill's folder is copied to `{outputPath}/.trigger/skills/{id}/` via a recursive copy.
197
+ 4. The existing Dockerfile `COPY` picks up `.trigger/skills/` along with the rest of the bundle — no Dockerfile changes.
198
+
199
+ If you're running `trigger dev`, the same layout appears in the local dev output directory, so `skill.local()` works the same way.
200
+
201
+ ## Path scoping rules
202
+
203
+ - `skill.path` always resolves to `${process.cwd()}/.trigger/skills/{id}/` at runtime. Don't hardcode paths elsewhere.
204
+ - `readFile` rejects `..` segments and absolute paths — the tool only exposes files inside the skill's own directory.
205
+ - `bash` runs with `cwd` set to the skill's root. Inside the script, relative paths resolve against the skill directory.
206
+ - Cross-skill access isn't provided — each skill is isolated by design. If two skills need to share data, either duplicate the shared file or consolidate the skills.
207
+
208
+ ## Current limitations
209
+
210
+ - `skill.resolve()` (backend-managed overrides) is not available yet — use `.local()` for now. Dashboard-editable `SKILL.md` is on the roadmap.
211
+ - No per-skill metrics in the dashboard yet.
212
+ - No Anthropic `/v1/skills` integration — use the portable path today; we're tracking the Anthropic optimization separately.
213
+
214
+ ## Full example
215
+
216
+ See [`projects/ai-chat/src/trigger/skills/time-utils/`](https://github.com/triggerdotdev/references/tree/main/projects/ai-chat/src/trigger/skills/time-utils) in the [references repo](https://github.com/triggerdotdev/references) for a working skill that bundles two bash scripts and a reference cheat-sheet, wired into a `chat.agent` that answers timezone questions.
217
+
218
+ ## Related
219
+
220
+ - [AI SDK cookbook — Agent Skills](https://ai-sdk.dev/cookbook/guides/agent-skills) — the userland pattern we build on
221
+ - [Anthropic Agent Skills](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) — Anthropic's codified version (server-side, optional future integration)