Skip to main content

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-5.5',
  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): The id of the agent.
  • model (LanguageModel): The language model to use. A string compatible with the Vercel AI Gateway (e.g., 'anthropic/claude-sonnet-5.5') or a provider instance (e.g., openai('gpt-6-astra')).
  • instructions? (Instructions): Instructions for the agent, used as the system prompt. Supports provider-specific options (e.g., caching) when using the SystemModelMessage form.
  • tools? (Record<string, Tool>): A set of tools the agent can call. Keys are tool names. Tools are serialized to JSON Schema across workflow step boundaries and validated with Ajv at runtime.
  • toolChoice? (ToolChoice): Tool call selection strategy. Options: 'auto' | 'none' | 'required' | { type: 'tool', toolName: string }. Default: 'auto'.
  • stopWhen? (StopCondition | StopCondition[]): Default stop condition for the agent loop. When omitted, generate defaults to 20 steps, while stream has no maximum step count. Use isStepCount() to bound execution. Per-call values override this default.
  • toolCallConcurrency? (number): Maximum number of local tool calls executing concurrently within a step, including approved calls. Must be a positive integer. Set to 1 for sequential execution in model order. Defaults to unlimited concurrency. Can be changed by prepareCall; explicit generate/stream call options take precedence. Provider-executed tools are unaffected.
  • activeTools? (ActiveTools<TTools>): Default set of active tools. Limits which tools the model can call without changing tool call and result types. Per-stream values override this default.
  • output? (OutputSpecification): Default structured output specification. Per-stream values override this default.
  • repairToolCall? (ToolCallRepairFunction): Default function to repair tool calls that fail to parse. Mark the function with 'use step' when used in a workflow. Per-stream values override this default.
  • experimental_download? (DownloadFunction): Default custom download function for URLs. Mark the function with 'use step' when used in a workflow. Per-stream values override this default.
  • experimental_sandbox? (Experimental_SandboxSession): Default sandbox session passed to tool descriptions and execution as experimental_sandbox, and exposed to prepareStep. Per-stream values override this default.
  • experimental_toolApprovalSecret? (WorkflowToolApprovalSecret): Workflow-safe reference to the environment variable containing the secret used to HMAC-sign tool approval requests and verify approved message history before tool execution. Only the environment variable name crosses workflow boundaries; the secret is read inside signing and verification steps. Per-stream values override this default.
  • prepareStep? (PrepareStepCallback): Callback called before each step in the agent loop. Use it to modify settings, manage context, inject messages dynamically, or override experimental_sandbox for the current step. Receives step number, previous steps, messages, context, and sandbox.
  • prepareCall? (PrepareCallCallback): Callback called once before the agent loop starts. Use it to transform model, instructions, tools configuration, or other settings based on runtime context. Cannot override tools (bound at construction for type safety).
  • runtimeContext? (Context): Default shared runtime context for every stream call. Flows through prepareStep, lifecycle callbacks, and step results. Per-stream values override this default. Must be serializable when used in workflows.
  • toolsContext? (InferToolSetContext<TTools>): Default per-tool context map for every stream call. Each tool receives only its own validated entry as context. Per-stream values override this default. Must be serializable when used in workflows.
  • telemetry? (TelemetryOptions): Telemetry configuration with options for enabling/disabling telemetry, setting a function ID, and recording inputs/outputs.
  • onStart? (WorkflowAgentOnStartCallback): Callback called when the agent starts streaming, before any LLM calls. Receives the model, messages, runtime context, and tools context. If also specified in stream(), both callbacks fire (constructor first). Takes precedence over experimental_onStart when both are provided in the constructor.
    • GenerateTextStartEvent
      • model (LanguageModel): The model being used for the generation.
      • messages (Array<ModelMessage>): The messages being sent to the model.
      • runtimeContext (Context): The shared runtime context for the agent loop.
      • toolsContext (InferToolSetContext<Tools>): The per-tool context map for the agent loop.
  • experimental_onStart? (WorkflowAgentOnStartCallback): Deprecated alias for onStart. Used only when onStart is not provided in the constructor.
  • onStepStart? (WorkflowAgentOnStepStartCallback): Callback called before each step (LLM call) begins. Receives step number, model, messages, previous steps, runtime context, and tools context. If also specified in stream(), both callbacks fire (constructor first). Takes precedence over experimental_onStepStart when both are provided in the constructor.
    • GenerateTextStepStartEvent
      • model (LanguageModel): The model being used for this step.
      • messages (Array<ModelMessage>): The messages that will be sent to the model for this step.
      • steps (ReadonlyArray<StepResult>): Results from all previously finished steps.
      • runtimeContext (Context): The shared runtime context for this step.
      • toolsContext (InferToolSetContext<Tools>): The per-tool context map for this step.
  • experimental_onStepStart? (WorkflowAgentOnStepStartCallback): Deprecated alias for onStepStart. Used only when onStepStart is not provided in the constructor.
  • onToolExecutionStart? (WorkflowAgentOnToolExecutionStartCallback): Callback called right before a tool's execute function runs. If also specified in stream(), both callbacks fire (constructor first). Experimental (can break in patch releases).
    • WorkflowAgentToolExecutionStartEvent
      • toolCall ({ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }): The tool call being executed. For concrete tool sets, the tool name and input are correlated.
      • stepNumber (number): The current step number, starting at zero.
      • messages (Array<ModelMessage>): Messages that were sent to the language model to initiate the response that contained the tool call.
      • toolContext (InferToolContext<TOOLS[NAME]> | undefined): The validated context for the tool call. For concrete tool sets, each event union member pairs it with the corresponding tool name.
  • onToolExecutionEnd? (WorkflowAgentOnToolExecutionEndCallback): Callback called right after a tool's execute function completes or errors. Check success to determine whether output or error is available. If also specified in stream(), both callbacks fire (constructor first). Experimental (can break in patch releases).
    • WorkflowAgentToolExecutionEndEvent
      • toolCall ({ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }): The tool call that was executed. For concrete tool sets, the tool name and input are correlated.
      • stepNumber (number): The current step number, starting at zero.
      • durationMs (number): Tool execution time in milliseconds. Workflow agents use durationMs; AI SDK Core generation callbacks use toolExecutionMs.
      • messages (Array<ModelMessage>): Messages that were sent to the language model to initiate the response that contained the tool call.
      • toolContext (InferToolContext<TOOLS[NAME]> | undefined): The validated context for the tool call. For concrete tool sets, each event union member pairs it with the corresponding tool name.
      • success (boolean): Whether the tool execution succeeded. Discriminates between the output and error event variants.
      • output? (InferToolOutput<TOOLS[NAME]>): The tool output. Available when success is true and correlated with the configured tool.
      • error? (unknown): The tool execution error. Available when success is false.
  • onStepEnd? (WorkflowAgentOnStepEndCallback): Callback invoked after each agent step completes. If also specified in stream(), both callbacks fire (constructor first).
  • onStepFinish? (WorkflowAgentOnStepFinishCallback): Deprecated. Use onStepEnd instead. This alias is only used as a fallback when onStepEnd is not provided.
  • onEnd? (WorkflowAgentOnEndCallback): Callback called when all agent steps are finished and the response is complete. Receives steps, messages, text, finish reason, total usage, and context. If also specified in stream(), both callbacks fire (constructor first).
  • maxOutputTokens? (number): Maximum number of tokens the model is allowed to generate.
  • temperature? (number): Sampling temperature, controls randomness.
  • topP? (number): Top-p (nucleus) sampling parameter.
  • topK? (number): Top-k sampling parameter.
  • presencePenalty? (number): Presence penalty parameter.
  • frequencyPenalty? (number): Frequency penalty parameter.
  • stopSequences? (string[]): Custom token sequences which stop the model output.
  • seed? (number): Seed for deterministic generation (if supported).
  • maxRetries? (number): How many times to retry retryable model-call failures. Set to 0 to disable retries. Retry-After response headers are respected, and durable workflow step retries are not stacked. Default: 2.
  • headers? (Record<string, string | undefined>): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.
  • providerOptions? (ProviderOptions): Additional provider-specific configuration.

Properties

  • id (string | undefined): The id of the agent. Used for telemetry identification. Read-only.
  • tools (Record<string, Tool>): The tool set configured for this agent. Read-only.

Methods

generate()

Runs the durable agent loop using the model's non-streaming doGenerate() method. Model calls and tool execution use the same orchestration as stream().

const result = await agent.generate({ prompt: 'Summarize the findings.' });
console.log(result.text);
console.log(result.usage);

Accepts WorkflowAgentGenerateOptions, sharing prompt/messages, instructions, generation settings, tools and runtime context, preparation, stopping, output, and lifecycle options with stream(). It does not accept writable, includeRawChunks, experimental_transform, sendFinish, or preventClose. Without a configured stop condition, generation stops after at most 20 steps. stream() retains its existing unbounded default.

The include option accepts requestBody, responseBody, and requestMessages booleans, all defaulting to false. Enable them only when the additional request/response data is needed. Response IDs, timestamps, model IDs, headers, and provider metadata are retained when supplied by the model.

Returns

Returns Promise<WorkflowAgentGenerateResult> with the core GenerateTextResult semantics:

  • text, finishReason, rawFinishReason, and finalStep describe the final step.
  • content, files, sources, warnings, tool calls, and tool results cover all steps.
  • usage and totalUsage sum all steps while retaining token details and unknown counts.
  • responseMessages includes generated messages, even when prepareStep replaces the input history.
  • output defaults to text, or follows the constructor/per-call output specification. Reading it throws NoOutputGeneratedError when output is unavailable, such as a final tool-calls response.

Inside a workflow, timeout bounds model calls, including retries and dispatch after suspension. It does not cancel a hook that a tool is waiting on. Use the Workflow runtime’s run.cancel() to stop a suspended run. See deadlines, cancellation, and replay.

Generation failures reject. The end callback runs before complete-output parsing; a parsing failure can therefore reject after onEnd has run. Existing Workflow callback shapes and preparation precedence apply; they are not all identical to core's callback APIs.

Note

Use the result API inside workflow code and return selected serializable fields (for example, text and usage) through run.returnValue. Returning the whole result class is not supported. Performance data currently measures the model response; complete tool/step timing is not yet reported.

Approval requests appear in content and responseMessages without requiring a writable. To continue, append responseMessages to the original history and add a tool message containing the user's tool-approval-response. Preserve the request signature and tool-call input; use the same experimental_toolApprovalSecret environment-variable reference on continuation. Local approvals are validated before execution, and provider-executed approval responses are forwarded to the provider. See approval round trips.

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>): A prompt string or a list of messages. You can either use prompt or messages but not both.
  • messages (Array<ModelMessage>): The conversation messages to process. You can either use prompt or messages but not both.
  • writable? (WritableStream<ModelCallStreamPart>): A writable stream that receives raw model stream parts in real-time. Convert to UI message chunks at the response boundary using createModelCallToUIChunkTransform().
  • instructions? (Instructions): Override the agent instructions for this call.
  • system? (string): Deprecated. Use instructions instead.
  • stopWhen? (StopCondition | StopCondition[]): Condition(s) for ending the agent loop. When omitted and no constructor-level condition is configured, WorkflowAgent has no maximum step count and continues until natural completion. Use isStepCount() to bound execution.
  • toolChoice? (ToolChoice): Override the tool choice strategy for this call. Default: 'auto'.
  • toolCallConcurrency? (number): Override the local tool call concurrency limit for this call. Must be a positive integer. Set to 1 for sequential execution in model order, including approved calls. Takes precedence over the constructor and prepareCall.
  • activeTools? (ActiveTools<TTools>): Limits the subset of tools available for this call without changing tool call and result types.
  • output? (OutputSpecification): Structured output specification. Use Output.object({ schema }) for typed objects or Output.text() for text.
  • timeout? (number): Model-call deadline in milliseconds. Inside a workflow, the absolute deadline is checked at dispatch and retry and remains in effect across suspension. It does not cancel a tool waiting on a Workflow hook; use run.cancel() to stop the suspended run.
  • sendFinish? (boolean): Whether to send a 'finish' chunk to the writable stream when streaming completes. Default: true.
  • preventClose? (boolean): Whether to prevent the writable stream from being closed after streaming completes. Default: false.
  • includeRawChunks? (boolean): Include raw, unprocessed chunks from the provider in the stream. Default: false.
  • repairToolCall? (ToolCallRepairFunction): Callback to attempt automatic recovery when a tool call cannot be parsed. Mark the function with 'use step' when used in a workflow.
  • experimental_transform? (StreamTextTransform | Array<StreamTextTransform>): Factories for stream transformations, applied in order to raw provider events before model processing and writable output. Each factory must return a TransformStream synchronously and maintain the stream structure. Mark each factory with 'use step' when used in a workflow.
  • experimental_download? (DownloadFunction): Custom download function for fetching files/URLs. Mark the function with 'use step' when used in a workflow.
  • experimental_sandbox? (Experimental_SandboxSession): Sandbox session passed to tool descriptions and execution as experimental_sandbox, and exposed to prepareStep. Overrides the constructor default.
  • experimental_toolApprovalSecret? (WorkflowToolApprovalSecret): Workflow-safe reference to the environment variable containing the secret used to HMAC-sign tool approval requests and verify approved message history before tool execution. Only the environment variable name crosses workflow boundaries; the secret is read inside signing and verification steps. Overrides the constructor default.
  • telemetry? (TelemetryOptions): Per-call telemetry configuration.
  • runtimeContext? (Context): Shared runtime context for this stream call. Overrides the constructor default and flows through prepareStep, lifecycle callbacks, and step results. Must be serializable when used in workflows.
  • toolsContext? (InferToolSetContext<TTools>): Per-tool context map for this stream call. Overrides the constructor default. Each tool receives only its own validated entry as context. Must be serializable when used in workflows.
  • prepareStep? (PrepareStepCallback): Per-call prepareStep override. Receives the initial instructions and messages alongside the current step state.
  • onStart? (WorkflowAgentOnStartCallback): Per-call onStart callback. If also specified in the constructor, both fire (constructor first). Takes precedence over experimental_onStart when both are provided in this call.
  • experimental_onStart? (WorkflowAgentOnStartCallback): Deprecated alias for the per-call onStart callback. Used only when onStart is not provided in this call.
  • onStepStart? (WorkflowAgentOnStepStartCallback): Per-call onStepStart callback. If also specified in the constructor, both fire (constructor first). Takes precedence over experimental_onStepStart when both are provided in this call.
  • experimental_onStepStart? (WorkflowAgentOnStepStartCallback): Deprecated alias for the per-call onStepStart callback. Used only when onStepStart is not provided in this call.
  • onToolExecutionStart? (WorkflowAgentOnToolExecutionStartCallback): Per-call onToolExecutionStart callback. If also specified in the constructor, both fire (constructor first).
  • onToolExecutionEnd? (WorkflowAgentOnToolExecutionEndCallback): Per-call onToolExecutionEnd callback. If also specified in the constructor, both fire (constructor first).
  • onStepEnd? (WorkflowAgentOnStepEndCallback): Per-call onStepEnd callback. If also specified in the constructor, both fire (constructor first).
  • onStepFinish? (WorkflowAgentOnStepFinishCallback): Deprecated. Use onStepEnd instead. This alias is only used as a fallback when onStepEnd is not provided.
  • onEnd? (WorkflowAgentOnEndCallback): Per-call onEnd callback. If also specified in the constructor, both fire (constructor first).
  • onError? (WorkflowAgentOnErrorCallback): Callback invoked when an error occurs during streaming.
  • onAbort? (WorkflowAgentOnAbortCallback): Callback invoked when the operation is aborted. Receives all previously finished steps.

Returns

Returns a Promise<WorkflowAgentStreamResult> with the following properties:

  • messages (Array<ModelMessage>): The final messages including all tool calls and results.
  • steps (Array<StepResult>): Details for all steps taken by the agent.
  • toolCalls (Array<ToolCall>): Tool calls from the last step, including unexecuted calls (e.g., tools requiring approval).
  • toolResults (Array<ToolResult>): Tool results from the last step. Only includes results for tools that were executed.
  • error (unknown | undefined): The original value from a model stream error part. The property is present when an error part was emitted, even if its value is undefined; use 'error' in result to distinguish that case.
  • output (OUTPUT): The structured output if an output specification was provided.

repairToolCall in a workflow

repairToolCall runs inside the model step. When you use it in a workflow, define the callback as a function with a 'use step' directive. You can set it on the constructor or pass it to stream().

Workflow saves a reference to the registered function and loads its implementation inside the model step. Keep any values captured by the function serializable. An ordinary inline callback causes a serialization error when it crosses this step boundary.

The callback can be asynchronous. Return the repaired tool call, or null if the repair cannot be completed. See tool call repair for repair strategies.

experimental_transform in a workflow

Pass a transform factory, or an array of factories, to stream(). Mark each factory with 'use step' so Workflow can load it inside the model step. The factory must return a TransformStream synchronously. Keep any captured values serializable.

import type { LanguageModelV4StreamPart } from '@ai-sdk/provider';
import { WorkflowAgent } from '@ai-sdk/workflow';
import { getWritable } from 'workflow';

function uppercaseText() {
  'use step';

  return new TransformStream<
    LanguageModelV4StreamPart,
    LanguageModelV4StreamPart
  >({
    transform(part, controller) {
      controller.enqueue(
        part.type === 'text-delta'
          ? { ...part, delta: part.delta.toUpperCase() }
          : part,
      );
    },
  });
}

export async function uppercaseWorkflow() {
  'use workflow';

  const agent = new WorkflowAgent({ model: 'openai/gpt-5' });

  return agent.stream({
    messages: [{ role: 'user', content: 'Write a short greeting.' }],
    writable: getWritable(),
    experimental_transform: uppercaseText,
  });
}

Utilities

createModelCallToUIChunkTransform(options?)

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()),
});

When resuming with a WorkflowChatTransport cursor, replay the raw workflow stream from index 0 and pass the non-negative UI chunk index to the transform:

const readable = run
  .getReadable({ startIndex: 0 })
  .pipeThrough(createModelCallToUIChunkTransform({ uiStartIndex: startIndex }));

uiStartIndex must be a non-negative safe integer. Raw model stream parts and UI message chunks are not one-to-one, so do not pass a UI chunk index to getReadable. Negative tail indexes require a durable stream that already stores UIMessageChunk objects.

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-5.5',
  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-5.5',
  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 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-5.5',
    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-5.5',
});

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-5.5',
  experimental_toolApprovalSecret: {
    environmentVariable: 'TOOL_APPROVAL_SECRET',
  },
  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,
    }),
  },
});

When experimental_toolApprovalSecret is configured, each approval request is signed over its approval ID, tool call ID, tool name, and validated input. Replayed approvals with a missing or invalid signature do not execute the tool. The signature is preserved in the durable stream and UI message history, while only the environment variable name crosses workflow boundaries. Signing and verification steps read the raw secret from their local environment and do not serialize it. A stream-level reference overrides the constructor value.

Agent with Lifecycle Callbacks

import { WorkflowAgent } from '@ai-sdk/workflow';

const agent = new WorkflowAgent({
  model: 'anthropic/claude-sonnet-5.5',
  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)
  async onStepEnd({ usage }) {
    await trackUsage(usage);
  },

  onEnd({ steps, totalUsage }) {
    console.log(
      `Done in ${steps.length} steps, ${totalUsage.totalTokens} tokens`,
    );
  },
});