Skip to content

[core] Detect when out-of-band events can change a replay's path - #4683

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/out-of-band-observation
Oct 6, 2026
Merged

VaguelySerious merged 2 commits into
mainfrom
peter/out-of-band-observation

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Detection only. Nothing in the runtime branches on it yet. This is groundwork for a future run-ahead mode, where an inline step's successor starts before the step's step_completed write returns. Today's fast paths de-opt as soon as any hook or wait is open. This PR classifies, at each inline-step boundary, whether an event from an out-of-band writer could change what the boundary leads to.

What it detects

  • Hooks: a hook counts as observed only while workflow code is waiting on its next payload (await hook, a pending for await iteration, .then). createHook registers a probe on the orchestrator context. The suspension snapshots it into WorkflowSuspension.observedHookIds when the VM goes idle. An unobserved hook's hook_received is buffered as an unarmed barrier and doesn't move the clock, so it can't change a decision until the first await.
  • AbortController system hooks: always observed, because signal.aborted is read synchronously.
  • Waits: sleep awaiters can't be seen. A wait counts as path-changing when its resumeAt falls inside the invocation's inline window plus clock skew (reuses waitDueThisInvocation). run.wakeUp() ignores resumeAt and is not covered; this is documented.
  • Steps: any pending step this invocation doesn't execute itself counts as path-changing, because step awaiters are invisible.
  • QuickJS: not covered. Its suspensions carry no observedHookIds, so every open hook counts as observed.

runtime/out-of-band-observation.ts documents the writer table, why unobserved hooks are inert, how a run-ahead gate should de-opt on this signal, and why run-ahead needs a pipeline-depth cap.

Output

observeOutOfBandWriters() returns an OutOfBandObservation (inert plus per-reason counts). It is computed in the inline batch gate in runtime.ts, only for batches that run a step inline. Per-invocation counters go on the span:

  • workflow.out_of_band.inert_boundaries
  • workflow.out_of_band.observed_hook_boundaries
  • workflow.out_of_band.unknown_hook_boundaries (open hooks on a suspension without awaiter tracking, e.g. QuickJS)
  • workflow.out_of_band.abort_signal_boundaries
  • workflow.out_of_band.external_step_boundaries
  • workflow.out_of_band.wait_due_boundaries

These measure how many boundaries run-ahead could pipeline.

Tests

out-of-band-observation.test.ts:

  • Unit tests for the classifier.
  • A sweep that replays five workflow shapes (late await, for await loop, background .then, hook raced against a step, and a sleep winning a race after an unawaited createHook). For every log position, it records whether the prefix reported the hook observed. It then inserts a payload there with a far-future createdAt and checks whether any step decision changes.
  • Asserted: an insertion at an unobserved position never changes a decision. The observed cases do change, so the test catches a broken detector. Forcing the probe to false fails 4 of the tests.

🤖 Generated with Claude Code

Classify, per inline-step boundary, whether any event an out-of-band writer
could append (hook payloads, AbortController aborts, wait timers, steps run by
other executors) is able to change the decisions the suspension leads to.
Hooks are classified by whether workflow code is waiting on their next payload;
waits by whether their resumeAt falls inside the invocation's inline window.

Nothing branches on the result yet. It is recorded on the invocation span and
documented as the gate a future run-ahead must consult, including the de-opt
and pipeline-depth rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
example-nextjs-workflow-turbopack Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
example-nextjs-workflow-webpack Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
example-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-astro-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-express-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-fastify-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-hono-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-nestjs-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-nitro-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-nuxt-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-python-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-sveltekit-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-tanstack-start-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workbench-vite-workflow Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workflow-docs Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workflow-swc-playground Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workflow-tarballs Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC
workflow-web Ready Ready Preview, v0 Oct 6, 2026 10:09pm UTC

@changeset-bot

changeset-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3e3a561

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@workflow/core Patch
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
workflow Patch
@workflow/world-testing Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch
@workflow/world-vercel Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 3e3a561 · Tue, 06 Oct 2026 22:32:07 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 345 2197 🔴 2309 🔴 2447 🔴 30
TTFS stream 358 2316 🔴 2371 🔴 2651 🔴 30
TTFS hook + stream 2458 2572 🔴 2606 🔴 2676 🔴 30
Fan-out TTFS Promise.all(100 steps) 534 848 981 2686 10
Fan-out TTLS Promise.all(100 steps) 2948 4491 4842 6229 10
STSO 1020 steps (inline) 118 155 178 329 1019
WO 1020 steps 154789 154789 154789 154789 1
CRTT first chunk (pooled) 30 72 110 171 28

Streams

Scenario CRTT 1st p75 p90 p99 CDV max iters
paced control (100/s, 60B) 40.5 81 135 350 124 10
size sweep (100/s, 160B-12KB) 44.5 89 162 335 144 10
replay gateway-gpt-5.4-nano-2000t (1x) 51 77 113 309 165 3
replay eve-gpt-5.6-sol-2000t (1x) 69 75 103 245 220 2
replay eve-gpt-5.6-sol-2000t (2x) 45 91 146 1140 160 3
📈 STSO distribution (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: 154408ms over 1019 samples

No main baseline with raw samples yet — showing this run's distribution on its own; the diff appears once a run on main has recorded them.

100-150 ms  ████████████████████████  steps 688
150-200 ms  ██████████                steps 283
200-250 ms  █                         steps  32
250-300 ms  █                         steps   4
300-350 ms  █                         steps   2
350-400 ms  █                         steps   3
400-450 ms  █                         steps   1
450-500 ms  █                         steps   1
500-550 ms  █                         steps   2
550-600 ms  █                         steps   1
600-650 ms  █                         steps   1
900-950 ms  █                         steps   1
📈 CRTT drill-down (RTT distributions & profiles)

No main baseline yet — percentages appear once a run on main has recorded CRTT.

variant  RTT 1ms→5s+     avg  p50  p90   p99     n
control  ·····▃█▁▁····  69.8   60  135   350  3000
sweep    ·····▃█▂▁····  74.3   64  162   335  3000
gw 1x    ·····▅█▂▁····  64.5   55  113   309  5295
eve 1x   ·····▅█▂▁····  63.8   56  103   245  5186
eve 2x   ·····▂█▂▁▁▁··    92   68  146  1140  7779

RTT over stream progress (avg per tenth of stream, bars scaled min→max):

control  ▇▄▃▁▄▆█▃▂▃  60–82ms
sweep    ▅█▄▅██▂▇▄▁  63–82ms
gw 1x    ▆█▃▃▃▃▁▃▄▄  53–83ms
eve 1x   ▄▅▁▂▄▂▅█▇▇  59–69ms
eve 2x   ▁▁▁▁▁▁▂█▁▁  67–258ms

RTT by chunk size (avg per log size bin, ~160B → ~12KB serialized, bars scaled min→max):

sweep  ▇██▄▅▁▁  72–76ms

Delivery jitter over stream progress (avg positive CDV per tenth of stream, bars scaled min→max):

control  █▄▂▁▄▂▆▃▂▄  14–20ms
sweep    ▄▄▃▁█▃▂▅▂▁  19–28ms
gw 1x    ▇█▃▂▄▄▁▃▄▅  10–17ms
eve 1x   ▂▃▁▃▄▄▁▄▅█  14–21ms
eve 2x   ▂▂▁▂▂▂▅█▂▄  13–24ms
📜 Previous results (1)

d086b1e

Tue, 06 Oct 2026 19:53:34 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 422 (-42%) 💚 2240 🔴 (+105%) 🔻 2251 🔴 (+102%) 🔻 2348 🔴 (+95%) 🔻 30
TTFS stream 1945 (+784%) 🔻 2182 🔴 (+108%) 🔻 2226 🔴 (+103%) 🔻 2454 🔴 (+72%) 🔻 30
TTFS hook + stream 618 (-30%) 💚 2455 🔴 (+85%) 🔻 2513 🔴 (+79%) 🔻 2753 🔴 (+76%) 🔻 30
Fan-out TTFS Promise.all(100 steps) 518 (-40%) 💚 683 (-60%) 💚 698 (-61%) 💚 2558 (+37%) 🔻 10
Fan-out TTLS Promise.all(100 steps) 1803 (-21%) 💚 3031 (-17%) 💚 3044 (-19%) 💚 8233 (+14%) 10
STSO 1020 steps (inline) 104 (+4.0%) 143 (+10%) 159 (+3.2%) 233 (+15%) 1019
WO 1020 steps 144126 (+9.4%) 144126 (+9.4%) 144126 (+9.4%) 144126 (+9.4%) 1
CRTT first chunk (pooled) 25 (-65%) 💚 46 (-68%) 💚 93 (-56%) 💚 146 (-59%) 💚 28

Streams

Scenario CRTT 1st p75 p90 p99 CDV max iters
paced control (100/s, 60B) 33.5 (-66%) 69 (-64%) 120 (-60%) 335 (-32%) 89 (-60%) 10
size sweep (100/s, 160B-12KB) 32 (-69%) 65 (-71%) 122 (-65%) 559 (-12%) 120 (-43%) 10
replay gateway-gpt-5.4-nano-2000t (1x) 34 (-75%) 63 (-67%) 153 (-52%) 2438 (+325%) 546 (+25%) 3
replay eve-gpt-5.6-sol-2000t (1x) 45.5 (-56%) 72 (-63%) 105 (-66%) 270 (-51%) 290 (-36%) 2
replay eve-gpt-5.6-sol-2000t (2x) 41 (-71%) 71 (-78%) 130 (-73%) 438 (-53%) 286 (-39%) 3
ℹ️ Metric definitions & methodology

Streams: first-chunk RTT (the stream-open path, before any buffering/backpressure), CRTT percentiles, and worst delivery stall (CDV max). Cells are medians across iterations; per-run values in the artifacts. No 🔴/🟢 marks until targets attach.

The collapsed STSO distribution section above buckets every step gap, split inline (same warm process — pure framework overhead) vs queue-hop (fresh process — dispatch, reinit, replay). █ = main, ┃ = this run, ░ = fill.

The collapsed CRTT drill-down: per-variant RTT histograms (fixed log bins, · = empty) and mean RTT/positive-CDV profile lines over stream progress and chunk size. Histograms, avgs, and profiles merge exactly across runs; p50–p99 are percentile-of-percentiles. Per-index rows live in the artifacts.

Metrics — TTFS: time to first step body (in-deployment start() → first step body) · Fan-out TTFS: fan-out time to first step (in-deployment start() → first of the parallel step bodies to complete) · Fan-out TTLS: fan-out time to last step (in-deployment start() → last of the parallel step bodies to complete, i.e. when the Promise.all resolves) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) · CRTT: chunk round-trip time (per-chunk write → read latency, one clock domain: deployment → stream backend → same deployment) · CDV: chunk delay variation / delivery jitter (inter-arrival gap minus inter-write gap per seq-adjacent pair; skew-free; the row is each run's MAX positive value, so one stall moves it)

Scenarios — step: one trivial no-op step, no stream; no hooks, so the run stays in turbo mode (in-process fast path) · stream: one streaming step; no hooks, so the run stays in turbo mode (in-process fast path) · hook + stream: registers a hook before one step, which exits turbo mode (dispatch path) · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges, and WO is the whole-run overhead outside step bodies · Promise.all(100 steps): 100 trivial no-op steps started together in a single Promise.all; Fan-out TTFS is the first of them to complete and Fan-out TTLS the last, both from the in-deployment clientStart, so their gap is the spread the runtime adds across the fan-out · paced control (100/s, 60B): the control: 300 tiny (~60B) deltas metronome-paced at 100/s — zero workload structure, so it reads the transport floor and flush cadence, and disambiguates transport-wide vs workload-specific when a replay row moves · size sweep (100/s, 160B-12KB): same pacing as the control with deltas padded in rotation across seven log-spaced sizes (~160B–12KB) — rotation decouples size from stream position, so it isolates whether chunk size causes latency · replay gateway-gpt-5.4-nano-2000t (1x): raw provider SSE cadence captured at the AI gateway boundary (gpt-5.4-nano, the most popular gateway model; per-token deltas p50 208B = the modal production chunk size), replayed exactly as measured — the typical customer's workload; its CDV is the typical customer's real delivery jitter · replay eve-gpt-5.6-sol-2000t (1x): a captured eve turn (gpt-5.6-sol, the most-used demanding eve model; ~2000 output tokens = production p50 turn length) replayed exactly as measured — eve's envelope protocol re-ships the cumulative message so sizes ramp 142B→13KB; the demanding outlier tenant's reality · replay eve-gpt-5.6-sol-2000t (2x): the same eve capture at 2x — the headroom/stress row; real fast-tier models emit the same chunk sizes at proportionally higher rate, so time compression is a faithful speed model · first chunk (pooled): every run's seq-0 RTT pooled across all stream scenarios — the first chunk precedes any workload differentiation, so pooling samples one shared stream-open path with exact percentiles

Replay cadences (semantic sha256) — eve-gpt-5.6-sol-2000t eaf22f5946e7c61f3c65c7006d550df180cfabd4e706254a09f22aec0cfb420d · gateway-gpt-5.4-nano-2000t 6f24ac518b6b83ff1d0e85a5fe78230db192716d66a7fc6b2fe022752001d041

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600

All timestamps are deployment-side; runs are triggered in-deployment, so the CI runner and api.vercel.com sit outside every measured window. TTFS = start() → first step body (includes dispatch + any cold start); Fan-out TTFS/TTLS = first/last step completion of one Promise.all from the same anchor (the gap is the runtime’s fan-out spread); STSO/WO between step bodies; CRTT inside the workflow (excludes the api.vercel.com read path).

Cold starts stay in the numbers (real bursty-workload latency, inflates P75+); Best is the warm floor.

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

✅ All tests passed

⚠️ Flaky E2E Tests (passed on retry)

These tests failed at least once and passed on a retry. A recurring entry here is a real race worth investigating.

  • a chain of takeovers: each victim ends with HookForceClaimedError, deliveries follow the current owner (nextjs-webpack · local-dev / local / node / stable)
  • a run can take over its own earlier hook (sveltekit · local-dev / local / node / stable)

🛠 Infra Events (absorbed by the harness)

Platform anomalies the e2e harness detected and worked around (e.g. a run the queue never picked up, replaced by a fresh run). Clustered timestamps indicate a backend blip; a steady drip indicates a platform issue worth escalating.

  • run-pickup-stall · runs app-generated source against a registered step (tanstack-start) · at 22:11:05Z · abandoned wrun_01M49M9YY5XX91HGNTWK9FTG3C
  • cold-start-warmup · suite warmup (tanstack-start) · at 22:11:08Z · abandoned wrun_01M49M9YY3KVWR131F8XDQC836
  • run-pickup-stall · workflow throw of a non-Error value round-trips verbatim as cause (nuxt) · at 22:12:02Z · abandoned wrun_01M49MBQ2A0HC0NQ0EBEZZ8HYC
  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (nextjs-webpack) · at 22:17:12Z · abandoned wrun_01M49MN55AEMHKQRQYJ19JT89H

E2E Test Summary

Summary
Passed Failed Skipped Total
✅ ▲ Vercel Production 3904 0 875 4779
✅ 💻 Local Development 4582 0 551 5133
✅ 📦 Local Production 4582 0 551 5133
✅ 🐘 Local Postgres 4582 0 551 5133
✅ 🪟 Windows 342 0 12 354
✅ 🌐 Cross-language Conformance 68 0 84 152
✅ dynamic-runs 0 0 0 0
✅ vercel-http-transport 879 0 183 1062
✅ vercel-multi-region 27 0 0 27
✅ vercel-ws-transport 595 0 113 708
Total 19561 0 2920 22481
Details by Category

✅ ▲ Vercel Production

App Passed Failed Skipped
✅ astro-node 142 0 35
✅ astro-quickjs 142 0 35
✅ example-node 142 0 35
✅ example-quickjs 142 0 35
✅ express-node 142 0 35
✅ express-quickjs 142 0 35
✅ fastify-node 142 0 35
✅ fastify-quickjs 142 0 35
✅ hono-node 142 0 35
✅ hono-quickjs 142 0 35
✅ nest-node 142 0 35
✅ nest-quickjs 142 0 35
✅ nextjs-turbopack-node 169 0 8
✅ nextjs-turbopack-quickjs 169 0 8
✅ nextjs-webpack-node 169 0 8
✅ nextjs-webpack-quickjs 169 0 8
✅ nitro-node 142 0 35
✅ nitro-quickjs 142 0 35
✅ nuxt-node 142 0 35
✅ nuxt-quickjs 142 0 35
✅ python-node 66 0 111
✅ sveltekit-node 161 0 16
✅ sveltekit-quickjs 161 0 16
✅ tanstack-start-node 142 0 35
✅ tanstack-start-quickjs 142 0 35
✅ vite-node 142 0 35
✅ vite-quickjs 142 0 35

✅ 💻 Local Development

App Passed Failed Skipped
✅ astro-stable-node 148 0 29
✅ astro-stable-quickjs 148 0 29
✅ express-stable-node 148 0 29
✅ express-stable-quickjs 148 0 29
✅ fastify-stable-node 148 0 29
✅ fastify-stable-quickjs 148 0 29
✅ hono-stable-node 148 0 29
✅ hono-stable-quickjs 148 0 29
✅ nest-stable-node 148 0 29
✅ nest-stable-quickjs 148 0 29
✅ nextjs-turbopack-canary-node 176 0 1
✅ nextjs-turbopack-canary-quickjs 176 0 1
✅ nextjs-turbopack-quickjs-snapshot 176 0 1
✅ nextjs-turbopack-stable-node 176 0 1
✅ nextjs-turbopack-stable-quickjs 176 0 1
✅ nextjs-webpack-canary-node 176 0 1
✅ nextjs-webpack-canary-quickjs 176 0 1
✅ nextjs-webpack-stable-node 176 0 1
✅ nextjs-webpack-stable-quickjs 176 0 1
✅ nitro-stable-node 148 0 29
✅ nitro-stable-quickjs 148 0 29
✅ nuxt-stable-node 148 0 29
✅ nuxt-stable-quickjs 148 0 29
✅ sveltekit-stable-node 167 0 10
✅ sveltekit-stable-quickjs 167 0 10
✅ tanstack-start-node 148 0 29
✅ tanstack-start-quickjs 148 0 29
✅ vite-stable-node 148 0 29
✅ vite-stable-quickjs 148 0 29

✅ 📦 Local Production

App Passed Failed Skipped
✅ astro-stable-node 148 0 29
✅ astro-stable-quickjs 148 0 29
✅ express-stable-node 148 0 29
✅ express-stable-quickjs 148 0 29
✅ fastify-stable-node 148 0 29
✅ fastify-stable-quickjs 148 0 29
✅ hono-stable-node 148 0 29
✅ hono-stable-quickjs 148 0 29
✅ nest-stable-node 148 0 29
✅ nest-stable-quickjs 148 0 29
✅ nextjs-turbopack-canary-node 176 0 1
✅ nextjs-turbopack-canary-quickjs 176 0 1
✅ nextjs-turbopack-quickjs-snapshot 176 0 1
✅ nextjs-turbopack-stable-node 176 0 1
✅ nextjs-turbopack-stable-quickjs 176 0 1
✅ nextjs-webpack-canary-node 176 0 1
✅ nextjs-webpack-canary-quickjs 176 0 1
✅ nextjs-webpack-stable-node 176 0 1
✅ nextjs-webpack-stable-quickjs 176 0 1
✅ nitro-stable-node 148 0 29
✅ nitro-stable-quickjs 148 0 29
✅ nuxt-stable-node 148 0 29
✅ nuxt-stable-quickjs 148 0 29
✅ sveltekit-stable-node 167 0 10
✅ sveltekit-stable-quickjs 167 0 10
✅ tanstack-start-node 148 0 29
✅ tanstack-start-quickjs 148 0 29
✅ vite-stable-node 148 0 29
✅ vite-stable-quickjs 148 0 29

✅ 🐘 Local Postgres

App Passed Failed Skipped
✅ astro-stable-node 148 0 29
✅ astro-stable-quickjs 148 0 29
✅ express-stable-node 148 0 29
✅ express-stable-quickjs 148 0 29
✅ fastify-stable-node 148 0 29
✅ fastify-stable-quickjs 148 0 29
✅ hono-stable-node 148 0 29
✅ hono-stable-quickjs 148 0 29
✅ nest-stable-node 148 0 29
✅ nest-stable-quickjs 148 0 29
✅ nextjs-turbopack-canary-node 176 0 1
✅ nextjs-turbopack-canary-quickjs 176 0 1
✅ nextjs-turbopack-quickjs-snapshot 176 0 1
✅ nextjs-turbopack-stable-node 176 0 1
✅ nextjs-turbopack-stable-quickjs 176 0 1
✅ nextjs-webpack-canary-node 176 0 1
✅ nextjs-webpack-canary-quickjs 176 0 1
✅ nextjs-webpack-stable-node 176 0 1
✅ nextjs-webpack-stable-quickjs 176 0 1
✅ nitro-stable-node 148 0 29
✅ nitro-stable-quickjs 148 0 29
✅ nuxt-stable-node 148 0 29
✅ nuxt-stable-quickjs 148 0 29
✅ sveltekit-stable-node 167 0 10
✅ sveltekit-stable-quickjs 167 0 10
✅ tanstack-start-node 148 0 29
✅ tanstack-start-quickjs 148 0 29
✅ vite-stable-node 148 0 29
✅ vite-stable-quickjs 148 0 29

✅ 🪟 Windows

App Passed Failed Skipped
✅ nextjs-turbopack-node 171 0 6
✅ nextjs-turbopack-quickjs 171 0 6

✅ 🌐 Cross-language Conformance

App Passed Failed Skipped
✅ python 68 0 84

✅ dynamic-runs

App Passed Failed Skipped
✅ astro-vercel 0 0 0
✅ example-vercel 0 0 0
✅ express-vercel 0 0 0
✅ fastify-vercel 0 0 0
✅ hono-vercel 0 0 0
✅ nest-vercel 0 0 0
✅ nextjs-turbopack-vercel 0 0 0
✅ nextjs-webpack-vercel 0 0 0
✅ nitro-vercel 0 0 0
✅ nuxt-vercel 0 0 0
✅ sveltekit-vercel 0 0 0
✅ tanstack-start-vercel 0 0 0
✅ vite-vercel 0 0 0

✅ vercel-http-transport

App Passed Failed Skipped
✅ example 142 0 35
✅ express 142 0 35
✅ hono 142 0 35
✅ nextjs-turbopack 169 0 8
✅ nitro 142 0 35
✅ vite 142 0 35

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

✅ vercel-ws-transport

App Passed Failed Skipped
✅ example 142 0 35
✅ express 142 0 35
✅ nextjs-turbopack 169 0 8
✅ vite 142 0 35

📋 View full workflow run

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 world-sim scenario book — 1 fail of 42 total

fence=per-spec

scenario outcome events virt replay violations
✅ smoke-no-steps completed 3 0ms ok 0
✅ smoke-one-step completed 6 0ms ok 0
✅ hook-at-step-started completed 12 0ms ok 0
✅ hook-at-step-completed completed 12 0ms ok 0
✅ hook-at-hook-created completed 12 0ms ok 0
✅ deadline-hook-wins completed 7 1.0h ok 0
✅ deadline-expires completed 7 1.0h ok 0
✅ step-vs-timer-early-settlement completed 8 1.0h ok 0
✅ long-sleep completed 11 30.0d ok 0
✅ hook-never-arrives stalled 3 0ms skipped 0
✅ step-retries-twice completed 10 2.0s ok 0
✅ parallel-steps completed 9 0ms ok 0
✅ hook-on-execution-state completed 12 0ms ok 0
✅ peek-hook-before-branch completed 12 0ms ok 0
✅ peek-hook-after-branch completed 12 0ms ok 0
✅ peek-hook-at-registration completed 12 0ms ok 0
✅ race-hook-before-probe completed 12 0ms ok 0
✅ race-hook-after-probe completed 12 0ms ok 0
✅ race-duplicate-delivery completed 13 0ms ok 0
✅ attr-hook-before-step completed 11 0ms ok 0
✅ attr-hook-after-step completed 11 0ms ok 0
✅ attr-from-step-body completed 13 0ms ok 0
✅ fork-hook-after-timeout completed 14 1.0m ok 0
✅ fork-hook-before-timeout completed 14 1.0m ok 0
✅ count-hook-after-timeout completed 17 1.0m ok 0
✅ count-hook-before-timeout completed 20 1.0m ok 0
✅ stale-read-step-count-fork completed 20 1.0m ok 0
✅ stale-read-equal-step-counts completed 14 1.0m ok 0
✅ step-vs-step-fork completed 12 0ms ok 0
✅ step-vs-step-fork-fenced completed 12 0ms ok 0
✅ fence-catches-benign-direction completed 12 5ms ok 0
✅ in-flight-before-decision completed 17 1.0m ok 0
❌ in-flight-before-decision-counted completed 17 1.0m ok 0
✅ in-flight-after-decision completed 19 2.0m ok 0
✅ stale-read-step-count-fork-fenced completed 20 1.0m ok 0
✅ fork-hook-wins completed 13 1.0m ok 0
✅ fork-timeout-wins completed 13 1.0m ok 0
✅ unclaimed-payload-under-fork completed 17 1.0m ok 0
✅ claimed-payload-under-fork completed 17 1.0m ok 0
✅ writers-independent-step-bodies completed 12 0ms ok 0
✅ writers-scripted-tempo completed 12 0ms ok 0
✅ cancel-mid-step cancelled 7 0ms skipped 0

Full trace: world-sim.txt

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor
Framework Flow route Step reg. Framework output
hono 267.4 KiB (±0) 97.4 KiB (-38 B) 2.01 MiB (+563 B)
nextjs-turbopack 274.8 KiB (±0) 426 B (±0) 987.8 KiB (+295 B)
About these numbers

Sizes are gzip; parentheses show the change against main.
Flow route and Step reg. gate this job, on raw bytes rather than the gzip shown, at max(2%, 50.0 KiB). Framework output is informational.

3e3a561 · run

@VaguelySerious
VaguelySerious marked this pull request as ready for review October 6, 2026 20:17
@VaguelySerious
VaguelySerious requested a review from a team as a code owner October 6, 2026 20:17

@TooTallNate TooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review: approve. I found nothing blocking, so there are no inline threads.

What I checked:

  • Probe correctness (hook.ts). promises.length > 0 tracks exactly the state that decides whether a hook_received registers an armed or unarmed barrier (hasWaitingConsumer uses the same expression). So "observed" and "this payload would be ordered against step results" are the same predicate and can't drift. Buffered-claim, for await between iterations, conflict and force-claim drains, and the Promise.race leftover over-report all behave as the module doc describes.
  • Ordering argument. A later wait_completed or registration delivery does gate on an unarmed hook barrier (DEFER_BEHIND). A step that follows it still gates on that armed entry, because the unarmed skip is direct, never transitive. Log order is preserved, so the "unobserved payload is inert" claim holds for the wait/registration interleavings I traced as well, not only the four swept shapes.
  • Snapshot timing. observedHookIds is taken inside the same scheduleWhenIdle callback that builds the suspension, so the probe and the queue describe the same instant. The completion-drain suspension in workflow.ts doesn't reach the batch gate. QuickJS degrades to unknownHookCount, which is conservative.
  • runtime.ts wiring. inlineCorrelationIds, ownedRecoverySteps and waitDueThisInvocation are all populated before the call. Backstopped and queued steps correctly count as external. Nothing branches on the result.
  • I ran it locally: out-of-band-observation.test.ts passes (11/11) and tsc --noEmit is clean for @workflow/core. CI is green.

Non-blocking suggestions, all fine as follow-ups:

  1. observed_hook_boundaries merges observedHookCount and unknownHookCount. If QuickJS runs reach this gate, every boundary with an open hook shows up as "observed hook" and skews the metric this PR is meant to collect. A separate unknown_hook_boundaries counter (or a workflow.out_of_band.hook_tracking: false attribute) would keep the measurement honest.
  2. observeOutOfBandWriters(...) and its new Set([...]) run on every batch suspension, but the result is only used when lazyInlineSteps.length > 0. Moving the call inside that branch avoids the allocation on the queue-only path.
  3. hookPayloadAwaiters entries are only removed on hook_conflict and hook_disposed events, not on a local dispose(). This is harmless today: the item is skipped via disposed, and the map is per-session. Dropping the entry in disposeHook too would match the "dropped once the hook can receive nothing more" doc on the field.
  4. The sweep only compares step decisions (name and args). Adding one shape with a sleep raced against a step after an unawaited createHook would pin the wait-barrier interleaving above with a test, which is the case most likely to regress once run-ahead starts relying on inert.

- Count boundaries without awaiter tracking separately (unknown_hook_boundaries)
- Classify only batches that run a step inline
- Drop a hook's awaiter probe on local dispose
- Sweep a sleep winning a race after an unawaited createHook

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@VaguelySerious

Copy link
Copy Markdown
Member Author

(AI) Addressed all four suggestions from the review in 3e3a561:

  1. Boundaries whose suspension has no awaiter tracking now count under a separate workflow.out_of_band.unknown_hook_boundaries instead of observed_hook_boundaries.
  2. observeOutOfBandWriters only runs when the batch has an inline step. outOfBandObservation is undefined otherwise.
  3. disposeHook drops the hook's entry from hookPayloadAwaiters.
  4. New sweep shape: an unawaited createHook, then sleep raced against a step with the sleep winning, so the wait_completed is delivered behind an unarmed hook barrier wherever the payload is inserted. The test asserts the sleep wins in the baseline, the hook stays unobserved until its await, and no insertion changes a step decision.

@VaguelySerious
VaguelySerious enabled auto-merge (squash) October 6, 2026 22:06
@VaguelySerious
VaguelySerious merged commit 3949c07 into main Oct 6, 2026
186 checks passed
@VaguelySerious
VaguelySerious deleted the peter/out-of-band-observation branch October 6, 2026 22:22
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

No backport to stable for 3949c07 (AI decision).

This commit adds new detection machinery (observeOutOfBandWriters, WorkflowSuspension.observedHookIds, hookPayloadAwaiters, and six new span attributes) explicitly as groundwork for a future run-ahead mode — the PR states nothing in the runtime branches on it yet. It fixes no user-visible defect, so it is additive feature/instrumentation work rather than a stability fix, and it does not belong on the maintenance line.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

3949c0717bbff96d2527ef7b5397e98684974f07

@github-actions github-actions Bot mentioned this pull request Oct 6, 2026
VaguelySerious added a commit that referenced this pull request Oct 6, 2026
#4683's out-of-band boundary classifier is kept as a standalone module; its
hooks into the replaced inline-step loop are not carried over (wiring it into
the single-orchestrator inline wait is the run-ahead follow-up).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VaguelySerious added a commit that referenced this pull request Oct 6, 2026
The node:vm orchestrator now consults observeOutOfBandWriters (#4683) at
every suspension. At an inert boundary on a World that records in-band
writes at their occurredAt (new capability inBandEventTime; world-local and
the append-only test World declare it), the inline steps it schedules run
ahead: their creation batch and their outcome go out as speculative writes
whose events join the log at the slot they will take, and the workflow
continues without waiting for them. At most WORKFLOW_RUN_AHEAD_DEPTH
(default 2, 0 disables) outcomes are unconfirmed at once.

- A non-inert boundary (observed or unknown hook, abort signal, external
  step, wait due within the window plus WORKFLOW_OPEN_WAIT_CLOCK_SKEW_MS)
  drains first: speculative writes settle and their reports are folded in
  before the boundary writes. The next inert boundary runs ahead again.
- Speculative writes are required writes in the in-band writer: a refusal
  or failure stops the writer before anything else is sent. Each commit is
  checked before the next write leaves: an event below it that would have
  made its boundary path-changing, or a stored time other than occurredAt,
  stops the delivery (loud warning) and it is redelivered. A write that
  landed at another slot only because an inert event moved it is repaired
  by reloading the log once the speculative writes settle.
- Turbo's first boundary is the trivially inert case of the same path.
- Span attributes record inert vs. drained boundaries, the de-opt reasons
  and depth-cap and stop counts.

QuickJS does not run ahead (it carries no observedHookIds).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pranaygp added a commit that referenced this pull request Oct 7, 2026
Catches the docs up with SDK changes that shipped in the last day
without (complete) docs updates.

| Change | Source PR | Docs edit |
| --- | --- | --- |
| `Serializable` type exported from `workflow` (released in 5.1.0) |
#4578, #4580 | New "Typing serializable values" subsection in
Serialization, with what the type doesn't cover |
| QuickJS snapshots VM memory by default at 1,000 events | #4690 | The
PR updated Runtime tuning. Added one sentence to the QuickJS bullet in
What's New |
| world-postgres reconnects a dropped `LISTEN` connection and re-reads
chunks for open stream readers | #4658 | Added to the Streaming bullet
under "How it works" on the Postgres World page |

I reviewed the other PRs from the last day and they need no further
docs: lifecycle hooks (#4438) and nested step IDs (#4615) shipped their
own docs, the out-of-band observation (#4683) is detection-only, the
turbo step fixes (#4685, #4667) make already-documented behavior work,
and the CLI manifest (#4376), `simple-git` bump (#4675), and benchmark
trim (#4649) aren't user-facing. The Workflows pages in `vercel/front`
were also checked and need no changes for these.

## Docs Preview

| Page | Preview |
| --- | --- |
| Serialization | [Typing serializable
values](https://workflow-docs-git-workflow-docs-cleanup-wed-7-oct.vercel.sh/docs/foundations/serialization#typing-serializable-values)
|
| What's New | [QuickJS VM
engine](https://workflow-docs-git-workflow-docs-cleanup-wed-7-oct.vercel.sh/docs/whats-new)
|
| Postgres World | [How it
works](https://workflow-docs-git-workflow-docs-cleanup-wed-7-oct.vercel.sh/worlds/postgres#how-it-works)
|

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
pranaygp pushed a commit that referenced this pull request Oct 8, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @workflow/world-local@5.1.0

### Minor Changes

- [#4718](#4718)
[`0139561`](0139561)
Thanks [@ruiconti](https://github.com/ruiconti)! - Store event and step
files in one directory per run for faster reads. Local run data written
by earlier releases is deleted on upgrade.

### Patch Changes

- [#4742](#4742)
[`b38360d`](b38360d)
Thanks [@pranaygp](https://github.com/pranaygp)! - Make a tagged
`clear()` delete only its own tag's lock files, so it no longer reopens
another tag's disposed hooks mid-run.
- Updated dependencies
[[`46b3bfb`](46b3bfb)]:
  - @workflow/world@5.0.3
  - @workflow/errors@5.0.3
## @workflow/world-vercel@5.2.0

### Minor Changes

- [#4722](#4722)
[`d0f6b91`](d0f6b91)
Thanks [@alangenfeld](https://github.com/alangenfeld)! - Stream writes
now upgrade to WebSocket by default; set
`WORKFLOW_STREAMS_TRANSPORT=http` to opt out.

### Patch Changes

- [#4712](#4712)
[`46b3bfb`](46b3bfb)
Thanks [@karthikscale3](https://github.com/karthikscale3)! - Reduce
redundant schema compilation by compiling final event schemas only and
reusing compiled v4 event-response and paginated response schemas.
- Updated dependencies
[[`46b3bfb`](46b3bfb)]:
  - @workflow/world@5.0.3
  - @workflow/errors@5.0.3
## @workflow/astro@5.0.3

### Patch Changes

- Updated dependencies
[[`1acc0ba`](1acc0ba)]:
  - @workflow/swc-plugin@5.0.1
  - @workflow/builders@5.0.3
  - @workflow/rollup@5.0.3
  - @workflow/vite@5.0.3
## @workflow/builders@5.0.3

### Patch Changes

- Updated dependencies
[[`de8d985`](de8d985),
[`306a40f`](306a40f),
[`515dc9f`](515dc9f),
[`1acc0ba`](1acc0ba),
[`878979b`](878979b),
[`3949c07`](3949c07),
[`5589df7`](5589df7)]:
  - @workflow/core@5.2.0
  - @workflow/swc-plugin@5.0.1
  - @workflow/errors@5.0.3
## @workflow/cli@5.0.3

### Patch Changes

- [#4376](#4376)
[`2036247`](2036247)
Thanks [@withkarann](https://github.com/withkarann)! - Ship an oclif
command manifest so the CLI loads only the command being run, and skip
loading the runtime on exit when no World was created. `workflow
--version` starts in about 50 ms instead of 650 ms.
- Updated dependencies
[[`de8d985`](de8d985),
[`306a40f`](306a40f),
[`46b3bfb`](46b3bfb),
[`515dc9f`](515dc9f),
[`1acc0ba`](1acc0ba),
[`878979b`](878979b),
[`3949c07`](3949c07),
[`5589df7`](5589df7),
[`28cb70d`](28cb70d),
[`0139561`](0139561),
[`b38360d`](b38360d),
[`d0f6b91`](d0f6b91)]:
  - @workflow/core@5.2.0
  - @workflow/world-vercel@5.2.0
  - @workflow/world@5.0.3
  - @workflow/swc-plugin@5.0.1
  - @workflow/web@5.0.3
  - @workflow/world-local@5.1.0
  - @workflow/builders@5.0.3
  - @workflow/errors@5.0.3
## @workflow/core@5.2.0

### Patch Changes

- [#4727](#4727)
[`de8d985`](de8d985)
Thanks [@pranaygp](https://github.com/pranaygp)! - Start the queued
branches of a wide `Promise.all` fan-out sooner. The batched fan-out now
commits its writes in `createBatch` calls of at most 16 events instead
of 32. Each queued branch's message waits on its chunk's commit and then
its chunk's batched publish, and both are faster for smaller chunks. In
production sweeps, the last branch of a 64-branch fan-out started about
90 ms sooner at p50, and of a 128-branch fan-out about 200 ms sooner,
with no change to the first branch at those widths.

- [#4324](#4324)
[`306a40f`](306a40f)
Thanks [@VaguelySerious](https://github.com/VaguelySerious)! - Deliver a
hook payload to the pending await when an earlier `Promise.race` over
the same hook was lost
  
Concurrent awaits of one hook, such as `Promise.all([hook, hook])`, may
now receive the same payload. Runs that already took the timeout branch
in this situation may fail to replay after upgrading in place.

- [#4438](#4438)
[`515dc9f`](515dc9f)
Thanks [@TooTallNate](https://github.com/TooTallNate)! - Reject
lifecycle registration inside steps, preserve reporting diagnostics for
unreadable errors, and document executor registration and
stream-lifetime constraints.

- [#4690](#4690)
[`878979b`](878979b)
Thanks [@TooTallNate](https://github.com/TooTallNate)! - Enable
VM-memory snapshotting by default for the QuickJS engine, with a
threshold of 1000 events. Set `WORKFLOW_SNAPSHOT_THRESHOLD=0` to opt
out. Runs without an encryption key (for example on world-local and
world-postgres) are still not snapshotted unless
`WORKFLOW_SNAPSHOT_ALLOW_UNENCRYPTED=1` is set.

- [#4683](#4683)
[`3949c07`](3949c07)
Thanks [@VaguelySerious](https://github.com/VaguelySerious)! - Record on
the invocation trace whether out-of-band events could affect each inline
step boundary

- [#4685](#4685)
[`5589df7`](5589df7)
Thanks [@pranaygp](https://github.com/pranaygp)! - Hold a turbo step's
awaited `step_started` until the backgrounded `run_started` lands, so an
explicit `WORKFLOW_OPTIMISTIC_INLINE_START=0` no longer gets its first
step rejected for a run that has not started yet.
- Updated dependencies
[[`46b3bfb`](46b3bfb),
[`0139561`](0139561),
[`b38360d`](b38360d),
[`d0f6b91`](d0f6b91)]:
  - @workflow/world-vercel@5.2.0
  - @workflow/world@5.0.3
  - @workflow/world-local@5.1.0
  - @workflow/errors@5.0.3
## @workflow/errors@5.0.3

### Patch Changes

- Updated dependencies
[[`46b3bfb`](46b3bfb)]:
  - @workflow/world@5.0.3
## @workflow/nest@5.1.1

### Patch Changes

- Updated dependencies
[[`1acc0ba`](1acc0ba)]:
  - @workflow/swc-plugin@5.0.1
  - @workflow/builders@5.0.3
## @workflow/next@5.0.3

### Patch Changes

- Updated dependencies
[[`de8d985`](de8d985),
[`306a40f`](306a40f),
[`515dc9f`](515dc9f),
[`1acc0ba`](1acc0ba),
[`878979b`](878979b),
[`3949c07`](3949c07),
[`5589df7`](5589df7)]:
  - @workflow/core@5.2.0
  - @workflow/swc-plugin@5.0.1
  - @workflow/builders@5.0.3
## @workflow/nitro@5.0.3

### Patch Changes

- Updated dependencies
[[`de8d985`](de8d985),
[`306a40f`](306a40f),
[`515dc9f`](515dc9f),
[`1acc0ba`](1acc0ba),
[`878979b`](878979b),
[`3949c07`](3949c07),
[`5589df7`](5589df7),
[`28cb70d`](28cb70d)]:
  - @workflow/core@5.2.0
  - @workflow/swc-plugin@5.0.1
  - @workflow/web@5.0.3
  - @workflow/builders@5.0.3
  - @workflow/rollup@5.0.3
  - @workflow/vite@5.0.3
## @workflow/nuxt@5.0.3

### Patch Changes

- Updated dependencies []:
  - @workflow/nitro@5.0.3
## @workflow/rollup@5.0.3

### Patch Changes

- Updated dependencies
[[`1acc0ba`](1acc0ba)]:
  - @workflow/swc-plugin@5.0.1
  - @workflow/builders@5.0.3
## @workflow/sveltekit@5.0.3

### Patch Changes

- Updated dependencies
[[`1acc0ba`](1acc0ba)]:
  - @workflow/swc-plugin@5.0.1
  - @workflow/builders@5.0.3
  - @workflow/rollup@5.0.3
  - @workflow/vite@5.0.3
## @workflow/swc-plugin@5.0.1

### Patch Changes

- [#4615](#4615)
[`1acc0ba`](1acc0ba)
Thanks [@karthikscale3](https://github.com/karthikscale3)! - Stabilize
nested step IDs across scopes and SWC transform modes. Self-hosted runs
that already recorded one of the previously colliding IDs may report a
replay divergence if they continue on upgraded code; restart those
in-flight runs after upgrading.
## @workflow/vite@5.0.3

### Patch Changes

- Updated dependencies []:
  - @workflow/builders@5.0.3
## @workflow/vitest@5.0.3

### Patch Changes

- Updated dependencies
[[`de8d985`](de8d985),
[`306a40f`](306a40f),
[`46b3bfb`](46b3bfb),
[`515dc9f`](515dc9f),
[`878979b`](878979b),
[`3949c07`](3949c07),
[`5589df7`](5589df7),
[`0139561`](0139561),
[`b38360d`](b38360d)]:
  - @workflow/core@5.2.0
  - @workflow/world@5.0.3
  - @workflow/world-local@5.1.0
  - @workflow/builders@5.0.3
  - @workflow/rollup@5.0.3
## @workflow/web@5.0.3

### Patch Changes

- [#4736](#4736)
[`28cb70d`](28cb70d)
Thanks [@TooTallNate](https://github.com/TooTallNate)! - Upgrade
`react-router` and `@react-router/*` to 7.18.4 to address CVE-2026-34077
and GHSA-qwww-vcr4-c8h2
- Updated dependencies
[[`0139561`](0139561),
[`b38360d`](b38360d)]:
  - @workflow/world-local@5.1.0
## @workflow/web-shared@5.0.3

### Patch Changes

- Updated dependencies
[[`de8d985`](de8d985),
[`306a40f`](306a40f),
[`46b3bfb`](46b3bfb),
[`515dc9f`](515dc9f),
[`878979b`](878979b),
[`3949c07`](3949c07),
[`5589df7`](5589df7)]:
  - @workflow/core@5.2.0
  - @workflow/world@5.0.3
## workflow@5.2.0

### Patch Changes

- [#4438](#4438)
[`515dc9f`](515dc9f)
Thanks [@TooTallNate](https://github.com/TooTallNate)! - Reject
lifecycle registration inside steps, preserve reporting diagnostics for
unreadable errors, and document executor registration and
stream-lifetime constraints.
- Updated dependencies
[[`2036247`](2036247),
[`de8d985`](de8d985),
[`306a40f`](306a40f),
[`515dc9f`](515dc9f),
[`878979b`](878979b),
[`3949c07`](3949c07),
[`5589df7`](5589df7)]:
  - @workflow/cli@5.0.3
  - @workflow/core@5.2.0
  - @workflow/next@5.0.3
  - @workflow/nitro@5.0.3
  - @workflow/typescript-plugin@5.0.0
  - @workflow/errors@5.0.3
  - @workflow/astro@5.0.3
  - @workflow/nest@5.1.1
  - @workflow/rollup@5.0.3
  - @workflow/sveltekit@5.0.3
  - @workflow/nuxt@5.0.3
## @workflow/world@5.0.3

### Patch Changes

- [#4712](#4712)
[`46b3bfb`](46b3bfb)
Thanks [@karthikscale3](https://github.com/karthikscale3)! - Reduce
redundant schema compilation by compiling final event schemas only and
reusing compiled v4 event-response and paginated response schemas.
## @workflow/world-postgres@5.0.3

### Patch Changes

- Updated dependencies
[[`46b3bfb`](46b3bfb),
[`0139561`](0139561),
[`b38360d`](b38360d)]:
  - @workflow/world@5.0.3
  - @workflow/world-local@5.1.0
  - @workflow/errors@5.0.3
## @workflow/world-testing@5.0.3

### Patch Changes

- Updated dependencies
[[`2036247`](2036247),
[`de8d985`](de8d985),
[`306a40f`](306a40f),
[`46b3bfb`](46b3bfb),
[`515dc9f`](515dc9f),
[`878979b`](878979b),
[`3949c07`](3949c07),
[`5589df7`](5589df7)]:
  - @workflow/cli@5.0.3
  - @workflow/core@5.2.0
  - @workflow/world@5.0.3
  - workflow@5.2.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview – workflow-docs — 3e3a5619 Deployed Oct 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants