workflow 5.0.0-beta.43 → 5.0.0-beta.44
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.
- package/README.md +6 -4
- package/dist/internal/builtins.d.ts +4 -4
- package/dist/internal/builtins.js +6 -6
- package/docs/ai/chat-session-modeling.mdx +23 -24
- package/docs/ai/defining-tools.mdx +5 -6
- package/docs/ai/human-in-the-loop.mdx +11 -11
- package/docs/ai/index.mdx +20 -20
- package/docs/ai/message-queueing.mdx +6 -6
- package/docs/ai/meta.json +1 -0
- package/docs/ai/resumable-streams.mdx +28 -28
- package/docs/ai/sleep-and-delays.mdx +9 -9
- package/docs/ai/streaming-updates-from-tools.mdx +4 -4
- package/docs/api-reference/vitest/index.mdx +8 -8
- package/docs/api-reference/workflow/create-hook.mdx +15 -15
- package/docs/api-reference/workflow/create-webhook.mdx +15 -15
- package/docs/api-reference/workflow/define-hook.mdx +10 -10
- package/docs/api-reference/workflow/fatal-error.mdx +2 -2
- package/docs/api-reference/workflow/fetch.mdx +7 -7
- package/docs/api-reference/workflow/get-step-metadata.mdx +2 -2
- package/docs/api-reference/workflow/get-workflow-metadata.mdx +3 -3
- package/docs/api-reference/workflow/get-writable.mdx +7 -7
- package/docs/api-reference/workflow/index.mdx +1 -1
- package/docs/api-reference/workflow/retryable-error.mdx +1 -1
- package/docs/api-reference/workflow/set-attributes.mdx +2 -2
- package/docs/api-reference/workflow/sleep.mdx +3 -3
- package/docs/api-reference/workflow-ai/durable-agent.mdx +42 -42
- package/docs/api-reference/workflow-ai/index.mdx +3 -3
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +28 -28
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +11 -11
- package/docs/api-reference/workflow-api/get-run.mdx +10 -10
- package/docs/api-reference/workflow-api/index.mdx +2 -4
- package/docs/api-reference/workflow-api/resume-hook.mdx +14 -14
- package/docs/api-reference/workflow-api/resume-webhook.mdx +3 -3
- package/docs/api-reference/workflow-api/start.mdx +16 -15
- package/docs/api-reference/workflow-astro/workflow.mdx +3 -3
- package/docs/api-reference/workflow-errors/entity-conflict-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +8 -8
- package/docs/api-reference/workflow-errors/index.mdx +6 -6
- package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +9 -9
- package/docs/api-reference/workflow-errors/run-expired-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +5 -5
- package/docs/api-reference/workflow-errors/throttle-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/too-early-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/workflow-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +5 -6
- package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +6 -6
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +5 -5
- package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/workflow-world-error.mdx +8 -8
- package/docs/api-reference/workflow-globals.mdx +12 -11
- package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +2 -2
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +4 -4
- package/docs/api-reference/workflow-nest/workflow-controller.mdx +1 -1
- package/docs/api-reference/workflow-nest/workflow-module.mdx +4 -4
- package/docs/api-reference/workflow-next/with-workflow.mdx +16 -16
- package/docs/api-reference/workflow-nitro/index.mdx +6 -6
- package/docs/api-reference/workflow-nuxt/index.mdx +4 -4
- package/docs/api-reference/workflow-observability/hydrate-data.mdx +5 -5
- package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +4 -4
- package/docs/api-reference/workflow-observability/index.mdx +6 -8
- package/docs/api-reference/workflow-observability/observability-revivers.mdx +2 -2
- package/docs/api-reference/workflow-observability/parse-class-name.mdx +3 -3
- package/docs/api-reference/workflow-observability/parse-step-name.mdx +4 -4
- package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +4 -4
- package/docs/api-reference/workflow-runtime/create-world.mdx +8 -8
- package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +6 -6
- package/docs/api-reference/workflow-runtime/get-world.mdx +4 -4
- package/docs/api-reference/workflow-runtime/health-check.mdx +1 -1
- package/docs/api-reference/workflow-runtime/index.mdx +2 -4
- package/docs/api-reference/workflow-runtime/set-world.mdx +15 -13
- package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +6 -5
- package/docs/api-reference/workflow-runtime/world/analytics.mdx +9 -9
- package/docs/api-reference/workflow-runtime/world/index.mdx +5 -3
- package/docs/api-reference/workflow-runtime/world/queue.mdx +11 -11
- package/docs/api-reference/workflow-runtime/world/storage.mdx +62 -28
- package/docs/api-reference/workflow-runtime/world/streams.mdx +7 -7
- package/docs/api-reference/workflow-serde/index.mdx +1 -1
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +2 -2
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +7 -7
- package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +3 -3
- package/docs/api-reference/workflow-vite/workflow.mdx +5 -5
- package/docs/changelog/attributes-mvp.mdx +39 -39
- package/docs/changelog/batched-event-writes.mdx +12 -12
- package/docs/changelog/eager-processing.mdx +63 -63
- package/docs/changelog/index.mdx +3 -3
- package/docs/changelog/lazy-event-creation.md +27 -27
- package/docs/changelog/resilient-resume.mdx +5 -5
- package/docs/changelog/resilient-start.mdx +14 -14
- package/docs/changelog/step-message-ownership.mdx +47 -47
- package/docs/changelog/turbo-mode.md +20 -20
- package/docs/comparisons/index.mdx +13 -13
- package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +15 -15
- package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +12 -12
- package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +11 -11
- package/docs/comparisons/workflow-sdk-vs-inngest.mdx +19 -19
- package/docs/comparisons/workflow-sdk-vs-temporal.mdx +23 -23
- package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +18 -17
- package/docs/configuration/build-and-diagnostics.mdx +5 -5
- package/docs/configuration/cli-and-web-ui.mdx +4 -4
- package/docs/configuration/runtime-tuning.mdx +86 -23
- package/docs/configuration/worlds.mdx +28 -14
- package/docs/cookbook/advanced/child-workflows.mdx +25 -25
- package/docs/cookbook/advanced/publishing-libraries.mdx +40 -40
- package/docs/cookbook/advanced/serializable-steps.mdx +21 -21
- package/docs/cookbook/advanced/upgrading-workflows.mdx +31 -31
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +20 -20
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +22 -22
- package/docs/cookbook/common-patterns/batching.mdx +14 -14
- package/docs/cookbook/common-patterns/idempotency.mdx +9 -9
- package/docs/cookbook/common-patterns/rate-limiting.mdx +3 -3
- package/docs/cookbook/common-patterns/saga.mdx +19 -19
- package/docs/cookbook/common-patterns/scheduling.mdx +23 -23
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +26 -26
- package/docs/cookbook/common-patterns/timeouts.mdx +23 -23
- package/docs/cookbook/common-patterns/webhooks.mdx +6 -6
- package/docs/cookbook/common-patterns/workflow-composition.mdx +19 -19
- package/docs/cookbook/index.mdx +22 -22
- package/docs/cookbook/integrations/ai-sdk.mdx +43 -41
- package/docs/cookbook/integrations/chat-sdk.mdx +34 -34
- package/docs/cookbook/integrations/sandbox.mdx +46 -46
- package/docs/deploying.mdx +15 -15
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +12 -12
- package/docs/errors/corrupted-event-log.mdx +11 -11
- package/docs/errors/deployment-mismatch.mdx +14 -14
- package/docs/errors/fetch-in-workflow.mdx +8 -8
- package/docs/errors/hook-conflict.mdx +11 -11
- package/docs/errors/index.mdx +1 -1
- package/docs/errors/node-js-module-in-workflow.mdx +5 -5
- package/docs/errors/replay-divergence.mdx +2 -2
- package/docs/errors/runtime-decryption-failed.mdx +12 -12
- package/docs/errors/serialization-failed.mdx +40 -12
- package/docs/errors/start-invalid-workflow-function.mdx +5 -5
- package/docs/errors/step-executed-multiple-times.mdx +2 -2
- package/docs/errors/step-not-registered.mdx +5 -5
- package/docs/errors/timeout-in-workflow.mdx +8 -8
- package/docs/errors/webhook-invalid-respond-with-value.mdx +18 -18
- package/docs/errors/webhook-response-not-sent.mdx +16 -16
- package/docs/errors/workflow-not-registered.mdx +5 -5
- package/docs/foundations/cancellation.mdx +31 -31
- package/docs/foundations/errors-and-retries.mdx +42 -11
- package/docs/foundations/hooks.mdx +35 -35
- package/docs/foundations/idempotency.mdx +9 -9
- package/docs/foundations/serialization.mdx +21 -22
- package/docs/foundations/starting-workflows.mdx +36 -37
- package/docs/foundations/streaming.mdx +46 -41
- package/docs/foundations/versioning.mdx +3 -3
- package/docs/foundations/workflows-and-steps.mdx +9 -9
- package/docs/getting-started/astro.mdx +16 -16
- package/docs/getting-started/express.mdx +8 -8
- package/docs/getting-started/fastify.mdx +8 -8
- package/docs/getting-started/hono.mdx +8 -8
- package/docs/getting-started/nestjs.mdx +18 -17
- package/docs/getting-started/next.mdx +11 -11
- package/docs/getting-started/nitro.mdx +16 -16
- package/docs/getting-started/nuxt.mdx +8 -8
- package/docs/getting-started/python.mdx +4 -4
- package/docs/getting-started/react-router/v7.mdx +1 -1
- package/docs/getting-started/react-router/v8.mdx +1 -1
- package/docs/getting-started/sveltekit.mdx +14 -14
- package/docs/getting-started/tanstack-start.mdx +12 -12
- package/docs/getting-started/vite.mdx +8 -8
- package/docs/how-it-works/cancellation.mdx +62 -62
- package/docs/how-it-works/code-transform.mdx +66 -54
- package/docs/how-it-works/encryption.mdx +25 -21
- package/docs/how-it-works/event-sourcing.mdx +53 -35
- package/docs/how-it-works/framework-integrations.mdx +12 -12
- package/docs/how-it-works/understanding-directives.mdx +21 -21
- package/docs/internal/index.mdx +6 -6
- package/docs/internal/nitro-native-build.mdx +2 -2
- package/docs/internal/nitro-web-ui.mdx +4 -4
- package/docs/internal/serializable-abort-controller.mdx +7 -7
- package/docs/observability/attributes.mdx +3 -3
- package/docs/observability/index.mdx +14 -10
- package/docs/observability/tracing.mdx +10 -10
- package/docs/testing/index.mdx +33 -33
- package/docs/testing/server-based.mdx +10 -10
- package/docs/whats-new.mdx +185 -0
- package/package.json +12 -12
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Runtime
|
|
2
|
+
title: Runtime tuning
|
|
3
3
|
description: Runtime environment variables for replay, inline execution, queue delivery, compression, tracing, and advanced limits.
|
|
4
4
|
type: reference
|
|
5
5
|
summary: Tune Workflow runtime behavior where workflows execute.
|
|
@@ -8,7 +8,7 @@ related:
|
|
|
8
8
|
- /docs/how-it-works/event-sourcing
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The runtime reads these variables where workflows execute. Set them on the deployment or dev server.
|
|
12
12
|
|
|
13
13
|
## Client polling
|
|
14
14
|
|
|
@@ -19,6 +19,31 @@ Runtime variables are read where workflows execute. Set them on the deployment o
|
|
|
19
19
|
- Delay between status requests made by [`Run.returnValue`](/docs/api-reference/workflow-api/get-run) while a workflow run is not yet complete.
|
|
20
20
|
- Increase it to reduce polling traffic at the cost of noticing completion later.
|
|
21
21
|
- This variable is read by the process awaiting `Run.returnValue`, such as an E2E test runner, rather than by the workflow deployment.
|
|
22
|
+
- Applies whenever the World cannot hold a read open. When it can, the wait below replaces most of this polling and the interval only paces retries.
|
|
23
|
+
|
|
24
|
+
### `WORKFLOW_RETURN_VALUE_WAIT_MS`
|
|
25
|
+
|
|
26
|
+
- Default: `50000`
|
|
27
|
+
- Minimum: `1`
|
|
28
|
+
- How long a single wait for a terminal run status may block, when the World supports waiting (`world.runs.waitForTerminalStatus()`). `Run.returnValue` re-issues the wait until the run finishes, so this bounds one request rather than the total wait.
|
|
29
|
+
- `50000` is the largest value the Vercel World can actually request: it clamps the ask to its 60s per-request HTTP timeout minus 10s of headroom, so the budget always expires as a response carrying the run's current status rather than as a client-side timeout. Raising this alone has no effect; raise `WORKFLOW_REQUEST_TIMEOUT_MS` as well.
|
|
30
|
+
- Read by the process awaiting `Run.returnValue`, like the interval above.
|
|
31
|
+
|
|
32
|
+
### `WORKFLOW_RETURN_VALUE_MAX_LONG_POLLS`
|
|
33
|
+
|
|
34
|
+
- Default: `10`
|
|
35
|
+
- Minimum: `1`
|
|
36
|
+
- How many consecutive waits `Run.returnValue` issues before falling back to interval polling for the rest of the await.
|
|
37
|
+
- It never ends the await. A run that outlives the cap continues waiting on `WORKFLOW_RETURN_VALUE_POLL_INTERVAL_MS` instead.
|
|
38
|
+
- Runs longer than roughly `WORKFLOW_RETURN_VALUE_MAX_LONG_POLLS` x `WORKFLOW_RETURN_VALUE_WAIT_MS` therefore spend most of their life interval polling, which costs more requests than continuing to wait would. Raise it if you routinely await long runs.
|
|
39
|
+
- Read by the process awaiting `Run.returnValue`, like the interval above.
|
|
40
|
+
|
|
41
|
+
### `WORKFLOW_RETURN_VALUE_LONG_POLL`
|
|
42
|
+
|
|
43
|
+
- Default: enabled
|
|
44
|
+
- Set to `0` or `false` to stop `Run.returnValue` from waiting and restore fixed-interval polling on `WORKFLOW_RETURN_VALUE_POLL_INTERVAL_MS` exactly.
|
|
45
|
+
- The kill switch for the wait. Worlds that do not implement waiting are already on the interval path, so this changes nothing for them.
|
|
46
|
+
- Read by the process awaiting `Run.returnValue`, like the interval above.
|
|
22
47
|
|
|
23
48
|
## Replay and queue delivery
|
|
24
49
|
|
|
@@ -41,7 +66,7 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
41
66
|
|
|
42
67
|
- Default: `48`
|
|
43
68
|
- Delivery attempts before a run or step is failed gracefully.
|
|
44
|
-
- Can only be lowered.
|
|
69
|
+
- Can only be lowered. Workflow calibrates the default to record failure before the queue expires the message.
|
|
45
70
|
|
|
46
71
|
### `WORKFLOW_MAX_EVENTS`
|
|
47
72
|
|
|
@@ -59,8 +84,8 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
59
84
|
### `WORKFLOW_DISABLE_LAZY_HOOK_RESUME`
|
|
60
85
|
|
|
61
86
|
- Default: enabled (lazy hook resume on)
|
|
62
|
-
- Resuming a hook persists the `hook_received` event and publishes the workflow invocation concurrently, cutting a round trip off resume latency. On this parallel path the queue message also carries the payload, so a transient event-write failure still resumes the run
|
|
63
|
-
- The runtime falls back to the sequential path
|
|
87
|
+
- Resuming a hook persists the `hook_received` event and publishes the workflow invocation concurrently, cutting a round trip off resume latency. On this parallel path, the queue message also carries the payload, so a transient event-write failure still resumes the run. The queue consumer re-ensures the `hook_received` event before replay. A backend `(runId, resumeId)` constraint keeps the two writers converging on exactly one event.
|
|
88
|
+
- The runtime falls back to the sequential path when the consumer or backend does not attest dedup support (or the payload is too large to inline on the queue message). On the sequential path, the event is written *before* dispatch, and its failure fails the resume. The fallback trades away that resilience to stay safe when dedup is not enforced; it does not preserve it.
|
|
64
89
|
- Set `1` to force the sequential path as a kill switch. The chosen strategy is reported on the resume span as `workflow.hook.resume_strategy`.
|
|
65
90
|
|
|
66
91
|
### `WORKFLOW_DEPLOYMENT_MISMATCH_MAX_RETRIES`
|
|
@@ -74,7 +99,7 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
74
99
|
### `WORKFLOW_RESILIENT_STEP_DISPATCH`
|
|
75
100
|
|
|
76
101
|
- Default: disabled
|
|
77
|
-
- When a suspension hands newly created steps to the queue, the runtime publishes each step's execution message in parallel with its `step_created` event write instead of sequencing them, cutting a round trip per dispatched step. The message also carries the serialized step input (`stepInput`), so a transient `step_created` write failure (429 / 5xx / transport) still executes the step
|
|
102
|
+
- When a suspension hands newly created steps to the queue, the runtime publishes each step's execution message in parallel with its `step_created` event write instead of sequencing them, cutting a round trip per dispatched step. The message also carries the serialized step input (`stepInput`), so a transient `step_created` write failure (429 / 5xx / transport) still executes the step. The queue consumer idempotently re-ensures the event before running it, converging with the producer's write on the step's correlation ID. This mirrors resilient start (`runInput`) and the lazy hook resume (`hookInput`).
|
|
78
103
|
- It is off by default because the publish races the create's verdict, and a create can come back refused: as a duplicate this replay should stop pursuing, or as a [stale write](#stale-reads-and-why-nothing-has-to-be-rejected) on a World that refuses rather than reports. Either way the message carrying the payload is already out, so the consumer can materialize a step whose create was refused, and nothing orders the verdict before the consumer's redelivery re-ensure. The sequential path is the only one that gives the message a happens-after edge over it.
|
|
79
104
|
- Even when enabled, the runtime falls back to the sequential create-then-publish dispatch when the step input is too large to inline on the queue message, or when the run's queue transport cannot carry binary payloads (pre-CBOR spec versions).
|
|
80
105
|
- Producer-side recoveries are reported on the suspension span as `workflow.step.resilient_dispatch_recovered`; a consumer that materialized the event reports `workflow.step.resilient_dispatch_materialized`.
|
|
@@ -83,11 +108,11 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
83
108
|
### Stale reads, and why nothing has to be rejected
|
|
84
109
|
|
|
85
110
|
- Not a variable: this is how a replay working from an out-of-date event log stays correct, and why no World needs a precondition guard to make it so.
|
|
86
|
-
- Three properties
|
|
111
|
+
- Three properties work together. A reader's log is always a **prefix** of the run's log, never a prefix with a hole in it. The World allocates positions at commit, so nothing lands behind a position a reader has already passed. Replay is **deterministic on a prefix**: the same prefix always yields the same decisions, so a shorter log does not mean a different run, only a run that has not caught up. Every write **reports what it missed**: a creation names the position it replayed from (`eventCount`), and the World returns the events occupying the positions it was pushed past. The replay merges those and continues, correcting itself on the write rather than on a read.
|
|
87
112
|
- So a stale replay costs a merge, not a rejection. None of the shipped Worlds refuses a write for being stale.
|
|
88
|
-
- A World *may* refuse instead, with 412 ([`PreconditionFailedError`](/docs/api-reference/workflow-errors/precondition-failed-error))
|
|
89
|
-
- A World that does refuse should only ever do so on evidence, and accept the write in every other case. A rejection then always means the position
|
|
90
|
-
- Two runtime behaviors follow from the properties above rather than from any fence. The per-step event-log delta optimization (consuming the delta returned by a step's terminal write instead of issuing an extra `events.list` per step) stays active while the run has an open hook
|
|
113
|
+
- A World *may* refuse instead, with 412 ([`PreconditionFailedError`](/docs/api-reference/workflow-errors/precondition-failed-error)). This is appropriate when it allocates positions somewhere other than the commit and cannot report a gap reliably. The runtime restarts the replay in the same invocation from a corrected event log and falls back to a re-invocation with a fresh replay after spending the restart budget. It never retries the rejected write as-is because a replay working from a corrected log derives different events.
|
|
114
|
+
- A World that does refuse should only ever do so on evidence, and accept the write in every other case. A rejection then always means the position was stale, while the absence of one proves nothing about currency.
|
|
115
|
+
- Two runtime behaviors follow from the properties above rather than from any fence. The per-step event-log delta optimization (consuming the delta returned by a step's terminal write instead of issuing an extra `events.list` per step) stays active while the run has an open hook. A `hook_received` event missed by the delta window is observed one iteration later, and the next write brings it back. While a hook is open, inline steps take the await-then-run path even when optimistic inline start is enabled. Several invocations race for one step's claim there, and awaiting it means the body runs only for the writer that won.
|
|
91
116
|
|
|
92
117
|
### `WORKFLOW_SLOT_GAP_CHECK`
|
|
93
118
|
|
|
@@ -97,6 +122,16 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
97
122
|
- The check trades one failure for another. Most holes stand for an event that never happened, and replaying past those is correct. A hole standing for an event that did happen looks identical, and replaying past that one produces a run whose result is silently wrong. Failing is the recoverable side of that trade.
|
|
98
123
|
- Set `0` to replay across holes instead.
|
|
99
124
|
|
|
125
|
+
### `WORKFLOW_SEALED_LOG`
|
|
126
|
+
|
|
127
|
+
- Default: enabled
|
|
128
|
+
- New runs are created at the sealed-log spec version, in which the World's backend assigns each event its position *before* the write commits rather than letting concurrent writers race for one. Concurrent writes then never contend for a position, which is what makes a wide fan-out cheap.
|
|
129
|
+
- The price of assigning positions in advance is that a writer which claims one and then dies leaves a position no writer will ever fill. The backend closes such a position by writing a `noop` event into it once it can prove the position was abandoned, so a reader still sees the dense log it needs. Replay steps over a `noop` without delivering it to the workflow or advancing the deterministic clock. Its timestamp belongs to whichever reader sealed it, not to the run.
|
|
130
|
+
- Set `0` to put a deployment back on the previous scheme, where each position is allocated by the write that occupies it. Use this as the kill switch if position assignment turns out to be at fault for event-log problems.
|
|
131
|
+
- Existing runs are unaffected either way. A run's spec version is stamped once, at creation, and read from the run for the rest of its life, so flipping this changes only what *new* runs get, and a run in flight keeps the scheme it started on. Every build reads sealed logs regardless of the setting.
|
|
132
|
+
- A run created at the sealed-log version can only be replayed by a reader that knows to skip `noop` events. That includes every runtime on this release train, but a runtime that pins its own accepted spec range separately, such as the Python runtime, has to catch up before it can read these runs. Switch this off in an environment where it has not.
|
|
133
|
+
- Only the Vercel World seals. The Local and Postgres Worlds allocate each position at the commit that occupies it, so they cannot leave a hole and never write a `noop`; the setting still moves the version they stamp, so the fleet stays on one spec.
|
|
134
|
+
|
|
100
135
|
### `WORKFLOW_PRECONDITION_MAX_INPROCESS_RESTARTS`
|
|
101
136
|
|
|
102
137
|
- Default: `3`
|
|
@@ -113,15 +148,26 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
113
148
|
|
|
114
149
|
- Default: `2`
|
|
115
150
|
- Delay before a re-invocation caused by a rejected event creation.
|
|
116
|
-
- Unlike an in-process restart, which re-reads immediately, a re-invocation only happens
|
|
151
|
+
- Unlike an in-process restart, which re-reads immediately, a re-invocation only happens after the in-process budget fails to catch up. The delay gives the other writers time to quiesce.
|
|
152
|
+
|
|
153
|
+
### `WORKFLOW_LOG_ORDER_DRAWS`
|
|
154
|
+
|
|
155
|
+
- Default: enabled
|
|
156
|
+
- Experimental. Pins correlation-ID draw order to event-log order. A branch-deciding delivery (a step result, hook payload, or wait completion) resolves to the workflow only after every earlier-in-log delivery's continuation has fully quiesced, and never ahead of a lower-slot delivery that is committed to happening.
|
|
157
|
+
- Without it, a delivery that resolves while an earlier delivery's continuation is still a few microtask hops from its next step/hook/wait call can overtake it on the run's shared correlation-ID sequence. Draw order, and therefore correlation IDs, then depends on how much of the event log a replay had loaded. Two concurrent replays holding different-length prefixes can bind one ID to two different entities, failing the run with `CORRUPTED_EVENT_LOG`.
|
|
158
|
+
- Costs one event-loop turn (roughly 15 to 20 microseconds via `setImmediate`) per branch-deciding delivery during replay, and more when continuations genuinely overlap. Measurements on a 100-step sequential replay show about 2 ms added end to end.
|
|
159
|
+
- Only applies to the default Node.js VM engine. `WORKFLOW_VM=quickjs` has its own event feed and correlation-ID sequence and is unaffected by this setting.
|
|
160
|
+
- Correlation IDs of runs created before the setting changed are not affected on platforms where a run keeps replaying on the deployment it started on. Elsewhere, only change it while no runs are in flight.
|
|
161
|
+
- Set `0` to opt back into arrival-order delivery resolution. Only the literal value `0` opts out; `false` or `off` leave it enabled.
|
|
117
162
|
|
|
118
163
|
## Inline execution
|
|
119
164
|
|
|
120
165
|
### `WORKFLOW_V2_TIMEOUT_MS`
|
|
121
166
|
|
|
122
|
-
- Default: `120000`
|
|
167
|
+
- Default: derived from the runtime deadline. The value is `600000` when the invocation has 25 minutes or more left, `300000` when it has 10 minutes or more, and `120000` otherwise.
|
|
123
168
|
- Wall-clock guard for the inline replay loop.
|
|
124
169
|
- Once elapsed, the handler requeues the workflow instead of continuing to run more inline work in the same invocation.
|
|
170
|
+
- The default comes from `World.getRuntimeDeadline()`, so raising a function's `maxDuration` widens the inline budget without configuration. Worlds that do not report a deadline get the flat `120000`. Setting this variable to a finite positive number overrides the tiering entirely.
|
|
125
171
|
|
|
126
172
|
### `WORKFLOW_MAX_INLINE_STEPS`
|
|
127
173
|
|
|
@@ -147,14 +193,14 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
147
193
|
|
|
148
194
|
- Default: enabled
|
|
149
195
|
- Keeps the suspended workflow VM alive across inline steps within one invocation, so each iteration of the inline loop appends only the newly written events instead of replaying the whole event log in a fresh VM.
|
|
150
|
-
- Suspensions involving hooks, waits, or attributes
|
|
151
|
-
- Step inputs made of plain data (objects, arrays, primitives) and standard built-ins (`Map`, `Set`, `Date`, `RegExp`, typed arrays, `ArrayBuffer`, `URL`, `Headers`) keep the VM retained. Patching or polyfilling built-in prototypes doesn't change that
|
|
196
|
+
- Suspensions involving hooks, waits, or attributes, as well as any replay divergence, always fall back to a full replay.
|
|
197
|
+
- Step inputs made of plain data (objects, arrays, primitives) and standard built-ins (`Map`, `Set`, `Date`, `RegExp`, typed arrays, `ArrayBuffer`, `URL`, `Headers`) keep the VM retained. Patching or polyfilling built-in prototypes doesn't change that because serialization never calls them. A boundary falls back to a full replay only when serializing its arguments runs code the workflow controls, such as a getter, a proxy, or a custom class serializer, or computes an `Error`'s stack trace.
|
|
152
198
|
- Set `0` or `false` to replay from scratch in a fresh VM on every iteration.
|
|
153
199
|
|
|
154
200
|
### `WORKFLOW_INLINE_OWNERSHIP`
|
|
155
201
|
|
|
156
202
|
- Default: enabled
|
|
157
|
-
- Records which queue message owns each inline step execution, so a wake (hook resume, elapsed wait) that replays the run mid-step schedules a delayed backstop instead of immediately
|
|
203
|
+
- Records which queue message owns each inline step execution, so a wake (hook resume, elapsed wait) that replays the run mid-step schedules a delayed backstop instead of immediately redispatching and reexecuting the step. See [Inline step message ownership](/docs/changelog/step-message-ownership).
|
|
158
204
|
- Set `0` or `false` to revert to the previous unconditional immediate re-dispatch.
|
|
159
205
|
|
|
160
206
|
### `WORKFLOW_INLINE_OWNERSHIP_LEASE_SECONDS`
|
|
@@ -170,17 +216,25 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
170
216
|
|
|
171
217
|
- Default: `node`
|
|
172
218
|
- Values: `node` or `quickjs`
|
|
173
|
-
- Selects the sandboxed VM engine that executes workflow functions (`"use workflow"`). Step functions are unaffected
|
|
219
|
+
- Selects the sandboxed VM engine that executes workflow functions (`"use workflow"`). Step functions are unaffected and always run with full Node.js access.
|
|
174
220
|
- `node` (default) runs workflow code in a [`node:vm`](https://nodejs.org/api/vm.html) context.
|
|
175
|
-
- `quickjs` (experimental) runs workflow code in a [QuickJS](https://github.com/quickjs-ng/quickjs) VM compiled to WebAssembly (via [`quickjs-wasi`](https://github.com/vercel-labs/quickjs-wasi)). Both engines implement the same event-replay execution model (seeded PRNG, deterministic clock, and correlation-ID sequences are identical), but the **global surface is not identical
|
|
176
|
-
- Global-surface differences under `quickjs`
|
|
177
|
-
- `crypto.getRandomValues()` and `crypto.randomUUID()` are provided and deterministic (seeded like the node engine's). All `crypto.subtle.*` methods throw with guidance to move to a step function
|
|
178
|
-
- `Intl` is not available (QuickJS has no ICU). The `Intl.*` constructors throw, and `toLocaleString`-family methods (including `localeCompare`) throw when called **with an explicit locale
|
|
221
|
+
- `quickjs` (experimental) runs workflow code in a [QuickJS](https://github.com/quickjs-ng/quickjs) VM compiled to WebAssembly (via [`quickjs-wasi`](https://github.com/vercel-labs/quickjs-wasi)). Both engines implement the same event-replay execution model (seeded PRNG, deterministic clock, and correlation-ID sequences are identical), but the **global surface is not identical**. Review the differences below before switching an existing deployment. The QuickJS engine is intended for platforms that do not implement `node:vm`, and is the foundation for future VM-memory snapshotting.
|
|
222
|
+
- Global-surface differences under `quickjs` apply to workflow functions only. Step functions always have full Node.js:
|
|
223
|
+
- `crypto.getRandomValues()` and `crypto.randomUUID()` are provided and deterministic (seeded like the node engine's). All `crypto.subtle.*` methods, including `digest`, throw with guidance to move to a step function. The node engine supports `digest`.
|
|
224
|
+
- `Intl` is not available (QuickJS has no ICU). The `Intl.*` constructors throw, and `toLocaleString`-family methods (including `localeCompare`) throw when called **with an explicit locale**. Calling them without arguments keeps the engine default. Perform locale-sensitive formatting in a step function.
|
|
179
225
|
- `WebAssembly` and `Atomics` are not available.
|
|
180
226
|
- `process` exposes only a frozen copy of `env`, matching the node engine.
|
|
181
227
|
- The engine choice is stamped into the run's `executionContext` when the run starts, so a run keeps executing on the engine it started on even if the deployment's `WORKFLOW_VM` changes. Runs without a stamped engine use the handler's `WORKFLOW_VM` value.
|
|
182
228
|
- Unknown values throw at startup.
|
|
183
229
|
|
|
230
|
+
### `WORKFLOW_QUICKJS_BASELINE_SNAPSHOT`
|
|
231
|
+
|
|
232
|
+
- Default: enabled
|
|
233
|
+
- Only read when `WORKFLOW_VM=quickjs`.
|
|
234
|
+
- Evaluates the workflow bundle once per function instance, snapshots the resulting VM, and restores that snapshot at the start of every invocation instead of re-evaluating the bundle. This is the dominant share of QuickJS VM startup: roughly 77 ms to 3 ms to first suspension for a 1.3 MB bundle.
|
|
235
|
+
- A bundle whose module scope consumes randomness, reads the clock, or replaces a serialization intrinsic cannot be snapshotted safely. The runtime detects these cases when preparing the snapshot and falls back to per-invocation evaluation.
|
|
236
|
+
- Set `0` or `false` to always evaluate the bundle per invocation.
|
|
237
|
+
|
|
184
238
|
## Compression and tracing
|
|
185
239
|
|
|
186
240
|
### `WORKFLOW_DISABLE_COMPRESSION`
|
|
@@ -225,11 +279,11 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
225
279
|
|
|
226
280
|
Node's own modules do less than the client they replace, so enabling this drops the per-call-site tuning the Worlds configure:
|
|
227
281
|
|
|
228
|
-
- Event-log requests lose HTTP/2, so concurrent reads and writes no longer share one connection, and the enlarged HTTP/2 receive windows no longer apply. This is the largest difference, and it slows down replays that read a big event log. It does not apply to event writes on [
|
|
282
|
+
- Event-log requests lose HTTP/2, so concurrent reads and writes no longer share one connection, and the enlarged HTTP/2 receive windows no longer apply. This is the largest difference, and it slows down replays that read a big event log. It does not apply to event writes on the [WebSocket events transport](/docs/configuration/worlds#workflow_events_transport), which is the default and takes neither transport.
|
|
229
283
|
- Requests lose their transport-level retry. Failures still surface to the layers above, which retry event writes and redeliver queue messages, so nothing is silently dropped, but a failure that a same-connection retry would have hidden now costs a full redelivery.
|
|
230
284
|
- Stream close loses its retry of retriable server errors. A transient failure at close can leave a stream marked closing until the run expires, where it would previously have resolved on the retry.
|
|
231
285
|
|
|
232
|
-
Connection pooling, keep-alive, and the request, header, and body deadlines are preserved
|
|
286
|
+
Connection pooling, keep-alive, and the request, header, and body deadlines are preserved. Node's agents configure pooling and keep-alive, and each request receives the deadlines from the Local World's two queue timeouts or the same defaults that the Vercel World's HTTP client currently applies. Queue sends are the exception in the other direction: that client takes no transport override, so its requests keep using the library either way.
|
|
233
287
|
|
|
234
288
|
A `dispatcher` passed to `createVercelWorld()` still wins over this variable. The variable chooses which transport the World builds when you have not supplied one.
|
|
235
289
|
|
|
@@ -249,7 +303,7 @@ These variables are primarily for tests, debugging, or unusual deployments.
|
|
|
249
303
|
### `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`
|
|
250
304
|
|
|
251
305
|
- Default: `0` (dispatch the first chunk of an idle stream immediately)
|
|
252
|
-
- Group-commit window for the *leading* chunk of an idle stream. `0` sends it at once; a positive value holds it up to that many milliseconds to collect a group
|
|
306
|
+
- Group-commit window for the *leading* chunk of an idle stream. `0` sends it at once; a positive value holds it up to that many milliseconds to collect a group. This opt-in setting trades first-chunk latency for larger batches and can benefit slow-but-steady producers. Chunks arriving while a request is already in flight always coalesce into the next group regardless of this setting.
|
|
253
307
|
- Also available as `streamFlushIntervalMs` on Worlds that expose it (the env var, when set, takes precedence over the World option).
|
|
254
308
|
|
|
255
309
|
### `WORKFLOW_STREAM_MAX_INFLIGHT_CHUNKS`
|
|
@@ -303,3 +357,12 @@ These variables are primarily for tests, debugging, or unusual deployments.
|
|
|
303
357
|
|
|
304
358
|
- Default: `10`
|
|
305
359
|
- Poll interval for detecting stream lock release.
|
|
360
|
+
|
|
361
|
+
## Limits
|
|
362
|
+
|
|
363
|
+
### `WORKFLOW_MAX_EVENTS_OVERRIDE`
|
|
364
|
+
|
|
365
|
+
- Default: unset
|
|
366
|
+
- Lowers the per-run event ceiling supplied by the World. A run whose event log reaches the ceiling fails with `MAX_EVENTS_EXCEEDED`, which stops a runaway loop from growing its log without bound.
|
|
367
|
+
- Clamp-down only: it never raises the World's limit, and it applies even when the World supplies none. With no World limit and no override, nothing is enforced.
|
|
368
|
+
- The Local and Vercel Worlds both supply a limit; the Local World defaults to 25,000 and is configurable with [`WORKFLOW_MAX_EVENTS`](/docs/configuration/worlds#workflow_max_events).
|
|
@@ -23,16 +23,16 @@ Outside Vercel, Workflow defaults to the Local World. On Vercel, leave `WORKFLOW
|
|
|
23
23
|
|
|
24
24
|
The World is selected when your app **runs**, from the environment of the process serving it, so changing `WORKFLOW_TARGET_WORLD` takes effect on the next start without a rebuild. Detection keys off `VERCEL_DEPLOYMENT_ID`, which Vercel sets in every deployed function and nothing else sets: with it, the Vercel World; without it, the Local World.
|
|
25
25
|
|
|
26
|
-
Broader signals are deliberately ignored. `vercel env pull` writes `VERCEL=1` into `.env.local`, so a dev server or a production server started on your own machine sees it while running against a writable filesystem
|
|
26
|
+
Broader signals are deliberately ignored. `vercel env pull` writes `VERCEL=1` into `.env.local`, so a dev server or a production server started on your own machine sees it while running against a writable filesystem, where the Local World is the right choice. Set `WORKFLOW_TARGET_WORLD=vercel` explicitly if you want such a process to talk to the Vercel World; starting a run then fails with an error naming the missing `VERCEL_DEPLOYMENT_ID`.
|
|
27
27
|
|
|
28
28
|
A deployment that pins `WORKFLOW_TARGET_WORLD=local` warns at startup and fails on its first write, because a Vercel deployment's filesystem is read-only.
|
|
29
29
|
|
|
30
30
|
Set `WORKFLOW_TARGET_WORLD` only when you want to use a custom or self-hosted World:
|
|
31
31
|
|
|
32
|
-
- `local
|
|
33
|
-
- `@workflow/world-postgres
|
|
34
|
-
- `./my-world.ts
|
|
35
|
-
- Any package specifier
|
|
32
|
+
- `local`: Alias for `@workflow/world-local`.
|
|
33
|
+
- `@workflow/world-postgres`: Postgres World package.
|
|
34
|
+
- `./my-world.ts`: Local module exporting a World, `createWorld()`, or a default factory.
|
|
35
|
+
- Any package specifier: Custom World package.
|
|
36
36
|
|
|
37
37
|
The `vercel` alias exists for manual selection and tooling, but deployed Vercel apps do not need to set it.
|
|
38
38
|
|
|
@@ -121,7 +121,12 @@ The Local World is the default outside Vercel and is intended for development.
|
|
|
121
121
|
|
|
122
122
|
- Environment variable fallback: `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`
|
|
123
123
|
- Default: `0` (dispatch the leading chunk of an idle stream immediately)
|
|
124
|
-
- Group-commit window for the leading chunk of an idle stream; a positive value trades first-chunk latency for larger groups. The `WORKFLOW_STREAM_FLUSH_INTERVAL_MS` environment variable, when set, overrides this option; otherwise the World option governs, including the
|
|
124
|
+
- Group-commit window for the leading chunk of an idle stream; a positive value trades first-chunk latency for larger groups. The `WORKFLOW_STREAM_FLUSH_INTERVAL_MS` environment variable, when set, overrides this option; otherwise the World option governs, including the first chunk.
|
|
125
|
+
|
|
126
|
+
### `WORKFLOW_MAX_EVENTS`
|
|
127
|
+
|
|
128
|
+
- Default: `25000`
|
|
129
|
+
- Per-run event ceiling reported to the runtime. A run whose event log reaches it fails with `MAX_EVENTS_EXCEEDED`, bounding a runaway loop. See [`WORKFLOW_MAX_EVENTS_OVERRIDE`](/docs/configuration/runtime-tuning#workflow_max_events_override) for the runtime-side clamp.
|
|
125
130
|
|
|
126
131
|
## Postgres World
|
|
127
132
|
|
|
@@ -184,13 +189,13 @@ The Postgres World is a self-hosted durable backend for long-running server proc
|
|
|
184
189
|
|
|
185
190
|
- Environment variable fallback: `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`
|
|
186
191
|
- Default: `0` (dispatch the leading chunk of an idle stream immediately)
|
|
187
|
-
- Group-commit window for the leading chunk of an idle stream; a positive value trades first-chunk latency for larger groups. The `WORKFLOW_STREAM_FLUSH_INTERVAL_MS` environment variable, when set, overrides this option; otherwise the World option governs, including the
|
|
192
|
+
- Group-commit window for the leading chunk of an idle stream; a positive value trades first-chunk latency for larger groups. The `WORKFLOW_STREAM_FLUSH_INTERVAL_MS` environment variable, when set, overrides this option; otherwise the World option governs, including the first chunk.
|
|
188
193
|
|
|
189
194
|
## Vercel World
|
|
190
195
|
|
|
191
196
|
The Vercel World is configured automatically inside Vercel deployments. The platform provides the deployment ID, project ID, request authentication, queue integration, storage, and encryption material.
|
|
192
197
|
|
|
193
|
-
Most applications should not set `WORKFLOW_VERCEL_*` variables on Vercel. They configure tooling that talks to a Vercel Workflow project from outside a deployment, such as the
|
|
198
|
+
Most applications should not set `WORKFLOW_VERCEL_*` variables on Vercel. They configure tooling that talks to a Vercel Workflow project from outside a deployment, such as the Workflow CLI, the web user interface (UI), continuous integration (CI), or tests. The runtime warns if these variables are set in a deployed Vercel Function because they do not control runtime configuration there.
|
|
194
199
|
|
|
195
200
|
Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, and `VERCEL_DEPLOYMENT_KEY` are read by the runtime inside Vercel deployments. Do not set them yourself.
|
|
196
201
|
|
|
@@ -240,7 +245,7 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an
|
|
|
240
245
|
|
|
241
246
|
- Default: disabled
|
|
242
247
|
- Set `1` to serialize orchestrator (flow) invocations per run: each run's replays get their own queue topic and the flow trigger is generated with `maxConcurrency: 1`. Inline step executions get per-step topics and keep full parallelism.
|
|
243
|
-
- Read at **both build time and runtime
|
|
248
|
+
- Read at **both build time and runtime**: set it as a project-level environment variable so the generated trigger and the runtime queue routing agree.
|
|
244
249
|
- Routing each run through a dedicated `maxConcurrency: 1` topic might lead to higher queue performance overhead. See [Vercel World](/worlds/vercel#workflow_sequential_replays) for details.
|
|
245
250
|
|
|
246
251
|
### `VERCEL_WORKFLOW_SERVER_URL`
|
|
@@ -265,7 +270,9 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an
|
|
|
265
270
|
- Factory option: none
|
|
266
271
|
- CLI flag: none
|
|
267
272
|
- Default: `60000`
|
|
273
|
+
- Clamp: `10000` to `120000` (values outside are clamped, with a warning)
|
|
268
274
|
- Per-request timeout for Vercel World HTTP calls to workflow-server.
|
|
275
|
+
- At the `10000` floor the run-status long poll disables itself, because its budget is this value minus 10s of headroom.
|
|
269
276
|
|
|
270
277
|
### `WORKFLOW_MAX_CHUNKS_PER_REQUEST`
|
|
271
278
|
|
|
@@ -274,18 +281,25 @@ Platform-provided values such as `VERCEL_DEPLOYMENT_ID`, `VERCEL_PROJECT_ID`, an
|
|
|
274
281
|
- Default: `1000`
|
|
275
282
|
- Maximum stream chunks written in one Vercel World request. Larger batches are split.
|
|
276
283
|
|
|
284
|
+
### `WORKFLOW_DISABLE_ANALYTICS_READS`
|
|
285
|
+
|
|
286
|
+
- Factory option: none
|
|
287
|
+
- CLI flag: none
|
|
288
|
+
- Default: disabled
|
|
289
|
+
- Set `1` to turn off the World's metadata-only `analytics` read namespace, forcing `workflow inspect` and web UI list views onto strongly consistent primary storage. Intended for tests and tooling that read entities immediately after writing them.
|
|
290
|
+
|
|
277
291
|
### `WORKFLOW_BATCH_TRANSITIONS`
|
|
278
292
|
|
|
279
293
|
- Surface: environment variable
|
|
280
294
|
- Default: on
|
|
281
|
-
- Set to `0` (or `false`) to **disable** batched event writes
|
|
295
|
+
- Set to `0` (or `false`) to **disable** batched event writes, the escape hatch that restores the exact prior one-write-per-event path.
|
|
282
296
|
|
|
283
|
-
When enabled (the default), a suspension's eager `step_created` and `wait_created` writes fold into batched `events.createBatch` calls (one durable write with per-event outcomes) on Worlds that implement the optional batch API. The fold only engages when the World implements `events.createBatch` (the Vercel World does; Local and Postgres do not), the run's spec version supports slot identity (≥ 6), and the suspension carries no attribute writes, hook writes, or resilient step dispatch
|
|
297
|
+
When enabled (the default), a suspension's eager `step_created` and `wait_created` writes fold into batched `events.createBatch` calls (one durable write with per-event outcomes) on Worlds that implement the optional batch API. The fold only engages when the World implements `events.createBatch` (the Vercel World does; Local and Postgres do not), the run's spec version supports slot identity (≥ 6), and the suspension carries no attribute writes, hook writes, or resilient step dispatch. Everything else keeps the single-event path unchanged, so disabling is only needed as an operational escape hatch. Batches are capped at 32 events; larger fan-outs commit in successive batches. See the [batched event writes changelog](/docs/changelog/batched-event-writes) for the World API contract.
|
|
284
298
|
|
|
285
299
|
### `WORKFLOW_EVENTS_TRANSPORT`
|
|
286
300
|
|
|
287
301
|
- Factory option: none
|
|
288
302
|
- CLI flag: none
|
|
289
|
-
- Default: `
|
|
290
|
-
-
|
|
291
|
-
- Ignored when the World is configured with `projectConfig` and routes through the `api-workflow` proxy
|
|
303
|
+
- Default: `ws`
|
|
304
|
+
- Ships workflow run events to the Vercel World over a WebSocket instead of one HTTP request each. Set to exactly `http` to opt out; any other value, including unset or empty, uses the WebSocket.
|
|
305
|
+
- Ignored when the World is configured with `projectConfig` and routes through the `api-workflow` proxy: that endpoint is an HTTP-only REST gateway and does not forward a WebSocket upgrade, so events stay on HTTP.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: Child Workflows
|
|
3
3
|
description: Spawn child workflows from a parent and wait for completion via hook resume.
|
|
4
4
|
type: guide
|
|
5
|
-
summary: Orchestrate independent child workflows from a parent using start(), defineHook(), and startAndWait()
|
|
5
|
+
summary: Orchestrate independent child workflows from a parent using start(), defineHook(), and startAndWait(). The child resumes the parent's hook when done instead of polling getRun().status.
|
|
6
6
|
related:
|
|
7
7
|
- /docs/api-reference/workflow-api/start
|
|
8
8
|
---
|
|
@@ -11,16 +11,16 @@ related:
|
|
|
11
11
|
text="Refactor this workflow to use child workflows. Keep the parent as an exported `"use workflow"` function. Move independent units of durable work into separate exported child workflow functions. From the parent, call `start(childWorkflow, [args])` from `workflow/api` or the documented `startAndWait`/hook pattern where completion must resume the parent. Pass only serializable state to children. For fan-out, start children in parallel with `Promise.all` or bounded batches, collect run IDs, handle partial failures with `Promise.allSettled`, and use `getRun(runId)` when status, cancellation, streams, or return values are needed. Verify child start, completion, failure, and parent resume behavior."
|
|
12
12
|
/>
|
|
13
13
|
|
|
14
|
-
Use child workflows when a single workflow needs to orchestrate many independent units of work. Each child runs as its own workflow with a separate event log, retry boundary, and failure scope
|
|
14
|
+
Use child workflows when a single workflow needs to orchestrate many independent units of work. Each child runs as its own workflow with a separate event log, retry boundary, and failure scope. If one child fails, it doesn't take down the parent or siblings.
|
|
15
15
|
|
|
16
16
|
## When to use child workflows
|
|
17
17
|
|
|
18
18
|
Child workflows are the right choice when:
|
|
19
19
|
|
|
20
|
-
- **Work units are independent
|
|
21
|
-
- **You need isolated failure boundaries
|
|
22
|
-
- **You want
|
|
23
|
-
- **You need per-item observability
|
|
20
|
+
- **Work units are independent**: Each child can run without knowing about the others (for example, when processing individual documents or generating separate reports).
|
|
21
|
+
- **You need isolated failure boundaries**: A failing child should not abort unrelated work. The parent decides how to handle failures.
|
|
22
|
+
- **You want large fan-out**: Spawning 50 or 500 children is practical because each runs on its own infrastructure.
|
|
23
|
+
- **You need per-item observability**: Each child workflow has its own run ID, status, and event log for monitoring.
|
|
24
24
|
|
|
25
25
|
For simpler cases where steps share a single event log, use [direct await composition](/cookbook/common-patterns/workflow-composition#direct-await-flattening) instead.
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ For simpler cases where steps share a single event log, use [direct await compos
|
|
|
28
28
|
|
|
29
29
|
The recommended pattern has four parts:
|
|
30
30
|
|
|
31
|
-
1. A **completion hook** the parent creates and awaits
|
|
31
|
+
1. A **completion hook** the parent creates and awaits, with zero compute while waiting
|
|
32
32
|
2. A **wrapped child export** that runs the real child in try/catch/finally and resumes the parent's hook from a step in `finally`
|
|
33
33
|
3. A **`start()` call** that spawns the wrapped child with the hook token (directly from the workflow in v5)
|
|
34
34
|
4. A **`startAndWait()` helper** that ties the hook, spawn, and typed result together
|
|
@@ -148,12 +148,12 @@ export async function processDocumentBatch(documentIds: string[]) {
|
|
|
148
148
|
|
|
149
149
|
Polling with `getRun().status` in a `sleep()` loop works, but hook resume is preferable because:
|
|
150
150
|
|
|
151
|
-
- **Zero compute while waiting
|
|
152
|
-
- **Immediate wake-up
|
|
153
|
-
- **Typed payloads
|
|
154
|
-
- **No worker-pool pressure
|
|
151
|
+
- **Zero compute while waiting**: The parent suspends on the hook instead of waking every poll interval.
|
|
152
|
+
- **Immediate wake-up**: The parent resumes as soon as the child finishes, not on the next poll tick.
|
|
153
|
+
- **Typed payloads**: The child sends `{ status, value | error }` directly, with no separate `returnValue` fetch step.
|
|
154
|
+
- **No worker-pool pressure**: `Run#returnValue` polling inside steps can hold worker slots while waiting for children (see [Eager Processing](/docs/changelog/eager-processing)).
|
|
155
155
|
|
|
156
|
-
When a parent calls a child workflow inline with `await` (flattened into the same run), the same wrapper and hook handshake still works
|
|
156
|
+
When a parent calls a child workflow inline with `await` (flattened into the same run), the same wrapper and hook handshake still works: pass the token and `await processDocumentWithCompletion(...)` inside `startAndWait()` instead of calling `start()`.
|
|
157
157
|
|
|
158
158
|
## Fan-out pattern: chunked spawning
|
|
159
159
|
|
|
@@ -227,7 +227,7 @@ declare function withChildCompletionHook<TResult>(
|
|
|
227
227
|
|
|
228
228
|
### Tolerating partial failures
|
|
229
229
|
|
|
230
|
-
Use `Promise.allSettled` with `startAndWait()` so one failing child doesn't abort siblings. The hook payload already carries `{ status: "failed", error }
|
|
230
|
+
Use `Promise.allSettled` with `startAndWait()` so one failing child doesn't abort siblings. The hook payload already carries `{ status: "failed", error }`, so no status polling is required.
|
|
231
231
|
|
|
232
232
|
```typescript
|
|
233
233
|
import { start } from "workflow/api";
|
|
@@ -302,18 +302,18 @@ async function startAndWaitWithRetries(
|
|
|
302
302
|
|
|
303
303
|
## Tips
|
|
304
304
|
|
|
305
|
-
-
|
|
306
|
-
- **Export wrapped children at module scope
|
|
307
|
-
- **Use stable hook keys
|
|
308
|
-
- **Use chunked spawning for large batches
|
|
309
|
-
- **
|
|
310
|
-
- **Use `deploymentId: "latest"
|
|
305
|
+
- **Call `defineHook().resume()` from a step**: The wrapped child's `finally` block calls a step that resumes the parent hook.
|
|
306
|
+
- **Export wrapped children at module scope**: The SDK registers `"use workflow"` functions statically, so a runtime higher-order function returned from `withChildCompletionHook()` cannot be passed to `start()`.
|
|
307
|
+
- **Use stable hook keys**: Document IDs, job IDs, or indexes prevent token collisions between parallel children in one parent run.
|
|
308
|
+
- **Use chunked spawning for large batches**: Starting 500 children at once can create a large burst of work. Break the work into chunks of 10–50.
|
|
309
|
+
- **Account for each child's retry semantics**: Steps inside child workflows retry independently. The parent sees the final `{ status, value | error }` payload from the hook.
|
|
310
|
+
- **Use `deploymentId: "latest"` when children should run on the most recent deployment**: See [Versioning](/docs/foundations/versioning) for the full model and the [`start()` API reference](/docs/api-reference/workflow-api/start#using-deploymentid-latest) for compatibility considerations.
|
|
311
311
|
|
|
312
312
|
## Key APIs
|
|
313
313
|
|
|
314
|
-
- [`start()`](/docs/api-reference/workflow-api/start)
|
|
315
|
-
- [`defineHook()`](/docs/api-reference/workflow/define-hook)
|
|
316
|
-
- [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook)
|
|
317
|
-
- [`getWorkflowMetadata()`](/docs/api-reference/workflow/get-workflow-metadata)
|
|
318
|
-
- [`"use workflow"`](/docs/foundations/workflows-and-steps)
|
|
319
|
-
- [`"use step"`](/docs/foundations/workflows-and-steps)
|
|
314
|
+
- [`start()`](/docs/api-reference/workflow-api/start): Spawns a new workflow run and returns its run ID.
|
|
315
|
+
- [`defineHook()`](/docs/api-reference/workflow/define-hook): Defines a typed hook for parent-child completion handshakes.
|
|
316
|
+
- [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook): Resumes a waiting parent from a step called by the child wrapper.
|
|
317
|
+
- [`getWorkflowMetadata()`](/docs/api-reference/workflow/get-workflow-metadata): Returns the parent run ID for deterministic hook tokens.
|
|
318
|
+
- [`"use workflow"`](/docs/foundations/workflows-and-steps): Marks the orchestrator function.
|
|
319
|
+
- [`"use step"`](/docs/foundations/workflows-and-steps): Marks functions with full Node.js access.
|