[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySeriousVaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default
Three removals that stand on their own, split out of the v5 API switch.
**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.
**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.
**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.
`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.
Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 10:03pm
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 10:03pm
example-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-express-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-python-workflowErrorErrorAug 13, 2026 10:03pm
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workflow-docsReadyReadyPreview, v0Aug 13, 2026 10:03pm
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 10:03pm
workflow-tarballsReadyReadyPreviewAug 13, 2026 10:03pm
workflow-webReadyReadyPreviewAug 13, 2026 10:03pm

@changeset-bot

changeset-botBot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

This PR includes changesets to release 20 packages
NameType
@workflow/worldPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/world-testingPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
workflowPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
PassedFailedSkippedTotal
✅ ▲ Vercel Production333805623900
✅ 💻 Local Development351705394056
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total148140221717031
Details by Category

✅ ▲ Vercel Production

AppPassedFailedSkipped
✅ astro-node128028
✅ astro-quickjs128028
✅ example-node128028
✅ express-node128028
✅ express-quickjs128028
✅ fastify-node128028
✅ fastify-quickjs128028
✅ hono-node128028
✅ hono-quickjs128028
✅ nest-node128028
✅ nest-quickjs128028
✅ nextjs-turbopack-node15303
✅ nextjs-turbopack-quickjs15303
✅ nextjs-webpack-node15303
✅ nextjs-webpack-quickjs15303
✅ nitro-node128028
✅ nitro-quickjs128028
✅ nuxt-node128028
✅ nuxt-quickjs128028
✅ sveltekit-node14709
✅ sveltekit-quickjs14709
✅ tanstack-start-node128028
✅ tanstack-start-quickjs128028
✅ vite-node128028
✅ vite-quickjs128028

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep207 (+13%)531 🔴 (-52%) 💚692 🔴 (-43%) 💚1454 🔴 (-8.6%)30
TTFSstream224 (-5.5%)721 🔴 (-35%) 💚1420 🔴 (+25%) 🔻1532 🔴 (+27%) 🔻30
TTFShook + stream371 (-3.4%)1716 🔴 (+25%) 🔻1812 🔴 (+30%) 🔻1892 🔴 (+23%) 🔻30
STSO1020 steps (inline)137 (-9.3%)198 (-12%)232 (-10%)325 (-38%) 💚1019
WO1020 steps192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚1
SLstream latency96 (+2.1%)152 🔴 (+1.3%)209 🔴 (+30%) 🔻452 🔴 (+28%) 🔻30
SOstream overhead (text)126 (-20%) 💚306 🔴 (+3.4%)603 🔴 (+39%) 🔻1127 🔴 (-25%) 💚30
SOstream overhead (structured)134 (+14%)227 (-44%) 💚283 (-49%) 💚926 (-63%) 💚30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

 100-150 ms ░┃ main 0 this 61 +61
150-200 ms █████████████████░░░░░░┃ main 512 this 719 +207
200-250 ms █████┃███████ main 385 this 184 -201
250-300 ms ┃█ main 71 this 39 -32
300-350 ms ┃ main 22 this 10 -12
350-400 ms ┃ main 11 this 0 -11
400-450 ms ┃ main 5 this 2 -3
450-500 ms ┃ main 1 this 1 +0
500-550 ms ┃ main 4 this 0 -4
550-600 ms ┃ main 0 this 1 +1
600-650 ms ┃ main 1 this 1 +0
650-700 ms ┃ main 1 this 0 -1
900-950 ms ┃ main 1 this 1 +0
1200-1250 ms ┃ main 1 this 0 -1
1600-1650 ms ┃ main 1 this 0 -1
2000-2050 ms ┃ main 1 this 0 -1
2400-2450 ms ┃ main 1 this 0 -1
6050-6100 ms ┃ main 1 this 0 -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1335 (+626%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+23%) 🔻1909 🔴 (+20%) 🔻30
TTFSstream315 (+33%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+32%) 🔻1725 🔴 (+43%) 🔻30
TTFShook + stream1340 (+249%) 🔻1752 🔴 (+27%) 🔻1779 🔴 (+28%) 🔻2082 🔴 (+36%) 🔻30
STSO1020 steps (inline)119 (-21%) 💚176 (-21%) 💚200 (-22%) 💚313 (-40%) 💚1019
WO1020 steps177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚1
SLstream latency100 (+6.4%)141 🔴 (-6.0%)191 🔴 (+19%) 🔻367 🔴 (+3.7%)30
SOstream overhead (text)133 (-15%) 💚199 (-33%) 💚204 (-53%) 💚224 (-85%) 💚30
SOstream overhead (structured)132 (+12%)184 (-55%) 💚206 (-63%) 💚294 (-88%) 💚30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1278 (+430%) 🔻1355 🔴 (+19%) 🔻1407 🔴 (+20%) 🔻1481 🔴 (+17%) 🔻30
TTFSstream1302 (+375%) 🔻1346 🔴 (+16%) 🔻1372 🔴 (+13%)1419 🔴 (-9.5%)30
TTFShook + stream1594 (+299%) 🔻1713 🔴 (+23%) 🔻1768 🔴 (+25%) 🔻1849 🔴 (+24%) 🔻30
STSO1020 steps (inline)117 (+3.5%)160 (-24%) 💚182 (-28%) 💚284 (-32%) 💚1019
WO1020 steps158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚1
SLstream latency85 (-12%)115 🔴 (-29%) 💚130 🔴 (-40%) 💚173 🔴 (-54%) 💚30
SOstream overhead (text)106 (-29%) 💚166 (-42%) 💚234 (-62%) 💚3271 🔴 (+323%) 🔻30
SOstream overhead (structured)100 (-24%) 💚146 (-45%) 💚178 (-51%) 💚211 (-99%) 💚30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · 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) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Resultno regressionsno regressionsno regressions1/14 regressions
Total14141414
completed14141413
CORRUPTED_EVENT_LOG0001
USER_ERROR0000
RUNTIME_ERROR0000
stuck0000
other0000
infra0000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm65100000
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm1CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00

@pranaygppranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.
So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.
The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.
And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result1/1 regressions — partial (1 of 14 planned)6/14 regressions
Total114
completed08
CORRUPTED_EVENT_LOG11
USER_ERROR00
RUNTIME_ERROR00
stuck05
other00
infra00
Config1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60100500
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm2stuckrunningwrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm1stuckrunningwrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm6stuckrunningwrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm4stuckrunningwrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm3stuckrunningwrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

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

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySeriousVaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default
Three removals that stand on their own, split out of the v5 API switch.
**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.
**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.
**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.
`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.
Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 10:03pm
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 10:03pm
example-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-express-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-python-workflowErrorErrorAug 13, 2026 10:03pm
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workflow-docsReadyReadyPreview, v0Aug 13, 2026 10:03pm
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 10:03pm
workflow-tarballsReadyReadyPreviewAug 13, 2026 10:03pm
workflow-webReadyReadyPreviewAug 13, 2026 10:03pm

@changeset-bot

changeset-botBot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

This PR includes changesets to release 20 packages
NameType
@workflow/worldPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/world-testingPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
workflowPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
PassedFailedSkippedTotal
✅ ▲ Vercel Production333805623900
✅ 💻 Local Development351705394056
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total148140221717031
Details by Category

✅ ▲ Vercel Production

AppPassedFailedSkipped
✅ astro-node128028
✅ astro-quickjs128028
✅ example-node128028
✅ express-node128028
✅ express-quickjs128028
✅ fastify-node128028
✅ fastify-quickjs128028
✅ hono-node128028
✅ hono-quickjs128028
✅ nest-node128028
✅ nest-quickjs128028
✅ nextjs-turbopack-node15303
✅ nextjs-turbopack-quickjs15303
✅ nextjs-webpack-node15303
✅ nextjs-webpack-quickjs15303
✅ nitro-node128028
✅ nitro-quickjs128028
✅ nuxt-node128028
✅ nuxt-quickjs128028
✅ sveltekit-node14709
✅ sveltekit-quickjs14709
✅ tanstack-start-node128028
✅ tanstack-start-quickjs128028
✅ vite-node128028
✅ vite-quickjs128028

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep207 (+13%)531 🔴 (-52%) 💚692 🔴 (-43%) 💚1454 🔴 (-8.6%)30
TTFSstream224 (-5.5%)721 🔴 (-35%) 💚1420 🔴 (+25%) 🔻1532 🔴 (+27%) 🔻30
TTFShook + stream371 (-3.4%)1716 🔴 (+25%) 🔻1812 🔴 (+30%) 🔻1892 🔴 (+23%) 🔻30
STSO1020 steps (inline)137 (-9.3%)198 (-12%)232 (-10%)325 (-38%) 💚1019
WO1020 steps192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚1
SLstream latency96 (+2.1%)152 🔴 (+1.3%)209 🔴 (+30%) 🔻452 🔴 (+28%) 🔻30
SOstream overhead (text)126 (-20%) 💚306 🔴 (+3.4%)603 🔴 (+39%) 🔻1127 🔴 (-25%) 💚30
SOstream overhead (structured)134 (+14%)227 (-44%) 💚283 (-49%) 💚926 (-63%) 💚30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

 100-150 ms ░┃ main 0 this 61 +61
150-200 ms █████████████████░░░░░░┃ main 512 this 719 +207
200-250 ms █████┃███████ main 385 this 184 -201
250-300 ms ┃█ main 71 this 39 -32
300-350 ms ┃ main 22 this 10 -12
350-400 ms ┃ main 11 this 0 -11
400-450 ms ┃ main 5 this 2 -3
450-500 ms ┃ main 1 this 1 +0
500-550 ms ┃ main 4 this 0 -4
550-600 ms ┃ main 0 this 1 +1
600-650 ms ┃ main 1 this 1 +0
650-700 ms ┃ main 1 this 0 -1
900-950 ms ┃ main 1 this 1 +0
1200-1250 ms ┃ main 1 this 0 -1
1600-1650 ms ┃ main 1 this 0 -1
2000-2050 ms ┃ main 1 this 0 -1
2400-2450 ms ┃ main 1 this 0 -1
6050-6100 ms ┃ main 1 this 0 -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1335 (+626%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+23%) 🔻1909 🔴 (+20%) 🔻30
TTFSstream315 (+33%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+32%) 🔻1725 🔴 (+43%) 🔻30
TTFShook + stream1340 (+249%) 🔻1752 🔴 (+27%) 🔻1779 🔴 (+28%) 🔻2082 🔴 (+36%) 🔻30
STSO1020 steps (inline)119 (-21%) 💚176 (-21%) 💚200 (-22%) 💚313 (-40%) 💚1019
WO1020 steps177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚1
SLstream latency100 (+6.4%)141 🔴 (-6.0%)191 🔴 (+19%) 🔻367 🔴 (+3.7%)30
SOstream overhead (text)133 (-15%) 💚199 (-33%) 💚204 (-53%) 💚224 (-85%) 💚30
SOstream overhead (structured)132 (+12%)184 (-55%) 💚206 (-63%) 💚294 (-88%) 💚30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1278 (+430%) 🔻1355 🔴 (+19%) 🔻1407 🔴 (+20%) 🔻1481 🔴 (+17%) 🔻30
TTFSstream1302 (+375%) 🔻1346 🔴 (+16%) 🔻1372 🔴 (+13%)1419 🔴 (-9.5%)30
TTFShook + stream1594 (+299%) 🔻1713 🔴 (+23%) 🔻1768 🔴 (+25%) 🔻1849 🔴 (+24%) 🔻30
STSO1020 steps (inline)117 (+3.5%)160 (-24%) 💚182 (-28%) 💚284 (-32%) 💚1019
WO1020 steps158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚1
SLstream latency85 (-12%)115 🔴 (-29%) 💚130 🔴 (-40%) 💚173 🔴 (-54%) 💚30
SOstream overhead (text)106 (-29%) 💚166 (-42%) 💚234 (-62%) 💚3271 🔴 (+323%) 🔻30
SOstream overhead (structured)100 (-24%) 💚146 (-45%) 💚178 (-51%) 💚211 (-99%) 💚30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · 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) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Resultno regressionsno regressionsno regressions1/14 regressions
Total14141414
completed14141413
CORRUPTED_EVENT_LOG0001
USER_ERROR0000
RUNTIME_ERROR0000
stuck0000
other0000
infra0000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm65100000
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm1CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00

@pranaygppranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.
So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.
The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.
And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result1/1 regressions — partial (1 of 14 planned)6/14 regressions
Total114
completed08
CORRUPTED_EVENT_LOG11
USER_ERROR00
RUNTIME_ERROR00
stuck05
other00
infra00
Config1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60100500
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm2stuckrunningwrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm1stuckrunningwrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm6stuckrunningwrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm4stuckrunningwrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm3stuckrunningwrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

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

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySeriousVaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default
Three removals that stand on their own, split out of the v5 API switch.
**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.
**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.
**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.
`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.
Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 10:03pm
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 10:03pm
example-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-express-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-python-workflowErrorErrorAug 13, 2026 10:03pm
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workflow-docsReadyReadyPreview, v0Aug 13, 2026 10:03pm
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 10:03pm
workflow-tarballsReadyReadyPreviewAug 13, 2026 10:03pm
workflow-webReadyReadyPreviewAug 13, 2026 10:03pm

@changeset-bot

changeset-botBot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

This PR includes changesets to release 20 packages
NameType
@workflow/worldPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/world-testingPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
workflowPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
PassedFailedSkippedTotal
✅ ▲ Vercel Production333805623900
✅ 💻 Local Development351705394056
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total148140221717031
Details by Category

✅ ▲ Vercel Production

AppPassedFailedSkipped
✅ astro-node128028
✅ astro-quickjs128028
✅ example-node128028
✅ express-node128028
✅ express-quickjs128028
✅ fastify-node128028
✅ fastify-quickjs128028
✅ hono-node128028
✅ hono-quickjs128028
✅ nest-node128028
✅ nest-quickjs128028
✅ nextjs-turbopack-node15303
✅ nextjs-turbopack-quickjs15303
✅ nextjs-webpack-node15303
✅ nextjs-webpack-quickjs15303
✅ nitro-node128028
✅ nitro-quickjs128028
✅ nuxt-node128028
✅ nuxt-quickjs128028
✅ sveltekit-node14709
✅ sveltekit-quickjs14709
✅ tanstack-start-node128028
✅ tanstack-start-quickjs128028
✅ vite-node128028
✅ vite-quickjs128028

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep207 (+13%)531 🔴 (-52%) 💚692 🔴 (-43%) 💚1454 🔴 (-8.6%)30
TTFSstream224 (-5.5%)721 🔴 (-35%) 💚1420 🔴 (+25%) 🔻1532 🔴 (+27%) 🔻30
TTFShook + stream371 (-3.4%)1716 🔴 (+25%) 🔻1812 🔴 (+30%) 🔻1892 🔴 (+23%) 🔻30
STSO1020 steps (inline)137 (-9.3%)198 (-12%)232 (-10%)325 (-38%) 💚1019
WO1020 steps192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚1
SLstream latency96 (+2.1%)152 🔴 (+1.3%)209 🔴 (+30%) 🔻452 🔴 (+28%) 🔻30
SOstream overhead (text)126 (-20%) 💚306 🔴 (+3.4%)603 🔴 (+39%) 🔻1127 🔴 (-25%) 💚30
SOstream overhead (structured)134 (+14%)227 (-44%) 💚283 (-49%) 💚926 (-63%) 💚30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

 100-150 ms ░┃ main 0 this 61 +61
150-200 ms █████████████████░░░░░░┃ main 512 this 719 +207
200-250 ms █████┃███████ main 385 this 184 -201
250-300 ms ┃█ main 71 this 39 -32
300-350 ms ┃ main 22 this 10 -12
350-400 ms ┃ main 11 this 0 -11
400-450 ms ┃ main 5 this 2 -3
450-500 ms ┃ main 1 this 1 +0
500-550 ms ┃ main 4 this 0 -4
550-600 ms ┃ main 0 this 1 +1
600-650 ms ┃ main 1 this 1 +0
650-700 ms ┃ main 1 this 0 -1
900-950 ms ┃ main 1 this 1 +0
1200-1250 ms ┃ main 1 this 0 -1
1600-1650 ms ┃ main 1 this 0 -1
2000-2050 ms ┃ main 1 this 0 -1
2400-2450 ms ┃ main 1 this 0 -1
6050-6100 ms ┃ main 1 this 0 -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1335 (+626%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+23%) 🔻1909 🔴 (+20%) 🔻30
TTFSstream315 (+33%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+32%) 🔻1725 🔴 (+43%) 🔻30
TTFShook + stream1340 (+249%) 🔻1752 🔴 (+27%) 🔻1779 🔴 (+28%) 🔻2082 🔴 (+36%) 🔻30
STSO1020 steps (inline)119 (-21%) 💚176 (-21%) 💚200 (-22%) 💚313 (-40%) 💚1019
WO1020 steps177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚1
SLstream latency100 (+6.4%)141 🔴 (-6.0%)191 🔴 (+19%) 🔻367 🔴 (+3.7%)30
SOstream overhead (text)133 (-15%) 💚199 (-33%) 💚204 (-53%) 💚224 (-85%) 💚30
SOstream overhead (structured)132 (+12%)184 (-55%) 💚206 (-63%) 💚294 (-88%) 💚30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1278 (+430%) 🔻1355 🔴 (+19%) 🔻1407 🔴 (+20%) 🔻1481 🔴 (+17%) 🔻30
TTFSstream1302 (+375%) 🔻1346 🔴 (+16%) 🔻1372 🔴 (+13%)1419 🔴 (-9.5%)30
TTFShook + stream1594 (+299%) 🔻1713 🔴 (+23%) 🔻1768 🔴 (+25%) 🔻1849 🔴 (+24%) 🔻30
STSO1020 steps (inline)117 (+3.5%)160 (-24%) 💚182 (-28%) 💚284 (-32%) 💚1019
WO1020 steps158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚1
SLstream latency85 (-12%)115 🔴 (-29%) 💚130 🔴 (-40%) 💚173 🔴 (-54%) 💚30
SOstream overhead (text)106 (-29%) 💚166 (-42%) 💚234 (-62%) 💚3271 🔴 (+323%) 🔻30
SOstream overhead (structured)100 (-24%) 💚146 (-45%) 💚178 (-51%) 💚211 (-99%) 💚30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · 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) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Resultno regressionsno regressionsno regressions1/14 regressions
Total14141414
completed14141413
CORRUPTED_EVENT_LOG0001
USER_ERROR0000
RUNTIME_ERROR0000
stuck0000
other0000
infra0000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm65100000
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm1CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00

@pranaygppranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.
So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.
The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.
And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result1/1 regressions — partial (1 of 14 planned)6/14 regressions
Total114
completed08
CORRUPTED_EVENT_LOG11
USER_ERROR00
RUNTIME_ERROR00
stuck05
other00
infra00
Config1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60100500
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm2stuckrunningwrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm1stuckrunningwrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm6stuckrunningwrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm4stuckrunningwrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm3stuckrunningwrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

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

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySeriousVaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default
Three removals that stand on their own, split out of the v5 API switch.
**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.
**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.
**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.
`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.
Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 10:03pm
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 10:03pm
example-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-express-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-python-workflowErrorErrorAug 13, 2026 10:03pm
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workflow-docsReadyReadyPreview, v0Aug 13, 2026 10:03pm
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 10:03pm
workflow-tarballsReadyReadyPreviewAug 13, 2026 10:03pm
workflow-webReadyReadyPreviewAug 13, 2026 10:03pm

@changeset-bot

changeset-botBot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

This PR includes changesets to release 20 packages
NameType
@workflow/worldPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/world-testingPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
workflowPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
PassedFailedSkippedTotal
✅ ▲ Vercel Production333805623900
✅ 💻 Local Development351705394056
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total148140221717031
Details by Category

✅ ▲ Vercel Production

AppPassedFailedSkipped
✅ astro-node128028
✅ astro-quickjs128028
✅ example-node128028
✅ express-node128028
✅ express-quickjs128028
✅ fastify-node128028
✅ fastify-quickjs128028
✅ hono-node128028
✅ hono-quickjs128028
✅ nest-node128028
✅ nest-quickjs128028
✅ nextjs-turbopack-node15303
✅ nextjs-turbopack-quickjs15303
✅ nextjs-webpack-node15303
✅ nextjs-webpack-quickjs15303
✅ nitro-node128028
✅ nitro-quickjs128028
✅ nuxt-node128028
✅ nuxt-quickjs128028
✅ sveltekit-node14709
✅ sveltekit-quickjs14709
✅ tanstack-start-node128028
✅ tanstack-start-quickjs128028
✅ vite-node128028
✅ vite-quickjs128028

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep207 (+13%)531 🔴 (-52%) 💚692 🔴 (-43%) 💚1454 🔴 (-8.6%)30
TTFSstream224 (-5.5%)721 🔴 (-35%) 💚1420 🔴 (+25%) 🔻1532 🔴 (+27%) 🔻30
TTFShook + stream371 (-3.4%)1716 🔴 (+25%) 🔻1812 🔴 (+30%) 🔻1892 🔴 (+23%) 🔻30
STSO1020 steps (inline)137 (-9.3%)198 (-12%)232 (-10%)325 (-38%) 💚1019
WO1020 steps192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚1
SLstream latency96 (+2.1%)152 🔴 (+1.3%)209 🔴 (+30%) 🔻452 🔴 (+28%) 🔻30
SOstream overhead (text)126 (-20%) 💚306 🔴 (+3.4%)603 🔴 (+39%) 🔻1127 🔴 (-25%) 💚30
SOstream overhead (structured)134 (+14%)227 (-44%) 💚283 (-49%) 💚926 (-63%) 💚30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

 100-150 ms ░┃ main 0 this 61 +61
150-200 ms █████████████████░░░░░░┃ main 512 this 719 +207
200-250 ms █████┃███████ main 385 this 184 -201
250-300 ms ┃█ main 71 this 39 -32
300-350 ms ┃ main 22 this 10 -12
350-400 ms ┃ main 11 this 0 -11
400-450 ms ┃ main 5 this 2 -3
450-500 ms ┃ main 1 this 1 +0
500-550 ms ┃ main 4 this 0 -4
550-600 ms ┃ main 0 this 1 +1
600-650 ms ┃ main 1 this 1 +0
650-700 ms ┃ main 1 this 0 -1
900-950 ms ┃ main 1 this 1 +0
1200-1250 ms ┃ main 1 this 0 -1
1600-1650 ms ┃ main 1 this 0 -1
2000-2050 ms ┃ main 1 this 0 -1
2400-2450 ms ┃ main 1 this 0 -1
6050-6100 ms ┃ main 1 this 0 -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1335 (+626%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+23%) 🔻1909 🔴 (+20%) 🔻30
TTFSstream315 (+33%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+32%) 🔻1725 🔴 (+43%) 🔻30
TTFShook + stream1340 (+249%) 🔻1752 🔴 (+27%) 🔻1779 🔴 (+28%) 🔻2082 🔴 (+36%) 🔻30
STSO1020 steps (inline)119 (-21%) 💚176 (-21%) 💚200 (-22%) 💚313 (-40%) 💚1019
WO1020 steps177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚1
SLstream latency100 (+6.4%)141 🔴 (-6.0%)191 🔴 (+19%) 🔻367 🔴 (+3.7%)30
SOstream overhead (text)133 (-15%) 💚199 (-33%) 💚204 (-53%) 💚224 (-85%) 💚30
SOstream overhead (structured)132 (+12%)184 (-55%) 💚206 (-63%) 💚294 (-88%) 💚30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1278 (+430%) 🔻1355 🔴 (+19%) 🔻1407 🔴 (+20%) 🔻1481 🔴 (+17%) 🔻30
TTFSstream1302 (+375%) 🔻1346 🔴 (+16%) 🔻1372 🔴 (+13%)1419 🔴 (-9.5%)30
TTFShook + stream1594 (+299%) 🔻1713 🔴 (+23%) 🔻1768 🔴 (+25%) 🔻1849 🔴 (+24%) 🔻30
STSO1020 steps (inline)117 (+3.5%)160 (-24%) 💚182 (-28%) 💚284 (-32%) 💚1019
WO1020 steps158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚1
SLstream latency85 (-12%)115 🔴 (-29%) 💚130 🔴 (-40%) 💚173 🔴 (-54%) 💚30
SOstream overhead (text)106 (-29%) 💚166 (-42%) 💚234 (-62%) 💚3271 🔴 (+323%) 🔻30
SOstream overhead (structured)100 (-24%) 💚146 (-45%) 💚178 (-51%) 💚211 (-99%) 💚30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · 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) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Resultno regressionsno regressionsno regressions1/14 regressions
Total14141414
completed14141413
CORRUPTED_EVENT_LOG0001
USER_ERROR0000
RUNTIME_ERROR0000
stuck0000
other0000
infra0000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm65100000
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm1CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00

@pranaygppranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.
So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.
The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.
And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result1/1 regressions — partial (1 of 14 planned)6/14 regressions
Total114
completed08
CORRUPTED_EVENT_LOG11
USER_ERROR00
RUNTIME_ERROR00
stuck05
other00
infra00
Config1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60100500
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm2stuckrunningwrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm1stuckrunningwrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm6stuckrunningwrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm4stuckrunningwrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm3stuckrunningwrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

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

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySeriousVaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default
Three removals that stand on their own, split out of the v5 API switch.
**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.
**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.
**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.
`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.
Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 10:03pm
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 10:03pm
example-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-express-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-python-workflowErrorErrorAug 13, 2026 10:03pm
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workflow-docsReadyReadyPreview, v0Aug 13, 2026 10:03pm
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 10:03pm
workflow-tarballsReadyReadyPreviewAug 13, 2026 10:03pm
workflow-webReadyReadyPreviewAug 13, 2026 10:03pm

@changeset-bot

changeset-botBot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

This PR includes changesets to release 20 packages
NameType
@workflow/worldPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/world-testingPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
workflowPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
PassedFailedSkippedTotal
✅ ▲ Vercel Production333805623900
✅ 💻 Local Development351705394056
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total148140221717031
Details by Category

✅ ▲ Vercel Production

AppPassedFailedSkipped
✅ astro-node128028
✅ astro-quickjs128028
✅ example-node128028
✅ express-node128028
✅ express-quickjs128028
✅ fastify-node128028
✅ fastify-quickjs128028
✅ hono-node128028
✅ hono-quickjs128028
✅ nest-node128028
✅ nest-quickjs128028
✅ nextjs-turbopack-node15303
✅ nextjs-turbopack-quickjs15303
✅ nextjs-webpack-node15303
✅ nextjs-webpack-quickjs15303
✅ nitro-node128028
✅ nitro-quickjs128028
✅ nuxt-node128028
✅ nuxt-quickjs128028
✅ sveltekit-node14709
✅ sveltekit-quickjs14709
✅ tanstack-start-node128028
✅ tanstack-start-quickjs128028
✅ vite-node128028
✅ vite-quickjs128028

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep207 (+13%)531 🔴 (-52%) 💚692 🔴 (-43%) 💚1454 🔴 (-8.6%)30
TTFSstream224 (-5.5%)721 🔴 (-35%) 💚1420 🔴 (+25%) 🔻1532 🔴 (+27%) 🔻30
TTFShook + stream371 (-3.4%)1716 🔴 (+25%) 🔻1812 🔴 (+30%) 🔻1892 🔴 (+23%) 🔻30
STSO1020 steps (inline)137 (-9.3%)198 (-12%)232 (-10%)325 (-38%) 💚1019
WO1020 steps192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚1
SLstream latency96 (+2.1%)152 🔴 (+1.3%)209 🔴 (+30%) 🔻452 🔴 (+28%) 🔻30
SOstream overhead (text)126 (-20%) 💚306 🔴 (+3.4%)603 🔴 (+39%) 🔻1127 🔴 (-25%) 💚30
SOstream overhead (structured)134 (+14%)227 (-44%) 💚283 (-49%) 💚926 (-63%) 💚30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

 100-150 ms ░┃ main 0 this 61 +61
150-200 ms █████████████████░░░░░░┃ main 512 this 719 +207
200-250 ms █████┃███████ main 385 this 184 -201
250-300 ms ┃█ main 71 this 39 -32
300-350 ms ┃ main 22 this 10 -12
350-400 ms ┃ main 11 this 0 -11
400-450 ms ┃ main 5 this 2 -3
450-500 ms ┃ main 1 this 1 +0
500-550 ms ┃ main 4 this 0 -4
550-600 ms ┃ main 0 this 1 +1
600-650 ms ┃ main 1 this 1 +0
650-700 ms ┃ main 1 this 0 -1
900-950 ms ┃ main 1 this 1 +0
1200-1250 ms ┃ main 1 this 0 -1
1600-1650 ms ┃ main 1 this 0 -1
2000-2050 ms ┃ main 1 this 0 -1
2400-2450 ms ┃ main 1 this 0 -1
6050-6100 ms ┃ main 1 this 0 -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1335 (+626%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+23%) 🔻1909 🔴 (+20%) 🔻30
TTFSstream315 (+33%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+32%) 🔻1725 🔴 (+43%) 🔻30
TTFShook + stream1340 (+249%) 🔻1752 🔴 (+27%) 🔻1779 🔴 (+28%) 🔻2082 🔴 (+36%) 🔻30
STSO1020 steps (inline)119 (-21%) 💚176 (-21%) 💚200 (-22%) 💚313 (-40%) 💚1019
WO1020 steps177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚1
SLstream latency100 (+6.4%)141 🔴 (-6.0%)191 🔴 (+19%) 🔻367 🔴 (+3.7%)30
SOstream overhead (text)133 (-15%) 💚199 (-33%) 💚204 (-53%) 💚224 (-85%) 💚30
SOstream overhead (structured)132 (+12%)184 (-55%) 💚206 (-63%) 💚294 (-88%) 💚30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1278 (+430%) 🔻1355 🔴 (+19%) 🔻1407 🔴 (+20%) 🔻1481 🔴 (+17%) 🔻30
TTFSstream1302 (+375%) 🔻1346 🔴 (+16%) 🔻1372 🔴 (+13%)1419 🔴 (-9.5%)30
TTFShook + stream1594 (+299%) 🔻1713 🔴 (+23%) 🔻1768 🔴 (+25%) 🔻1849 🔴 (+24%) 🔻30
STSO1020 steps (inline)117 (+3.5%)160 (-24%) 💚182 (-28%) 💚284 (-32%) 💚1019
WO1020 steps158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚1
SLstream latency85 (-12%)115 🔴 (-29%) 💚130 🔴 (-40%) 💚173 🔴 (-54%) 💚30
SOstream overhead (text)106 (-29%) 💚166 (-42%) 💚234 (-62%) 💚3271 🔴 (+323%) 🔻30
SOstream overhead (structured)100 (-24%) 💚146 (-45%) 💚178 (-51%) 💚211 (-99%) 💚30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · 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) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Resultno regressionsno regressionsno regressions1/14 regressions
Total14141414
completed14141413
CORRUPTED_EVENT_LOG0001
USER_ERROR0000
RUNTIME_ERROR0000
stuck0000
other0000
infra0000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm65100000
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm1CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00

@pranaygppranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.
So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.
The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.
And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result1/1 regressions — partial (1 of 14 planned)6/14 regressions
Total114
completed08
CORRUPTED_EVENT_LOG11
USER_ERROR00
RUNTIME_ERROR00
stuck05
other00
infra00
Config1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60100500
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm2stuckrunningwrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm1stuckrunningwrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm6stuckrunningwrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm4stuckrunningwrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm3stuckrunningwrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

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

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySeriousVaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default
Three removals that stand on their own, split out of the v5 API switch.
**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.
**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.
**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.
`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.
Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 10:03pm
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 10:03pm
example-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-express-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-python-workflowErrorErrorAug 13, 2026 10:03pm
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workflow-docsReadyReadyPreview, v0Aug 13, 2026 10:03pm
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 10:03pm
workflow-tarballsReadyReadyPreviewAug 13, 2026 10:03pm
workflow-webReadyReadyPreviewAug 13, 2026 10:03pm

@changeset-bot

changeset-botBot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

This PR includes changesets to release 20 packages
NameType
@workflow/worldPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/world-testingPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
workflowPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
PassedFailedSkippedTotal
✅ ▲ Vercel Production333805623900
✅ 💻 Local Development351705394056
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total148140221717031
Details by Category

✅ ▲ Vercel Production

AppPassedFailedSkipped
✅ astro-node128028
✅ astro-quickjs128028
✅ example-node128028
✅ express-node128028
✅ express-quickjs128028
✅ fastify-node128028
✅ fastify-quickjs128028
✅ hono-node128028
✅ hono-quickjs128028
✅ nest-node128028
✅ nest-quickjs128028
✅ nextjs-turbopack-node15303
✅ nextjs-turbopack-quickjs15303
✅ nextjs-webpack-node15303
✅ nextjs-webpack-quickjs15303
✅ nitro-node128028
✅ nitro-quickjs128028
✅ nuxt-node128028
✅ nuxt-quickjs128028
✅ sveltekit-node14709
✅ sveltekit-quickjs14709
✅ tanstack-start-node128028
✅ tanstack-start-quickjs128028
✅ vite-node128028
✅ vite-quickjs128028

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep207 (+13%)531 🔴 (-52%) 💚692 🔴 (-43%) 💚1454 🔴 (-8.6%)30
TTFSstream224 (-5.5%)721 🔴 (-35%) 💚1420 🔴 (+25%) 🔻1532 🔴 (+27%) 🔻30
TTFShook + stream371 (-3.4%)1716 🔴 (+25%) 🔻1812 🔴 (+30%) 🔻1892 🔴 (+23%) 🔻30
STSO1020 steps (inline)137 (-9.3%)198 (-12%)232 (-10%)325 (-38%) 💚1019
WO1020 steps192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚1
SLstream latency96 (+2.1%)152 🔴 (+1.3%)209 🔴 (+30%) 🔻452 🔴 (+28%) 🔻30
SOstream overhead (text)126 (-20%) 💚306 🔴 (+3.4%)603 🔴 (+39%) 🔻1127 🔴 (-25%) 💚30
SOstream overhead (structured)134 (+14%)227 (-44%) 💚283 (-49%) 💚926 (-63%) 💚30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

 100-150 ms ░┃ main 0 this 61 +61
150-200 ms █████████████████░░░░░░┃ main 512 this 719 +207
200-250 ms █████┃███████ main 385 this 184 -201
250-300 ms ┃█ main 71 this 39 -32
300-350 ms ┃ main 22 this 10 -12
350-400 ms ┃ main 11 this 0 -11
400-450 ms ┃ main 5 this 2 -3
450-500 ms ┃ main 1 this 1 +0
500-550 ms ┃ main 4 this 0 -4
550-600 ms ┃ main 0 this 1 +1
600-650 ms ┃ main 1 this 1 +0
650-700 ms ┃ main 1 this 0 -1
900-950 ms ┃ main 1 this 1 +0
1200-1250 ms ┃ main 1 this 0 -1
1600-1650 ms ┃ main 1 this 0 -1
2000-2050 ms ┃ main 1 this 0 -1
2400-2450 ms ┃ main 1 this 0 -1
6050-6100 ms ┃ main 1 this 0 -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1335 (+626%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+23%) 🔻1909 🔴 (+20%) 🔻30
TTFSstream315 (+33%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+32%) 🔻1725 🔴 (+43%) 🔻30
TTFShook + stream1340 (+249%) 🔻1752 🔴 (+27%) 🔻1779 🔴 (+28%) 🔻2082 🔴 (+36%) 🔻30
STSO1020 steps (inline)119 (-21%) 💚176 (-21%) 💚200 (-22%) 💚313 (-40%) 💚1019
WO1020 steps177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚1
SLstream latency100 (+6.4%)141 🔴 (-6.0%)191 🔴 (+19%) 🔻367 🔴 (+3.7%)30
SOstream overhead (text)133 (-15%) 💚199 (-33%) 💚204 (-53%) 💚224 (-85%) 💚30
SOstream overhead (structured)132 (+12%)184 (-55%) 💚206 (-63%) 💚294 (-88%) 💚30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1278 (+430%) 🔻1355 🔴 (+19%) 🔻1407 🔴 (+20%) 🔻1481 🔴 (+17%) 🔻30
TTFSstream1302 (+375%) 🔻1346 🔴 (+16%) 🔻1372 🔴 (+13%)1419 🔴 (-9.5%)30
TTFShook + stream1594 (+299%) 🔻1713 🔴 (+23%) 🔻1768 🔴 (+25%) 🔻1849 🔴 (+24%) 🔻30
STSO1020 steps (inline)117 (+3.5%)160 (-24%) 💚182 (-28%) 💚284 (-32%) 💚1019
WO1020 steps158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚1
SLstream latency85 (-12%)115 🔴 (-29%) 💚130 🔴 (-40%) 💚173 🔴 (-54%) 💚30
SOstream overhead (text)106 (-29%) 💚166 (-42%) 💚234 (-62%) 💚3271 🔴 (+323%) 🔻30
SOstream overhead (structured)100 (-24%) 💚146 (-45%) 💚178 (-51%) 💚211 (-99%) 💚30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · 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) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Resultno regressionsno regressionsno regressions1/14 regressions
Total14141414
completed14141413
CORRUPTED_EVENT_LOG0001
USER_ERROR0000
RUNTIME_ERROR0000
stuck0000
other0000
infra0000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm65100000
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm1CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00

@pranaygppranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.
So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.
The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.
And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result1/1 regressions — partial (1 of 14 planned)6/14 regressions
Total114
completed08
CORRUPTED_EVENT_LOG11
USER_ERROR00
RUNTIME_ERROR00
stuck05
other00
infra00
Config1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60100500
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm2stuckrunningwrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm1stuckrunningwrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm6stuckrunningwrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm4stuckrunningwrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm3stuckrunningwrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

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

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySeriousVaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default
Three removals that stand on their own, split out of the v5 API switch.
**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.
**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.
**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.
`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.
Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 10:03pm
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 10:03pm
example-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-express-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-python-workflowErrorErrorAug 13, 2026 10:03pm
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workflow-docsReadyReadyPreview, v0Aug 13, 2026 10:03pm
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 10:03pm
workflow-tarballsReadyReadyPreviewAug 13, 2026 10:03pm
workflow-webReadyReadyPreviewAug 13, 2026 10:03pm

@changeset-bot

changeset-botBot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

This PR includes changesets to release 20 packages
NameType
@workflow/worldPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/world-testingPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
workflowPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
PassedFailedSkippedTotal
✅ ▲ Vercel Production333805623900
✅ 💻 Local Development351705394056
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total148140221717031
Details by Category

✅ ▲ Vercel Production

AppPassedFailedSkipped
✅ astro-node128028
✅ astro-quickjs128028
✅ example-node128028
✅ express-node128028
✅ express-quickjs128028
✅ fastify-node128028
✅ fastify-quickjs128028
✅ hono-node128028
✅ hono-quickjs128028
✅ nest-node128028
✅ nest-quickjs128028
✅ nextjs-turbopack-node15303
✅ nextjs-turbopack-quickjs15303
✅ nextjs-webpack-node15303
✅ nextjs-webpack-quickjs15303
✅ nitro-node128028
✅ nitro-quickjs128028
✅ nuxt-node128028
✅ nuxt-quickjs128028
✅ sveltekit-node14709
✅ sveltekit-quickjs14709
✅ tanstack-start-node128028
✅ tanstack-start-quickjs128028
✅ vite-node128028
✅ vite-quickjs128028

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep207 (+13%)531 🔴 (-52%) 💚692 🔴 (-43%) 💚1454 🔴 (-8.6%)30
TTFSstream224 (-5.5%)721 🔴 (-35%) 💚1420 🔴 (+25%) 🔻1532 🔴 (+27%) 🔻30
TTFShook + stream371 (-3.4%)1716 🔴 (+25%) 🔻1812 🔴 (+30%) 🔻1892 🔴 (+23%) 🔻30
STSO1020 steps (inline)137 (-9.3%)198 (-12%)232 (-10%)325 (-38%) 💚1019
WO1020 steps192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚1
SLstream latency96 (+2.1%)152 🔴 (+1.3%)209 🔴 (+30%) 🔻452 🔴 (+28%) 🔻30
SOstream overhead (text)126 (-20%) 💚306 🔴 (+3.4%)603 🔴 (+39%) 🔻1127 🔴 (-25%) 💚30
SOstream overhead (structured)134 (+14%)227 (-44%) 💚283 (-49%) 💚926 (-63%) 💚30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

 100-150 ms ░┃ main 0 this 61 +61
150-200 ms █████████████████░░░░░░┃ main 512 this 719 +207
200-250 ms █████┃███████ main 385 this 184 -201
250-300 ms ┃█ main 71 this 39 -32
300-350 ms ┃ main 22 this 10 -12
350-400 ms ┃ main 11 this 0 -11
400-450 ms ┃ main 5 this 2 -3
450-500 ms ┃ main 1 this 1 +0
500-550 ms ┃ main 4 this 0 -4
550-600 ms ┃ main 0 this 1 +1
600-650 ms ┃ main 1 this 1 +0
650-700 ms ┃ main 1 this 0 -1
900-950 ms ┃ main 1 this 1 +0
1200-1250 ms ┃ main 1 this 0 -1
1600-1650 ms ┃ main 1 this 0 -1
2000-2050 ms ┃ main 1 this 0 -1
2400-2450 ms ┃ main 1 this 0 -1
6050-6100 ms ┃ main 1 this 0 -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1335 (+626%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+23%) 🔻1909 🔴 (+20%) 🔻30
TTFSstream315 (+33%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+32%) 🔻1725 🔴 (+43%) 🔻30
TTFShook + stream1340 (+249%) 🔻1752 🔴 (+27%) 🔻1779 🔴 (+28%) 🔻2082 🔴 (+36%) 🔻30
STSO1020 steps (inline)119 (-21%) 💚176 (-21%) 💚200 (-22%) 💚313 (-40%) 💚1019
WO1020 steps177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚1
SLstream latency100 (+6.4%)141 🔴 (-6.0%)191 🔴 (+19%) 🔻367 🔴 (+3.7%)30
SOstream overhead (text)133 (-15%) 💚199 (-33%) 💚204 (-53%) 💚224 (-85%) 💚30
SOstream overhead (structured)132 (+12%)184 (-55%) 💚206 (-63%) 💚294 (-88%) 💚30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1278 (+430%) 🔻1355 🔴 (+19%) 🔻1407 🔴 (+20%) 🔻1481 🔴 (+17%) 🔻30
TTFSstream1302 (+375%) 🔻1346 🔴 (+16%) 🔻1372 🔴 (+13%)1419 🔴 (-9.5%)30
TTFShook + stream1594 (+299%) 🔻1713 🔴 (+23%) 🔻1768 🔴 (+25%) 🔻1849 🔴 (+24%) 🔻30
STSO1020 steps (inline)117 (+3.5%)160 (-24%) 💚182 (-28%) 💚284 (-32%) 💚1019
WO1020 steps158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚1
SLstream latency85 (-12%)115 🔴 (-29%) 💚130 🔴 (-40%) 💚173 🔴 (-54%) 💚30
SOstream overhead (text)106 (-29%) 💚166 (-42%) 💚234 (-62%) 💚3271 🔴 (+323%) 🔻30
SOstream overhead (structured)100 (-24%) 💚146 (-45%) 💚178 (-51%) 💚211 (-99%) 💚30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · 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) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Resultno regressionsno regressionsno regressions1/14 regressions
Total14141414
completed14141413
CORRUPTED_EVENT_LOG0001
USER_ERROR0000
RUNTIME_ERROR0000
stuck0000
other0000
infra0000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm65100000
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm1CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00

@pranaygppranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.
So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.
The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.
And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result1/1 regressions — partial (1 of 14 planned)6/14 regressions
Total114
completed08
CORRUPTED_EVENT_LOG11
USER_ERROR00
RUNTIME_ERROR00
stuck05
other00
infra00
Config1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60100500
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm2stuckrunningwrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm1stuckrunningwrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm6stuckrunningwrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm4stuckrunningwrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm3stuckrunningwrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

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

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[core] Drop pre-slot event ID support and preconditionGuard capability - #3519

Merged
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard
Aug 13, 2026
Merged

[core] Drop pre-slot event ID support and preconditionGuard capability#3519
VaguelySerious merged 4 commits into
mainfrom
peter/drop-pre-slot-and-guard

Conversation

@VaguelySerious

@VaguelySeriousVaguelySerious commented Aug 13, 2026

Copy link
Copy Markdown
Member

Three removals split out of #3512, which needed all of them but is about something else (the v5 event API). Nothing here depends on v5.

  1. Slot-numbered event ids are a requirement of the World contract, and the pre-slot code is gone
  • slotEventIds is removed along with the dead code. Nothing consumed it, and a flag describing something mandatory only invites a World to leave it off and then fail every replay. The substance moves to Storage.events: density, bump-and-report, and allocate the position at the commit, which is what makes a reader's log a prefix rather than a prefix with a hole in it
  • the building-a-world guide states it as a requirement in both places it comes up, with a warning callout naming the failure a World author would otherwise meet at replay time
  • @workflow/world-testing asserts it: numbers events by position checks every id decodes to a slot, that the run is dense from 1, and that ids are canonical (a World padding to a different width sorts its own log wrongly past ten events). Verified it can fail, not just pass
  • breaking for custom Worlds. One written against earlier guidance passed conformance and would have failed every replay with Event id is not slot-numbered. It now fails in the suite instead. The GA docs need an explicit migration note; that is not in this PR
  • maxEventSlot, findEventSlotGap and the step executor's slot observer went through a lenient decoder that answered "no position" for an id that is not a slot. That leniency was the pre-slot support: a write whose eventCount is absent is indistinguishable, to a World, from one that honestly loaded nothing
  • they now call requireEventSlot (new, in @workflow/world), which throws
  • skew protection makes this safe on Vercel: a run executes on the deployment that created it, so a build carrying this never replays a run created before slot ids
  • test fixtures across packages/core move from hand-written ULIDs to slotToEventId(n)
  1. preconditionGuard capability removed
  • the need for a precondition guard is gone rather than universal, which is why the capability could be deleted. Three properties do it: a reader's log is a prefix of the run's log rather than a prefix with a hole in it, since positions are allocated at the commit; replay is deterministic on a prefix, so a shorter log means a run that has not caught up rather than one that decides differently; and every write reports what it missed, naming the position it was derived from and getting back the events it was pushed past
  • no World in this repository returns 412 any more. The error type, its docs page and the runtime's handling of it stay for a World that allocates positions away from the commit and would rather refuse than report
  • the per-step inline event-log delta stays enabled while the run has an open hook
  • an inline step's step_started claim is awaited before the body runs (optimistic start suppressed on stale-sensitive batches)
  • resilient step dispatch no longer consults it (see 3)
  • not a no-op for world-local / world-postgres: neither declared the capability, so both now pay the await-then-run claim while a hook is open, and both get the inline delta in that same case. Only world-vercel declared it
  1. WORKFLOW_RESILIENT_STEP_DISPATCH off by default
  • =1 to opt in, where it used to be =0 to opt out
  • previously gated by capabilities.preconditionGuard, which no longer exists, and already off for world-vercel, which declared it
  • the publish races the create's verdict: a World that refuses the step_created sends the runtime back to replay while the payload-carrying message is already out, and nothing orders the refusal before the consumer's redelivery re-ensure
  1. Deletes .changeset/windows-preload-timeout.md (committed here by mistake)

@workflow/world-sim moves to slot ids alongside the runtime, since it drives the real one. Its book goes 35/6/6 to 38/3/3 mint-ordered and stays 41/0/0 append-only: four of the six reds staged a read missing an event the log already held, which under ULIDs was indistinguishable from a complete read and under slots is a gap the runtime re-reads past. Baselines updated in the workflow header, DESIGN.md and the workbench README.

#3512 is now stacked on this.

… resilient dispatch by default
Three removals that stand on their own, split out of the v5 API switch.
**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.
**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.
**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.
`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.
Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 10:03pm
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 10:03pm
example-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-express-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-python-workflowErrorErrorAug 13, 2026 10:03pm
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 10:03pm
workflow-docsReadyReadyPreview, v0Aug 13, 2026 10:03pm
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 10:03pm
workflow-tarballsReadyReadyPreviewAug 13, 2026 10:03pm
workflow-webReadyReadyPreviewAug 13, 2026 10:03pm

@changeset-bot

changeset-botBot commented Aug 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ca4d70

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

This PR includes changesets to release 20 packages
NameType
@workflow/worldPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/world-testingPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
workflowPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

E2E Test Summary

Summary
PassedFailedSkippedTotal
✅ ▲ Vercel Production333805623900
✅ 💻 Local Development351705394056
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total148140221717031
Details by Category

✅ ▲ Vercel Production

AppPassedFailedSkipped
✅ astro-node128028
✅ astro-quickjs128028
✅ example-node128028
✅ express-node128028
✅ express-quickjs128028
✅ fastify-node128028
✅ fastify-quickjs128028
✅ hono-node128028
✅ hono-quickjs128028
✅ nest-node128028
✅ nest-quickjs128028
✅ nextjs-turbopack-node15303
✅ nextjs-turbopack-quickjs15303
✅ nextjs-webpack-node15303
✅ nextjs-webpack-quickjs15303
✅ nitro-node128028
✅ nitro-quickjs128028
✅ nuxt-node128028
✅ nuxt-quickjs128028
✅ sveltekit-node14709
✅ sveltekit-quickjs14709
✅ tanstack-start-node128028
✅ tanstack-start-quickjs128028
✅ vite-node128028
✅ vite-quickjs128028

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

The benchmark run for 1ca4d70 failed. See the run logs for details.

Partial results from the failed run:

commit 1ca4d70 · Thu, 13 Aug 2026 22:17:05 GMT · run logs

Backend: vercel · app: nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep207 (+13%)531 🔴 (-52%) 💚692 🔴 (-43%) 💚1454 🔴 (-8.6%)30
TTFSstream224 (-5.5%)721 🔴 (-35%) 💚1420 🔴 (+25%) 🔻1532 🔴 (+27%) 🔻30
TTFShook + stream371 (-3.4%)1716 🔴 (+25%) 🔻1812 🔴 (+30%) 🔻1892 🔴 (+23%) 🔻30
STSO1020 steps (inline)137 (-9.3%)198 (-12%)232 (-10%)325 (-38%) 💚1019
WO1020 steps192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚192470 (-16%) 💚1
SLstream latency96 (+2.1%)152 🔴 (+1.3%)209 🔴 (+30%) 🔻452 🔴 (+28%) 🔻30
SOstream overhead (text)126 (-20%) 💚306 🔴 (+3.4%)603 🔴 (+39%) 🔻1127 🔴 (-25%) 💚30
SOstream overhead (structured)134 (+14%)227 (-44%) 💚283 (-49%) 💚926 (-63%) 💚30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 228513ms → this run 190800ms (Δ -37713ms, -17%)

 100-150 ms ░┃ main 0 this 61 +61
150-200 ms █████████████████░░░░░░┃ main 512 this 719 +207
200-250 ms █████┃███████ main 385 this 184 -201
250-300 ms ┃█ main 71 this 39 -32
300-350 ms ┃ main 22 this 10 -12
350-400 ms ┃ main 11 this 0 -11
400-450 ms ┃ main 5 this 2 -3
450-500 ms ┃ main 1 this 1 +0
500-550 ms ┃ main 4 this 0 -4
550-600 ms ┃ main 0 this 1 +1
600-650 ms ┃ main 1 this 1 +0
650-700 ms ┃ main 1 this 0 -1
900-950 ms ┃ main 1 this 1 +0
1200-1250 ms ┃ main 1 this 0 -1
1600-1650 ms ┃ main 1 this 0 -1
2000-2050 ms ┃ main 1 this 0 -1
2400-2450 ms ┃ main 1 this 0 -1
6050-6100 ms ┃ main 1 this 0 -1
📜 Previous results (2)

88553f8

Thu, 13 Aug 2026 21:32:46 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1335 (+626%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+23%) 🔻1909 🔴 (+20%) 🔻30
TTFSstream315 (+33%) 🔻1462 🔴 (+32%) 🔻1494 🔴 (+32%) 🔻1725 🔴 (+43%) 🔻30
TTFShook + stream1340 (+249%) 🔻1752 🔴 (+27%) 🔻1779 🔴 (+28%) 🔻2082 🔴 (+36%) 🔻30
STSO1020 steps (inline)119 (-21%) 💚176 (-21%) 💚200 (-22%) 💚313 (-40%) 💚1019
WO1020 steps177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚177071 (-23%) 💚1
SLstream latency100 (+6.4%)141 🔴 (-6.0%)191 🔴 (+19%) 🔻367 🔴 (+3.7%)30
SOstream overhead (text)133 (-15%) 💚199 (-33%) 💚204 (-53%) 💚224 (-85%) 💚30
SOstream overhead (structured)132 (+12%)184 (-55%) 💚206 (-63%) 💚294 (-88%) 💚30

392953f

Thu, 13 Aug 2026 02:02:54 GMT · run logs

vercel / nextjs-turbopack

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1278 (+430%) 🔻1355 🔴 (+19%) 🔻1407 🔴 (+20%) 🔻1481 🔴 (+17%) 🔻30
TTFSstream1302 (+375%) 🔻1346 🔴 (+16%) 🔻1372 🔴 (+13%)1419 🔴 (-9.5%)30
TTFShook + stream1594 (+299%) 🔻1713 🔴 (+23%) 🔻1768 🔴 (+25%) 🔻1849 🔴 (+24%) 🔻30
STSO1020 steps (inline)117 (+3.5%)160 (-24%) 💚182 (-28%) 💚284 (-32%) 💚1019
WO1020 steps158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚158374 (-24%) 💚1
SLstream latency85 (-12%)115 🔴 (-29%) 💚130 🔴 (-40%) 💚173 🔴 (-54%) 💚30
SOstream overhead (text)106 (-29%) 💚166 (-42%) 💚234 (-62%) 💚3271 🔴 (+323%) 🔻30
SOstream overhead (structured)100 (-24%) 💚146 (-45%) 💚178 (-51%) 💚211 (-99%) 💚30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · 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) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. Fan-out TTFS/TTLS are the first and last step completions of a single Promise.all over trivial steps, from the same anchor, so the gap between the two rows is the spread the runtime adds across the fan-out. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

1 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 01:57 UTC #1
logs / deploy
2026-08-13 21:18 UTC #1
logs / deploy
2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:04 UTC #1
logs / deploy
Resultno regressionsno regressionsno regressions1/14 regressions
Total14141414
completed14141413
CORRUPTED_EVENT_LOG0001
USER_ERROR0000
RUNTIME_ERROR0000
stuck0000
other0000
infra0000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm65100000
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm1CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_41KZYJ92TP0GYBNDKW3FJBWQ3Y

@VaguelySerious
VaguelySerious marked this pull request as ready for review August 13, 2026 02:00

@pranaygppranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the full diff, verified the sim-book baselines locally (mint-ordered 38/3/3, append-only 41/0/0, --no-fence 5), and re-ran the affected suites on the current head — all green. 88553f8 addressed every should-fix from the first review round (the #3404 doc revert, the 412-fence safety-argument framing, the latent null-runId mint in the sim facade, the stale scenario descriptions, and the world-vercel changeset). Two non-blocking threads remain open: the building-a-world capabilities paragraph should state slot allocation as a hard requirement of the v5 World contract (and the v5 GA docs PR should call out the breaking change for custom-world maintainers), and the pre-slot-run story for self-hosted worlds deserves a line in the release notes. The new world-local/world-postgres repro lanes added in 7d214db will give this PR's delta-with-open-hooks change its first storm datapoints on the worlds it actually affects.

Review feedback: `slotEventIds` could no longer read as an optional extra. The
runtime reads a position out of every event id it loads and fails the run when
it cannot, so a World that does not allocate slots cannot replay a single
workflow — the flag was the only optional part, not the allocation.
So the flag is gone, the same way `preconditionGuard` went: nothing consumed it
(three Worlds declared it and two doc comments mentioned it), and a flag
describing something mandatory only invites a World to leave it off and wonder
why replay fails. The substance moves to `Storage.events`, which is what a
World implements, and says which two properties the runtime actually relies on:
density, and bump-and-report. It also now states the part that was implicit —
allocate the position *at the commit*, because that is what makes a reader's
log a prefix rather than a prefix with a hole in it.
The guide says it as a requirement in both places it comes up: the capabilities
paragraph, which now points out what is deliberately not a capability, and the
Event ID Allocation section, which opens with a warning callout naming the
failure a World author would otherwise meet at replay time.
And `@workflow/world-testing` asserts it, which is the reviewer's point that a
World written against earlier guidance passes conformance today and fails every
replay later. The new case checks that every id decodes to a slot, that the run
is dense from 1, and that ids are in canonical form — a World padding to a
different width sorts its own log wrongly past ten events. Verified by breaking
the expectation on purpose: red on world-local, then green again, and green on
world-postgres.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actionsBot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro (world-local)

6 of 14 latest repro runs hit event-log regressions.

Run History

Metric2026-08-13 21:58 UTC #1
logs / deploy
2026-08-13 22:09 UTC #1
logs / deploy
Result1/1 regressions — partial (1 of 14 planned)6/14 regressions
Total114
completed08
CORRUPTED_EVENT_LOG11
USER_ERROR00
RUNTIME_ERROR00
stuck05
other00
infra00
Config1 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60100500
hook-storm66000000
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5CORRUPTED_EVENT_LOGfailedCORRUPTED_EVENT_LOGwrun_01KZYJ91JEXV3WD1549HAYYKDA
step-storm2stuckrunningwrun_01KZYJ91HYK431CQPQR0JPQMQ3
step-storm1stuckrunningwrun_01KZYJ91JFM9T3GFEMP0XGP345
step-storm6stuckrunningwrun_01KZYJ91JCB5ABNFESGZ952JWK
step-storm4stuckrunningwrun_01KZYJ91JA3HAWEFRKRE0DWGMB
step-storm3stuckrunningwrun_01KZYJ91J87ZE0CBRGH1SM1MTH

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for dc85865 (AI decision).

This is a deliberate contract/API change rather than a stability fix: it removes the slotEventIds and preconditionGuard World capabilities (explicitly called out as breaking for custom Worlds), makes slot-numbered event ids a hard requirement with a throwing requireEventSlot, flips the WORKFLOW_RESILIENT_STEP_DISPATCH default from on to off, and changes runtime behavior for world-local/world-postgres (they now pay the await-then-run claim and get the inline delta). It also adds new CI lanes and conformance-suite assertions that are feature work, and it is the base of a stacked v5 events-API PR. Shipping it to stable would break existing custom Worlds and change existing defaults for users who stayed behind for stability.

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

dc85865718fdf5e4abdb5ad8edf715ec956bf07d

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@VaguelySerious@pranaygp