workflow 5.0.0-beta.2 → 5.0.0-beta.21

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 (170) hide show
  1. package/dist/api-workflow.d.ts +1 -1
  2. package/dist/api-workflow.d.ts.map +1 -1
  3. package/dist/api-workflow.js +2 -2
  4. package/dist/api.d.ts +5 -1
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/api.js +14 -2
  7. package/dist/internal/builtins.d.ts +17 -0
  8. package/dist/internal/builtins.d.ts.map +1 -1
  9. package/dist/internal/builtins.js +65 -1
  10. package/dist/observability.d.ts +1 -1
  11. package/dist/observability.js +2 -2
  12. package/dist/runtime.d.ts +1 -1
  13. package/dist/runtime.d.ts.map +1 -1
  14. package/dist/runtime.js +2 -2
  15. package/docs/ai/index.mdx +27 -23
  16. package/docs/api-reference/index.mdx +24 -0
  17. package/docs/api-reference/meta.json +8 -0
  18. package/docs/api-reference/vitest/index.mdx +28 -7
  19. package/docs/api-reference/workflow/create-hook.mdx +38 -0
  20. package/docs/api-reference/workflow/create-webhook.mdx +1 -0
  21. package/docs/api-reference/workflow/experimental-set-attributes.mdx +65 -0
  22. package/docs/api-reference/workflow/fetch.mdx +5 -0
  23. package/docs/api-reference/workflow/index.mdx +3 -0
  24. package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -45
  25. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +7 -0
  26. package/docs/api-reference/workflow-api/get-run.mdx +6 -0
  27. package/docs/api-reference/workflow-api/index.mdx +6 -8
  28. package/docs/api-reference/workflow-api/resume-hook.mdx +57 -0
  29. package/docs/api-reference/workflow-api/start.mdx +13 -5
  30. package/docs/api-reference/workflow-astro/index.mdx +18 -0
  31. package/docs/api-reference/workflow-astro/meta.json +4 -0
  32. package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
  33. package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  34. package/docs/api-reference/workflow-errors/index.mdx +85 -0
  35. package/docs/api-reference/workflow-errors/meta.json +5 -0
  36. package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  37. package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
  38. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
  39. package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  40. package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  41. package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  42. package/docs/api-reference/workflow-nest/index.mdx +31 -0
  43. package/docs/api-reference/workflow-nest/meta.json +9 -0
  44. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  45. package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  46. package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
  47. package/docs/api-reference/workflow-next/with-workflow.mdx +56 -2
  48. package/docs/api-reference/workflow-nitro/index.mdx +59 -0
  49. package/docs/api-reference/workflow-nuxt/index.mdx +47 -0
  50. package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  51. package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  52. package/docs/api-reference/workflow-observability/index.mdx +64 -0
  53. package/docs/api-reference/workflow-observability/meta.json +11 -0
  54. package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  55. package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  56. package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  57. package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  58. package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
  59. package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  60. package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
  61. package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
  62. package/docs/api-reference/workflow-runtime/index.mdx +43 -0
  63. package/docs/api-reference/workflow-runtime/meta.json +12 -0
  64. package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
  65. package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  66. package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +5 -8
  67. package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
  68. package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +2 -2
  69. package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +11 -4
  70. package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
  71. package/docs/api-reference/workflow-serde/index.mdx +0 -1
  72. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
  73. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +1 -2
  74. package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
  75. package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
  76. package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  77. package/docs/api-reference/workflow-vite/index.mdx +18 -0
  78. package/docs/api-reference/workflow-vite/meta.json +4 -0
  79. package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
  80. package/docs/changelog/attributes-mvp.mdx +380 -0
  81. package/docs/changelog/eager-processing.mdx +269 -0
  82. package/docs/changelog/index.mdx +2 -1
  83. package/docs/changelog/lazy-event-creation.md +127 -0
  84. package/docs/changelog/meta.json +7 -1
  85. package/docs/changelog/resilient-start.mdx +31 -283
  86. package/docs/changelog/turbo-mode.md +87 -0
  87. package/docs/cookbook/advanced/child-workflows.mdx +315 -0
  88. package/docs/cookbook/advanced/meta.json +2 -3
  89. package/docs/cookbook/advanced/publishing-libraries.mdx +87 -29
  90. package/docs/cookbook/advanced/serializable-steps.mdx +17 -5
  91. package/docs/cookbook/advanced/upgrading-workflows.mdx +195 -0
  92. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +156 -0
  93. package/docs/cookbook/agent-patterns/durable-agent.mdx +11 -184
  94. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +150 -173
  95. package/docs/cookbook/agent-patterns/meta.json +1 -7
  96. package/docs/cookbook/common-patterns/batching.mdx +44 -118
  97. package/docs/cookbook/common-patterns/idempotency.mdx +36 -52
  98. package/docs/cookbook/common-patterns/meta.json +4 -4
  99. package/docs/cookbook/common-patterns/rate-limiting.mdx +1 -1
  100. package/docs/cookbook/common-patterns/saga.mdx +128 -33
  101. package/docs/cookbook/common-patterns/scheduling.mdx +77 -193
  102. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +155 -0
  103. package/docs/cookbook/common-patterns/timeouts.mdx +100 -0
  104. package/docs/cookbook/common-patterns/workflow-composition.mdx +117 -0
  105. package/docs/cookbook/index.mdx +14 -17
  106. package/docs/cookbook/integrations/ai-sdk.mdx +330 -142
  107. package/docs/cookbook/integrations/chat-sdk.mdx +264 -151
  108. package/docs/cookbook/integrations/sandbox.mdx +482 -81
  109. package/docs/cookbook/meta.json +1 -1
  110. package/docs/deploying/building-a-world.mdx +1 -1
  111. package/docs/deploying/world/postgres-world.mdx +5 -3
  112. package/docs/deploying/world/vercel-world.mdx +2 -0
  113. package/docs/errors/abort-signal-timeout-in-workflow.mdx +80 -0
  114. package/docs/errors/corrupted-event-log.mdx +5 -5
  115. package/docs/errors/hook-conflict.mdx +56 -4
  116. package/docs/errors/index.mdx +9 -0
  117. package/docs/errors/replay-divergence.mdx +27 -0
  118. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  119. package/docs/errors/step-executed-multiple-times.mdx +23 -0
  120. package/docs/errors/step-not-registered.mdx +1 -1
  121. package/docs/foundations/cancellation.mdx +459 -0
  122. package/docs/foundations/errors-and-retries.mdx +7 -3
  123. package/docs/foundations/hooks.mdx +29 -0
  124. package/docs/foundations/idempotency.mdx +236 -11
  125. package/docs/foundations/index.mdx +3 -3
  126. package/docs/foundations/meta.json +3 -2
  127. package/docs/foundations/serialization.mdx +78 -42
  128. package/docs/foundations/starting-workflows.mdx +6 -2
  129. package/docs/foundations/streaming.mdx +14 -23
  130. package/docs/foundations/versioning.mdx +263 -0
  131. package/docs/getting-started/astro.mdx +6 -0
  132. package/docs/getting-started/index.mdx +6 -7
  133. package/docs/getting-started/meta.json +1 -0
  134. package/docs/getting-started/nestjs.mdx +9 -0
  135. package/docs/getting-started/next.mdx +5 -3
  136. package/docs/getting-started/nitro.mdx +22 -0
  137. package/docs/getting-started/sveltekit.mdx +6 -0
  138. package/docs/getting-started/tanstack-start.mdx +241 -0
  139. package/docs/how-it-works/cancellation.mdx +287 -0
  140. package/docs/how-it-works/code-transform.mdx +2 -2
  141. package/docs/how-it-works/encryption.mdx +2 -2
  142. package/docs/how-it-works/event-sourcing.mdx +2 -2
  143. package/docs/how-it-works/meta.json +2 -1
  144. package/docs/internal/index.mdx +21 -0
  145. package/docs/internal/meta.json +10 -0
  146. package/docs/internal/nitro-native-build.mdx +38 -0
  147. package/docs/internal/nitro-web-ui.mdx +24 -0
  148. package/docs/internal/serializable-abort-controller.mdx +148 -0
  149. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +63 -16
  150. package/docs/migration-guides/migrating-from-inngest.mdx +44 -22
  151. package/docs/migration-guides/migrating-from-temporal.mdx +43 -14
  152. package/docs/migration-guides/migrating-from-trigger-dev.mdx +59 -27
  153. package/docs/observability/attributes.mdx +87 -0
  154. package/docs/observability/index.mdx +25 -1
  155. package/docs/observability/meta.json +1 -1
  156. package/docs/observability/tracing.mdx +106 -0
  157. package/docs/testing/index.mdx +2 -2
  158. package/package.json +14 -13
  159. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  160. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
  161. package/docs/cookbook/advanced/custom-serialization.mdx +0 -168
  162. package/docs/cookbook/advanced/durable-objects.mdx +0 -148
  163. package/docs/cookbook/advanced/isomorphic-packages.mdx +0 -145
  164. package/docs/cookbook/agent-patterns/stop-workflow.mdx +0 -216
  165. package/docs/cookbook/agent-patterns/tool-orchestration.mdx +0 -255
  166. package/docs/cookbook/agent-patterns/tool-streaming.mdx +0 -181
  167. package/docs/cookbook/common-patterns/child-workflows.mdx +0 -372
  168. package/docs/cookbook/common-patterns/content-router.mdx +0 -207
  169. package/docs/cookbook/common-patterns/fan-out.mdx +0 -208
  170. package/docs/foundations/common-patterns.mdx +0 -265
@@ -32,7 +32,7 @@ npx skills add https://github.com/vercel/workflow --skill migrating-to-workflow-
32
32
 
33
33
  ## What changes when you leave trigger.dev?
34
34
 
35
- trigger.dev v3 defines durable work with `task()` or `schemaTask()` from `@trigger.dev/sdk/v3`, deploys tasks to the trigger.dev cloud or a self-hosted instance, and triggers runs via `tasks.trigger()`. A separate worker fleet picks up runs, applies retry policies, and routes `wait.for`, `wait.forToken`, and `metadata.stream` calls through the platform.
35
+ trigger.dev v3 defines durable work with `task()` or `schemaTask()` from `@trigger.dev/sdk` (trigger.dev v3), deploys tasks to the trigger.dev cloud or a self-hosted instance, and triggers runs via `tasks.trigger()`. A separate worker fleet picks up runs, applies retry policies, and routes `wait.for`, `wait.forToken`, and `metadata.stream` calls through the platform.
36
36
 
37
37
  The Workflow SDK replaces that with `"use workflow"` functions that orchestrate `"use step"` functions in plain TypeScript. There is no task registry, separate deploy target, or SDK client. Durable replay, retries, and event history ship with the runtime.
38
38
 
@@ -46,21 +46,25 @@ Migration collapses the task abstraction into plain async functions. Business lo
46
46
  | `schemaTask({ schema, run })` | Typed function + `"use workflow"` | Validate inputs at the call site. |
47
47
  | Inline `run` body | `"use step"` function | Side effects move into named steps. |
48
48
  | `logger` / `metadata.set` | `console` + `getWritable({ namespace: 'status' })` | Logs flow through the run timeline. Status writes go on a named stream. |
49
- | `wait.for({ seconds })` / `wait.until({ date })` | `sleep()` | Import from `workflow`. |
49
+ | `wait.for({ seconds \| minutes \| hours \| days })` / `wait.until({ date })` | `sleep()` | Import from `workflow`. |
50
50
  | `wait.forToken({ timeout })` | `createHook()` + `Promise.race` with `sleep()` | Hooks carry a typed token. |
51
- | `tasks.trigger()` / `triggerAndWait()` | `start()` and `getRun(runId).returnValue` | Wrap both in `"use step"` functions. |
51
+ | `tasks.trigger()` / `triggerAndWait()` | `start()` and `getRun(runId).returnValue` | Call `start()` directly; wrap `getRun()` collection in a `"use step"` function. |
52
52
  | `batch.triggerAndWait()` | `Promise.all(runIds.map(collectResult))` | Fan out via standard concurrency. |
53
53
  | `AbortTaskRunError` | `FatalError` | Stops retries immediately. |
54
- | `retry.onThrow` / `retry.fetch` | `RetryableError`, `FatalError`, `maxRetries` | Retry lives on the step. Exponential backoff is a step `maxRetries` config, not a helper. |
55
- | `metadata.stream()` / Realtime | `getWritable()` / `getWritable({ namespace })` | Named streams are the canonical read channel. Clients read from the end of the stream for current status. Do not poll `getRun()` or persist status to a database for client reads. |
54
+ | `retry.onThrow` / `retry.fetch` | `RetryableError`, `FatalError`, `maxRetries` | Retry count lives on the step via `myStep.maxRetries = N` (default 3). Control delay between attempts by throwing `new RetryableError(msg, { retryAfter: '5s' })` — there is no built-in exponential helper; compute the delay yourself based on `getStepMetadata().attempt` if you need one. |
55
+ | `metadata.stream()` / Realtime | `getWritable()` / `getWritable({ namespace })` | For granular business status (progress updates, current-stage messages), prefer writing to `getWritable({ namespace: 'status' })` from a step and reading from the end of the named stream on the client. Use `getRun(runId).status` for terminal/lifecycle state only. |
56
56
  | Self-hosted worker + dashboard | Managed execution + built-in UI | No worker fleet to operate. |
57
57
 
58
58
  ## Translate your first workflow
59
59
 
60
+ <Callout type="warn">
61
+ trigger.dev's `task.run` body has full Node.js access. The SDK's `'use workflow'` body runs in a sandboxed VM — side effects (I/O, `Date.now()`, `Math.random()`, DB, fetch) must live inside `'use step'` functions. Orchestration stays in the workflow body.
62
+ </Callout>
63
+
60
64
  Start with the shell. trigger.dev wraps the handler in `task()`; the Workflow SDK marks the function with a directive.
61
65
 
62
66
  ```typescript title="trigger/order.ts (trigger.dev)"
63
- import { task } from '@trigger.dev/sdk/v3';
67
+ import { task } from '@trigger.dev/sdk';
64
68
 
65
69
  export const processOrder = task({
66
70
  id: 'process-order',
@@ -116,7 +120,7 @@ No id lookup, no API key, no separate worker. `start()` returns a handle immedia
116
120
 
117
121
  {/* @skip-typecheck: trigger.dev SDK types not available */}
118
122
  ```typescript title="workflow/workflows/refund.ts (trigger.dev, abbreviated)"
119
- // import { wait } from '@trigger.dev/sdk/v3';
123
+ // import { wait } from '@trigger.dev/sdk';
120
124
  const token = await wait.createToken({ timeout: '7d' });
121
125
  const approval = await wait.forToken<{ approved: boolean }>(token.id).unwrap();
122
126
  // External system resumes with: await wait.completeToken(token.id, { approved: true });
@@ -130,10 +134,17 @@ using approval = createHook<{ approved: boolean }>({ // [!code highlight]
130
134
  const payload = await approval;
131
135
  ```
132
136
 
133
- **What changed:** the platform-issued opaque token becomes an app-owned string. The caller that resumes the run supplies that same string, so there is no token lookup.
137
+ **What changed:** the platform-issued opaque token becomes an app-owned string. The caller that resumes the run supplies that same string, so there is no token lookup. (trigger.dev also exposes `token.url` for external callers; the SDK analog is `createWebhook().url`).
134
138
 
135
139
  ### Resume from an API route
136
140
 
141
+ There are two shapes of resume, and `wait.forToken` can map to either:
142
+
143
+ - **Server-side resume (known token):** `createHook<T>({ token: 'business-token' })` + `resumeHook(token, payload)` from an API route. Use this when your app knows the token shape and controls the resume call.
144
+ - **Third-party callback URL (generated token):** `createWebhook({ respondWith: 'default' })` + pass `webhook.url` to the external system. The external system hits the URL to resume.
145
+
146
+ See [`/docs/foundations/hooks`](/docs/foundations/hooks) for both surfaces.
147
+
137
148
  trigger.dev completes a token with `wait.completeToken(tokenId, { approved })`. The SDK equivalent is `resumeHook`:
138
149
 
139
150
  ```typescript title="app/api/refunds/[refundId]/approve/route.ts"
@@ -164,26 +175,19 @@ return { refundId, status: 'approved' };
164
175
  ```
165
176
 
166
177
  <Callout type="info">
167
- A hook is an inbound write channel. The caller that knows the token resumes the run with a typed payload. To expose in-flight state to a dashboard, write updates from a step with `getWritable()` (or `getWritable({ namespace: 'status' })`), and have the client read from the end of that named stream.
178
+ A hook is an inbound write channel. The caller that knows the token resumes the run with a typed payload. For granular business status (progress updates, current-stage messages), prefer writing to `getWritable({ namespace: 'status' })` from a step and reading from the end of the named stream on the client. Use `getRun(runId).status` for terminal/lifecycle state only.
168
179
  </Callout>
169
180
 
170
181
  ## Spawn a child workflow
171
182
 
172
- `triggerAndWait()` splits into two steps: spawn and collect. `start()` and `getRun()` are runtime APIs, so wrap them in `"use step"` functions. Return the full `Run` object from `spawnChild` so observability tooling can deep-link to the child run.
173
-
174
- ```typescript title="workflow/workflows/parent.ts"
175
- import { start } from 'workflow/api';
183
+ `triggerAndWait()` splits into spawn and collect. In v5, call `start()` directly from the workflow to spawn the child. Wrap `getRun()` and `returnValue` access in a `"use step"` function when you need to collect the result.
176
184
 
177
- async function spawnChild(item: string) {
178
- 'use step';
179
- return start(childWorkflow, [item]); // [!code highlight]
180
- }
181
- ```
185
+ You can return either the full `Run` object (enables deep-linking) or just `run.runId` (simpler). The runtime serializes `Run` to its `runId` in the event log either way.
182
186
 
183
- Await the result in a second step, then orchestrate both from the parent:
187
+ Await the result in a step, then orchestrate both from the parent:
184
188
 
185
189
  ```typescript title="workflow/workflows/parent.ts"
186
- import { getRun } from 'workflow/api';
190
+ import { getRun, start } from 'workflow/api';
187
191
 
188
192
  async function collectResult(runId: string) {
189
193
  'use step';
@@ -193,19 +197,21 @@ async function collectResult(runId: string) {
193
197
 
194
198
  export async function parentWorkflow(item: string) {
195
199
  'use workflow';
196
- const child = await spawnChild(item);
200
+ const child = await start(childWorkflow, [item]); // [!code highlight]
197
201
  return await collectResult(child.runId);
198
202
  }
199
203
  ```
200
204
 
201
- To fan out, call `spawnChild` inside a loop, then `Promise.all` the `collectResult` calls. That replaces `batch.triggerAndWait()`.
205
+ To fan out, call `start()` inside a loop, then `Promise.all` the `collectResult` calls. That replaces `batch.triggerAndWait()`.
206
+
207
+ `Promise.all` rejects on first failure; use `Promise.allSettled` if you need batch-mode error tolerance similar to trigger.dev's `{ ok, output, error }` per-run result.
202
208
 
203
209
  ## What you stop operating
204
210
 
205
211
  Dropping the trigger.dev SDK removes several moving parts:
206
212
 
207
213
  - **No task registry or `id` strings.** Workflow files carry directive annotations and export plain functions.
208
- - **No `@trigger.dev/sdk/v3` client or API key.** `start()` launches runs directly from API routes or server actions.
214
+ - **No `@trigger.dev/sdk` client or API key.** `start()` launches runs directly from API routes or server actions.
209
215
  - **No worker fleet or self-hosted instance.** The runtime schedules execution inside the app's deploy target.
210
216
  - **No separate Realtime channel.** `getWritable()` streams updates from steps over the run's durable stream.
211
217
  - **No dashboard account.** The built-in observability UI (`npx workflow web`) reads the same event log the runtime writes.
@@ -218,7 +224,7 @@ Pick one trigger.dev task and migrate it end-to-end before touching the rest. Th
218
224
 
219
225
  ### Step 1: Install the Workflow SDK
220
226
 
221
- Add the runtime. The Next.js integration ships as the `workflow/next` subpath of the same package.
227
+ Add the runtime. Framework integrations (Next.js, Nitro, Nuxt, SvelteKit, Astro, Nest) are subpath exports of the same `workflow` package, e.g. `workflow/next` no additional install needed.
222
228
 
223
229
  ```bash
224
230
  pnpm add workflow
@@ -255,10 +261,10 @@ async function loadOrder(id: string) {
255
261
 
256
262
  ### Step 4: Replace `wait.*` with hooks and `sleep`
257
263
 
258
- - `wait.for({ seconds })` / `wait.until({ date })` → `sleep('5m')` or `sleep(date)` from `workflow`.
264
+ - `wait.for({ seconds | minutes | hours | days })` / `wait.until({ date })` → `sleep('5m')` or `sleep(date)` from `workflow`.
259
265
  - `wait.forToken(token)` → `createHook({ token })` + `await`. Complete it with `resumeHook(token, payload)` from an API route.
260
266
  - `wait.forToken({ timeout })` → `Promise.race([hook, sleep(timeout)])`.
261
- - `triggerAndWait(payload)` → wrap `start(child, [payload])` in a `"use step"` function and return the `Run` object, then read the result with a second step that calls `getRun(runId).returnValue`.
267
+ - `triggerAndWait(payload)` → call `start(child, [payload])` from the workflow and return the `Run` object, then read the result with a step that calls `getRun(runId).returnValue`.
262
268
 
263
269
  ### Step 5: Start runs from the app
264
270
 
@@ -279,6 +285,29 @@ export async function POST(req: Request) {
279
285
 
280
286
  Remove the `@trigger.dev/sdk` dependency, the `trigger.config.ts` file, the `trigger/` directory, and any self-hosted worker deployment. Delete dashboard API keys from the environment. Verify the run in `npx workflow web` before shipping.
281
287
 
288
+ ## Retries on steps
289
+
290
+ Retry count lives on the step function itself. Set it as a property on the step:
291
+
292
+ ```typescript
293
+ async function chargePayment(orderId: string) {
294
+ "use step";
295
+ // ...
296
+ }
297
+ chargePayment.maxRetries = 5;
298
+ ```
299
+
300
+ Throw `new RetryableError(msg, { retryAfter: '5s' })` to control delay between attempts, or `FatalError` to stop retries immediately. See [`/docs/foundations/errors-and-retries`](/docs/foundations/errors-and-retries).
301
+
302
+ ## Features without a 1:1 equivalent
303
+
304
+ - **`schedules.task()` / cron triggers.** The SDK has no built-in scheduler. Trigger runs from Vercel Cron or a system cron calling `start()`.
305
+ - **Concurrency keys / queue concurrency limits.** No direct analog. Enforce limits inside steps (semaphores, external coordinator) or debounce at the publisher.
306
+ - **`machine` presets / custom images.** Machine specs are per-task in trigger.dev; in the SDK, function resources are per-deployment (configure via your hosting platform).
307
+ - **Realtime / `subscribeToRun`.** Use `getRun(runId).getReadable()` plus named `getWritable()` streams for live progress.
308
+ - **`onFailure` lifecycle hook.** No equivalent. Handle cleanup in the workflow body with a try/catch + compensation-stack pattern.
309
+ - **Trigger.dev dashboard.** Workflow SDK ships `npx workflow web` for local inspection and the Vercel Observability tab for deployed runs.
310
+
282
311
  ## Quick-start checklist
283
312
 
284
313
  - Replace `task({ id, run })` with a `"use workflow"` function; launch it with `start()`.
@@ -286,7 +315,7 @@ Remove the `@trigger.dev/sdk` dependency, the `trigger.config.ts` file, the `tri
286
315
  - Swap `wait.for` / `wait.until` for `sleep()` from `workflow`.
287
316
  - Swap `wait.forToken` for `createHook()` (internal) or `createWebhook()` (HTTP).
288
317
  - Model `wait.forToken` timeouts as `Promise.race()` between the hook and `sleep()`.
289
- - Replace `triggerAndWait()` with `"use step"` wrappers around `start()` and `getRun()`.
318
+ - Replace `triggerAndWait()` with direct `start()` calls and a `"use step"` wrapper around `getRun()` when collecting results.
290
319
  - Replace `batch.triggerAndWait()` with `Promise.all` over the collected child `Run` handles.
291
320
  - Move `schemaTask` validation to the call site; pass typed arguments into the workflow.
292
321
  - Replace `AbortTaskRunError` with `FatalError`; model retries per step with `RetryableError` and `maxRetries`.
@@ -294,3 +323,6 @@ Remove the `@trigger.dev/sdk` dependency, the `trigger.config.ts` file, the `tri
294
323
  - Replace `metadata.stream()` and Realtime with `getWritable()`.
295
324
  - Remove the `@trigger.dev/sdk` dependency, `trigger.config.ts`, and any self-hosted worker.
296
325
  - Deploy and verify runs end-to-end with the built-in observability UI.
326
+
327
+ ---
328
+ *Verified against `workflow@5.0.0-beta.1` and `@trigger.dev/sdk` v3 on 2026-04-16.*
@@ -0,0 +1,87 @@
1
+ ---
2
+ title: Attributes
3
+ description: Attach experimental metadata to workflow runs for observability.
4
+ type: reference
5
+ summary: Add string attributes to a workflow run.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/observability
10
+ - /docs/api-reference/workflow/experimental-set-attributes
11
+ - /docs/api-reference/workflow-errors/workflow-world-error
12
+ ---
13
+
14
+ <Callout type="warn">
15
+ This feature is experimental and may change before the stable attributes API is released.
16
+ </Callout>
17
+
18
+ [`experimental_setAttributes`](/docs/api-reference/workflow/experimental-set-attributes) attaches plaintext string metadata to the current workflow run. These attributes are displayed in observability CLI/UI.
19
+ In the future, you'll be able to search and filter runs by attributes.
20
+
21
+ You can also seed any attributes directly when starting a run:
22
+
23
+ {/* @skip-typecheck: abbreviated usage; orderWorkflow is defined below */}
24
+ ```typescript lineNumbers
25
+ const run = await start(orderWorkflow, ["ord_123"], {
26
+ attributes: { source: "checkout" }, // [!code highlight]
27
+ })
28
+ ```
29
+
30
+ ```typescript lineNumbers
31
+ import { experimental_setAttributes } from "workflow"
32
+
33
+ export async function orderWorkflow(orderId: string) {
34
+ "use workflow"
35
+
36
+ await experimental_setAttributes({ // [!code highlight]
37
+ phase: "received", // [!code highlight]
38
+ orderId, // [!code highlight]
39
+ }) // [!code highlight]
40
+
41
+ // ...work...
42
+
43
+ await experimental_setAttributes({ phase: "complete" }) // [!code highlight]
44
+ }
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ Call [`experimental_setAttributes`](/docs/api-reference/workflow/experimental-set-attributes) from a `"use workflow"` function or a `"use step"` function. Plain application code is not supported because there is no active workflow run to attach attributes to.
50
+
51
+ Values must be strings. Pass `undefined` to remove a key:
52
+
53
+ ```typescript lineNumbers
54
+ import { experimental_setAttributes } from "workflow"
55
+
56
+ export async function cleanupAttributes() {
57
+ "use workflow"
58
+
59
+ await experimental_setAttributes({ staleKey: undefined }) // [!code highlight]
60
+ }
61
+ ```
62
+
63
+ Attribute keys must be 1-256 characters, values must be strings up to 256 bytes, and each run can have up to 64 attributes. Keys that start with `$` are reserved for framework and library code.
64
+
65
+ ## Viewing attributes
66
+
67
+ The run details panel in the observability UI shows the run's current attributes as key-value rows. Reserved `$`-prefixed keys are marked with a badge and sorted after user keys:
68
+
69
+ ![Run details panel showing the Attributes card with reserved keys badged](/screenshots/attributes/run-details-attributes.png)
70
+
71
+ Each `experimental_setAttributes` call appears on the trace timeline as a diamond marker at the moment the attributes were written:
72
+
73
+ ![Trace timeline with attr_set diamond markers on the run row](/screenshots/attributes/trace-timeline.png)
74
+
75
+ Expanding an `attr_set` event — in the run sidebar or the Events tab — shows the changed keys, removed keys, and whether the write came from the workflow body or a step (with the attempt number):
76
+
77
+ ![Expanded attr_set events showing changes and the writer](/screenshots/attributes/run-details-attr-set-events.png)
78
+
79
+ ## Experimental Behavior
80
+
81
+ While attributes are experimental:
82
+
83
+ - Attributes require a World implementing spec version 4 or later.
84
+ - Writes from workflow and step bodies append native `attr_set` events and immediately materialize `run.attributes`.
85
+ - Storage errors surface rather than being silently ignored: transient errors on workflow-body writes are retried, and a write the World rejects as invalid (for example, exceeding the per-run attribute cap across multiple calls) fails the run with the validation error.
86
+ - Step-body storage errors throw from `experimental_setAttributes` like any other step-side network write. Catch the error inside the step if the attribute is best-effort.
87
+ - Reading and querying attributes is not available yet. A query API is planned.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: Observability
3
3
  description: Inspect, monitor, and debug workflows through the CLI and Web UI with powerful observability tools.
4
- type: guide
4
+ type: overview
5
5
  summary: Inspect and debug workflow runs using the CLI and Web UI.
6
6
  prerequisites:
7
7
  - /docs/foundations
@@ -42,6 +42,19 @@ npx workflow inspect runs --web
42
42
 
43
43
  ![Workflow SDK Web UI](/o11y-ui.png)
44
44
 
45
+ To share a link to a specific run without opening a browser, use the `--url`
46
+ flag. It prints the dashboard deep link to stdout and exits (no browser, no
47
+ local server) — useful for scripts, PR comments, or automation. Add `--json` to
48
+ get `{ "url": "..." }`.
49
+
50
+ ```bash
51
+ # Print the deep-link URL for a run (no browser, no server)
52
+ npx workflow inspect run <run_id> --url
53
+
54
+ # Vercel runs: add the backend (and --env preview for preview deployments)
55
+ npx workflow inspect run <run_id> --backend vercel --url
56
+ ```
57
+
45
58
  ## Backends
46
59
 
47
60
  The Workflow SDK CLI can inspect data from any [World](/docs/deploying). By default, it inspects data in your local development environment. For example, if you are using Next.js to develop workflows locally, the
@@ -63,3 +76,14 @@ npx workflow inspect runs --backend vercel
63
76
  ```
64
77
 
65
78
  When deployed to Vercel, workflow data is [encrypted end-to-end](/docs/how-it-works/encryption). Encrypted fields display as locked placeholders until you choose to decrypt them using the **Decrypt** button in the web UI or the `--decrypt` flag in the CLI.
79
+
80
+ ## More Observability Features
81
+
82
+ <Cards>
83
+ <Card href="/docs/observability/tracing" title="Tracing">
84
+ Distributed tracing with OpenTelemetry for workflow runs, steps, and queue deliveries.
85
+ </Card>
86
+ <Card href="/docs/observability/attributes" title="Attributes">
87
+ Attach experimental metadata to workflow runs for observability.
88
+ </Card>
89
+ </Cards>
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "title": "Observability",
3
- "pages": ["observability"]
3
+ "pages": ["tracing", "attributes"]
4
4
  }
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: Tracing
3
+ description: Distributed tracing with OpenTelemetry for workflow runs, steps, and queue deliveries.
4
+ type: guide
5
+ summary: Trace workflow execution end to end with OpenTelemetry.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/observability
10
+ - /docs/observability/attributes
11
+ - /docs/how-it-works/event-sourcing
12
+ ---
13
+
14
+ The Workflow SDK is instrumented with [OpenTelemetry](https://opentelemetry.io) out of the box. It emits spans for workflow starts, every workflow and step invocation, and the HTTP calls it makes to the workflow backend — and it propagates trace context across queue deliveries so a run remains traceable end to end.
15
+
16
+ The SDK only depends on the OpenTelemetry **API**, never on an SDK or exporter. If your application does not register an OpenTelemetry SDK, all tracing code is a silent no-op with no overhead and no behavior change.
17
+
18
+ ## Enabling tracing
19
+
20
+ Register any OpenTelemetry Node SDK in your application. On Vercel with Next.js, the simplest setup is [`@vercel/otel`](https://vercel.com/docs/observability/otel-overview) in `instrumentation.ts`:
21
+
22
+ ```typescript title="instrumentation.ts" lineNumbers
23
+ import { registerOTel } from "@vercel/otel"
24
+
25
+ export function register() {
26
+ registerOTel({ serviceName: "my-app" })
27
+ }
28
+ ```
29
+
30
+ No workflow-specific configuration is required. As soon as a tracer provider and propagator are registered, the SDK's spans, context propagation, and span links activate automatically.
31
+
32
+ ## Spans
33
+
34
+ | Span name | Kind | Emitted when |
35
+ | --- | --- | --- |
36
+ | `workflow.start <name>` | internal | `start()` is called in your application code |
37
+ | `workflow.execute <name>` | consumer (root) | a queue delivery invokes the workflow — replay, orchestration, and inline steps run under it |
38
+ | `step.execute <name>` | internal (inline) / consumer + root (queue-delivered) | a step function executes |
39
+ | `http <method>` | client | the SDK calls the workflow backend (event reads/writes) |
40
+
41
+ `<name>` is the short function name (for example `processOrder`); the full machine name, including the source module, is available in the `workflow.name` / `step.name` attributes.
42
+
43
+ ## Key attributes
44
+
45
+ | Attribute | Description |
46
+ | --- | --- |
47
+ | `workflow.run.id` | The run ID (`wrun_...`). Present on every workflow and step span — the primary key for finding all spans of a run. |
48
+ | `workflow.name` | The workflow function name. |
49
+ | `workflow.trace.mode` | The active trace mode (`linked` or `continuous`). |
50
+ | `workflow.trace.propagated` | Whether the invocation received trace context from the queue message. |
51
+ | `workflow.queue.overhead_ms` | Time between the message being enqueued and the handler starting — queue dwell plus any cold start. |
52
+
53
+ ## Trace shape: one trace per invocation
54
+
55
+ A single workflow run can span hours or days across many separate function invocations: every step completion, `sleep()` wake-up, and retry is a new queue delivery. Stitching all of that into one trace produces giant, slow-loading traces that most tracing backends truncate.
56
+
57
+ Instead, the SDK creates **one bounded trace per invocation**. Each `workflow.execute` (or background `step.execute`) span starts a new trace root and attaches two **span links**:
58
+
59
+ - a link to the **enqueue site** — the span that queued the message which triggered this invocation, and
60
+ - a link to the **run origin** — the trace in which `start()` was originally called.
61
+
62
+ A span link is OpenTelemetry's relationship for "causally related, but in a different trace." It is the standard pattern for asynchronous messaging, where producing and consuming a message can be separated by arbitrary time.
63
+
64
+ ```mermaid
65
+ flowchart LR
66
+ O["start() request trace"]
67
+ A["invocation 1"]
68
+ B["invocation 2"]
69
+ C["invocation 3 ..."]
70
+ A -. "link" .-> O
71
+ B -. "link" .-> O
72
+ C -. "link" .-> O
73
+ B -. "link" .-> A
74
+ C -. "link" .-> B
75
+
76
+ style O fill:#a78bfa,stroke:#8b5cf6,color:#000
77
+ ```
78
+
79
+ Each invocation links back to the trace that enqueued it and to the run origin.
80
+
81
+ To see a whole run, query by attribute rather than by trace ID — for example `workflow.run.id = wrun_...` in your tracing backend — or follow the span links between invocation traces.
82
+
83
+ ## Trace modes
84
+
85
+ The `WORKFLOW_TRACE_MODE` environment variable controls the shape:
86
+
87
+ | Mode | Behavior |
88
+ | --- | --- |
89
+ | `linked` (default) | Each invocation is its own trace root with span links to the enqueue site and the run origin. Traces stay small; sampling is decided per invocation. |
90
+ | `continuous` | The run-origin context becomes the **parent** of every invocation, so the entire run shares one trace ID. |
91
+
92
+ <Callout type="warn">
93
+ This is a behavior change from v4, which always used `continuous`-style tracing. If you have dashboards or queries that assume one trace ID per run, either update them to use `workflow.run.id` and span links, or set `WORKFLOW_TRACE_MODE=continuous` to restore the previous shape. Note that in `linked` mode each invocation root makes its own sampling decision, and the number of root spans increases to one per invocation.
94
+ </Callout>
95
+
96
+ ## Context propagation
97
+
98
+ When tracing is enabled, the SDK propagates [W3C Trace Context](https://www.w3.org/TR/trace-context/) on its outbound calls:
99
+
100
+ - **Backend requests** carry `traceparent`, `tracestate`, and `baggage` headers, so backend spans can join your trace.
101
+ - **Queue messages** carry the run-origin trace context in the message payload, and the queue re-delivers the producer's context to the workflow handler, where it becomes the enqueue-site span link.
102
+ - **Baggage** carries `workflow.run_id` and `workflow.name` entries during workflow execution, allowing downstream services you call from steps to tag their own telemetry with the run ID.
103
+
104
+ <Callout>
105
+ Baggage entries set by your application are propagated as a `baggage` HTTP header on the SDK's backend requests, like any other OpenTelemetry-instrumented HTTP call. Avoid placing sensitive values in baggage.
106
+ </Callout>
@@ -109,7 +109,7 @@ That's it. The plugin automatically:
109
109
 
110
110
  1. Transforms `"use workflow"` and `"use step"` directives via SWC
111
111
  2. Builds workflow and step bundles before tests run
112
- 3. Sets up an in-process workflow runtime using a fresh [Local World](/docs/worlds/local) instance in each test worker — all workflow data is cleared automatically between test files for full isolation
112
+ 3. Sets up an in-process workflow runtime using a fresh [Local World](/worlds/local) instance in each test worker — all workflow data is cleared automatically between test files for full isolation
113
113
 
114
114
  <Callout type="info">
115
115
  Use a separate Vitest configuration and a distinct file naming convention (e.g. `*.integration.test.ts`) to keep unit tests and integration tests separate. Unit tests run with a standard Vitest config without the workflow plugin, while integration tests use the config above.
@@ -117,7 +117,7 @@ Use a separate Vitest configuration and a distinct file naming convention (e.g.
117
117
 
118
118
  ### Writing Integration Tests
119
119
 
120
- Use [`start()`](/docs/api-reference/workflow-api/start) to trigger a workflow and [`run.returnValue`](/docs/api-reference/workflow-api/start#returnvalue) to get the result. `returnValue` is a promise that blocks until the workflow completes (or throws if it fails):
120
+ Use [`start()`](/docs/api-reference/workflow-api/start) to trigger a workflow and [`run.returnValue`](/docs/api-reference/workflow-api/start#returns) to get the result. `returnValue` is a promise that blocks until the workflow completes (or throws if it fails):
121
121
 
122
122
  ```typescript title="workflows/calculate.integration.test.ts" lineNumbers
123
123
  import { describe, it, expect } from "vitest";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "5.0.0-beta.2",
3
+ "version": "5.0.0-beta.21",
4
4
  "description": "Workflow SDK - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -29,6 +29,7 @@
29
29
  ".": {
30
30
  "types": "./dist/index.d.ts",
31
31
  "workflow": "./dist/workflow.js",
32
+ "node": "./dist/index.js",
32
33
  "require": "./dist/typescript-plugin.cjs",
33
34
  "default": "./dist/index.js"
34
35
  },
@@ -56,18 +57,18 @@
56
57
  },
57
58
  "dependencies": {
58
59
  "ms": "2.1.3",
59
- "@workflow/astro": "5.0.0-beta.2",
60
- "@workflow/core": "5.0.0-beta.2",
61
- "@workflow/errors": "5.0.0-beta.1",
62
- "@workflow/cli": "5.0.0-beta.2",
63
- "@workflow/typescript-plugin": "5.0.0-beta.2",
64
- "@workflow/next": "5.0.0-beta.2",
65
- "@workflow/utils": "5.0.0-beta.1",
66
- "@workflow/nuxt": "5.0.0-beta.2",
67
- "@workflow/nest": "5.0.0-beta.2",
68
- "@workflow/nitro": "5.0.0-beta.2",
69
- "@workflow/sveltekit": "5.0.0-beta.2",
70
- "@workflow/rollup": "5.0.0-beta.2"
60
+ "@workflow/astro": "5.0.0-beta.21",
61
+ "@workflow/cli": "5.0.0-beta.21",
62
+ "@workflow/errors": "5.0.0-beta.8",
63
+ "@workflow/typescript-plugin": "5.0.0-beta.4",
64
+ "@workflow/utils": "5.0.0-beta.4",
65
+ "@workflow/core": "5.0.0-beta.21",
66
+ "@workflow/next": "5.0.0-beta.21",
67
+ "@workflow/nest": "5.0.0-beta.21",
68
+ "@workflow/nitro": "5.0.0-beta.21",
69
+ "@workflow/nuxt": "5.0.0-beta.21",
70
+ "@workflow/sveltekit": "5.0.0-beta.21",
71
+ "@workflow/rollup": "5.0.0-beta.21"
71
72
  },
72
73
  "devDependencies": {
73
74
  "@types/ms": "2.1.0",
@@ -1,4 +0,0 @@
1
- {
2
- "title": "World SDK",
3
- "pages": ["storage", "streams", "queue", "observability"]
4
- }