Skip to content

feat: allow start() to be called directly inside workflow functions - #1491

Merged
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start
Apr 17, 2026
Merged

feat: allow start() to be called directly inside workflow functions#1491
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start

Conversation

@pranaygp

@pranaygppranaygp commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Add "use step" to start() so it can be called directly from "use workflow" functions, without needing a wrapper step function.

This is a dramatically simplified replacement for the original PR approach. Instead of complex VM-side proxy classes and builtin step delegation, the entire implementation is:

  1. Add "use step" to start() in packages/core/src/runtime/start.ts
  2. Re-export start from @workflow/core/runtime/start in api-workflow.ts (instead of a throwing stub)

This works because:

Changes

  • packages/core/src/runtime/start.ts: Add "use step" directive
  • packages/workflow/src/api-workflow.ts: Re-export start from core instead of stub
  • workbench/example/workflows/99_e2e.ts: Add startFromWorkflow and fibonacciWorkflow e2e workflows
  • packages/core/e2e/e2e.test.ts: Add e2e tests
  • workbench/nextjs-turbopack/app/workflows/definitions.ts: Add default args

E2E Tests

  • startFromWorkflow: Parent spawns child via start(), child signals parent via hook, verifies bidirectional communication
  • fibonacciWorkflow: Recursive workflow composition — fib(6) = 8 via tree of child workflows using Promise.all([start(...), start(...)])

@vercel

vercelBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-botBot commented Mar 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5826c86

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

This PR includes changesets to release 17 packages
NameType
@workflow/coreMinor
workflowMinor
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-testingPatch
@workflow/aiMajor
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.042s (-2.3%)1.005s (~)0.963s101.00x
💻 LocalExpress0.043s (-3.6%)1.005s (~)0.963s101.01x
💻 LocalNext.js (Turbopack)0.050s1.005s0.955s101.18x
🐘 PostgresNext.js (Turbopack)0.057s1.009s0.953s101.35x
🐘 PostgresExpress0.060s (+3.8%)1.010s (~)0.950s101.43x
🐘 PostgresNitro0.087s (-9.0% 🟢)1.029s (-1.3%)0.942s102.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.255s (+1.5%)1.912s (-18.0% 🟢)1.657s101.00x
▲ VercelNitro0.413s (+0.9%)2.209s (-12.0% 🟢)1.795s101.62x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalExpress1.128s (~)2.006s (~)0.878s101.01x
🐘 PostgresNext.js (Turbopack)1.136s2.010s0.874s101.02x
💻 LocalNitro1.137s (~)2.006s (~)0.870s101.02x
🐘 PostgresExpress1.145s (~)2.009s (~)0.864s101.03x
🐘 PostgresNitro1.150s (+0.9%)2.009s (~)0.859s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.841s (-52.7% 🟢)3.610s (-38.9% 🟢)1.769s101.00x
▲ VercelNext.js (Turbopack)1.941s (-4.6%)3.671s (-4.2%)1.731s101.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.791s11.023s0.232s31.00x
🐘 PostgresNitro10.846s (~)11.019s (~)0.173s31.01x
🐘 PostgresExpress10.866s (-0.9%)11.018s (~)0.151s31.01x
🐘 PostgresNext.js (Turbopack)10.897s11.024s0.127s31.01x
💻 LocalExpress10.935s (~)11.024s (~)0.088s31.01x
💻 LocalNitro10.956s (~)11.024s (~)0.068s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.899s (-28.8% 🟢)18.812s (-25.1% 🟢)1.912s21.00x
▲ VercelNext.js (Turbopack)17.099s (-1.3%)18.986s (-2.1%)1.887s21.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.427s15.022s0.595s41.00x
🐘 PostgresNitro14.487s (-0.7%)15.027s (~)0.540s41.00x
🐘 PostgresExpress14.497s (-0.6%)15.021s (~)0.524s41.00x
💻 LocalNext.js (Turbopack)14.599s15.030s0.431s41.01x
💻 LocalExpress14.977s (~)15.280s (+1.7%)0.303s41.04x
💻 LocalNitro15.014s (~)15.531s (-3.1%)0.516s41.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro31.103s (-51.7% 🟢)33.088s (-50.3% 🟢)1.985s21.00x
▲ VercelNext.js (Turbopack)34.283s (-34.8% 🟢)36.218s (-33.7% 🟢)1.935s21.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.777s (-1.6%)14.021s (-3.9%)0.244s71.00x
🐘 PostgresNitro13.849s (-0.8%)14.023s (-2.0%)0.174s71.01x
🐘 PostgresNext.js (Turbopack)13.905s14.024s0.119s71.01x
💻 LocalNext.js (Turbopack)15.836s16.029s0.193s61.15x
💻 LocalExpress16.630s (~)17.030s (~)0.400s61.21x
💻 LocalNitro16.773s (~)17.031s (~)0.258s61.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)54.152s (-86.2% 🟢)55.639s (-85.9% 🟢)1.487s21.00x
▲ VercelNitro56.014s (-86.8% 🟢)57.859s (-86.4% 🟢)1.845s21.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.237s2.009s0.772s151.00x
🐘 PostgresNitro1.259s (-1.2%)2.011s (~)0.752s151.02x
🐘 PostgresExpress1.260s (~)2.008s (~)0.748s151.02x
💻 LocalNext.js (Turbopack)1.467s2.005s0.538s151.19x
💻 LocalExpress1.539s (+3.4%)2.006s (~)0.467s151.24x
💻 LocalNitro1.584s (-2.9%)2.006s (-3.3%)0.422s151.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.295s (-18.6% 🟢)3.768s (-12.8% 🟢)1.473s81.00x
▲ VercelNext.js (Turbopack)2.682s (-21.1% 🟢)4.363s (-11.5% 🟢)1.681s71.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.323s (-1.2%)3.010s (~)0.687s101.00x
🐘 PostgresExpress2.371s (~)3.008s (~)0.637s101.02x
🐘 PostgresNext.js (Turbopack)2.411s3.009s0.598s101.04x
💻 LocalNext.js (Turbopack)2.864s3.208s0.344s101.23x
💻 LocalExpress3.041s (+3.0%)3.885s (+12.5% 🔺)0.844s81.31x
💻 LocalNitro3.188s (+1.4%)3.886s (~)0.697s81.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.493s (-38.5% 🟢)4.128s (-30.3% 🟢)1.635s81.00x
▲ VercelNext.js (Turbopack)3.312s (-53.3% 🟢)4.688s (-47.4% 🟢)1.375s71.33x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.479s (~)4.010s (~)0.531s81.00x
🐘 PostgresExpress3.486s (~)4.011s (~)0.525s81.00x
🐘 PostgresNext.js (Turbopack)3.646s4.011s0.365s81.05x
💻 LocalNext.js (Turbopack)7.295s8.015s0.720s42.10x
💻 LocalExpress8.300s (~)9.021s (~)0.721s42.39x
💻 LocalNitro9.008s (+7.9% 🔺)9.520s (+5.5% 🔺)0.512s42.59x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.317s (-5.9% 🟢)5.227s (-5.5% 🟢)1.910s61.00x
▲ VercelNext.js (Turbopack)3.665s (-58.9% 🟢)5.170s (-52.8% 🟢)1.505s61.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s2.008s0.780s151.00x
🐘 PostgresNitro1.253s (~)2.008s (~)0.755s151.02x
🐘 PostgresExpress1.265s (+0.6%)2.007s (~)0.742s151.03x
💻 LocalNext.js (Turbopack)1.480s2.006s0.526s151.20x
💻 LocalExpress1.537s (-18.8% 🟢)2.006s (-15.1% 🟢)0.469s151.25x
💻 LocalNitro1.581s (-15.3% 🟢)2.007s (-14.3% 🟢)0.426s151.29x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.065s (-16.0% 🟢)3.741s (-10.3% 🟢)1.676s91.00x
▲ VercelNext.js (Turbopack)2.073s (-29.3% 🟢)3.679s (-20.7% 🟢)1.606s91.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (-0.5%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.371s (+1.3%)3.009s (~)0.638s101.02x
🐘 PostgresNext.js (Turbopack)2.395s3.008s0.613s101.03x
💻 LocalNext.js (Turbopack)2.899s3.341s0.443s91.25x
💻 LocalExpress3.031s (-3.2%)3.883s (+3.2%)0.852s81.30x
💻 LocalNitro3.064s (~)3.885s (~)0.821s81.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.511s (-22.3% 🟢)3.996s (-21.3% 🟢)1.485s81.00x
▲ VercelNext.js (Turbopack)2.943s (-6.4% 🟢)4.388s (-3.0%)1.445s81.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.478s (-0.6%)4.011s (~)0.533s81.00x
🐘 PostgresNitro3.481s (~)4.011s (~)0.530s81.00x
🐘 PostgresNext.js (Turbopack)3.657s4.012s0.355s81.05x
💻 LocalNext.js (Turbopack)8.041s8.767s0.726s42.31x
💻 LocalExpress8.919s (+1.3%)9.271s (~)0.352s42.56x
💻 LocalNitro9.163s (~)10.022s (~)0.859s32.63x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.059s (-39.9% 🟢)4.889s (-28.3% 🟢)1.830s71.00x
▲ VercelNext.js (Turbopack)3.409s (-49.6% 🟢)5.067s (-40.7% 🟢)1.659s61.11x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.779s1.023s0.244s591.00x
🐘 PostgresExpress0.799s (-4.7%)1.006s (-1.7%)0.207s601.03x
💻 LocalNext.js (Turbopack)0.829s1.004s0.176s601.06x
🐘 PostgresNitro0.834s (+1.6%)1.006s (~)0.172s601.07x
💻 LocalExpress0.999s (+1.5%)1.369s (+27.2% 🔺)0.370s441.28x
💻 LocalNitro1.016s (+3.6%)1.611s (+47.3% 🔺)0.595s381.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro9.843s (-55.4% 🟢)12.413s (-48.3% 🟢)2.570s51.00x
▲ VercelNext.js (Turbopack)12.026s (-17.1% 🟢)13.929s (-13.4% 🟢)1.903s51.22x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.884s (-4.7%)2.075s (-8.1% 🟢)0.192s441.00x
🐘 PostgresNext.js (Turbopack)1.940s2.202s0.263s411.03x
🐘 PostgresNitro2.009s (+4.2%)2.580s (+22.8% 🔺)0.571s351.07x
💻 LocalNext.js (Turbopack)2.607s3.008s0.401s301.38x
💻 LocalNitro3.062s (+0.9%)3.842s (+2.2%)0.780s241.63x
💻 LocalExpress3.083s (+2.2%)3.843s (+7.2% 🔺)0.760s241.64x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro29.627s (-25.0% 🟢)31.818s (-23.0% 🟢)2.191s31.00x
▲ VercelNext.js (Turbopack)30.003s (-39.8% 🟢)31.773s (-38.6% 🟢)1.770s31.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.838s (-3.8%)4.042s (-7.5% 🟢)0.204s301.00x
🐘 PostgresNext.js (Turbopack)3.845s4.043s0.198s301.00x
🐘 PostgresNitro4.114s (~)4.742s (+3.0%)0.628s261.07x
💻 LocalNext.js (Turbopack)8.439s9.017s0.578s142.20x
💻 LocalExpress9.229s (~)9.941s (-0.8%)0.712s132.40x
💻 LocalNitro9.362s (+0.7%)10.103s (+0.8%)0.741s122.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro79.683s (-17.8% 🟢)81.801s (-16.9% 🟢)2.118s21.00x
▲ VercelNext.js (Turbopack)83.234s (-22.3% 🟢)84.822s (-22.1% 🟢)1.588s21.04x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.254s1.007s0.753s601.00x
🐘 PostgresExpress0.286s (+1.3%)1.007s (~)0.720s601.13x
🐘 PostgresNitro0.292s (+3.2%)1.008s (~)0.715s601.15x
💻 LocalExpress0.591s (+5.5% 🔺)1.005s (~)0.413s602.33x
💻 LocalNitro0.604s (~)1.005s (-1.7%)0.401s602.38x
💻 LocalNext.js (Turbopack)0.636s1.096s0.460s552.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.935s (-4.3%)3.931s (+3.6%)1.996s161.00x
▲ VercelNitro2.045s (+23.1% 🔺)3.924s (+17.1% 🔺)1.879s161.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.475s1.006s0.531s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.006s (~)0.518s901.03x
🐘 PostgresNitro0.498s (~)1.007s (~)0.508s901.05x
💻 LocalNext.js (Turbopack)2.523s3.009s0.485s305.31x
💻 LocalExpress2.549s (+1.4%)3.010s (~)0.461s305.36x
💻 LocalNitro2.595s (+2.2%)3.010s (~)0.415s305.46x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.216s (~)5.023s (+4.2%)1.806s181.00x
▲ VercelNext.js (Turbopack)3.287s (-7.0% 🟢)5.034s (-3.1%)1.746s181.02x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.774s (-5.4% 🟢)1.007s (-1.0%)0.233s1201.00x
🐘 PostgresNext.js (Turbopack)0.777s1.006s0.230s1201.00x
🐘 PostgresNitro0.794s (~)1.008s (~)0.214s1201.03x
💻 LocalNext.js (Turbopack)10.324s10.943s0.619s1213.34x
💻 LocalNitro11.556s (+3.3%)12.130s (+4.0%)0.574s1014.93x
💻 LocalExpress11.688s (+4.4%)12.329s (+3.3%)0.641s1015.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.003s (-22.3% 🟢)7.762s (-17.4% 🟢)1.759s161.00x
▲ VercelNext.js (Turbopack)13.494s (+30.7% 🔺)15.200s (+23.7% 🔺)1.707s112.25x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.179s1.003s0.012s1.018s0.839s101.00x
💻 LocalExpress0.203s (+1.8%)1.004s (~)0.012s (-3.3%)1.018s (~)0.815s101.13x
🐘 PostgresExpress0.203s (-0.9%)0.992s (-0.7%)0.002s (~)1.010s (~)0.806s101.14x
💻 LocalNitro0.204s (-4.5%)1.004s (~)0.012s (-4.8%)1.018s (~)0.814s101.14x
🐘 PostgresNext.js (Turbopack)0.207s1.001s0.002s1.011s0.804s101.16x
🐘 PostgresNitro0.210s (+2.5%)0.995s (~)0.001s (-20.0% 🟢)1.009s (~)0.799s101.18x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.595s (-76.7% 🟢)3.061s (-64.6% 🟢)0.709s (+12.1% 🔺)4.212s (-57.0% 🟢)2.617s101.00x
▲ VercelNitro1.672s (-56.4% 🟢)3.060s (-42.0% 🟢)0.780s (+5.1% 🔺)4.359s (-32.8% 🟢)2.687s101.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.605s (-3.9%)1.005s (~)0.004s (+2.2%)1.022s (~)0.417s591.00x
🐘 PostgresNext.js (Turbopack)0.607s1.009s0.006s1.024s0.417s591.00x
🐘 PostgresNitro0.627s (~)1.003s (~)0.004s (-4.5%)1.022s (~)0.395s591.04x
💻 LocalNext.js (Turbopack)0.756s1.011s0.020s1.126s0.370s541.25x
💻 LocalExpress0.794s (+4.8%)1.012s (-1.6%)0.015s (+54.8% 🔺)1.029s (-1.1%)0.235s591.31x
💻 LocalNitro0.968s (+15.5% 🔺)1.012s (~)0.010s (+6.7% 🔺)1.228s (+10.0% 🔺)0.260s491.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)4.375s (-74.1% 🟢)6.053s (-66.8% 🟢)0.188s (-11.1% 🟢)6.702s (-64.6% 🟢)2.327s91.00x
▲ VercelNitro4.545s (-84.6% 🟢)6.277s (-79.6% 🟢)0.225s (+101.1% 🔺)6.958s (-78.1% 🟢)2.413s91.04x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.937s1.203s0.000s1.216s0.279s501.00x
🐘 PostgresNitro0.948s (-2.1%)1.167s (-6.4% 🟢)0.000s (-7.7% 🟢)1.181s (-6.1% 🟢)0.233s521.01x
🐘 PostgresExpress0.953s (-0.9%)1.269s (-0.7%)0.000s (-51.1% 🟢)1.294s (-0.9%)0.341s471.02x
💻 LocalExpress1.252s (+2.3%)2.024s (~)0.000s (~)2.026s (~)0.773s301.34x
💻 LocalNext.js (Turbopack)1.281s2.021s0.001s2.024s0.742s301.37x
💻 LocalNitro1.420s (+16.1% 🔺)2.021s (~)0.000s (+221.4% 🔺)2.202s (+8.9% 🔺)0.782s281.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-1.5%)4.107s (-6.5% 🟢)0.000s (-100.0% 🟢)4.696s (-2.4%)1.691s131.00x
▲ VercelNext.js (Turbopack)3.462s (-66.0% 🟢)5.066s (-56.0% 🟢)0.000s (NaN%)5.508s (-54.3% 🟢)2.045s111.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.669s (-5.8% 🟢)2.058s (-5.5% 🟢)0.000s (+Infinity% 🔺)2.082s (-5.3% 🟢)0.413s291.00x
🐘 PostgresNext.js (Turbopack)1.790s2.072s0.000s2.083s0.293s291.07x
🐘 PostgresNitro1.825s (+1.9%)2.258s (+5.4% 🔺)0.000s (+107.4% 🔺)2.269s (+4.3%)0.444s271.09x
💻 LocalExpress3.530s (+1.8%)4.034s (~)0.000s (-41.7% 🟢)4.038s (~)0.508s152.12x
💻 LocalNitro3.649s (+7.7% 🔺)4.099s (+1.6%)0.001s (+62.5% 🔺)4.102s (+1.6%)0.453s152.19x
💻 LocalNext.js (Turbopack)3.847s4.389s0.000s4.395s0.547s142.31x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.955s (-3.4%)5.118s (-4.8%)0.000s (-100.0% 🟢)5.592s (-3.5%)1.637s111.00x
▲ VercelNext.js (Turbopack)4.537s (-19.2% 🟢)5.937s (-15.0% 🟢)0.000s (-100.0% 🟢)6.464s (-14.3% 🟢)1.927s101.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)17/21
🐘 PostgresNext.js (Turbopack)9/21
▲ VercelNitro16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres16/21
Nitro🐘 Postgres16/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)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9450671012
✅ 💻 Local Development10180861104
✅ 📦 Local Production10180861104
✅ 🐘 Local Postgres10180861104
✅ 🪟 Windows920092
✅ 📋 Other258018276
Total434903434692

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8507
✅ example8507
✅ express8507
✅ fastify8507
✅ hono8507
✅ nextjs-turbopack9002
✅ nextjs-webpack9002
✅ nitro8507
✅ nuxt8507
✅ sveltekit8507
✅ vite8507
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable8606
✅ e2e-local-postgres-nest-stable8606
✅ e2e-local-prod-nest-stable8606

📋 View full workflow run

@pranaygppranaygp changed the title Revert "Revert \"Add support for calling start() inside workflow functions\""Revert Revert "Add support for calling start() inside workflow functions"Mar 23, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-enables calling start() from workflow/api directly inside "use workflow" functions by routing the call through internal built-in steps, with Run objects working in workflow VM context (step-backed getters/methods) and improved observability UI hydration/rendering for run references.

Changes:

  • Add workflow-context start() delegation via WORKFLOW_START, backed by a built-in start step and a workflow-VM WorkflowRun proxy class.
  • Extend serialization/hydration to support Run/WorkflowRun across boundaries and render Run references as clickable UI elements.
  • Update builders, plugin behavior/docs, and add unit + e2e coverage for startFromWorkflow and recursive fibonacciWorkflow.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
workbench/nextjs-turbopack/app/workflows/definitions.tsAdds default args for new e2e workflows.
workbench/example/workflows/99_e2e.tsAdds startFromWorkflow and recursive fibonacciWorkflow workflows for e2e.
skills/workflow/SKILL.mdUpdates skill guidance to reflect start() support inside workflows.
packages/workflow/src/runtime.tsRe-exports getRun and start from core runtime for rewritten step imports.
packages/workflow/src/internal/builtins.tsAdds built-in start step and Run.* step-backed static methods.
packages/workflow/src/api-workflow.tsRoutes start() in workflow bundle via WORKFLOW_START injection.
packages/web/app/components/run-detail-view.tsxAdds run-ref click navigation wiring.
packages/web-shared/src/lib/hydration.tsExposes RunRef helpers for UI hydration.
packages/web-shared/src/components/workflow-traces/trace-span-construction.tsImproves span naming fallback behavior.
packages/web-shared/src/components/workflow-trace-view.tsxPlumbs onRunClick and resets selection on run change.
packages/web-shared/src/components/ui/data-inspector.tsxAdds RunRef rendering + “opaque ref” collapsing + click contexts.
packages/web-shared/src/components/sidebar/entity-detail-panel.tsxPasses onRunClick into detail panel rendering.
packages/web-shared/src/components/sidebar/attribute-panel.tsxAdds Run click context and improves stepName display fallback.
packages/web-shared/src/components/run-trace-view.tsxThreads onRunClick through trace view components.
packages/swc-plugin-workflow/transform/src/lib.rsRemoves __builtin* step-id special casing to use normal IDs.
packages/swc-plugin-workflow/spec.mdUpdates spec examples to include builtins module-specifier IDs.
packages/next/src/builder-deferred.tsRewrites bare specifiers (incl. dynamic import) and ensures builtins are included in step files.
packages/core/src/workflow/start.tsImplements workflow-VM createStart() using internal start built-in step.
packages/core/src/workflow/start.test.tsAdds unit tests for workflow-context start() behavior and option validation.
packages/core/src/workflow/run.tsAdds workflow-VM WorkflowRun proxy delegating to built-in Run steps.
packages/core/src/workflow/run.test.tsAdds unit tests asserting step delegation naming/arguments.
packages/core/src/workflow/builtin-step-id.tsAdds helper to construct built-in step IDs.
packages/core/src/workflow.tsInjects WORKFLOW_START, registers WorkflowRun, and updates response builtins to qualified IDs.
packages/core/src/symbols.tsAdds WORKFLOW_START symbol.
packages/core/src/serialization.tsAdds Run reducer/reviver using __serializable marker + class registry.
packages/core/src/serialization-format.tsAdds RunRef marker/type + reviver mapping Run → RunRef for o11y.
packages/core/src/runtime/step-handler.tsRegisters runtime Run class in host class registry for Run reviver.
packages/core/src/runtime/start.tsDelegates start() to injected workflow implementation when in workflow VM.
packages/core/src/runtime/run.tsAdds Run.__serializable marker for serialization.
packages/core/src/private.tsExtends builtin step aliasing to accept fully-qualified builtins step IDs.
packages/core/e2e/e2e.test.tsAdds e2e tests validating workflow-context start() and recursion.
docs/proxy.tsFormatting-only string quote normalization.
docs/lib/ai-agent-detection.tsFormatting-only string quote normalization.
docs/content/docs/foundations/starting-workflows.mdxDocuments calling start() inside workflow functions and recursive patterns.
docs/content/docs/foundations/common-patterns.mdxUpdates patterns docs to use direct start() in workflows.
docs/content/docs/api-reference/workflow-api/start.mdxUpdates API reference to include workflow-context usage.
AGENTS.mdUpdates local e2e instructions to include WORKFLOW_PUBLIC_MANIFEST=1.
.changeset/start-in-workflow.mdDeclares patch release for workflow-context start() support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/workflow/src/api-workflow.ts Outdated
Comment threadpackages/core/src/workflow/run.ts Outdated
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/serialization-format.ts
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/next/src/builder-deferred.ts Outdated
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
… duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
…t all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
…d of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
… step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
…just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
@VaguelySerious

Copy link
Copy Markdown
Member

Fails nextjs-turbopack tests

argsOrOptions?: TArgs | StartOptions,
options?: StartOptions
) {
'use step';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice

Sign up for freeto 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.

4 participants

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

feat: allow start() to be called directly inside workflow functions - #1491

Merged
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start
Apr 17, 2026
Merged

feat: allow start() to be called directly inside workflow functions#1491
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start

Conversation

@pranaygp

@pranaygppranaygp commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Add "use step" to start() so it can be called directly from "use workflow" functions, without needing a wrapper step function.

This is a dramatically simplified replacement for the original PR approach. Instead of complex VM-side proxy classes and builtin step delegation, the entire implementation is:

  1. Add "use step" to start() in packages/core/src/runtime/start.ts
  2. Re-export start from @workflow/core/runtime/start in api-workflow.ts (instead of a throwing stub)

This works because:

Changes

  • packages/core/src/runtime/start.ts: Add "use step" directive
  • packages/workflow/src/api-workflow.ts: Re-export start from core instead of stub
  • workbench/example/workflows/99_e2e.ts: Add startFromWorkflow and fibonacciWorkflow e2e workflows
  • packages/core/e2e/e2e.test.ts: Add e2e tests
  • workbench/nextjs-turbopack/app/workflows/definitions.ts: Add default args

E2E Tests

  • startFromWorkflow: Parent spawns child via start(), child signals parent via hook, verifies bidirectional communication
  • fibonacciWorkflow: Recursive workflow composition — fib(6) = 8 via tree of child workflows using Promise.all([start(...), start(...)])

@vercel

vercelBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-botBot commented Mar 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5826c86

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

This PR includes changesets to release 17 packages
NameType
@workflow/coreMinor
workflowMinor
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-testingPatch
@workflow/aiMajor
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.042s (-2.3%)1.005s (~)0.963s101.00x
💻 LocalExpress0.043s (-3.6%)1.005s (~)0.963s101.01x
💻 LocalNext.js (Turbopack)0.050s1.005s0.955s101.18x
🐘 PostgresNext.js (Turbopack)0.057s1.009s0.953s101.35x
🐘 PostgresExpress0.060s (+3.8%)1.010s (~)0.950s101.43x
🐘 PostgresNitro0.087s (-9.0% 🟢)1.029s (-1.3%)0.942s102.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.255s (+1.5%)1.912s (-18.0% 🟢)1.657s101.00x
▲ VercelNitro0.413s (+0.9%)2.209s (-12.0% 🟢)1.795s101.62x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalExpress1.128s (~)2.006s (~)0.878s101.01x
🐘 PostgresNext.js (Turbopack)1.136s2.010s0.874s101.02x
💻 LocalNitro1.137s (~)2.006s (~)0.870s101.02x
🐘 PostgresExpress1.145s (~)2.009s (~)0.864s101.03x
🐘 PostgresNitro1.150s (+0.9%)2.009s (~)0.859s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.841s (-52.7% 🟢)3.610s (-38.9% 🟢)1.769s101.00x
▲ VercelNext.js (Turbopack)1.941s (-4.6%)3.671s (-4.2%)1.731s101.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.791s11.023s0.232s31.00x
🐘 PostgresNitro10.846s (~)11.019s (~)0.173s31.01x
🐘 PostgresExpress10.866s (-0.9%)11.018s (~)0.151s31.01x
🐘 PostgresNext.js (Turbopack)10.897s11.024s0.127s31.01x
💻 LocalExpress10.935s (~)11.024s (~)0.088s31.01x
💻 LocalNitro10.956s (~)11.024s (~)0.068s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.899s (-28.8% 🟢)18.812s (-25.1% 🟢)1.912s21.00x
▲ VercelNext.js (Turbopack)17.099s (-1.3%)18.986s (-2.1%)1.887s21.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.427s15.022s0.595s41.00x
🐘 PostgresNitro14.487s (-0.7%)15.027s (~)0.540s41.00x
🐘 PostgresExpress14.497s (-0.6%)15.021s (~)0.524s41.00x
💻 LocalNext.js (Turbopack)14.599s15.030s0.431s41.01x
💻 LocalExpress14.977s (~)15.280s (+1.7%)0.303s41.04x
💻 LocalNitro15.014s (~)15.531s (-3.1%)0.516s41.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro31.103s (-51.7% 🟢)33.088s (-50.3% 🟢)1.985s21.00x
▲ VercelNext.js (Turbopack)34.283s (-34.8% 🟢)36.218s (-33.7% 🟢)1.935s21.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.777s (-1.6%)14.021s (-3.9%)0.244s71.00x
🐘 PostgresNitro13.849s (-0.8%)14.023s (-2.0%)0.174s71.01x
🐘 PostgresNext.js (Turbopack)13.905s14.024s0.119s71.01x
💻 LocalNext.js (Turbopack)15.836s16.029s0.193s61.15x
💻 LocalExpress16.630s (~)17.030s (~)0.400s61.21x
💻 LocalNitro16.773s (~)17.031s (~)0.258s61.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)54.152s (-86.2% 🟢)55.639s (-85.9% 🟢)1.487s21.00x
▲ VercelNitro56.014s (-86.8% 🟢)57.859s (-86.4% 🟢)1.845s21.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.237s2.009s0.772s151.00x
🐘 PostgresNitro1.259s (-1.2%)2.011s (~)0.752s151.02x
🐘 PostgresExpress1.260s (~)2.008s (~)0.748s151.02x
💻 LocalNext.js (Turbopack)1.467s2.005s0.538s151.19x
💻 LocalExpress1.539s (+3.4%)2.006s (~)0.467s151.24x
💻 LocalNitro1.584s (-2.9%)2.006s (-3.3%)0.422s151.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.295s (-18.6% 🟢)3.768s (-12.8% 🟢)1.473s81.00x
▲ VercelNext.js (Turbopack)2.682s (-21.1% 🟢)4.363s (-11.5% 🟢)1.681s71.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.323s (-1.2%)3.010s (~)0.687s101.00x
🐘 PostgresExpress2.371s (~)3.008s (~)0.637s101.02x
🐘 PostgresNext.js (Turbopack)2.411s3.009s0.598s101.04x
💻 LocalNext.js (Turbopack)2.864s3.208s0.344s101.23x
💻 LocalExpress3.041s (+3.0%)3.885s (+12.5% 🔺)0.844s81.31x
💻 LocalNitro3.188s (+1.4%)3.886s (~)0.697s81.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.493s (-38.5% 🟢)4.128s (-30.3% 🟢)1.635s81.00x
▲ VercelNext.js (Turbopack)3.312s (-53.3% 🟢)4.688s (-47.4% 🟢)1.375s71.33x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.479s (~)4.010s (~)0.531s81.00x
🐘 PostgresExpress3.486s (~)4.011s (~)0.525s81.00x
🐘 PostgresNext.js (Turbopack)3.646s4.011s0.365s81.05x
💻 LocalNext.js (Turbopack)7.295s8.015s0.720s42.10x
💻 LocalExpress8.300s (~)9.021s (~)0.721s42.39x
💻 LocalNitro9.008s (+7.9% 🔺)9.520s (+5.5% 🔺)0.512s42.59x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.317s (-5.9% 🟢)5.227s (-5.5% 🟢)1.910s61.00x
▲ VercelNext.js (Turbopack)3.665s (-58.9% 🟢)5.170s (-52.8% 🟢)1.505s61.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s2.008s0.780s151.00x
🐘 PostgresNitro1.253s (~)2.008s (~)0.755s151.02x
🐘 PostgresExpress1.265s (+0.6%)2.007s (~)0.742s151.03x
💻 LocalNext.js (Turbopack)1.480s2.006s0.526s151.20x
💻 LocalExpress1.537s (-18.8% 🟢)2.006s (-15.1% 🟢)0.469s151.25x
💻 LocalNitro1.581s (-15.3% 🟢)2.007s (-14.3% 🟢)0.426s151.29x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.065s (-16.0% 🟢)3.741s (-10.3% 🟢)1.676s91.00x
▲ VercelNext.js (Turbopack)2.073s (-29.3% 🟢)3.679s (-20.7% 🟢)1.606s91.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (-0.5%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.371s (+1.3%)3.009s (~)0.638s101.02x
🐘 PostgresNext.js (Turbopack)2.395s3.008s0.613s101.03x
💻 LocalNext.js (Turbopack)2.899s3.341s0.443s91.25x
💻 LocalExpress3.031s (-3.2%)3.883s (+3.2%)0.852s81.30x
💻 LocalNitro3.064s (~)3.885s (~)0.821s81.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.511s (-22.3% 🟢)3.996s (-21.3% 🟢)1.485s81.00x
▲ VercelNext.js (Turbopack)2.943s (-6.4% 🟢)4.388s (-3.0%)1.445s81.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.478s (-0.6%)4.011s (~)0.533s81.00x
🐘 PostgresNitro3.481s (~)4.011s (~)0.530s81.00x
🐘 PostgresNext.js (Turbopack)3.657s4.012s0.355s81.05x
💻 LocalNext.js (Turbopack)8.041s8.767s0.726s42.31x
💻 LocalExpress8.919s (+1.3%)9.271s (~)0.352s42.56x
💻 LocalNitro9.163s (~)10.022s (~)0.859s32.63x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.059s (-39.9% 🟢)4.889s (-28.3% 🟢)1.830s71.00x
▲ VercelNext.js (Turbopack)3.409s (-49.6% 🟢)5.067s (-40.7% 🟢)1.659s61.11x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.779s1.023s0.244s591.00x
🐘 PostgresExpress0.799s (-4.7%)1.006s (-1.7%)0.207s601.03x
💻 LocalNext.js (Turbopack)0.829s1.004s0.176s601.06x
🐘 PostgresNitro0.834s (+1.6%)1.006s (~)0.172s601.07x
💻 LocalExpress0.999s (+1.5%)1.369s (+27.2% 🔺)0.370s441.28x
💻 LocalNitro1.016s (+3.6%)1.611s (+47.3% 🔺)0.595s381.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro9.843s (-55.4% 🟢)12.413s (-48.3% 🟢)2.570s51.00x
▲ VercelNext.js (Turbopack)12.026s (-17.1% 🟢)13.929s (-13.4% 🟢)1.903s51.22x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.884s (-4.7%)2.075s (-8.1% 🟢)0.192s441.00x
🐘 PostgresNext.js (Turbopack)1.940s2.202s0.263s411.03x
🐘 PostgresNitro2.009s (+4.2%)2.580s (+22.8% 🔺)0.571s351.07x
💻 LocalNext.js (Turbopack)2.607s3.008s0.401s301.38x
💻 LocalNitro3.062s (+0.9%)3.842s (+2.2%)0.780s241.63x
💻 LocalExpress3.083s (+2.2%)3.843s (+7.2% 🔺)0.760s241.64x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro29.627s (-25.0% 🟢)31.818s (-23.0% 🟢)2.191s31.00x
▲ VercelNext.js (Turbopack)30.003s (-39.8% 🟢)31.773s (-38.6% 🟢)1.770s31.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.838s (-3.8%)4.042s (-7.5% 🟢)0.204s301.00x
🐘 PostgresNext.js (Turbopack)3.845s4.043s0.198s301.00x
🐘 PostgresNitro4.114s (~)4.742s (+3.0%)0.628s261.07x
💻 LocalNext.js (Turbopack)8.439s9.017s0.578s142.20x
💻 LocalExpress9.229s (~)9.941s (-0.8%)0.712s132.40x
💻 LocalNitro9.362s (+0.7%)10.103s (+0.8%)0.741s122.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro79.683s (-17.8% 🟢)81.801s (-16.9% 🟢)2.118s21.00x
▲ VercelNext.js (Turbopack)83.234s (-22.3% 🟢)84.822s (-22.1% 🟢)1.588s21.04x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.254s1.007s0.753s601.00x
🐘 PostgresExpress0.286s (+1.3%)1.007s (~)0.720s601.13x
🐘 PostgresNitro0.292s (+3.2%)1.008s (~)0.715s601.15x
💻 LocalExpress0.591s (+5.5% 🔺)1.005s (~)0.413s602.33x
💻 LocalNitro0.604s (~)1.005s (-1.7%)0.401s602.38x
💻 LocalNext.js (Turbopack)0.636s1.096s0.460s552.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.935s (-4.3%)3.931s (+3.6%)1.996s161.00x
▲ VercelNitro2.045s (+23.1% 🔺)3.924s (+17.1% 🔺)1.879s161.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.475s1.006s0.531s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.006s (~)0.518s901.03x
🐘 PostgresNitro0.498s (~)1.007s (~)0.508s901.05x
💻 LocalNext.js (Turbopack)2.523s3.009s0.485s305.31x
💻 LocalExpress2.549s (+1.4%)3.010s (~)0.461s305.36x
💻 LocalNitro2.595s (+2.2%)3.010s (~)0.415s305.46x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.216s (~)5.023s (+4.2%)1.806s181.00x
▲ VercelNext.js (Turbopack)3.287s (-7.0% 🟢)5.034s (-3.1%)1.746s181.02x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.774s (-5.4% 🟢)1.007s (-1.0%)0.233s1201.00x
🐘 PostgresNext.js (Turbopack)0.777s1.006s0.230s1201.00x
🐘 PostgresNitro0.794s (~)1.008s (~)0.214s1201.03x
💻 LocalNext.js (Turbopack)10.324s10.943s0.619s1213.34x
💻 LocalNitro11.556s (+3.3%)12.130s (+4.0%)0.574s1014.93x
💻 LocalExpress11.688s (+4.4%)12.329s (+3.3%)0.641s1015.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.003s (-22.3% 🟢)7.762s (-17.4% 🟢)1.759s161.00x
▲ VercelNext.js (Turbopack)13.494s (+30.7% 🔺)15.200s (+23.7% 🔺)1.707s112.25x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.179s1.003s0.012s1.018s0.839s101.00x
💻 LocalExpress0.203s (+1.8%)1.004s (~)0.012s (-3.3%)1.018s (~)0.815s101.13x
🐘 PostgresExpress0.203s (-0.9%)0.992s (-0.7%)0.002s (~)1.010s (~)0.806s101.14x
💻 LocalNitro0.204s (-4.5%)1.004s (~)0.012s (-4.8%)1.018s (~)0.814s101.14x
🐘 PostgresNext.js (Turbopack)0.207s1.001s0.002s1.011s0.804s101.16x
🐘 PostgresNitro0.210s (+2.5%)0.995s (~)0.001s (-20.0% 🟢)1.009s (~)0.799s101.18x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.595s (-76.7% 🟢)3.061s (-64.6% 🟢)0.709s (+12.1% 🔺)4.212s (-57.0% 🟢)2.617s101.00x
▲ VercelNitro1.672s (-56.4% 🟢)3.060s (-42.0% 🟢)0.780s (+5.1% 🔺)4.359s (-32.8% 🟢)2.687s101.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.605s (-3.9%)1.005s (~)0.004s (+2.2%)1.022s (~)0.417s591.00x
🐘 PostgresNext.js (Turbopack)0.607s1.009s0.006s1.024s0.417s591.00x
🐘 PostgresNitro0.627s (~)1.003s (~)0.004s (-4.5%)1.022s (~)0.395s591.04x
💻 LocalNext.js (Turbopack)0.756s1.011s0.020s1.126s0.370s541.25x
💻 LocalExpress0.794s (+4.8%)1.012s (-1.6%)0.015s (+54.8% 🔺)1.029s (-1.1%)0.235s591.31x
💻 LocalNitro0.968s (+15.5% 🔺)1.012s (~)0.010s (+6.7% 🔺)1.228s (+10.0% 🔺)0.260s491.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)4.375s (-74.1% 🟢)6.053s (-66.8% 🟢)0.188s (-11.1% 🟢)6.702s (-64.6% 🟢)2.327s91.00x
▲ VercelNitro4.545s (-84.6% 🟢)6.277s (-79.6% 🟢)0.225s (+101.1% 🔺)6.958s (-78.1% 🟢)2.413s91.04x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.937s1.203s0.000s1.216s0.279s501.00x
🐘 PostgresNitro0.948s (-2.1%)1.167s (-6.4% 🟢)0.000s (-7.7% 🟢)1.181s (-6.1% 🟢)0.233s521.01x
🐘 PostgresExpress0.953s (-0.9%)1.269s (-0.7%)0.000s (-51.1% 🟢)1.294s (-0.9%)0.341s471.02x
💻 LocalExpress1.252s (+2.3%)2.024s (~)0.000s (~)2.026s (~)0.773s301.34x
💻 LocalNext.js (Turbopack)1.281s2.021s0.001s2.024s0.742s301.37x
💻 LocalNitro1.420s (+16.1% 🔺)2.021s (~)0.000s (+221.4% 🔺)2.202s (+8.9% 🔺)0.782s281.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-1.5%)4.107s (-6.5% 🟢)0.000s (-100.0% 🟢)4.696s (-2.4%)1.691s131.00x
▲ VercelNext.js (Turbopack)3.462s (-66.0% 🟢)5.066s (-56.0% 🟢)0.000s (NaN%)5.508s (-54.3% 🟢)2.045s111.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.669s (-5.8% 🟢)2.058s (-5.5% 🟢)0.000s (+Infinity% 🔺)2.082s (-5.3% 🟢)0.413s291.00x
🐘 PostgresNext.js (Turbopack)1.790s2.072s0.000s2.083s0.293s291.07x
🐘 PostgresNitro1.825s (+1.9%)2.258s (+5.4% 🔺)0.000s (+107.4% 🔺)2.269s (+4.3%)0.444s271.09x
💻 LocalExpress3.530s (+1.8%)4.034s (~)0.000s (-41.7% 🟢)4.038s (~)0.508s152.12x
💻 LocalNitro3.649s (+7.7% 🔺)4.099s (+1.6%)0.001s (+62.5% 🔺)4.102s (+1.6%)0.453s152.19x
💻 LocalNext.js (Turbopack)3.847s4.389s0.000s4.395s0.547s142.31x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.955s (-3.4%)5.118s (-4.8%)0.000s (-100.0% 🟢)5.592s (-3.5%)1.637s111.00x
▲ VercelNext.js (Turbopack)4.537s (-19.2% 🟢)5.937s (-15.0% 🟢)0.000s (-100.0% 🟢)6.464s (-14.3% 🟢)1.927s101.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)17/21
🐘 PostgresNext.js (Turbopack)9/21
▲ VercelNitro16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres16/21
Nitro🐘 Postgres16/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)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9450671012
✅ 💻 Local Development10180861104
✅ 📦 Local Production10180861104
✅ 🐘 Local Postgres10180861104
✅ 🪟 Windows920092
✅ 📋 Other258018276
Total434903434692

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8507
✅ example8507
✅ express8507
✅ fastify8507
✅ hono8507
✅ nextjs-turbopack9002
✅ nextjs-webpack9002
✅ nitro8507
✅ nuxt8507
✅ sveltekit8507
✅ vite8507
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable8606
✅ e2e-local-postgres-nest-stable8606
✅ e2e-local-prod-nest-stable8606

📋 View full workflow run

@pranaygppranaygp changed the title Revert "Revert \"Add support for calling start() inside workflow functions\""Revert Revert "Add support for calling start() inside workflow functions"Mar 23, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-enables calling start() from workflow/api directly inside "use workflow" functions by routing the call through internal built-in steps, with Run objects working in workflow VM context (step-backed getters/methods) and improved observability UI hydration/rendering for run references.

Changes:

  • Add workflow-context start() delegation via WORKFLOW_START, backed by a built-in start step and a workflow-VM WorkflowRun proxy class.
  • Extend serialization/hydration to support Run/WorkflowRun across boundaries and render Run references as clickable UI elements.
  • Update builders, plugin behavior/docs, and add unit + e2e coverage for startFromWorkflow and recursive fibonacciWorkflow.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
workbench/nextjs-turbopack/app/workflows/definitions.tsAdds default args for new e2e workflows.
workbench/example/workflows/99_e2e.tsAdds startFromWorkflow and recursive fibonacciWorkflow workflows for e2e.
skills/workflow/SKILL.mdUpdates skill guidance to reflect start() support inside workflows.
packages/workflow/src/runtime.tsRe-exports getRun and start from core runtime for rewritten step imports.
packages/workflow/src/internal/builtins.tsAdds built-in start step and Run.* step-backed static methods.
packages/workflow/src/api-workflow.tsRoutes start() in workflow bundle via WORKFLOW_START injection.
packages/web/app/components/run-detail-view.tsxAdds run-ref click navigation wiring.
packages/web-shared/src/lib/hydration.tsExposes RunRef helpers for UI hydration.
packages/web-shared/src/components/workflow-traces/trace-span-construction.tsImproves span naming fallback behavior.
packages/web-shared/src/components/workflow-trace-view.tsxPlumbs onRunClick and resets selection on run change.
packages/web-shared/src/components/ui/data-inspector.tsxAdds RunRef rendering + “opaque ref” collapsing + click contexts.
packages/web-shared/src/components/sidebar/entity-detail-panel.tsxPasses onRunClick into detail panel rendering.
packages/web-shared/src/components/sidebar/attribute-panel.tsxAdds Run click context and improves stepName display fallback.
packages/web-shared/src/components/run-trace-view.tsxThreads onRunClick through trace view components.
packages/swc-plugin-workflow/transform/src/lib.rsRemoves __builtin* step-id special casing to use normal IDs.
packages/swc-plugin-workflow/spec.mdUpdates spec examples to include builtins module-specifier IDs.
packages/next/src/builder-deferred.tsRewrites bare specifiers (incl. dynamic import) and ensures builtins are included in step files.
packages/core/src/workflow/start.tsImplements workflow-VM createStart() using internal start built-in step.
packages/core/src/workflow/start.test.tsAdds unit tests for workflow-context start() behavior and option validation.
packages/core/src/workflow/run.tsAdds workflow-VM WorkflowRun proxy delegating to built-in Run steps.
packages/core/src/workflow/run.test.tsAdds unit tests asserting step delegation naming/arguments.
packages/core/src/workflow/builtin-step-id.tsAdds helper to construct built-in step IDs.
packages/core/src/workflow.tsInjects WORKFLOW_START, registers WorkflowRun, and updates response builtins to qualified IDs.
packages/core/src/symbols.tsAdds WORKFLOW_START symbol.
packages/core/src/serialization.tsAdds Run reducer/reviver using __serializable marker + class registry.
packages/core/src/serialization-format.tsAdds RunRef marker/type + reviver mapping Run → RunRef for o11y.
packages/core/src/runtime/step-handler.tsRegisters runtime Run class in host class registry for Run reviver.
packages/core/src/runtime/start.tsDelegates start() to injected workflow implementation when in workflow VM.
packages/core/src/runtime/run.tsAdds Run.__serializable marker for serialization.
packages/core/src/private.tsExtends builtin step aliasing to accept fully-qualified builtins step IDs.
packages/core/e2e/e2e.test.tsAdds e2e tests validating workflow-context start() and recursion.
docs/proxy.tsFormatting-only string quote normalization.
docs/lib/ai-agent-detection.tsFormatting-only string quote normalization.
docs/content/docs/foundations/starting-workflows.mdxDocuments calling start() inside workflow functions and recursive patterns.
docs/content/docs/foundations/common-patterns.mdxUpdates patterns docs to use direct start() in workflows.
docs/content/docs/api-reference/workflow-api/start.mdxUpdates API reference to include workflow-context usage.
AGENTS.mdUpdates local e2e instructions to include WORKFLOW_PUBLIC_MANIFEST=1.
.changeset/start-in-workflow.mdDeclares patch release for workflow-context start() support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/workflow/src/api-workflow.ts Outdated
Comment threadpackages/core/src/workflow/run.ts Outdated
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/serialization-format.ts
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/next/src/builder-deferred.ts Outdated
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
… duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
…t all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
…d of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
… step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
…just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
@VaguelySerious

Copy link
Copy Markdown
Member

Fails nextjs-turbopack tests

argsOrOptions?: TArgs | StartOptions,
options?: StartOptions
) {
'use step';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice

Sign up for freeto 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.

4 participants

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

feat: allow start() to be called directly inside workflow functions - #1491

Merged
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start
Apr 17, 2026
Merged

feat: allow start() to be called directly inside workflow functions#1491
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start

Conversation

@pranaygp

@pranaygppranaygp commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Add "use step" to start() so it can be called directly from "use workflow" functions, without needing a wrapper step function.

This is a dramatically simplified replacement for the original PR approach. Instead of complex VM-side proxy classes and builtin step delegation, the entire implementation is:

  1. Add "use step" to start() in packages/core/src/runtime/start.ts
  2. Re-export start from @workflow/core/runtime/start in api-workflow.ts (instead of a throwing stub)

This works because:

Changes

  • packages/core/src/runtime/start.ts: Add "use step" directive
  • packages/workflow/src/api-workflow.ts: Re-export start from core instead of stub
  • workbench/example/workflows/99_e2e.ts: Add startFromWorkflow and fibonacciWorkflow e2e workflows
  • packages/core/e2e/e2e.test.ts: Add e2e tests
  • workbench/nextjs-turbopack/app/workflows/definitions.ts: Add default args

E2E Tests

  • startFromWorkflow: Parent spawns child via start(), child signals parent via hook, verifies bidirectional communication
  • fibonacciWorkflow: Recursive workflow composition — fib(6) = 8 via tree of child workflows using Promise.all([start(...), start(...)])

@vercel

vercelBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-botBot commented Mar 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5826c86

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

This PR includes changesets to release 17 packages
NameType
@workflow/coreMinor
workflowMinor
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-testingPatch
@workflow/aiMajor
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.042s (-2.3%)1.005s (~)0.963s101.00x
💻 LocalExpress0.043s (-3.6%)1.005s (~)0.963s101.01x
💻 LocalNext.js (Turbopack)0.050s1.005s0.955s101.18x
🐘 PostgresNext.js (Turbopack)0.057s1.009s0.953s101.35x
🐘 PostgresExpress0.060s (+3.8%)1.010s (~)0.950s101.43x
🐘 PostgresNitro0.087s (-9.0% 🟢)1.029s (-1.3%)0.942s102.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.255s (+1.5%)1.912s (-18.0% 🟢)1.657s101.00x
▲ VercelNitro0.413s (+0.9%)2.209s (-12.0% 🟢)1.795s101.62x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalExpress1.128s (~)2.006s (~)0.878s101.01x
🐘 PostgresNext.js (Turbopack)1.136s2.010s0.874s101.02x
💻 LocalNitro1.137s (~)2.006s (~)0.870s101.02x
🐘 PostgresExpress1.145s (~)2.009s (~)0.864s101.03x
🐘 PostgresNitro1.150s (+0.9%)2.009s (~)0.859s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.841s (-52.7% 🟢)3.610s (-38.9% 🟢)1.769s101.00x
▲ VercelNext.js (Turbopack)1.941s (-4.6%)3.671s (-4.2%)1.731s101.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.791s11.023s0.232s31.00x
🐘 PostgresNitro10.846s (~)11.019s (~)0.173s31.01x
🐘 PostgresExpress10.866s (-0.9%)11.018s (~)0.151s31.01x
🐘 PostgresNext.js (Turbopack)10.897s11.024s0.127s31.01x
💻 LocalExpress10.935s (~)11.024s (~)0.088s31.01x
💻 LocalNitro10.956s (~)11.024s (~)0.068s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.899s (-28.8% 🟢)18.812s (-25.1% 🟢)1.912s21.00x
▲ VercelNext.js (Turbopack)17.099s (-1.3%)18.986s (-2.1%)1.887s21.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.427s15.022s0.595s41.00x
🐘 PostgresNitro14.487s (-0.7%)15.027s (~)0.540s41.00x
🐘 PostgresExpress14.497s (-0.6%)15.021s (~)0.524s41.00x
💻 LocalNext.js (Turbopack)14.599s15.030s0.431s41.01x
💻 LocalExpress14.977s (~)15.280s (+1.7%)0.303s41.04x
💻 LocalNitro15.014s (~)15.531s (-3.1%)0.516s41.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro31.103s (-51.7% 🟢)33.088s (-50.3% 🟢)1.985s21.00x
▲ VercelNext.js (Turbopack)34.283s (-34.8% 🟢)36.218s (-33.7% 🟢)1.935s21.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.777s (-1.6%)14.021s (-3.9%)0.244s71.00x
🐘 PostgresNitro13.849s (-0.8%)14.023s (-2.0%)0.174s71.01x
🐘 PostgresNext.js (Turbopack)13.905s14.024s0.119s71.01x
💻 LocalNext.js (Turbopack)15.836s16.029s0.193s61.15x
💻 LocalExpress16.630s (~)17.030s (~)0.400s61.21x
💻 LocalNitro16.773s (~)17.031s (~)0.258s61.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)54.152s (-86.2% 🟢)55.639s (-85.9% 🟢)1.487s21.00x
▲ VercelNitro56.014s (-86.8% 🟢)57.859s (-86.4% 🟢)1.845s21.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.237s2.009s0.772s151.00x
🐘 PostgresNitro1.259s (-1.2%)2.011s (~)0.752s151.02x
🐘 PostgresExpress1.260s (~)2.008s (~)0.748s151.02x
💻 LocalNext.js (Turbopack)1.467s2.005s0.538s151.19x
💻 LocalExpress1.539s (+3.4%)2.006s (~)0.467s151.24x
💻 LocalNitro1.584s (-2.9%)2.006s (-3.3%)0.422s151.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.295s (-18.6% 🟢)3.768s (-12.8% 🟢)1.473s81.00x
▲ VercelNext.js (Turbopack)2.682s (-21.1% 🟢)4.363s (-11.5% 🟢)1.681s71.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.323s (-1.2%)3.010s (~)0.687s101.00x
🐘 PostgresExpress2.371s (~)3.008s (~)0.637s101.02x
🐘 PostgresNext.js (Turbopack)2.411s3.009s0.598s101.04x
💻 LocalNext.js (Turbopack)2.864s3.208s0.344s101.23x
💻 LocalExpress3.041s (+3.0%)3.885s (+12.5% 🔺)0.844s81.31x
💻 LocalNitro3.188s (+1.4%)3.886s (~)0.697s81.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.493s (-38.5% 🟢)4.128s (-30.3% 🟢)1.635s81.00x
▲ VercelNext.js (Turbopack)3.312s (-53.3% 🟢)4.688s (-47.4% 🟢)1.375s71.33x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.479s (~)4.010s (~)0.531s81.00x
🐘 PostgresExpress3.486s (~)4.011s (~)0.525s81.00x
🐘 PostgresNext.js (Turbopack)3.646s4.011s0.365s81.05x
💻 LocalNext.js (Turbopack)7.295s8.015s0.720s42.10x
💻 LocalExpress8.300s (~)9.021s (~)0.721s42.39x
💻 LocalNitro9.008s (+7.9% 🔺)9.520s (+5.5% 🔺)0.512s42.59x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.317s (-5.9% 🟢)5.227s (-5.5% 🟢)1.910s61.00x
▲ VercelNext.js (Turbopack)3.665s (-58.9% 🟢)5.170s (-52.8% 🟢)1.505s61.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s2.008s0.780s151.00x
🐘 PostgresNitro1.253s (~)2.008s (~)0.755s151.02x
🐘 PostgresExpress1.265s (+0.6%)2.007s (~)0.742s151.03x
💻 LocalNext.js (Turbopack)1.480s2.006s0.526s151.20x
💻 LocalExpress1.537s (-18.8% 🟢)2.006s (-15.1% 🟢)0.469s151.25x
💻 LocalNitro1.581s (-15.3% 🟢)2.007s (-14.3% 🟢)0.426s151.29x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.065s (-16.0% 🟢)3.741s (-10.3% 🟢)1.676s91.00x
▲ VercelNext.js (Turbopack)2.073s (-29.3% 🟢)3.679s (-20.7% 🟢)1.606s91.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (-0.5%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.371s (+1.3%)3.009s (~)0.638s101.02x
🐘 PostgresNext.js (Turbopack)2.395s3.008s0.613s101.03x
💻 LocalNext.js (Turbopack)2.899s3.341s0.443s91.25x
💻 LocalExpress3.031s (-3.2%)3.883s (+3.2%)0.852s81.30x
💻 LocalNitro3.064s (~)3.885s (~)0.821s81.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.511s (-22.3% 🟢)3.996s (-21.3% 🟢)1.485s81.00x
▲ VercelNext.js (Turbopack)2.943s (-6.4% 🟢)4.388s (-3.0%)1.445s81.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.478s (-0.6%)4.011s (~)0.533s81.00x
🐘 PostgresNitro3.481s (~)4.011s (~)0.530s81.00x
🐘 PostgresNext.js (Turbopack)3.657s4.012s0.355s81.05x
💻 LocalNext.js (Turbopack)8.041s8.767s0.726s42.31x
💻 LocalExpress8.919s (+1.3%)9.271s (~)0.352s42.56x
💻 LocalNitro9.163s (~)10.022s (~)0.859s32.63x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.059s (-39.9% 🟢)4.889s (-28.3% 🟢)1.830s71.00x
▲ VercelNext.js (Turbopack)3.409s (-49.6% 🟢)5.067s (-40.7% 🟢)1.659s61.11x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.779s1.023s0.244s591.00x
🐘 PostgresExpress0.799s (-4.7%)1.006s (-1.7%)0.207s601.03x
💻 LocalNext.js (Turbopack)0.829s1.004s0.176s601.06x
🐘 PostgresNitro0.834s (+1.6%)1.006s (~)0.172s601.07x
💻 LocalExpress0.999s (+1.5%)1.369s (+27.2% 🔺)0.370s441.28x
💻 LocalNitro1.016s (+3.6%)1.611s (+47.3% 🔺)0.595s381.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro9.843s (-55.4% 🟢)12.413s (-48.3% 🟢)2.570s51.00x
▲ VercelNext.js (Turbopack)12.026s (-17.1% 🟢)13.929s (-13.4% 🟢)1.903s51.22x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.884s (-4.7%)2.075s (-8.1% 🟢)0.192s441.00x
🐘 PostgresNext.js (Turbopack)1.940s2.202s0.263s411.03x
🐘 PostgresNitro2.009s (+4.2%)2.580s (+22.8% 🔺)0.571s351.07x
💻 LocalNext.js (Turbopack)2.607s3.008s0.401s301.38x
💻 LocalNitro3.062s (+0.9%)3.842s (+2.2%)0.780s241.63x
💻 LocalExpress3.083s (+2.2%)3.843s (+7.2% 🔺)0.760s241.64x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro29.627s (-25.0% 🟢)31.818s (-23.0% 🟢)2.191s31.00x
▲ VercelNext.js (Turbopack)30.003s (-39.8% 🟢)31.773s (-38.6% 🟢)1.770s31.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.838s (-3.8%)4.042s (-7.5% 🟢)0.204s301.00x
🐘 PostgresNext.js (Turbopack)3.845s4.043s0.198s301.00x
🐘 PostgresNitro4.114s (~)4.742s (+3.0%)0.628s261.07x
💻 LocalNext.js (Turbopack)8.439s9.017s0.578s142.20x
💻 LocalExpress9.229s (~)9.941s (-0.8%)0.712s132.40x
💻 LocalNitro9.362s (+0.7%)10.103s (+0.8%)0.741s122.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro79.683s (-17.8% 🟢)81.801s (-16.9% 🟢)2.118s21.00x
▲ VercelNext.js (Turbopack)83.234s (-22.3% 🟢)84.822s (-22.1% 🟢)1.588s21.04x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.254s1.007s0.753s601.00x
🐘 PostgresExpress0.286s (+1.3%)1.007s (~)0.720s601.13x
🐘 PostgresNitro0.292s (+3.2%)1.008s (~)0.715s601.15x
💻 LocalExpress0.591s (+5.5% 🔺)1.005s (~)0.413s602.33x
💻 LocalNitro0.604s (~)1.005s (-1.7%)0.401s602.38x
💻 LocalNext.js (Turbopack)0.636s1.096s0.460s552.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.935s (-4.3%)3.931s (+3.6%)1.996s161.00x
▲ VercelNitro2.045s (+23.1% 🔺)3.924s (+17.1% 🔺)1.879s161.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.475s1.006s0.531s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.006s (~)0.518s901.03x
🐘 PostgresNitro0.498s (~)1.007s (~)0.508s901.05x
💻 LocalNext.js (Turbopack)2.523s3.009s0.485s305.31x
💻 LocalExpress2.549s (+1.4%)3.010s (~)0.461s305.36x
💻 LocalNitro2.595s (+2.2%)3.010s (~)0.415s305.46x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.216s (~)5.023s (+4.2%)1.806s181.00x
▲ VercelNext.js (Turbopack)3.287s (-7.0% 🟢)5.034s (-3.1%)1.746s181.02x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.774s (-5.4% 🟢)1.007s (-1.0%)0.233s1201.00x
🐘 PostgresNext.js (Turbopack)0.777s1.006s0.230s1201.00x
🐘 PostgresNitro0.794s (~)1.008s (~)0.214s1201.03x
💻 LocalNext.js (Turbopack)10.324s10.943s0.619s1213.34x
💻 LocalNitro11.556s (+3.3%)12.130s (+4.0%)0.574s1014.93x
💻 LocalExpress11.688s (+4.4%)12.329s (+3.3%)0.641s1015.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.003s (-22.3% 🟢)7.762s (-17.4% 🟢)1.759s161.00x
▲ VercelNext.js (Turbopack)13.494s (+30.7% 🔺)15.200s (+23.7% 🔺)1.707s112.25x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.179s1.003s0.012s1.018s0.839s101.00x
💻 LocalExpress0.203s (+1.8%)1.004s (~)0.012s (-3.3%)1.018s (~)0.815s101.13x
🐘 PostgresExpress0.203s (-0.9%)0.992s (-0.7%)0.002s (~)1.010s (~)0.806s101.14x
💻 LocalNitro0.204s (-4.5%)1.004s (~)0.012s (-4.8%)1.018s (~)0.814s101.14x
🐘 PostgresNext.js (Turbopack)0.207s1.001s0.002s1.011s0.804s101.16x
🐘 PostgresNitro0.210s (+2.5%)0.995s (~)0.001s (-20.0% 🟢)1.009s (~)0.799s101.18x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.595s (-76.7% 🟢)3.061s (-64.6% 🟢)0.709s (+12.1% 🔺)4.212s (-57.0% 🟢)2.617s101.00x
▲ VercelNitro1.672s (-56.4% 🟢)3.060s (-42.0% 🟢)0.780s (+5.1% 🔺)4.359s (-32.8% 🟢)2.687s101.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.605s (-3.9%)1.005s (~)0.004s (+2.2%)1.022s (~)0.417s591.00x
🐘 PostgresNext.js (Turbopack)0.607s1.009s0.006s1.024s0.417s591.00x
🐘 PostgresNitro0.627s (~)1.003s (~)0.004s (-4.5%)1.022s (~)0.395s591.04x
💻 LocalNext.js (Turbopack)0.756s1.011s0.020s1.126s0.370s541.25x
💻 LocalExpress0.794s (+4.8%)1.012s (-1.6%)0.015s (+54.8% 🔺)1.029s (-1.1%)0.235s591.31x
💻 LocalNitro0.968s (+15.5% 🔺)1.012s (~)0.010s (+6.7% 🔺)1.228s (+10.0% 🔺)0.260s491.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)4.375s (-74.1% 🟢)6.053s (-66.8% 🟢)0.188s (-11.1% 🟢)6.702s (-64.6% 🟢)2.327s91.00x
▲ VercelNitro4.545s (-84.6% 🟢)6.277s (-79.6% 🟢)0.225s (+101.1% 🔺)6.958s (-78.1% 🟢)2.413s91.04x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.937s1.203s0.000s1.216s0.279s501.00x
🐘 PostgresNitro0.948s (-2.1%)1.167s (-6.4% 🟢)0.000s (-7.7% 🟢)1.181s (-6.1% 🟢)0.233s521.01x
🐘 PostgresExpress0.953s (-0.9%)1.269s (-0.7%)0.000s (-51.1% 🟢)1.294s (-0.9%)0.341s471.02x
💻 LocalExpress1.252s (+2.3%)2.024s (~)0.000s (~)2.026s (~)0.773s301.34x
💻 LocalNext.js (Turbopack)1.281s2.021s0.001s2.024s0.742s301.37x
💻 LocalNitro1.420s (+16.1% 🔺)2.021s (~)0.000s (+221.4% 🔺)2.202s (+8.9% 🔺)0.782s281.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-1.5%)4.107s (-6.5% 🟢)0.000s (-100.0% 🟢)4.696s (-2.4%)1.691s131.00x
▲ VercelNext.js (Turbopack)3.462s (-66.0% 🟢)5.066s (-56.0% 🟢)0.000s (NaN%)5.508s (-54.3% 🟢)2.045s111.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.669s (-5.8% 🟢)2.058s (-5.5% 🟢)0.000s (+Infinity% 🔺)2.082s (-5.3% 🟢)0.413s291.00x
🐘 PostgresNext.js (Turbopack)1.790s2.072s0.000s2.083s0.293s291.07x
🐘 PostgresNitro1.825s (+1.9%)2.258s (+5.4% 🔺)0.000s (+107.4% 🔺)2.269s (+4.3%)0.444s271.09x
💻 LocalExpress3.530s (+1.8%)4.034s (~)0.000s (-41.7% 🟢)4.038s (~)0.508s152.12x
💻 LocalNitro3.649s (+7.7% 🔺)4.099s (+1.6%)0.001s (+62.5% 🔺)4.102s (+1.6%)0.453s152.19x
💻 LocalNext.js (Turbopack)3.847s4.389s0.000s4.395s0.547s142.31x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.955s (-3.4%)5.118s (-4.8%)0.000s (-100.0% 🟢)5.592s (-3.5%)1.637s111.00x
▲ VercelNext.js (Turbopack)4.537s (-19.2% 🟢)5.937s (-15.0% 🟢)0.000s (-100.0% 🟢)6.464s (-14.3% 🟢)1.927s101.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)17/21
🐘 PostgresNext.js (Turbopack)9/21
▲ VercelNitro16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres16/21
Nitro🐘 Postgres16/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)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9450671012
✅ 💻 Local Development10180861104
✅ 📦 Local Production10180861104
✅ 🐘 Local Postgres10180861104
✅ 🪟 Windows920092
✅ 📋 Other258018276
Total434903434692

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8507
✅ example8507
✅ express8507
✅ fastify8507
✅ hono8507
✅ nextjs-turbopack9002
✅ nextjs-webpack9002
✅ nitro8507
✅ nuxt8507
✅ sveltekit8507
✅ vite8507
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable8606
✅ e2e-local-postgres-nest-stable8606
✅ e2e-local-prod-nest-stable8606

📋 View full workflow run

@pranaygppranaygp changed the title Revert "Revert \"Add support for calling start() inside workflow functions\""Revert Revert "Add support for calling start() inside workflow functions"Mar 23, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-enables calling start() from workflow/api directly inside "use workflow" functions by routing the call through internal built-in steps, with Run objects working in workflow VM context (step-backed getters/methods) and improved observability UI hydration/rendering for run references.

Changes:

  • Add workflow-context start() delegation via WORKFLOW_START, backed by a built-in start step and a workflow-VM WorkflowRun proxy class.
  • Extend serialization/hydration to support Run/WorkflowRun across boundaries and render Run references as clickable UI elements.
  • Update builders, plugin behavior/docs, and add unit + e2e coverage for startFromWorkflow and recursive fibonacciWorkflow.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
workbench/nextjs-turbopack/app/workflows/definitions.tsAdds default args for new e2e workflows.
workbench/example/workflows/99_e2e.tsAdds startFromWorkflow and recursive fibonacciWorkflow workflows for e2e.
skills/workflow/SKILL.mdUpdates skill guidance to reflect start() support inside workflows.
packages/workflow/src/runtime.tsRe-exports getRun and start from core runtime for rewritten step imports.
packages/workflow/src/internal/builtins.tsAdds built-in start step and Run.* step-backed static methods.
packages/workflow/src/api-workflow.tsRoutes start() in workflow bundle via WORKFLOW_START injection.
packages/web/app/components/run-detail-view.tsxAdds run-ref click navigation wiring.
packages/web-shared/src/lib/hydration.tsExposes RunRef helpers for UI hydration.
packages/web-shared/src/components/workflow-traces/trace-span-construction.tsImproves span naming fallback behavior.
packages/web-shared/src/components/workflow-trace-view.tsxPlumbs onRunClick and resets selection on run change.
packages/web-shared/src/components/ui/data-inspector.tsxAdds RunRef rendering + “opaque ref” collapsing + click contexts.
packages/web-shared/src/components/sidebar/entity-detail-panel.tsxPasses onRunClick into detail panel rendering.
packages/web-shared/src/components/sidebar/attribute-panel.tsxAdds Run click context and improves stepName display fallback.
packages/web-shared/src/components/run-trace-view.tsxThreads onRunClick through trace view components.
packages/swc-plugin-workflow/transform/src/lib.rsRemoves __builtin* step-id special casing to use normal IDs.
packages/swc-plugin-workflow/spec.mdUpdates spec examples to include builtins module-specifier IDs.
packages/next/src/builder-deferred.tsRewrites bare specifiers (incl. dynamic import) and ensures builtins are included in step files.
packages/core/src/workflow/start.tsImplements workflow-VM createStart() using internal start built-in step.
packages/core/src/workflow/start.test.tsAdds unit tests for workflow-context start() behavior and option validation.
packages/core/src/workflow/run.tsAdds workflow-VM WorkflowRun proxy delegating to built-in Run steps.
packages/core/src/workflow/run.test.tsAdds unit tests asserting step delegation naming/arguments.
packages/core/src/workflow/builtin-step-id.tsAdds helper to construct built-in step IDs.
packages/core/src/workflow.tsInjects WORKFLOW_START, registers WorkflowRun, and updates response builtins to qualified IDs.
packages/core/src/symbols.tsAdds WORKFLOW_START symbol.
packages/core/src/serialization.tsAdds Run reducer/reviver using __serializable marker + class registry.
packages/core/src/serialization-format.tsAdds RunRef marker/type + reviver mapping Run → RunRef for o11y.
packages/core/src/runtime/step-handler.tsRegisters runtime Run class in host class registry for Run reviver.
packages/core/src/runtime/start.tsDelegates start() to injected workflow implementation when in workflow VM.
packages/core/src/runtime/run.tsAdds Run.__serializable marker for serialization.
packages/core/src/private.tsExtends builtin step aliasing to accept fully-qualified builtins step IDs.
packages/core/e2e/e2e.test.tsAdds e2e tests validating workflow-context start() and recursion.
docs/proxy.tsFormatting-only string quote normalization.
docs/lib/ai-agent-detection.tsFormatting-only string quote normalization.
docs/content/docs/foundations/starting-workflows.mdxDocuments calling start() inside workflow functions and recursive patterns.
docs/content/docs/foundations/common-patterns.mdxUpdates patterns docs to use direct start() in workflows.
docs/content/docs/api-reference/workflow-api/start.mdxUpdates API reference to include workflow-context usage.
AGENTS.mdUpdates local e2e instructions to include WORKFLOW_PUBLIC_MANIFEST=1.
.changeset/start-in-workflow.mdDeclares patch release for workflow-context start() support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/workflow/src/api-workflow.ts Outdated
Comment threadpackages/core/src/workflow/run.ts Outdated
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/serialization-format.ts
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/next/src/builder-deferred.ts Outdated
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
… duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
…t all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
…d of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
… step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
…just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
@VaguelySerious

Copy link
Copy Markdown
Member

Fails nextjs-turbopack tests

argsOrOptions?: TArgs | StartOptions,
options?: StartOptions
) {
'use step';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice

Sign up for freeto 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.

4 participants

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

feat: allow start() to be called directly inside workflow functions - #1491

Merged
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start
Apr 17, 2026
Merged

feat: allow start() to be called directly inside workflow functions#1491
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start

Conversation

@pranaygp

@pranaygppranaygp commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Add "use step" to start() so it can be called directly from "use workflow" functions, without needing a wrapper step function.

This is a dramatically simplified replacement for the original PR approach. Instead of complex VM-side proxy classes and builtin step delegation, the entire implementation is:

  1. Add "use step" to start() in packages/core/src/runtime/start.ts
  2. Re-export start from @workflow/core/runtime/start in api-workflow.ts (instead of a throwing stub)

This works because:

Changes

  • packages/core/src/runtime/start.ts: Add "use step" directive
  • packages/workflow/src/api-workflow.ts: Re-export start from core instead of stub
  • workbench/example/workflows/99_e2e.ts: Add startFromWorkflow and fibonacciWorkflow e2e workflows
  • packages/core/e2e/e2e.test.ts: Add e2e tests
  • workbench/nextjs-turbopack/app/workflows/definitions.ts: Add default args

E2E Tests

  • startFromWorkflow: Parent spawns child via start(), child signals parent via hook, verifies bidirectional communication
  • fibonacciWorkflow: Recursive workflow composition — fib(6) = 8 via tree of child workflows using Promise.all([start(...), start(...)])

@vercel

vercelBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-botBot commented Mar 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5826c86

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

This PR includes changesets to release 17 packages
NameType
@workflow/coreMinor
workflowMinor
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-testingPatch
@workflow/aiMajor
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.042s (-2.3%)1.005s (~)0.963s101.00x
💻 LocalExpress0.043s (-3.6%)1.005s (~)0.963s101.01x
💻 LocalNext.js (Turbopack)0.050s1.005s0.955s101.18x
🐘 PostgresNext.js (Turbopack)0.057s1.009s0.953s101.35x
🐘 PostgresExpress0.060s (+3.8%)1.010s (~)0.950s101.43x
🐘 PostgresNitro0.087s (-9.0% 🟢)1.029s (-1.3%)0.942s102.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.255s (+1.5%)1.912s (-18.0% 🟢)1.657s101.00x
▲ VercelNitro0.413s (+0.9%)2.209s (-12.0% 🟢)1.795s101.62x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalExpress1.128s (~)2.006s (~)0.878s101.01x
🐘 PostgresNext.js (Turbopack)1.136s2.010s0.874s101.02x
💻 LocalNitro1.137s (~)2.006s (~)0.870s101.02x
🐘 PostgresExpress1.145s (~)2.009s (~)0.864s101.03x
🐘 PostgresNitro1.150s (+0.9%)2.009s (~)0.859s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.841s (-52.7% 🟢)3.610s (-38.9% 🟢)1.769s101.00x
▲ VercelNext.js (Turbopack)1.941s (-4.6%)3.671s (-4.2%)1.731s101.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.791s11.023s0.232s31.00x
🐘 PostgresNitro10.846s (~)11.019s (~)0.173s31.01x
🐘 PostgresExpress10.866s (-0.9%)11.018s (~)0.151s31.01x
🐘 PostgresNext.js (Turbopack)10.897s11.024s0.127s31.01x
💻 LocalExpress10.935s (~)11.024s (~)0.088s31.01x
💻 LocalNitro10.956s (~)11.024s (~)0.068s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.899s (-28.8% 🟢)18.812s (-25.1% 🟢)1.912s21.00x
▲ VercelNext.js (Turbopack)17.099s (-1.3%)18.986s (-2.1%)1.887s21.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.427s15.022s0.595s41.00x
🐘 PostgresNitro14.487s (-0.7%)15.027s (~)0.540s41.00x
🐘 PostgresExpress14.497s (-0.6%)15.021s (~)0.524s41.00x
💻 LocalNext.js (Turbopack)14.599s15.030s0.431s41.01x
💻 LocalExpress14.977s (~)15.280s (+1.7%)0.303s41.04x
💻 LocalNitro15.014s (~)15.531s (-3.1%)0.516s41.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro31.103s (-51.7% 🟢)33.088s (-50.3% 🟢)1.985s21.00x
▲ VercelNext.js (Turbopack)34.283s (-34.8% 🟢)36.218s (-33.7% 🟢)1.935s21.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.777s (-1.6%)14.021s (-3.9%)0.244s71.00x
🐘 PostgresNitro13.849s (-0.8%)14.023s (-2.0%)0.174s71.01x
🐘 PostgresNext.js (Turbopack)13.905s14.024s0.119s71.01x
💻 LocalNext.js (Turbopack)15.836s16.029s0.193s61.15x
💻 LocalExpress16.630s (~)17.030s (~)0.400s61.21x
💻 LocalNitro16.773s (~)17.031s (~)0.258s61.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)54.152s (-86.2% 🟢)55.639s (-85.9% 🟢)1.487s21.00x
▲ VercelNitro56.014s (-86.8% 🟢)57.859s (-86.4% 🟢)1.845s21.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.237s2.009s0.772s151.00x
🐘 PostgresNitro1.259s (-1.2%)2.011s (~)0.752s151.02x
🐘 PostgresExpress1.260s (~)2.008s (~)0.748s151.02x
💻 LocalNext.js (Turbopack)1.467s2.005s0.538s151.19x
💻 LocalExpress1.539s (+3.4%)2.006s (~)0.467s151.24x
💻 LocalNitro1.584s (-2.9%)2.006s (-3.3%)0.422s151.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.295s (-18.6% 🟢)3.768s (-12.8% 🟢)1.473s81.00x
▲ VercelNext.js (Turbopack)2.682s (-21.1% 🟢)4.363s (-11.5% 🟢)1.681s71.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.323s (-1.2%)3.010s (~)0.687s101.00x
🐘 PostgresExpress2.371s (~)3.008s (~)0.637s101.02x
🐘 PostgresNext.js (Turbopack)2.411s3.009s0.598s101.04x
💻 LocalNext.js (Turbopack)2.864s3.208s0.344s101.23x
💻 LocalExpress3.041s (+3.0%)3.885s (+12.5% 🔺)0.844s81.31x
💻 LocalNitro3.188s (+1.4%)3.886s (~)0.697s81.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.493s (-38.5% 🟢)4.128s (-30.3% 🟢)1.635s81.00x
▲ VercelNext.js (Turbopack)3.312s (-53.3% 🟢)4.688s (-47.4% 🟢)1.375s71.33x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.479s (~)4.010s (~)0.531s81.00x
🐘 PostgresExpress3.486s (~)4.011s (~)0.525s81.00x
🐘 PostgresNext.js (Turbopack)3.646s4.011s0.365s81.05x
💻 LocalNext.js (Turbopack)7.295s8.015s0.720s42.10x
💻 LocalExpress8.300s (~)9.021s (~)0.721s42.39x
💻 LocalNitro9.008s (+7.9% 🔺)9.520s (+5.5% 🔺)0.512s42.59x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.317s (-5.9% 🟢)5.227s (-5.5% 🟢)1.910s61.00x
▲ VercelNext.js (Turbopack)3.665s (-58.9% 🟢)5.170s (-52.8% 🟢)1.505s61.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s2.008s0.780s151.00x
🐘 PostgresNitro1.253s (~)2.008s (~)0.755s151.02x
🐘 PostgresExpress1.265s (+0.6%)2.007s (~)0.742s151.03x
💻 LocalNext.js (Turbopack)1.480s2.006s0.526s151.20x
💻 LocalExpress1.537s (-18.8% 🟢)2.006s (-15.1% 🟢)0.469s151.25x
💻 LocalNitro1.581s (-15.3% 🟢)2.007s (-14.3% 🟢)0.426s151.29x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.065s (-16.0% 🟢)3.741s (-10.3% 🟢)1.676s91.00x
▲ VercelNext.js (Turbopack)2.073s (-29.3% 🟢)3.679s (-20.7% 🟢)1.606s91.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (-0.5%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.371s (+1.3%)3.009s (~)0.638s101.02x
🐘 PostgresNext.js (Turbopack)2.395s3.008s0.613s101.03x
💻 LocalNext.js (Turbopack)2.899s3.341s0.443s91.25x
💻 LocalExpress3.031s (-3.2%)3.883s (+3.2%)0.852s81.30x
💻 LocalNitro3.064s (~)3.885s (~)0.821s81.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.511s (-22.3% 🟢)3.996s (-21.3% 🟢)1.485s81.00x
▲ VercelNext.js (Turbopack)2.943s (-6.4% 🟢)4.388s (-3.0%)1.445s81.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.478s (-0.6%)4.011s (~)0.533s81.00x
🐘 PostgresNitro3.481s (~)4.011s (~)0.530s81.00x
🐘 PostgresNext.js (Turbopack)3.657s4.012s0.355s81.05x
💻 LocalNext.js (Turbopack)8.041s8.767s0.726s42.31x
💻 LocalExpress8.919s (+1.3%)9.271s (~)0.352s42.56x
💻 LocalNitro9.163s (~)10.022s (~)0.859s32.63x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.059s (-39.9% 🟢)4.889s (-28.3% 🟢)1.830s71.00x
▲ VercelNext.js (Turbopack)3.409s (-49.6% 🟢)5.067s (-40.7% 🟢)1.659s61.11x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.779s1.023s0.244s591.00x
🐘 PostgresExpress0.799s (-4.7%)1.006s (-1.7%)0.207s601.03x
💻 LocalNext.js (Turbopack)0.829s1.004s0.176s601.06x
🐘 PostgresNitro0.834s (+1.6%)1.006s (~)0.172s601.07x
💻 LocalExpress0.999s (+1.5%)1.369s (+27.2% 🔺)0.370s441.28x
💻 LocalNitro1.016s (+3.6%)1.611s (+47.3% 🔺)0.595s381.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro9.843s (-55.4% 🟢)12.413s (-48.3% 🟢)2.570s51.00x
▲ VercelNext.js (Turbopack)12.026s (-17.1% 🟢)13.929s (-13.4% 🟢)1.903s51.22x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.884s (-4.7%)2.075s (-8.1% 🟢)0.192s441.00x
🐘 PostgresNext.js (Turbopack)1.940s2.202s0.263s411.03x
🐘 PostgresNitro2.009s (+4.2%)2.580s (+22.8% 🔺)0.571s351.07x
💻 LocalNext.js (Turbopack)2.607s3.008s0.401s301.38x
💻 LocalNitro3.062s (+0.9%)3.842s (+2.2%)0.780s241.63x
💻 LocalExpress3.083s (+2.2%)3.843s (+7.2% 🔺)0.760s241.64x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro29.627s (-25.0% 🟢)31.818s (-23.0% 🟢)2.191s31.00x
▲ VercelNext.js (Turbopack)30.003s (-39.8% 🟢)31.773s (-38.6% 🟢)1.770s31.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.838s (-3.8%)4.042s (-7.5% 🟢)0.204s301.00x
🐘 PostgresNext.js (Turbopack)3.845s4.043s0.198s301.00x
🐘 PostgresNitro4.114s (~)4.742s (+3.0%)0.628s261.07x
💻 LocalNext.js (Turbopack)8.439s9.017s0.578s142.20x
💻 LocalExpress9.229s (~)9.941s (-0.8%)0.712s132.40x
💻 LocalNitro9.362s (+0.7%)10.103s (+0.8%)0.741s122.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro79.683s (-17.8% 🟢)81.801s (-16.9% 🟢)2.118s21.00x
▲ VercelNext.js (Turbopack)83.234s (-22.3% 🟢)84.822s (-22.1% 🟢)1.588s21.04x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.254s1.007s0.753s601.00x
🐘 PostgresExpress0.286s (+1.3%)1.007s (~)0.720s601.13x
🐘 PostgresNitro0.292s (+3.2%)1.008s (~)0.715s601.15x
💻 LocalExpress0.591s (+5.5% 🔺)1.005s (~)0.413s602.33x
💻 LocalNitro0.604s (~)1.005s (-1.7%)0.401s602.38x
💻 LocalNext.js (Turbopack)0.636s1.096s0.460s552.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.935s (-4.3%)3.931s (+3.6%)1.996s161.00x
▲ VercelNitro2.045s (+23.1% 🔺)3.924s (+17.1% 🔺)1.879s161.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.475s1.006s0.531s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.006s (~)0.518s901.03x
🐘 PostgresNitro0.498s (~)1.007s (~)0.508s901.05x
💻 LocalNext.js (Turbopack)2.523s3.009s0.485s305.31x
💻 LocalExpress2.549s (+1.4%)3.010s (~)0.461s305.36x
💻 LocalNitro2.595s (+2.2%)3.010s (~)0.415s305.46x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.216s (~)5.023s (+4.2%)1.806s181.00x
▲ VercelNext.js (Turbopack)3.287s (-7.0% 🟢)5.034s (-3.1%)1.746s181.02x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.774s (-5.4% 🟢)1.007s (-1.0%)0.233s1201.00x
🐘 PostgresNext.js (Turbopack)0.777s1.006s0.230s1201.00x
🐘 PostgresNitro0.794s (~)1.008s (~)0.214s1201.03x
💻 LocalNext.js (Turbopack)10.324s10.943s0.619s1213.34x
💻 LocalNitro11.556s (+3.3%)12.130s (+4.0%)0.574s1014.93x
💻 LocalExpress11.688s (+4.4%)12.329s (+3.3%)0.641s1015.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.003s (-22.3% 🟢)7.762s (-17.4% 🟢)1.759s161.00x
▲ VercelNext.js (Turbopack)13.494s (+30.7% 🔺)15.200s (+23.7% 🔺)1.707s112.25x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.179s1.003s0.012s1.018s0.839s101.00x
💻 LocalExpress0.203s (+1.8%)1.004s (~)0.012s (-3.3%)1.018s (~)0.815s101.13x
🐘 PostgresExpress0.203s (-0.9%)0.992s (-0.7%)0.002s (~)1.010s (~)0.806s101.14x
💻 LocalNitro0.204s (-4.5%)1.004s (~)0.012s (-4.8%)1.018s (~)0.814s101.14x
🐘 PostgresNext.js (Turbopack)0.207s1.001s0.002s1.011s0.804s101.16x
🐘 PostgresNitro0.210s (+2.5%)0.995s (~)0.001s (-20.0% 🟢)1.009s (~)0.799s101.18x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.595s (-76.7% 🟢)3.061s (-64.6% 🟢)0.709s (+12.1% 🔺)4.212s (-57.0% 🟢)2.617s101.00x
▲ VercelNitro1.672s (-56.4% 🟢)3.060s (-42.0% 🟢)0.780s (+5.1% 🔺)4.359s (-32.8% 🟢)2.687s101.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.605s (-3.9%)1.005s (~)0.004s (+2.2%)1.022s (~)0.417s591.00x
🐘 PostgresNext.js (Turbopack)0.607s1.009s0.006s1.024s0.417s591.00x
🐘 PostgresNitro0.627s (~)1.003s (~)0.004s (-4.5%)1.022s (~)0.395s591.04x
💻 LocalNext.js (Turbopack)0.756s1.011s0.020s1.126s0.370s541.25x
💻 LocalExpress0.794s (+4.8%)1.012s (-1.6%)0.015s (+54.8% 🔺)1.029s (-1.1%)0.235s591.31x
💻 LocalNitro0.968s (+15.5% 🔺)1.012s (~)0.010s (+6.7% 🔺)1.228s (+10.0% 🔺)0.260s491.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)4.375s (-74.1% 🟢)6.053s (-66.8% 🟢)0.188s (-11.1% 🟢)6.702s (-64.6% 🟢)2.327s91.00x
▲ VercelNitro4.545s (-84.6% 🟢)6.277s (-79.6% 🟢)0.225s (+101.1% 🔺)6.958s (-78.1% 🟢)2.413s91.04x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.937s1.203s0.000s1.216s0.279s501.00x
🐘 PostgresNitro0.948s (-2.1%)1.167s (-6.4% 🟢)0.000s (-7.7% 🟢)1.181s (-6.1% 🟢)0.233s521.01x
🐘 PostgresExpress0.953s (-0.9%)1.269s (-0.7%)0.000s (-51.1% 🟢)1.294s (-0.9%)0.341s471.02x
💻 LocalExpress1.252s (+2.3%)2.024s (~)0.000s (~)2.026s (~)0.773s301.34x
💻 LocalNext.js (Turbopack)1.281s2.021s0.001s2.024s0.742s301.37x
💻 LocalNitro1.420s (+16.1% 🔺)2.021s (~)0.000s (+221.4% 🔺)2.202s (+8.9% 🔺)0.782s281.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-1.5%)4.107s (-6.5% 🟢)0.000s (-100.0% 🟢)4.696s (-2.4%)1.691s131.00x
▲ VercelNext.js (Turbopack)3.462s (-66.0% 🟢)5.066s (-56.0% 🟢)0.000s (NaN%)5.508s (-54.3% 🟢)2.045s111.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.669s (-5.8% 🟢)2.058s (-5.5% 🟢)0.000s (+Infinity% 🔺)2.082s (-5.3% 🟢)0.413s291.00x
🐘 PostgresNext.js (Turbopack)1.790s2.072s0.000s2.083s0.293s291.07x
🐘 PostgresNitro1.825s (+1.9%)2.258s (+5.4% 🔺)0.000s (+107.4% 🔺)2.269s (+4.3%)0.444s271.09x
💻 LocalExpress3.530s (+1.8%)4.034s (~)0.000s (-41.7% 🟢)4.038s (~)0.508s152.12x
💻 LocalNitro3.649s (+7.7% 🔺)4.099s (+1.6%)0.001s (+62.5% 🔺)4.102s (+1.6%)0.453s152.19x
💻 LocalNext.js (Turbopack)3.847s4.389s0.000s4.395s0.547s142.31x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.955s (-3.4%)5.118s (-4.8%)0.000s (-100.0% 🟢)5.592s (-3.5%)1.637s111.00x
▲ VercelNext.js (Turbopack)4.537s (-19.2% 🟢)5.937s (-15.0% 🟢)0.000s (-100.0% 🟢)6.464s (-14.3% 🟢)1.927s101.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)17/21
🐘 PostgresNext.js (Turbopack)9/21
▲ VercelNitro16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres16/21
Nitro🐘 Postgres16/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)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9450671012
✅ 💻 Local Development10180861104
✅ 📦 Local Production10180861104
✅ 🐘 Local Postgres10180861104
✅ 🪟 Windows920092
✅ 📋 Other258018276
Total434903434692

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8507
✅ example8507
✅ express8507
✅ fastify8507
✅ hono8507
✅ nextjs-turbopack9002
✅ nextjs-webpack9002
✅ nitro8507
✅ nuxt8507
✅ sveltekit8507
✅ vite8507
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable8606
✅ e2e-local-postgres-nest-stable8606
✅ e2e-local-prod-nest-stable8606

📋 View full workflow run

@pranaygppranaygp changed the title Revert "Revert \"Add support for calling start() inside workflow functions\""Revert Revert "Add support for calling start() inside workflow functions"Mar 23, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-enables calling start() from workflow/api directly inside "use workflow" functions by routing the call through internal built-in steps, with Run objects working in workflow VM context (step-backed getters/methods) and improved observability UI hydration/rendering for run references.

Changes:

  • Add workflow-context start() delegation via WORKFLOW_START, backed by a built-in start step and a workflow-VM WorkflowRun proxy class.
  • Extend serialization/hydration to support Run/WorkflowRun across boundaries and render Run references as clickable UI elements.
  • Update builders, plugin behavior/docs, and add unit + e2e coverage for startFromWorkflow and recursive fibonacciWorkflow.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
workbench/nextjs-turbopack/app/workflows/definitions.tsAdds default args for new e2e workflows.
workbench/example/workflows/99_e2e.tsAdds startFromWorkflow and recursive fibonacciWorkflow workflows for e2e.
skills/workflow/SKILL.mdUpdates skill guidance to reflect start() support inside workflows.
packages/workflow/src/runtime.tsRe-exports getRun and start from core runtime for rewritten step imports.
packages/workflow/src/internal/builtins.tsAdds built-in start step and Run.* step-backed static methods.
packages/workflow/src/api-workflow.tsRoutes start() in workflow bundle via WORKFLOW_START injection.
packages/web/app/components/run-detail-view.tsxAdds run-ref click navigation wiring.
packages/web-shared/src/lib/hydration.tsExposes RunRef helpers for UI hydration.
packages/web-shared/src/components/workflow-traces/trace-span-construction.tsImproves span naming fallback behavior.
packages/web-shared/src/components/workflow-trace-view.tsxPlumbs onRunClick and resets selection on run change.
packages/web-shared/src/components/ui/data-inspector.tsxAdds RunRef rendering + “opaque ref” collapsing + click contexts.
packages/web-shared/src/components/sidebar/entity-detail-panel.tsxPasses onRunClick into detail panel rendering.
packages/web-shared/src/components/sidebar/attribute-panel.tsxAdds Run click context and improves stepName display fallback.
packages/web-shared/src/components/run-trace-view.tsxThreads onRunClick through trace view components.
packages/swc-plugin-workflow/transform/src/lib.rsRemoves __builtin* step-id special casing to use normal IDs.
packages/swc-plugin-workflow/spec.mdUpdates spec examples to include builtins module-specifier IDs.
packages/next/src/builder-deferred.tsRewrites bare specifiers (incl. dynamic import) and ensures builtins are included in step files.
packages/core/src/workflow/start.tsImplements workflow-VM createStart() using internal start built-in step.
packages/core/src/workflow/start.test.tsAdds unit tests for workflow-context start() behavior and option validation.
packages/core/src/workflow/run.tsAdds workflow-VM WorkflowRun proxy delegating to built-in Run steps.
packages/core/src/workflow/run.test.tsAdds unit tests asserting step delegation naming/arguments.
packages/core/src/workflow/builtin-step-id.tsAdds helper to construct built-in step IDs.
packages/core/src/workflow.tsInjects WORKFLOW_START, registers WorkflowRun, and updates response builtins to qualified IDs.
packages/core/src/symbols.tsAdds WORKFLOW_START symbol.
packages/core/src/serialization.tsAdds Run reducer/reviver using __serializable marker + class registry.
packages/core/src/serialization-format.tsAdds RunRef marker/type + reviver mapping Run → RunRef for o11y.
packages/core/src/runtime/step-handler.tsRegisters runtime Run class in host class registry for Run reviver.
packages/core/src/runtime/start.tsDelegates start() to injected workflow implementation when in workflow VM.
packages/core/src/runtime/run.tsAdds Run.__serializable marker for serialization.
packages/core/src/private.tsExtends builtin step aliasing to accept fully-qualified builtins step IDs.
packages/core/e2e/e2e.test.tsAdds e2e tests validating workflow-context start() and recursion.
docs/proxy.tsFormatting-only string quote normalization.
docs/lib/ai-agent-detection.tsFormatting-only string quote normalization.
docs/content/docs/foundations/starting-workflows.mdxDocuments calling start() inside workflow functions and recursive patterns.
docs/content/docs/foundations/common-patterns.mdxUpdates patterns docs to use direct start() in workflows.
docs/content/docs/api-reference/workflow-api/start.mdxUpdates API reference to include workflow-context usage.
AGENTS.mdUpdates local e2e instructions to include WORKFLOW_PUBLIC_MANIFEST=1.
.changeset/start-in-workflow.mdDeclares patch release for workflow-context start() support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/workflow/src/api-workflow.ts Outdated
Comment threadpackages/core/src/workflow/run.ts Outdated
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/serialization-format.ts
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/next/src/builder-deferred.ts Outdated
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
… duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
…t all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
…d of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
… step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
…just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
@VaguelySerious

Copy link
Copy Markdown
Member

Fails nextjs-turbopack tests

argsOrOptions?: TArgs | StartOptions,
options?: StartOptions
) {
'use step';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice

Sign up for freeto 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.

4 participants

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

feat: allow start() to be called directly inside workflow functions - #1491

Merged
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start
Apr 17, 2026
Merged

feat: allow start() to be called directly inside workflow functions#1491
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start

Conversation

@pranaygp

@pranaygppranaygp commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Add "use step" to start() so it can be called directly from "use workflow" functions, without needing a wrapper step function.

This is a dramatically simplified replacement for the original PR approach. Instead of complex VM-side proxy classes and builtin step delegation, the entire implementation is:

  1. Add "use step" to start() in packages/core/src/runtime/start.ts
  2. Re-export start from @workflow/core/runtime/start in api-workflow.ts (instead of a throwing stub)

This works because:

Changes

  • packages/core/src/runtime/start.ts: Add "use step" directive
  • packages/workflow/src/api-workflow.ts: Re-export start from core instead of stub
  • workbench/example/workflows/99_e2e.ts: Add startFromWorkflow and fibonacciWorkflow e2e workflows
  • packages/core/e2e/e2e.test.ts: Add e2e tests
  • workbench/nextjs-turbopack/app/workflows/definitions.ts: Add default args

E2E Tests

  • startFromWorkflow: Parent spawns child via start(), child signals parent via hook, verifies bidirectional communication
  • fibonacciWorkflow: Recursive workflow composition — fib(6) = 8 via tree of child workflows using Promise.all([start(...), start(...)])

@vercel

vercelBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-botBot commented Mar 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5826c86

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

This PR includes changesets to release 17 packages
NameType
@workflow/coreMinor
workflowMinor
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-testingPatch
@workflow/aiMajor
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.042s (-2.3%)1.005s (~)0.963s101.00x
💻 LocalExpress0.043s (-3.6%)1.005s (~)0.963s101.01x
💻 LocalNext.js (Turbopack)0.050s1.005s0.955s101.18x
🐘 PostgresNext.js (Turbopack)0.057s1.009s0.953s101.35x
🐘 PostgresExpress0.060s (+3.8%)1.010s (~)0.950s101.43x
🐘 PostgresNitro0.087s (-9.0% 🟢)1.029s (-1.3%)0.942s102.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.255s (+1.5%)1.912s (-18.0% 🟢)1.657s101.00x
▲ VercelNitro0.413s (+0.9%)2.209s (-12.0% 🟢)1.795s101.62x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalExpress1.128s (~)2.006s (~)0.878s101.01x
🐘 PostgresNext.js (Turbopack)1.136s2.010s0.874s101.02x
💻 LocalNitro1.137s (~)2.006s (~)0.870s101.02x
🐘 PostgresExpress1.145s (~)2.009s (~)0.864s101.03x
🐘 PostgresNitro1.150s (+0.9%)2.009s (~)0.859s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.841s (-52.7% 🟢)3.610s (-38.9% 🟢)1.769s101.00x
▲ VercelNext.js (Turbopack)1.941s (-4.6%)3.671s (-4.2%)1.731s101.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.791s11.023s0.232s31.00x
🐘 PostgresNitro10.846s (~)11.019s (~)0.173s31.01x
🐘 PostgresExpress10.866s (-0.9%)11.018s (~)0.151s31.01x
🐘 PostgresNext.js (Turbopack)10.897s11.024s0.127s31.01x
💻 LocalExpress10.935s (~)11.024s (~)0.088s31.01x
💻 LocalNitro10.956s (~)11.024s (~)0.068s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.899s (-28.8% 🟢)18.812s (-25.1% 🟢)1.912s21.00x
▲ VercelNext.js (Turbopack)17.099s (-1.3%)18.986s (-2.1%)1.887s21.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.427s15.022s0.595s41.00x
🐘 PostgresNitro14.487s (-0.7%)15.027s (~)0.540s41.00x
🐘 PostgresExpress14.497s (-0.6%)15.021s (~)0.524s41.00x
💻 LocalNext.js (Turbopack)14.599s15.030s0.431s41.01x
💻 LocalExpress14.977s (~)15.280s (+1.7%)0.303s41.04x
💻 LocalNitro15.014s (~)15.531s (-3.1%)0.516s41.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro31.103s (-51.7% 🟢)33.088s (-50.3% 🟢)1.985s21.00x
▲ VercelNext.js (Turbopack)34.283s (-34.8% 🟢)36.218s (-33.7% 🟢)1.935s21.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.777s (-1.6%)14.021s (-3.9%)0.244s71.00x
🐘 PostgresNitro13.849s (-0.8%)14.023s (-2.0%)0.174s71.01x
🐘 PostgresNext.js (Turbopack)13.905s14.024s0.119s71.01x
💻 LocalNext.js (Turbopack)15.836s16.029s0.193s61.15x
💻 LocalExpress16.630s (~)17.030s (~)0.400s61.21x
💻 LocalNitro16.773s (~)17.031s (~)0.258s61.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)54.152s (-86.2% 🟢)55.639s (-85.9% 🟢)1.487s21.00x
▲ VercelNitro56.014s (-86.8% 🟢)57.859s (-86.4% 🟢)1.845s21.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.237s2.009s0.772s151.00x
🐘 PostgresNitro1.259s (-1.2%)2.011s (~)0.752s151.02x
🐘 PostgresExpress1.260s (~)2.008s (~)0.748s151.02x
💻 LocalNext.js (Turbopack)1.467s2.005s0.538s151.19x
💻 LocalExpress1.539s (+3.4%)2.006s (~)0.467s151.24x
💻 LocalNitro1.584s (-2.9%)2.006s (-3.3%)0.422s151.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.295s (-18.6% 🟢)3.768s (-12.8% 🟢)1.473s81.00x
▲ VercelNext.js (Turbopack)2.682s (-21.1% 🟢)4.363s (-11.5% 🟢)1.681s71.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.323s (-1.2%)3.010s (~)0.687s101.00x
🐘 PostgresExpress2.371s (~)3.008s (~)0.637s101.02x
🐘 PostgresNext.js (Turbopack)2.411s3.009s0.598s101.04x
💻 LocalNext.js (Turbopack)2.864s3.208s0.344s101.23x
💻 LocalExpress3.041s (+3.0%)3.885s (+12.5% 🔺)0.844s81.31x
💻 LocalNitro3.188s (+1.4%)3.886s (~)0.697s81.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.493s (-38.5% 🟢)4.128s (-30.3% 🟢)1.635s81.00x
▲ VercelNext.js (Turbopack)3.312s (-53.3% 🟢)4.688s (-47.4% 🟢)1.375s71.33x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.479s (~)4.010s (~)0.531s81.00x
🐘 PostgresExpress3.486s (~)4.011s (~)0.525s81.00x
🐘 PostgresNext.js (Turbopack)3.646s4.011s0.365s81.05x
💻 LocalNext.js (Turbopack)7.295s8.015s0.720s42.10x
💻 LocalExpress8.300s (~)9.021s (~)0.721s42.39x
💻 LocalNitro9.008s (+7.9% 🔺)9.520s (+5.5% 🔺)0.512s42.59x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.317s (-5.9% 🟢)5.227s (-5.5% 🟢)1.910s61.00x
▲ VercelNext.js (Turbopack)3.665s (-58.9% 🟢)5.170s (-52.8% 🟢)1.505s61.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s2.008s0.780s151.00x
🐘 PostgresNitro1.253s (~)2.008s (~)0.755s151.02x
🐘 PostgresExpress1.265s (+0.6%)2.007s (~)0.742s151.03x
💻 LocalNext.js (Turbopack)1.480s2.006s0.526s151.20x
💻 LocalExpress1.537s (-18.8% 🟢)2.006s (-15.1% 🟢)0.469s151.25x
💻 LocalNitro1.581s (-15.3% 🟢)2.007s (-14.3% 🟢)0.426s151.29x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.065s (-16.0% 🟢)3.741s (-10.3% 🟢)1.676s91.00x
▲ VercelNext.js (Turbopack)2.073s (-29.3% 🟢)3.679s (-20.7% 🟢)1.606s91.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (-0.5%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.371s (+1.3%)3.009s (~)0.638s101.02x
🐘 PostgresNext.js (Turbopack)2.395s3.008s0.613s101.03x
💻 LocalNext.js (Turbopack)2.899s3.341s0.443s91.25x
💻 LocalExpress3.031s (-3.2%)3.883s (+3.2%)0.852s81.30x
💻 LocalNitro3.064s (~)3.885s (~)0.821s81.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.511s (-22.3% 🟢)3.996s (-21.3% 🟢)1.485s81.00x
▲ VercelNext.js (Turbopack)2.943s (-6.4% 🟢)4.388s (-3.0%)1.445s81.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.478s (-0.6%)4.011s (~)0.533s81.00x
🐘 PostgresNitro3.481s (~)4.011s (~)0.530s81.00x
🐘 PostgresNext.js (Turbopack)3.657s4.012s0.355s81.05x
💻 LocalNext.js (Turbopack)8.041s8.767s0.726s42.31x
💻 LocalExpress8.919s (+1.3%)9.271s (~)0.352s42.56x
💻 LocalNitro9.163s (~)10.022s (~)0.859s32.63x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.059s (-39.9% 🟢)4.889s (-28.3% 🟢)1.830s71.00x
▲ VercelNext.js (Turbopack)3.409s (-49.6% 🟢)5.067s (-40.7% 🟢)1.659s61.11x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.779s1.023s0.244s591.00x
🐘 PostgresExpress0.799s (-4.7%)1.006s (-1.7%)0.207s601.03x
💻 LocalNext.js (Turbopack)0.829s1.004s0.176s601.06x
🐘 PostgresNitro0.834s (+1.6%)1.006s (~)0.172s601.07x
💻 LocalExpress0.999s (+1.5%)1.369s (+27.2% 🔺)0.370s441.28x
💻 LocalNitro1.016s (+3.6%)1.611s (+47.3% 🔺)0.595s381.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro9.843s (-55.4% 🟢)12.413s (-48.3% 🟢)2.570s51.00x
▲ VercelNext.js (Turbopack)12.026s (-17.1% 🟢)13.929s (-13.4% 🟢)1.903s51.22x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.884s (-4.7%)2.075s (-8.1% 🟢)0.192s441.00x
🐘 PostgresNext.js (Turbopack)1.940s2.202s0.263s411.03x
🐘 PostgresNitro2.009s (+4.2%)2.580s (+22.8% 🔺)0.571s351.07x
💻 LocalNext.js (Turbopack)2.607s3.008s0.401s301.38x
💻 LocalNitro3.062s (+0.9%)3.842s (+2.2%)0.780s241.63x
💻 LocalExpress3.083s (+2.2%)3.843s (+7.2% 🔺)0.760s241.64x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro29.627s (-25.0% 🟢)31.818s (-23.0% 🟢)2.191s31.00x
▲ VercelNext.js (Turbopack)30.003s (-39.8% 🟢)31.773s (-38.6% 🟢)1.770s31.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.838s (-3.8%)4.042s (-7.5% 🟢)0.204s301.00x
🐘 PostgresNext.js (Turbopack)3.845s4.043s0.198s301.00x
🐘 PostgresNitro4.114s (~)4.742s (+3.0%)0.628s261.07x
💻 LocalNext.js (Turbopack)8.439s9.017s0.578s142.20x
💻 LocalExpress9.229s (~)9.941s (-0.8%)0.712s132.40x
💻 LocalNitro9.362s (+0.7%)10.103s (+0.8%)0.741s122.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro79.683s (-17.8% 🟢)81.801s (-16.9% 🟢)2.118s21.00x
▲ VercelNext.js (Turbopack)83.234s (-22.3% 🟢)84.822s (-22.1% 🟢)1.588s21.04x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.254s1.007s0.753s601.00x
🐘 PostgresExpress0.286s (+1.3%)1.007s (~)0.720s601.13x
🐘 PostgresNitro0.292s (+3.2%)1.008s (~)0.715s601.15x
💻 LocalExpress0.591s (+5.5% 🔺)1.005s (~)0.413s602.33x
💻 LocalNitro0.604s (~)1.005s (-1.7%)0.401s602.38x
💻 LocalNext.js (Turbopack)0.636s1.096s0.460s552.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.935s (-4.3%)3.931s (+3.6%)1.996s161.00x
▲ VercelNitro2.045s (+23.1% 🔺)3.924s (+17.1% 🔺)1.879s161.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.475s1.006s0.531s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.006s (~)0.518s901.03x
🐘 PostgresNitro0.498s (~)1.007s (~)0.508s901.05x
💻 LocalNext.js (Turbopack)2.523s3.009s0.485s305.31x
💻 LocalExpress2.549s (+1.4%)3.010s (~)0.461s305.36x
💻 LocalNitro2.595s (+2.2%)3.010s (~)0.415s305.46x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.216s (~)5.023s (+4.2%)1.806s181.00x
▲ VercelNext.js (Turbopack)3.287s (-7.0% 🟢)5.034s (-3.1%)1.746s181.02x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.774s (-5.4% 🟢)1.007s (-1.0%)0.233s1201.00x
🐘 PostgresNext.js (Turbopack)0.777s1.006s0.230s1201.00x
🐘 PostgresNitro0.794s (~)1.008s (~)0.214s1201.03x
💻 LocalNext.js (Turbopack)10.324s10.943s0.619s1213.34x
💻 LocalNitro11.556s (+3.3%)12.130s (+4.0%)0.574s1014.93x
💻 LocalExpress11.688s (+4.4%)12.329s (+3.3%)0.641s1015.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.003s (-22.3% 🟢)7.762s (-17.4% 🟢)1.759s161.00x
▲ VercelNext.js (Turbopack)13.494s (+30.7% 🔺)15.200s (+23.7% 🔺)1.707s112.25x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.179s1.003s0.012s1.018s0.839s101.00x
💻 LocalExpress0.203s (+1.8%)1.004s (~)0.012s (-3.3%)1.018s (~)0.815s101.13x
🐘 PostgresExpress0.203s (-0.9%)0.992s (-0.7%)0.002s (~)1.010s (~)0.806s101.14x
💻 LocalNitro0.204s (-4.5%)1.004s (~)0.012s (-4.8%)1.018s (~)0.814s101.14x
🐘 PostgresNext.js (Turbopack)0.207s1.001s0.002s1.011s0.804s101.16x
🐘 PostgresNitro0.210s (+2.5%)0.995s (~)0.001s (-20.0% 🟢)1.009s (~)0.799s101.18x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.595s (-76.7% 🟢)3.061s (-64.6% 🟢)0.709s (+12.1% 🔺)4.212s (-57.0% 🟢)2.617s101.00x
▲ VercelNitro1.672s (-56.4% 🟢)3.060s (-42.0% 🟢)0.780s (+5.1% 🔺)4.359s (-32.8% 🟢)2.687s101.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.605s (-3.9%)1.005s (~)0.004s (+2.2%)1.022s (~)0.417s591.00x
🐘 PostgresNext.js (Turbopack)0.607s1.009s0.006s1.024s0.417s591.00x
🐘 PostgresNitro0.627s (~)1.003s (~)0.004s (-4.5%)1.022s (~)0.395s591.04x
💻 LocalNext.js (Turbopack)0.756s1.011s0.020s1.126s0.370s541.25x
💻 LocalExpress0.794s (+4.8%)1.012s (-1.6%)0.015s (+54.8% 🔺)1.029s (-1.1%)0.235s591.31x
💻 LocalNitro0.968s (+15.5% 🔺)1.012s (~)0.010s (+6.7% 🔺)1.228s (+10.0% 🔺)0.260s491.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)4.375s (-74.1% 🟢)6.053s (-66.8% 🟢)0.188s (-11.1% 🟢)6.702s (-64.6% 🟢)2.327s91.00x
▲ VercelNitro4.545s (-84.6% 🟢)6.277s (-79.6% 🟢)0.225s (+101.1% 🔺)6.958s (-78.1% 🟢)2.413s91.04x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.937s1.203s0.000s1.216s0.279s501.00x
🐘 PostgresNitro0.948s (-2.1%)1.167s (-6.4% 🟢)0.000s (-7.7% 🟢)1.181s (-6.1% 🟢)0.233s521.01x
🐘 PostgresExpress0.953s (-0.9%)1.269s (-0.7%)0.000s (-51.1% 🟢)1.294s (-0.9%)0.341s471.02x
💻 LocalExpress1.252s (+2.3%)2.024s (~)0.000s (~)2.026s (~)0.773s301.34x
💻 LocalNext.js (Turbopack)1.281s2.021s0.001s2.024s0.742s301.37x
💻 LocalNitro1.420s (+16.1% 🔺)2.021s (~)0.000s (+221.4% 🔺)2.202s (+8.9% 🔺)0.782s281.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-1.5%)4.107s (-6.5% 🟢)0.000s (-100.0% 🟢)4.696s (-2.4%)1.691s131.00x
▲ VercelNext.js (Turbopack)3.462s (-66.0% 🟢)5.066s (-56.0% 🟢)0.000s (NaN%)5.508s (-54.3% 🟢)2.045s111.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.669s (-5.8% 🟢)2.058s (-5.5% 🟢)0.000s (+Infinity% 🔺)2.082s (-5.3% 🟢)0.413s291.00x
🐘 PostgresNext.js (Turbopack)1.790s2.072s0.000s2.083s0.293s291.07x
🐘 PostgresNitro1.825s (+1.9%)2.258s (+5.4% 🔺)0.000s (+107.4% 🔺)2.269s (+4.3%)0.444s271.09x
💻 LocalExpress3.530s (+1.8%)4.034s (~)0.000s (-41.7% 🟢)4.038s (~)0.508s152.12x
💻 LocalNitro3.649s (+7.7% 🔺)4.099s (+1.6%)0.001s (+62.5% 🔺)4.102s (+1.6%)0.453s152.19x
💻 LocalNext.js (Turbopack)3.847s4.389s0.000s4.395s0.547s142.31x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.955s (-3.4%)5.118s (-4.8%)0.000s (-100.0% 🟢)5.592s (-3.5%)1.637s111.00x
▲ VercelNext.js (Turbopack)4.537s (-19.2% 🟢)5.937s (-15.0% 🟢)0.000s (-100.0% 🟢)6.464s (-14.3% 🟢)1.927s101.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)17/21
🐘 PostgresNext.js (Turbopack)9/21
▲ VercelNitro16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres16/21
Nitro🐘 Postgres16/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)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9450671012
✅ 💻 Local Development10180861104
✅ 📦 Local Production10180861104
✅ 🐘 Local Postgres10180861104
✅ 🪟 Windows920092
✅ 📋 Other258018276
Total434903434692

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8507
✅ example8507
✅ express8507
✅ fastify8507
✅ hono8507
✅ nextjs-turbopack9002
✅ nextjs-webpack9002
✅ nitro8507
✅ nuxt8507
✅ sveltekit8507
✅ vite8507
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable8606
✅ e2e-local-postgres-nest-stable8606
✅ e2e-local-prod-nest-stable8606

📋 View full workflow run

@pranaygppranaygp changed the title Revert "Revert \"Add support for calling start() inside workflow functions\""Revert Revert "Add support for calling start() inside workflow functions"Mar 23, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-enables calling start() from workflow/api directly inside "use workflow" functions by routing the call through internal built-in steps, with Run objects working in workflow VM context (step-backed getters/methods) and improved observability UI hydration/rendering for run references.

Changes:

  • Add workflow-context start() delegation via WORKFLOW_START, backed by a built-in start step and a workflow-VM WorkflowRun proxy class.
  • Extend serialization/hydration to support Run/WorkflowRun across boundaries and render Run references as clickable UI elements.
  • Update builders, plugin behavior/docs, and add unit + e2e coverage for startFromWorkflow and recursive fibonacciWorkflow.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
workbench/nextjs-turbopack/app/workflows/definitions.tsAdds default args for new e2e workflows.
workbench/example/workflows/99_e2e.tsAdds startFromWorkflow and recursive fibonacciWorkflow workflows for e2e.
skills/workflow/SKILL.mdUpdates skill guidance to reflect start() support inside workflows.
packages/workflow/src/runtime.tsRe-exports getRun and start from core runtime for rewritten step imports.
packages/workflow/src/internal/builtins.tsAdds built-in start step and Run.* step-backed static methods.
packages/workflow/src/api-workflow.tsRoutes start() in workflow bundle via WORKFLOW_START injection.
packages/web/app/components/run-detail-view.tsxAdds run-ref click navigation wiring.
packages/web-shared/src/lib/hydration.tsExposes RunRef helpers for UI hydration.
packages/web-shared/src/components/workflow-traces/trace-span-construction.tsImproves span naming fallback behavior.
packages/web-shared/src/components/workflow-trace-view.tsxPlumbs onRunClick and resets selection on run change.
packages/web-shared/src/components/ui/data-inspector.tsxAdds RunRef rendering + “opaque ref” collapsing + click contexts.
packages/web-shared/src/components/sidebar/entity-detail-panel.tsxPasses onRunClick into detail panel rendering.
packages/web-shared/src/components/sidebar/attribute-panel.tsxAdds Run click context and improves stepName display fallback.
packages/web-shared/src/components/run-trace-view.tsxThreads onRunClick through trace view components.
packages/swc-plugin-workflow/transform/src/lib.rsRemoves __builtin* step-id special casing to use normal IDs.
packages/swc-plugin-workflow/spec.mdUpdates spec examples to include builtins module-specifier IDs.
packages/next/src/builder-deferred.tsRewrites bare specifiers (incl. dynamic import) and ensures builtins are included in step files.
packages/core/src/workflow/start.tsImplements workflow-VM createStart() using internal start built-in step.
packages/core/src/workflow/start.test.tsAdds unit tests for workflow-context start() behavior and option validation.
packages/core/src/workflow/run.tsAdds workflow-VM WorkflowRun proxy delegating to built-in Run steps.
packages/core/src/workflow/run.test.tsAdds unit tests asserting step delegation naming/arguments.
packages/core/src/workflow/builtin-step-id.tsAdds helper to construct built-in step IDs.
packages/core/src/workflow.tsInjects WORKFLOW_START, registers WorkflowRun, and updates response builtins to qualified IDs.
packages/core/src/symbols.tsAdds WORKFLOW_START symbol.
packages/core/src/serialization.tsAdds Run reducer/reviver using __serializable marker + class registry.
packages/core/src/serialization-format.tsAdds RunRef marker/type + reviver mapping Run → RunRef for o11y.
packages/core/src/runtime/step-handler.tsRegisters runtime Run class in host class registry for Run reviver.
packages/core/src/runtime/start.tsDelegates start() to injected workflow implementation when in workflow VM.
packages/core/src/runtime/run.tsAdds Run.__serializable marker for serialization.
packages/core/src/private.tsExtends builtin step aliasing to accept fully-qualified builtins step IDs.
packages/core/e2e/e2e.test.tsAdds e2e tests validating workflow-context start() and recursion.
docs/proxy.tsFormatting-only string quote normalization.
docs/lib/ai-agent-detection.tsFormatting-only string quote normalization.
docs/content/docs/foundations/starting-workflows.mdxDocuments calling start() inside workflow functions and recursive patterns.
docs/content/docs/foundations/common-patterns.mdxUpdates patterns docs to use direct start() in workflows.
docs/content/docs/api-reference/workflow-api/start.mdxUpdates API reference to include workflow-context usage.
AGENTS.mdUpdates local e2e instructions to include WORKFLOW_PUBLIC_MANIFEST=1.
.changeset/start-in-workflow.mdDeclares patch release for workflow-context start() support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/workflow/src/api-workflow.ts Outdated
Comment threadpackages/core/src/workflow/run.ts Outdated
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/serialization-format.ts
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/next/src/builder-deferred.ts Outdated
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
… duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
…t all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
…d of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
… step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
…just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
@VaguelySerious

Copy link
Copy Markdown
Member

Fails nextjs-turbopack tests

argsOrOptions?: TArgs | StartOptions,
options?: StartOptions
) {
'use step';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice

Sign up for freeto 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.

4 participants

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

feat: allow start() to be called directly inside workflow functions - #1491

Merged
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start
Apr 17, 2026
Merged

feat: allow start() to be called directly inside workflow functions#1491
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start

Conversation

@pranaygp

@pranaygppranaygp commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Add "use step" to start() so it can be called directly from "use workflow" functions, without needing a wrapper step function.

This is a dramatically simplified replacement for the original PR approach. Instead of complex VM-side proxy classes and builtin step delegation, the entire implementation is:

  1. Add "use step" to start() in packages/core/src/runtime/start.ts
  2. Re-export start from @workflow/core/runtime/start in api-workflow.ts (instead of a throwing stub)

This works because:

Changes

  • packages/core/src/runtime/start.ts: Add "use step" directive
  • packages/workflow/src/api-workflow.ts: Re-export start from core instead of stub
  • workbench/example/workflows/99_e2e.ts: Add startFromWorkflow and fibonacciWorkflow e2e workflows
  • packages/core/e2e/e2e.test.ts: Add e2e tests
  • workbench/nextjs-turbopack/app/workflows/definitions.ts: Add default args

E2E Tests

  • startFromWorkflow: Parent spawns child via start(), child signals parent via hook, verifies bidirectional communication
  • fibonacciWorkflow: Recursive workflow composition — fib(6) = 8 via tree of child workflows using Promise.all([start(...), start(...)])

@vercel

vercelBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-botBot commented Mar 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5826c86

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

This PR includes changesets to release 17 packages
NameType
@workflow/coreMinor
workflowMinor
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-testingPatch
@workflow/aiMajor
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.042s (-2.3%)1.005s (~)0.963s101.00x
💻 LocalExpress0.043s (-3.6%)1.005s (~)0.963s101.01x
💻 LocalNext.js (Turbopack)0.050s1.005s0.955s101.18x
🐘 PostgresNext.js (Turbopack)0.057s1.009s0.953s101.35x
🐘 PostgresExpress0.060s (+3.8%)1.010s (~)0.950s101.43x
🐘 PostgresNitro0.087s (-9.0% 🟢)1.029s (-1.3%)0.942s102.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.255s (+1.5%)1.912s (-18.0% 🟢)1.657s101.00x
▲ VercelNitro0.413s (+0.9%)2.209s (-12.0% 🟢)1.795s101.62x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalExpress1.128s (~)2.006s (~)0.878s101.01x
🐘 PostgresNext.js (Turbopack)1.136s2.010s0.874s101.02x
💻 LocalNitro1.137s (~)2.006s (~)0.870s101.02x
🐘 PostgresExpress1.145s (~)2.009s (~)0.864s101.03x
🐘 PostgresNitro1.150s (+0.9%)2.009s (~)0.859s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.841s (-52.7% 🟢)3.610s (-38.9% 🟢)1.769s101.00x
▲ VercelNext.js (Turbopack)1.941s (-4.6%)3.671s (-4.2%)1.731s101.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.791s11.023s0.232s31.00x
🐘 PostgresNitro10.846s (~)11.019s (~)0.173s31.01x
🐘 PostgresExpress10.866s (-0.9%)11.018s (~)0.151s31.01x
🐘 PostgresNext.js (Turbopack)10.897s11.024s0.127s31.01x
💻 LocalExpress10.935s (~)11.024s (~)0.088s31.01x
💻 LocalNitro10.956s (~)11.024s (~)0.068s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.899s (-28.8% 🟢)18.812s (-25.1% 🟢)1.912s21.00x
▲ VercelNext.js (Turbopack)17.099s (-1.3%)18.986s (-2.1%)1.887s21.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.427s15.022s0.595s41.00x
🐘 PostgresNitro14.487s (-0.7%)15.027s (~)0.540s41.00x
🐘 PostgresExpress14.497s (-0.6%)15.021s (~)0.524s41.00x
💻 LocalNext.js (Turbopack)14.599s15.030s0.431s41.01x
💻 LocalExpress14.977s (~)15.280s (+1.7%)0.303s41.04x
💻 LocalNitro15.014s (~)15.531s (-3.1%)0.516s41.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro31.103s (-51.7% 🟢)33.088s (-50.3% 🟢)1.985s21.00x
▲ VercelNext.js (Turbopack)34.283s (-34.8% 🟢)36.218s (-33.7% 🟢)1.935s21.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.777s (-1.6%)14.021s (-3.9%)0.244s71.00x
🐘 PostgresNitro13.849s (-0.8%)14.023s (-2.0%)0.174s71.01x
🐘 PostgresNext.js (Turbopack)13.905s14.024s0.119s71.01x
💻 LocalNext.js (Turbopack)15.836s16.029s0.193s61.15x
💻 LocalExpress16.630s (~)17.030s (~)0.400s61.21x
💻 LocalNitro16.773s (~)17.031s (~)0.258s61.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)54.152s (-86.2% 🟢)55.639s (-85.9% 🟢)1.487s21.00x
▲ VercelNitro56.014s (-86.8% 🟢)57.859s (-86.4% 🟢)1.845s21.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.237s2.009s0.772s151.00x
🐘 PostgresNitro1.259s (-1.2%)2.011s (~)0.752s151.02x
🐘 PostgresExpress1.260s (~)2.008s (~)0.748s151.02x
💻 LocalNext.js (Turbopack)1.467s2.005s0.538s151.19x
💻 LocalExpress1.539s (+3.4%)2.006s (~)0.467s151.24x
💻 LocalNitro1.584s (-2.9%)2.006s (-3.3%)0.422s151.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.295s (-18.6% 🟢)3.768s (-12.8% 🟢)1.473s81.00x
▲ VercelNext.js (Turbopack)2.682s (-21.1% 🟢)4.363s (-11.5% 🟢)1.681s71.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.323s (-1.2%)3.010s (~)0.687s101.00x
🐘 PostgresExpress2.371s (~)3.008s (~)0.637s101.02x
🐘 PostgresNext.js (Turbopack)2.411s3.009s0.598s101.04x
💻 LocalNext.js (Turbopack)2.864s3.208s0.344s101.23x
💻 LocalExpress3.041s (+3.0%)3.885s (+12.5% 🔺)0.844s81.31x
💻 LocalNitro3.188s (+1.4%)3.886s (~)0.697s81.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.493s (-38.5% 🟢)4.128s (-30.3% 🟢)1.635s81.00x
▲ VercelNext.js (Turbopack)3.312s (-53.3% 🟢)4.688s (-47.4% 🟢)1.375s71.33x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.479s (~)4.010s (~)0.531s81.00x
🐘 PostgresExpress3.486s (~)4.011s (~)0.525s81.00x
🐘 PostgresNext.js (Turbopack)3.646s4.011s0.365s81.05x
💻 LocalNext.js (Turbopack)7.295s8.015s0.720s42.10x
💻 LocalExpress8.300s (~)9.021s (~)0.721s42.39x
💻 LocalNitro9.008s (+7.9% 🔺)9.520s (+5.5% 🔺)0.512s42.59x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.317s (-5.9% 🟢)5.227s (-5.5% 🟢)1.910s61.00x
▲ VercelNext.js (Turbopack)3.665s (-58.9% 🟢)5.170s (-52.8% 🟢)1.505s61.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s2.008s0.780s151.00x
🐘 PostgresNitro1.253s (~)2.008s (~)0.755s151.02x
🐘 PostgresExpress1.265s (+0.6%)2.007s (~)0.742s151.03x
💻 LocalNext.js (Turbopack)1.480s2.006s0.526s151.20x
💻 LocalExpress1.537s (-18.8% 🟢)2.006s (-15.1% 🟢)0.469s151.25x
💻 LocalNitro1.581s (-15.3% 🟢)2.007s (-14.3% 🟢)0.426s151.29x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.065s (-16.0% 🟢)3.741s (-10.3% 🟢)1.676s91.00x
▲ VercelNext.js (Turbopack)2.073s (-29.3% 🟢)3.679s (-20.7% 🟢)1.606s91.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (-0.5%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.371s (+1.3%)3.009s (~)0.638s101.02x
🐘 PostgresNext.js (Turbopack)2.395s3.008s0.613s101.03x
💻 LocalNext.js (Turbopack)2.899s3.341s0.443s91.25x
💻 LocalExpress3.031s (-3.2%)3.883s (+3.2%)0.852s81.30x
💻 LocalNitro3.064s (~)3.885s (~)0.821s81.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.511s (-22.3% 🟢)3.996s (-21.3% 🟢)1.485s81.00x
▲ VercelNext.js (Turbopack)2.943s (-6.4% 🟢)4.388s (-3.0%)1.445s81.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.478s (-0.6%)4.011s (~)0.533s81.00x
🐘 PostgresNitro3.481s (~)4.011s (~)0.530s81.00x
🐘 PostgresNext.js (Turbopack)3.657s4.012s0.355s81.05x
💻 LocalNext.js (Turbopack)8.041s8.767s0.726s42.31x
💻 LocalExpress8.919s (+1.3%)9.271s (~)0.352s42.56x
💻 LocalNitro9.163s (~)10.022s (~)0.859s32.63x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.059s (-39.9% 🟢)4.889s (-28.3% 🟢)1.830s71.00x
▲ VercelNext.js (Turbopack)3.409s (-49.6% 🟢)5.067s (-40.7% 🟢)1.659s61.11x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.779s1.023s0.244s591.00x
🐘 PostgresExpress0.799s (-4.7%)1.006s (-1.7%)0.207s601.03x
💻 LocalNext.js (Turbopack)0.829s1.004s0.176s601.06x
🐘 PostgresNitro0.834s (+1.6%)1.006s (~)0.172s601.07x
💻 LocalExpress0.999s (+1.5%)1.369s (+27.2% 🔺)0.370s441.28x
💻 LocalNitro1.016s (+3.6%)1.611s (+47.3% 🔺)0.595s381.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro9.843s (-55.4% 🟢)12.413s (-48.3% 🟢)2.570s51.00x
▲ VercelNext.js (Turbopack)12.026s (-17.1% 🟢)13.929s (-13.4% 🟢)1.903s51.22x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.884s (-4.7%)2.075s (-8.1% 🟢)0.192s441.00x
🐘 PostgresNext.js (Turbopack)1.940s2.202s0.263s411.03x
🐘 PostgresNitro2.009s (+4.2%)2.580s (+22.8% 🔺)0.571s351.07x
💻 LocalNext.js (Turbopack)2.607s3.008s0.401s301.38x
💻 LocalNitro3.062s (+0.9%)3.842s (+2.2%)0.780s241.63x
💻 LocalExpress3.083s (+2.2%)3.843s (+7.2% 🔺)0.760s241.64x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro29.627s (-25.0% 🟢)31.818s (-23.0% 🟢)2.191s31.00x
▲ VercelNext.js (Turbopack)30.003s (-39.8% 🟢)31.773s (-38.6% 🟢)1.770s31.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.838s (-3.8%)4.042s (-7.5% 🟢)0.204s301.00x
🐘 PostgresNext.js (Turbopack)3.845s4.043s0.198s301.00x
🐘 PostgresNitro4.114s (~)4.742s (+3.0%)0.628s261.07x
💻 LocalNext.js (Turbopack)8.439s9.017s0.578s142.20x
💻 LocalExpress9.229s (~)9.941s (-0.8%)0.712s132.40x
💻 LocalNitro9.362s (+0.7%)10.103s (+0.8%)0.741s122.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro79.683s (-17.8% 🟢)81.801s (-16.9% 🟢)2.118s21.00x
▲ VercelNext.js (Turbopack)83.234s (-22.3% 🟢)84.822s (-22.1% 🟢)1.588s21.04x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.254s1.007s0.753s601.00x
🐘 PostgresExpress0.286s (+1.3%)1.007s (~)0.720s601.13x
🐘 PostgresNitro0.292s (+3.2%)1.008s (~)0.715s601.15x
💻 LocalExpress0.591s (+5.5% 🔺)1.005s (~)0.413s602.33x
💻 LocalNitro0.604s (~)1.005s (-1.7%)0.401s602.38x
💻 LocalNext.js (Turbopack)0.636s1.096s0.460s552.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.935s (-4.3%)3.931s (+3.6%)1.996s161.00x
▲ VercelNitro2.045s (+23.1% 🔺)3.924s (+17.1% 🔺)1.879s161.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.475s1.006s0.531s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.006s (~)0.518s901.03x
🐘 PostgresNitro0.498s (~)1.007s (~)0.508s901.05x
💻 LocalNext.js (Turbopack)2.523s3.009s0.485s305.31x
💻 LocalExpress2.549s (+1.4%)3.010s (~)0.461s305.36x
💻 LocalNitro2.595s (+2.2%)3.010s (~)0.415s305.46x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.216s (~)5.023s (+4.2%)1.806s181.00x
▲ VercelNext.js (Turbopack)3.287s (-7.0% 🟢)5.034s (-3.1%)1.746s181.02x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.774s (-5.4% 🟢)1.007s (-1.0%)0.233s1201.00x
🐘 PostgresNext.js (Turbopack)0.777s1.006s0.230s1201.00x
🐘 PostgresNitro0.794s (~)1.008s (~)0.214s1201.03x
💻 LocalNext.js (Turbopack)10.324s10.943s0.619s1213.34x
💻 LocalNitro11.556s (+3.3%)12.130s (+4.0%)0.574s1014.93x
💻 LocalExpress11.688s (+4.4%)12.329s (+3.3%)0.641s1015.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.003s (-22.3% 🟢)7.762s (-17.4% 🟢)1.759s161.00x
▲ VercelNext.js (Turbopack)13.494s (+30.7% 🔺)15.200s (+23.7% 🔺)1.707s112.25x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.179s1.003s0.012s1.018s0.839s101.00x
💻 LocalExpress0.203s (+1.8%)1.004s (~)0.012s (-3.3%)1.018s (~)0.815s101.13x
🐘 PostgresExpress0.203s (-0.9%)0.992s (-0.7%)0.002s (~)1.010s (~)0.806s101.14x
💻 LocalNitro0.204s (-4.5%)1.004s (~)0.012s (-4.8%)1.018s (~)0.814s101.14x
🐘 PostgresNext.js (Turbopack)0.207s1.001s0.002s1.011s0.804s101.16x
🐘 PostgresNitro0.210s (+2.5%)0.995s (~)0.001s (-20.0% 🟢)1.009s (~)0.799s101.18x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.595s (-76.7% 🟢)3.061s (-64.6% 🟢)0.709s (+12.1% 🔺)4.212s (-57.0% 🟢)2.617s101.00x
▲ VercelNitro1.672s (-56.4% 🟢)3.060s (-42.0% 🟢)0.780s (+5.1% 🔺)4.359s (-32.8% 🟢)2.687s101.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.605s (-3.9%)1.005s (~)0.004s (+2.2%)1.022s (~)0.417s591.00x
🐘 PostgresNext.js (Turbopack)0.607s1.009s0.006s1.024s0.417s591.00x
🐘 PostgresNitro0.627s (~)1.003s (~)0.004s (-4.5%)1.022s (~)0.395s591.04x
💻 LocalNext.js (Turbopack)0.756s1.011s0.020s1.126s0.370s541.25x
💻 LocalExpress0.794s (+4.8%)1.012s (-1.6%)0.015s (+54.8% 🔺)1.029s (-1.1%)0.235s591.31x
💻 LocalNitro0.968s (+15.5% 🔺)1.012s (~)0.010s (+6.7% 🔺)1.228s (+10.0% 🔺)0.260s491.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)4.375s (-74.1% 🟢)6.053s (-66.8% 🟢)0.188s (-11.1% 🟢)6.702s (-64.6% 🟢)2.327s91.00x
▲ VercelNitro4.545s (-84.6% 🟢)6.277s (-79.6% 🟢)0.225s (+101.1% 🔺)6.958s (-78.1% 🟢)2.413s91.04x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.937s1.203s0.000s1.216s0.279s501.00x
🐘 PostgresNitro0.948s (-2.1%)1.167s (-6.4% 🟢)0.000s (-7.7% 🟢)1.181s (-6.1% 🟢)0.233s521.01x
🐘 PostgresExpress0.953s (-0.9%)1.269s (-0.7%)0.000s (-51.1% 🟢)1.294s (-0.9%)0.341s471.02x
💻 LocalExpress1.252s (+2.3%)2.024s (~)0.000s (~)2.026s (~)0.773s301.34x
💻 LocalNext.js (Turbopack)1.281s2.021s0.001s2.024s0.742s301.37x
💻 LocalNitro1.420s (+16.1% 🔺)2.021s (~)0.000s (+221.4% 🔺)2.202s (+8.9% 🔺)0.782s281.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-1.5%)4.107s (-6.5% 🟢)0.000s (-100.0% 🟢)4.696s (-2.4%)1.691s131.00x
▲ VercelNext.js (Turbopack)3.462s (-66.0% 🟢)5.066s (-56.0% 🟢)0.000s (NaN%)5.508s (-54.3% 🟢)2.045s111.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.669s (-5.8% 🟢)2.058s (-5.5% 🟢)0.000s (+Infinity% 🔺)2.082s (-5.3% 🟢)0.413s291.00x
🐘 PostgresNext.js (Turbopack)1.790s2.072s0.000s2.083s0.293s291.07x
🐘 PostgresNitro1.825s (+1.9%)2.258s (+5.4% 🔺)0.000s (+107.4% 🔺)2.269s (+4.3%)0.444s271.09x
💻 LocalExpress3.530s (+1.8%)4.034s (~)0.000s (-41.7% 🟢)4.038s (~)0.508s152.12x
💻 LocalNitro3.649s (+7.7% 🔺)4.099s (+1.6%)0.001s (+62.5% 🔺)4.102s (+1.6%)0.453s152.19x
💻 LocalNext.js (Turbopack)3.847s4.389s0.000s4.395s0.547s142.31x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.955s (-3.4%)5.118s (-4.8%)0.000s (-100.0% 🟢)5.592s (-3.5%)1.637s111.00x
▲ VercelNext.js (Turbopack)4.537s (-19.2% 🟢)5.937s (-15.0% 🟢)0.000s (-100.0% 🟢)6.464s (-14.3% 🟢)1.927s101.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)17/21
🐘 PostgresNext.js (Turbopack)9/21
▲ VercelNitro16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres16/21
Nitro🐘 Postgres16/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)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9450671012
✅ 💻 Local Development10180861104
✅ 📦 Local Production10180861104
✅ 🐘 Local Postgres10180861104
✅ 🪟 Windows920092
✅ 📋 Other258018276
Total434903434692

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8507
✅ example8507
✅ express8507
✅ fastify8507
✅ hono8507
✅ nextjs-turbopack9002
✅ nextjs-webpack9002
✅ nitro8507
✅ nuxt8507
✅ sveltekit8507
✅ vite8507
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable8606
✅ e2e-local-postgres-nest-stable8606
✅ e2e-local-prod-nest-stable8606

📋 View full workflow run

@pranaygppranaygp changed the title Revert "Revert \"Add support for calling start() inside workflow functions\""Revert Revert "Add support for calling start() inside workflow functions"Mar 23, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-enables calling start() from workflow/api directly inside "use workflow" functions by routing the call through internal built-in steps, with Run objects working in workflow VM context (step-backed getters/methods) and improved observability UI hydration/rendering for run references.

Changes:

  • Add workflow-context start() delegation via WORKFLOW_START, backed by a built-in start step and a workflow-VM WorkflowRun proxy class.
  • Extend serialization/hydration to support Run/WorkflowRun across boundaries and render Run references as clickable UI elements.
  • Update builders, plugin behavior/docs, and add unit + e2e coverage for startFromWorkflow and recursive fibonacciWorkflow.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
workbench/nextjs-turbopack/app/workflows/definitions.tsAdds default args for new e2e workflows.
workbench/example/workflows/99_e2e.tsAdds startFromWorkflow and recursive fibonacciWorkflow workflows for e2e.
skills/workflow/SKILL.mdUpdates skill guidance to reflect start() support inside workflows.
packages/workflow/src/runtime.tsRe-exports getRun and start from core runtime for rewritten step imports.
packages/workflow/src/internal/builtins.tsAdds built-in start step and Run.* step-backed static methods.
packages/workflow/src/api-workflow.tsRoutes start() in workflow bundle via WORKFLOW_START injection.
packages/web/app/components/run-detail-view.tsxAdds run-ref click navigation wiring.
packages/web-shared/src/lib/hydration.tsExposes RunRef helpers for UI hydration.
packages/web-shared/src/components/workflow-traces/trace-span-construction.tsImproves span naming fallback behavior.
packages/web-shared/src/components/workflow-trace-view.tsxPlumbs onRunClick and resets selection on run change.
packages/web-shared/src/components/ui/data-inspector.tsxAdds RunRef rendering + “opaque ref” collapsing + click contexts.
packages/web-shared/src/components/sidebar/entity-detail-panel.tsxPasses onRunClick into detail panel rendering.
packages/web-shared/src/components/sidebar/attribute-panel.tsxAdds Run click context and improves stepName display fallback.
packages/web-shared/src/components/run-trace-view.tsxThreads onRunClick through trace view components.
packages/swc-plugin-workflow/transform/src/lib.rsRemoves __builtin* step-id special casing to use normal IDs.
packages/swc-plugin-workflow/spec.mdUpdates spec examples to include builtins module-specifier IDs.
packages/next/src/builder-deferred.tsRewrites bare specifiers (incl. dynamic import) and ensures builtins are included in step files.
packages/core/src/workflow/start.tsImplements workflow-VM createStart() using internal start built-in step.
packages/core/src/workflow/start.test.tsAdds unit tests for workflow-context start() behavior and option validation.
packages/core/src/workflow/run.tsAdds workflow-VM WorkflowRun proxy delegating to built-in Run steps.
packages/core/src/workflow/run.test.tsAdds unit tests asserting step delegation naming/arguments.
packages/core/src/workflow/builtin-step-id.tsAdds helper to construct built-in step IDs.
packages/core/src/workflow.tsInjects WORKFLOW_START, registers WorkflowRun, and updates response builtins to qualified IDs.
packages/core/src/symbols.tsAdds WORKFLOW_START symbol.
packages/core/src/serialization.tsAdds Run reducer/reviver using __serializable marker + class registry.
packages/core/src/serialization-format.tsAdds RunRef marker/type + reviver mapping Run → RunRef for o11y.
packages/core/src/runtime/step-handler.tsRegisters runtime Run class in host class registry for Run reviver.
packages/core/src/runtime/start.tsDelegates start() to injected workflow implementation when in workflow VM.
packages/core/src/runtime/run.tsAdds Run.__serializable marker for serialization.
packages/core/src/private.tsExtends builtin step aliasing to accept fully-qualified builtins step IDs.
packages/core/e2e/e2e.test.tsAdds e2e tests validating workflow-context start() and recursion.
docs/proxy.tsFormatting-only string quote normalization.
docs/lib/ai-agent-detection.tsFormatting-only string quote normalization.
docs/content/docs/foundations/starting-workflows.mdxDocuments calling start() inside workflow functions and recursive patterns.
docs/content/docs/foundations/common-patterns.mdxUpdates patterns docs to use direct start() in workflows.
docs/content/docs/api-reference/workflow-api/start.mdxUpdates API reference to include workflow-context usage.
AGENTS.mdUpdates local e2e instructions to include WORKFLOW_PUBLIC_MANIFEST=1.
.changeset/start-in-workflow.mdDeclares patch release for workflow-context start() support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/workflow/src/api-workflow.ts Outdated
Comment threadpackages/core/src/workflow/run.ts Outdated
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/serialization-format.ts
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/next/src/builder-deferred.ts Outdated
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
… duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
…t all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
…d of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
… step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
…just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
@VaguelySerious

Copy link
Copy Markdown
Member

Fails nextjs-turbopack tests

argsOrOptions?: TArgs | StartOptions,
options?: StartOptions
) {
'use step';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice

Sign up for freeto 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.

4 participants

@pranaygp@VaguelySerious@TooTallNate
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: allow `start()` to be called directly inside workflow functions by pranaygp · Pull Request #1491 · vercel/workflow · GitHub
Skip to content

feat: allow start() to be called directly inside workflow functions - #1491

Merged
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start
Apr 17, 2026
Merged

feat: allow start() to be called directly inside workflow functions#1491
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start

Conversation

@pranaygp

@pranaygppranaygp commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Add "use step" to start() so it can be called directly from "use workflow" functions, without needing a wrapper step function.

This is a dramatically simplified replacement for the original PR approach. Instead of complex VM-side proxy classes and builtin step delegation, the entire implementation is:

  1. Add "use step" to start() in packages/core/src/runtime/start.ts
  2. Re-export start from @workflow/core/runtime/start in api-workflow.ts (instead of a throwing stub)

This works because:

Changes

  • packages/core/src/runtime/start.ts: Add "use step" directive
  • packages/workflow/src/api-workflow.ts: Re-export start from core instead of stub
  • workbench/example/workflows/99_e2e.ts: Add startFromWorkflow and fibonacciWorkflow e2e workflows
  • packages/core/e2e/e2e.test.ts: Add e2e tests
  • workbench/nextjs-turbopack/app/workflows/definitions.ts: Add default args

E2E Tests

  • startFromWorkflow: Parent spawns child via start(), child signals parent via hook, verifies bidirectional communication
  • fibonacciWorkflow: Recursive workflow composition — fib(6) = 8 via tree of child workflows using Promise.all([start(...), start(...)])

@vercel

vercelBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-botBot commented Mar 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5826c86

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

This PR includes changesets to release 17 packages
NameType
@workflow/coreMinor
workflowMinor
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-testingPatch
@workflow/aiMajor
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.042s (-2.3%)1.005s (~)0.963s101.00x
💻 LocalExpress0.043s (-3.6%)1.005s (~)0.963s101.01x
💻 LocalNext.js (Turbopack)0.050s1.005s0.955s101.18x
🐘 PostgresNext.js (Turbopack)0.057s1.009s0.953s101.35x
🐘 PostgresExpress0.060s (+3.8%)1.010s (~)0.950s101.43x
🐘 PostgresNitro0.087s (-9.0% 🟢)1.029s (-1.3%)0.942s102.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.255s (+1.5%)1.912s (-18.0% 🟢)1.657s101.00x
▲ VercelNitro0.413s (+0.9%)2.209s (-12.0% 🟢)1.795s101.62x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalExpress1.128s (~)2.006s (~)0.878s101.01x
🐘 PostgresNext.js (Turbopack)1.136s2.010s0.874s101.02x
💻 LocalNitro1.137s (~)2.006s (~)0.870s101.02x
🐘 PostgresExpress1.145s (~)2.009s (~)0.864s101.03x
🐘 PostgresNitro1.150s (+0.9%)2.009s (~)0.859s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.841s (-52.7% 🟢)3.610s (-38.9% 🟢)1.769s101.00x
▲ VercelNext.js (Turbopack)1.941s (-4.6%)3.671s (-4.2%)1.731s101.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.791s11.023s0.232s31.00x
🐘 PostgresNitro10.846s (~)11.019s (~)0.173s31.01x
🐘 PostgresExpress10.866s (-0.9%)11.018s (~)0.151s31.01x
🐘 PostgresNext.js (Turbopack)10.897s11.024s0.127s31.01x
💻 LocalExpress10.935s (~)11.024s (~)0.088s31.01x
💻 LocalNitro10.956s (~)11.024s (~)0.068s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.899s (-28.8% 🟢)18.812s (-25.1% 🟢)1.912s21.00x
▲ VercelNext.js (Turbopack)17.099s (-1.3%)18.986s (-2.1%)1.887s21.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.427s15.022s0.595s41.00x
🐘 PostgresNitro14.487s (-0.7%)15.027s (~)0.540s41.00x
🐘 PostgresExpress14.497s (-0.6%)15.021s (~)0.524s41.00x
💻 LocalNext.js (Turbopack)14.599s15.030s0.431s41.01x
💻 LocalExpress14.977s (~)15.280s (+1.7%)0.303s41.04x
💻 LocalNitro15.014s (~)15.531s (-3.1%)0.516s41.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro31.103s (-51.7% 🟢)33.088s (-50.3% 🟢)1.985s21.00x
▲ VercelNext.js (Turbopack)34.283s (-34.8% 🟢)36.218s (-33.7% 🟢)1.935s21.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.777s (-1.6%)14.021s (-3.9%)0.244s71.00x
🐘 PostgresNitro13.849s (-0.8%)14.023s (-2.0%)0.174s71.01x
🐘 PostgresNext.js (Turbopack)13.905s14.024s0.119s71.01x
💻 LocalNext.js (Turbopack)15.836s16.029s0.193s61.15x
💻 LocalExpress16.630s (~)17.030s (~)0.400s61.21x
💻 LocalNitro16.773s (~)17.031s (~)0.258s61.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)54.152s (-86.2% 🟢)55.639s (-85.9% 🟢)1.487s21.00x
▲ VercelNitro56.014s (-86.8% 🟢)57.859s (-86.4% 🟢)1.845s21.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.237s2.009s0.772s151.00x
🐘 PostgresNitro1.259s (-1.2%)2.011s (~)0.752s151.02x
🐘 PostgresExpress1.260s (~)2.008s (~)0.748s151.02x
💻 LocalNext.js (Turbopack)1.467s2.005s0.538s151.19x
💻 LocalExpress1.539s (+3.4%)2.006s (~)0.467s151.24x
💻 LocalNitro1.584s (-2.9%)2.006s (-3.3%)0.422s151.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.295s (-18.6% 🟢)3.768s (-12.8% 🟢)1.473s81.00x
▲ VercelNext.js (Turbopack)2.682s (-21.1% 🟢)4.363s (-11.5% 🟢)1.681s71.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.323s (-1.2%)3.010s (~)0.687s101.00x
🐘 PostgresExpress2.371s (~)3.008s (~)0.637s101.02x
🐘 PostgresNext.js (Turbopack)2.411s3.009s0.598s101.04x
💻 LocalNext.js (Turbopack)2.864s3.208s0.344s101.23x
💻 LocalExpress3.041s (+3.0%)3.885s (+12.5% 🔺)0.844s81.31x
💻 LocalNitro3.188s (+1.4%)3.886s (~)0.697s81.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.493s (-38.5% 🟢)4.128s (-30.3% 🟢)1.635s81.00x
▲ VercelNext.js (Turbopack)3.312s (-53.3% 🟢)4.688s (-47.4% 🟢)1.375s71.33x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.479s (~)4.010s (~)0.531s81.00x
🐘 PostgresExpress3.486s (~)4.011s (~)0.525s81.00x
🐘 PostgresNext.js (Turbopack)3.646s4.011s0.365s81.05x
💻 LocalNext.js (Turbopack)7.295s8.015s0.720s42.10x
💻 LocalExpress8.300s (~)9.021s (~)0.721s42.39x
💻 LocalNitro9.008s (+7.9% 🔺)9.520s (+5.5% 🔺)0.512s42.59x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.317s (-5.9% 🟢)5.227s (-5.5% 🟢)1.910s61.00x
▲ VercelNext.js (Turbopack)3.665s (-58.9% 🟢)5.170s (-52.8% 🟢)1.505s61.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s2.008s0.780s151.00x
🐘 PostgresNitro1.253s (~)2.008s (~)0.755s151.02x
🐘 PostgresExpress1.265s (+0.6%)2.007s (~)0.742s151.03x
💻 LocalNext.js (Turbopack)1.480s2.006s0.526s151.20x
💻 LocalExpress1.537s (-18.8% 🟢)2.006s (-15.1% 🟢)0.469s151.25x
💻 LocalNitro1.581s (-15.3% 🟢)2.007s (-14.3% 🟢)0.426s151.29x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.065s (-16.0% 🟢)3.741s (-10.3% 🟢)1.676s91.00x
▲ VercelNext.js (Turbopack)2.073s (-29.3% 🟢)3.679s (-20.7% 🟢)1.606s91.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (-0.5%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.371s (+1.3%)3.009s (~)0.638s101.02x
🐘 PostgresNext.js (Turbopack)2.395s3.008s0.613s101.03x
💻 LocalNext.js (Turbopack)2.899s3.341s0.443s91.25x
💻 LocalExpress3.031s (-3.2%)3.883s (+3.2%)0.852s81.30x
💻 LocalNitro3.064s (~)3.885s (~)0.821s81.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.511s (-22.3% 🟢)3.996s (-21.3% 🟢)1.485s81.00x
▲ VercelNext.js (Turbopack)2.943s (-6.4% 🟢)4.388s (-3.0%)1.445s81.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.478s (-0.6%)4.011s (~)0.533s81.00x
🐘 PostgresNitro3.481s (~)4.011s (~)0.530s81.00x
🐘 PostgresNext.js (Turbopack)3.657s4.012s0.355s81.05x
💻 LocalNext.js (Turbopack)8.041s8.767s0.726s42.31x
💻 LocalExpress8.919s (+1.3%)9.271s (~)0.352s42.56x
💻 LocalNitro9.163s (~)10.022s (~)0.859s32.63x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.059s (-39.9% 🟢)4.889s (-28.3% 🟢)1.830s71.00x
▲ VercelNext.js (Turbopack)3.409s (-49.6% 🟢)5.067s (-40.7% 🟢)1.659s61.11x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.779s1.023s0.244s591.00x
🐘 PostgresExpress0.799s (-4.7%)1.006s (-1.7%)0.207s601.03x
💻 LocalNext.js (Turbopack)0.829s1.004s0.176s601.06x
🐘 PostgresNitro0.834s (+1.6%)1.006s (~)0.172s601.07x
💻 LocalExpress0.999s (+1.5%)1.369s (+27.2% 🔺)0.370s441.28x
💻 LocalNitro1.016s (+3.6%)1.611s (+47.3% 🔺)0.595s381.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro9.843s (-55.4% 🟢)12.413s (-48.3% 🟢)2.570s51.00x
▲ VercelNext.js (Turbopack)12.026s (-17.1% 🟢)13.929s (-13.4% 🟢)1.903s51.22x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.884s (-4.7%)2.075s (-8.1% 🟢)0.192s441.00x
🐘 PostgresNext.js (Turbopack)1.940s2.202s0.263s411.03x
🐘 PostgresNitro2.009s (+4.2%)2.580s (+22.8% 🔺)0.571s351.07x
💻 LocalNext.js (Turbopack)2.607s3.008s0.401s301.38x
💻 LocalNitro3.062s (+0.9%)3.842s (+2.2%)0.780s241.63x
💻 LocalExpress3.083s (+2.2%)3.843s (+7.2% 🔺)0.760s241.64x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro29.627s (-25.0% 🟢)31.818s (-23.0% 🟢)2.191s31.00x
▲ VercelNext.js (Turbopack)30.003s (-39.8% 🟢)31.773s (-38.6% 🟢)1.770s31.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.838s (-3.8%)4.042s (-7.5% 🟢)0.204s301.00x
🐘 PostgresNext.js (Turbopack)3.845s4.043s0.198s301.00x
🐘 PostgresNitro4.114s (~)4.742s (+3.0%)0.628s261.07x
💻 LocalNext.js (Turbopack)8.439s9.017s0.578s142.20x
💻 LocalExpress9.229s (~)9.941s (-0.8%)0.712s132.40x
💻 LocalNitro9.362s (+0.7%)10.103s (+0.8%)0.741s122.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro79.683s (-17.8% 🟢)81.801s (-16.9% 🟢)2.118s21.00x
▲ VercelNext.js (Turbopack)83.234s (-22.3% 🟢)84.822s (-22.1% 🟢)1.588s21.04x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.254s1.007s0.753s601.00x
🐘 PostgresExpress0.286s (+1.3%)1.007s (~)0.720s601.13x
🐘 PostgresNitro0.292s (+3.2%)1.008s (~)0.715s601.15x
💻 LocalExpress0.591s (+5.5% 🔺)1.005s (~)0.413s602.33x
💻 LocalNitro0.604s (~)1.005s (-1.7%)0.401s602.38x
💻 LocalNext.js (Turbopack)0.636s1.096s0.460s552.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.935s (-4.3%)3.931s (+3.6%)1.996s161.00x
▲ VercelNitro2.045s (+23.1% 🔺)3.924s (+17.1% 🔺)1.879s161.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.475s1.006s0.531s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.006s (~)0.518s901.03x
🐘 PostgresNitro0.498s (~)1.007s (~)0.508s901.05x
💻 LocalNext.js (Turbopack)2.523s3.009s0.485s305.31x
💻 LocalExpress2.549s (+1.4%)3.010s (~)0.461s305.36x
💻 LocalNitro2.595s (+2.2%)3.010s (~)0.415s305.46x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.216s (~)5.023s (+4.2%)1.806s181.00x
▲ VercelNext.js (Turbopack)3.287s (-7.0% 🟢)5.034s (-3.1%)1.746s181.02x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.774s (-5.4% 🟢)1.007s (-1.0%)0.233s1201.00x
🐘 PostgresNext.js (Turbopack)0.777s1.006s0.230s1201.00x
🐘 PostgresNitro0.794s (~)1.008s (~)0.214s1201.03x
💻 LocalNext.js (Turbopack)10.324s10.943s0.619s1213.34x
💻 LocalNitro11.556s (+3.3%)12.130s (+4.0%)0.574s1014.93x
💻 LocalExpress11.688s (+4.4%)12.329s (+3.3%)0.641s1015.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.003s (-22.3% 🟢)7.762s (-17.4% 🟢)1.759s161.00x
▲ VercelNext.js (Turbopack)13.494s (+30.7% 🔺)15.200s (+23.7% 🔺)1.707s112.25x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.179s1.003s0.012s1.018s0.839s101.00x
💻 LocalExpress0.203s (+1.8%)1.004s (~)0.012s (-3.3%)1.018s (~)0.815s101.13x
🐘 PostgresExpress0.203s (-0.9%)0.992s (-0.7%)0.002s (~)1.010s (~)0.806s101.14x
💻 LocalNitro0.204s (-4.5%)1.004s (~)0.012s (-4.8%)1.018s (~)0.814s101.14x
🐘 PostgresNext.js (Turbopack)0.207s1.001s0.002s1.011s0.804s101.16x
🐘 PostgresNitro0.210s (+2.5%)0.995s (~)0.001s (-20.0% 🟢)1.009s (~)0.799s101.18x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.595s (-76.7% 🟢)3.061s (-64.6% 🟢)0.709s (+12.1% 🔺)4.212s (-57.0% 🟢)2.617s101.00x
▲ VercelNitro1.672s (-56.4% 🟢)3.060s (-42.0% 🟢)0.780s (+5.1% 🔺)4.359s (-32.8% 🟢)2.687s101.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.605s (-3.9%)1.005s (~)0.004s (+2.2%)1.022s (~)0.417s591.00x
🐘 PostgresNext.js (Turbopack)0.607s1.009s0.006s1.024s0.417s591.00x
🐘 PostgresNitro0.627s (~)1.003s (~)0.004s (-4.5%)1.022s (~)0.395s591.04x
💻 LocalNext.js (Turbopack)0.756s1.011s0.020s1.126s0.370s541.25x
💻 LocalExpress0.794s (+4.8%)1.012s (-1.6%)0.015s (+54.8% 🔺)1.029s (-1.1%)0.235s591.31x
💻 LocalNitro0.968s (+15.5% 🔺)1.012s (~)0.010s (+6.7% 🔺)1.228s (+10.0% 🔺)0.260s491.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)4.375s (-74.1% 🟢)6.053s (-66.8% 🟢)0.188s (-11.1% 🟢)6.702s (-64.6% 🟢)2.327s91.00x
▲ VercelNitro4.545s (-84.6% 🟢)6.277s (-79.6% 🟢)0.225s (+101.1% 🔺)6.958s (-78.1% 🟢)2.413s91.04x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.937s1.203s0.000s1.216s0.279s501.00x
🐘 PostgresNitro0.948s (-2.1%)1.167s (-6.4% 🟢)0.000s (-7.7% 🟢)1.181s (-6.1% 🟢)0.233s521.01x
🐘 PostgresExpress0.953s (-0.9%)1.269s (-0.7%)0.000s (-51.1% 🟢)1.294s (-0.9%)0.341s471.02x
💻 LocalExpress1.252s (+2.3%)2.024s (~)0.000s (~)2.026s (~)0.773s301.34x
💻 LocalNext.js (Turbopack)1.281s2.021s0.001s2.024s0.742s301.37x
💻 LocalNitro1.420s (+16.1% 🔺)2.021s (~)0.000s (+221.4% 🔺)2.202s (+8.9% 🔺)0.782s281.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-1.5%)4.107s (-6.5% 🟢)0.000s (-100.0% 🟢)4.696s (-2.4%)1.691s131.00x
▲ VercelNext.js (Turbopack)3.462s (-66.0% 🟢)5.066s (-56.0% 🟢)0.000s (NaN%)5.508s (-54.3% 🟢)2.045s111.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.669s (-5.8% 🟢)2.058s (-5.5% 🟢)0.000s (+Infinity% 🔺)2.082s (-5.3% 🟢)0.413s291.00x
🐘 PostgresNext.js (Turbopack)1.790s2.072s0.000s2.083s0.293s291.07x
🐘 PostgresNitro1.825s (+1.9%)2.258s (+5.4% 🔺)0.000s (+107.4% 🔺)2.269s (+4.3%)0.444s271.09x
💻 LocalExpress3.530s (+1.8%)4.034s (~)0.000s (-41.7% 🟢)4.038s (~)0.508s152.12x
💻 LocalNitro3.649s (+7.7% 🔺)4.099s (+1.6%)0.001s (+62.5% 🔺)4.102s (+1.6%)0.453s152.19x
💻 LocalNext.js (Turbopack)3.847s4.389s0.000s4.395s0.547s142.31x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.955s (-3.4%)5.118s (-4.8%)0.000s (-100.0% 🟢)5.592s (-3.5%)1.637s111.00x
▲ VercelNext.js (Turbopack)4.537s (-19.2% 🟢)5.937s (-15.0% 🟢)0.000s (-100.0% 🟢)6.464s (-14.3% 🟢)1.927s101.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)17/21
🐘 PostgresNext.js (Turbopack)9/21
▲ VercelNitro16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres16/21
Nitro🐘 Postgres16/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)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9450671012
✅ 💻 Local Development10180861104
✅ 📦 Local Production10180861104
✅ 🐘 Local Postgres10180861104
✅ 🪟 Windows920092
✅ 📋 Other258018276
Total434903434692

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8507
✅ example8507
✅ express8507
✅ fastify8507
✅ hono8507
✅ nextjs-turbopack9002
✅ nextjs-webpack9002
✅ nitro8507
✅ nuxt8507
✅ sveltekit8507
✅ vite8507
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable8606
✅ e2e-local-postgres-nest-stable8606
✅ e2e-local-prod-nest-stable8606

📋 View full workflow run

@pranaygppranaygp changed the title Revert "Revert \"Add support for calling start() inside workflow functions\""Revert Revert "Add support for calling start() inside workflow functions"Mar 23, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-enables calling start() from workflow/api directly inside "use workflow" functions by routing the call through internal built-in steps, with Run objects working in workflow VM context (step-backed getters/methods) and improved observability UI hydration/rendering for run references.

Changes:

  • Add workflow-context start() delegation via WORKFLOW_START, backed by a built-in start step and a workflow-VM WorkflowRun proxy class.
  • Extend serialization/hydration to support Run/WorkflowRun across boundaries and render Run references as clickable UI elements.
  • Update builders, plugin behavior/docs, and add unit + e2e coverage for startFromWorkflow and recursive fibonacciWorkflow.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
workbench/nextjs-turbopack/app/workflows/definitions.tsAdds default args for new e2e workflows.
workbench/example/workflows/99_e2e.tsAdds startFromWorkflow and recursive fibonacciWorkflow workflows for e2e.
skills/workflow/SKILL.mdUpdates skill guidance to reflect start() support inside workflows.
packages/workflow/src/runtime.tsRe-exports getRun and start from core runtime for rewritten step imports.
packages/workflow/src/internal/builtins.tsAdds built-in start step and Run.* step-backed static methods.
packages/workflow/src/api-workflow.tsRoutes start() in workflow bundle via WORKFLOW_START injection.
packages/web/app/components/run-detail-view.tsxAdds run-ref click navigation wiring.
packages/web-shared/src/lib/hydration.tsExposes RunRef helpers for UI hydration.
packages/web-shared/src/components/workflow-traces/trace-span-construction.tsImproves span naming fallback behavior.
packages/web-shared/src/components/workflow-trace-view.tsxPlumbs onRunClick and resets selection on run change.
packages/web-shared/src/components/ui/data-inspector.tsxAdds RunRef rendering + “opaque ref” collapsing + click contexts.
packages/web-shared/src/components/sidebar/entity-detail-panel.tsxPasses onRunClick into detail panel rendering.
packages/web-shared/src/components/sidebar/attribute-panel.tsxAdds Run click context and improves stepName display fallback.
packages/web-shared/src/components/run-trace-view.tsxThreads onRunClick through trace view components.
packages/swc-plugin-workflow/transform/src/lib.rsRemoves __builtin* step-id special casing to use normal IDs.
packages/swc-plugin-workflow/spec.mdUpdates spec examples to include builtins module-specifier IDs.
packages/next/src/builder-deferred.tsRewrites bare specifiers (incl. dynamic import) and ensures builtins are included in step files.
packages/core/src/workflow/start.tsImplements workflow-VM createStart() using internal start built-in step.
packages/core/src/workflow/start.test.tsAdds unit tests for workflow-context start() behavior and option validation.
packages/core/src/workflow/run.tsAdds workflow-VM WorkflowRun proxy delegating to built-in Run steps.
packages/core/src/workflow/run.test.tsAdds unit tests asserting step delegation naming/arguments.
packages/core/src/workflow/builtin-step-id.tsAdds helper to construct built-in step IDs.
packages/core/src/workflow.tsInjects WORKFLOW_START, registers WorkflowRun, and updates response builtins to qualified IDs.
packages/core/src/symbols.tsAdds WORKFLOW_START symbol.
packages/core/src/serialization.tsAdds Run reducer/reviver using __serializable marker + class registry.
packages/core/src/serialization-format.tsAdds RunRef marker/type + reviver mapping Run → RunRef for o11y.
packages/core/src/runtime/step-handler.tsRegisters runtime Run class in host class registry for Run reviver.
packages/core/src/runtime/start.tsDelegates start() to injected workflow implementation when in workflow VM.
packages/core/src/runtime/run.tsAdds Run.__serializable marker for serialization.
packages/core/src/private.tsExtends builtin step aliasing to accept fully-qualified builtins step IDs.
packages/core/e2e/e2e.test.tsAdds e2e tests validating workflow-context start() and recursion.
docs/proxy.tsFormatting-only string quote normalization.
docs/lib/ai-agent-detection.tsFormatting-only string quote normalization.
docs/content/docs/foundations/starting-workflows.mdxDocuments calling start() inside workflow functions and recursive patterns.
docs/content/docs/foundations/common-patterns.mdxUpdates patterns docs to use direct start() in workflows.
docs/content/docs/api-reference/workflow-api/start.mdxUpdates API reference to include workflow-context usage.
AGENTS.mdUpdates local e2e instructions to include WORKFLOW_PUBLIC_MANIFEST=1.
.changeset/start-in-workflow.mdDeclares patch release for workflow-context start() support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/workflow/src/api-workflow.ts Outdated
Comment threadpackages/core/src/workflow/run.ts Outdated
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/serialization-format.ts
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/next/src/builder-deferred.ts Outdated
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
… duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
…t all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
…d of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
… step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
…just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
@VaguelySerious

Copy link
Copy Markdown
Member

Fails nextjs-turbopack tests

argsOrOptions?: TArgs | StartOptions,
options?: StartOptions
) {
'use step';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice

Sign up for freeto 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.

4 participants

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

feat: allow start() to be called directly inside workflow functions - #1491

Merged
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start
Apr 17, 2026
Merged

feat: allow start() to be called directly inside workflow functions#1491
TooTallNate merged 8 commits into
mainfrom
pgp/revert-revert-start

Conversation

@pranaygp

@pranaygppranaygp commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Add "use step" to start() so it can be called directly from "use workflow" functions, without needing a wrapper step function.

This is a dramatically simplified replacement for the original PR approach. Instead of complex VM-side proxy classes and builtin step delegation, the entire implementation is:

  1. Add "use step" to start() in packages/core/src/runtime/start.ts
  2. Re-export start from @workflow/core/runtime/start in api-workflow.ts (instead of a throwing stub)

This works because:

Changes

  • packages/core/src/runtime/start.ts: Add "use step" directive
  • packages/workflow/src/api-workflow.ts: Re-export start from core instead of stub
  • workbench/example/workflows/99_e2e.ts: Add startFromWorkflow and fibonacciWorkflow e2e workflows
  • packages/core/e2e/e2e.test.ts: Add e2e tests
  • workbench/nextjs-turbopack/app/workflows/definitions.ts: Add default args

E2E Tests

  • startFromWorkflow: Parent spawns child via start(), child signals parent via hook, verifies bidirectional communication
  • fibonacciWorkflow: Recursive workflow composition — fib(6) = 8 via tree of child workflows using Promise.all([start(...), start(...)])

@vercel

vercelBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@changeset-bot

changeset-botBot commented Mar 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5826c86

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

This PR includes changesets to release 17 packages
NameType
@workflow/coreMinor
workflowMinor
@workflow/buildersPatch
@workflow/cliPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-testingPatch
@workflow/aiMajor
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro0.042s (-2.3%)1.005s (~)0.963s101.00x
💻 LocalExpress0.043s (-3.6%)1.005s (~)0.963s101.01x
💻 LocalNext.js (Turbopack)0.050s1.005s0.955s101.18x
🐘 PostgresNext.js (Turbopack)0.057s1.009s0.953s101.35x
🐘 PostgresExpress0.060s (+3.8%)1.010s (~)0.950s101.43x
🐘 PostgresNitro0.087s (-9.0% 🟢)1.029s (-1.3%)0.942s102.06x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)0.255s (+1.5%)1.912s (-18.0% 🟢)1.657s101.00x
▲ VercelNitro0.413s (+0.9%)2.209s (-12.0% 🟢)1.795s101.62x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.116s2.006s0.890s101.00x
💻 LocalExpress1.128s (~)2.006s (~)0.878s101.01x
🐘 PostgresNext.js (Turbopack)1.136s2.010s0.874s101.02x
💻 LocalNitro1.137s (~)2.006s (~)0.870s101.02x
🐘 PostgresExpress1.145s (~)2.009s (~)0.864s101.03x
🐘 PostgresNitro1.150s (+0.9%)2.009s (~)0.859s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.841s (-52.7% 🟢)3.610s (-38.9% 🟢)1.769s101.00x
▲ VercelNext.js (Turbopack)1.941s (-4.6%)3.671s (-4.2%)1.731s101.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)10.791s11.023s0.232s31.00x
🐘 PostgresNitro10.846s (~)11.019s (~)0.173s31.01x
🐘 PostgresExpress10.866s (-0.9%)11.018s (~)0.151s31.01x
🐘 PostgresNext.js (Turbopack)10.897s11.024s0.127s31.01x
💻 LocalExpress10.935s (~)11.024s (~)0.088s31.01x
💻 LocalNitro10.956s (~)11.024s (~)0.068s31.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro16.899s (-28.8% 🟢)18.812s (-25.1% 🟢)1.912s21.00x
▲ VercelNext.js (Turbopack)17.099s (-1.3%)18.986s (-2.1%)1.887s21.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)14.427s15.022s0.595s41.00x
🐘 PostgresNitro14.487s (-0.7%)15.027s (~)0.540s41.00x
🐘 PostgresExpress14.497s (-0.6%)15.021s (~)0.524s41.00x
💻 LocalNext.js (Turbopack)14.599s15.030s0.431s41.01x
💻 LocalExpress14.977s (~)15.280s (+1.7%)0.303s41.04x
💻 LocalNitro15.014s (~)15.531s (-3.1%)0.516s41.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro31.103s (-51.7% 🟢)33.088s (-50.3% 🟢)1.985s21.00x
▲ VercelNext.js (Turbopack)34.283s (-34.8% 🟢)36.218s (-33.7% 🟢)1.935s21.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express13.777s (-1.6%)14.021s (-3.9%)0.244s71.00x
🐘 PostgresNitro13.849s (-0.8%)14.023s (-2.0%)0.174s71.01x
🐘 PostgresNext.js (Turbopack)13.905s14.024s0.119s71.01x
💻 LocalNext.js (Turbopack)15.836s16.029s0.193s61.15x
💻 LocalExpress16.630s (~)17.030s (~)0.400s61.21x
💻 LocalNitro16.773s (~)17.031s (~)0.258s61.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)54.152s (-86.2% 🟢)55.639s (-85.9% 🟢)1.487s21.00x
▲ VercelNitro56.014s (-86.8% 🟢)57.859s (-86.4% 🟢)1.845s21.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.237s2.009s0.772s151.00x
🐘 PostgresNitro1.259s (-1.2%)2.011s (~)0.752s151.02x
🐘 PostgresExpress1.260s (~)2.008s (~)0.748s151.02x
💻 LocalNext.js (Turbopack)1.467s2.005s0.538s151.19x
💻 LocalExpress1.539s (+3.4%)2.006s (~)0.467s151.24x
💻 LocalNitro1.584s (-2.9%)2.006s (-3.3%)0.422s151.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.295s (-18.6% 🟢)3.768s (-12.8% 🟢)1.473s81.00x
▲ VercelNext.js (Turbopack)2.682s (-21.1% 🟢)4.363s (-11.5% 🟢)1.681s71.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.323s (-1.2%)3.010s (~)0.687s101.00x
🐘 PostgresExpress2.371s (~)3.008s (~)0.637s101.02x
🐘 PostgresNext.js (Turbopack)2.411s3.009s0.598s101.04x
💻 LocalNext.js (Turbopack)2.864s3.208s0.344s101.23x
💻 LocalExpress3.041s (+3.0%)3.885s (+12.5% 🔺)0.844s81.31x
💻 LocalNitro3.188s (+1.4%)3.886s (~)0.697s81.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.493s (-38.5% 🟢)4.128s (-30.3% 🟢)1.635s81.00x
▲ VercelNext.js (Turbopack)3.312s (-53.3% 🟢)4.688s (-47.4% 🟢)1.375s71.33x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro3.479s (~)4.010s (~)0.531s81.00x
🐘 PostgresExpress3.486s (~)4.011s (~)0.525s81.00x
🐘 PostgresNext.js (Turbopack)3.646s4.011s0.365s81.05x
💻 LocalNext.js (Turbopack)7.295s8.015s0.720s42.10x
💻 LocalExpress8.300s (~)9.021s (~)0.721s42.39x
💻 LocalNitro9.008s (+7.9% 🔺)9.520s (+5.5% 🔺)0.512s42.59x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.317s (-5.9% 🟢)5.227s (-5.5% 🟢)1.910s61.00x
▲ VercelNext.js (Turbopack)3.665s (-58.9% 🟢)5.170s (-52.8% 🟢)1.505s61.10x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s2.008s0.780s151.00x
🐘 PostgresNitro1.253s (~)2.008s (~)0.755s151.02x
🐘 PostgresExpress1.265s (+0.6%)2.007s (~)0.742s151.03x
💻 LocalNext.js (Turbopack)1.480s2.006s0.526s151.20x
💻 LocalExpress1.537s (-18.8% 🟢)2.006s (-15.1% 🟢)0.469s151.25x
💻 LocalNitro1.581s (-15.3% 🟢)2.007s (-14.3% 🟢)0.426s151.29x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.065s (-16.0% 🟢)3.741s (-10.3% 🟢)1.676s91.00x
▲ VercelNext.js (Turbopack)2.073s (-29.3% 🟢)3.679s (-20.7% 🟢)1.606s91.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro2.328s (-0.5%)3.011s (~)0.683s101.00x
🐘 PostgresExpress2.371s (+1.3%)3.009s (~)0.638s101.02x
🐘 PostgresNext.js (Turbopack)2.395s3.008s0.613s101.03x
💻 LocalNext.js (Turbopack)2.899s3.341s0.443s91.25x
💻 LocalExpress3.031s (-3.2%)3.883s (+3.2%)0.852s81.30x
💻 LocalNitro3.064s (~)3.885s (~)0.821s81.32x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.511s (-22.3% 🟢)3.996s (-21.3% 🟢)1.485s81.00x
▲ VercelNext.js (Turbopack)2.943s (-6.4% 🟢)4.388s (-3.0%)1.445s81.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.478s (-0.6%)4.011s (~)0.533s81.00x
🐘 PostgresNitro3.481s (~)4.011s (~)0.530s81.00x
🐘 PostgresNext.js (Turbopack)3.657s4.012s0.355s81.05x
💻 LocalNext.js (Turbopack)8.041s8.767s0.726s42.31x
💻 LocalExpress8.919s (+1.3%)9.271s (~)0.352s42.56x
💻 LocalNitro9.163s (~)10.022s (~)0.859s32.63x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.059s (-39.9% 🟢)4.889s (-28.3% 🟢)1.830s71.00x
▲ VercelNext.js (Turbopack)3.409s (-49.6% 🟢)5.067s (-40.7% 🟢)1.659s61.11x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.779s1.023s0.244s591.00x
🐘 PostgresExpress0.799s (-4.7%)1.006s (-1.7%)0.207s601.03x
💻 LocalNext.js (Turbopack)0.829s1.004s0.176s601.06x
🐘 PostgresNitro0.834s (+1.6%)1.006s (~)0.172s601.07x
💻 LocalExpress0.999s (+1.5%)1.369s (+27.2% 🔺)0.370s441.28x
💻 LocalNitro1.016s (+3.6%)1.611s (+47.3% 🔺)0.595s381.30x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro9.843s (-55.4% 🟢)12.413s (-48.3% 🟢)2.570s51.00x
▲ VercelNext.js (Turbopack)12.026s (-17.1% 🟢)13.929s (-13.4% 🟢)1.903s51.22x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.884s (-4.7%)2.075s (-8.1% 🟢)0.192s441.00x
🐘 PostgresNext.js (Turbopack)1.940s2.202s0.263s411.03x
🐘 PostgresNitro2.009s (+4.2%)2.580s (+22.8% 🔺)0.571s351.07x
💻 LocalNext.js (Turbopack)2.607s3.008s0.401s301.38x
💻 LocalNitro3.062s (+0.9%)3.842s (+2.2%)0.780s241.63x
💻 LocalExpress3.083s (+2.2%)3.843s (+7.2% 🔺)0.760s241.64x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro29.627s (-25.0% 🟢)31.818s (-23.0% 🟢)2.191s31.00x
▲ VercelNext.js (Turbopack)30.003s (-39.8% 🟢)31.773s (-38.6% 🟢)1.770s31.01x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express3.838s (-3.8%)4.042s (-7.5% 🟢)0.204s301.00x
🐘 PostgresNext.js (Turbopack)3.845s4.043s0.198s301.00x
🐘 PostgresNitro4.114s (~)4.742s (+3.0%)0.628s261.07x
💻 LocalNext.js (Turbopack)8.439s9.017s0.578s142.20x
💻 LocalExpress9.229s (~)9.941s (-0.8%)0.712s132.40x
💻 LocalNitro9.362s (+0.7%)10.103s (+0.8%)0.741s122.44x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro79.683s (-17.8% 🟢)81.801s (-16.9% 🟢)2.118s21.00x
▲ VercelNext.js (Turbopack)83.234s (-22.3% 🟢)84.822s (-22.1% 🟢)1.588s21.04x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.254s1.007s0.753s601.00x
🐘 PostgresExpress0.286s (+1.3%)1.007s (~)0.720s601.13x
🐘 PostgresNitro0.292s (+3.2%)1.008s (~)0.715s601.15x
💻 LocalExpress0.591s (+5.5% 🔺)1.005s (~)0.413s602.33x
💻 LocalNitro0.604s (~)1.005s (-1.7%)0.401s602.38x
💻 LocalNext.js (Turbopack)0.636s1.096s0.460s552.50x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.935s (-4.3%)3.931s (+3.6%)1.996s161.00x
▲ VercelNitro2.045s (+23.1% 🔺)3.924s (+17.1% 🔺)1.879s161.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.475s1.006s0.531s901.00x
🐘 PostgresExpress0.488s (-4.3%)1.006s (~)0.518s901.03x
🐘 PostgresNitro0.498s (~)1.007s (~)0.508s901.05x
💻 LocalNext.js (Turbopack)2.523s3.009s0.485s305.31x
💻 LocalExpress2.549s (+1.4%)3.010s (~)0.461s305.36x
💻 LocalNitro2.595s (+2.2%)3.010s (~)0.415s305.46x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.216s (~)5.023s (+4.2%)1.806s181.00x
▲ VercelNext.js (Turbopack)3.287s (-7.0% 🟢)5.034s (-3.1%)1.746s181.02x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.774s (-5.4% 🟢)1.007s (-1.0%)0.233s1201.00x
🐘 PostgresNext.js (Turbopack)0.777s1.006s0.230s1201.00x
🐘 PostgresNitro0.794s (~)1.008s (~)0.214s1201.03x
💻 LocalNext.js (Turbopack)10.324s10.943s0.619s1213.34x
💻 LocalNitro11.556s (+3.3%)12.130s (+4.0%)0.574s1014.93x
💻 LocalExpress11.688s (+4.4%)12.329s (+3.3%)0.641s1015.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro6.003s (-22.3% 🟢)7.762s (-17.4% 🟢)1.759s161.00x
▲ VercelNext.js (Turbopack)13.494s (+30.7% 🔺)15.200s (+23.7% 🔺)1.707s112.25x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)0.179s1.003s0.012s1.018s0.839s101.00x
💻 LocalExpress0.203s (+1.8%)1.004s (~)0.012s (-3.3%)1.018s (~)0.815s101.13x
🐘 PostgresExpress0.203s (-0.9%)0.992s (-0.7%)0.002s (~)1.010s (~)0.806s101.14x
💻 LocalNitro0.204s (-4.5%)1.004s (~)0.012s (-4.8%)1.018s (~)0.814s101.14x
🐘 PostgresNext.js (Turbopack)0.207s1.001s0.002s1.011s0.804s101.16x
🐘 PostgresNitro0.210s (+2.5%)0.995s (~)0.001s (-20.0% 🟢)1.009s (~)0.799s101.18x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)1.595s (-76.7% 🟢)3.061s (-64.6% 🟢)0.709s (+12.1% 🔺)4.212s (-57.0% 🟢)2.617s101.00x
▲ VercelNitro1.672s (-56.4% 🟢)3.060s (-42.0% 🟢)0.780s (+5.1% 🔺)4.359s (-32.8% 🟢)2.687s101.05x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.605s (-3.9%)1.005s (~)0.004s (+2.2%)1.022s (~)0.417s591.00x
🐘 PostgresNext.js (Turbopack)0.607s1.009s0.006s1.024s0.417s591.00x
🐘 PostgresNitro0.627s (~)1.003s (~)0.004s (-4.5%)1.022s (~)0.395s591.04x
💻 LocalNext.js (Turbopack)0.756s1.011s0.020s1.126s0.370s541.25x
💻 LocalExpress0.794s (+4.8%)1.012s (-1.6%)0.015s (+54.8% 🔺)1.029s (-1.1%)0.235s591.31x
💻 LocalNitro0.968s (+15.5% 🔺)1.012s (~)0.010s (+6.7% 🔺)1.228s (+10.0% 🔺)0.260s491.60x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)4.375s (-74.1% 🟢)6.053s (-66.8% 🟢)0.188s (-11.1% 🟢)6.702s (-64.6% 🟢)2.327s91.00x
▲ VercelNitro4.545s (-84.6% 🟢)6.277s (-79.6% 🟢)0.225s (+101.1% 🔺)6.958s (-78.1% 🟢)2.413s91.04x
▲ VercelExpress⚠️missing-----

🔍 Observability: Next.js (Turbopack) | Nitro

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.937s1.203s0.000s1.216s0.279s501.00x
🐘 PostgresNitro0.948s (-2.1%)1.167s (-6.4% 🟢)0.000s (-7.7% 🟢)1.181s (-6.1% 🟢)0.233s521.01x
🐘 PostgresExpress0.953s (-0.9%)1.269s (-0.7%)0.000s (-51.1% 🟢)1.294s (-0.9%)0.341s471.02x
💻 LocalExpress1.252s (+2.3%)2.024s (~)0.000s (~)2.026s (~)0.773s301.34x
💻 LocalNext.js (Turbopack)1.281s2.021s0.001s2.024s0.742s301.37x
💻 LocalNitro1.420s (+16.1% 🔺)2.021s (~)0.000s (+221.4% 🔺)2.202s (+8.9% 🔺)0.782s281.52x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.005s (-1.5%)4.107s (-6.5% 🟢)0.000s (-100.0% 🟢)4.696s (-2.4%)1.691s131.00x
▲ VercelNext.js (Turbopack)3.462s (-66.0% 🟢)5.066s (-56.0% 🟢)0.000s (NaN%)5.508s (-54.3% 🟢)2.045s111.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.669s (-5.8% 🟢)2.058s (-5.5% 🟢)0.000s (+Infinity% 🔺)2.082s (-5.3% 🟢)0.413s291.00x
🐘 PostgresNext.js (Turbopack)1.790s2.072s0.000s2.083s0.293s291.07x
🐘 PostgresNitro1.825s (+1.9%)2.258s (+5.4% 🔺)0.000s (+107.4% 🔺)2.269s (+4.3%)0.444s271.09x
💻 LocalExpress3.530s (+1.8%)4.034s (~)0.000s (-41.7% 🟢)4.038s (~)0.508s152.12x
💻 LocalNitro3.649s (+7.7% 🔺)4.099s (+1.6%)0.001s (+62.5% 🔺)4.102s (+1.6%)0.453s152.19x
💻 LocalNext.js (Turbopack)3.847s4.389s0.000s4.395s0.547s142.31x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.955s (-3.4%)5.118s (-4.8%)0.000s (-100.0% 🟢)5.592s (-3.5%)1.637s111.00x
▲ VercelNext.js (Turbopack)4.537s (-19.2% 🟢)5.937s (-15.0% 🟢)0.000s (-100.0% 🟢)6.464s (-14.3% 🟢)1.927s101.15x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNext.js (Turbopack)17/21
🐘 PostgresNext.js (Turbopack)9/21
▲ VercelNitro16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres18/21
Next.js (Turbopack)🐘 Postgres16/21
Nitro🐘 Postgres16/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)

📋 View full workflow run

@github-actions

github-actionsBot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production9450671012
✅ 💻 Local Development10180861104
✅ 📦 Local Production10180861104
✅ 🐘 Local Postgres10180861104
✅ 🪟 Windows920092
✅ 📋 Other258018276
Total434903434692

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro8507
✅ example8507
✅ express8507
✅ fastify8507
✅ hono8507
✅ nextjs-turbopack9002
✅ nextjs-webpack9002
✅ nitro8507
✅ nuxt8507
✅ sveltekit8507
✅ vite8507
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable8606
✅ express-stable8606
✅ fastify-stable8606
✅ hono-stable8606
✅ nextjs-turbopack-canary73019
✅ nextjs-turbopack-stable9200
✅ nextjs-webpack-canary73019
✅ nextjs-webpack-stable9200
✅ nitro-stable8606
✅ nuxt-stable8606
✅ sveltekit-stable8606
✅ vite-stable8606
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack9200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable8606
✅ e2e-local-postgres-nest-stable8606
✅ e2e-local-prod-nest-stable8606

📋 View full workflow run

@pranaygppranaygp changed the title Revert "Revert \"Add support for calling start() inside workflow functions\""Revert Revert "Add support for calling start() inside workflow functions"Mar 23, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Re-enables calling start() from workflow/api directly inside "use workflow" functions by routing the call through internal built-in steps, with Run objects working in workflow VM context (step-backed getters/methods) and improved observability UI hydration/rendering for run references.

Changes:

  • Add workflow-context start() delegation via WORKFLOW_START, backed by a built-in start step and a workflow-VM WorkflowRun proxy class.
  • Extend serialization/hydration to support Run/WorkflowRun across boundaries and render Run references as clickable UI elements.
  • Update builders, plugin behavior/docs, and add unit + e2e coverage for startFromWorkflow and recursive fibonacciWorkflow.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
FileDescription
workbench/nextjs-turbopack/app/workflows/definitions.tsAdds default args for new e2e workflows.
workbench/example/workflows/99_e2e.tsAdds startFromWorkflow and recursive fibonacciWorkflow workflows for e2e.
skills/workflow/SKILL.mdUpdates skill guidance to reflect start() support inside workflows.
packages/workflow/src/runtime.tsRe-exports getRun and start from core runtime for rewritten step imports.
packages/workflow/src/internal/builtins.tsAdds built-in start step and Run.* step-backed static methods.
packages/workflow/src/api-workflow.tsRoutes start() in workflow bundle via WORKFLOW_START injection.
packages/web/app/components/run-detail-view.tsxAdds run-ref click navigation wiring.
packages/web-shared/src/lib/hydration.tsExposes RunRef helpers for UI hydration.
packages/web-shared/src/components/workflow-traces/trace-span-construction.tsImproves span naming fallback behavior.
packages/web-shared/src/components/workflow-trace-view.tsxPlumbs onRunClick and resets selection on run change.
packages/web-shared/src/components/ui/data-inspector.tsxAdds RunRef rendering + “opaque ref” collapsing + click contexts.
packages/web-shared/src/components/sidebar/entity-detail-panel.tsxPasses onRunClick into detail panel rendering.
packages/web-shared/src/components/sidebar/attribute-panel.tsxAdds Run click context and improves stepName display fallback.
packages/web-shared/src/components/run-trace-view.tsxThreads onRunClick through trace view components.
packages/swc-plugin-workflow/transform/src/lib.rsRemoves __builtin* step-id special casing to use normal IDs.
packages/swc-plugin-workflow/spec.mdUpdates spec examples to include builtins module-specifier IDs.
packages/next/src/builder-deferred.tsRewrites bare specifiers (incl. dynamic import) and ensures builtins are included in step files.
packages/core/src/workflow/start.tsImplements workflow-VM createStart() using internal start built-in step.
packages/core/src/workflow/start.test.tsAdds unit tests for workflow-context start() behavior and option validation.
packages/core/src/workflow/run.tsAdds workflow-VM WorkflowRun proxy delegating to built-in Run steps.
packages/core/src/workflow/run.test.tsAdds unit tests asserting step delegation naming/arguments.
packages/core/src/workflow/builtin-step-id.tsAdds helper to construct built-in step IDs.
packages/core/src/workflow.tsInjects WORKFLOW_START, registers WorkflowRun, and updates response builtins to qualified IDs.
packages/core/src/symbols.tsAdds WORKFLOW_START symbol.
packages/core/src/serialization.tsAdds Run reducer/reviver using __serializable marker + class registry.
packages/core/src/serialization-format.tsAdds RunRef marker/type + reviver mapping Run → RunRef for o11y.
packages/core/src/runtime/step-handler.tsRegisters runtime Run class in host class registry for Run reviver.
packages/core/src/runtime/start.tsDelegates start() to injected workflow implementation when in workflow VM.
packages/core/src/runtime/run.tsAdds Run.__serializable marker for serialization.
packages/core/src/private.tsExtends builtin step aliasing to accept fully-qualified builtins step IDs.
packages/core/e2e/e2e.test.tsAdds e2e tests validating workflow-context start() and recursion.
docs/proxy.tsFormatting-only string quote normalization.
docs/lib/ai-agent-detection.tsFormatting-only string quote normalization.
docs/content/docs/foundations/starting-workflows.mdxDocuments calling start() inside workflow functions and recursive patterns.
docs/content/docs/foundations/common-patterns.mdxUpdates patterns docs to use direct start() in workflows.
docs/content/docs/api-reference/workflow-api/start.mdxUpdates API reference to include workflow-context usage.
AGENTS.mdUpdates local e2e instructions to include WORKFLOW_PUBLIC_MANIFEST=1.
.changeset/start-in-workflow.mdDeclares patch release for workflow-context start() support.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadpackages/workflow/src/api-workflow.ts Outdated
Comment threadpackages/core/src/workflow/run.ts Outdated
Comment threadpackages/swc-plugin-workflow/spec.md Outdated
Comment threadpackages/core/src/serialization-format.ts
Comment threadpackages/web-shared/src/components/ui/data-inspector.tsx
Comment threadpackages/next/src/builder-deferred.ts Outdated
Add 'use step' to start() so it can be called directly from workflow
code. The SWC compiler strips the function body in workflow mode and
replaces it with a step proxy. When called from a workflow:
1. The workflow function reference is serialized via WorkflowFunction
reducer (serializes { workflowId })
2. start() executes in the step context with full Node.js access
3. The returned Run is serialized via WORKFLOW_SERIALIZE and deserialized
back in the workflow VM
4. Run getters (.status, .returnValue, etc.) are 'use step' getters
that each execute as separate steps
Also re-exports start from @workflow/core/runtime/start in api-workflow.ts
instead of using a throwing stub, adds e2e tests for startFromWorkflow
(with hook communication) and fibonacciWorkflow (recursive composition).
… duplicate classes
Files belonging to packages (detected by walking up to find a
package.json with a name field) are imported via relative path
instead of being copied to __workflow_step_files__/. Copying creates
a second module instance which breaks JS native private field (#)
brand checks when the runtime creates instances from one copy and
the step handler accesses fields from the other.
…t all package step files
Regular package step files (like fetch) must still be copied to ensure
the SWC loader registers them. Only serde class files from packages are
excluded from copying since those define classes with JS native private
fields (#) that break when duplicated.
…d of full copies
For package files that define serde classes (like Run), generate a thin
wrapper that imports the original class and registers steps/classes from
the manifest. This avoids duplicating the class definition (which breaks
JS native private field brand checks) while still registering all step
functions and the class in the serialization registry.
Regular package step files (like fetch) are still copied as before.
… step mode
Instead of copying package serde+step files (which creates duplicate
classes with #private brand check issues) or generating fragile wrappers,
add the original file paths to a shared forceStepModeFiles set. The
loader checks this set and transforms those files in step mode directly,
so the SWC plugin generates proper step registrations on the original
class — no duplication, no reimplemented registration logic.
…just copies
The loader now selects step mode for any file that has 'use step'
directives or serde patterns, regardless of whether it's a deferred
step copy. Step mode is a superset of client mode — the only addition
is step registry IIFEs, which are harmless for non-step consumers.
This means package serde+step files (like Run) no longer need to be
copied to get step registrations. They're imported directly in the
step route and the loader transforms the original file in step mode.
One class instance, no duplication, no wrapper generation.
@VaguelySerious

Copy link
Copy Markdown
Member

Fails nextjs-turbopack tests

argsOrOptions?: TArgs | StartOptions,
options?: StartOptions
) {
'use step';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice

Sign up for freeto 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.

4 participants

@pranaygp@VaguelySerious@TooTallNate