Skip to content

Add opt-in QuickJS WASM VM engine (WORKFLOW_VM=quickjs) - #3048

Draft
TooTallNate wants to merge 13 commits into
mainfrom
quickjs-vm
Draft

Add opt-in QuickJS WASM VM engine (WORKFLOW_VM=quickjs)#3048
TooTallNate wants to merge 13 commits into
mainfrom
quickjs-vm

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Summary

Adds an experimental QuickJS WASM VM engine for workflow execution, opt-in via WORKFLOW_VM=quickjs (or per-run executionContext.workflowVm). The engine performs the same full event replay as the default node:vm engine, but runs workflow code in a QuickJS-NG VM compiled to WebAssembly via quickjs-wasi@3.1.0.

This is PR 1 of the incremental revival of the snapshot-runtime effort (#1300, RFC #1298): the QuickJS VM is deliberately decoupled from snapshotting. Motivations:

  • Enables platforms that do not implement node:vm (e.g. Cloudflare Workers — WASM only)
  • Foundation for VM-memory snapshotting follow-ups (threshold-based snapshots, build-time initial snapshots) without the always-snapshot overhead that stalled the original branch
  • Same-deployment coexistence: the engine choice is stamped into executionContext at start(), so a run keeps executing on the engine it started on

Results

  • 135/135 e2e tests pass with WORKFLOW_VM=quickjs against the nextjs-turbopack workbench (local dev, world-local)
  • +5.6% e2e wall clock vs node:vm (the original snapshot branch was ~+25%; the delta there was dominated by per-suspension snapshot save/load round-trips, which this PR does not do)
  • Full core unit suite green (1,563 tests), 29 new engine unit tests

How it works

  • WORKFLOW_VM=node|quickjs (runtime/vm-mode.ts), default node. Per-run affinity via executionContext.workflowVm, validated at startup.
  • runtime/quickjs-runtime.ts: every invocation creates a fresh QuickJS VM, evaluates the workflow bundle, re-executes the workflow from the top, and resolves awaited primitives from the recorded event log (processEvents). Workflow primitives (useStep, sleep, createHook, setAttributes, AbortController) are implemented as VM-side bootstrap JS backed by a pending-op/resolver model.
  • runtime/quickjs-entrypoint.ts: loads the full event log, runs the VM, and dispatches durable side effects for pending ops (step/hook/wait/attribute events + step queueing via the unified V2 queue) on suspension, plus a terminal drain (abort recordings, system-hook disposal, fire-and-forget writes) on completion/failure — mirroring drainPendingQueueItems.
  • VM boundary serialization via a bundled devalue codec (vm-serde-bundle), with the WASM binary + native extensions (encoding, headers, url, structured-clone) base64-embedded so no filesystem access or file tracing is needed at runtime.
  • The VM bootstrap is structured in two phases (static init vs per-run init) as groundwork for restoring from a build-time VM snapshot in a follow-up.

Determinism model

Full replay requires every invocation of a run to regenerate identical correlationIds and observe identical time:

  • PRNG seed: runId:workflowName:deploymentId (invocation-stable — notably NOT startedAt, which differs between turbo's synthesized run object and the durably stored run), matching the node:vm engine. Identical ids across concurrent invocations are load-bearing for the world's per-(run, correlation) dedup.
  • ULID timestamps: pinned to the run creation time embedded in the runId ULID.
  • Clock: Date.now() / new Date() inside the VM read a host-controlled WASI clock_time_get override that starts at run creation and advances to each processed event's createdAt — the same deterministic replay clock as the node:vm engine's updateTimestamp.

Feature parity work (beyond the original branch)

The WIP branch predated ~2.5 months of main. Ported into the VM model: setAttributes (attr_set events + immediate re-invoke), hook-backed AbortController/AbortSignal (system hooks, abort stream packets, statics), hook.getConflict() + conflict adoption (conflicting Run revived through the VM class registry), HookConflictError/RuntimeDecryptionError serialization, byte-stream framing round-trip (webhook bodies), cross-run writable forwarding symbols, bound step proxies (.bind override preserving stepId/__boundThis), decrypt+decompress of specVersion≥5 payloads before VM handoff, and turbo-safe re-invocation via reinvoke().

Scope / deliberately deferred

  • No world.snapshots interface, no snapshot persistence, no compression/encryption of VM state — that is PR 3/4 (threshold-based snapshotting) per the roadmap
  • No inline-step fast path in the QuickJS entrypoint yet (steps go through the queue) — PR 2 alongside benchmarks
  • Vercel-prod e2e legs stay node-only (deployed apps would need WORKFLOW_VM set project-side); CI covers a quickjs axis on the vitest-plugin job and a nextjs-turbopack leg across the local dev/prod/postgres e2e jobs
  • quickjs-wasi added to minimumReleaseAgeExclude (3.1.0 published same-day; vercel-labs package)

Docs

Documented WORKFLOW_VM in v5 Runtime Tuning (docs/content/docs/v5/configuration/runtime-tuning.mdx).

@TooTallNate
TooTallNate requested review from a team and ijjk as code owners July 22, 2026 08:42
Copilot AI review requested due to automatic review settings July 22, 2026 08:42
@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7ccf0f0

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

This PR includes changesets to release 16 packages
Name Type
@workflow/core Minor
workflow Minor
@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/nuxt Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch

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

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

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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

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

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

Adds an experimental QuickJS WASM-based workflow VM engine to @workflow/core, selectable via WORKFLOW_VM=quickjs (or per-run affinity via executionContext.workflowVm), while keeping the existing node:vm replay engine as default. This expands the runtime’s portability (WASM-only platforms) and lays groundwork for later snapshotting work, while maintaining the same full event-replay semantics.

Changes:

  • Introduces QuickJS runtime + entrypoint implementing full replay, pending-op extraction, and durable side-effect dispatch/queueing.
  • Adds VM-compatible serialization (devalue reducers/revivers + bundled serde IIFE) and QuickJS asset embedding (WASM + extensions).
  • Extends CI/test matrix and docs to cover the new WORKFLOW_VM option.

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
scripts/create-test-matrix.mjs Adds an e2e leg that runs nextjs-turbopack with WORKFLOW_VM=quickjs.
pnpm-workspace.yaml Excludes quickjs-wasi from minimum release age checks.
pnpm-lock.yaml Locks quickjs-wasi@3.1.0.
packages/core/turbo.json Adds generated QuickJS/serde artifacts to Turbo build outputs.
packages/core/src/telemetry/semantic-conventions.ts Adds semantic convention keys for VM engine + QuickJS runtime stats.
packages/core/src/source-map.ts Adds stripInlineSourceMap() to reduce QuickJS heap usage.
packages/core/src/source-map.test.ts Adds unit tests for inline source map stripping.
packages/core/src/serialization/workflow-vm.ts Adds VM-safe workflow-mode (de)serialization (no Node deps).
packages/core/src/serialization/workflow-vm.test.ts Validates VM serializer round-trips + Node/VM compatibility.
packages/core/src/serialization/vm-bundle-entry.ts Entry point for esbuild’d VM serde bundle (installs serialize/deserialize + ULID generator).
packages/core/src/serialization/reducers/common-vm.ts VM-safe reducers/revivers (base64 via atob/btoa, error subclass support, Web API-ish objects).
packages/core/src/serialization/compat.test.ts Adds compatibility coverage between new modules and legacy serialization pipeline.
packages/core/src/serialization/codec-devalue-vm.ts VM-compatible devalue codec, including AbortController/Signal handling for workflow VM context.
packages/core/src/runtime/vm-mode.ts Adds WORKFLOW_VM parsing + run-affinity selection logic.
packages/core/src/runtime/vm-mode.test.ts Unit tests for VM mode selection precedence and validation.
packages/core/src/runtime/start.ts Stamps executionContext.workflowVm at run start when WORKFLOW_VM is set.
packages/core/src/runtime/quickjs-runtime.ts Implements QuickJS VM execution + replay processing + pending-op model.
packages/core/src/runtime/quickjs-runtime.test.ts Adds unit tests for replay semantics, determinism, clock, abort, etc.
packages/core/src/runtime/quickjs-entrypoint.ts Implements host-side entrypoint: fetch events, run VM, create events/queue work, finalize runs.
packages/core/src/runtime.ts Dispatches to QuickJS engine when selected, including turbo-safe reinvocation behavior.
packages/core/scripts/build-vm-serde-bundle.js New build step to generate bundled serde string module for VM bootstrap.
packages/core/scripts/build-quickjs-assets.js New build step to embed QuickJS WASM + extensions into TS for runtime import.
packages/core/package.json Adds build steps + quickjs-wasi dependency.
packages/core/.gitignore Ignores generated QuickJS asset + serde bundle TS outputs.
docs/content/docs/v5/configuration/runtime-tuning.mdx Documents WORKFLOW_VM configuration and behavior.
.github/workflows/tests.yml Adds vitest-plugin matrix for WORKFLOW_VM and plumbs WORKFLOW_VM into e2e jobs.
.changeset/quickjs-vm-engine.md Adds changeset for @workflow/core + workflow minor bump.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

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

Comment thread packages/core/src/source-map.ts Outdated
Comment thread packages/core/src/runtime/quickjs-entrypoint.ts Outdated
Comment thread packages/core/scripts/build-vm-serde-bundle.js Outdated
Comment thread packages/core/scripts/build-quickjs-assets.js
@TooTallNate
TooTallNate marked this pull request as draft July 22, 2026 08:47
…les; harden step-listing e2e assertions against eventually-consistent reads
… assertions (analytics listing can omit attempt entirely)
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​quickjs-wasi@​3.1.07810010095100

View full report

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.

2 participants