
Building a Resilient Support Bot with Vercel AI SDK v7 Durable Workflows
Process crashes used to mean lost context for AI agents. I tested the new WorkflowAgent in Vercel AI SDK v7 to see if it actually survives mid-thought restarts.
Running multi-step agents in production usually feels like balancing plates. If your server restarts or a deployment goes out while an agent is thinking, you lose the entire state. The user has to start over, which is a terrible experience.
I wanted to see if the new WorkflowAgent in Vercel AI SDK v7 solves this. I built a simple customer support bot that performs a multi-step refund process: fetching user data, checking the policy, and issuing a refund via Stripe.
The Problem with Short-Lived Contexts
Usually, if an agent calls an external API and your Edge function times out, that execution is dead. Vercel's approach with v7 moves state management into a durable backend. You define the workflow, and the SDK checkpoints the state after every LLM generation and tool call.
Building the Durable Bot
The code looks surprisingly similar to the old chat wrapper, but you swap in the WorkflowAgent class and provide a persistence layer.
import { WorkflowAgent } from 'ai';
import { openai } from '@ai-sdk/openai';
import { postgresStore } from '@ai-sdk/postgres'; // The persistence layer
export const supportWorkflow = new WorkflowAgent({
model: openai('gpt-4o'),
store: postgresStore(process.env.DATABASE_URL),
system: 'You are a support agent. You can fetch user details and issue refunds.',
tools: {
fetchUserDetails: {
description: 'Get user purchase history',
parameters: z.object({ userId: z.string() }),
execute: async ({ userId }) => {
// Fetch from DB
return { purchases: [{ id: 'order_123', amount: 50, status: 'paid' }] };
},
},
issueRefund: {
description: 'Refund a specific order',
parameters: z.object({ orderId: z.string() }),
execute: async ({ orderId }) => {
// Call Stripe
return { status: 'refunded', amount: 50 };
},
},
},
});
// To trigger or resume it:
export async function POST(req: Request) {
const { workflowId, message } = await req.json();
const result = await supportWorkflow.run({
id: workflowId,
prompt: message,
});
return Response.json(result);
}
Does it work?
I ran this locally, triggered a refund request, and then aggressively killed my Node process right after the agent called fetchUserDetails but before it called issueRefund.
When I restarted the server and pinged the same workflowId, the agent did not ask for the user details again. It loaded the checkpoint from Postgres, read the transaction history, and immediately called the Stripe tool to finish the job.
It works, but you have to be careful with your database choice. I started with a local Redis instance and had serialization issues with some of the more complex tool results. Sticking to Postgres or Vercel KV seems to be the intended path. If you are building agents that take more than five seconds to finish a task, you probably need to migrate to this.
Related reading
The 2026-07-28 MCP revision removes sessions, the initialize handshake, and server-initiated requests. Here's what actually breaks in your server, the new wire format, the requestState and MRTR patterns that replace sessions, and the SDK v2 migration path.
agent-for-insurance is an open-source drafting aid that will not state a coverage conclusion without citing your policy's own text. Here's how it works, and the two parsing bugs that taught us why that rule has to be enforced in code, not prose.
A hands-on guide to building autonomous AI agents on the Claude Opus 5 API: the agent loop in Python, mid-conversation system messages, prompt caching costs, and what an overnight dependency-upgrade run actually cost.
Let's build something great.
Have a project in mind? We are an elite software and AI development studio ready to bring your ideas to production. Let's talk about your roadmap.