This started as an old, now-deleted X post. Most of the original content is gone and I’ve reconstructed the details as accurately as possible, but the core ideas remain solid.

A 7-hour spelunk through the use workflow source code: SWC plugins, durable state machines, HTTP orchestration loops, and the surprisingly elegant engine underneath it all.

This was written live, discovery-first. Every time I uncovered a piece of the puzzle, I wrote it down immediately. The flow reflects the actual order of exploration. You’ll still get the complete picture.

* * *

The Surface: SWC Plugin

Everything starts with a Rust SWC plugin you wire into your bundler config. This is the compiler-level hook that intercepts your code and transforms both use step and use workflow before it ever runs. The magic of the whole system lives behind this transformation.

What happens to “use step”

When the compiler sees a function annotated with "use step", it takes that entire function and registers it into a global map. Simple, but critical.

packages/core/src/private.ts
// The global step registry. Just a Map.
const stepRegistry: Map<string, StepsFunction> = new Map();

Every "use step" function lands in this registry under a generated step ID. The actual business logic is stored here, waiting to be invoked later.

What happens to “use workflow”

The "use workflow" function gets wrapped in a proxy. The compiler replaces every call to your step functions inside the workflow with this:

// Generated by SWC. Your original call is replaced with this.
return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//workflows/user-signup.ts//createUser")(...args);

Your code no longer calls your business logic directly. It calls a runtime-injected function that wraps your logic. This is how Vercel intercepts every step call without touching your source manually.

SWC Transform Pipeline
Your SourceSWC Plugin (Rust)Step Registry (Map) + Proxy Wrapper (globalThis)
* * *

Starting the Engine: start()

When you call start() on a workflow, here’s what actually happens:

// High-level flow inside start()
const workflowId = getWorkflowId(); // bundled by SWC
const world = getEnvironment();     // local | vercel | custom
const args = dehydrateWorkflowArguments(input);

// Durable state storage — tracks every step, every output
const store = await world.createDurableStorage(workflowId);

// Enqueue the workflow to run via HTTP
await world.queue(workflowId, args);

Two things are key here. First, a durable storage object is created that will track every step input, output, and status for the entire lifecycle of this workflow. Second, world.queue() is just an HTTP call to /.well-known/workflow/v1/flow. That’s it. The workflow hasn’t run any of your code yet.

Why queue instead of run directly? Because “run now” means “run synchronously on this machine.” Queueing means the work can survive crashes, be retried, and resume from a checkpoint. The queue is the durability guarantee.

* * *

The Two Generated Endpoints

The SWC plugin doesn’t just transform your step functions. It also injects two route handlers directly into your app directory. These are the arteries of the whole system:

Generated Endpoints
/.well-known/workflow/v1/flow (orchestrates & resumes)/.well-known/workflow/v1/step (executes step logic)

Both are populated with your generated code by the compiler. The /flow endpoint gets the wrapped workflow functions (no actual step logic). The /step endpoint gets the actual step business logic via registerStepFunction.

* * *

Inside /flow: The Orchestrator

When /flow gets called, it invokes workflowEntrypoint. Here’s what that does:

// workflowEntrypoint inside /flow
const { runId } = parseQueuedMessage(request);

// Transition state: pending → running
await store.updateStatus(runId, "running");

// Run the workflow — but steps won't execute yet
await runWorkflow(workflowFn, context);

Inside runWorkflow, the globalThis[Symbol.for("WORKFLOW_USE_STEP")] gets defined. This is the injection point: your step proxy now points to useStep which has full workflow context.

The workflow function runs inside a Node.js VM sandbox. The whole thing executes, but here’s the twist: none of your step code runs yet.

WorkflowSuspension: the signal

Inside useStep, every step that hasn’t been completed yet throws a WorkflowSuspension error. Not a real error. A signal. It tells the system: “these steps need to run.”

// Inside useStep — simplified
const result = store.getStepResult(stepId);

if (result.isDone()) {
  return result.value; // Already ran, skip it
}

// Not done — signal suspension
throw new WorkflowSuspension([stepId]);

Back in workflowEntrypoint, the WorkflowSuspension is caught. Every suspended step gets pushed into a queue with the prefix __wkf_step_{step_name}, and an HTTP request fires to /.well-known/workflow/v1/step for each one.

Suspension to Step Execution
/flow runsNode VM executes workflow fnWorkflowSuspension throwncatches suspensionStep Queue/step endpoint (HTTP call per step)
* * *

Inside /step: Where Your Code Actually Runs

This is the endpoint where your business logic finally executes. The generated route has all your step functions registered via registerStepFunction. When a step request arrives, stepEntrypoint takes over:

// stepEntrypoint — simplified
const fn = stepRegistry.get(stepId);
const args = hydrateArguments(rawArgs);

try {
  const result = await fn(...args);
  await store.persistResult(stepId, result); // saved to durable storage
} catch (err) {
  await store.persistError(stepId, err);     // errors too
}

// Signal the workflow to resume
await world.queue(workflowId); // fires /.well-known/workflow/v1/flow again

After executing, the step result is persisted to durable storage. Then the workflow is re-queued by calling /flow again via HTTP. The cycle restarts.

* * *

The Loop: How It Keeps Going

There’s no long-running process. No background daemon. No stateful server sitting around. It’s just HTTP calls chaining.

Full Execution Loop
start()/flow runsSuspension thrown/step runsResult persisted/flow re-queuedrepeat until done

Every time /flow runs, it checks the durable storage. Steps that already completed are skipped instantly (their outputs are replayed from storage). New steps throw WorkflowSuspension and get dispatched. The whole workflow is a loop of HTTP calls updating and reading a JSON state file.

The workflow function can be replayed from scratch every single time /flow fires. Completed steps don’t re-execute, they return their cached result. This is how you get “no double side effects” and “no duplicate emails” for free. Design your steps to be idempotent anyway — don’t rely on this as a safety net.

* * *

Why This Architecture?

The genius here isn’t any single piece. It’s the combination:

  • SWC plugin — zero developer overhead. You write normal TypeScript. The compiler instruments it.
  • Durable storage — every step input and output is persisted. Crash the server mid-workflow, it picks up exactly where it left off.
  • WorkflowSuspension as a control signal — using exceptions for flow control sounds alarming until you see how clean it makes the orchestration logic.
  • Serverless-native HTTP loop — no long-running processes. Every invocation is stateless. The state lives in storage.
  • Pluggable world interface — run locally with SQLite, run on Vercel with their infra, point it at Postgres. The engine doesn’t care. “Make any TypeScript function durable” isn’t marketing. It literally just needs a world with storage and a queue.
* * *

What I Didn’t Cover

Seven hours and there’s still surface area I didn’t get to. Worth knowing these exist:

  • How workflow arguments are serialized via dehydrateWorkflowArguments and how custom types survive that round-trip
  • The full durable storage schema — how it tracks step dependencies and partial graph state
  • The Node VM sandboxing specifics — what’s exposed, what’s not, why it matters for replay safety
  • The custom fetch implementation inside the workflow world
  • How Request and Response become step-based streams
  • Max attempts, backoff, and error escalation paths
tl;dr

SWC plugin instruments your code at compile time. start() creates durable storage and fires an HTTP request to /flow. /flow runs your workflow in a VM — steps throw WorkflowSuspension if not yet done. Each suspended step fires as an HTTP call to /step. /step runs your business logic, persists the result, then re-queues /flow. Loop continues. Completed steps are skipped via cached results. Finish when no new suspensions are thrown.

All the code analyzed came directly from vercel/workflow on GitHub.