The best workflow engine is a programming language
The idea of orchestrating long-running, stateful logic on top of unreliable, stateless infrastructure isn't new. We've had message queues, job runners, microservice choreographies, and full-blown workflow engines for a long time. What we never had was a version of it that felt good to write.
I'd spent about six months working on a fork of Temporal, mostly on weekends, trying to turn it into a serverless answer with DX that felt more Vercel-native. Eventually it dawned on me that to ship that experience, I'd need to own the execution environment too. So I dropped the fork, joined Vercel, and started hacking on a new framework from scratch alongside Nathan Rajlich.
Code is already a DAG
A workflow is a DAG, a directed acyclic graph. Before Temporal (and Cadence before it), nearly every workflow framework made you draw that DAG by hand. Apache Airflow is the canonical example. You describe your pipeline as an explicit graph of tasks and dependencies, and your actual logic gets buried inside the nodes."
That always felt backwards to me. We already have a tool for expressing "do this, then that, and these in parallel, and branch here." It's called a programming language. An abstract syntax tree is a DAG. Software itself is a DAG.
I knew that in the abstract, but it didn't truly hit me until I saw Temporal, where you write what looks like normal sequential code and the engine makes it durable underneath. It was the dream all along.
What running Temporal taught me
Temporal is great once your infrastructure exists. I was starting from scratch, and setting it up meant:
- Standing up Temporal Cloud (or self-hosting the server: Frontend, History, Matching, and Worker services, plus a Cassandra/Postgres/MySQL backend and sharding).
- Running your own worker fleet: Temporal never executes your code. Your workers poll the server and run your workflows and activities. In practice that's a Kubernetes cluster you own.
- Wiring it all together: task queues, activity registration, client config.
- Managing the workers yourself: scaling, uptime, restarts, and the build-and-deploy pipeline for the worker processes.
- Setting up encryption: workers talk to the control plane over the internet, so you're configuring mutual TLS and a data converter to encrypt payloads before they leave your environment.
Once all of that was in place, the workflow dream mostly became real. But I wanted something easy to start that could still scale as my project grew into production.
Versioning in-flight workflows
Beyond owning the worker build-and-deploy process myself, there was a harder problem. If I changed my workflow code while runs were still in flight, I was kind of stuck. There were no longer any workers running the old, correct version of that code. Replay would hit my new code with an old event history and break on a non-determinism error.
The official answer is the patching API (patched() / GetVersion). You branch your code on a change ID and march it through a multi-stage deprecate-then-remove lifecycle as old runs age out of retention. It works, but it means evolving workflow code very carefully. Over enough changes, the workflow code rots into a thicket of version flags.
I couldn't explain Temporal signals
Whenever I tried to show running Temporal demos to friends, signals / queries / updates turned into the single most confusing part. They're three separate primitives for getting data in and out of a running workflow, each with its own rules. Queries can't block, signals are fire-and-forget and get buffered, and you have to know when to reach for which. The concepts did land but it wasn't very intuitive. To me, that was a DX smell. If I can't explain the human-in-the-loop primitive without a whiteboard, it's too complicated. That's why Workflow SDK replaces all three with a single primitive, the hook.
What Workflow SDK looks like today
With Workflow SDK, you write one file of normal code. "use workflow" marks an orchestrator; "use step" marks a unit of side-effecting work. In between it's just await, try/catch, Promise.all, loops, and conditionals.
A few things fall out of that one file:
- No DAG file: The compiler reads the directives and splits the code into workflow and client/step bundles. The graph is your control flow.
- Retries: Uncaught errors retry by default. Throw
newFatalError(...)to stop. Throw newRetryableError(..., { retryAfter })for custom backoff. Setfn.maxRetries = nto tune. - Ad-hoc webhooks:
const webhook = createWebhook()creates a real, callable URL inside a running workflow, in a single line. The run parks until someone hits it, with no route or handler. - Hooks: Webhooks are sugar over a more general primitive. Create a hook with
createHook(), await it, and the data someone sends is what comes back. This one concept replaces signals, queries and updates
Here's an ad-hoc webhook end to end. An approval that can pause for seconds or for weeks, with nothing extra to deploy.
A library, not a platform
Traditional engines make you bring infrastructure to the framework. Workflow SDK brings the framework to infrastructure you already have.
DBOS had the right idea
While I was working out how to actually build this, I studied every workflow engine I could find. The one that stood out was DBOS, an open-source durable execution library that runs almost entirely client-side. The only thing its server needs is Postgres.
Workflow SDK adopts that shape. There's no bespoke orchestrator to self-host or pay for, and no new stateful system to operate. The only infrastructure it needs is infrastructure you already run: your app, a database, a queue. All the heavy lifting happens in the library.
The backend is swappable
Workflow SDK pushes the idea past one database. The whole thing is a spec, a way of writing workflows, steps, and hooks that any infrastructure can back. You mix and match the substrate to fit your stack:
- Redis / Kafka / etc. for your streams
- Postgres / Cassandra / the file system / Turso / Durable Objects / etc. for durability
- Vercel Queues / SQS / Cloudflare Queues / etc. for your queue
The runtime only ever talks to a single interface, the World, which covers storage, queuing, auth, and streaming. Swapping any layer never touches your workflow code.
In fact, we maintain a first-party Postgres world inspired by DBOS, using Postgres for durability, queueing, and streaming. And in every world, there's no worker fleet and no control plane. A framework integration exposes two plain HTTP endpoints that deploy like the rest of your app.
Even the Vercel backend is "just" a CRUD API
The Vercel Workflow Server, the thing that backs workflows on Vercel, is stateless and does no compute or orchestration at all. It's just like the Postgres world, but extended with Vercel's authentication and multi-tenancy. A CRUD API, nothing more. It even runs as a regular Vercel deployment. All the heavy lifting, all the actual workflow logic, lives in the open-source, Apache-licensed client-side library, which can be extended freely.
So the "managed" offering isn't a black box you're locked into. It's just another World, one implementation of a spec you can read, fork, and extend. The data format isn't bespoke either. You can swap out any piece of the stack.
Some patterns lean on the platform
I should be upfront. A few of Workflow SDK's design choices are shaped by how Vercel executes code. The clearest example is versioning, the same code-evolution problem that makes traditional engines so painful.
Our answer is to pin each run to the specific deployment that started it. The run keeps executing against the exact copy of the code it began on, so changing your workflow never breaks in-flight runs. It's a clean solution, but it was only easy to build because Vercel already keeps immutable deployments around for long periods. The official Postgres world doesn't track and route versions this way yet. Encouragingly, community worlds have already implemented the spec as intended. Platformatic's version-safe durable workflows on Kubernetes is a great example.
And I think that's exactly the right trade. Pinning moves the complexity of versioning off the developer writing workflows and onto the infrastructure. That burden lands on the world author, the infra provider, and us as the framework authors. If everyone using a framework hits the same hard problem and has to solve it themselves in the same painful ways, the framework has failed them. A good framework moves that complexity upstream.
Steps should be free
Performance is the next big problem for us to solve. Invoking a step isn't cheap. Each step involves networking and a trip over a queue to durably commit its result correctly. For correctness, that's the right thing to do. But it works against the pitch that distributed computing should feel like function calls.
When you write ordinary code, you don't think twice about the overhead of a function call. It's so fast it's effectively free. But if every step carried a heavy overhead, the abstraction leaks. You start rationing steps, reasoning about granularity, declining to turn a small function into a checkpoint because it's "not worth a step."
So the goal is blunt: steps should be free.
In the ideal version of this, you could take any function in an existing codebase, add "use step", and everything would just work, with no serialization constraints and no performance penalty.
That dream drives us.
What if any developer could turn a plain function into something with the performance, networking, observability, and availability of a standalone microservice, but using just two words: "use step"? That would change how we write code.
Workflow v5 (in beta at the time of writing) already delivers up to a 5x performance improvement with no change to the user-facing API, and v6 will push it further while making third-party worlds just as fast. We're sharing details as we go in this GitHub discussion, and you can start testing today with npm install workflow@beta.
I set out to build a durable framework that feels like writing ordinary code, runs on the infrastructure you already have, and is fast enough that you never think twice about reaching for a step. It's real today, and we're building the rest of it in the open.
Come build Workflow SDK with us
If designing durable systems under real-world constraints sounds like fun, the Workflow team at Vercel is hiring.
See open roles.