WorkflowAgent
Creates a durable, resumable AI agent for use inside a workflow. WorkflowAgent handles the agent loop, tool schema serialization across workflow step boundaries, and built-in tool approval flows.
Unlike ToolLoopAgent from the ai package, WorkflowAgent is designed to survive process restarts, pause for human approval, and integrate with the Workflow DevKit's step mechanism.
WorkflowAgent supports runtimeContext for shared agent state and toolsContext for per-tool context. Because these values can cross workflow and step boundaries, keep them serializable durable data. Unlike ToolLoopAgent, do not place functions, class instances, symbols, database clients, or SDK clients in context; pass identifiers or configuration and recreate non-serializable resources inside step functions.
import { WorkflowAgent } from '@ai-sdk/workflow';import { tool } from 'ai';import { z } from 'zod';
const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', instructions: 'You are a helpful assistant.', tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => ({ location, temperature: 72, }), }), },});
const result = await agent.stream({ messages: [ { role: 'user', content: [{ type: 'text', text: 'What is the weather in NYC?' }], }, ],});
console.log(result.messages);To see WorkflowAgent in action, check out these examples.
Import
import { WorkflowAgent } from "@ai-sdk/workflow"Constructor
Parameters
id?:
model:
instructions?:
tools?:
toolChoice?:
stopWhen?:
activeTools?:
output?:
repairToolCall?:
experimental_download?:
experimental_sandbox?:
prepareStep?:
prepareCall?:
runtimeContext?:
toolsContext?:
telemetry?:
experimental_onStart?:
model:
messages:
runtimeContext:
toolsContext:
experimental_onStepStart?:
model:
messages:
steps:
runtimeContext:
toolsContext:
onToolExecutionStart?:
callId:
toolCall:
messages:
toolContext:
onToolExecutionEnd?:
callId:
toolCall:
durationMs:
messages:
toolContext:
toolOutput:
onStepEnd?:
onStepFinish?:
onEnd?:
maxOutputTokens?:
temperature?:
topP?:
topK?:
presencePenalty?:
frequencyPenalty?:
stopSequences?:
seed?:
maxRetries?:
headers?:
providerOptions?:
Properties
id:
tools:
Methods
stream()
Runs the agent loop, streaming responses and executing tool calls as needed. Returns a promise resolving to a WorkflowAgentStreamResult.
const result = await agent.stream({ messages: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }],});prompt:
messages:
writable?:
system?:
stopWhen?:
toolChoice?:
activeTools?:
output?:
timeout?:
sendFinish?:
preventClose?:
includeRawChunks?:
repairToolCall?:
experimental_transform?:
experimental_download?:
experimental_sandbox?:
telemetry?:
runtimeContext?:
toolsContext?:
prepareStep?:
experimental_onStart?:
experimental_onStepStart?:
onToolExecutionStart?:
onToolExecutionEnd?:
onStepEnd?:
onStepFinish?:
onEnd?:
onError?:
onAbort?:
Returns
Returns a Promise<WorkflowAgentStreamResult> with the following properties:
messages:
steps:
toolCalls:
toolResults:
output:
Utilities
createModelCallToUIChunkTransform()
Creates a TransformStream that converts raw ModelCallStreamPart chunks (written by the agent to the writable stream) into UIMessageChunk objects suitable for client consumption.
import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()),});toUIMessageChunk()
Converts a single ModelCallStreamPart to a UIMessageChunk. Returns undefined for parts that don't map to UI chunks.
import { toUIMessageChunk } from '@ai-sdk/workflow';
const uiChunk = toUIMessageChunk(modelCallPart);Types
ActiveTools
type ActiveTools<TTools extends ToolSet> = | ReadonlyArray<keyof TTools & string> | undefined;Limits a workflow agent call to the listed tool names. undefined means no tool restriction is applied.
InferWorkflowAgentUIMessage
Infers the UI message type for a WorkflowAgent instance. Optionally accepts a second type argument for custom message metadata.
import { WorkflowAgent, InferWorkflowAgentUIMessage } from '@ai-sdk/workflow';
const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', tools: { weather: weatherTool },});
type MyAgentUIMessage = InferWorkflowAgentUIMessage<typeof agent>;InferWorkflowAgentTools
Infers the tool set type of a WorkflowAgent instance.
import { WorkflowAgent, InferWorkflowAgentTools } from '@ai-sdk/workflow';
type MyTools = InferWorkflowAgentTools<typeof myAgent>;Examples
Basic Agent with Tools
import { WorkflowAgent } from '@ai-sdk/workflow';import { tool } from 'ai';import { z } from 'zod';
const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', instructions: 'You are a helpful assistant.', tools: { weather: tool({ description: 'Get weather for a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => ({ location, temperature: 72, condition: 'sunny', }), }), },});
const result = await agent.stream({ messages: [ { role: 'user', content: [{ type: 'text', text: 'What is the weather in NYC?' }], }, ],});
console.log(result.messages);console.log(result.steps);Agent in a Workflow with Durable Tools
import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow';import { convertToModelMessages, tool, type UIMessage } from 'ai';import { getWritable } from 'workflow';import { z } from 'zod';
// Tool execute functions marked with 'use step' become durable workflow steps// with automatic retries and persistenceasync function searchFlightsStep(input: { origin: string; destination: string;}) { 'use step'; const response = await fetch(`https://api.flights.example/search?...`); return response.json();}
export async function chat(messages: UIMessage[]) { 'use workflow';
const modelMessages = await convertToModelMessages(messages);
const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', instructions: 'You are a flight booking assistant.', tools: { searchFlights: tool({ description: 'Search for available flights', inputSchema: z.object({ origin: z.string(), destination: z.string(), }), execute: searchFlightsStep, }), }, });
const result = await agent.stream({ messages: modelMessages, writable: getWritable<ModelCallStreamPart>(), });
return { messages: result.messages };}import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';import { createUIMessageStreamResponse, type UIMessage } from 'ai';import { start } from 'workflow/api';import { chat } from '@/workflow/agent-chat';
export async function POST(request: Request) { const { messages }: { messages: UIMessage[] } = await request.json();
const run = await start(chat, [messages]);
return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), });}Agent with Structured Output
import { WorkflowAgent, Output } from '@ai-sdk/workflow';import { z } from 'zod';
const analysisAgent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6',});
const result = await analysisAgent.stream({ messages: [ { role: 'user', content: [ { type: 'text', text: 'Analyze: "The product exceeded my expectations!"', }, ], }, ], output: Output.object({ schema: z.object({ sentiment: z.enum(['positive', 'negative', 'neutral']), score: z.number(), summary: z.string(), }), }),});
console.log(result.output);// { sentiment: 'positive', score: 9, summary: '...' }Agent with Tool Approval
For WorkflowAgent, tool approval is configured on the tool definition with
needsApproval. For generateText, streamText, and ToolLoopAgent, use
toolApproval instead.
import { WorkflowAgent } from '@ai-sdk/workflow';import { tool } from 'ai';import { z } from 'zod';
const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', tools: { bookFlight: tool({ description: 'Book a flight', inputSchema: z.object({ flightId: z.string(), passengerName: z.string(), }), needsApproval: true, // Pauses the agent until user approves execute: bookFlightStep, }), },});Agent with Lifecycle Callbacks
import { WorkflowAgent } from '@ai-sdk/workflow';
const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', tools: { weather: weatherTool },
// Agent-wide callbacks onStepEnd({ usage }) { console.log('Tokens used:', usage.totalTokens); },});
const result = await agent.stream({ messages,
// Per-call callbacks (both fire) onStepEnd({ usage }) { await trackUsage(usage); },
onEnd({ steps, totalUsage }) { console.log( `Done in ${steps.length} steps, ${totalUsage.totalTokens} tokens`, ); },});