Skip to content

[core] Ack'd write-channel stream writes with framed-v2 writer markers#2731

Open
VaguelySerious wants to merge 10 commits into
mainfrom
peter/streaming-writes
Open

[core] Ack'd write-channel stream writes with framed-v2 writer markers#2731
VaguelySerious wants to merge 10 commits into
mainfrom
peter/streaming-writes

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Jun 30, 2026

Copy link
Copy Markdown
Member

What

Stream writes move from one short PUT per 10 ms flush batch to a long-lived, acknowledged write channel (WebSocket), with a new framed-v2 wire format that stamps every frame with a per-writer marker (writerId + seq).

  • Streamer.connectWrite (optional world capability): opens a write channel; each chunk is one binary message, persisted and published by the backend on arrival and acked back {index, chunkIndex} in order.
  • StreamSocketWriter: evicts its replay buffer per ack (memory bounded by a small in-flight window, not connection lifetime), recycles connections proactively (~110 s), and survives unclean closes by resending unacked frames on a fresh channel, under consecutive + lifetime reconnect budgets.
  • framed-v2: read-side dedupe by (writerId, seq) makes the resend overlap exactly-once, including with concurrent writers to one stream (parent → child forwarded writables mint their own writerId; framing is per-stream and carried in descriptors and through the workflow VM round-trip).
  • getWritable emits framed-v2 based on framedStreamMarkersEnabled(<run's SDK version>); getReadable reproduces the same decision from the run's executionContext.workflowCoreVersion (lazily, on first chunk). Everything below the version cutoff stays byte-identical framed-v1 + batch writes, including all non-Vercel worlds (no connectWrite → batch path, unchanged).

Why

Today every active writer costs ~100 requests/second and a chunk is only durable-confirmed when its batch's PUT resolves. A streaming request body can't fix this on deployed infra (bodies are delivered to functions only at close — verified empirically), while WS messages arrive incrementally: the deployed probe measured p50 75 ms chunk-to-ack, with live readers seeing chunks immediately via per-chunk publish.

Validation

  • Unit: 1393 core + 226 world-vercel tests green, including the socket writer's reconnect/resend/rotation/budget matrix, sink selection, the getWritable flip round-trip (markers on, markers off, batch fallback), and ack-protocol edge cases.
  • Deployed probes against the world-vercel backend preview: 30-frame slow-drip over the real channel — 30/30 acks in order, contiguous chunk indices, live reader sees every chunk while the writer is still open.
  • WORKFLOW_EXPERIMENTAL_STREAM_MARKERS=1 force-enables the path for e2e ahead of the version cutoff, so e2e can assert engagement rather than silently exercising the legacy path.
  • TODO(release): framedStreamMarkers minVersion is 5.0.0-beta.27 — verify it matches the version this actually ships in.

🤖 Generated with Claude Code

Groundwork for single-request streaming stream writes. Adds an opt-in
framed-v2 frame header carrying a per-writer marker ([writerId][seq])
to both the byte framer/unframer and the object serialize/deserialize
streams, plus a shared marker codec. The reader strips the marker and
dedupes replays by max-seq-per-writerId, so a frame recovery re-sends
after it was already persisted is delivered exactly once.

Not yet wired into any writer (no writerId is passed today), so there
is no user-facing behavior change. Capability gating + the streaming
segment writer + tail-match recovery follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6cdfe67

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 Minor
@workflow/world Minor
@workflow/world-vercel Minor
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
workflow Minor
@workflow/world-testing Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt 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 Jun 30, 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 2, 2026 8:55pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jul 2, 2026 8:55pm
example-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-astro-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-express-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-fastify-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-hono-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-nitro-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workbench-vite-workflow Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jul 2, 2026 8:55pm
workflow-swc-playground Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workflow-tarballs Ready Ready Preview, Comment Jul 2, 2026 8:55pm
workflow-web Ready Ready Preview, Comment Jul 2, 2026 8:55pm

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 0.041s (-27.1% 🟢) 1.005s (~) 0.964s 10 1.00x
💻 Local Express 0.044s (-7.9% 🟢) 1.006s (~) 0.962s 10 1.08x
💻 Local Nitro 0.049s (-15.7% 🟢) 1.006s (-1.3%) 0.957s 10 1.20x
🐘 Postgres Next.js (Turbopack) 0.062s (-0.8%) 1.012s (~) 0.951s 10 1.52x
🐘 Postgres Express 0.066s (+3.3%) 1.013s (~) 0.947s 10 1.62x
🐘 Postgres Nitro 0.067s (~) 1.013s (~) 0.945s 10 1.65x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 0.206s (-7.9% 🟢) 2.098s (+17.6% 🔺) 1.892s 10 1.00x
▲ Vercel Next.js (Turbopack) 0.232s (-69.0% 🟢) 2.073s (-15.9% 🟢) 1.841s 10 1.12x
▲ Vercel Nitro 0.289s (+60.5% 🔺) 2.087s (+28.3% 🔺) 1.798s 10 1.40x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.072s (-1.9%) 2.005s (~) 0.933s 10 1.00x
💻 Local Express 1.081s (~) 2.006s (~) 0.925s 10 1.01x
💻 Local Nitro 1.086s (~) 2.007s (~) 0.921s 10 1.01x
🐘 Postgres Express 1.095s (~) 2.010s (~) 0.915s 10 1.02x
🐘 Postgres Nitro 1.098s (~) 2.010s (~) 0.912s 10 1.02x
🐘 Postgres Next.js (Turbopack) 1.103s (~) 2.010s (~) 0.907s 10 1.03x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.376s (~) 3.164s (-4.3%) 1.789s 10 1.00x
▲ Vercel Express 1.418s (-4.2%) 3.236s (-3.1%) 1.818s 10 1.03x
▲ Vercel Next.js (Turbopack) 2.598s (+26.2% 🔺) 4.526s (+24.5% 🔺) 1.928s 10 1.89x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 10.375s (-1.2%) 11.020s (~) 0.645s 3 1.00x
💻 Local Express 10.440s (~) 11.023s (~) 0.584s 3 1.01x
🐘 Postgres Express 10.468s (~) 11.015s (~) 0.547s 3 1.01x
💻 Local Nitro 10.472s (~) 11.023s (~) 0.550s 3 1.01x
🐘 Postgres Next.js (Turbopack) 10.525s (~) 11.020s (~) 0.495s 3 1.01x
🐘 Postgres Nitro 10.546s (~) 11.017s (~) 0.471s 3 1.02x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 11.611s (~) 13.077s (+2.4%) 1.466s 3 1.00x
▲ Vercel Next.js (Turbopack) 13.063s (+11.0% 🔺) 14.256s (+3.0%) 1.194s 3 1.13x
▲ Vercel Express 13.372s (+14.6% 🔺) 15.374s (+16.3% 🔺) 2.002s 3 1.15x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 13.537s (-1.6%) 14.026s (~) 0.489s 5 1.00x
💻 Local Express 13.621s (~) 14.027s (~) 0.406s 5 1.01x
💻 Local Nitro 13.622s (~) 14.028s (~) 0.405s 5 1.01x
🐘 Postgres Nitro 13.648s (~) 14.020s (~) 0.372s 5 1.01x
🐘 Postgres Express 13.696s (~) 14.020s (~) 0.325s 5 1.01x
🐘 Postgres Next.js (Turbopack) 13.790s (+0.8%) 14.024s (~) 0.233s 5 1.02x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 16.247s (-1.3%) 17.487s (-2.8%) 1.239s 4 1.00x
▲ Vercel Express 17.076s (+1.8%) 18.777s (+1.6%) 1.701s 4 1.05x
▲ Vercel Next.js (Turbopack) 17.991s (+3.4%) 19.861s (+2.9%) 1.871s 4 1.11x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 11.847s (-4.6%) 12.021s (-7.7% 🟢) 0.174s 8 1.00x
🐘 Postgres Nitro 12.101s (-1.8%) 13.020s (~) 0.920s 7 1.02x
💻 Local Express 12.122s (~) 13.025s (~) 0.903s 7 1.02x
🐘 Postgres Express 12.256s (~) 13.017s (~) 0.761s 7 1.03x
💻 Local Nitro 12.259s (~) 13.025s (~) 0.766s 7 1.03x
🐘 Postgres Next.js (Turbopack) 12.757s (+2.7%) 13.302s (+2.2%) 0.545s 7 1.08x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 17.826s (-3.8%) 18.933s (-5.6% 🟢) 1.108s 5 1.00x
▲ Vercel Express 17.889s (+1.8%) 19.579s (+4.0%) 1.690s 5 1.00x
▲ Vercel Next.js (Turbopack) 20.071s (+3.2%) 21.935s (+1.6%) 1.863s 5 1.13x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.179s (-0.9%) 2.008s (~) 0.828s 15 1.00x
🐘 Postgres Express 1.202s (-0.5%) 2.006s (~) 0.805s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.206s (~) 2.009s (~) 0.802s 15 1.02x
💻 Local Next.js (Turbopack) 1.387s (-3.2%) 2.006s (~) 0.619s 15 1.18x
💻 Local Nitro 1.410s (~) 2.007s (~) 0.596s 15 1.20x
💻 Local Express 1.452s (+5.2% 🔺) 2.006s (~) 0.555s 15 1.23x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.082s (-1.4%) 3.642s (+2.6%) 1.560s 9 1.00x
▲ Vercel Nitro 2.137s (-4.9%) 3.405s (-7.1% 🟢) 1.267s 9 1.03x
▲ Vercel Next.js (Turbopack) 3.422s (+8.3% 🔺) 4.687s (-10.1% 🟢) 1.265s 7 1.64x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.316s (-2.7%) 2.318s (-6.2% 🟢) 1.002s 13 1.00x
🐘 Postgres Next.js (Turbopack) 1.319s (~) 2.918s (-3.0%) 1.599s 11 1.00x
🐘 Postgres Express 1.380s (+0.6%) 2.592s (+3.3%) 1.213s 12 1.05x
💻 Local Next.js (Turbopack) 2.298s (-3.9%) 3.007s (~) 0.710s 10 1.75x
💻 Local Express 2.376s (-4.2%) 3.008s (-3.2%) 0.632s 10 1.81x
💻 Local Nitro 2.486s (+2.9%) 3.009s (~) 0.524s 10 1.89x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.315s (-4.7%) 3.807s (-4.5%) 1.492s 8 1.00x
▲ Vercel Nitro 2.433s (~) 3.692s (-1.7%) 1.258s 9 1.05x
▲ Vercel Next.js (Turbopack) 3.688s (+7.5% 🔺) 5.144s (-2.1%) 1.456s 6 1.59x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.582s (-1.5%) 4.438s (+3.1%) 2.856s 7 1.00x
🐘 Postgres Express 1.639s (-3.0%) 4.136s (-6.9% 🟢) 2.497s 8 1.04x
🐘 Postgres Next.js (Turbopack) 3.269s (+2.6%) 6.017s (+2.9%) 2.747s 5 2.07x
💻 Local Next.js (Turbopack) 3.511s (-7.2% 🟢) 4.439s (-8.8% 🟢) 0.928s 7 2.22x
💻 Local Express 3.580s (+1.3%) 4.441s (-6.1% 🟢) 0.860s 7 2.26x
💻 Local Nitro 4.713s (+22.8% 🔺) 5.014s (+9.3% 🔺) 0.301s 6 2.98x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.814s (-5.5% 🟢) 4.535s (+1.1%) 1.721s 7 1.00x
▲ Vercel Nitro 3.365s (+8.6% 🔺) 5.118s (+8.1% 🔺) 1.753s 6 1.20x
▲ Vercel Next.js (Turbopack) 5.062s (+12.4% 🔺) 6.645s (+4.6%) 1.583s 5 1.80x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.185s (-3.7%) 2.007s (-3.3%) 0.822s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.189s (-5.0% 🟢) 2.007s (-3.3%) 0.818s 15 1.00x
🐘 Postgres Nitro 1.194s (~) 2.008s (~) 0.815s 15 1.01x
💻 Local Next.js (Turbopack) 1.360s (-6.6% 🟢) 2.006s (~) 0.646s 15 1.15x
💻 Local Nitro 1.431s (~) 2.007s (~) 0.575s 15 1.21x
💻 Local Express 1.437s (~) 2.006s (~) 0.570s 15 1.21x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.117s (~) 3.769s (+5.1% 🔺) 1.652s 8 1.00x
▲ Vercel Nitro 2.221s (-10.5% 🟢) 3.532s (-8.2% 🟢) 1.312s 9 1.05x
▲ Vercel Next.js (Turbopack) 3.650s (+8.0% 🔺) 5.133s (-3.3%) 1.483s 6 1.72x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.307s (-0.8%) 2.222s (-7.1% 🟢) 0.915s 14 1.00x
🐘 Postgres Next.js (Turbopack) 1.323s (-1.1%) 3.109s (+3.3%) 1.786s 10 1.01x
🐘 Postgres Express 1.341s (-1.8%) 2.394s (-7.7% 🟢) 1.053s 13 1.03x
💻 Local Next.js (Turbopack) 2.297s (-12.9% 🟢) 3.008s (~) 0.711s 10 1.76x
💻 Local Express 2.498s (+1.8%) 3.109s (+3.4%) 0.610s 10 1.91x
💻 Local Nitro 2.541s (+2.5%) 3.009s (~) 0.468s 10 1.94x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.231s (-5.1% 🟢) 3.375s (-10.5% 🟢) 1.144s 9 1.00x
▲ Vercel Express 2.368s (+4.8%) 3.819s (+6.7% 🔺) 1.451s 8 1.06x
▲ Vercel Next.js (Turbopack) 3.915s (+15.4% 🔺) 5.482s (+2.8%) 1.567s 6 1.76x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.605s (~) 4.138s (~) 2.533s 8 1.00x
🐘 Postgres Express 1.607s (-3.0%) 4.011s (-6.7% 🟢) 2.404s 8 1.00x
🐘 Postgres Next.js (Turbopack) 2.908s (-11.8% 🟢) 6.014s (-3.2%) 3.106s 5 1.81x
💻 Local Next.js (Turbopack) 4.577s (-18.3% 🟢) 5.210s (-13.4% 🟢) 0.634s 6 2.85x
💻 Local Nitro 4.880s (-13.4% 🟢) 5.848s (-2.8%) 0.969s 6 3.04x
💻 Local Express 5.052s (-8.1% 🟢) 5.851s (-2.7%) 0.799s 6 3.15x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.787s (-2.9%) 4.522s (+2.6%) 1.735s 7 1.00x
▲ Vercel Express 3.312s (+23.9% 🔺) 5.075s (+17.9% 🔺) 1.764s 6 1.19x
▲ Vercel Next.js (Turbopack) 4.457s (-11.2% 🟢) 5.955s (-12.9% 🟢) 1.498s 6 1.60x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 0.513s (-18.7% 🟢) 1.006s (~) 0.493s 60 1.00x
🐘 Postgres Nitro 0.548s (-6.2% 🟢) 1.023s (-1.7%) 0.475s 59 1.07x
🐘 Postgres Express 0.572s (-4.7%) 1.023s (~) 0.451s 59 1.12x
💻 Local Express 0.582s (+2.4%) 1.005s (~) 0.423s 60 1.13x
🐘 Postgres Next.js (Turbopack) 0.608s (+7.9% 🔺) 1.023s (+1.7%) 0.416s 59 1.18x
💻 Local Nitro 0.616s (+2.7%) 1.005s (~) 0.389s 60 1.20x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.417s (-6.3% 🟢) 3.714s (-8.8% 🟢) 1.297s 17 1.00x
▲ Vercel Express 2.460s (-5.2% 🟢) 3.941s (+4.1%) 1.482s 16 1.02x
▲ Vercel Next.js (Turbopack) 4.060s (+19.2% 🔺) 5.940s (+14.2% 🔺) 1.880s 11 1.68x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.237s (-8.7% 🟢) 2.007s (~) 0.771s 45 1.00x
💻 Local Next.js (Turbopack) 1.320s (-20.3% 🟢) 2.005s (-1.2%) 0.685s 45 1.07x
🐘 Postgres Express 1.339s (-5.5% 🟢) 2.030s (-1.1%) 0.691s 45 1.08x
🐘 Postgres Next.js (Turbopack) 1.380s (-4.2%) 2.008s (-1.1%) 0.628s 45 1.12x
💻 Local Express 1.461s (-1.3%) 2.006s (-1.1%) 0.545s 45 1.18x
💻 Local Nitro 1.519s (~) 2.029s (+1.1%) 0.510s 45 1.23x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.558s (-7.5% 🟢) 6.913s (-8.6% 🟢) 1.355s 14 1.00x
▲ Vercel Express 5.804s (-6.3% 🟢) 7.361s (-3.1%) 1.557s 13 1.04x
▲ Vercel Next.js (Turbopack) 8.998s (~) 10.809s (-2.6%) 1.811s 9 1.62x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.544s (-5.9% 🟢) 3.058s (-0.9%) 0.514s 40 1.00x
🐘 Postgres Express 2.696s (+1.3%) 3.085s (+1.7%) 0.389s 39 1.06x
🐘 Postgres Next.js (Turbopack) 2.799s (+1.0%) 3.059s (+1.7%) 0.260s 40 1.10x
💻 Local Next.js (Turbopack) 2.968s (-17.3% 🟢) 3.368s (-17.4% 🟢) 0.401s 36 1.17x
💻 Local Express 3.212s (+1.5%) 3.912s (+0.8%) 0.700s 31 1.26x
💻 Local Nitro 3.325s (+2.4%) 4.043s (+0.8%) 0.718s 30 1.31x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 11.441s (-8.8% 🟢) 13.117s (-6.8% 🟢) 1.676s 10 1.00x
▲ Vercel Nitro 11.538s (-3.8%) 13.103s (-1.9%) 1.565s 10 1.01x
▲ Vercel Next.js (Turbopack) 18.612s (+6.3% 🔺) 19.906s (~) 1.294s 7 1.63x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.188s (-10.4% 🟢) 1.006s (-1.7%) 0.818s 60 1.00x
🐘 Postgres Nitro 0.208s (-6.3% 🟢) 1.006s (~) 0.799s 60 1.11x
🐘 Postgres Express 0.231s (+8.0% 🔺) 1.006s (~) 0.775s 60 1.23x
💻 Local Next.js (Turbopack) 0.503s (-22.9% 🟢) 1.004s (-1.8%) 0.501s 60 2.68x
💻 Local Nitro 0.520s (+4.3%) 1.005s (~) 0.485s 60 2.77x
💻 Local Express 0.529s (+8.4% 🔺) 1.004s (~) 0.475s 60 2.82x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.068s (+1.1%) 2.529s (+11.0% 🔺) 1.461s 24 1.00x
▲ Vercel Nitro 1.071s (+9.0% 🔺) 2.313s (+4.4%) 1.242s 26 1.00x
▲ Vercel Next.js (Turbopack) 2.722s (+36.4% 🔺) 4.369s (+13.2% 🔺) 1.647s 14 2.55x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.340s (+1.5%) 1.006s (~) 0.666s 90 1.00x
🐘 Postgres Next.js (Turbopack) 0.347s (+17.1% 🔺) 1.006s (~) 0.659s 90 1.02x
🐘 Postgres Express 0.353s (+9.0% 🔺) 1.017s (+1.1%) 0.664s 89 1.04x
💻 Local Next.js (Turbopack) 2.272s (-14.7% 🟢) 3.014s (~) 0.742s 30 6.68x
💻 Local Nitro 2.447s (-3.9%) 3.009s (~) 0.562s 30 7.19x
💻 Local Express 2.545s (+2.9%) 3.010s (~) 0.465s 30 7.48x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.362s (+2.7%) 2.801s (+13.3% 🔺) 1.439s 33 1.00x
▲ Vercel Nitro 1.411s (+1.9%) 2.786s (+1.4%) 1.375s 33 1.04x
▲ Vercel Next.js (Turbopack) 3.386s (+32.5% 🔺) 5.024s (+18.3% 🔺) 1.638s 18 2.49x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.530s (-0.6%) 3.010s (~) 2.479s 40 1.00x
🐘 Postgres Nitro 0.554s (+9.6% 🔺) 1.160s (+12.5% 🔺) 0.607s 104 1.04x
🐘 Postgres Express 0.582s (+11.5% 🔺) 1.118s (+3.7%) 0.536s 108 1.10x
💻 Local Next.js (Turbopack) 5.592s (-7.5% 🟢) 7.898s (-11.8% 🟢) 2.306s 16 10.55x
💻 Local Express 5.603s (-3.7%) 8.291s (-6.7% 🟢) 2.689s 15 10.57x
💻 Local Nitro 6.074s (+13.2% 🔺) 9.098s (+8.8% 🔺) 3.024s 14 11.45x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.974s (+9.0% 🔺) 3.597s (+5.9% 🔺) 1.623s 34 1.00x
▲ Vercel Express 2.004s (+9.4% 🔺) 3.868s (+5.7% 🔺) 1.864s 32 1.02x
▲ Vercel Next.js (Turbopack) 4.639s (+12.1% 🔺) 6.171s (+3.6%) 1.533s 20 2.35x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.111s (-3.2%) 1.974s (~) 0.008s (-39.2% 🟢) 2.014s (~) 0.902s 10 1.00x
🐘 Postgres Nitro 1.151s (-0.6%) 1.996s (~) 0.001s (-7.7% 🟢) 2.010s (~) 0.859s 10 1.04x
💻 Local Nitro 1.161s (~) 2.005s (~) 0.012s (-0.8%) 2.020s (~) 0.859s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.171s (~) 2.000s (~) 0.001s (+16.7% 🔺) 2.011s (~) 0.840s 10 1.05x
💻 Local Express 1.179s (+2.4%) 2.004s (~) 0.010s (-1.0%) 2.018s (~) 0.838s 10 1.06x
🐘 Postgres Express 1.181s (+1.7%) 1.998s (~) 0.001s (+30.0% 🔺) 2.011s (~) 0.829s 10 1.06x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.902s (-12.5% 🟢) 3.285s (-3.9%) 1.808s (-3.4%) 5.560s (-2.6%) 3.659s 10 1.00x
▲ Vercel Nitro 2.167s (+2.3%) 3.211s (+0.9%) 1.930s (+4.3%) 5.702s (+4.1%) 3.535s 10 1.14x
▲ Vercel Next.js (Turbopack) 3.672s (+8.5% 🔺) 3.545s (-18.8% 🟢) 1.274s (+34.7% 🔺) 6.489s (-1.4%) 2.817s 10 1.93x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.506s (-6.6% 🟢) 1.975s (~) 0.010s (-24.5% 🟢) 2.020s (~) 0.514s 30 1.00x
🐘 Postgres Nitro 1.575s (~) 2.004s (~) 0.005s (+10.9% 🔺) 2.028s (~) 0.453s 30 1.05x
💻 Local Express 1.584s (~) 2.007s (~) 0.013s (+0.8%) 2.024s (~) 0.441s 30 1.05x
💻 Local Nitro 1.586s (+0.6%) 2.011s (~) 0.012s (-6.2% 🟢) 2.026s (~) 0.440s 30 1.05x
🐘 Postgres Express 1.591s (~) 2.006s (~) 0.005s (-8.8% 🟢) 2.026s (~) 0.435s 30 1.06x
🐘 Postgres Next.js (Turbopack) 1.627s (-3.7%) 2.010s (-1.6%) 0.005s (-0.6%) 2.025s (-1.7%) 0.398s 30 1.08x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.387s (~) 6.843s (+2.9%) 0.272s (+20.4% 🔺) 7.583s (+3.1%) 2.196s 8 1.00x
▲ Vercel Nitro 5.484s (+0.7%) 6.552s (~) 0.441s (+130.4% 🔺) 7.391s (+2.9%) 1.907s 9 1.02x
▲ Vercel Next.js (Turbopack) 10.090s (+11.0% 🔺) 11.526s (+9.4% 🔺) 0.268s (-2.3%) 12.318s (+4.8%) 2.228s 5 1.87x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.777s (~) 1.028s (-3.5%) 0.000s (-34.5% 🟢) 1.053s (-3.2%) 0.277s 57 1.00x
🐘 Postgres Express 0.820s (+4.8%) 1.126s (+7.2% 🔺) 0.000s (+Infinity% 🔺) 1.140s (+5.4% 🔺) 0.319s 53 1.06x
🐘 Postgres Next.js (Turbopack) 0.975s (+0.7%) 1.395s (-3.1%) 0.000s (+281.4% 🔺) 1.403s (-4.2%) 0.428s 43 1.26x
💻 Local Next.js (Turbopack) 1.174s (-12.3% 🟢) 1.777s (-10.1% 🟢) 0.000s (+7.8% 🔺) 1.809s (-10.3% 🟢) 0.635s 34 1.51x
💻 Local Express 1.416s (+2.5%) 1.981s (+1.6%) 0.000s (-12.5% 🟢) 1.984s (+1.6%) 0.568s 31 1.82x
💻 Local Nitro 1.438s (+4.5%) 2.014s (+1.7%) 0.000s (-15.5% 🟢) 2.017s (+1.7%) 0.578s 30 1.85x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.991s (-1.6%) 4.080s (-2.2%) 0.001s (+Infinity% 🔺) 4.473s (-3.0%) 1.481s 14 1.00x
▲ Vercel Express 3.197s (+10.5% 🔺) 4.635s (+14.0% 🔺) 0.000s (-100.0% 🟢) 5.072s (+14.0% 🔺) 1.874s 12 1.07x
▲ Vercel Next.js (Turbopack) 5.182s (+6.3% 🔺) 5.508s (-4.1%) 0.000s (-100.0% 🟢) 6.686s (-2.5%) 1.504s 10 1.73x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.693s (-4.6%) 2.304s (-3.8%) 0.000s (-100.0% 🟢) 2.347s (-2.6%) 0.654s 26 1.00x
🐘 Postgres Nitro 1.810s (-1.6%) 2.434s (+2.3%) 0.000s (NaN%) 2.456s (+2.5%) 0.647s 25 1.07x
🐘 Postgres Next.js (Turbopack) 2.505s (-8.9% 🟢) 3.002s (-10.0% 🟢) 0.000s (-100.0% 🟢) 3.010s (-10.0% 🟢) 0.505s 20 1.48x
💻 Local Next.js (Turbopack) 3.073s (-11.6% 🟢) 3.522s (-11.7% 🟢) 0.001s (+63.9% 🔺) 3.555s (-11.8% 🟢) 0.481s 17 1.82x
💻 Local Nitro 3.247s (+2.2%) 3.777s (+2.8%) 0.000s (+6.3% 🔺) 3.780s (+2.8%) 0.533s 16 1.92x
💻 Local Express 3.331s (-3.2%) 4.028s (~) 0.000s (+75.0% 🔺) 4.031s (~) 0.700s 15 1.97x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.283s (+3.2%) 5.207s (-2.0%) 0.000s (-100.0% 🟢) 5.685s (-0.7%) 1.401s 11 1.00x
▲ Vercel Express 4.291s (+1.0%) 5.659s (+7.6% 🔺) 0.000s (+Infinity% 🔺) 6.121s (+8.5% 🔺) 1.830s 10 1.00x
▲ Vercel Next.js (Turbopack) 7.019s (-1.9%) 7.320s (-9.1% 🟢) 0.000s (-100.0% 🟢) 8.279s (-9.2% 🟢) 1.260s 8 1.64x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 21/21
🐘 Postgres Nitro 14/21
▲ Vercel Nitro 11/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 14/21
Next.js (Turbopack) 🐘 Postgres 12/21
Nitro 🐘 Postgres 17/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1442 0 230 1672
✅ 💻 Local Development 1605 0 219 1824
✅ 📦 Local Production 1605 0 219 1824
✅ 🐘 Local Postgres 1593 0 231 1824
✅ 🪟 Windows 152 0 0 152
✅ 📋 Other 885 0 179 1064
Total 7282 0 1078 8360

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 125 0 27
✅ example 125 0 27
✅ express 125 0 27
✅ fastify 125 0 27
✅ hono 125 0 27
✅ nextjs-turbopack 149 0 3
✅ nextjs-webpack 149 0 3
✅ nitro 125 0 27
✅ nuxt 125 0 27
✅ sveltekit 144 0 8
✅ vite 125 0 27
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 127 0 25
✅ express-stable 127 0 25
✅ fastify-stable 127 0 25
✅ hono-stable 127 0 25
✅ nextjs-turbopack-canary 133 0 19
✅ nextjs-turbopack-stable 152 0 0
✅ nextjs-webpack-canary 133 0 19
✅ nextjs-webpack-stable 152 0 0
✅ nitro-stable 127 0 25
✅ nuxt-stable 127 0 25
✅ sveltekit-stable 146 0 6
✅ vite-stable 127 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 127 0 25
✅ express-stable 127 0 25
✅ fastify-stable 127 0 25
✅ hono-stable 127 0 25
✅ nextjs-turbopack-canary 133 0 19
✅ nextjs-turbopack-stable 152 0 0
✅ nextjs-webpack-canary 133 0 19
✅ nextjs-webpack-stable 152 0 0
✅ nitro-stable 127 0 25
✅ nuxt-stable 127 0 25
✅ sveltekit-stable 146 0 6
✅ vite-stable 127 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 126 0 26
✅ express-stable 126 0 26
✅ fastify-stable 126 0 26
✅ hono-stable 126 0 26
✅ nextjs-turbopack-canary 132 0 20
✅ nextjs-turbopack-stable 151 0 1
✅ nextjs-webpack-canary 132 0 20
✅ nextjs-webpack-stable 151 0 1
✅ nitro-stable 126 0 26
✅ nuxt-stable 126 0 26
✅ sveltekit-stable 145 0 7
✅ vite-stable 126 0 26
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 152 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 127 0 25
✅ e2e-local-dev-tanstack-start- 127 0 25
✅ e2e-local-postgres-nest-stable 126 0 26
✅ e2e-local-postgres-tanstack-start- 126 0 26
✅ e2e-local-prod-nest-stable 127 0 25
✅ e2e-local-prod-tanstack-start- 127 0 25
✅ e2e-vercel-prod-tanstack-start 125 0 27

📋 View full workflow run

FNV-1a (64-bit) over a seed string → 8-byte writerId. Callers pass a
value stable across deterministic replays (a seeded STABLE_ULID), so
the writerId is replay-stable without consuming the VM's seeded RNG
(which would shift the sequence observed by user code).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread packages/core/src/serialization/segment-writer.ts Outdated
recoverStreamTail reconstructs, after an unclean segment failure, which of a
writer's in-flight frames the backend persisted, and replays only the rest:

- Scans the persisted tail window [max(priorIndices)+1, tailIndex], matching
  the writer's OWN frames by the framed-v2 marker (concurrent writers share a
  stream, so a server index isn't attributable to one writer).
- Handles the reserve-ahead race (tailIndex can point at a reserved-but-
  unpersisted chunk) with read-with-backoff 10/100/1000ms; a tail chunk still
  missing after the window is a real write failure, surfaced not skipped.
- Replays frames with seq > max-persisted-seq on a fresh writeStream, bounded
  by maxAttempts (re-scans between attempts since a replay may partially
  persist).

StreamSegmentWriter.recover now receives the prior clean segment's indices as
the scan anchor. Both fully unit-tested; not yet wired into the writer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite WorkflowServerWritableStream from unconditional 10ms timer
batching to lazy sink selection: when the world exposes
streams.writeStream and the writer has a writerId (framed-v2), frames
flow through StreamSegmentWriter (one long-lived streaming request per
~10s segment, clean 200 drops the buffer, recoverStreamTail replays
unconfirmed frames on unclean failure). Otherwise the existing
per-batch writeMulti path is used, extracted verbatim into
createBatchSink.

No behavior change yet: no caller passes a writerId, so all writers
stay on the batch path until getWritable is wired for framed-v2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
},
DEFAULT_SEGMENT_CONFIG
);

@vercel vercel Bot Jul 2, 2026

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.

The socket sink's close() skips the run-ready barrier on an empty stream, so X-Stream-Done can race run creation in turbo optimistic start (run-not-found / lost close marker).

Fix on Vercel

VaguelySerious and others added 2 commits July 2, 2026 13:49
Replace the streaming-PUT write path with an ack'd WebSocket write
channel, and wire framed-v2 emission end to end.

Transport: `Streamer.connectWrite` opens a channel on the world-vercel
v3 `/ws` stream route (undici WebSocket so the upgrade carries the same
auth headers as every HTTP call). Each chunk is one binary message,
persisted + published on arrival and acked back `{index, chunkIndex}`
in order. The streaming-PUT `writeStream` is removed: the platform
buffers streaming request bodies whole, so it could never deliver
incremental visibility (probed empirically; the WS path delivers
~75ms p50 chunk-to-ack on deployed infra).

Writer: `StreamSocketWriter` replaces the segment writer — the local
buffer is evicted per ack instead of per request, bounded by an
in-flight window; connections recycle proactively under the server's
bound; an unclean close resends everything unacked on a fresh channel,
with consecutive + lifetime reconnect budgets. The tail-scan recovery
module is removed: read-side framed-v2 dedupe (writerId+seq) absorbs
the persisted-but-unacked resend overlap, which acks make tiny.

Flip: `getWritable` derives a replay-stable writerId and emits
framed-v2 when `framedStreamMarkersEnabled(own version)` — a per-run
decision every reader can reproduce. `getReadable` derives the same
answer from the run's `executionContext.workflowCoreVersion` via a
lazy thenable (fetched only when the first chunk arrives). Framing is
per-stream: forwarded writables carry `framing` in their descriptor
(and through the workflow VM round-trip), and a forwarded writer mints
its own writerId. `WORKFLOW_EXPERIMENTAL_STREAM_MARKERS=1`
force-enables for tests/e2e ahead of the version cutoff, so e2e can
assert engagement instead of silently exercising the legacy path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
this.ensureReadyPromise ??= this.deps.ensureReady();
await this.ensureReadyPromise;
}
const epoch = ++this.epoch;

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.

On a WebSocket connect failure, onChannelClose is invoked twice for one failed attempt, double-counting consecutiveReconnects/totalReconnects and scheduling two reconnect timers.

Fix on Vercel

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