workflow 5.0.0-beta.44 → 5.0.0-beta.47

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.
@@ -12,9 +12,11 @@ related:
12
12
 
13
13
  Resumes a workflow run by sending a payload to a hook identified by its token.
14
14
 
15
- It creates a `hook_received` event and re-triggers the workflow to continue execution.
15
+ It durably writes the `hook_received` event and only then publishes a workflow wake. The call resolves only after both operations succeed, in that order.
16
16
 
17
- A Hook kept by `experimental_minRetention` after its workflow ends cannot be resumed. `resumeHook()` throws `HookNotFoundError` in that case.
17
+ `resumeHook()` throws `HookNotFoundError` when no hook holds the token or when its `hook_received` write is refused because the hook was disposed or the run ended. See [durable hook resume](/docs/changelog/lazy-hook-resume).
18
+
19
+ If `resumeHook()` throws any other error, the outcome is ambiguous only in dispatch, never in durability: the event may already be durable even though the workflow wake failed, and any later wake of the run delivers it. Calling `resumeHook()` again creates a new `resumeId` and can append a second `hook_received`. Callers that need at-most-once behavior across separate invocations must retain and deduplicate their own request key.
18
20
 
19
21
  <Callout type="warn">
20
22
  `resumeHook` is a runtime function that must be called from outside a workflow function.
@@ -50,7 +52,7 @@ showSections={["parameters"]}
50
52
 
51
53
  ### Returns
52
54
 
53
- Returns a `Promise<ResumedHook>`, a `Hook` extended with an optional `resilientResume` flag. Resolving means the resume was accepted and the workflow will continue, whether the `hook_received` event was written directly or, on the parallel fast path, delivered through the workflow queue for the runtime to materialize (see the [lazy hook resume changelog](/docs/changelog/resilient-resume)). `resilientResume` is `true` only when the direct event write failed transiently and the resume was recovered through the queue; on the happy path it is absent. The resolved hook:
55
+ Returns a `Promise<ResumedHook>`, a `Hook` extended with an optional `resilientResume` flag. Resolving means the payload is durably recorded as `hook_received` and the workflow wake was accepted. `resilientResume` is retained for source compatibility and is no longer set by any path. The resolved hook:
54
56
 
55
57
  <TSDoc
56
58
  definition={`
@@ -11,7 +11,7 @@ related:
11
11
 
12
12
  Resumes a workflow run by sending an HTTP `Request` to a webhook identified by its token.
13
13
 
14
- This function creates a `hook_received` event and re-triggers the workflow to continue execution. It's designed to be called from API routes or server actions that receive external HTTP requests.
14
+ This function publishes a workflow invocation carrying the request; the runtime creates the `hook_received` event from it and continues execution. It's designed to be called from API routes or server actions that receive external HTTP requests.
15
15
 
16
16
  <Callout type="warn">
17
17
  `resumeWebhook` is a runtime function that must be called from outside a workflow function.
@@ -108,6 +108,18 @@ The `deploymentId` option is currently a Vercel-specific feature. Other Worlds m
108
108
  In Worlds without atomic, immutable deployments (such as local development or self-hosted Postgres), there is no notion of multiple deployments to resolve between, so `deploymentId: "latest"` has no effect: the SDK logs a warning and the run targets the current deployment. This means a workflow that opts into `"latest"` on Vercel still runs unchanged in local development.
109
109
  </Callout>
110
110
 
111
+ <Callout type="info">
112
+ Resolving `"latest"` is the one `start()` path that calls the Vercel API, so it
113
+ needs an identity that can see the calling deployment. Inside a Vercel
114
+ deployment the SDK authenticates with the deployment's own OIDC token, which
115
+ carries the owning team, and this takes precedence over a `VERCEL_TOKEN` set in
116
+ the function's environment. A `VERCEL_TOKEN` belongs to a *user* and carries no
117
+ team, so authenticating with it scopes the lookup to that user's default team
118
+ and fails with a 404 whenever that is not the team that owns the deployment.
119
+ Outside a deployment (CLI, CI, the dashboard) `VERCEL_TOKEN` is still used;
120
+ configure the World's `teamId` so the request is scoped explicitly.
121
+ </Callout>
122
+
111
123
  <Callout type="warn">
112
124
  When using `deploymentId: "latest"`, the workflow run will execute on a potentially different deployment than the one calling `start()`. Be mindful of forward and backward compatibility:
113
125
 
@@ -273,7 +273,7 @@ If you need behavior the MVP does not provide (read, list, filter, initial attri
273
273
  Unit tests in `@workflow/world` (validation surface) and `@workflow/core` (VM-side dispatch + host-side stub):
274
274
 
275
275
  - Validation rules: key length, value byte cap, `$` prefix, per-batch duplicates, post-merge count cap (with `existingKeys` so updates of present keys don't falsely trip the cap)
276
- - Reserved `$` namespace: rejected by default, accepted when `allowReservedAttributes: true` is passed (both for `validateAttributeKey` and at the batch level via `validateAttributeChanges`)
276
+ - Reserved `$` namespace: rejected by default and accepted by the contextual `validateAttributeChanges` check when `allowReservedAttributes: true` is passed
277
277
  - `experimental_setAttributes({})` is a no-op (no dispatch, no events)
278
278
  - `undefined` value normalizes to a `null`-valued change on the wire
279
279
  - The `{ allowReservedAttributes: true }` opt-in is forwarded through the step bridge so the world receives the flag
@@ -367,7 +367,7 @@ For the MVP the endpoint reuses the existing `WORKFLOW_EVENT` fact with `eventTy
367
367
 
368
368
  ### Validation rules are shared between SDK and world
369
369
 
370
- Validation lives in a single helper exported from `@workflow/world` (`validateAttributeChanges`, `validateAttributeKey`, `validateAttributeValue`). Both the SDK `experimental_setAttributes` helper and the `world-local` / `world-postgres` implementations call it; the `world-vercel` backing service applies the same rules independently. The shared module is the authoritative spec for the limits (256-char keys, 256-byte values, max 64 attributes per run, `$`-prefixed keys reserved), so any future change goes through one file.
370
+ Context-free validation lives in the exported Zod schemas (`AttributeKeySchema`, `AttributeValueSchema`, `AttributeChangeSchema`, and `AttributeChangesSchema`). The schema-free `validateAttributeChanges` helper adds rules that depend on caller context, including the post-merge count and reserved `$` namespace. Both the SDK `experimental_setAttributes` helper and the `world-local` / `world-postgres` implementations call it; the `world-vercel` backing service applies the same rules independently. The shared module remains the authoritative spec for the limits (256-char keys, 256-byte values, max 64 attributes per run, `$`-prefixed keys reserved).
371
371
 
372
372
  ### Run row reconstruction had to thread `attributes` through
373
373
 
@@ -12,6 +12,7 @@ Stay up to date with the latest changes to Workflow SDK.
12
12
 
13
13
  ## 2026
14
14
 
15
+ - [Durable hook resume](/docs/changelog/lazy-hook-resume) (August 2026)
15
16
  - [Resilient hook resume](/docs/changelog/resilient-resume) (July 2026)
16
17
  - [Eager processing of steps and incremental event replay](/docs/changelog/eager-processing) (March 2026)
17
18
  - Serializable AbortController and AbortSignal (March 12, 2026)
@@ -0,0 +1,78 @@
1
+ ---
2
+ title: Durable hook resume
3
+ description: resumeHook() durably writes hook_received and only then publishes the workflow wake, so a resolved call can never be lost to a disposal race.
4
+ ---
5
+
6
+ # Durable hook resume
7
+
8
+ ## Motivation
9
+
10
+ The previous lazy path published the serialized hook payload on the workflow
11
+ queue and left the queue consumer to create `hook_received`. If the hook was
12
+ disposed after `resumeHook()` returned but before the consumer write committed,
13
+ that write was rejected and the acknowledged queue delivery could not resume the
14
+ workflow: the caller was told the resume succeeded, and it was lost.
15
+
16
+ `resumeHook()` now resolves only after both the durable event write and the
17
+ workflow wake have succeeded, in that order.
18
+
19
+ ## Design
20
+
21
+ The dispatch is strictly serial:
22
+
23
+ 1. The hook is resolved by token. An unknown token throws `HookNotFoundError`.
24
+ 2. The producer writes `hook_received` durably into the run's event log. A
25
+ client-minted `resumeId` and payload digest ride the write when the backend
26
+ supports atomic resume claims, so transport-level retries of the same write
27
+ converge on exactly one committed event. A write refused because the hook
28
+ was disposed or the run ended throws `HookNotFoundError`.
29
+ 3. Only after the write is acknowledged does the producer publish the workflow
30
+ wake. The wake carries no payload — the payload lives in the event log — so
31
+ nothing rides on the queue message but the trigger. Publication is retried
32
+ a bounded number of times.
33
+
34
+ Because the event is committed before the wake exists, a disposal or run
35
+ completion racing the queue delivery cannot erase a resume the caller was told
36
+ succeeded: the delivery replays the committed event from the log.
37
+
38
+ - `ResumedHook.resilientResume` remains on the type for source compatibility
39
+ and is no longer set. The internal `resumeHookDurable()` entry point is
40
+ removed; `resumeHook()` itself now provides the durable guarantee.
41
+
42
+ A resolved call proves that the event is durable and the wake was accepted.
43
+ `HookNotFoundError` proves this invocation committed no event. Any other thrown
44
+ error is ambiguous only in *dispatch*, never in durability: a wake failure
45
+ after the write leaves the event committed, and any later wake of the run
46
+ (from any source) delivers it. A fresh `resumeHook()` invocation mints a new
47
+ `resumeId`, so blindly retrying a failed call can append a second
48
+ `hook_received`; callers that need at-most-once behavior across separate
49
+ invocations must deduplicate on their own request key.
50
+
51
+ ## Behavior change: resumes against an ended run
52
+
53
+ The lazy path never observed the server's rejection — it published a message
54
+ and resolved, so a resume against a run that had already ended reported
55
+ success (reachable whenever the hook record outlives its run, e.g. token
56
+ retention). The durable write restores the check: **a resume against an ended
57
+ run now throws `HookNotFoundError`**, and a late webhook delivery to a
58
+ finished run answers 404 where it previously answered 202. Senders that treat
59
+ 4xx as terminal will stop retrying such deliveries; that is the correct
60
+ signal, since nothing can resume an ended run.
61
+
62
+ A transient write conflict (HTTP 409, e.g. an event-slot conflict that
63
+ escaped the server's internal retry budget under contention) is no longer
64
+ re-keyed to `HookNotFoundError`. It surfaces as a retryable error, and its
65
+ rejected transaction committed nothing, so retrying the resume is safe.
66
+
67
+ ## Compatibility
68
+
69
+ Nothing about the queue message changes: the wake has the same shape the
70
+ sequential path always published, so no consumer, backend, or server
71
+ coordination is needed and either side can roll back independently.
72
+
73
+ Consumers continue to accept legacy `hookInput` messages from older producers,
74
+ materializing their payload before replay. This permits rolling upgrades
75
+ without a coordinated producer and consumer deployment.
76
+
77
+ `WORKFLOW_DISABLE_LAZY_HOOK_RESUME` no longer gates anything and is ignored:
78
+ there is no lazy path left to disable.
@@ -2,6 +2,7 @@
2
2
  "title": "Changelog",
3
3
  "pages": [
4
4
  "index",
5
+ "lazy-hook-resume",
5
6
  "eager-processing",
6
7
  "resilient-resume",
7
8
  "resilient-start",
@@ -5,6 +5,16 @@ description: resumeHook() now tolerates transient event storage failures when th
5
5
 
6
6
  # Resilient `resumeHook()`
7
7
 
8
+ <Callout type="info">
9
+ Superseded by [durable hook resume](/docs/changelog/lazy-hook-resume):
10
+ `resumeHook()` now writes the `hook_received` event durably and only then
11
+ publishes the workflow wake, so the two-writer design, the queue-carried
12
+ payload, and the `resilientResume` flag described below are historical. The
13
+ `(runId, resumeId)` constraint remains, converging transport-level retries
14
+ of the producer's own write (and legacy `hookInput` redeliveries from older
15
+ producers).
16
+ </Callout>
17
+
8
18
  ## Motivation
9
19
 
10
20
  `resumeHook()` used to write the `hook_received` event and dispatch the workflow queue message strictly one after the other, so every resume paid two sequential round trips and a transient event-storage failure failed the whole resume even when the queue was healthy. This change runs both writes **concurrently** (cutting a round trip off resume latency) and, on the same path, brings `resumeHook()` to parity with [resilient `start()`](/docs/changelog/resilient-start): a transient event-write failure no longer fails the resume when the payload can still be delivered through the queue.
@@ -19,4 +29,4 @@ description: resumeHook() now tolerates transient event storage failures when th
19
29
 
20
30
  ## Compatibility
21
31
 
22
- The parallel fast path is gated per resume: it activates only when both the target run's queue consumer and the live backend independently attest dedup support (re-checked on every resume, so rollout and rollback both degrade safely). Otherwise (for oversized payloads, legacy runs, or with `WORKFLOW_DISABLE_LAZY_HOOK_RESUME=1`), `resumeHook()` falls back to the original sequential write-then-dispatch path. Because runs keep executing on the deployment they were created on, a resume targeting a run from an older deployment uses the sequential path.
32
+ The parallel fast path is gated per resume: it activates only when both the target run's queue consumer and the live backend independently attest dedup support (re-checked on every resume, so rollout and rollback both degrade safely). Otherwise (for oversized payloads or legacy runs), `resumeHook()` falls back to the original sequential write-then-dispatch path. Because runs keep executing on the deployment they were created on, a resume targeting a run from an older deployment uses the sequential path.
@@ -81,13 +81,6 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
81
81
  - Default: `3`
82
82
  - Recovery replays before replay divergence is recorded as corruption.
83
83
 
84
- ### `WORKFLOW_DISABLE_LAZY_HOOK_RESUME`
85
-
86
- - Default: enabled (lazy hook resume on)
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.
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`.
90
-
91
84
  ### `WORKFLOW_DEPLOYMENT_MISMATCH_MAX_RETRIES`
92
85
 
93
86
  - Default: `3`
@@ -99,7 +92,7 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
99
92
  ### `WORKFLOW_RESILIENT_STEP_DISPATCH`
100
93
 
101
94
  - Default: disabled
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`).
95
+ - 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 legacy lazy hook resume's `hookInput` (which current producers no longer send; see [durable hook resume](/docs/changelog/lazy-hook-resume)).
103
96
  - 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.
104
97
  - 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).
105
98
  - 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`.
@@ -193,7 +186,7 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
193
186
 
194
187
  - Default: enabled
195
188
  - 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.
196
- - Suspensions involving hooks, waits, or attributes, as well as any replay divergence, always fall back to a full replay.
189
+ - A step-driven suspension can keep the VM retained even when hooks are open or created at the same boundary. Hook-only suspensions park the invocation. Suspensions involving waits or attributes, runs with an open wait, and any replay divergence fall back to a full replay.
197
190
  - 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.
198
191
  - Set `0` or `false` to replay from scratch in a fresh VM on every iteration.
199
192
 
@@ -1,37 +1,46 @@
1
1
  ---
2
2
  title: corrupted-event-log
3
- description: The workflow's event log contains an event that no consumer can process, indicating corruption or invalid state.
3
+ description: The workflow's event log contains an event that cannot be processed or a stored payload that cannot be read.
4
4
  type: troubleshooting
5
- summary: Resolve corrupted event log errors caused by orphaned or unattributable events.
5
+ summary: Resolve corrupted event log errors caused by invalid events or unreadable stored payloads.
6
6
  prerequisites:
7
7
  - /docs/foundations/workflows-and-steps
8
8
  related:
9
9
  - /docs/foundations/errors-and-retries
10
10
  ---
11
11
 
12
- This error occurs when the Workflow runtime repeatedly cannot replay events in the event log. This usually means the event log is in an invalid state, such as an orphaned event or one no consumer can attribute to anything the workflow did, or that a runtime determinism bug persists across retry attempts.
12
+ This error occurs when the Workflow runtime cannot safely replay the event log. The log may be in an invalid state, such as an orphaned event or one no consumer can attribute to anything the workflow did, or it may reference a stored payload that the World can no longer read.
13
13
 
14
- This is a **workflow-level fatal error**. It cannot be caught or handled inside your workflow code. The runtime first retries transient replay divergence automatically; it marks the run as failed with this error only after replay still cannot recover.
14
+ This is a **workflow-level fatal error**. It cannot be caught or handled inside your workflow code. The runtime retries transient replay divergence automatically, but an unreadable stored payload is terminal immediately because replaying cannot restore it.
15
15
 
16
16
  ## Error message
17
17
 
18
+ For replay divergence:
19
+
18
20
  ```text
19
21
  Workflow replay diverged <divergenceCount> times after <maxRecoveryReplays> recovery replays; latest divergent event was <eventId>. Last divergence: <details>
20
22
  ```
21
23
 
24
+ For an unreadable stored payload:
25
+
26
+ ```text
27
+ the event log references a payload that no longer exists in storage: <details>
28
+ ```
29
+
22
30
  ## Why this happens
23
31
 
24
32
  Workflows persist their progress as an ordered event log. During replay, the runtime processes each event in sequence. Every event must be consumed by a matching callback, such as a step or sleep waiting for its result. An event no callback ever claims is one the runtime would have to drop to finish the run, so it fails the run instead of returning a result that silently ignored it.
25
33
 
26
34
  A delivery written from outside the replay, such as a hook firing or a step completing on another invocation, can land ahead of the events the replay is writing itself. That is ordinary concurrency rather than corruption, so the runtime holds such an event and offers it to each consumer the replay registers afterwards. The failure comes only when the workflow function returns while an event is still held, at which point no consumer can ever appear. A replay that suspends still holding one reports it on the span (`workflow.events.parked.count`, `.event_id`, `.event_type`) and leaves the decision to the replay that follows.
27
35
 
28
- Before failing, the runtime retries a divergent replay and surfaces this terminal error only if replay still cannot recover.
36
+ Before failing on divergence, the runtime retries the replay and surfaces this terminal error only if replay still cannot recover. It does not retry a payload that the World reports as permanently missing.
29
37
 
30
38
  Common scenarios that produce this error:
31
39
 
32
40
  - **An unclaimed event that repeats nothing**: A duplicate of a kind the log already records for that entity is read past rather than failing the run, so a second `step_completed` or `wait_completed` is not this error (see [Duplicate Events](/docs/how-it-works/event-sourcing#duplicate-events)). What fails is an unclaimed event with no earlier counterpart to defer to: a `step_started` behind a `step_completed` on a log that never recorded a `step_started`, for instance. No consumer remains for the step, and there is no earlier event of that kind the replay could be reading instead.
33
41
  - **Orphaned events**: A `step_completed` or `wait_completed` event whose `correlationId` doesn't match any step or sleep in the workflow code, so the replay reaches its end still holding it.
34
42
  - **A hole in the log**: Events are numbered by their position in the run's log, and those positions are dense, so a position below the log's highest that holds no event means the log the replay loaded is incomplete. The runtime cannot tell a position no write ever occupied from one whose event it failed to read, so it refuses to replay rather than produce a result that may be silently wrong. See [`WORKFLOW_SLOT_GAP_CHECK`](/docs/configuration/runtime-tuning#workflow_slot_gap_check).
43
+ - **An unreadable stored payload**: An event row still references a payload object, but the World reports that the object no longer exists in its storage. The same log would fail on every replay, so the run fails immediately instead of retrying forever.
35
44
 
36
45
  ## What to do
37
46
 
@@ -47,7 +56,7 @@ npm install workflow@latest
47
56
 
48
57
  ### 2. Retry the failed run
49
58
 
50
- If this error is displayed, automatic replay recovery has already been exhausted and the run has been marked as `failed`. You can re-run it using the **Re-run** button in the Workflow Dashboard.
59
+ If this error reports replay divergence, automatic replay recovery has already been exhausted. If it reports an unreadable payload, recovery cannot recreate that payload. In either case, the run has been marked as `failed`. You can re-run the workflow using the **Re-run** button in the Workflow Dashboard; a re-run starts a new run with a new event log.
51
60
 
52
61
  ### 3. Report the issue
53
62
 
@@ -55,7 +64,7 @@ If the error persists after upgrading, [open an issue on GitHub](https://github.
55
64
 
56
65
  - The version of the `workflow` package you are using
57
66
  - The run ID(s) of the affected workflow run(s)
58
- - The error message (including `eventType`, `correlationId`, and `eventId`)
67
+ - The complete error message, including any `eventType`, `correlationId`, `eventId`, or payload details
59
68
  - Any details about the event log or the workflow that triggered the error
60
69
 
61
70
  ## This error cannot be caught
@@ -195,7 +195,7 @@ try {
195
195
  | `MAX_DELIVERIES_EXCEEDED` | The run exceeded the maximum number of queue deliveries |
196
196
  | `REPLAY_TIMEOUT` | A workflow replay exceeded the maximum allowed duration |
197
197
  | `REPLAY_DIVERGENCE` | A replay could not consume the event log deterministically, usually because of non-deterministic workflow code. |
198
- | `CORRUPTED_EVENT_LOG` | The event log contains orphaned or mismatched events and cannot be replayed. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
198
+ | `CORRUPTED_EVENT_LOG` | The event log cannot be replayed: it contains orphaned or mismatched events, or one of its stored payloads is no longer readable from the World's storage. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
199
199
  | `WORLD_CONTRACT_ERROR` | A World response violated the SDK contract; points at a World implementation bug |
200
200
  | `RUNTIME_ERROR` | An internal runtime error. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
201
201
 
@@ -33,7 +33,7 @@ The largest change in v5 has no API surface: the runtime does far less work per
33
33
 
34
34
  **The runtime avoids waiting on the persistence layer where it can determine that is safe for your workload.** The runtime skips many API calls when they aren't needed, such as requesting the event log on a run's first invocation. Step creation is folded into step execution rather than being its own round trip. The inline loop consumes the event-log delta from the previous step's write instead of re-listing events. Each optimization is gated on specific runtime conditions and can be turned off individually. See [Runtime tuning](/docs/configuration/runtime-tuning).
35
35
 
36
- **The workflow VM is kept alive across inline steps.** Within one invocation, a step-only suspension keeps the live VM and hydrated state, so the next iteration appends only the newly written events instead of rebuilding the sandbox and replaying the whole log. Step inputs made of plain data or standard built-ins keep this fast path; see [`WORKFLOW_RETAINED_VM`](/docs/configuration/runtime-tuning#workflow_retained_vm).
36
+ **The workflow VM is kept alive across inline steps.** Within one invocation, a step-driven suspension keeps the live VM and hydrated state, including when hooks are open or created at the same boundary, so the next iteration appends only the newly written events instead of rebuilding the sandbox and replaying the whole log. Step inputs made of plain data or standard built-ins keep this fast path; see [`WORKFLOW_RETAINED_VM`](/docs/configuration/runtime-tuning#workflow_retained_vm).
37
37
 
38
38
  **Resuming a hook takes one round trip instead of two.** `resumeHook()` writes the `hook_received` event and dispatches the queue message concurrently, with a `(runId, resumeId)` dedup constraint keeping the two writers converging on exactly one event. See [Resilient hook resumption](/docs/changelog/resilient-resume).
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow",
3
- "version": "5.0.0-beta.44",
3
+ "version": "5.0.0-beta.47",
4
4
  "description": "Workflow SDK - Build durable, resilient, and observable workflows",
5
5
  "main": "dist/typescript-plugin.cjs",
6
6
  "type": "module",
@@ -59,18 +59,18 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "ms": "2.1.3",
62
- "@workflow/astro": "5.0.0-beta.44",
63
- "@workflow/cli": "5.0.0-beta.44",
64
- "@workflow/core": "5.0.0-beta.44",
65
- "@workflow/errors": "5.0.0-beta.18",
62
+ "@workflow/astro": "5.0.0-beta.47",
63
+ "@workflow/cli": "5.0.0-beta.47",
64
+ "@workflow/core": "5.0.0-beta.47",
65
+ "@workflow/errors": "5.0.0-beta.19",
66
66
  "@workflow/typescript-plugin": "5.0.0-beta.5",
67
- "@workflow/utils": "5.0.0-beta.9",
68
- "@workflow/next": "5.0.0-beta.44",
69
- "@workflow/nest": "5.0.0-beta.44",
70
- "@workflow/nitro": "5.0.0-beta.44",
71
- "@workflow/nuxt": "5.0.0-beta.44",
72
- "@workflow/sveltekit": "5.0.0-beta.44",
73
- "@workflow/rollup": "5.0.0-beta.44"
67
+ "@workflow/utils": "5.0.0-beta.10",
68
+ "@workflow/next": "5.0.0-beta.47",
69
+ "@workflow/nest": "5.0.0-beta.47",
70
+ "@workflow/nitro": "5.0.0-beta.47",
71
+ "@workflow/nuxt": "5.0.0-beta.47",
72
+ "@workflow/sveltekit": "5.0.0-beta.47",
73
+ "@workflow/rollup": "5.0.0-beta.47"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/ms": "2.1.0",