Durable execution that reads like plain code
Orchestrating long-running, stateful logic on stateless infrastructure has traditionally meant choosing between message queues, job runners, microservice choreography, or a full workflow engine. Each comes with its own tax: standing up new infrastructure, writing explicit task graphs, or managing worker fleets. Workflow SDK, an open-source project from Vercel, takes a different route by treating the programming language itself as the workflow definition.
The project began after its author spent six months on a Temporal fork, trying to build a serverless-oriented experience. The realization that shipping that DX required owning the execution environment led to abandoning the fork and building a new framework from scratch with Nathan Rajlich.
Your code already describes the graph
A workflow is fundamentally a directed acyclic graph. Older frameworks like Apache Airflow force developers to draw that DAG explicitly, burying actual logic inside task nodes. But a programming language already expresses sequencing, branching, and parallelism naturally through control flow. An AST is a DAG. Temporal demonstrated that writing normal sequential code that executes durably underneath is possible — the missing piece was making that easy to operate.
Standing up Temporal from scratch is a project in itself:
- Provisioning Temporal Cloud or self-hosting the server (Frontend, History, Matching, Worker services, plus a database backend with sharding)
- Running your own worker fleet, since Temporal never executes your code itself — in practice that means owning a Kubernetes cluster
- Wiring task queues, activity registration, and client configuration
- Managing worker scaling, uptime, restarts, and build-and-deploy pipelines
- Configuring mutual TLS and a data converter for payload encryption between workers and the control plane
Beyond the operational overhead, evolving workflow code while runs are in flight creates a determinism problem. Replaying old event history against new code throws non-determinism errors. The standard patching API (patched() / GetVersion) lets you branch on a change ID and migrate through a deprecate-then-remove lifecycle, but accumulated version flags rot the code over time.
Temporal's signaling model also proved counterintuitive. Signals, queries, and updates are three separate primitives for getting data in and out of a running workflow, each with its own semantics: queries can't block, signals are fire-and-forget and buffered. Workflow SDK collapses all three into a single hook concept.
Directives and hooks replace workflow scaffolding
Workflow SDK splits a TypeScript file into orchestrator and step roles using two directives. "use workflow" marks the orchestrator; "use step" marks side-effecting work. Everything between them is ordinary await, try/catch, Promise.all, loops, and conditionals.
order-workflow.ts
export async function processOrderWorkflow(orderId: string) {
"use workflow";
const order = await fetchOrder(orderId);
await chargePayment(order);
return { orderId, status: "completed" };
}
async function chargePayment(order: Order) {
"use step"; // full Node.js access in here
const charge = await stripe.charges.create({ /* ... */ });
return { chargeId: charge.id };
}
An order workflow calling one durable step. Uncaught errors in the step retry automatically.
This design yields several properties without explicit configuration:
- No DAG file: The compiler reads the directives and splits code into workflow and client/step bundles based on control flow.
- Retries by default: Uncaught errors retry automatically. Throw
new FatalError(...)to stop, ornew RetryableError(..., { retryAfter })for custom backoff.fn.maxRetries = ntunes the limit. - Ad-hoc webhooks:
createWebhook()creates a callable URL inside a running workflow in one line. The run blocks until the URL is hit — no route or handler required. - Hooks: The more general primitive under webhooks.
createHook<T>()awaits incoming data, replacing signals, queries, and updates.
approve-expense.ts
export async function approveExpense(expense: Expense) {
"use workflow";
// A real, callable URL, created inside the run
const webhook = createWebhook();
// Sending the email is a durable step
await emailManager(expense.managerEmail, webhook.url);
// Parks here until someone POSTs to webhook.url
const request = await webhook;
const { approved } = await request.json();
return { expenseId: expense.id, approved };
}
An approval workflow that parks on a webhook until someone responds, for seconds or weeks.
Library over platform
Traditional engines require bringing infrastructure to the framework. Workflow SDK inverts that: the framework runs on infrastructure you already have. The architecture draws on DBOS, an open-source durable execution library that needs only Postgres on the server side.
There's no bespoke orchestrator to self-host, no new stateful system to operate. The required pieces are your app, a database, and a queue — all handled inside the library.
The backend is swappable by design. The runtime talks to a single interface called the World, covering storage, queuing, auth, and streaming. Any substrate can back it:
- Redis, Kafka, or other streams
- Postgres, Cassandra, the file system, Turso, or Durable Objects for durability
- Vercel Queues, SQS, or Cloudflare Queues for queueing
Swapping a layer doesn't touch workflow code. A first-party Postgres world, inspired by DBOS, uses Postgres for durability, queueing, and streaming. In every world, there's no worker fleet or control plane — a framework integration exposes two plain HTTP endpoints that deploy with the rest of your app.
The Vercel Workflow Server is itself stateless, doing no compute or orchestration. It's a CRUD API extending the Postgres world with Vercel authentication and multi-tenancy, running as a regular Vercel deployment. The actual workflow logic lives in the open-source, Apache-licensed client-side library. The managed offering is just one implementation of a swappable spec, not a black box.
Version pinning moves complexity upstream
Some design choices lean on Vercel's platform. Versioning is the clearest example. Workflow SDK pins each run to the deployment that started it. The run continues executing against that exact code copy, so code changes never break in-flight runs. This was straightforward to build because Vercel already retains immutable deployments for long periods. The official Postgres world doesn't yet track and route versions this way, though community implementations like Platformatic's version-safe durable workflows on Kubernetes have implemented the spec as intended.
This trade-off is deliberate: pinning shifts the versioning burden from workflow developers onto infrastructure and the framework authors themselves. If every user of a framework hits the same hard problem and solves it individually with painful workarounds, the framework has failed.
The performance goal: steps should be free
Each step invocation currently involves networking and a queue round-trip to durably commit its result. That's correct for reliability, but it cuts against the pitch that distributed computing should feel like function calls. Ordinary function call overhead is effectively free; if steps carry heavy cost, developers start rationing them and second-guessing checkpoint granularity.
The stated goal is blunt: steps should be free. Ideal workflow code means taking any existing function, adding "use step", and getting microservice-level performance, networking, observability, and availability with no serialization constraints.
Workflow v5 (in beta) already delivers up to a 5x performance improvement with no user-facing API changes. v6 aims to push further while matching third-party worlds to first-party performance. Testing starts with npm install workflow@beta, with details shared in the project's GitHub discussion.
Workflow SDK positions itself as a spec with an extensible library core: durable workflows written as ordinary code, running on existing infrastructure, with enough speed that reaching for a step requires no second thought.



