Skip to content
Skip to main content
A rugged metal black-box style recorder with a glowing amber status light on a workbench, representing a durable AI agent that survives crashes
10 min readBy Carlos Aragon

Vercel Workflow DevKit: Durable AI Agents That Survive Crashes and Deploys

Workflow DevKit turns every tool call an agent makes into a persisted, retryable step instead of a line in a function that dies with the process. Write "use workflow" on the orchestrator and "use step" on each unit of work, wrap the loop in DurableAgent, and a crash, a redeploy, or a pause that lasts weeks resumes exactly where it left off. Here's what that actually looks like in code, and where it beats — and loses to — n8n.

The Failure Mode: A Deploy Kills the Agent Mid-Run

I hand-rolled an agent loop inside a Next.js API route last year that researched, wrote, and published a daily blog post — seven or eight tool calls deep: search, draft, generate an image, build, push, verify the live URL. It worked fine until the day I shipped an unrelated fix while the route was mid-run. Vercel rolled the new deployment, the in-flight function got torn down, and the loop had no idea it had already burned four tool calls and a chunk of API budget. The next trigger started from zero. Nothing crashed loudly — it just silently redid work and re-spent money, which is worse, because you don't find out until you're reading the bill.

That's the failure mode Workflow DevKit is built to close. A normal agent loop keeps its entire state — which tool calls happened, what they returned, where it is in the plan — in one process's memory. The moment that process goes away, so does the state. It doesn't matter whether the cause is a crash, a timeout, or your own git push; the outcome is identical.

The core idea:

Durable execution doesn't make the process immortal. It makes the progress immortal, by checkpointing every completed step outside the process, so a new process can pick up from the last checkpoint instead of the beginning.

Two Directives Do Most of the Work

Workflow DevKit's whole model comes down to two function-level directives. "use workflow" marks the orchestrating function — it runs in a sandboxed VM whose entire job is to call other functions in order, nothing more. "use step" marks a unit of real work — it gets full Node.js access, automatic retry on failure, and its return value is persisted so the runtime never has to execute it twice.

async function fetchUserData(userId) {
  "use step";               // full Node.js access, retried, persisted
  const res = await fetch(`https://api.example.com/users/${userId}`);
  return res.json();
}

export async function pipeline(userId) {
  "use workflow";           // orchestration only, sandboxed VM
  const data = await fetchUserData(userId);
  const processed = await processWithAI(data);
  return { success: true, processed };
}

The sandbox restriction is the part people trip over first: no fetch(), no setTimeout, no direct Node.js modules inside a "use workflow" function. That's deliberate — the orchestrator has to be deterministic so it can be safely replayed, and network calls and timers are exactly the kind of nondeterminism replay can't tolerate. You use sleep() from the workflow package instead of setTimeout, and you push real I/O down into steps. Once you internalize “steps do the work, the workflow just calls steps in order,” the model clicks.

DurableAgent: The AI SDK Loop, Minus the Amnesia

DurableAgent, from the @workflow/ai package, is where this gets useful for agent work specifically. It's the same tool-calling pattern you already know from the Vercel AI SDK — a model, a system prompt, a set of tools — except each tool's executefunction that needs Node.js or npm access is written as a step, and the agent's progress through the conversation is checkpointed after every one.

import { DurableAgent } from "@workflow/ai/agent";
import { getWritable } from "workflow";
import { z } from "zod";

async function lookupData({ query }) {
  "use step";
  return `Results for "${query}"`;
}

export async function agentWorkflow(userMessage) {
  "use workflow";

  const agent = new DurableAgent({
    model: "anthropic/claude-sonnet-4-5",
    system: "You are a helpful assistant.",
    tools: {
      lookupData: {
        description: "Search for information",
        inputSchema: z.object({ query: z.string() }),
        execute: lookupData,
      },
    },
  });

  const result = await agent.stream({
    messages: [{ role: "user", content: userMessage }],
    writable: getWritable(),
    maxSteps: 10,
  });

  return result.messages;
}

Say that agent is four tool calls into a seven-call plan when the underlying compute gets recycled. On a plain AI SDK loop, that's a full restart — tool calls one through four run again, and if any of them were non-idempotent (sent an email, charged a card, posted to Slack), you have a duplicate-side-effect problem on top of the wasted spend. With DurableAgent, the runtime replays deterministically from the checkpoint: calls one through four return their already-persisted results instantly, at zero cost, and only call five actually executes against the model again. That single property is the entire pitch.

It's the same discipline I wrote about wiring into n8n loops to stop silent agent failures and duplicate executions — deterministic state beats hoping the model remembers, or hoping the process doesn't die. Workflow DevKit just bakes that discipline into the runtime instead of making you build it by hand.

A durable AI agent checkpointing its progress like a black-box recorder, so it can resume after a crash or redeploy
Every completed step gets checkpointed outside the process — that's the whole trick.

Pausing for a Human Without Running a Queue

The pattern I like most is createHook(). An agent that needs a human to approve an action — send this email, refund this order, publish this post — suspends at the hook and burns zero compute while it waits. It isn't polling a database every thirty seconds. An API route calls resumeHook(token, payload)whenever the approval actually happens, whether that's ten seconds or ten days later, and the workflow resumes exactly where it paused.

export async function approvalWorkflow(docId) {
  "use workflow";
  const hook = createHook({ token: `approval-${docId}` });
  const { approved } = await hook;   // suspends here, costs nothing
  return approved ? publish(docId) : discard(docId);
}

I run this exact shape today with human-in-the-loop approvals in n8n, where a wait node parks the execution until a webhook fires. The n8n version is faster to wire up visually and easier for a non-engineer to inspect mid-flight. The Workflow DevKit version is faster to reason about in code review, ships inside the same repo as the rest of your app, and gives you full TypeScript types on the payload. Same idea, different trade-off on where the complexity lives.

Workflow DevKit vs n8n vs Rolling Your Own Queue

None of these three approaches is strictly better — they trade complexity for control in different places:

  • Workflow DevKit: code-first, lives in your existing Next.js/Node app, no separate service to run, full type safety, deterministic replay built in. Best when you're shipping a custom agent as a feature of your own product and the pain is state loss on crash or redeploy.
  • n8n: visual canvas, hundreds of prebuilt integration nodes, non-engineers can read and modify a flow, built-in execution history UI. Best when the team building it isn't all engineers, or the win is breadth of pre-built connectors, not custom logic.
  • Hand-rolled queue (BullMQ, SQS, a Postgres jobs table): maximum control, zero framework lock-in, but you're building retry logic, checkpointing, and replay yourself — which is precisely the boilerplate both of the above exist to remove.

In practice I reach for Workflow DevKit specifically when the agent is the product — a feature customers interact with inside my app — and n8n when the agent is internal glue between systems I don't own. I laid out the equivalent decision for Claude Agent SDK vs LangGraph a few weeks back, and the shape of the answer is the same here: pick the tool whose durability model matches where the agent actually lives, not the one with the flashiest demo.

The Trade-offs Before You Adopt It

  • Everything crossing the workflow/step boundary must be serializable — plain objects, Date, Map, Set, typed arrays, and a few more, but no functions, class instances, or Symbols. Pass data, not callbacks.
  • The sandbox rules are strict inside "use workflow" functions specifically: no fetch, no timers, no Node.js modules. Push anything that touches the network or the filesystem into a step. DurableAgent handles this for you automatically for model calls.
  • Debugging a paused or replayed workflow takes new muscle memory — `npx workflow inspect run <id>` and `npx workflow web` are the tools, and they're worth learning before you need them at 2am, not during.
  • The managed backend is a Vercel product. The runtime itself is open source, but if you want the observability dashboard and the hosted checkpoint store, you're inside the Vercel ecosystem.

None of that is disqualifying — it's the same shape of trade-off as adopting any durable-execution framework, Temporal included. You're exchanging some upfront rules for never having to hand-write checkpointing again. Vercel's own writeup on the programming model is worth reading directly if you want the reasoning behind the sandbox design, in their durable execution announcement.

The Short Version

  • "use workflow" marks the orchestrator (sandboxed, deterministic); "use step" marks real work (full Node.js, retried, persisted).
  • DurableAgent wraps the standard AI SDK tool-calling pattern so every tool call is checkpointed — a crash resumes at the next call, not call one.
  • createHook() suspends a workflow for a human or an external event at zero compute cost while it waits, then resumeHook() wakes it up exactly where it paused.
  • It's not a replacement for n8n — reach for it when the agent is a feature of your own app and the failure you're fighting is lost state on crash or redeploy.
  • Serialization limits and sandbox rules inside workflow functions are the real learning curve; push I/O into steps and DurableAgent handles the rest.

Building an Agent That Has to Survive Real Production?

I build production AI agents on Next.js, the Claude API, n8n, and Vercel — including the durable, resumable kind that can't afford to lose state on a redeploy. If you're shipping an agent as a real feature and need it to actually hold up, let's talk.

Related Posts