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?:

string

model:

LanguageModel

instructions?:

Instructions

tools?:

Record<string, Tool>

toolChoice?:

ToolChoice

stopWhen?:

StopCondition | StopCondition[]

activeTools?:

ActiveTools<TTools>

output?:

OutputSpecification

repairToolCall?:

ToolCallRepairFunction

experimental_download?:

DownloadFunction

experimental_sandbox?:

Experimental_SandboxSession

prepareStep?:

PrepareStepCallback

prepareCall?:

PrepareCallCallback

runtimeContext?:

Context

toolsContext?:

InferToolSetContext<TTools>

telemetry?:

TelemetryOptions

experimental_onStart?:

WorkflowAgentOnStartCallback
GenerateTextStartEvent

model:

LanguageModel

messages:

Array<ModelMessage>

runtimeContext:

Context

toolsContext:

InferToolSetContext<Tools>

experimental_onStepStart?:

WorkflowAgentOnStepStartCallback
GenerateTextStepStartEvent

model:

LanguageModel

messages:

Array<ModelMessage>

steps:

ReadonlyArray<StepResult>

runtimeContext:

Context

toolsContext:

InferToolSetContext<Tools>

onToolExecutionStart?:

WorkflowAgentonToolExecutionStartCallback
ToolExecutionStartEvent

callId:

string

toolCall:

{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }

messages:

Array<ModelMessage>

toolContext:

InferToolContext<TOOLS[toolName]>

onToolExecutionEnd?:

WorkflowAgentonToolExecutionEndCallback
ToolExecutionEndEvent

callId:

string

toolCall:

{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }

durationMs:

number

messages:

Array<ModelMessage>

toolContext:

InferToolContext<TOOLS[toolName]>

toolOutput:

ToolOutput<TOOLS>

onStepEnd?:

WorkflowAgentOnStepEndCallback

onStepFinish?:

WorkflowAgentOnStepFinishCallback

onEnd?:

WorkflowAgentOnEndCallback

maxOutputTokens?:

number

temperature?:

number

topP?:

number

topK?:

number

presencePenalty?:

number

frequencyPenalty?:

number

stopSequences?:

string[]

seed?:

number

maxRetries?:

number

headers?:

Record<string, string | undefined>

providerOptions?:

ProviderOptions

Properties

id:

string | undefined

tools:

Record<string, Tool>

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:

string | Array<ModelMessage>

messages:

Array<ModelMessage>

writable?:

WritableStream<ModelCallStreamPart>

system?:

string

stopWhen?:

StopCondition | StopCondition[]

toolChoice?:

ToolChoice

activeTools?:

ActiveTools<TTools>

output?:

OutputSpecification

timeout?:

number

sendFinish?:

boolean

preventClose?:

boolean

includeRawChunks?:

boolean

repairToolCall?:

ToolCallRepairFunction

experimental_transform?:

StreamTextTransform | Array<StreamTextTransform>

experimental_download?:

DownloadFunction

experimental_sandbox?:

Experimental_SandboxSession

telemetry?:

TelemetryOptions

runtimeContext?:

Context

toolsContext?:

InferToolSetContext<TTools>

prepareStep?:

PrepareStepCallback

experimental_onStart?:

WorkflowAgentOnStartCallback

experimental_onStepStart?:

WorkflowAgentOnStepStartCallback

onToolExecutionStart?:

WorkflowAgentonToolExecutionStartCallback

onToolExecutionEnd?:

WorkflowAgentonToolExecutionEndCallback

onStepEnd?:

WorkflowAgentOnStepEndCallback

onStepFinish?:

WorkflowAgentOnStepFinishCallback

onEnd?:

WorkflowAgentOnEndCallback

onError?:

WorkflowAgentOnErrorCallback

onAbort?:

WorkflowAgentOnAbortCallback

Returns

Returns a Promise<WorkflowAgentStreamResult> with the following properties:

messages:

Array<ModelMessage>

steps:

Array<StepResult>

toolCalls:

Array<ToolCall>

toolResults:

Array<ToolResult>

output:

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

workflow/agent-chat.ts
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 persistence
async 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 };
}
app/api/chat/route.ts
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`,
);
},
});