AI Engineer Code 2025

Building durable Agents with Workflow DevKit & AI SDK

Read the talk

Building durable Agents with Workflow DevKit & AI SDK

Turn a request-bound coding agent into a workflow with persistent streams, durable waits and human approval, then examine the boundaries of retries, permissions and deployment versioning.

From a talk by Peter Wielander

Before you start: Familiarity with TypeScript async functions, Next.js route handlers and AI SDK tools will help you follow the implementation.

An agent that works locally still needs a production runtime

A coding agent can accept a prompt, generate files and show a working application—and still leave its developer responsible for everything that happens when a connection drops, a tool fails or a user takes days to approve the next action. Peter Wielander begins with that gap between building agent capabilities and operating them in production. The workshop converts the Vibe Coding Platform into a durable agent while retaining its streaming interface. The intended result includes resumability, human approval and an inspector that exposes execution as a timeline of steps.

Workflow inspector with completed status and green timing bars, including executeGenerateFiles and executeRunCom… steps.Open full source frame
A completed codeWorkflow run shown as a timeline of steps.

The underlying agent loop alternates between an LLM and backend operations: tool calls, MCP servers, approval requests and other asynchronous tasks. For an agent that might run for hours, the usual supporting infrastructure includes queues, persisted messages and intermediate state, retry handling and observability. Workflow DevKit brings those responsibilities into an open-source TypeScript orchestration library. This workshop deploys to Vercel; other deployment targets require a compatible backend adapter.

The workflow controls the sequence; steps perform the work. A workflow is the orchestration function that decides when to call the model, when to execute a tool and when to return to the model. The LLM calls and tool executions become isolated steps whose results can be persisted and whose failures can be retried. That separation also creates the foundation for reconnectable streams, suspension and webhook-driven approval.

0:280:59
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

0:28 · section reference included

Keep the existing application boundaries visible

The starting point is the apps/vibe-coding-platform application in vercel/examples, using the workshop’s stripped-down conf/1-base branch. Numbered checkpoint branches let participants compare each increment. The linked repository’s current main branch has since moved to AI SDK v6; the checkpoint names here refer to the recording’s implementation.

Slide naming the repository and branch above the OSS Vibe Coding Platform interface, with chat, preview, sandbox filesystem and output areas.Open full source frame
The baseline example in vercel/examples on branch conf/1-base.

Running pnpm dev brings up a prompt-driven coding interface. The agent generates files, runs code and eventually exposes the application in an iframe. Vercel Sandbox provides the filesystem and execution environment. The chat endpoint checks the requested model ID, creates an AI SDK agent and streams its output back to the browser. Internally, the agent alternates streamText calls with tool execution.

ToolResponsibility
Create sandboxWrap sandbox creation
Get sandbox URLRetrieve the preview address
Run commandExecute a command in the sandbox
generateFilesGenerate a file from a prompt

The tool definitions combine instructions, a Zod input schema and an execution function. For example, executeRunCommand wraps sandbox.runCommand with error handling. On the client, useChat consumes the stream and updates the interface. These boundaries matter: the workflow refactor changes how execution survives failures, while the tools still perform the same operations and the UI still expects the same messages.

5:105:29
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

5:10 · section reference included

Extract and compile the orchestration function

Start by installing the runtime and its AI helpers:

bash

pnpm add workflow @workflow/ai

For Next.js, wrap the existing configuration with withWorkflow from workflow/next:

typescript

import type { NextConfig } from "next";
import { withWorkflow } from "workflow/next";

const nextConfig: NextConfig = {};

export default withWorkflow(nextConfig);

This enables compilation of workflow code separately from the application’s ordinary code. Wielander also adds the optional TypeScript plugin for better completion.

Next, move the agent invocation into a separate function, passing in the arguments previously available inside the route handler. At first this is only an extraction. Adding "use workflow" at the beginning of that function changes its execution contract: the compiler creates a separate bundle and restricts imports that would introduce side effects into orchestration. Deterministic orchestration can be replayed without repeating arbitrary external operations.

The AI SDK agent hides its model calls inside its loop, so marking those calls individually would be awkward. DurableAgent supplies that integration: its internal LLM calls already have step boundaries. The refactor then needs two changes around streaming and invocation:

  1. Obtain the workflow-associated writable stream with getWritable and give it to the agent, replacing the route handler’s stream.
  2. Start the extracted function through start from workflow/api, passing its arguments separately.
  3. Use the returned run instance to expose the workflow’s stream to the UI.

The route now starts a durable run and connects the browser to its output. It no longer owns the entire lifetime of the agent’s execution.

9:179:28
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

9:17 · section reference included

Mark the tools and inspect the run

DurableAgent covers model calls, but the application’s four tools still need "use step" in their execution functions. On the demonstrated serverless deployment, each step executes separately. The runtime records its inputs and successful outputs, reuses recorded results during recovery and retries failed execution. After adding the directives, Wielander restarts pnpm dev and submits another query: the application continues streaming.

Open the local inspector from the application directory:

bash

npx workflow web

Every marked step becomes an inspectable span with inputs, outputs and associated events. This makes the execution boundaries visible without adding a separate tracing layer to every tool.

In this application, each prompt starts a workflow that runs to completion. A different product could make an entire user session one workflow: process a query, wait for another query, then continue the loop. That choice determines what the run ID represents—a single request or a longer-lived interaction—and permits sessions that remain active across weeks of intermittent work.

15:3815:50
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

15:38 · section reference included

Restore the tool progress packets

The first refactor preserves the agent’s text stream, but the right-hand tool panel is empty. Its custom UI depends on data packets that the tools are no longer sending. Within a tool, getWritable retrieves the same default workflow stream; obtaining its writer lets the tool emit the packets the client already understands. For sandbox creation, the demonstrated packet is data-create-sandbox, carrying the tool-call identity and sandbox identity.

A workflow can also expose named streams for separate channels. The storage belongs to the backend adapter: Wielander describes local file-backed streams and a possible Redis-backed production implementation keyed by workflow identity. Steps attach as writers, and clients attach as readers. The API handler simply returns access to that stream. Because the stream survives the handler, losing the connection does not erase the output; a client can reconnect to the same run.

After restarting the development server, the sandbox’s initial loading state appears. It still does not change to completed, because only the initial packet has been written. Completion and other progress transitions require their own packets. Wielander switches to conf/2-workflow, which already contains the necessary writer.write calls across the tools, restoring the richer interface without changing the orchestration design.

18:4219:08
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

18:42 · section reference included

Reconnect without starting another agent

Persistent output needs a client reconnection path. The implementation proceeds from identifying the run to retrieving its stream:

  1. Return run.runId alongside the initial stream. Wielander uses a response header, although another transport would also work.
  2. Add a Next.js route at /chat/ID/stream.
  3. Extract the ID and call getRun from the workflow API.
  4. Return that run’s readable stream, without invoking the agent again.
  5. Accept a start index when the client needs to continue from a particular chunk rather than replay the entire output.

Resuming a stream reconnects to existing work; it does not submit the prompt again.

The 4-streams checkpoint supplies the client transport changes. Middleware around useChat checks for an existing run ID and chooses the reconnection endpoint when one is available. Wielander shows this conditional transport code briefly rather than rebuilding it live, then initiates a Vercel preview deployment before moving on to suspension.

23:4224:18
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

23:42 · section reference included

Wait without keeping an execution instance alive

The orchestration layer runs briefly to arrange step execution. When an otherwise idle workflow reaches a durable wait, it can release execution resources until it is time to continue. Inside a workflow, a three-day pause is expressed directly:

typescript

import { sleep } from "workflow";

export async function waitThreeDays() {
  "use workflow";

  await sleep("3 days");
  return "ready";
}

The persistent stream remains available to reconnecting clients. Exposing sleep to an agent also makes longer-lived behavior possible: process email, wait a day, then process email again.

What survives if the execution instance disappears? A step invocation is registered as an event; successful completion records its output. Recovery uses that saved history and the step results to reconstruct progress. Completed external calls need not be performed again merely to reach the point where execution stopped. This is durable recovery through recorded execution, rather than a requirement to keep the original process and its memory alive.

The preview deployment is now live. Wielander opens the same style of workflow inspector against the deployed application, using deployment-selection options, and inspects its steps. He also invokes the cancellation control; the displayed confirmation offers Keep Running and Cancel Run. The intended operational experience is the same locally and remotely: inspect a run, understand its progress and stop it when necessary.

Cancel Workflow Run dialog over a workflow timeline, with Keep Running and Cancel Run buttons.Open full source frame
The workflow inspector asks whether to cancel a running workflow.
27:4728:04
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

27:47 · section reference included

Expose sleep as an agent tool

The next tool accepts a numeric timeout in milliseconds and calls Workflow’s sleep. Its execution function does not need another "use step" directive: sleep already participates in durable execution and appears in inspection. The tool description restricts it to explicit user requests.

typescript

import { tool } from "ai";
import { sleep } from "workflow";
import { z } from "zod";

export async function runSleepCommand({
  timeoutMs,
}: {
  timeoutMs: number;
}) {
  await sleep(timeoutMs);
  return { status: "awake" as const };
}

export const sleepTool = tool({
  description: "Wait only when the user explicitly requests it.",
  inputSchema: z.object({
    timeoutMs: z.number().nonnegative(),
  }),
  execute: runSleepCommand,
});

Add sleepTool to the agent’s tools. To display progress, use a separate step for the stream write: writing directly from replayable orchestration would emit the packet again each time that orchestration replays.

A participant’s setup error exposes a separate dependency. Creating an AI Gateway API key has not satisfied the example’s Sandbox authentication requirements; the participant sees a missing-header/OIDC error. Wielander explains that this application also requires logging into Vercel because it uses Vercel Sandbox. He promises a later Sandbox-free branch for local execution, but does not demonstrate it in the session.

The sleep progress packet includes the tool-call ID, and the existing UI expects wait handling rather than a data-sleep type. Wielander requests a 30-second sleep followed by a random result; the workflow inspector shows the sleep operation, while the application still fails to display its expected progress packet. The durable operation and the client’s rendering integration are separate things to verify.

31:5832:27
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

31:58 · section reference included

Pause for approval before building a Pokédex

The conf/5-webhooks checkpoint adds a human approval tool. Its sequence resembles sleep: log progress, create a webhook, expose its URL and await the webhook. Instead of resuming after elapsed time, the workflow resumes when a request reaches the generated endpoint. The demonstration asks the agent to wait for human approval before coding a Pokémon index.

After another development-server restart, the run pauses at the approval link. The endpoint uses the local host in development and the deployment host in production. It is also more than a notification URL: Wielander describes request handling with respondWith and validation of the request body against a Zod schema before allowing execution to continue.

An audience question sharpens the meaning of suspension. Sleep and webhook waits appear like steps in the inspector and compose through promises, but internally they suspend execution. An otherwise idle run does not keep a worker busy waiting. Concurrent work can still continue, and Promise.race can combine a wait with another operation.

The distinction becomes clearer with the audience’s example of a process continuously calculating π in a sandbox. Workflow suspension does not mean suspending that VM. Vercel Sandbox is the separate environment that holds files and executes generated programs—Wielander compares it to an EC2 instance.

OperationWhat it affects
Workflow sleepThe workflow’s continuation
Terminal sleepA command running inside the sandbox
Webhook waitContinuation pending an external request

After the approval URL is clicked, the workflow resumes and produces the Pokédex.

37:2037:31
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

37:20 · section reference included

Durability stops at the boundaries you expose

Could the same workflow launch Claude Code? A terminal application does not automatically expose its internal model calls and tool calls as workflow steps. Wielander suggests adding workflow boundaries to an agent implementation whose orchestration can be instrumented. Simply starting Claude Code inside a VM is closer to opening an SSH session: the workflow can record the command step, but it does not thereby manage the VM’s internal state or every operation performed by the external agent. Only integrated step boundaries receive the workflow’s granular resumability and inspection.

Permissions remain application responsibilities. If a tool can delete an S3 resource, its author must enforce the authorization for that deletion. Network access and model-provider access likewise retain the application’s existing controls. Workflow DevKit supplies durable orchestration; it does not itself sandbox untrusted code. The example uses Vercel Sandbox as a separate isolation mechanism, with a fresh VM for a sandbox session.

43:1843:41
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

43:18 · section reference included

Separate run lifetime from step limits and deployment changes

A long-running workflow is bound to its deployment. In Wielander’s example, a workflow runs for a week while the application is deployed five more times: the existing run continues on its original deployment, while new runs use the newer deployment. A year-long monthly-summary agent makes the next problem obvious—eventually an existing run may need newer code. He describes a planned upgrade interface that checks step signatures and recorded events; cancellation followed by a new run is the available alternative in the workshop.

Long workflow lifetimes do not remove serverless execution limits. Each step is still subject to its hosting platform’s timeout. Wielander uses five- and fifteen-minute limits as platform-dependent examples: a step that exceeds its limit fails, may retry and exposes those repeated failures in inspection. The remedy is to split oversized work into smaller steps. Continuing an existing run after such a code change belongs to the upgrade capability being discussed.

Each call to the example’s chat route starts an independent workflow, and that workflow continues even when nobody reads its stream. The run API supports inspection of active runs, their versions and current steps, along with cancellation. Concurrency in the demonstrated system is bounded by provider capacity rather than an application-level admission limit. Per-step and per-workflow concurrency controls are presented as upcoming features: a proposed limit of ten active runs would queue additional starts until capacity becomes available. Wielander uses that mechanism to illustrate different free and paid product tiers.

Two more requests concern manipulating recorded progress:

  • Rollback: An audience member asks about moving from step seven back to step three. Persisted inputs and outputs make earlier replay conceivable, but the operation is explicitly unsupported in the workshop. Another approach raised in the discussion is to model subsequent state changes while retaining a growing event log.
  • Metadata: A proposed tagging API would attach arbitrary tags during execution, potentially supporting early exit or deduplication.

These are extensions to the runtime’s recorded history, rather than features added to the coding agent during the demonstration.

46:4447:02
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

46:44 · section reference included

Compose backends, schedules and communication

Portability has two layers. The framework integration handles compilation and the application’s API layer; the durability backend provides storage and queuing. Hosting Next.js somewhere does not by itself provide that backend. Wielander points to a first-party Postgres implementation and explains that adapters connect workflow execution to databases, storage and queues. Run data is also available through an API and open-source UI components, allowing applications to build their own inspection experience or export information to systems such as Datadog.

Scheduling follows ordinary TypeScript control flow. A loop can await a day, perform work and repeat. For a wall-clock schedule, calculate the delay to the next desired time; for conditional work, wake periodically and check whether execution is needed. External API checks must happen in steps. A scheduling workflow can launch another workflow or invoke an agent directly. If each scheduled execution needs a separate output channel, deterministic names passed to getWritable let clients select the appropriate stream.

Cancellation and early wake-up solve different problems. The inspector, API and CLI can cancel an existing workflow. To continue early rather than cancel, race a timer against approval:

typescript

import { createWebhook, sleep } from "workflow";

export async function waitForApprovalOrTomorrow() {
  "use workflow";

  const approval = createWebhook();

  await Promise.race([
    sleep("1 day"),
    approval,
  ]);

  return "ready";
}

As in the approval tool, the application must expose the webhook URL to the person who can trigger it. The race permits continuation when either event arrives; it does not establish cancellation behavior for the losing operation.

Communication between agents also uses application-level mechanisms. Steps can call Node.js APIs or fetch, coordinate through a shared database, or write to a shared stream. The choice depends on whether the agents need shared state, external requests or a common output channel.

52:1752:23
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

52:17 · section reference included

Design side effects for retries

Persisting a completed step does not make a partially failed side effect safe to repeat. Compiler restrictions protect deterministic workflow code; steps intentionally permit external effects. If a step changes an external system and then fails before its completion is recorded, its author must make another attempt well behaved through transactional or idempotent operations.

Retry policy is also an application decision. An ordinary failure can signal that the runtime should retry, but error-specific handling may instead request human intervention or choose another path. Durable orchestration provides a place to express those decisions; it does not know whether repeating a particular business operation is acceptable.

The cumulative conf/5-webhooks checkpoint contains the workflow conversion, resumable streams, sleep and human approval. Wielander promises to share the slides afterward. At the time of the recording, Workflow DevKit is in beta, and he tentatively forecasts general availability in January. The discussion mentions production use in v0. Wielander and a participant report more than one million internal workflow runs per day. That operational claim sits alongside continuing work on API stability and outstanding issues.

The project invites feature requests through GitHub RFC discussions and issues. Its adapters are open source, so teams can inspect how the runtime connects to backend storage and use those implementations as a starting point for their own infrastructure.

58:0358:13
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

58:03 · section reference included

Pin versions by default, migrate deliberately

The versioning discussion returns to deployment identity. A deployment corresponds to a workflow version, and a run retains the identity of the version it uses. Deployment-specific URLs also scope webhook routing; the public API routes new work to whichever version the application selects. Wielander describes run.upgrade as part of the proposed interface for checking compatibility and moving existing runs to newer code.

For bulk migration, the proposed UI or CLI would select runs, check whether their recorded progress can continue under the target version and upgrade compatible runs. If that is not possible, it could offer cancellation and a new run with the same inputs. The compiler supplies information for these checks: Wielander describes storing AST information and workflow and step input/output signatures in a version manifest.

Recorded event history provides another compatibility test: replay it against the new code and see whether the new implementation can follow it. The audience pushes on the danger of automatically changing many active agents at once. This exposes a real design tradeoff. If code evolves in place, the current implementation may need to handle histories originating from many older versions, accumulating compatibility branches and extra reasoning burden.

Atomic deployment environments allow a simpler default: existing runs remain pinned, and migration is an explicit operation. Full replay, partial replay and in-place upgrades remain possible design choices above that foundation. The hard part is making the migration decision understandable and safe for the operator, rather than silently forcing every active run onto newly deployed code.

1:01:461:02:02
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

1:01:46 · section reference included

Inspect execution without exposing secrets

The final observability question asks what a team can send to its existing Datadog installation. Wielander identifies OpenTelemetry spans as the export path, then clarifies that first-party enrichment with step and event-log context is planned, potentially through a helper. Applications can emit their own telemetry and attach their own tags. The local inspector demonstrated earlier and the proposed external telemetry enrichment are distinct parts of the operational interface.

The last question concerns secrets, including secrets that need updating. Workflow inspection exposes stored inputs and outputs to users with inspection API access; ordinary application callers would not normally have those privileges. For credentials used by execution code, Wielander points to the deployment’s usual process environment and environment-variable injection. Avoid logging those values. This explains how execution obtains secrets, but does not specify how rotation propagates to runs pinned to older deployments.

At this point in the recording, encryption of persisted workflow data is described as future work. The immediate design boundary is therefore concrete: operational visibility includes recorded data, so access to the inspector and the contents of step inputs, outputs and logs all need deliberate treatment. Durability preserves the information needed to continue execution; the application must also decide which information is appropriate to preserve and expose.

1:07:161:07:26
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

1:07:16 · section reference included

Resources

From the talk

  • A coding-agent application with Vercel Sandbox execution, live previews, file browsing and command logs. The link follows the current main branch.

  • Source and setup instructions for the TypeScript durability framework introduced as Workflow DevKit, including framework integration and backend options.

  • The launch explanation of workflow and step directives, deterministic replay, durable waits, webhooks and portable execution backends.

Updates since the talk

Transcript download
Read the transcript and explore slides

Loading transcript and slides…