workflow 5.0.0-beta.5 → 5.0.0-beta.50

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 (256) hide show
  1. package/README.md +68 -23
  2. package/dist/api-workflow.d.ts +1 -1
  3. package/dist/api-workflow.d.ts.map +1 -1
  4. package/dist/api-workflow.js +1 -1
  5. package/dist/api.d.ts +3 -3
  6. package/dist/api.d.ts.map +1 -1
  7. package/dist/api.js +5 -7
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +6 -1
  11. package/dist/internal/builtins.d.ts +20 -3
  12. package/dist/internal/builtins.d.ts.map +1 -1
  13. package/dist/internal/builtins.js +68 -4
  14. package/dist/internal/errors.d.ts +1 -1
  15. package/dist/internal/errors.d.ts.map +1 -1
  16. package/dist/internal/errors.js +2 -2
  17. package/dist/nest-builder.d.ts +2 -0
  18. package/dist/nest-builder.d.ts.map +1 -0
  19. package/dist/nest-builder.js +2 -0
  20. package/dist/nest-vercel-builder.d.ts +2 -0
  21. package/dist/nest-vercel-builder.d.ts.map +1 -0
  22. package/dist/nest-vercel-builder.js +2 -0
  23. package/dist/observability.d.ts +1 -1
  24. package/dist/observability.js +2 -2
  25. package/dist/runtime.d.ts +2 -1
  26. package/dist/runtime.d.ts.map +1 -1
  27. package/dist/runtime.js +4 -1
  28. package/docs/ai/chat-session-modeling.mdx +29 -26
  29. package/docs/ai/defining-tools.mdx +6 -7
  30. package/docs/ai/human-in-the-loop.mdx +11 -11
  31. package/docs/ai/index.mdx +50 -45
  32. package/docs/ai/message-queueing.mdx +16 -16
  33. package/docs/ai/meta.json +1 -0
  34. package/docs/ai/resumable-streams.mdx +40 -28
  35. package/docs/ai/sleep-and-delays.mdx +10 -10
  36. package/docs/ai/streaming-updates-from-tools.mdx +6 -6
  37. package/docs/api-reference/index.mdx +24 -0
  38. package/docs/api-reference/meta.json +8 -0
  39. package/docs/api-reference/vitest/index.mdx +9 -15
  40. package/docs/api-reference/workflow/create-hook.mdx +89 -10
  41. package/docs/api-reference/workflow/create-webhook.mdx +16 -15
  42. package/docs/api-reference/workflow/define-hook.mdx +35 -33
  43. package/docs/api-reference/workflow/fatal-error.mdx +30 -8
  44. package/docs/api-reference/workflow/fetch.mdx +14 -10
  45. package/docs/api-reference/workflow/get-step-metadata.mdx +2 -2
  46. package/docs/api-reference/workflow/get-workflow-metadata.mdx +3 -3
  47. package/docs/api-reference/workflow/get-writable.mdx +7 -7
  48. package/docs/api-reference/workflow/index.mdx +4 -1
  49. package/docs/api-reference/workflow/retryable-error.mdx +1 -1
  50. package/docs/api-reference/workflow/set-attributes.mdx +61 -0
  51. package/docs/api-reference/workflow/sleep.mdx +4 -4
  52. package/docs/api-reference/workflow-ai/durable-agent.mdx +48 -86
  53. package/docs/api-reference/workflow-ai/index.mdx +3 -3
  54. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +67 -24
  55. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +26 -12
  56. package/docs/api-reference/workflow-api/get-run.mdx +43 -8
  57. package/docs/api-reference/workflow-api/index.mdx +6 -10
  58. package/docs/api-reference/workflow-api/resume-hook.mdx +73 -12
  59. package/docs/api-reference/workflow-api/resume-webhook.mdx +11 -9
  60. package/docs/api-reference/workflow-api/start.mdx +60 -13
  61. package/docs/api-reference/workflow-astro/index.mdx +18 -0
  62. package/docs/api-reference/workflow-astro/meta.json +4 -0
  63. package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
  64. package/docs/api-reference/workflow-errors/entity-conflict-error.mdx +4 -4
  65. package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  66. package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +8 -8
  67. package/docs/api-reference/workflow-errors/index.mdx +88 -0
  68. package/docs/api-reference/workflow-errors/meta.json +6 -0
  69. package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +68 -0
  70. package/docs/api-reference/workflow-errors/run-expired-error.mdx +2 -2
  71. package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  72. package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +5 -5
  73. package/docs/api-reference/workflow-errors/throttle-error.mdx +2 -2
  74. package/docs/api-reference/workflow-errors/too-early-error.mdx +2 -2
  75. package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
  76. package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +5 -6
  77. package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +6 -6
  78. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +5 -5
  79. package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  80. package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +4 -4
  81. package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  82. package/docs/api-reference/workflow-errors/workflow-world-error.mdx +8 -8
  83. package/docs/api-reference/workflow-globals.mdx +14 -10
  84. package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  85. package/docs/api-reference/workflow-nest/index.mdx +31 -0
  86. package/docs/api-reference/workflow-nest/meta.json +9 -0
  87. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  88. package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  89. package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
  90. package/docs/api-reference/workflow-next/with-workflow.mdx +39 -17
  91. package/docs/api-reference/workflow-nitro/index.mdx +60 -0
  92. package/docs/api-reference/workflow-nuxt/index.mdx +48 -0
  93. package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  94. package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  95. package/docs/api-reference/workflow-observability/index.mdx +62 -0
  96. package/docs/api-reference/workflow-observability/meta.json +11 -0
  97. package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  98. package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  99. package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  100. package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  101. package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
  102. package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  103. package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +11 -14
  104. package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
  105. package/docs/api-reference/workflow-runtime/index.mdx +41 -0
  106. package/docs/api-reference/workflow-runtime/meta.json +12 -0
  107. package/docs/api-reference/workflow-runtime/set-world.mdx +51 -0
  108. package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +43 -0
  109. package/docs/api-reference/workflow-runtime/world/analytics.mdx +315 -0
  110. package/docs/api-reference/workflow-runtime/world/index.mdx +60 -0
  111. package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
  112. package/docs/api-reference/workflow-runtime/world/queue.mdx +88 -0
  113. package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +98 -34
  114. package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +8 -8
  115. package/docs/api-reference/workflow-serde/index.mdx +1 -2
  116. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +3 -4
  117. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +8 -8
  118. package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
  119. package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
  120. package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  121. package/docs/api-reference/workflow-vite/index.mdx +18 -0
  122. package/docs/api-reference/workflow-vite/meta.json +4 -0
  123. package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
  124. package/docs/changelog/attributes-mvp.mdx +380 -0
  125. package/docs/changelog/batched-event-writes.mdx +79 -0
  126. package/docs/changelog/eager-processing.mdx +110 -436
  127. package/docs/changelog/index.mdx +4 -2
  128. package/docs/changelog/lazy-event-creation.md +127 -0
  129. package/docs/changelog/lazy-hook-resume.mdx +78 -0
  130. package/docs/changelog/meta.json +11 -1
  131. package/docs/changelog/resilient-resume.mdx +32 -0
  132. package/docs/changelog/resilient-start.mdx +33 -285
  133. package/docs/changelog/step-message-ownership.mdx +360 -0
  134. package/docs/changelog/turbo-mode.md +87 -0
  135. package/docs/comparisons/index.mdx +66 -0
  136. package/docs/comparisons/meta.json +11 -0
  137. package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
  138. package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
  139. package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
  140. package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
  141. package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
  142. package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +104 -0
  143. package/docs/configuration/build-and-diagnostics.mdx +70 -0
  144. package/docs/configuration/cli-and-web-ui.mdx +241 -0
  145. package/docs/configuration/framework-options.mdx +165 -0
  146. package/docs/configuration/index.mdx +32 -0
  147. package/docs/configuration/meta.json +12 -0
  148. package/docs/configuration/runtime-tuning.mdx +376 -0
  149. package/docs/configuration/worlds.mdx +313 -0
  150. package/docs/cookbook/advanced/child-workflows.mdx +211 -264
  151. package/docs/cookbook/advanced/meta.json +6 -1
  152. package/docs/cookbook/advanced/publishing-libraries.mdx +65 -56
  153. package/docs/cookbook/advanced/serializable-steps.mdx +28 -20
  154. package/docs/cookbook/advanced/upgrading-workflows.mdx +199 -0
  155. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +27 -19
  156. package/docs/cookbook/agent-patterns/durable-agent.mdx +14 -142
  157. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +30 -22
  158. package/docs/cookbook/common-patterns/batching.mdx +18 -14
  159. package/docs/cookbook/common-patterns/idempotency.mdx +41 -53
  160. package/docs/cookbook/common-patterns/rate-limiting.mdx +8 -4
  161. package/docs/cookbook/common-patterns/saga.mdx +23 -19
  162. package/docs/cookbook/common-patterns/scheduling.mdx +34 -22
  163. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +29 -25
  164. package/docs/cookbook/common-patterns/timeouts.mdx +26 -21
  165. package/docs/cookbook/common-patterns/webhooks.mdx +10 -6
  166. package/docs/cookbook/common-patterns/workflow-composition.mdx +30 -27
  167. package/docs/cookbook/index.mdx +22 -21
  168. package/docs/cookbook/integrations/ai-sdk.mdx +85 -47
  169. package/docs/cookbook/integrations/chat-sdk.mdx +50 -33
  170. package/docs/cookbook/integrations/sandbox.mdx +62 -45
  171. package/docs/deploying.mdx +95 -0
  172. package/docs/errors/abort-signal-timeout-in-workflow.mdx +16 -12
  173. package/docs/errors/corrupted-event-log.mdx +29 -18
  174. package/docs/errors/deployment-mismatch.mdx +71 -0
  175. package/docs/errors/fetch-in-workflow.mdx +11 -7
  176. package/docs/errors/hook-conflict.mdx +69 -13
  177. package/docs/errors/index.mdx +2 -36
  178. package/docs/errors/node-js-module-in-workflow.mdx +9 -5
  179. package/docs/errors/replay-divergence.mdx +27 -0
  180. package/docs/errors/run-expired.mdx +85 -0
  181. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  182. package/docs/errors/serialization-failed.mdx +44 -12
  183. package/docs/errors/start-invalid-workflow-function.mdx +9 -5
  184. package/docs/errors/step-executed-multiple-times.mdx +23 -0
  185. package/docs/errors/step-not-registered.mdx +6 -6
  186. package/docs/errors/timeout-in-workflow.mdx +12 -8
  187. package/docs/errors/webhook-invalid-respond-with-value.mdx +18 -18
  188. package/docs/errors/webhook-response-not-sent.mdx +20 -16
  189. package/docs/errors/workflow-not-registered.mdx +5 -5
  190. package/docs/foundations/cancellation.mdx +31 -32
  191. package/docs/foundations/errors-and-retries.mdx +42 -11
  192. package/docs/foundations/hooks.mdx +64 -35
  193. package/docs/foundations/idempotency.mdx +244 -12
  194. package/docs/foundations/index.mdx +1 -23
  195. package/docs/foundations/meta.json +2 -1
  196. package/docs/foundations/serialization.mdx +21 -22
  197. package/docs/foundations/starting-workflows.mdx +106 -30
  198. package/docs/foundations/streaming.mdx +107 -59
  199. package/docs/foundations/versioning.mdx +263 -0
  200. package/docs/foundations/workflows-and-steps.mdx +9 -9
  201. package/docs/getting-started/astro.mdx +22 -18
  202. package/docs/getting-started/express.mdx +15 -11
  203. package/docs/getting-started/fastify.mdx +15 -11
  204. package/docs/getting-started/hono.mdx +15 -11
  205. package/docs/getting-started/index.mdx +10 -3
  206. package/docs/getting-started/meta.json +3 -1
  207. package/docs/getting-started/nestjs.mdx +87 -20
  208. package/docs/getting-started/next.mdx +22 -16
  209. package/docs/getting-started/nitro.mdx +22 -18
  210. package/docs/getting-started/nuxt.mdx +15 -11
  211. package/docs/getting-started/python.mdx +135 -40
  212. package/docs/getting-started/react-router/index.mdx +33 -0
  213. package/docs/getting-started/react-router/meta.json +5 -0
  214. package/docs/getting-started/react-router/v7.mdx +237 -0
  215. package/docs/getting-started/react-router/v8.mdx +232 -0
  216. package/docs/getting-started/sveltekit.mdx +20 -16
  217. package/docs/getting-started/tanstack-start.mdx +17 -13
  218. package/docs/getting-started/vite.mdx +15 -11
  219. package/docs/how-it-works/cancellation.mdx +63 -63
  220. package/docs/how-it-works/code-transform.mdx +83 -67
  221. package/docs/how-it-works/encryption.mdx +30 -26
  222. package/docs/how-it-works/event-sourcing.mdx +98 -34
  223. package/docs/how-it-works/framework-integrations.mdx +96 -337
  224. package/docs/how-it-works/understanding-directives.mdx +22 -22
  225. package/docs/internal/index.mdx +6 -4
  226. package/docs/internal/meta.json +6 -1
  227. package/docs/internal/nitro-native-build.mdx +38 -0
  228. package/docs/internal/nitro-web-ui.mdx +24 -0
  229. package/docs/internal/serializable-abort-controller.mdx +7 -7
  230. package/docs/meta.json +3 -2
  231. package/docs/observability/attributes.mdx +134 -0
  232. package/docs/observability/index.mdx +32 -10
  233. package/docs/observability/meta.json +1 -1
  234. package/docs/observability/retention.mdx +93 -0
  235. package/docs/observability/tracing.mdx +124 -0
  236. package/docs/testing/index.mdx +36 -36
  237. package/docs/testing/server-based.mdx +10 -10
  238. package/docs/whats-new.mdx +186 -0
  239. package/package.json +17 -14
  240. package/docs/api-reference/workflow-api/world/index.mdx +0 -58
  241. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  242. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
  243. package/docs/api-reference/workflow-api/world/queue.mdx +0 -86
  244. package/docs/deploying/building-a-world.mdx +0 -251
  245. package/docs/deploying/index.mdx +0 -95
  246. package/docs/deploying/meta.json +0 -4
  247. package/docs/deploying/world/local-world.mdx +0 -84
  248. package/docs/deploying/world/meta.json +0 -4
  249. package/docs/deploying/world/postgres-world.mdx +0 -224
  250. package/docs/deploying/world/vercel-world.mdx +0 -179
  251. package/docs/migration-guides/index.mdx +0 -34
  252. package/docs/migration-guides/meta.json +0 -9
  253. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -363
  254. package/docs/migration-guides/migrating-from-inngest.mdx +0 -314
  255. package/docs/migration-guides/migrating-from-temporal.mdx +0 -318
  256. package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -337
@@ -12,23 +12,31 @@ related:
12
12
  - /docs/api-reference/workflow-api/get-run
13
13
  ---
14
14
 
15
- When building chat interfaces, it's common to run into network interruptions, page refreshes, or serverless function timeouts, which can break the connection to an in-progress agent.
15
+ <Callout type="warn">
16
+ `WorkflowChatTransport` now ships in AI SDK as a 1:1 port, so import it from `@ai-sdk/workflow` (the `@workflow/ai` export is deprecated). See [Resumable Streaming with `WorkflowChatTransport`](https://ai-sdk.dev/v7/docs/agents/workflow-agent#resumable-streaming-with-workflowchattransport) for the full reference.
17
+ </Callout>
18
+
19
+ Network interruptions, page refreshes, or Vercel Functions timeouts can break a chat interface's connection to an in-progress agent.
20
+
21
+ Workflow runs and their attached streams are durable, so users can resume a stream without resending a message or waiting for the entire response again. The client can optionally sync only the data missed since the last connection.
16
22
 
17
- Where a standard chat implementation would require the user to resend their message and wait for the entire response again, workflow runs are durable, and so are the streams attached to them. This means a stream can be resumed at any point, optionally only syncing the data that was missed since the last connection.
23
+ Workflow SDK supports resumable streams, but the client must identify the stream and the position from which to reconnect. The [`WorkflowChatTransport`](/docs/api-reference/workflow-ai/workflow-chat-transport) helper is a drop-in AI SDK transport that handles this client-side resumption logic.
18
24
 
19
- Resumable streams come out of the box with Workflow SDK, however, the client needs to recognize that a stream exists, and needs to know which stream to reconnect to, and needs to know where to start from. For this, Workflow SDK provides the [`WorkflowChatTransport`](/docs/api-reference/workflow-ai/workflow-chat-transport) helper, a drop-in transport for the AI SDK that handles client-side resumption logic for you.
25
+ <Callout type="info">
26
+ When deploying a streaming route to Vercel, enable request cancellation so a browser disconnect terminates that route's abandoned stream reader instead of letting the function run until `FUNCTION_INVOCATION_TIMEOUT`. See [Avoiding Function Timeouts After Client Disconnects](/docs/foundations/streaming#avoiding-function-timeouts-after-client-disconnects).
27
+ </Callout>
20
28
 
21
29
  ## Implementing stream resumption
22
30
 
23
- Let's add stream resumption to our Flight Booking Agent that we build in the [Building Durable AI Agents](/docs/ai) guide.
31
+ Add stream resumption to the Flight Booking Agent from the [Building Durable AI Agents](/docs/ai) guide.
24
32
 
25
33
  <Steps>
26
34
 
27
35
  <Step>
28
36
 
29
- ### Return the Run ID from Your API
37
+ ### Return the run ID from your API
30
38
 
31
- Modify your chat endpoint to include the workflow run ID in a response header. The Run ID uniquely identifies the run's stream, so it allows the client to know which stream to reconnect to.
39
+ Modify your chat endpoint to include the workflow run ID in a response header. The run ID uniquely identifies the stream so the client knows which stream to reconnect to.
32
40
 
33
41
  {/*@skip-typecheck: incomplete code sample*/}
34
42
 
@@ -54,9 +62,9 @@ export async function POST(req: Request) {
54
62
 
55
63
  <Step>
56
64
 
57
- ### Add a Stream Reconnection Endpoint
65
+ ### Add a stream reconnection endpoint
58
66
 
59
- Currently we only have one API endpoint that always creates a new run, so we need to create a new API route that returns the stream for an existing run:
67
+ Create an API route that returns the stream for an existing run. The current endpoint always creates a new run.
60
68
 
61
69
  ```typescript title="app/api/chat/[id]/stream/route.ts" lineNumbers
62
70
  import { createUIMessageStreamResponse } from "ai";
@@ -92,16 +100,16 @@ export async function GET(
92
100
  }
93
101
  ```
94
102
 
95
- The `startIndex` parameter ensures the client can choose where to resume the stream from. For instance, if the function times out during streaming, the chat transport will use `startIndex` to resume the stream exactly from the last token it received. Negative values are also supported (e.g. `-5` starts 5 chunks before the end), which is useful for custom stream consumers (such as a dashboard showing recent output) that want to show the most recent output without replaying the full stream.
103
+ The `startIndex` parameter lets the client choose where to resume the stream. For example, if the function times out during streaming, the chat transport uses `startIndex` to resume from the last token it received. Negative values are also supported. A value of `-5` starts 5 chunks before the end, which is useful for custom stream consumers that show recent output without replaying the full stream.
96
104
 
97
- When using a negative `startIndex`, your stream endpoint must return a `x-workflow-stream-tail-index` header in order for relative resumption to work. Missing the header will fall back to replaying the entire stream.
105
+ When using a negative `startIndex`, your stream endpoint must return an `x-workflow-stream-tail-index` header for relative resumption. If the header is missing, the transport replays the entire stream.
98
106
  </Step>
99
107
 
100
108
  <Step>
101
109
 
102
- ### Use `WorkflowChatTransport` in the Client
110
+ ### Use `WorkflowChatTransport` in the client
103
111
 
104
- Replace the default transport in AI-SDK's `useChat` with [`WorkflowChatTransport`](
112
+ Replace the default transport in AI SDK's `useChat` with [`WorkflowChatTransport`](
105
113
  /docs/api-reference/workflow-ai/workflow-chat-transport
106
114
  ), and update the callbacks to store and use the latest run ID. For now, we'll store the run ID in localStorage. For your own app, this would be stored wherever you store session information.
107
115
 
@@ -109,7 +117,7 @@ Replace the default transport in AI-SDK's `useChat` with [`WorkflowChatTransport
109
117
  "use client";
110
118
 
111
119
  import { useChat } from "@ai-sdk/react";
112
- import { WorkflowChatTransport } from "@workflow/ai"; // [!code highlight]
120
+ import { WorkflowChatTransport } from "@ai-sdk/workflow"; // [!code highlight]
113
121
  import { useMemo, useState } from "react";
114
122
 
115
123
  export default function ChatPage() {
@@ -158,23 +166,23 @@ export default function ChatPage() {
158
166
 
159
167
  </Steps>
160
168
 
161
- Now try the flight booking example again. Open it up in a separate tab, or spam the refresh button, and see how the client connects to the same chat stream every time.
169
+ Open the flight booking example in another tab or refresh the page repeatedly. The client reconnects to the same chat stream each time.
162
170
 
163
- ## How It Works
171
+ ## How it works
164
172
 
165
- 1. When the user sends a message, `WorkflowChatTransport` makes a POST to `/api/chat`
166
- 2. The API starts a workflow and returns the run ID in the `x-workflow-run-id` header
167
- 3. `onChatSendMessage` stores this run ID in localStorage
168
- 4. If the stream is interrupted before receiving a "finish" chunk, the transport automatically reconnects
169
- 5. `prepareReconnectToStreamRequest` builds the reconnection URL using the stored run ID, pointing to the new endpoint `/api/chat/{runId}/stream`
170
- 6. The reconnection endpoint returns the stream from where the client left off
171
- 7. When the stream completes, `onChatEnd` clears the stored run ID
173
+ 1. When the user sends a message, `WorkflowChatTransport` makes a `POST` request to `/api/chat`.
174
+ 2. The API starts a workflow and returns the run ID in the `x-workflow-run-id` header.
175
+ 3. `onChatSendMessage` stores this run ID in `localStorage`.
176
+ 4. If the stream is interrupted before receiving a `finish` chunk, the transport automatically reconnects.
177
+ 5. `prepareReconnectToStreamRequest` builds the reconnection URL using the stored run ID and points to `/api/chat/{runId}/stream`.
178
+ 6. The reconnection endpoint returns the stream from where the client left off.
179
+ 7. When the stream completes, `onChatEnd` clears the stored run ID.
172
180
 
173
- This approach also handles page refreshes, as the client will automatically reconnect to the stream from the last known position when the UI loads with a stored run ID, following the behavior of [AI SDK's stream resumption](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-resume-streams#chatbot-resume-streams).
181
+ This approach also handles page refreshes, as the client will automatically reconnect to the stream from the last known position when the user interface (UI) loads with a stored run ID, following the behavior of [AI SDK's stream resumption](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-resume-streams#chatbot-resume-streams).
174
182
 
175
183
  ### Resuming from the end of the stream
176
184
 
177
- By default, reconnecting replays the entire stream from the beginning (`startIndex: 0`). If you only need to show recent output for example, when resuming a long conversation after a page refresh you can set `initialStartIndex` to a negative value to read from the end of the stream instead:
185
+ By default, reconnecting replays the entire stream from the beginning (`startIndex: 0`). If you only need to show recent output (for example, when resuming a long conversation after a page refresh), you can set `initialStartIndex` to a negative value to read from the end of the stream instead:
178
186
 
179
187
  {/*@skip-typecheck: incomplete code sample*/}
180
188
 
@@ -194,8 +202,12 @@ This avoids replaying potentially thousands of chunks and lets the UI render fas
194
202
  When using a negative `initialStartIndex`, the reconnection endpoint **must** return the `x-workflow-stream-tail-index` header (as shown in [Step 2](#add-a-stream-reconnection-endpoint) above). The transport uses this header to compute absolute chunk positions so that retries after a disconnect resume from the correct position. If the header is missing, the transport falls back to `startIndex: 0` (replaying the entire stream) and logs a warning.
195
203
  </Callout>
196
204
 
197
- ## Related Documentation
205
+ ### Mid-part resumes
206
+
207
+ A workflow stream is a flat sequence of chunks, but the AI SDK's UI protocol groups chunks into logical parts (`text-*`, `reasoning-*`, `tool-input-*`) that must be opened with a `*-start` before any `*-delta` or `*-end`. A non-zero `startIndex` can land in the middle of an open part. See [`WorkflowChatTransport` → Mid-part resumes](/docs/api-reference/workflow-ai/workflow-chat-transport#mid-part-resumes) for how this is handled and an example of rewinding to a step boundary on the server.
208
+
209
+ ## Related documentation
198
210
 
199
- - [`WorkflowChatTransport` API Reference](/docs/api-reference/workflow-ai/workflow-chat-transport) - Full configuration options
200
- - [Streaming](/docs/foundations/streaming) - Understanding workflow streams
201
- - [`getRun()` API Reference](/docs/api-reference/workflow-api/get-run) - Retrieving existing runs
211
+ - [`WorkflowChatTransport` API reference](/docs/api-reference/workflow-ai/workflow-chat-transport): Full configuration options
212
+ - [Streaming](/docs/foundations/streaming): Understanding workflow streams
213
+ - [`getRun()` API reference](/docs/api-reference/workflow-api/get-run): Retrieving existing runs
@@ -12,23 +12,23 @@ related:
12
12
  - /docs/api-reference/workflow/sleep
13
13
  ---
14
14
 
15
- AI agents sometimes need to pause execution in order to schedule recurring or future actions, wait before retrying an operation (e.g. for rate limiting), or wait for external state to be available.
15
+ AI agents sometimes need to pause execution to schedule recurring or future actions, wait before retrying an operation (e.g. for rate limiting), or wait for external state to be available.
16
16
 
17
- Workflow SDK's `sleep` function enables Agents to pause execution without consuming resources, and resume at a specified time, after a specified duration, or in response to an external event. Workflow operation that suspend will survive restarts, new deploys, and infrastructure changes, independent of whether the suspense takes seconds or months.
17
+ Workflow SDK's `sleep` function enables Agents to pause execution without consuming resources, and resume at a specified time, after a specified duration, or in response to an external event. Workflow operations that suspend will survive restarts, new deploys, and infrastructure changes, independent of whether the suspense takes seconds or months.
18
18
 
19
19
  <Callout type="info">
20
20
  See the [`sleep()` API Reference](/docs/api-reference/workflow/sleep) for the full list of supported duration formats and detailed API documentation, and see the [hooks](/docs/foundations/hooks) documentation for more information on how to resume in response to external events.
21
21
  </Callout>
22
22
 
23
- ## Adding a Sleep Tool
23
+ ## Adding a sleep tool
24
24
 
25
- Sleep is a built-in function in Workflow SDK, so exposing it as a tool is as simple as wrapping it in a tool definition. Learn more about how to define tools in [Patterns for Defining Tools](/docs/ai/defining-tools).
25
+ Sleep is a built-in function in Workflow SDK. To expose it as a tool, wrap it in a tool definition. Learn more about how to define tools in [Patterns for Defining Tools](/docs/ai/defining-tools).
26
26
 
27
27
  <Steps>
28
28
 
29
29
  <Step>
30
30
 
31
- ### Define the Tool
31
+ ### Define the tool
32
32
 
33
33
  Add a new "sleep" tool to the `tools` defined in `workflows/chat/steps/tools.ts`:
34
34
 
@@ -60,7 +60,7 @@ export const flightBookingTools = {
60
60
  ```
61
61
 
62
62
  <Callout type="info">
63
- Note that the `sleep()` function must be called from within a workflow context, not from within a step. This is why `executeSleep` does not have `"use step"` - it runs in the workflow context where `sleep()` is available.
63
+ Call `sleep()` from within a workflow context, not from within a step. `executeSleep` does not have `"use step"` because it runs in the workflow context where `sleep()` is available.
64
64
  </Callout>
65
65
 
66
66
  This already makes the full sleep functionality available to the Agent!
@@ -71,7 +71,7 @@ export const flightBookingTools = {
71
71
 
72
72
  ### Show the tool status in the UI
73
73
 
74
- To round it off, extend the UI to display the tool call status. This can be done either by displaying the tool call information directly, or by emitting custom data parts to the stream (see [Streaming Updates from Tools](/docs/ai/streaming-updates-from-tools) for more details). In this case, since there aren't any fine-grained progress updates to show, we'll just display the tool call information directly:
74
+ To round it off, extend the UI to display the tool call status. This can be done either by displaying the tool call information directly, or by emitting custom data parts to the stream (see [Streaming Updates from Tools](/docs/ai/streaming-updates-from-tools) for more details). Since there aren't any fine-grained progress updates to show, we'll display the tool call information directly:
75
75
 
76
76
  {/*@skip-typecheck: incomplete code sample*/}
77
77
 
@@ -153,11 +153,11 @@ function renderToolOutput(part: any) {
153
153
 
154
154
  Now, try out the Flight Booking Agent again, and ask it to sleep for 10 seconds before checking any flight. You'll see the agent pause, and the UI reflect the tool call status.
155
155
 
156
- ## Use Cases
156
+ ## Use cases
157
157
 
158
158
  Aside from providing `sleep()` as a tool, there are other use cases for Agents that commonly call for suspension and resumption.
159
159
 
160
- ### Rate Limiting
160
+ ### Rate limiting
161
161
 
162
162
  When hitting API rate limits, use `RetryableError` with a delay:
163
163
 
@@ -180,7 +180,7 @@ async function callRateLimitedAPI(endpoint: string) {
180
180
  }
181
181
  ```
182
182
 
183
- ## Related Documentation
183
+ ## Related documentation
184
184
 
185
185
  - [`sleep()` API Reference](/docs/api-reference/workflow/sleep) - Full API documentation with all duration formats
186
186
  - [Workflows and Steps](/docs/foundations/workflows-and-steps) - Understanding workflow context
@@ -16,13 +16,13 @@ After [building a durable AI agent](/docs/ai), we already get UI message chunks
16
16
 
17
17
  Workflow SDK enables this by letting step functions write custom chunks to the same stream the agent uses. These chunks appear as data parts in your messages, which you can render however you like.
18
18
 
19
- As an example, we'll extend out Flight Booking Agent to use emit more granular progress updates while searching for flights.
19
+ As an example, we'll extend our Flight Booking Agent to emit more granular progress updates while searching for flights.
20
20
 
21
21
  <Steps>
22
22
 
23
23
  <Step>
24
24
 
25
- ### Define Your Data Part Type
25
+ ### Define your data part type
26
26
 
27
27
  First, define a TypeScript type for your custom data part. This ensures type safety across your tool and client code:
28
28
 
@@ -44,9 +44,9 @@ The `type` field must be a string starting with `data-` followed by your custom
44
44
 
45
45
  <Step>
46
46
 
47
- ### Emit Updates from Your Tool
47
+ ### Emit updates from your tool
48
48
 
49
- Use [`getWritable()`](/docs/api-reference/workflow/get-writable) inside a step function to get a handle to the stream. This is the same stream that the LLM and other tools calls are writing to, so we can inject out own data packets directly.
49
+ Use [`getWritable()`](/docs/api-reference/workflow/get-writable) inside a step function to get a handle to the stream. This is the same stream that the LLM and other tool calls are writing to, so we can inject our own data packets directly.
50
50
 
51
51
  {/* @skip-typecheck: incomplete code sample */}
52
52
  ```typescript title="workflows/chat/steps/tools.ts" lineNumbers
@@ -95,7 +95,7 @@ Key points:
95
95
  </Step>
96
96
 
97
97
  <Step>
98
- ### Handle Data Parts in the Client
98
+ ### Handle data parts in the client
99
99
 
100
100
  Update your chat component to detect and render the custom data parts. Data parts are stored in the message's `parts` array alongside text and tool invocation parts:
101
101
 
@@ -143,7 +143,7 @@ The pattern is:
143
143
 
144
144
  Now, when you run the agent to search for flights, you'll see the flight results pop up one after another. This will be most useful if you have tool calls that take minutes to complete, and you need to show granular progress updates to the user.
145
145
 
146
- ## Related Documentation
146
+ ## Related documentation
147
147
 
148
148
  - [Building Durable AI Agents](/docs/ai) - Complete guide to durable agents
149
149
  - [`getWritable()` API Reference](/docs/api-reference/workflow/get-writable) - Stream API details
@@ -17,9 +17,33 @@ All the functions and primitives that come with Workflow SDK by package.
17
17
  <Card title="workflow/api" href="/docs/api-reference/workflow-api">
18
18
  API reference for runtime functions from the `workflow/api` package.
19
19
  </Card>
20
+ <Card title="workflow/runtime" href="/docs/api-reference/workflow-runtime">
21
+ Runtime functions for resolving the World instance and the low-level World SDK, including storage and analytics queries.
22
+ </Card>
23
+ <Card title="workflow/observability" href="/docs/api-reference/workflow-observability">
24
+ Utilities to hydrate step I/O, parse display names, and decrypt workflow data.
25
+ </Card>
20
26
  <Card title="workflow/next" href="/docs/api-reference/workflow-next">
21
27
  Next.js integration for Workflow SDK that automatically configures bundling and runtime support.
22
28
  </Card>
29
+ <Card title="workflow/nitro" href="/docs/api-reference/workflow-nitro">
30
+ Nitro module for workflow bundling and runtime support.
31
+ </Card>
32
+ <Card title="workflow/nuxt" href="/docs/api-reference/workflow-nuxt">
33
+ Nuxt module for workflow bundling and runtime support.
34
+ </Card>
35
+ <Card title="workflow/sveltekit" href="/docs/api-reference/workflow-sveltekit">
36
+ SvelteKit Vite plugin for workflow bundling and runtime support.
37
+ </Card>
38
+ <Card title="workflow/astro" href="/docs/api-reference/workflow-astro">
39
+ Astro integration for workflow bundling and runtime support.
40
+ </Card>
41
+ <Card title="workflow/vite" href="/docs/api-reference/workflow-vite">
42
+ Standalone Vite plugin for workflow bundling and runtime support.
43
+ </Card>
44
+ <Card title="workflow/nest" href="/docs/api-reference/workflow-nest">
45
+ NestJS module for workflow bundling and runtime support.
46
+ </Card>
23
47
  <Card title="workflow/errors" href="/docs/api-reference/workflow-errors">
24
48
  Semantic error types for handling workflow storage backend failures.
25
49
  </Card>
@@ -4,7 +4,15 @@
4
4
  "workflow-globals",
5
5
  "workflow",
6
6
  "workflow-api",
7
+ "workflow-runtime",
8
+ "workflow-observability",
7
9
  "workflow-next",
10
+ "workflow-nitro",
11
+ "workflow-nuxt",
12
+ "workflow-sveltekit",
13
+ "workflow-astro",
14
+ "workflow-vite",
15
+ "workflow-nest",
8
16
  "workflow-errors",
9
17
  "workflow-serde",
10
18
  "workflow-ai",
@@ -3,7 +3,7 @@ title: "@workflow/vitest"
3
3
  description: Vitest plugin and test helpers for integration testing workflows in-process.
4
4
  ---
5
5
 
6
- The `@workflow/vitest` package provides a Vitest plugin and test helpers for running full workflow integration tests in-process no server required.
6
+ The `@workflow/vitest` package provides a Vitest plugin and test helpers for running full workflow integration tests in-process, no server required.
7
7
 
8
8
  ## Plugin
9
9
 
@@ -11,7 +11,6 @@ The `@workflow/vitest` package provides a Vitest plugin and test helpers for run
11
11
 
12
12
  Returns a Vite plugin array that handles SWC transforms, bundle building, and in-process handler registration automatically.
13
13
 
14
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
15
14
 
16
15
  ```typescript
17
16
  import { defineConfig } from "vitest/config";
@@ -22,9 +21,8 @@ export default defineConfig({
22
21
  });
23
22
  ```
24
23
 
25
- Pass a [`WorkflowTestOptions`](#workflowtestoptions) object when your project uses a non-standard layout for example, a monorepo where `workflows/` does not live at the Vitest config's directory, or when the default `.workflow-data` / `.workflow-vitest` output locations need to move. The plugin forwards these paths to `buildWorkflowTests()` and `setupWorkflowTests()` through Vitest's per-project provided context, so each Vitest workspace project stays isolated.
24
+ Pass a [`WorkflowTestOptions`](#workflowtestoptions) object when your project uses a non-standard layout, for example, a monorepo where `workflows/` does not live at the Vitest config's directory, or when the default `.workflow-data` / `.workflow-vitest` output locations need to move. The plugin forwards these paths to `buildWorkflowTests()` and `setupWorkflowTests()` through Vitest's per-project provided context, so each Vitest workspace project stays isolated.
26
25
 
27
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
28
26
 
29
27
  ```typescript
30
28
  import { defineConfig } from "vitest/config";
@@ -48,13 +46,12 @@ export default defineConfig({
48
46
 
49
47
  **Returns:** `Plugin[]`
50
48
 
51
- ## Setup Functions
49
+ ## Setup functions
52
50
 
53
51
  ### `buildWorkflowTests()`
54
52
 
55
53
  Builds workflow and step bundles to disk. Called automatically by the `workflow()` plugin in `globalSetup`. Use directly only for [manual setup](/docs/testing#manual-setup).
56
54
 
57
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
58
55
 
59
56
  ```typescript
60
57
  import { buildWorkflowTests } from "@workflow/vitest";
@@ -72,11 +69,10 @@ export async function setup() {
72
69
 
73
70
  ### `setupWorkflowTests()`
74
71
 
75
- Sets up an in-process workflow runtime in each test worker. Imports pre-built bundles, creates a [Local World](/docs/worlds/local) instance with direct handlers, and sets it as the global world. Clears all workflow data on each invocation for full test isolation.
72
+ Sets up an in-process workflow runtime in each test worker. Imports pre-built bundles, creates a [Local World](/worlds/local) instance with direct handlers, and sets it as the global world. Clears all workflow data on each invocation for full test isolation.
76
73
 
77
74
  Called automatically by the `workflow()` plugin in `setupFiles`. Use directly only for [manual setup](/docs/testing#manual-setup).
78
75
 
79
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
80
76
 
81
77
  ```typescript
82
78
  import { beforeAll, afterAll } from "vitest";
@@ -112,13 +108,12 @@ Tears down the workflow test world. Clears the global world and closes the Local
112
108
  | `dataDir` | `string` | `<rootDir>/.workflow-data` | Directory for workflow runtime data written by the test world. Relative paths resolve against `cwd`. |
113
109
  | `outDir` | `string` | `<rootDir>/.workflow-vitest` | Directory for generated workflow and step bundles. Relative paths resolve against `cwd`. |
114
110
 
115
- ## Test Helpers
111
+ ## Test helpers
116
112
 
117
113
  ### `waitForSleep()`
118
114
 
119
- Polls the event log until the workflow has a pending `sleep()` call one with a `wait_created` event but no corresponding `wait_completed` event. Returns the correlation ID of the pending sleep, which can be passed to [`wakeUp()`](/docs/api-reference/workflow-api/get-run) to target a specific sleep.
115
+ Polls the event log until the workflow has a pending `sleep()` call, one with a `wait_created` event but no corresponding `wait_completed` event. Returns the correlation ID of the pending sleep, which can be passed to [`wakeUp()`](/docs/api-reference/workflow-api/get-run) to target a specific sleep.
120
116
 
121
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
122
117
 
123
118
  ```typescript
124
119
  import { waitForSleep } from "@workflow/vitest"; // [!code highlight]
@@ -136,9 +131,9 @@ await getRun(run.runId).wakeUp({ correlationIds: [sleepId] }); // [!code highlig
136
131
  | `run` | `Run<any>` | The workflow run to monitor |
137
132
  | `options?` | `WaitOptions` | Polling and timeout configuration |
138
133
 
139
- **Returns:** `Promise<string>` The correlation ID of the first pending sleep. Pass this to `wakeUp({ correlationIds: [id] })` to target a specific sleep.
134
+ **Returns:** `Promise<string>`, the correlation ID of the first pending sleep. Pass this to `wakeUp({ correlationIds: [id] })` to target a specific sleep.
140
135
 
141
- #### Behavior with Multiple Sleeps
136
+ #### Behavior with multiple sleeps
142
137
 
143
138
  - **Sequential sleeps**: `waitForSleep()` returns each sleep as the workflow reaches it. After waking one, call `waitForSleep()` again for the next.
144
139
  - **Parallel sleeps**: `waitForSleep()` returns whichever pending sleep is found first. After waking it, call `waitForSleep()` again to get the next one.
@@ -147,7 +142,6 @@ await getRun(run.runId).wakeUp({ correlationIds: [sleepId] }); // [!code highlig
147
142
 
148
143
  Polls the hook list and event log until a hook matching the optional `token` filter exists that hasn't been received yet. Returns the matching hook object.
149
144
 
150
- {/* @skip-typecheck - @workflow/vitest not available in docs-typecheck */}
151
145
 
152
146
  ```typescript
153
147
  import { waitForHook } from "@workflow/vitest"; // [!code highlight]
@@ -165,7 +159,7 @@ await resumeHook(hook.token, { approved: true }); // [!code highlight]
165
159
  | `run` | `Run<any>` | The workflow run to monitor |
166
160
  | `options?` | `WaitOptions & { token?: string }` | Polling, timeout, and optional token filter |
167
161
 
168
- **Returns:** `Promise<Hook>` The first pending hook matching the filter. The hook object includes `token`, `hookId`, and `runId`.
162
+ **Returns:** `Promise<Hook>`, the first pending hook matching the filter. The hook object includes `token`, `hookId`, and `runId`.
169
163
 
170
164
  ### `WaitOptions`
171
165
 
@@ -8,6 +8,7 @@ prerequisites:
8
8
  related:
9
9
  - /docs/api-reference/workflow/define-hook
10
10
  - /docs/api-reference/workflow/create-webhook
11
+ - /docs/foundations/idempotency
11
12
  ---
12
13
 
13
14
  Creates a low-level hook primitive that can be used to resume a workflow run with arbitrary payloads.
@@ -25,7 +26,7 @@ export async function hookWorkflow() {
25
26
  }
26
27
  ```
27
28
 
28
- ## API Signature
29
+ ## API signature
29
30
 
30
31
  ### Parameters
31
32
 
@@ -65,9 +66,11 @@ export default Hook;`}
65
66
 
66
67
  The returned `Hook` object also implements `AsyncIterable<T>`, which allows you to iterate over incoming payloads using `for await...of` syntax.
67
68
 
69
+ Use `hook.getConflict()` to check whether the hook token is already claimed by another hook, including one kept reserved after its run ends, without waiting for hook payload data. Calling `createHook()` on its own does not register the hook: registration is only committed when the workflow suspends. Awaiting `hook.getConflict()` suspends the workflow to commit the registration, then resolves with `null` once `hook_created` is recorded, or with the conflicting [`Run`](/docs/api-reference/workflow-api/get-run).
70
+
68
71
  ## Examples
69
72
 
70
- ### Basic Usage
73
+ ### Basic usage
71
74
 
72
75
  When creating a hook, you can specify a payload type for automatic type safety:
73
76
 
@@ -88,7 +91,7 @@ export async function approvalWorkflow() {
88
91
  }
89
92
  ```
90
93
 
91
- ### Customizing Tokens
94
+ ### Customizing tokens
92
95
 
93
96
  Tokens are used to identify a specific hook. You can customize the token to be more specific to a use case.
94
97
 
@@ -112,7 +115,82 @@ export async function slackBotWorkflow(channelId: string) {
112
115
  }
113
116
  ```
114
117
 
115
- ### Waiting for Multiple Payloads
118
+ ### Detecting token conflicts
119
+
120
+ Use `hook.getConflict()` when the workflow needs to claim a hook token before doing other work, but does not need a payload yet:
121
+
122
+ ```typescript lineNumbers
123
+ import { createHook } from "workflow";
124
+
125
+ declare function chargeOrder(orderId: string): Promise<void>; // @setup
126
+
127
+ async function processOrder(orderId: string) {
128
+ "use workflow";
129
+
130
+ using hook = createHook({ // [!code highlight]
131
+ token: `order:${orderId}` // [!code highlight]
132
+ }); // [!code highlight]
133
+
134
+ const conflict = await hook.getConflict(); // [!code highlight]
135
+ if (conflict) { // [!code highlight]
136
+ // Another active workflow run already owns this token.
137
+ return { dedupedTo: conflict.runId };
138
+ }
139
+
140
+ await chargeOrder(orderId);
141
+ }
142
+ ```
143
+
144
+ Because `createHook()` alone does not suspend the workflow, awaiting `hook.getConflict()` is what actually suspends the run and commits the hook registration. It only waits for registration. To receive payload data from a future `resumeHook()` call, await the hook itself or iterate it with `for await...of`.
145
+
146
+ On a conflict, the resolved value is a `Run` handle for the run that owns the token, with durable step-backed accessors. The duplicate run can decide in code how to handle it: return or log `conflict.runId`, inspect `await conflict.status`, wait on `await conflict.returnValue`, or cancel the owner with `await conflict.cancel()` and continue in the current run. See [Run idempotency](/docs/foundations/idempotency#run-idempotency) for these strategies in context.
147
+
148
+ <Callout type="info">
149
+ Custom hook tokens are the recommended way to coordinate active workflow runs. Use a deterministic token from your domain, such as an order ID or conversation ID, create the hook near the beginning of the workflow, and check `await hook.getConflict()` before work that depends on owning the token. See [Run idempotency](/docs/foundations/idempotency#run-idempotency).
150
+ </Callout>
151
+
152
+ ### Keep a token unavailable after the run ends
153
+
154
+ By default, another Hook can use the token after its workflow ends. Set `experimental_minRetention` to keep the token unavailable for at least a specific time after `createHook()` runs:
155
+
156
+ ```typescript lineNumbers
157
+ import { createHook } from "workflow";
158
+
159
+ declare function processOwnedOrder(orderId: string): Promise<void>; // @setup
160
+
161
+ export async function processOrder(orderId: string) {
162
+ "use workflow";
163
+
164
+ const hook = createHook({ // [!code highlight]
165
+ token: `order:${orderId}`, // [!code highlight]
166
+ experimental_minRetention: "30d", // [!code highlight]
167
+ }); // [!code highlight]
168
+
169
+ const conflict = await hook.getConflict();
170
+ if (conflict) {
171
+ return { status: "duplicate" as const, runId: conflict.runId };
172
+ }
173
+
174
+ await processOwnedOrder(orderId);
175
+ return { status: "processed" as const };
176
+ }
177
+ ```
178
+
179
+ `experimental_minRetention` accepts the same values as [`sleep()`](/docs/api-reference/workflow/sleep): a duration string such as `"30d"`, a number of milliseconds, or an absolute `Date`. Durations start when `createHook()` runs.
180
+
181
+ The Hook remains active until the workflow ends, even if the configured time passes first. Another Hook can use the token only after both the workflow has ended and the configured time has passed. For example, `"30d"` keeps the token unavailable for 29 more days if the workflow ends after 1 day. A workflow that runs for more than 30 days releases the token when it ends.
182
+
183
+ After the workflow ends, [`getHookByToken()`](/docs/api-reference/workflow-api/get-hook-by-token) can still find the Hook until retention ends, but the Hook cannot be resumed.
184
+
185
+ <Callout type="warn">
186
+ `using` auto-disposes the Hook at scope exit, which releases the token immediately and defeats `experimental_minRetention`. Declare retained Hooks with `const` and let the runtime clean them up when the run ends.
187
+ </Callout>
188
+
189
+ <Callout type="warn">
190
+ This option is experimental. Worlds can limit how long tokens are retained; see [World configuration](/docs/configuration/worlds) for each World's limit. If the configured World does not support minimum retention, the workflow fails when registering the Hook. `createWebhook()` does not accept this option.
191
+ </Callout>
192
+
193
+ ### Waiting for multiple payloads
116
194
 
117
195
  You can also wait for multiple payloads by using the `for await...of` syntax.
118
196
 
@@ -135,7 +213,7 @@ export async function collectHookWorkflow() {
135
213
  }
136
214
  ```
137
215
 
138
- ### Disposing Hooks Early
216
+ ### Disposing hooks early
139
217
 
140
218
  You can dispose a hook early to release its token for reuse by another workflow. This is useful for handoff patterns where one workflow needs to transfer a hook token to another workflow while still running.
141
219
 
@@ -164,7 +242,7 @@ export async function handoffWorkflow(channelId: string) {
164
242
 
165
243
  After calling `dispose()`, the hook will no longer receive events and its token becomes available for other workflows to use.
166
244
 
167
- ### Automatic Disposal with `using`
245
+ ### Automatic disposal with `using`
168
246
 
169
247
  Hooks implement the [TC39 Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) proposal, allowing automatic disposal with the `using` keyword:
170
248
 
@@ -190,8 +268,9 @@ export async function scopedHookWorkflow(channelId: string) {
190
268
 
191
269
  This is equivalent to manually calling `dispose()` but ensures the hook is always cleaned up, even if an error occurs.
192
270
 
193
- ## Related Functions
271
+ ## Related functions
194
272
 
195
- - [`defineHook()`](/docs/api-reference/workflow/define-hook) - Type-safe hook helper
196
- - [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) - Resume a hook with a payload
197
- - [`createWebhook()`](/docs/api-reference/workflow/create-webhook) - Higher-level HTTP webhook abstraction
273
+ - [`defineHook()`](/docs/api-reference/workflow/define-hook): Type-safe hook helper
274
+ - [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook): Resume a hook with a payload
275
+ - [`createWebhook()`](/docs/api-reference/workflow/create-webhook): Higher-level HTTP webhook abstraction
276
+ - [Idempotency](/docs/foundations/idempotency): Deduplicate step side effects and workflow starts