Skip to content

Attributes MVP (experimental and write-only)#2088

Open
VaguelySerious wants to merge 17 commits into
mainfrom
peter/attributes-mvp-plan
Open

Attributes MVP (experimental and write-only)#2088
VaguelySerious wants to merge 17 commits into
mainfrom
peter/attributes-mvp-plan

Conversation

@VaguelySerious
Copy link
Copy Markdown
Member

@VaguelySerious VaguelySerious commented May 22, 2026

Summary

Implements the Workflow Attributes MVP — a minimal, write-only attributes API designed to land before the full event-sourced attributes feature in #1933 (which requires a SPEC_VERSION_CURRENT bump and coordinated rollout across worlds, builders, and runtime).

User surface:

import { setAttributes } from 'workflow';

export async function myWorkflow(orderId: string) {
  'use workflow';
  await setAttributes({ phase: 'processing', orderId });
  // ...
  await setAttributes({ phase: 'done' });
  await setAttributes({ orderId: undefined }); // remove a key
}

Attributes are stored plaintext on the WorkflowRun entity and visible via world.runs.get() / world.runs.list() (and any observability surface built on top). The wire format mirrors the future attr_set event's eventData.changes, so the SDK signature and wire body shape are stable across MVP → 5.0.0.

setAttributes is callable from a workflow body only. The call is dispatched through an internal __builtin_set_attributes step bridge so the mutation gets a step_created → step_completed event pair without inventing a new event type. The host-side export (resolved from step bodies or plain host code) throws FatalError directing the caller back to a workflow body — step-body support can be added later without breaking the workflow-body contract.

See docs/content/docs/v5/changelog/attributes-mvp.mdx for the full design, trade-offs, and implementation notes — including decisions made during build-out (endpoint namespacing, concurrency semantics, usage-fact schema choice, optional-world fallback behavior, etc.).

Paired with vercel/workflow-server#442 which adds the server-side POST /api/v2/runs/:runId/attributes endpoint, ElectroDB column, and the new WORKFLOW_ATTRIBUTE usage-fact carrying the post-merge map.

What's in this PR

Layer Change
@workflow/world Shared validation + applyAttributeChanges helper. Optional experimentalSetAttributes on Storage.runs. Optional attributes field on WorkflowRunBaseSchema.
@workflow/core New setAttributes(record). VM-side helper validates inline and dispatches via the standard WORKFLOW_USE_STEP mechanism — no new global symbols, no bridge plumbing. Host-side export is a FatalError-throwing stub for non-workflow-body callers.
workflow package __builtin_set_attributes step body in internal/builtins.ts. Reads world + run id directly from globalThis symbols populated by the runtime; zero imports from @workflow/core so it stays a true leaf in the deferred-entries graph.
@workflow/world-local Filesystem impl with a per-run async mutex so concurrent writes within a process don't lose updates. Threads attributes through the run lifecycle event reconstructions.
@workflow/world-postgres New attributes jsonb column (migration 0013_add_attributes.sql). SQL-side atomic merge using jsonb_set / - operators in a single UPDATE.
@workflow/world-vercel Pure HTTP wrapper posting { changes: [...] } to /v2/runs/:runId/attributes.
Docs Changelog entry at docs/content/docs/v5/changelog/attributes-mvp.mdx with full design + implementation notes.

Architecture (workflow-body dispatch)

  1. User calls setAttributes(attrs) from 'use workflow' body.
  2. Workflow VM resolves the import via the workflow package-exports condition to packages/core/src/workflow/set-attributes.ts, which validates the input inline and produces canonical AttributeChange[].
  3. Dispatch happens through the standard globalThis[WORKFLOW_USE_STEP]('__builtin_set_attributes')(changes) — same mechanism every other step call uses.
  4. Step worker runs __builtin_set_attributes (in packages/workflow/src/internal/builtins.ts). The step body reads the world from globalThis[Symbol.for('@workflow/world//cache')] and the active run id from globalThis[Symbol.for('WORKFLOW_STEP_CONTEXT_STORAGE')], then calls world.runs.experimentalSetAttributes(runId, changes). No imports from @workflow/core — this is what keeps the Next.js deferred-entries discoverer from walking into world adapters and triggering webpack's regex-extractor stack overflow.
  5. Step completes; workflow resumes.

What's NOT in this PR (not in MVP)

  • Calling setAttributes from a step body or plain host code (throws FatalError; can be added later)
  • Reading attributes inside a workflow or step (getAttribute / getAttributes)
  • start(workflow, input, { attributes }) (initial attributes at run creation)
  • Filtering / enumerating runs by attribute (runs.list({ attributes }), listAttributeKeys, listAttributeValues)
  • Writer attribution (workflow vs step + attempt) — needs the attr_set event type
  • Non-string value types
  • Reserved-key ($-prefixed) namespace (just blocked at validation today)

Outlines the bare-MVP, write-only attributes design that defers the
`attr_set` event type (and the associated SPEC_VERSION_CURRENT bump)
to the full 5.0.0 feature. Forward-compatible SDK surface and wire
format. `experimentalSetAttributes` is optional on the World
interface so third-party worlds keep working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 22, 2026

📊 Benchmark Results

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

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 0.038s (-13.3% 🟢) 1.005s (~) 0.967s 10 1.00x
💻 Local Nitro 0.041s (-6.0% 🟢) 1.006s (~) 0.965s 10 1.05x
🐘 Postgres Express 0.058s (~) 1.011s (~) 0.953s 10 1.51x
🐘 Postgres Nitro 0.059s (-37.7% 🟢) 1.013s (-2.9%) 0.954s 10 1.54x
💻 Local Next.js (Turbopack) 0.060s 1.006s 0.946s 10 1.57x
🐘 Postgres Next.js (Turbopack) 0.066s 1.011s 0.945s 10 1.72x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 0.304s (+29.3% 🔺) 2.223s (+4.1%) 1.918s 10 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.087s (-3.4%) 2.006s (~) 0.919s 10 1.00x
💻 Local Nitro 1.096s (-3.1%) 2.009s (~) 0.912s 10 1.01x
🐘 Postgres Express 1.108s (-3.4%) 2.008s (~) 0.900s 10 1.02x
🐘 Postgres Nitro 1.118s (-2.0%) 2.012s (~) 0.894s 10 1.03x
💻 Local Next.js (Turbopack) 1.121s 2.006s 0.885s 10 1.03x
🐘 Postgres Next.js (Turbopack) 1.135s 2.009s 0.873s 10 1.04x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.683s (-10.3% 🟢) 3.460s (-9.1% 🟢) 1.778s 10 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 10.528s (-3.6%) 11.022s (~) 0.493s 3 1.00x
🐘 Postgres Nitro 10.536s (-3.1%) 11.019s (~) 0.483s 3 1.00x
🐘 Postgres Express 10.549s (-3.8%) 11.016s (~) 0.467s 3 1.00x
💻 Local Nitro 10.571s (-3.4%) 11.022s (~) 0.450s 3 1.00x
💻 Local Next.js (Turbopack) 10.769s 11.021s 0.252s 3 1.02x
🐘 Postgres Next.js (Turbopack) 10.820s 11.018s 0.198s 3 1.03x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 14.517s (-14.5% 🟢) 16.335s (-18.4% 🟢) 1.819s 2 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 13.667s (-8.7% 🟢) 14.027s (-6.7% 🟢) 0.360s 5 1.00x
🐘 Postgres Express 13.762s (-5.6% 🟢) 14.018s (-6.7% 🟢) 0.256s 5 1.01x
🐘 Postgres Nitro 13.831s (-5.2% 🟢) 14.019s (-6.7% 🟢) 0.188s 5 1.01x
💻 Local Nitro 13.854s (-8.0% 🟢) 14.026s (-12.5% 🟢) 0.172s 5 1.01x
💻 Local Next.js (Turbopack) 14.326s 15.029s 0.703s 4 1.05x
🐘 Postgres Next.js (Turbopack) 14.336s 15.016s 0.679s 4 1.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 22.014s (-56.2% 🟢) 24.230s (-53.9% 🟢) 2.216s 3 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 12.327s (-25.7% 🟢) 13.024s (-23.5% 🟢) 0.697s 7 1.00x
🐘 Postgres Nitro 12.448s (-10.9% 🟢) 13.020s (-9.0% 🟢) 0.572s 7 1.01x
💻 Local Nitro 12.476s (-25.7% 🟢) 13.024s (-23.5% 🟢) 0.548s 7 1.01x
🐘 Postgres Express 12.585s (-10.2% 🟢) 13.019s (-10.8% 🟢) 0.433s 7 1.02x
💻 Local Next.js (Turbopack) 13.509s 14.027s 0.517s 7 1.10x
🐘 Postgres Next.js (Turbopack) 13.838s 14.018s 0.180s 7 1.12x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 28.760s (-76.3% 🟢) 30.355s (-75.5% 🟢) 1.595s 3 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.181s (-6.3% 🟢) 2.007s (~) 0.826s 15 1.00x
💻 Local Express 1.182s (-20.6% 🟢) 2.006s (~) 0.825s 15 1.00x
💻 Local Nitro 1.185s (-27.3% 🟢) 2.006s (-3.3%) 0.820s 15 1.00x
🐘 Postgres Nitro 1.185s (-7.0% 🟢) 2.007s (~) 0.821s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.229s 2.007s 0.778s 15 1.04x
💻 Local Next.js (Turbopack) 1.299s 2.005s 0.706s 15 1.10x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.801s (+32.9% 🔺) 6.811s (+47.3% 🔺) 3.010s 5 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.210s (-48.7% 🟢) 2.007s (-33.3% 🟢) 0.797s 15 1.00x
🐘 Postgres Nitro 1.237s (-47.4% 🟢) 2.007s (-33.3% 🟢) 0.770s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.407s 2.007s 0.600s 15 1.16x
💻 Local Express 1.693s (-42.7% 🟢) 2.005s (-41.9% 🟢) 0.312s 15 1.40x
💻 Local Next.js (Turbopack) 1.772s 2.150s 0.379s 14 1.46x
💻 Local Nitro 1.785s (-43.2% 🟢) 2.006s (-48.4% 🟢) 0.221s 15 1.47x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 4.242s (+17.2% 🔺) 5.787s (+13.2% 🔺) 1.545s 6 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.323s (-62.0% 🟢) 2.007s (-49.9% 🟢) 0.684s 15 1.00x
🐘 Postgres Nitro 1.362s (-60.9% 🟢) 2.007s (-49.9% 🟢) 0.645s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.703s 2.152s 0.448s 14 1.29x
💻 Local Express 4.432s (-46.8% 🟢) 5.012s (-44.5% 🟢) 0.579s 6 3.35x
💻 Local Next.js (Turbopack) 4.466s 5.013s 0.546s 6 3.37x
💻 Local Nitro 5.413s (-35.2% 🟢) 6.014s (-33.3% 🟢) 0.601s 5 4.09x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.977s (+41.0% 🔺) 7.933s (+29.5% 🔺) 1.956s 4 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.178s (-6.3% 🟢) 2.007s (~) 0.829s 15 1.00x
🐘 Postgres Nitro 1.179s (-6.2% 🟢) 2.009s (~) 0.831s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.239s 2.008s 0.768s 15 1.05x
💻 Local Express 1.470s (-22.4% 🟢) 2.006s (-15.1% 🟢) 0.536s 15 1.25x
💻 Local Nitro 1.541s (-17.4% 🟢) 2.007s (-14.3% 🟢) 0.466s 15 1.31x
💻 Local Next.js (Turbopack) 1.871s 2.507s 0.636s 12 1.59x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.886s (+11.8% 🔺) 4.205s (-3.3%) 1.319s 8 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.218s (-48.0% 🟢) 2.008s (-33.3% 🟢) 0.790s 15 1.00x
🐘 Postgres Nitro 1.260s (-46.1% 🟢) 2.020s (-32.9% 🟢) 0.760s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.351s 2.006s 0.656s 15 1.11x
💻 Local Express 1.897s (-39.4% 🟢) 2.223s (-40.9% 🟢) 0.326s 14 1.56x
💻 Local Next.js (Turbopack) 2.059s 2.675s 0.617s 12 1.69x
💻 Local Nitro 2.151s (-29.8% 🟢) 2.591s (-33.3% 🟢) 0.440s 12 1.77x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.635s (+13.9% 🔺) 5.061s (+5.6% 🔺) 1.426s 6 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.317s (-62.3% 🟢) 2.075s (-48.3% 🟢) 0.758s 15 1.00x
🐘 Postgres Nitro 1.364s (-60.8% 🟢) 2.008s (-49.9% 🟢) 0.643s 15 1.04x
🐘 Postgres Next.js (Turbopack) 1.663s 2.225s 0.562s 14 1.26x
💻 Local Next.js (Turbopack) 5.386s 5.679s 0.294s 6 4.09x
💻 Local Express 5.422s (-38.4% 🟢) 6.017s (-35.1% 🟢) 0.595s 5 4.12x
💻 Local Nitro 5.961s (-34.8% 🟢) 6.214s (-38.0% 🟢) 0.253s 5 4.52x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.841s (-9.0% 🟢) 7.842s (-4.1%) 2.001s 4 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.587s (-28.5% 🟢) 1.041s (+3.5%) 0.454s 58 1.00x
💻 Local Express 0.588s (-40.3% 🟢) 1.005s (-6.6% 🟢) 0.417s 60 1.00x
🐘 Postgres Express 0.594s (-29.2% 🟢) 1.024s (~) 0.430s 59 1.01x
💻 Local Nitro 0.612s (-37.6% 🟢) 1.005s (-8.2% 🟢) 0.393s 60 1.04x
🐘 Postgres Next.js (Turbopack) 0.794s 1.007s 0.212s 60 1.35x
💻 Local Next.js (Turbopack) 0.854s 1.005s 0.150s 60 1.46x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 6.063s (-68.1% 🟢) 7.787s (-63.5% 🟢) 1.724s 8 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.308s (-32.1% 🟢) 2.007s (-4.5%) 0.699s 45 1.00x
🐘 Postgres Express 1.401s (-29.1% 🟢) 2.030s (-10.1% 🟢) 0.628s 45 1.07x
💻 Local Express 1.459s (-51.6% 🟢) 2.005s (-44.1% 🟢) 0.546s 45 1.12x
💻 Local Nitro 1.555s (-48.8% 🟢) 2.007s (-46.6% 🟢) 0.452s 45 1.19x
🐘 Postgres Next.js (Turbopack) 1.899s 2.076s 0.177s 44 1.45x
💻 Local Next.js (Turbopack) 2.100s 3.008s 0.908s 30 1.61x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 13.919s (-59.7% 🟢) 15.732s (-57.3% 🟢) 1.812s 6 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.641s (-35.6% 🟢) 3.058s (-33.6% 🟢) 0.417s 40 1.00x
🐘 Postgres Express 2.709s (-32.1% 🟢) 3.058s (-30.0% 🟢) 0.348s 40 1.03x
💻 Local Express 3.207s (-65.2% 🟢) 3.911s (-61.0% 🟢) 0.704s 31 1.21x
💻 Local Nitro 3.395s (-63.5% 🟢) 4.010s (-60.0% 🟢) 0.615s 30 1.29x
🐘 Postgres Next.js (Turbopack) 3.814s 4.109s 0.295s 30 1.44x
💻 Local Next.js (Turbopack) 4.362s 5.010s 0.648s 24 1.65x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 27.867s (-78.6% 🟢) 30.086s (-77.2% 🟢) 2.219s 4 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.212s (-25.3% 🟢) 1.006s (~) 0.794s 60 1.00x
🐘 Postgres Express 0.224s (-20.7% 🟢) 1.006s (~) 0.782s 60 1.06x
🐘 Postgres Next.js (Turbopack) 0.278s 1.023s 0.745s 59 1.31x
💻 Local Nitro 0.421s (-30.3% 🟢) 1.004s (-1.7%) 0.583s 60 1.99x
💻 Local Express 0.462s (-17.6% 🟢) 1.021s (+1.7%) 0.560s 59 2.18x
💻 Local Next.js (Turbopack) 0.571s 1.004s 0.433s 60 2.70x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.151s (+10.1% 🔺) 3.757s (+3.3%) 1.606s 16 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.326s (-34.3% 🟢) 1.006s (~) 0.680s 90 1.00x
🐘 Postgres Express 0.326s (-36.0% 🟢) 1.006s (~) 0.680s 90 1.00x
🐘 Postgres Next.js (Turbopack) 0.462s 1.006s 0.544s 90 1.42x
💻 Local Nitro 2.053s (-19.1% 🟢) 2.580s (-14.3% 🟢) 0.526s 35 6.30x
💻 Local Express 2.087s (-17.0% 🟢) 2.609s (-13.3% 🟢) 0.522s 35 6.40x
💻 Local Next.js (Turbopack) 2.388s 3.043s 0.655s 30 7.33x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.251s (+72.4% 🔺) 6.995s (+45.5% 🔺) 1.744s 14 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.625s (-23.6% 🟢) 1.006s (-1.1%) 0.380s 120 1.00x
🐘 Postgres Nitro 0.661s (-16.4% 🟢) 1.006s (~) 0.345s 120 1.06x
🐘 Postgres Next.js (Turbopack) 0.937s 1.387s 0.450s 87 1.50x
💻 Local Nitro 9.415s (-15.9% 🟢) 10.024s (-14.1% 🟢) 0.609s 12 15.05x
💻 Local Express 9.489s (-15.2% 🟢) 10.198s (-14.6% 🟢) 0.709s 12 15.17x
💻 Local Next.js (Turbopack) 10.199s 10.861s 0.662s 12 16.31x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 13.741s (+85.2% 🔺) 15.593s (+68.7% 🔺) 1.852s 8 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -
▲ Vercel Nitro ⚠️ missing - - - -

🔍 Observability: Express

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.156s (+463.6% 🔺) 2.002s (+100.5% 🔺) 0.001s (-25.0% 🟢) 2.010s (+98.7% 🔺) 0.854s 10 1.00x
💻 Local Express 1.157s (+481.1% 🔺) 2.004s (+99.5% 🔺) 0.011s (-12.4% 🟢) 2.017s (+98.2% 🔺) 0.860s 10 1.00x
💻 Local Nitro 1.158s (+442.0% 🔺) 2.005s (+99.6% 🔺) 0.012s (-3.2%) 2.019s (+98.2% 🔺) 0.861s 10 1.00x
🐘 Postgres Nitro 1.167s (+469.1% 🔺) 1.996s (+99.7% 🔺) 0.001s (-13.3% 🟢) 2.010s (+98.8% 🔺) 0.843s 10 1.01x
💻 Local Next.js (Turbopack) 1.206s 2.003s 0.011s 2.017s 0.811s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.226s 2.002s 0.001s 2.011s 0.785s 10 1.06x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.942s (+17.4% 🔺) 3.685s (-9.9% 🟢) 2.387s (+148.5% 🔺) 6.626s (+18.5% 🔺) 3.685s 10 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: Express

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.574s (+107.9% 🔺) 2.012s (+95.5% 🔺) 0.010s (+2.0%) 2.023s (+94.6% 🔺) 0.450s 30 1.00x
🐘 Postgres Nitro 1.578s (+152.9% 🔺) 2.005s (+99.2% 🔺) 0.004s (-2.5%) 2.024s (+97.9% 🔺) 0.446s 30 1.00x
🐘 Postgres Express 1.579s (+150.6% 🔺) 2.006s (+99.3% 🔺) 0.003s (-13.8% 🟢) 2.022s (+97.7% 🔺) 0.444s 30 1.00x
💻 Local Next.js (Turbopack) 1.718s 2.010s 0.010s 2.024s 0.305s 30 1.09x
🐘 Postgres Next.js (Turbopack) 1.735s 2.010s 0.004s 2.026s 0.291s 30 1.10x
💻 Local Nitro 1.762s (+110.0% 🔺) 2.012s (+98.8% 🔺) 0.009s (-0.5%) 2.202s (+97.4% 🔺) 0.441s 28 1.12x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.981s (-8.0% 🟢) 7.136s (-10.9% 🟢) 0.234s (-42.7% 🟢) 7.822s (-11.5% 🟢) 1.841s 8 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: Express

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.692s (-28.5% 🟢) 1.013s (-18.8% 🟢) 0.000s (-18.6% 🟢) 1.023s (-18.6% 🟢) 0.331s 59 1.00x
🐘 Postgres Express 0.721s (-24.9% 🟢) 1.071s (-16.2% 🟢) 0.000s (+23.2% 🔺) 1.084s (-17.1% 🟢) 0.362s 56 1.04x
🐘 Postgres Next.js (Turbopack) 0.852s 1.111s 0.000s 1.126s 0.274s 54 1.23x
💻 Local Nitro 1.429s (+16.9% 🔺) 2.015s (~) 0.000s (+200.0% 🔺) 2.017s (~) 0.588s 30 2.06x
💻 Local Express 1.451s (+18.4% 🔺) 2.014s (~) 0.000s (-20.0% 🟢) 2.016s (~) 0.566s 30 2.10x
💻 Local Next.js (Turbopack) 1.517s 2.014s 0.000s 2.017s 0.500s 30 2.19x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.565s (-4.7%) 4.814s (-5.7% 🟢) 0.001s (+541.7% 🔺) 5.193s (-6.1% 🟢) 1.628s 12 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: Express

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

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.359s (-23.3% 🟢) 2.034s (-6.6% 🟢) 0.000s (+Infinity% 🔺) 2.048s (-6.8% 🟢) 0.689s 30 1.00x
🐘 Postgres Nitro 1.489s (-16.9% 🟢) 2.141s (~) 0.000s (+100.0% 🔺) 2.163s (-0.5%) 0.674s 28 1.10x
🐘 Postgres Next.js (Turbopack) 1.637s 2.146s 0.000s 2.153s 0.516s 28 1.20x
💻 Local Nitro 3.098s (-8.5% 🟢) 3.734s (-7.4% 🟢) 0.001s (+32.4% 🔺) 3.738s (-7.4% 🟢) 0.640s 17 2.28x
💻 Local Next.js (Turbopack) 3.123s 3.676s 0.001s 3.682s 0.558s 17 2.30x
💻 Local Express 3.229s (-6.9% 🟢) 3.845s (-4.7%) 0.000s (-84.4% 🟢) 3.847s (-4.7%) 0.618s 16 2.38x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.802s (+26.5% 🔺) 6.776s (+12.5% 🔺) 0.000s (+Infinity% 🔺) 7.235s (+12.0% 🔺) 1.433s 9 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -
▲ Vercel Nitro ⚠️ missing - - - - -

🔍 Observability: Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Express 15/21
🐘 Postgres Express 12/21
▲ Vercel Express 21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 22, 2026

🦋 Changeset detected

Latest commit: 45aa4c8

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

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

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

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

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 22, 2026

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

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment May 25, 2026 10:56am
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 25, 2026 10:56am
example-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-astro-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-express-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-fastify-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-hono-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-nitro-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-nuxt-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-sveltekit-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-tanstack-start-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workbench-vite-workflow Ready Ready Preview, Comment May 25, 2026 10:56am
workflow-docs Ready Ready Preview, Comment, Open in v0 May 25, 2026 10:56am
workflow-swc-playground Ready Ready Preview, Comment May 25, 2026 10:56am
workflow-tarballs Ready Ready Preview, Comment May 25, 2026 10:56am
workflow-web Ready Ready Preview, Comment May 25, 2026 10:56am

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 22, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
❌ ▲ Vercel Production 1232 1 219 1452
❌ 💻 Local Development 1483 14 219 1716
✅ 📦 Local Production 1629 0 219 1848
✅ 🐘 Local Postgres 1629 0 219 1848
✅ 🪟 Windows 132 0 0 132
✅ 📋 Other 748 0 176 924
Total 6853 15 1052 7920

❌ Failed Tests

▲ Vercel Production (1 failed)

nextjs-turbopack (1 failed):

  • DurableAgent e2e prepareStep on constructor stream-level prepareStep overrides constructor-level
💻 Local Development (14 failed)

nextjs-webpack-canary (7 failed):

  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling retry behavior FatalError fails immediately without retries
  • error handling catchability workflow throw round-trips FatalError + cause through run_failed event
  • error handling not registered WorkflowNotRegisteredError fails the run when workflow does not exist
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KSFCN8F2WCA5QNM2TRXQW90J

nextjs-webpack-stable-lazy-discovery-disabled (7 failed):

  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling retry behavior FatalError fails immediately without retries
  • error handling catchability workflow throw round-trips FatalError + cause through run_failed event
  • error handling not registered WorkflowNotRegisteredError fails the run when workflow does not exist
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KSFCN8F2WCA5QNM2TRXQW90J

Details by Category

❌ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 106 0 26
✅ example 106 0 26
✅ express 106 0 26
✅ fastify 106 0 26
✅ hono 106 0 26
❌ nextjs-turbopack 129 1 2
✅ nextjs-webpack 130 0 2
✅ nitro 106 0 26
✅ nuxt 106 0 26
✅ sveltekit 125 0 7
✅ vite 106 0 26
❌ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 107 0 25
✅ express-stable 107 0 25
✅ fastify-stable 107 0 25
✅ hono-stable 107 0 25
✅ nextjs-turbopack-canary 113 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 132 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 132 0 0
❌ nextjs-webpack-canary 106 7 19
❌ nextjs-webpack-stable-lazy-discovery-disabled 125 7 0
✅ nitro-stable 107 0 25
✅ nuxt-stable 107 0 25
✅ sveltekit-stable 126 0 6
✅ vite-stable 107 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 107 0 25
✅ express-stable 107 0 25
✅ fastify-stable 107 0 25
✅ hono-stable 107 0 25
✅ nextjs-turbopack-canary 113 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 132 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 132 0 0
✅ nextjs-webpack-canary 113 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 132 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 132 0 0
✅ nitro-stable 107 0 25
✅ nuxt-stable 107 0 25
✅ sveltekit-stable 126 0 6
✅ vite-stable 107 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 107 0 25
✅ express-stable 107 0 25
✅ fastify-stable 107 0 25
✅ hono-stable 107 0 25
✅ nextjs-turbopack-canary 113 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 132 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 132 0 0
✅ nextjs-webpack-canary 113 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 132 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 132 0 0
✅ nitro-stable 107 0 25
✅ nuxt-stable 107 0 25
✅ sveltekit-stable 126 0 6
✅ vite-stable 107 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 132 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 107 0 25
✅ e2e-local-dev-tanstack-start- 107 0 25
✅ e2e-local-postgres-nest-stable 107 0 25
✅ e2e-local-postgres-tanstack-start- 107 0 25
✅ e2e-local-prod-nest-stable 107 0 25
✅ e2e-local-prod-tanstack-start- 107 0 25
✅ e2e-vercel-prod-tanstack-start 106 0 26

📋 View full workflow run


Some E2E test jobs failed:

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

Check the workflow run for details.

Implements the V5 Workflow Attributes MVP per the changelog plan:

- @workflow/world: shared validation + apply helpers; optional
  experimentalSetAttributes on Storage.runs; attributes field on
  WorkflowRunBaseSchema. Optional so third-party worlds keep working.
- @workflow/core: setAttributes() helper. Detects workflow VM vs step
  context, normalizes undefined→null, validates client-side, dispatches
  via an internal "use step" function. Feature-detects the world method
  and no-ops with a one-time warning if missing.
- @workflow/world-local: file-backed impl with a per-run async mutex
  so concurrent writes do not lose updates within a process. Threads
  attributes through the run lifecycle event reconstructions so they
  survive subsequent run_started/_completed/_failed/_cancelled writes.
- @workflow/world-postgres: jsonb column with SQL-side atomic merge
  (jsonb_set / `-`); 0013 migration.
- @workflow/world-vercel: HTTP wrapper posting the documented
  { changes: [...] } body to /v2/runs/:runId/attributes.

Tests: 18 validation unit + 10 SDK unit + 10 world-local integration +
3 world-postgres integration. End-to-end coverage in the workbench is
deferred until the paired workflow-server endpoint is deployed to a
preview.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous design used a 'use step' indirection inside @workflow/core
so setAttributes could be called from both workflow and step bodies via
a single SDK surface. That broke nextjs-webpack Local Dev: the deferred-
entries discoverer in webpack dev mode walks transitive imports from
'use step' files, and putting a step file inside @workflow/core/dist
pulled host-side world adapters and @vercel/queue into the
step-discovery graph. Webpack's regex-based import extractor then blew
the call stack with "RangeError: Maximum call stack size exceeded at
RegExpStringIterator.next" on tarball-installed deployments.
runtime/start.ts and runtime/run.ts get away with the same directive
because they're never reachable from packages/core/src/workflow/index.ts
(the VM bundle entry); ours was.

A host-side bridge comparable to sleep would have fixed it but is
substantial wiring for a feature whose end state (event-sourced
attr_set) replaces the bridge mechanism entirely. Pragmatic MVP path:
restrict to step body and let users wrap in a step explicitly. Full
5.0.0 lifts the restriction via attr_set events through the workflow
controller; SDK signature is stable across the cutover.

- Workflow-VM-side setAttributes throws FatalError with wrap-in-step
  instructions
- Step-side setAttributes works (validates, dispatches, world-detects)
- set-attributes-shared.ts is now pure validation; no 'use step', no
  world imports
- Test updated to assert the FatalError on workflow-body calls
- Changelog MDX updated with the new scope + a "Why workflow-body
  dispatch is deferred" implementation note

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread packages/core/src/set-attributes-shared.ts Outdated
vercel Bot and others added 10 commits May 23, 2026 07:09
… setAttributes is "supported via the host-side bridge in `set-attributes.ts`, which calls into an actual step via `registerStepFunction`" — no such bridge or registerStepFunction usage exists.

This commit fixes the issue reported at packages/core/src/set-attributes-shared.ts:25

**Bug:** The comment on lines 24-26 of `packages/core/src/set-attributes-shared.ts` describes an intermediate design approach that was abandoned before the final implementation. It states: "workflow-body use is supported only via the host-side bridge in `set-attributes.ts`, which calls into an actual step via `registerStepFunction`."

In the actual final implementation:

1.  `packages/core/src/workflow/set-attributes.ts` (the workflow-VM-side export) unconditionally throws `FatalError` — there is no bridge support at all.
2.  `packages/core/src/set-attributes.ts` (the host-side export) explicitly checks for workflow-body context and throws `FatalError` with a message telling users to wrap the call in a `'use step'` function.
3.  A grep for `registerStepFunction` in combination with `setAttributes` returns zero results — no such wiring exists.

The comment is misleading to any developer reading the codebase: it implies workflow-body use works via a bridge mechanism, when in fact it throws a fatal error.

**Fix:** Updated lines 24-26 to accurately describe the actual behavior: "workflow-body use throws FatalError — users must wrap the call in their own `'use step'` function." This aligns with the implementation in both `set-attributes.ts` and `workflow/set-attributes.ts`, and with the JSDoc on `setAttributes` which explicitly documents the step-body-only restriction and the workaround pattern.


Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
For local e2e validation against the workflow-server attributes-mvp
preview deployment. Do not merge — this constant must be empty on main.

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

Workflow-body `setAttributes` calls now dispatch through an internal
`__builtin_set_attributes` step rather than throwing FatalError. The
workflow-VM helper validates input and then invokes a host-side
useStep dispatcher pre-bound under WORKFLOW_SET_ATTRIBUTES; the step
body forwards to the same world adapter call the step-body path uses.

Putting the 'use step' directive inside `packages/workflow/src/internal/builtins.ts`
(next to the existing `__builtin_response_*` builtins) instead of
`@workflow/core/dist/` avoids the deferred-entry discoverer hazard
that motivated the prior MVP-only step-body restriction.

- Add `WORKFLOW_SET_ATTRIBUTES` symbol + workflow.ts wiring
- New `step-set-attributes.ts` host helper (`applySetAttributesChanges`)
  shared between step-body and workflow-body paths
- `__builtin_set_attributes` builtin step
- Refactor workflow-side `setAttributes` to dispatch via the bridge
- Update changelog MDX + add tests (16 unit, 2 e2e)
- e2e workflows `setAttributesFromStepWorkflow` and
  `setAttributesFromWorkflowBodyWorkflow` cover both paths

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The comment described the prior FatalError-on-workflow-body workaround;
update it to reflect the current bridge-via-builtins design.

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

`__builtin_set_attributes` dynamically imported
`@workflow/core/_step-set-attributes` as a literal string. The Next.js
deferred-entries discoverer in `@workflow/next/builder-deferred.ts`
matches `import('...')` regex-style and walks the resolved file's
transitive imports — which reach the world adapter and `@vercel/queue`,
triggering `RangeError: Maximum call stack size exceeded` inside
`RegExpStringIterator.next` on tarball-installed nextjs-webpack
builds. The build never completes, so dev-mode e2e tests time out
across the board.

Assemble the specifier at runtime (same trick `get-world-lazy.ts`
uses for `./world.js`) so the discoverer doesn't see a literal target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
Signed-off-by: Peter Wielander <peter.wielander@vercel.com>
…idge

The runtime-built specifier broke step bundle resolution across every
framework — `Cannot find package '@workflow/core' imported from
.../node_modules/.nitro/workflow/steps.mjs`. Bundlers can't statically
resolve a concatenated string, so the dependency never lands in the
step bundle and Node's loader fails at runtime.

Reverts fbb0c59. The original webpack-dev discoverer-overflow it
tried to fix only affected 3 jobs; this regression took down 30+ jobs
across all frameworks. The discoverer issue needs a different fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ngle dispatch path

Drops step-body support for the MVP. The architecture becomes:

  - workflow VM `setAttributes` validates inline and dispatches via the
    standard `globalThis[WORKFLOW_USE_STEP]('__builtin_set_attributes')`
    mechanism — same as every other step call from a workflow body
  - host-side `setAttributes` is a stub that throws FatalError telling
    callers to use a workflow body
  - `__builtin_set_attributes` step body reads world + run id directly
    from `globalThis` symbols populated by the runtime, with no imports
    from `@workflow/core`

This deletes the bridge plumbing the previous design needed:

  - `WORKFLOW_SET_ATTRIBUTES` global symbol + the workflow.ts pre-bind
  - `packages/core/src/set-attributes-shared.ts` (normalize helper)
  - `packages/core/src/step-set-attributes.ts` (host-side helper)
  - the `@workflow/core/_step-set-attributes` package export and the
    dynamic import that pulled it in from the step bundle

Side benefit: the Next.js deferred-entries discoverer can no longer
walk from `__builtin_set_attributes` into the world adapter / queue
chain that broke webpack-dev builds in the previous shape, because the
step body holds zero @workflow/core imports.

Workbench example and e2e tests reduced to a single
`setAttributesWorkflow` covering workflow-body dispatch. World-side
implementations are unchanged; step-body support can be added later
without touching the workflow-body contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment on lines +62 to +63
const WORKFLOW_SERVER_URL_OVERRIDE =
'https://workflow-server-git-peter-attributes-mvp.vercel.sh';
Copy link
Copy Markdown
Contributor

@vercel vercel Bot May 25, 2026

Choose a reason for hiding this comment

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

WORKFLOW_SERVER_URL_OVERRIDE is set to a preview branch URL instead of an empty string, which would route all production @workflow/world-vercel clients to a preview-branch workflow-server if merged to main.

Fix on Vercel

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant