Skip to content

[core] framed-v2 stream wire format with per-writer markers#2731

Draft
VaguelySerious wants to merge 7 commits into
mainfrom
peter/streaming-writes
Draft

[core] framed-v2 stream wire format with per-writer markers#2731
VaguelySerious wants to merge 7 commits into
mainfrom
peter/streaming-writes

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Jun 30, 2026

Copy link
Copy Markdown
Member

Draft / WIP. Moving stream writes from one HTTP roundtrip per flush batch to a single long-lived streaming request, with client-driven reconnect/recovery. This PR builds the pieces bottom-up; each is isolated, unit-tested, and dormant until the final wiring flips it on. No user-facing behavior change yet — nothing emits framed-v2 or uses the streaming writer until the writer/reader are wired together (deliberately deferred; see below).

Landed so far (all behind the final wiring)

1. framed-v2 wire format — extends framed-v1 (length-prefixed) with a per-writer marker so a writer can find its own frames in a shared, interleaved tail during recovery:

[4-byte BE length][writerId: 8 bytes][seq: 8-byte BE uint64][inner payload]
  • Marker codec serialization/frame-marker.ts (buildFramedV2Frame/readFrameMarker/writerIdKey; FRAME_HEADER_SIZE single-sourced) + deriveWriterId (replay-deterministic, FNV-1a over a seeded ULID — does not consume VM RNG).
  • Both stream kinds carry the marker when opted in (byte + object framers/unframers). Read-side dedupe tracks max seq per writerId → exactly-once delivery when recovery re-sends an already-persisted frame.

2. Streaming transport (world-vercel)instrumentedFetch accepts a ReadableStream body and sets duplex: 'half' (upload-only, not bidirectional). New Streamer.writeStream(runId, name, chunks) opens one long-lived PUT (same v2 endpoint + multi-chunk wire format as writeMulti, world owns per-frame framing) and returns the backend chunkIndices (the recovery anchor). Uses the global dispatcher + no timeout, mirroring the live-read: a consumed stream body can't be replayed, so recovery lives above the transport.

3. Capability gateframedStreamMarkers (minVersion 5.0.0-beta.27), gating framed-v2 + streaming writes together; below the cutoff, producers fall back to framed-v1 + per-batch writeMulti.

4. Segment managerserialization/segment-writer.ts StreamSegmentWriter: streams frames into a sequence of long-lived PUTs, soft-closing each at min(~10s, maxFrames, byte budget) / on idle / on close(); a clean response commits the segment (drop buffer, advance anchor), an unclean one hands unconfirmed frames to recovery. Fully dependency-injected; serialized segments; backpressure-honoring.

5. Recoveryserialization/segment-recovery.ts recoverStreamTail: reads the persisted tail, matches this writer's own frames by marker, resumes after its highest already-persisted seq, handles the reserve-ahead race with read-with-backoff (10/100/1000ms → real error if still missing), replays only the un-persisted suffix, bounded retries.

Still to come (the wiring)

Rewrite WorkflowServerWritableStream to use the segment manager when the world supports writeStream; thread the writerId and framing='framed-v2' through getWritable and the object-stream read sites in lockstep; resolve cross-deployment/dashboard read discovery of the framing; server verification; integration + e2e (mid-segment disconnect → exactly-once/in-order, concurrent writers).

Tests

Unit-tested per piece: frame-marker, byte-stream-framing, serialization (framed-v2), capabilities, segment-writer, segment-recovery, streamer (writeStream), http-core (duplex). Full src/ unit suite green; pnpm typecheck clean. Changeset: @workflow/core minor.

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: c8ae6e5

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

This PR includes changesets to release 16 packages
Name Type
@workflow/core 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/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 1, 2026 12:24am
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jul 1, 2026 12:24am
example-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workbench-astro-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workbench-express-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workbench-fastify-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workbench-hono-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workbench-nitro-workflow Building Building Preview, Comment Jul 1, 2026 12:24am
workbench-nuxt-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workbench-sveltekit-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workbench-vite-workflow Ready Ready Preview, Comment Jul 1, 2026 12:24am
workflow-docs Ready Ready Preview, Comment, Open in v0 Jul 1, 2026 12:24am
workflow-swc-playground Ready Ready Preview, Comment Jul 1, 2026 12:24am
workflow-tarballs Ready Ready Preview, Comment Jul 1, 2026 12:24am
workflow-web Ready Ready Preview, Comment Jul 1, 2026 12:24am

@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 🥇 Nitro 0.045s (+1.8%) 1.008s (~) 0.963s 10 1.00x
💻 Local Express 0.046s (~) 1.006s (~) 0.960s 10 1.02x
💻 Local Next.js (Turbopack) 0.051s (-5.8% 🟢) 1.006s (~) 0.955s 10 1.13x
🐘 Postgres Next.js (Turbopack) 0.058s (-47.8% 🟢) 1.011s (-2.8%) 0.953s 10 1.29x
🐘 Postgres Express 0.065s (-10.8% 🟢) 1.010s (~) 0.945s 10 1.45x
🐘 Postgres Nitro 0.092s (+46.4% 🔺) 1.012s (~) 0.920s 10 2.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 0.519s (+12.3% 🔺) 2.099s (-18.2% 🟢) 1.580s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.077s (~) 2.006s (~) 0.929s 10 1.00x
💻 Local Express 1.082s (~) 2.007s (~) 0.925s 10 1.00x
💻 Local Next.js (Turbopack) 1.086s (~) 2.006s (~) 0.921s 10 1.01x
🐘 Postgres Next.js (Turbopack) 1.092s (-0.9%) 2.008s (~) 0.916s 10 1.01x
🐘 Postgres Express 1.093s (~) 2.010s (~) 0.917s 10 1.01x
🐘 Postgres Nitro 1.104s (+1.1%) 2.009s (~) 0.906s 10 1.02x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.223s (-1.0%) 3.757s (-1.6%) 1.533s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.439s (~) 11.022s (~) 0.583s 3 1.00x
💻 Local Express 10.443s (~) 11.022s (~) 0.579s 3 1.00x
🐘 Postgres Next.js (Turbopack) 10.468s (+0.7%) 11.010s (~) 0.542s 3 1.00x
🐘 Postgres Express 10.473s (~) 11.020s (~) 0.547s 3 1.00x
💻 Local Next.js (Turbopack) 10.489s (~) 11.023s (~) 0.534s 3 1.00x
🐘 Postgres Nitro 10.525s (+0.6%) 11.020s (~) 0.495s 3 1.01x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 12.751s (+0.6%) 14.730s (~) 1.979s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 13.555s (~) 14.027s (~) 0.472s 5 1.00x
🐘 Postgres Nitro 13.569s (-0.7%) 14.018s (~) 0.450s 5 1.00x
🐘 Postgres Express 13.593s (~) 14.020s (~) 0.427s 5 1.00x
🐘 Postgres Next.js (Turbopack) 13.624s (~) 14.020s (~) 0.396s 5 1.01x
💻 Local Next.js (Turbopack) 13.685s (~) 14.028s (~) 0.343s 5 1.01x
💻 Local Express 13.698s (~) 14.027s (~) 0.328s 5 1.01x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 17.421s (+1.3%) 19.641s (+1.7%) 2.220s 4 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 12.027s (-0.7%) 12.774s (-1.9%) 0.748s 8 1.00x
🐘 Postgres Nitro 12.223s (~) 13.019s (~) 0.796s 7 1.02x
🐘 Postgres Next.js (Turbopack) 12.241s (-1.5%) 13.018s (+0.8%) 0.777s 7 1.02x
🐘 Postgres Express 12.433s (~) 13.017s (~) 0.584s 7 1.03x
💻 Local Express 12.505s (+2.7%) 13.168s (+1.1%) 0.663s 7 1.04x
💻 Local Next.js (Turbopack) 12.607s (+3.5%) 13.169s (+1.1%) 0.562s 7 1.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 19.897s (~) 22.228s (+1.6%) 2.331s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.185s (-6.9% 🟢) 2.007s (-1.6%) 0.822s 15 1.00x
🐘 Postgres Express 1.185s (~) 2.008s (~) 0.822s 15 1.00x
🐘 Postgres Nitro 1.193s (-0.7%) 2.008s (~) 0.815s 15 1.01x
💻 Local Express 1.374s (-1.3%) 2.007s (~) 0.633s 15 1.16x
💻 Local Nitro 1.395s (~) 2.006s (~) 0.611s 15 1.18x
💻 Local Next.js (Turbopack) 1.409s (-1.0%) 2.006s (~) 0.598s 15 1.19x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 4.034s (+21.7% 🔺) 6.428s (+25.5% 🔺) 2.394s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.309s (-1.7%) 3.009s (+21.4% 🔺) 1.700s 10 1.00x
🐘 Postgres Nitro 1.325s (~) 2.508s (-11.3% 🟢) 1.184s 12 1.01x
🐘 Postgres Express 1.330s (-1.9%) 2.295s (-0.9%) 0.965s 14 1.02x
💻 Local Express 2.301s (-10.9% 🟢) 3.010s (~) 0.709s 10 1.76x
💻 Local Nitro 2.305s (-8.5% 🟢) 3.009s (-3.2%) 0.704s 10 1.76x
💻 Local Next.js (Turbopack) 2.453s (-10.5% 🟢) 3.010s (-3.2%) 0.557s 10 1.87x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 4.032s (+11.1% 🔺) 5.851s (+1.3%) 1.819s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.626s (+2.3%) 4.137s (+3.1%) 2.512s 8 1.00x
🐘 Postgres Express 1.634s (+2.8%) 4.012s (~) 2.378s 8 1.00x
🐘 Postgres Next.js (Turbopack) 2.906s (+59.7% 🔺) 5.850s (+19.6% 🔺) 2.944s 6 1.79x
💻 Local Next.js (Turbopack) 3.123s (-60.2% 🟢) 4.441s (-44.6% 🟢) 1.318s 7 1.92x
💻 Local Nitro 3.690s (-40.0% 🟢) 4.869s (-28.6% 🟢) 1.179s 7 2.27x
💻 Local Express 4.359s (-34.6% 🟢) 5.012s (-32.4% 🟢) 0.654s 7 2.68x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 4.699s (+13.2% 🔺) 6.724s (+10.1% 🔺) 2.025s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.180s (~) 2.008s (~) 0.828s 15 1.00x
🐘 Postgres Nitro 1.191s (+1.6%) 2.007s (~) 0.816s 15 1.01x
🐘 Postgres Express 1.193s (+1.1%) 2.007s (~) 0.814s 15 1.01x
💻 Local Nitro 1.400s (+2.1%) 2.007s (~) 0.607s 15 1.19x
💻 Local Express 1.453s (~) 2.006s (~) 0.554s 15 1.23x
💻 Local Next.js (Turbopack) 1.496s (+4.7%) 2.007s (~) 0.511s 15 1.27x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 3.256s (+5.8% 🔺) 5.059s (+2.5%) 1.803s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.325s (+1.4%) 2.223s (-10.0% 🟢) 0.898s 14 1.00x
🐘 Postgres Nitro 1.328s (+1.1%) 2.735s (+5.5% 🔺) 1.407s 11 1.00x
🐘 Postgres Next.js (Turbopack) 1.331s (-5.3% 🟢) 3.009s (+18.8% 🔺) 1.678s 10 1.00x
💻 Local Express 2.474s (-6.2% 🟢) 3.008s (~) 0.534s 10 1.87x
💻 Local Nitro 2.487s (-9.2% 🟢) 3.009s (-10.0% 🟢) 0.522s 10 1.88x
💻 Local Next.js (Turbopack) 2.719s (+1.9%) 3.210s (~) 0.491s 10 2.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 4.339s (+27.4% 🔺) 5.725s (+8.6% 🔺) 1.386s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.638s (~) 4.146s (-3.6%) 2.508s 8 1.00x
🐘 Postgres Express 1.642s (+3.1%) 4.439s (+7.3% 🔺) 2.796s 7 1.00x
🐘 Postgres Next.js (Turbopack) 2.904s (+11.4% 🔺) 5.682s (+8.6% 🔺) 2.778s 6 1.77x
💻 Local Nitro 4.224s (-34.4% 🟢) 5.348s (-27.9% 🟢) 1.124s 6 2.58x
💻 Local Express 5.511s (-13.0% 🟢) 6.014s (-11.8% 🟢) 0.503s 5 3.36x
💻 Local Next.js (Turbopack) 5.868s (-7.8% 🟢) 6.214s (-13.9% 🟢) 0.346s 5 3.58x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 5.108s (+16.7% 🔺) 6.939s (+12.4% 🔺) 1.832s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.522s (-14.0% 🟢) 1.006s (-4.4%) 0.484s 60 1.00x
🐘 Postgres Express 0.552s (~) 1.023s (+1.7%) 0.472s 59 1.06x
🐘 Postgres Nitro 0.564s (~) 1.041s (+1.7%) 0.477s 58 1.08x
💻 Local Nitro 0.583s (-3.5%) 1.022s (-1.7%) 0.439s 59 1.12x
💻 Local Express 0.597s (+8.1% 🔺) 1.022s (+1.7%) 0.425s 59 1.14x
💻 Local Next.js (Turbopack) 0.629s (-4.8%) 1.005s (-5.0%) 0.376s 60 1.21x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 3.560s (-1.4%) 5.450s (+1.3%) 1.890s 12 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.257s (-15.6% 🟢) 2.008s (-6.0% 🟢) 0.751s 45 1.00x
🐘 Postgres Express 1.307s (-3.3%) 2.008s (-1.1%) 0.701s 45 1.04x
🐘 Postgres Nitro 1.333s (+1.5%) 2.029s (-1.2%) 0.696s 45 1.06x
💻 Local Nitro 1.408s (-1.5%) 2.006s (~) 0.598s 45 1.12x
💻 Local Express 1.475s (+5.4% 🔺) 2.006s (~) 0.531s 45 1.17x
💻 Local Next.js (Turbopack) 1.558s (+4.7%) 2.007s (~) 0.448s 45 1.24x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 8.334s (+5.1% 🔺) 10.028s (+2.4%) 1.694s 9 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.585s (-1.7%) 3.059s (-0.8%) 0.474s 40 1.00x
🐘 Postgres Nitro 2.589s (-0.6%) 3.085s (+1.7%) 0.496s 39 1.00x
🐘 Postgres Next.js (Turbopack) 2.653s (-10.0% 🟢) 3.111s (-9.8% 🟢) 0.458s 39 1.03x
💻 Local Nitro 3.153s (-0.8%) 3.759s (-4.7%) 0.605s 32 1.22x
💻 Local Express 3.154s (+5.6% 🔺) 3.879s (+11.9% 🔺) 0.725s 31 1.22x
💻 Local Next.js (Turbopack) 3.414s (+6.6% 🔺) 4.044s (+0.9%) 0.630s 30 1.32x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 17.027s (+1.0%) 18.949s (+1.3%) 1.922s 7 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: 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.172s (-25.5% 🟢) 1.006s (~) 0.833s 60 1.00x
🐘 Postgres Express 0.214s (-3.5%) 1.006s (~) 0.792s 60 1.24x
🐘 Postgres Nitro 0.218s (+1.7%) 1.006s (~) 0.788s 60 1.27x
💻 Local Nitro 0.481s (+12.0% 🔺) 1.004s (~) 0.523s 60 2.79x
💻 Local Express 0.505s (+12.2% 🔺) 1.005s (~) 0.500s 60 2.93x
💻 Local Next.js (Turbopack) 0.674s (+7.2% 🔺) 1.040s (+3.5%) 0.366s 58 3.91x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.283s (+15.1% 🔺) 4.250s (+10.6% 🔺) 1.967s 15 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.288s (-9.5% 🟢) 1.017s (~) 0.729s 89 1.00x
🐘 Postgres Express 0.333s (~) 1.006s (~) 0.673s 90 1.16x
🐘 Postgres Nitro 0.343s (+1.9%) 1.017s (+1.1%) 0.674s 89 1.19x
💻 Local Nitro 2.434s (+26.5% 🔺) 3.009s (+19.9% 🔺) 0.575s 30 8.46x
💻 Local Express 2.452s (+12.7% 🔺) 3.009s (+8.8% 🔺) 0.557s 30 8.52x
💻 Local Next.js (Turbopack) 2.678s (-8.5% 🟢) 3.008s (-11.0% 🟢) 0.330s 30 9.30x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 3.125s (+25.3% 🔺) 5.077s (+23.6% 🔺) 1.952s 18 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: 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.516s (+3.2%) 2.961s (+17.7% 🔺) 2.445s 41 1.00x
🐘 Postgres Express 0.522s (-4.0%) 1.107s (-0.9%) 0.586s 109 1.01x
🐘 Postgres Nitro 0.561s (+2.2%) 1.118s (-0.9%) 0.557s 108 1.09x
💻 Local Express 5.421s (-41.4% 🟢) 8.597s (-17.7% 🟢) 3.176s 14 10.51x
💻 Local Nitro 5.810s (-39.8% 🟢) 8.952s (-16.9% 🟢) 3.142s 14 11.26x
💻 Local Next.js (Turbopack) 6.313s (-41.5% 🟢) 9.333s (-20.6% 🟢) 3.020s 13 12.23x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 4.160s (+2.4%) 5.961s (+1.6%) 1.801s 21 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: 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 🥇 Nitro 1.149s (-0.6%) 2.004s (~) 0.010s (-3.7%) 2.017s (~) 0.868s 10 1.00x
🐘 Postgres Next.js (Turbopack) 1.151s (-5.8% 🟢) 2.001s (+0.6%) 0.002s (+77.8% 🔺) 2.011s (~) 0.860s 10 1.00x
💻 Local Next.js (Turbopack) 1.151s (+1.1%) 1.966s (~) 0.013s (+10.8% 🔺) 2.022s (~) 0.870s 10 1.00x
💻 Local Express 1.153s (+1.1%) 2.004s (~) 0.012s (+13.2% 🔺) 2.019s (~) 0.866s 10 1.00x
🐘 Postgres Nitro 1.157s (-2.6%) 1.997s (~) 0.001s (~) 2.011s (~) 0.854s 10 1.01x
🐘 Postgres Express 1.163s (+0.9%) 1.998s (~) 0.001s (~) 2.011s (~) 0.848s 10 1.01x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 4.014s (+25.0% 🔺) 9.168s (+129.4% 🔺) 0.380s (-53.9% 🟢) 10.796s (+76.6% 🔺) 6.782s 10 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: 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 🥇 Nitro 1.536s (-1.5%) 2.010s (~) 0.012s (-9.1% 🟢) 2.025s (~) 0.490s 30 1.00x
💻 Local Express 1.561s (+1.4%) 2.010s (~) 0.012s (-11.2% 🟢) 2.025s (~) 0.464s 30 1.02x
🐘 Postgres Next.js (Turbopack) 1.568s (-17.0% 🟢) 2.008s (-11.3% 🟢) 0.005s (-33.8% 🟢) 2.025s (-12.1% 🟢) 0.456s 30 1.02x
🐘 Postgres Express 1.576s (-0.7%) 2.005s (-1.5%) 0.005s (+0.7%) 2.027s (-1.6%) 0.451s 30 1.03x
💻 Local Next.js (Turbopack) 1.640s (+3.9%) 1.967s (~) 0.013s (+13.8% 🔺) 2.026s (~) 0.385s 30 1.07x
🐘 Postgres Nitro 1.771s (+12.6% 🔺) 2.184s (+8.8% 🔺) 0.005s (-3.4%) 2.205s (+8.7% 🔺) 0.434s 28 1.15x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 9.373s (-0.5%) 11.456s (+6.5% 🔺) 0.212s (-17.4% 🟢) 12.177s (+3.0%) 2.804s 5 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.801s (+2.8%) 1.087s (+2.5%) 0.000s (+Infinity% 🔺) 1.112s (+3.0%) 0.311s 54 1.00x
🐘 Postgres Nitro 0.890s (+15.5% 🔺) 1.192s (+7.8% 🔺) 0.000s (-78.4% 🟢) 1.212s (+8.4% 🔺) 0.323s 50 1.11x
🐘 Postgres Next.js (Turbopack) 1.015s (-26.3% 🟢) 1.363s (-30.5% 🟢) 0.000s (+Infinity% 🔺) 1.371s (-31.6% 🟢) 0.356s 44 1.27x
💻 Local Nitro 1.301s (~) 1.862s (-7.5% 🟢) 0.000s (-9.1% 🟢) 1.865s (-7.5% 🟢) 0.563s 33 1.63x
💻 Local Express 1.372s (+7.7% 🔺) 2.013s (~) 0.001s (+58.3% 🔺) 2.016s (~) 0.645s 30 1.71x
💻 Local Next.js (Turbopack) 1.413s (+2.3%) 1.977s (~) 0.000s (+20.0% 🔺) 2.016s (~) 0.603s 30 1.76x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 4.776s (+12.7% 🔺) 5.874s (+8.2% 🔺) 0.000s (+Infinity% 🔺) 6.887s (+11.1% 🔺) 2.111s 9 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: 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.653s (-4.4%) 2.303s (+0.7%) 0.000s (+107.7% 🔺) 2.321s (+0.6%) 0.668s 26 1.00x
🐘 Postgres Nitro 1.937s (+2.4%) 2.496s (+2.6%) 0.000s (-100.0% 🟢) 2.509s (+2.4%) 0.572s 24 1.17x
🐘 Postgres Next.js (Turbopack) 2.482s (-12.2% 🟢) 2.954s (-12.0% 🟢) 0.000s (NaN%) 2.963s (-12.6% 🟢) 0.482s 21 1.50x
💻 Local Express 3.202s (-5.7% 🟢) 3.732s (-7.2% 🟢) 0.001s (+14.7% 🔺) 3.736s (-7.3% 🟢) 0.533s 17 1.94x
💻 Local Nitro 3.237s (-12.2% 🟢) 3.839s (-11.0% 🟢) 0.000s (-31.9% 🟢) 3.842s (-11.0% 🟢) 0.605s 16 1.96x
💻 Local Next.js (Turbopack) 3.531s (+1.3%) 4.055s (+1.7%) 0.002s (+53.3% 🔺) 4.097s (+1.6%) 0.566s 15 2.14x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 7.677s (+10.6% 🔺) 8.462s (+6.8% 🔺) 0.000s (+Infinity% 🔺) 9.867s (+11.6% 🔺) 2.190s 7 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

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

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 16/21
Next.js (Turbopack) 🐘 Postgres 19/21
Nitro 🐘 Postgres 14/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


Some benchmark jobs failed:

  • Local: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@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 1472 0 200 1672
✅ 📦 Local Production 1605 0 219 1824
✅ 🐘 Local Postgres 1593 0 231 1824
✅ 🪟 Windows 152 0 0 152
✅ 📋 Other 885 0 179 1064
Total 7149 0 1059 8208

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-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


Some E2E test jobs failed:

  • Vercel Prod: success
  • Local Dev: failure
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

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>
…e caller

Keep the wire encoding in the world (as writeMulti does): writeStream now
takes a ReadableStream of raw frames and applies the multi-chunk length
prefix per frame via a TransformStream (frameMultiChunkStream), preserving
backpressure. The caller streams frames; the world owns the format.

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

The segment manager that will drive WorkflowServerWritableStream's streaming
writes. Feeds raw frames into a sequence of long-lived streaming PUTs
("segments"), soft-closing each at min(~10s, maxFrames, byte budget) and on
idle, and finalizing on close(). A clean segment response commits every frame
in it (drop the unconfirmed buffer, advance the recovery anchor); an unclean
failure hands the unconfirmed frames to a recover() callback (wired in a later
commit with the tail-match + backoff logic).

Fully dependency-injected (transport, recover, clock, timers) and unit-tested
for ordering, maxFrames/time/idle soft-close, recovery, and abort. Not yet
wired into the writer.

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

@vercel vercel Bot Jul 1, 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.

An idle-timer-triggered segment finalize failure is silently swallowed instead of surfaced, so unconfirmed frames can be silently dropped without any error reaching the caller.

Fix on Vercel

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>
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