Skip to content

perf(core): continue partial run_started preloads from cursor - #3124

Draft
NathanColosimo wants to merge 6 commits into
mainfrom
codex/consume-run-started-preload-cursor
Draft

perf(core): continue partial run_started preloads from cursor#3124
NathanColosimo wants to merge 6 commits into
mainfrom
codex/consume-run-started-preload-cursor

Conversation

@NathanColosimo

@NathanColosimo NathanColosimo commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • represent bootstrap replay state as one MutableEventLog snapshot instead of parallel optional values
  • retain lifecycle events returned by duplicate run_started and continue strictly after their cursor
  • use each World default page size, so Vercel v4 inherits the server-controlled 500-event streaming default
  • infer events.create results from the request event type, making required run/step entities and their startedAt timestamps non-optional
  • remove redundant runtime assertions for run_created, run_started, and step_started

Why

A fresh workflow invocation must replay its durable event log. The server already reads run_created and run_started to recover an idempotent duplicate start, so the SDK should keep those events and continue after their cursor instead of loading the same prefix again.

The World interface also previously collapsed every event response into one broad EventResult bag where run, step, and their start timestamps were optional. The implementations guarantee those values for runtime setup events, so the public type now expresses that contract and callers use the returned values directly.

Flow

  1. run_started returns its materialized run and may include a resolved lifecycle prefix plus cursor/hasMore.
  2. If the prefix is partial, the SDK calls loadWorkflowRunEvents(runId, cursor).
  3. The loader drains the suffix using each Worlds normal page size; v4 inherits the server-controlled 500-event default.
  4. The runtime stores one complete { events, cursor } snapshot for replay.
  5. EventResultFor<T> narrows the materialized entity for run_created, run_started, and step_started, so runtime code does not re-check interface guarantees.

If an older World omits the cursor, the SDK performs the prior full load. If a World rejects cursor pagination with HTTP 400 and the loader retries from the beginning, the merge removes prefix IDs once.

Verification

  • pnpm --filter @workflow/core test — 1,505 passed, 3 expected failures
  • pnpm --filter @workflow/world-local test — 478 passed
  • pnpm --filter @workflow/world-vercel test — 296 passed
  • pnpm --filter @workflow/world-postgres test — 162 passed
  • pnpm typecheck — passed
  • pnpm build — 27/27 tasks
  • changed-file Biome check — no errors
  • root pnpm lint remains blocked by existing generated docs/workbench diagnostics unrelated to this PR
  • autoreview (Codex gpt-5.6-sol xhigh + Claude Opus 4.8 xhigh) — clean before the focused result-typing follow-up

@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5e704ff

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

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

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

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

@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jul 27, 2026 11:52pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jul 27, 2026 11:52pm
example-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-astro-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-express-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-fastify-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-hono-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-nestjs-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-nitro-workflow Building Building Preview, Comment Jul 27, 2026 11:52pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workbench-vite-workflow Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jul 27, 2026 11:52pm
workflow-swc-playground Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workflow-tarballs Ready Ready Preview, Comment Jul 27, 2026 11:52pm
workflow-web Ready Ready Preview, Comment Jul 27, 2026 11:52pm

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 5e704ff · Tue, 28 Jul 2026 00:12:09 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1240 (+68%) 🔻 1341 🔴 (+19%) 🔻 1376 🔴 (+14%) 1703 🔴 (+25%) 🔻 30
TTFS stream 225 (-28%) 💚 1309 🔴 (+19%) 🔻 1337 🔴 (+17%) 🔻 1454 🔴 (+24%) 🔻 30
TTFS hook + stream 483 (-62%) 💚 1659 🔴 (+18%) 🔻 1699 🔴 (+16%) 🔻 1891 🔴 (+16%) 🔻 30
STSO 1020 steps (1-20) 198 (+16%) 🔻 310 🔴 (+2.6%) 326 🔴 (-7.4%) 347 🔴 (-8.4%) 19
STSO 1020 steps (101-120) 217 (+10%) 279 🔴 (-18%) 💚 316 🔴 (-23%) 💚 442 🔴 (-12%) 19
STSO 1020 steps (1001-1020) 507 (+5.0%) 557 🔴 (-3.0%) 585 🔴 (-9.4%) 611 🔴 (-7.0%) 19
WO 1020 steps 420205 (-4.3%) 420205 (-4.3%) 420205 (-4.3%) 420205 (-4.3%) 1
SL stream latency 124 (+33%) 🔻 178 🔴 (+6.0%) 205 🔴 (-12%) 393 🔴 (+55%) 🔻 30
SO stream overhead (text) 141 (+7.6%) 207 (-29%) 💚 234 (-34%) 💚 431 (+2.4%) 30
SO stream overhead (structured) 139 (+3.0%) 199 (-23%) 💚 233 (-16%) 💚 287 (-31%) 💚 30
📜 Previous results (5)

c3b400b

Mon, 27 Jul 2026 23:32:01 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 241 (-67%) 💚 1334 🔴 (+18%) 🔻 1349 🔴 (+11%) 1395 🔴 (+2.0%) 30
TTFS stream 1285 (+309%) 🔻 1333 🔴 (+21%) 🔻 1374 🔴 (+20%) 🔻 1403 🔴 (+20%) 🔻 30
TTFS hook + stream 476 (-62%) 💚 1573 🔴 (+12%) 1594 🔴 (+9.1%) 1652 🔴 (+1.0%) 30
STSO 1020 steps (1-20) 168 (-1.2%) 326 🔴 (+7.9%) 417 🔴 (+18%) 🔻 494 🔴 (+30%) 🔻 19
STSO 1020 steps (101-120) 199 (+1.0%) 264 🔴 (-22%) 💚 396 🔴 (-3.2%) 660 🔴 (+32%) 🔻 19
STSO 1020 steps (1001-1020) 504 (+4.3%) 562 🔴 (-2.1%) 587 🔴 (-9.1%) 618 🔴 (-5.9%) 19
WO 1020 steps 405834 (-7.6%) 405834 (-7.6%) 405834 (-7.6%) 405834 (-7.6%) 1
SL stream latency 110 (+18%) 🔻 156 🔴 (-7.1%) 164 🔴 (-30%) 💚 332 🔴 (+31%) 🔻 30
SO stream overhead (text) 99 (-24%) 💚 195 (-33%) 💚 241 (-32%) 💚 391 (-7.1%) 30
SO stream overhead (structured) 125 (-7.4%) 236 (-9.2%) 342 (+23%) 🔻 2859 🔴 (+592%) 🔻 30

a82f28c

Mon, 27 Jul 2026 22:25:01 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 226 (-7.8%) 1278 🔴 (-5.5%) 1315 🔴 (-4.4%) 1353 🔴 (-6.5%) 30
TTFS stream 232 (-77%) 💚 1303 🔴 (-3.1%) 1321 🔴 (-2.7%) 1340 🔴 (-7.1%) 30
TTFS hook + stream 423 (-25%) 💚 1425 🔴 (+9.3%) 1465 🔴 (+6.1%) 1550 🔴 (+3.1%) 30
STSO 1020 steps (1-20) 154 (-13%) 356 🔴 (+28%) 🔻 465 🔴 (+38%) 🔻 481 🔴 (+35%) 🔻 19
STSO 1020 steps (101-120) 195 (-1.5%) 264 🔴 (-17%) 💚 301 🔴 (-38%) 💚 307 🔴 (-41%) 💚 19
STSO 1020 steps (1001-1020) 453 (+3.0%) 535 🔴 (-6.0%) 567 🔴 (-26%) 💚 581 🔴 (-36%) 💚 19
WO 1020 steps 402069 (-4.0%) 402069 (-4.0%) 402069 (-4.0%) 402069 (-4.0%) 1
SL stream latency 93 (+3.3%) 170 🔴 (-17%) 💚 190 🔴 (-19%) 💚 273 🔴 (-36%) 💚 30
SO stream overhead (text) 115 (-22%) 💚 191 (-35%) 💚 256 (-47%) 💚 387 (-81%) 💚 30
SO stream overhead (structured) 124 (-1.6%) 212 (-11%) 283 (+5.2%) 431 (+25%) 🔻 30

ef184e4

Mon, 27 Jul 2026 22:01:04 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1162 (+16%) 🔻 1317 🔴 (+16%) 🔻 1352 🔴 (+14%) 1369 🔴 (-15%) 30
TTFS stream 1205 (+298%) 🔻 1288 🔴 (+12%) 1350 🔴 (+13%) 1414 🔴 (+5.4%) 30
TTFS hook + stream 1456 (+222%) 🔻 1538 🔴 (+7.3%) 1587 🔴 (+6.8%) 2728 🔴 (+46%) 🔻 30
STSO 1020 steps (1-20) 179 (-3.2%) 306 🔴 (+5.2%) 323 🔴 (-4.7%) 362 🔴 (-11%) 19
STSO 1020 steps (101-120) 179 (-7.7%) 254 🔴 (-13%) 274 🔴 (-50%) 💚 647 🔴 (+1.6%) 19
STSO 1020 steps (1001-1020) 464 (-13%) 523 🔴 (-23%) 💚 574 🔴 (-39%) 💚 639 🔴 (-35%) 💚 19
WO 1020 steps 388609 (-13%) 388609 (-13%) 388609 (-13%) 388609 (-13%) 1
SL stream latency 72 (-28%) 💚 138 🔴 (-36%) 💚 148 🔴 (-67%) 💚 197 🔴 (-65%) 💚 30
SO stream overhead (text) 102 (-20%) 💚 153 (-37%) 💚 186 (-34%) 💚 254 (-73%) 💚 30
SO stream overhead (structured) 100 (-16%) 💚 149 (-46%) 💚 160 (-45%) 💚 775 (+85%) 🔻 30

ef184e4

Mon, 27 Jul 2026 21:02:39 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 264 (-63%) 💚 1317 🔴 (+27%) 🔻 1341 🔴 (+23%) 🔻 1372 🔴 (-3.3%) 30
TTFS stream 248 (-71%) 💚 1337 🔴 (+40%) 🔻 1364 🔴 (+40%) 🔻 1381 🔴 (+39%) 🔻 30
STSO 1020 steps (1-20) 177 (-2.2%) 333 🔴 (+21%) 🔻 433 🔴 (+42%) 🔻 667 🔴 (+69%) 🔻 19
STSO 1020 steps (101-120) 202 (+8.6%) 281 🔴 (+5.2%) 344 🔴 (+14%) 452 🔴 (-81%) 💚 19
STSO 1020 steps (1001-1020) 487 (+8.9%) 589 🔴 (+14%) 605 🔴 (+12%) 620 🔴 (-5.6%) 19
WO 1020 steps 408533 (+6.2%) 408533 (+6.2%) 408533 (+6.2%) 408533 (+6.2%) 1
SL stream latency 100 (+30%) 🔻 182 🔴 (+47%) 🔻 189 🔴 (+31%) 🔻 215 🔴 (-16%) 💚 30
SO stream overhead (text) 130 (+34%) 🔻 252 🔴 (+63%) 🔻 311 (+78%) 🔻 771 (+289%) 🔻 30
SO stream overhead (structured) 115 (+14%) 196 (+17%) 🔻 210 (+19%) 🔻 406 (-73%) 💚 30

2f2e9e9

Mon, 27 Jul 2026 19:19:43 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 410 (-42%) 💚 1412 🔴 (+37%) 🔻 1462 🔴 (+34%) 🔻 1595 🔴 (+12%) 30
TTFS stream 265 (-69%) 💚 1361 🔴 (+43%) 🔻 1396 🔴 (+44%) 🔻 1442 🔴 (+45%) 🔻 30
TTFS hook + stream 1239 (+13%) 1688 🔴 (+37%) 🔻 1743 🔴 (+38%) 🔻 1962 🔴 (+34%) 🔻 30
STSO 1020 steps (1-20) 177 (-2.2%) 284 🔴 (+2.9%) 665 🔴 (+119%) 🔻 806 🔴 (+105%) 🔻 19
STSO 1020 steps (101-120) 183 (-1.6%) 252 🔴 (-5.6%) 318 🔴 (+5.6%) 414 🔴 (-83%) 💚 19
STSO 1020 steps (1001-1020) 471 (+5.4%) 564 🔴 (+9.3%) 593 🔴 (+10%) 1191 🔴 (+81%) 🔻 19
WO 1020 steps 393013 (+2.2%) 393013 (+2.2%) 393013 (+2.2%) 393013 (+2.2%) 1
SL stream latency 102 (+32%) 🔻 174 🔴 (+40%) 🔻 384 🔴 (+167%) 🔻 454 🔴 (+78%) 🔻 30
SO stream overhead (text) 115 (+19%) 🔻 174 (+12%) 193 (+10%) 261 (+32%) 🔻 30
SO stream overhead (structured) 123 (+22%) 🔻 186 (+11%) 209 (+18%) 🔻 288 (-81%) 💚 30
ℹ️ Metric definitions & methodology

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) · 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 · 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 · STSO (1-20) 20/30/60 · STSO (101-120) 30/45/90 · STSO (1001-1020) 40/60/120

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. 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-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

Some tests failed

❌ Failed E2E Tests

▲ Vercel Production (1 failed)

astro (1 failed):

  • outputStreamWorkflow negative startIndex (reads from end)

E2E Test Summary

Summary
Passed Failed Skipped Total
❌ ▲ Vercel Production 1454 1 239 1694
✅ 💻 Local Development 1621 0 227 1848
✅ 📦 Local Production 1621 0 227 1848
✅ 🐘 Local Postgres 1621 0 227 1848
✅ 🪟 Windows 154 0 0 154
✅ 📋 Other 1020 0 212 1232
✅ vercel-multi-region 27 0 0 27
Total 7518 1 1132 8651
Details by Category

❌ ▲ Vercel Production

App Passed Failed Skipped
❌ astro 125 1 28
✅ example 126 0 28
✅ express 126 0 28
✅ fastify 126 0 28
✅ hono 126 0 28
✅ nextjs-turbopack 151 0 3
✅ nextjs-webpack 151 0 3
✅ nitro 126 0 28
✅ nuxt 126 0 28
✅ sveltekit 145 0 9
✅ vite 126 0 28

✅ 💻 Local Development

App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-canary 135 0 19
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26

✅ 📦 Local Production

App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-canary 135 0 19
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26

✅ 🐘 Local Postgres

App Passed Failed Skipped
✅ astro-stable 128 0 26
✅ express-stable 128 0 26
✅ fastify-stable 128 0 26
✅ hono-stable 128 0 26
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-turbopack-stable 154 0 0
✅ nextjs-webpack-canary 135 0 19
✅ nextjs-webpack-stable 154 0 0
✅ nitro-stable 128 0 26
✅ nuxt-stable 128 0 26
✅ sveltekit-stable 147 0 7
✅ vite-stable 128 0 26

✅ 🪟 Windows

App Passed Failed Skipped
✅ nextjs-turbopack 154 0 0

✅ 📋 Other

App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 128 0 26
✅ e2e-local-dev-tanstack-start- 128 0 26
✅ e2e-local-postgres-nest-stable 128 0 26
✅ e2e-local-postgres-tanstack-start- 128 0 26
✅ e2e-local-prod-nest-stable 128 0 26
✅ e2e-local-prod-tanstack-start- 128 0 26
✅ e2e-vercel-prod-nest 126 0 28
✅ e2e-vercel-prod-tanstack-start 126 0 28

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

📋 View full workflow run

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant