[world] Make spec version 6 the current version - #3542
Conversation
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
🦋 Changeset detectedLatest commit: 00059e2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 20 packages
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 |
📊 Workflow Benchmarkscommit Backend:
📈 STSO distribution vs main (inline / queue-hop histograms)1020 steps (inline) Cumulative STSO time: main 194368ms → this run 192196ms (Δ -2172ms, -1%) 📜 Previous results (1)d46e1c0Fri, 14 Aug 2026 01:36:36 GMT · run logs
ℹ️ Metric definitions & methodologyThe 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: Best/P75/P90/P99 deltas compare against the most recent benchmark run on 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 ( Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the |
🧪 E2E Test Results✅ All tests passed E2E Test SummarySummary
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ vercel-multi-region
|
| */ | ||
| export const SPEC_VERSION_CURRENT = | ||
| SPEC_VERSION_SUPPORTS_COMPRESSION as SpecVersion; | ||
| SPEC_VERSION_SUPPORTS_SLOT_IDENTITY as SpecVersion; |
Sim WorldSimulated world deterministic testing for races. Traces 🟠 Mint-ordered log — 3 fail of 41 total
Full trace: 🟢 Append-only log — 0 fail of 41 total
Full trace: |
The constant's own doc argued against the bump this branch makes, so rewrite it around what is now true: slot-numbered event ids are a requirement of the World contract (`requireEventSlot` fails a run whose ids are not positions), which makes 6 both the version a World stamps and the lowest one the runtime can serve. Same for the compatibility range's rationale and the `World.specVersion` docs. world-vercel declared the slot-identity literal rather than the current version. Equal today, but the next bump would raise the runtime's floor past the adapter shipped alongside it and reject it, so declare the current version instead. Document the requirement for World authors: the building-a-world guide never mentioned `specVersion`, and the conformance suite checked that event ids are positions without checking the version declared next to them.
The three entries described one change from three angles, which reads as three separate release notes. The narrowed World compatibility range is the breaking part: a World declaring an older spec version stops working against this runtime.
…n skill Two pieces, both for a World implementer rather than an application. - The Spec versions section is rewritten for #3542. The accepted range narrows to a single version, because slot-numbered event ids stopped being optional, so a World declaring 5 is now refused before its first run rather than partway through one. The guidance that mattered stays the same and gets a reason: declare SPEC_VERSION_CURRENT, not a literal, and not SPEC_VERSION_SUPPORTS_SLOT_IDENTITY, which is a literal by another name. - skills/migrating-world-v4-to-v5 is a second migration skill, for the World rather than the app. The two jobs share almost no surface, and the World half is the one where a type-clean port still fails on the first replay, so it leads with event id allocation and treats the signature changes as the small half. The application skill now hands that work off instead of summarizing it. Also drops what the main merge left stale: the precondition guard row in whats-new and the guard bullet in the application skill (the capability, the env var and the stateUpdatedAt triple are all gone), and the eventCount-supersedes-the-triple paragraph in the upgrade guide. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Event Log Race ReproNo event-log regressions in the latest repro job. Run History
Latest Scenario Breakdown
|
Event Log Race Repro (world-postgres)6 of 14 latest repro runs hit event-log regressions. Run History
Latest Scenario Breakdown
Latest Non-Completed Runs
|
Event Log Race Repro (world-local)6 of 14 latest repro runs hit event-log regressions. Run History
Latest Scenario Breakdown
Latest Non-Completed Runs
|
|
No backport to This is an intentional protocol/behavior change, not a fix: it bumps To override, re-run the Backport to stable workflow manually via |
SPEC_VERSION_CURRENTwas pinned to 5 whileworld-verceldeclared 6, so the constant stopped describing what the SDK actually does. #3519 made slot-numbered event ids a requirement of the World contract rather than something a World opts into, which removed the reason the pin existed.How a run's spec version is chosen
start()reads it off the World (opts.specVersion ?? world.specVersion), not fromSPEC_VERSION_CURRENT. Becauseworld-verceldeclares the slot-identity version, runs on it have been created at spec 6 for a while.SPEC_VERSION_CURRENTis three other things: the versionworld-local,world-postgresandworld-simdeclare, the value stamped on every event other thanrun_created, and the floor of the World compatibility check.What this changes
[5, 6]to{6}. A World declaring 5 is refused before the first run rather than failing partway through one, when the runtime cannot read a position out of an event id it loaded.world-local,world-postgresandworld-simstamp 6, matching the slot-numbered ids they already allocate. Their declaration understated what they do.Nothing in the runtime is gated on 6: every version test is
>=against 3, 4 or 5. Runs already created keep their persisted version, and a World resolves a run's identity scheme from storage rather than from this constant.Also in this PR
world-verceldeclaresSPEC_VERSION_CURRENTinstead of the literal version that introduced slots. The two are equal today, but the next bump would raise the runtime's floor past the adapter shipped alongside it and reject it.spec-version.ts,world-compatibility.tsandWorld.specVersion.specVersionfor the first time, including why a World author should declare the constant rather than a number.Docs Preview
Verification
Build, typecheck, docs code samples and docs link lint pass. Unit tests pass for
core(2117),world-local(546),world-vercel(501) andworld. Theworld-postgresconformance suite passes against Docker with the new assertion executing rather than skipped.