> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/vercel/workflow/llms.txt
> Use this file to discover all available pages before exploring further.

# DurableAgent

> Build AI agents with automatic state management and retries

## Overview

`DurableAgent` is a class for building AI agents that maintain state across workflow executions. It wraps AI model providers with durable execution capabilities, ensuring that your AI agents can survive interruptions, handle long-running operations, and automatically recover from failures.

## Constructor

```typescript theme={null}
const agent = new DurableAgent(options)
```

<ParamField path="options" type="DurableAgentOptions" required>
  Configuration for the durable agent

  <Expandable title="properties">
    <ParamField path="model" type="string | (() => Promise<LanguageModel>)" required>
      The AI model to use. Can be:

      * A string for AI Gateway (e.g., `'anthropic/claude-opus'`)
      * A function returning a model instance from a provider
    </ParamField>

    <ParamField path="tools" type="ToolSet">
      Tools available to the agent. Each tool should have:

      * `description`: Human-readable description
      * `inputSchema`: Zod schema for validation
      * `execute`: Function to run (can be a workflow step)
    </ParamField>

    <ParamField path="system" type="string">
      System prompt to guide the agent's behavior
    </ParamField>

    <ParamField path="toolChoice" type="ToolChoice">
      Strategy for tool selection. Options: `'auto'`, `'required'`, `'none'`, or `{ type: 'tool', toolName: string }`

      Default: `'auto'`
    </ParamField>

    <ParamField path="maxOutputTokens" type="number">
      Maximum tokens to generate in responses
    </ParamField>

    <ParamField path="temperature" type="number">
      Sampling temperature (0-1+). Higher values increase randomness.

      Recommended: Set either `temperature` or `topP`, not both
    </ParamField>

    <ParamField path="topP" type="number">
      Nucleus sampling probability (0-1). Only tokens with top P probability mass are considered.

      Recommended: Set either `temperature` or `topP`, not both
    </ParamField>

    <ParamField path="topK" type="number">
      Only sample from top K options. Advanced use only.
    </ParamField>

    <ParamField path="presencePenalty" type="number">
      Penalty for repeating information (-1 to 1). 0 means no penalty.
    </ParamField>

    <ParamField path="frequencyPenalty" type="number">
      Penalty for repeating words/phrases (-1 to 1). 0 means no penalty.
    </ParamField>

    <ParamField path="stopSequences" type="string[]">
      Stop generation when these sequences are encountered
    </ParamField>

    <ParamField path="seed" type="number">
      Random seed for deterministic generation (if supported by model)
    </ParamField>

    <ParamField path="maxRetries" type="number" default="2">
      Maximum retry attempts for transient failures
    </ParamField>

    <ParamField path="experimental_telemetry" type="TelemetrySettings">
      Observability configuration for tracing and metrics
    </ParamField>
  </Expandable>
</ParamField>

## Methods

### stream()

Streams AI responses with tool execution and state management.

```typescript theme={null}
const result = await agent.stream(options)
```

<ParamField path="options" type="DurableAgentStreamOptions" required>
  <Expandable title="properties">
    <ParamField path="messages" type="ModelMessage[]" required>
      Conversation history in AI SDK format. Each message has:

      * `role`: `'user'`, `'assistant'`, or `'system'`
      * `content`: String or array of content parts
    </ParamField>

    <ParamField path="writable" type="WritableStream<UIMessageChunk>" required>
      Stream to write response chunks. Use `getWritable()` from `workflow` package.
    </ParamField>

    <ParamField path="system" type="string">
      Override the system prompt for this request
    </ParamField>

    <ParamField path="preventClose" type="boolean" default="false">
      Keep stream open after completion (useful for multiple writes)
    </ParamField>

    <ParamField path="sendStart" type="boolean" default="true">
      Send a 'start' chunk at the beginning of the stream
    </ParamField>

    <ParamField path="sendFinish" type="boolean" default="true">
      Send a 'finish' chunk at the end of the stream
    </ParamField>

    <ParamField path="maxSteps" type="number">
      Maximum number of sequential LLM calls. Prevents infinite loops.
    </ParamField>

    <ParamField path="stopWhen" type="StopCondition | StopCondition[]">
      Conditions to stop generation early (e.g., when specific tools are called)
    </ParamField>

    <ParamField path="toolChoice" type="ToolChoice">
      Override tool selection strategy for this request
    </ParamField>

    <ParamField path="activeTools" type="string[]">
      Limit available tools to this subset
    </ParamField>

    <ParamField path="experimental_output" type="OutputSpecification">
      Parse structured output from the response. Use `Output.object({ schema })` or `Output.text()`.
    </ParamField>

    <ParamField path="collectUIMessages" type="boolean" default="false">
      Accumulate UIMessage\[] during streaming. Result will include `uiMessages` property.
    </ParamField>

    <ParamField path="includeRawChunks" type="boolean" default="false">
      Include raw provider chunks in the stream for advanced use cases
    </ParamField>

    <ParamField path="prepareStep" type="PrepareStepCallback">
      Callback before each LLM call. Use for context management or dynamic configuration.

      ```typescript theme={null}
      prepareStep: async ({ messages, stepNumber }) => {
        // Inject messages, change model, etc.
        return { messages: [...messages, ...injectedMessages] };
      }
      ```
    </ParamField>

    <ParamField path="onStepFinish" type="StreamTextOnStepFinishCallback">
      Called after each LLM step completes
    </ParamField>

    <ParamField path="onFinish" type="StreamTextOnFinishCallback">
      Called when all steps complete successfully
    </ParamField>

    <ParamField path="onError" type="StreamTextOnErrorCallback">
      Called when an error occurs during streaming
    </ParamField>

    <ParamField path="onAbort" type="StreamTextOnAbortCallback">
      Called when the operation is aborted
    </ParamField>

    <ParamField path="experimental_context" type="unknown">
      Context passed to tool execution functions
    </ParamField>

    <ParamField path="experimental_repairToolCall" type="ToolCallRepairFunction">
      Function to repair failed tool call parsing
    </ParamField>

    <ParamField path="experimental_transform" type="StreamTextTransform | StreamTextTransform[]">
      Custom stream transformations
    </ParamField>

    <ParamField path="experimental_download" type="DownloadFunction">
      Custom URL download handler
    </ParamField>
  </Expandable>
</ParamField>

**Returns:** `Promise<DurableAgentStreamResult>`

<ResponseField name="messages" type="ModelMessage[]">
  Final conversation messages including all tool calls and results
</ResponseField>

<ResponseField name="steps" type="StepResult[]">
  Details for each LLM step executed during the stream
</ResponseField>

<ResponseField name="experimental_output" type="OUTPUT">
  Parsed structured output (only when `experimental_output` is specified)
</ResponseField>

<ResponseField name="uiMessages" type="UIMessage[]">
  Accumulated UI messages (only when `collectUIMessages: true`)
</ResponseField>

## Examples

### Basic Usage

```typescript theme={null}
import { DurableAgent } from '@workflow/ai';
import { anthropic } from '@workflow/ai/providers/anthropic';
import { getWritable } from 'workflow';

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

  const agent = new DurableAgent({
    model: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })('claude-3-5-sonnet-20241022'),
    system: 'You are a helpful coding assistant.',
    temperature: 0.7,
  });

  const result = await agent.stream({
    messages: [
      { role: 'user', content: 'Explain how async/await works in JavaScript' },
    ],
    writable: getWritable(),
  });

  console.log('Generated', result.steps.length, 'steps');
}
```

### With Tools

```typescript theme={null}
import { DurableAgent } from '@workflow/ai';
import { openai } from '@workflow/ai/providers/openai';
import { getWritable } from 'workflow';
import { z } from 'zod';

async function searchDatabase(query: string) {
  'use step';
  // This runs as a durable step with automatic retries
  const results = await db.search(query);
  return results;
}

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

  const agent = new DurableAgent({
    model: openai({ apiKey: process.env.OPENAI_API_KEY })('gpt-4o'),
    tools: {
      searchDatabase: {
        description: 'Search the customer database',
        inputSchema: z.object({
          query: z.string().describe('Search query'),
        }),
        execute: async ({ query }) => searchDatabase(query),
      },
    },
  });

  await agent.stream({
    messages: [
      { role: 'user', content: 'Find customers in San Francisco' },
    ],
    writable: getWritable(),
    maxSteps: 5,
  });
}
```

### Structured Output

```typescript theme={null}
import { DurableAgent, Output } from '@workflow/ai';
import { google } from '@workflow/ai/providers/google';
import { getWritable } from 'workflow';
import { z } from 'zod';

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

  const agent = new DurableAgent({
    model: google({ apiKey: process.env.GOOGLE_API_KEY })('gemini-2.0-flash-exp'),
  });

  const result = await agent.stream({
    messages: [
      { role: 'user', content: 'This product is amazing! I love it.' },
    ],
    writable: getWritable(),
    experimental_output: Output.object({
      schema: z.object({
        sentiment: z.enum(['positive', 'negative', 'neutral']),
        confidence: z.number().min(0).max(1),
        reasoning: z.string(),
      }),
    }),
  });

  console.log(result.experimental_output);
  // { sentiment: 'positive', confidence: 0.95, reasoning: '...' }
}
```

### Dynamic Context Management

```typescript theme={null}
import { DurableAgent } from '@workflow/ai';
import { anthropic } from '@workflow/ai/providers/anthropic';
import { getWritable } from 'workflow';

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

  const agent = new DurableAgent({
    model: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })('claude-3-5-sonnet-20241022'),
  });

  await agent.stream({
    messages: [
      { role: 'user', content: 'Help me with my code' },
    ],
    writable: getWritable(),
    prepareStep: async ({ messages, stepNumber }) => {
      // Inject context from external sources before each LLM call
      if (stepNumber === 0) {
        const context = await loadUserContext();
        return {
          messages: [
            { role: 'system', content: `User context: ${context}` },
            ...messages,
          ],
        };
      }
      return {};
    },
  });
}
```

## Type Definitions

### ToolSet

```typescript theme={null}
type ToolSet = Record<string, {
  description: string;
  inputSchema: ZodSchema;
  execute?: (input: any, context: {
    toolCallId: string;
    messages: ModelMessage[];
    experimental_context?: unknown;
  }) => Promise<any> | any;
}>;
```

### ModelMessage

```typescript theme={null}
type ModelMessage = {
  role: 'user' | 'assistant' | 'system';
  content: string | Array<{
    type: 'text' | 'image';
    text?: string;
    image?: string | Uint8Array | URL;
  }>;
};
```

### StepResult

```typescript theme={null}
type StepResult = {
  text: string;
  toolCalls: ToolCall[];
  toolResults: ToolResult[];
  finishReason: 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other';
  usage: {
    promptTokens: number;
    completionTokens: number;
    totalTokens: number;
  };
  response: {
    id: string;
    model: string;
    timestamp: Date;
  };
};
```

## Best Practices

1. **Use workflow steps for tools**: Mark tool `execute` functions with `'use step'` for automatic retries and durability

2. **Set maxSteps**: Always set a reasonable `maxSteps` limit to prevent infinite loops

3. **Handle errors gracefully**: Use `onError` callback to log and handle errors appropriately

4. **Manage context size**: Use `prepareStep` to inject/remove messages dynamically and manage context window

5. **Stream to the client**: Always use `getWritable()` to stream responses for better UX

6. **Choose the right model**: Use `prepareStep` to switch models based on task complexity

## See Also

* [AI Providers](/api-reference/workflow-ai/providers)
* [Workflow API](/api-reference/workflow-api/create-workflow-runtime)
* [AI SDK Documentation](https://sdk.vercel.ai/docs)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.