ai-sdk-deepagent

Customization Guide

Comprehensive guide to customizing agent behavior, tools, backends, and configuration

This guide covers all customization options for ai-sdk-deep-agent, from basic prompts to advanced middleware and control hooks.

Table of Contents

  1. System Prompts
  2. Tool Configuration
  3. Adding Custom Tools
  4. Backend Selection and Configuration
  5. Middleware Usage
  6. Loop Control Customization
  7. Generation Options
  8. Subagent Customization
  9. Provider-Specific Options
  10. Agent Memory and Skills
  11. Structured Output
  12. Human-in-the-Loop

This document is actively maintained and updated with the latest features.

1. System Prompts

Basic Custom Prompt

Provide custom instructions to shape agent behavior:

import { createDeepAgent } from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  systemPrompt: `You are a senior software architect specializing in TypeScript.
Focus on clean code, type safety, and testing best practices.
Always explain trade-offs when making architectural decisions.`,
});

Custom Prompt Components

The library builds a complete system prompt from multiple components:

// Full prompt composition (in order):
1. Your custom systemPrompt (if provided)
2. BASE_PROMPT - "In order to complete the objective..."
3. TODO_SYSTEM_PROMPT - Task planning instructions
4. FILESYSTEM_SYSTEM_PROMPT - Virtual filesystem usage
5. EXECUTE_SYSTEM_PROMPT - Shell command execution (if sandbox backend)
6. TASK_SYSTEM_PROMPT - Subagent spawning instructions (if subagents enabled)
7. Skills prompt - Agent-specific skills (if agentId provided)
8. Agent memory - Persistent memory context (if agent memory middleware)

Replacing Default Prompts

To completely override default prompts, provide an empty systemPrompt and use middleware:

import { wrapLanguageModel } from 'ai';

const customPromptMiddleware = {
  wrapGenerate: async ({ doGenerate, params }) => {
    // Replace all prompts with your custom version
    const customPrompt = params.prompt.map(msg => {
      if (msg.role === 'system') {
        return { ...msg, content: 'Your completely custom prompt here' };
      }
      return msg;
    });
    return doGenerate({ ...params, prompt: customPrompt });
  },
};

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  middleware: customPromptMiddleware,
});

This document is actively maintained and updated with the latest features.

2. Tool Configuration

Default Tools

By default, agents include:

  • write_todos - Task planning and tracking
  • ls - List directories
  • read_file - Read file contents
  • write_file - Write new files
  • edit_file - Edit existing files
  • glob - Find files by pattern
  • grep - Search file contents
  • web_search - Web search (if TAVILY_API_KEY set)
  • http_request - HTTP requests
  • fetch_url - Fetch and convert URLs to markdown
  • execute - Shell commands (if sandbox backend)
  • task - Spawn subagents

Disabling Web Tools

Remove web tools by not setting TAVILY_API_KEY:

// Web tools only load when TAVILY_API_KEY is set
delete process.env.TAVILY_API_KEY;

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  // web_search, http_request, fetch_url will not be available
});

Disabling Subagent Tool

Prevent agent from spawning subagents:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  includeGeneralPurposeAgent: false, // Disable general subagent
  subagents: [], // No custom subagents
  // task tool will not be available
});

Custom Tool Selection

Replace specific tools with custom implementations:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  tools: {
    // Override default write_todos with custom version
    write_todos: myCustomTodosTool,

    // Add new custom tools
    my_custom_tool: customTool,
  },
  // Your tools are merged with built-in tools
  // (except tools you explicitly override)
});

This document is actively maintained and updated with the latest features.

3. Adding Custom Tools

Basic Custom Tool

Use AI SDK's tool() function:

import { tool } from 'ai';
import { z } from 'zod';

const weatherTool = tool({
  description: 'Get current weather for a location',
  parameters: z.object({
    location: z.string().describe('City name or coordinates'),
    units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
  }),
  execute: async ({ location, units }) => {
    // Call weather API
    const response = await fetch(`https://api.weather.com/${location}`);
    const data = await response.json();
    return {
      location,
      temperature: data.temp,
      units,
      condition: data.condition,
    };
  },
});

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  tools: { get_weather: weatherTool },
});

Tool with Event Emission

Emit custom events during tool execution:

import type { EventCallback } from 'ai-sdk-deep-agent';

function createCustomTool(onEvent?: EventCallback) {
  return tool({
    description: 'Custom tool with events',
    parameters: z.object({
      input: z.string(),
    }),
    execute: async ({ input }) => {
      // Emit start event
      onEvent?.({
        type: 'custom-start',
        input,
      });

      const result = await doWork(input);

      // Emit finish event
      onEvent?.({
        type: 'custom-finish',
        result,
      });

      return result;
    },
  });
}

Tool with Backend Access

Create tools that interact with the virtual filesystem:

function createBackendTool(backend: BackendProtocol) {
  return tool({
    description: 'Analyze all files in a directory',
    parameters: z.object({
      directory: z.string(),
    }),
    execute: async ({ directory }) => {
      const files = await backend.lsInfo(directory);

      const analysis = await Promise.all(
        files.map(async (file) => {
          if (!file.is_dir) {
            const content = await backend.read(file.path);
            return { path: file.path, analysis: analyzeContent(content) };
          }
        })
      );

      return analysis;
    },
  });
}

Advanced Tool Example: Database Query

const dbQueryTool = tool({
  description: 'Execute read-only SQL queries against the database',
  parameters: z.object({
    query: z.string().describe('SELECT query to execute'),
  }),
  execute: async ({ query }) => {
    // Validate query is read-only
    if (!query.trim().toLowerCase().startsWith('select')) {
      throw new Error('Only SELECT queries are allowed');
    }

    const result = await db.execute(query);

    return {
      columns: result.columns,
      rows: result.rows.slice(0, 100), // Limit results
      count: result.rows.length,
    };
  },
});

This document is actively maintained and updated with the latest features.

4. Backend Selection and Configuration

StateBackend (Default)

In-memory storage for ephemeral sessions:

import { StateBackend } from 'ai-sdk-deep-agent';

const state = { todos: [], files: {} };
const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  backend: new StateBackend(state),
});

// Access state after generation
const result = await agent.generate({
  prompt: "Create a file",
});

console.log(result.state.files); // Files created during generation

FilesystemBackend

Persist files to disk:

import { FilesystemBackend } from 'ai-sdk-deep-agent';

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  backend: new FilesystemBackend({
    rootDir: './workspace', // Files written to this directory
  }),
});

PersistentBackend

Cross-session persistence with key-value store:

import { PersistentBackend, InMemoryStore } from 'ai-sdk-deep-agent';

const store = new InMemoryStore();
// Or use Redis, SQLite, etc.

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  backend: new PersistentBackend({
    store,
    namespace: 'project-1', // Isolate state by namespace
  }),
});

// State persists across agent instances
await agent.generate({ prompt: "Create file.txt" });

// Later session...
const agent2 = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  backend: new PersistentBackend({ store, namespace: 'project-1' }),
});
// file.txt is still available

CompositeBackend

Route files to different backends by path prefix:

import { CompositeBackend, FilesystemBackend, StateBackend } from 'ai-sdk-deep-agent';

const state = { todos: [], files: {} };

const backend = new CompositeBackend(
  new StateBackend(state), // Default backend
  {
    '/persistent/': new FilesystemBackend({ rootDir: './persistent' }),
    '/cache/': new StateBackend(state),
    '/temp/': new FilesystemBackend({ rootDir: './tmp' }),
  }
);

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  backend,
});

// Agent writes to /persistent/file.txt → disk
// Agent writes to /cache/file.txt → memory
// Agent writes to /other/file.txt → memory (default)

LocalSandbox

Enable code execution:

import { LocalSandbox } from 'ai-sdk-deep-agent';

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  backend: new LocalSandbox({
    rootDir: './sandbox',
    timeoutMs: 30000, // 30 second timeout per command
  }),
  // execute tool is now available
});

Custom Backend

Implement the BackendProtocol interface:

import type {
  BackendProtocol,
  FileData,
  FileInfo,
  WriteResult,
  EditResult,
  GrepMatch,
} from 'ai-sdk-deep-agent';

class CloudStorageBackend implements BackendProtocol {
  async lsInfo(path: string): Promise<FileInfo[]> {
    // List files in cloud storage
  }

  async read(filePath: string, offset?: number, limit?: number): Promise<string> {
    // Read from cloud
  }

  async readRaw(filePath: string): Promise<FileData> {
    // Read raw file data
  }

  async write(filePath: string, content: string): Promise<WriteResult> {
    // Write to cloud
  }

  async edit(
    filePath: string,
    oldString: string,
    newString: string,
    replaceAll?: boolean
  ): Promise<EditResult> {
    // Edit file in cloud
  }

  async grepRaw(
    pattern: string,
    path?: string,
    glob?: string | null
  ): Promise<GrepMatch[] | string> {
    // Search cloud files
  }

  async globInfo(pattern: string, path?: string): Promise<FileInfo[]> {
    // Glob cloud files
  }
}

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  backend: new CloudStorageBackend(),
});

This document is actively maintained and updated with the latest features.

5. Middleware Usage

Logging Middleware

Log all model calls:

import { wrapLanguageModel } from 'ai';

const loggingMiddleware = {
  wrapGenerate: async ({ doGenerate, params }) => {
    console.log('=== Model Call ===');
    console.log('Prompt:', params.prompt);
    console.log('Tools:', Object.keys(params.tools || {}));

    const start = Date.now();
    const result = await doGenerate();

    console.log('Response:', result.text);
    console.log('Duration:', Date.now() - start, 'ms');
    console.log('==================');

    return result;
  },
};

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  middleware: [loggingMiddleware],
});

Agent Memory Middleware

Load persistent agent memory from filesystem:

import { createAgentMemoryMiddleware } from 'ai-sdk-deep-agent/middleware';

const memoryMiddleware = createAgentMemoryMiddleware({
  agentId: 'code-architect',
  workingDirectory: process.cwd(),
  // Memory loaded from:
  // - ~/.deepagents/code-architect/agent.md (user-level)
  // - .deepagents/agent.md (project-level)
});

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  middleware: [memoryMiddleware],
});

// Agent remembers preferences and context across conversations
// Agent can update memory using write_file tool

Custom Telemetry Middleware

Track token usage and costs:

let totalTokens = 0;
let totalCost = 0;

const telemetryMiddleware = {
  wrapGenerate: async ({ doGenerate, params }) => {
    const result = await doGenerate();

    // Track usage (if available)
    if ('usage' in result) {
      const usage = result.usage as { totalTokens: number };
      totalTokens += usage.totalTokens;
      totalCost += calculateCost(usage.totalTokens);
    }

    console.log(`Cumulative: ${totalTokens} tokens, $${totalCost.toFixed(2)}`);

    return result;
  },
};

Rate Limiting Middleware

Prevent API overuse:

const rateLimiter = {
  lastCall: 0,
  minInterval: 1000, // 1 second between calls

  wrapGenerate: async ({ doGenerate }) => {
    const now = Date.now();
    const elapsed = now - rateLimiter.lastCall;

    if (elapsed < rateLimiter.minInterval) {
      const delay = rateLimiter.minInterval - elapsed;
      await new Promise(resolve => setTimeout(resolve, delay));
    }

    rateLimiter.lastCall = Date.now();
    return doGenerate();
  },
};

Multiple Middleware

Combine multiple middleware functions:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  middleware: [
    loggingMiddleware,
    memoryMiddleware,
    telemetryMiddleware,
  ],
  // Middleware applied in order
});

This document is actively maintained and updated with the latest features.

6. Loop Control Customization

Custom Stop Conditions

Stop the agent loop based on custom conditions:

import { stepCountIs } from 'ai';

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  loopControl: {
    stopWhen: [
      stepCountIs(50), // Max 50 steps

      // Stop when all todos completed
      ({ state }) => {
        return state.todos.length > 0 &&
          state.todos.every(t => t.status === 'completed');
      },

      // Stop when specific tool called
      ({ toolCalls }) => {
        return toolCalls.some(tc => tc.toolName === 'finish');
      },
    ],
  },
});

Prepare Step Hook

Modify parameters before each step:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  loopControl: {
    prepareStep: async ({ stepNumber, model, tools }) => {
      console.log(`Preparing step ${stepNumber}`);

      // Use smaller model for later steps
      if (stepNumber > 10) {
        return {
          model: anthropic('claude-haiku-4-5-20251001'),
        };
      }

      // Limit tool choices for first step
      if (stepNumber === 1) {
        return {
          activeTools: ['write_todos'], // Only allow planning
        };
      }

      return {}; // No changes
    },
  },
});

On Step Finish Hook

React to completed steps:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  loopControl: {
    onStepFinish: async ({ toolCalls, toolResults, messages }) => {
      // Log to analytics
      analytics.track('agent_step', {
        toolCount: toolCalls.length,
        toolsUsed: toolCalls.map(tc => tc.toolName),
      });

      // Check for errors
      const failedTools = toolResults.filter(
        r => !('output' in r) || r.error
      );

      if (failedTools.length > 0) {
        console.warn('Failed tools:', failedTools);
      }
    },
  },
});

On Finish Hook

Cleanup after agent completes:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  loopControl: {
    onFinish: async ({ finishReason, usage, messages }) => {
      console.log('Agent finished:', finishReason);

      // Save to database
      await db.conversations.create({
        messages,
        usage,
        timestamp: new Date(),
      });
    },
  },
});

This document is actively maintained and updated with the latest features.

7. Generation Options

Temperature and Sampling

Control response creativity:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  generationOptions: {
    temperature: 0.7,        // 0-2, higher = more creative
    topP: 0.9,              // Nucleus sampling (0-1)
    topK: 40,               // Top-K sampling
  },
});

// Low temperature for deterministic tasks
const codeAgent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  generationOptions: {
    temperature: 0.1, // More focused, deterministic
  },
});

// High temperature for creative tasks
const creativeAgent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  generationOptions: {
    temperature: 1.5, // More varied, creative
  },
});

Response Length Control

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  generationOptions: {
    maxOutputTokens: 4096,    // Limit response length
  },
});

Penalties

Reduce repetition:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  generationOptions: {
    presencePenalty: 0.5,    // -1 to 1, penalize new topics
    frequencyPenalty: 0.5,   // -1 to 1, penalize repetition
  },
});

Deterministic Outputs

Use seed for reproducible results:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  generationOptions: {
    temperature: 0,      // Must be 0 for deterministic
    seed: 42,            // Same seed = same output
  },
});

Stop Sequences

Define custom stop strings:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  generationOptions: {
    stopSequences: ['END_CONVERSATION', '---'],
  },
});

Retry Configuration

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  generationOptions: {
    maxRetries: 3, // Retry failed API calls up to 3 times
  },
});

This document is actively maintained and updated with the latest features.

8. Subagent Customization

Basic Subagent

Create specialized subagents:

import { type SubAgent } from 'ai-sdk-deep-agent';

const researchAgent: SubAgent = {
  name: 'researcher',
  description: 'Specialized for deep research and analysis tasks',
  systemPrompt: `You are a research specialist.
Focus on finding accurate information and citing sources.
Provide detailed, well-structured reports.`,
};

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  subagents: [researchAgent],
});

Subagent with Custom Tools

const readonlyAgent: SubAgent = {
  name: 'code-reviewer',
  description: 'Read-only agent for code review',
  systemPrompt: 'Review code for bugs and improvements',

  // Only provide read tools
  tools: [
    createLsTool,
    createReadFileTool,
    createGrepTool,
    createGlobTool,
  ],
};

Subagent with Different Model

Use faster/cheaper model for subagents:

import { anthropic } from '@ai-sdk/anthropic';

const summarizerAgent: SubAgent = {
  name: 'summarizer',
  description: 'Quickly summarize content',
  systemPrompt: 'Provide concise summaries',

  model: anthropic('claude-haiku-4-5-20251001'), // Faster, cheaper
};

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'), // Main model
  subagents: [summarizerAgent],
});

Subagent with Structured Output

const analystAgent: SubAgent = {
  name: 'analyst',
  description: 'Analyze data and return structured insights',
  systemPrompt: 'Analyze the data and extract key insights',

  output: {
    schema: z.object({
      summary: z.string(),
      keyFindings: z.array(z.string()),
      confidence: z.number().min(0).max(1),
      recommendations: z.array(z.string()).optional(),
    }),
    description: 'Structured analysis report',
  },
};

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  subagents: [analystAgent],
});

// Access structured output in events
for await (const event of agent.streamWithEvents({ prompt: 'Analyze data' })) {
  if (event.type === 'subagent-finish' && event.subagentName === 'analyst') {
    if (event.output) {
      console.log('Summary:', event.output.summary);
      console.log('Confidence:', event.output.confidence);
    }
  }
}

Subagent with Custom Generation Options

const creativeSubagent: SubAgent = {
  name: 'writer',
  description: 'Creative writing assistant',
  systemPrompt: 'Write engaging content',

  generationOptions: {
    temperature: 0.9,        // More creative
    maxOutputTokens: 2048,
    topP: 0.95,
  },
};

Subagent with HITL

const destructiveAgent: SubAgent = {
  name: 'file-operations',
  description: 'Handle file operations with approval',
  systemPrompt: 'Perform file operations carefully',

  interruptOn: {
    write_file: true,     // Always approve
    edit_file: true,      // Always approve
  },
};

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  subagents: [destructiveAgent],
  interruptOn: {
    // Main agent requires approval for subagent calls
    task: true,
  },
});

Multiple Specialized Subagents

const agents: SubAgent[] = [
  {
    name: 'researcher',
    description: 'Conduct research and gather information',
    systemPrompt: 'You are a research specialist...',
    model: anthropic('claude-haiku-4-5-20251001'),
  },
  {
    name: 'writer',
    description: 'Write and edit content',
    systemPrompt: 'You are a professional writer...',
    generationOptions: { temperature: 0.8 },
  },
  {
    name: 'analyst',
    description: 'Analyze data and provide insights',
    systemPrompt: 'You are a data analyst...',
    output: {
      schema: z.object({
        insights: z.array(z.string()),
        summary: z.string(),
      }),
    },
  },
];

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  subagents: agents,
});

Disable General-Purpose Agent

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  includeGeneralPurposeAgent: false, // Remove default subagent
  subagents: [
    // Only use explicitly defined subagents
    researchAgent,
    writerAgent,
  ],
});

This document is actively maintained and updated with the latest features.

9. Provider-Specific Options

Anthropic-Specific Options

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  advancedOptions: {
    providerOptions: {
      anthropic: {
        headers: {
          'anthropic-beta': 'max-tokens-3-5-2024-01-01',
          'anthropic-dangerous-direct-browser-access': 'true',
        },
      },
    },
  },
});

OpenAI-Specific Options

import { openai } from '@ai-sdk/openai';

const agent = createDeepAgent({
  model: openai('gpt-4o'),
  advancedOptions: {
    providerOptions: {
      openai: {
        user: 'user-123', // User identification
        includeUsage: true,
      },
    },
  },
});

Azure OpenAI

import { azure } from '@ai-sdk/azure';

const agent = createDeepAgent({
  model: azure('gpt-4', {
    apiKey: process.env.AZURE_API_KEY,
    resourceName: 'my-resource',
    apiVersion: '2024-02-01',
  }),
  advancedOptions: {
    providerOptions: {
      azure: {
        headers: {
          'x-ms-user-agent': 'my-app/1.0',
        },
      },
    },
  },
});

Prompt Caching (Anthropic)

Enable caching for repeated prompts:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  enablePromptCaching: true, // Cache system prompt

  // Or manually via provider options
  advancedOptions: {
    providerOptions: {
      anthropic: {
        cacheControl: {
          type: 'ephemeral',
        },
      },
    },
  },
});

Custom Endpoint

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514', {
    baseURL: 'https://custom-endpoint.com',
    apiKey: process.env.CUSTOM_API_KEY,
  }),
});

This document is actively maintained and updated with the latest features.

10. Agent Memory and Skills

Agent Memory

Enable persistent memory across conversations:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  agentId: 'my-coding-assistant',
  // Memory loaded from:
  // - ~/.deepagents/my-coding-assistant/agent.md (user-level)
  // - .deepagents/agent.md (project-level, if in git repo)

  // Agent can update memory using filesystem tools
});

// Agent remembers:
// - User preferences
// - Project conventions
// - Past decisions
// - Working patterns

Skills System

Load reusable skills from standardized directories:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  agentId: 'my-agent',
  // Skills loaded from:
  // - ~/.deepagents/my-agent/skills/*/SKILL.md (user)
  // - .deepagents/skills/*/SKILL.md (project)
});

Skill Format:

---

<Callout type="info">This document is actively maintained and updated with the latest features.</Callout>
name: code-review
description: Best practices for reviewing code
---

<Callout type="info">This document is actively maintained and updated with the latest features.</Callout>

# Code Review Skill

When reviewing code:
1. Check for security vulnerabilities
2. Verify error handling
3. Assess test coverage
4. Check performance implications
5. Verify style guide compliance

Agent automatically:

  • Discovers skills on startup
  • Injects skill descriptions into system prompt
  • Reads full skill instructions when needed
  • Follows skill workflows

Custom Memory with Middleware

import { createAgentMemoryMiddleware } from 'ai-sdk-deep-agent/middleware';

const memoryMiddleware = createAgentMemoryMiddleware({
  agentId: 'my-agent',
  workingDirectory: process.cwd(),
  userDeepagentsDir: '/custom/path/.deepagents',
  requestProjectApproval: async (projectPath) => {
    console.log(`Create .deepagents/ in ${projectPath}?`);
    // Ask user for approval
    return true; // User approved
  },
});

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  middleware: [memoryMiddleware],
});

This document is actively maintained and updated with the latest features.

11. Structured Output

Basic Structured Output

Enforce typed responses:

import { z } from 'zod';

const schema = z.object({
  title: z.string().describe('Document title'),
  summary: z.string().describe('Brief summary'),
  tags: z.array(z.string()).describe('Relevant tags'),
  confidence: z.number().min(0).max(1).describe('Confidence score'),
});

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  output: {
    schema,
    description: 'Structured document analysis',
  },
});

const result = await agent.generate({
  prompt: 'Analyze this document',
});

// result.output is typed: { title: string, summary: string, tags: string[], confidence: number }
console.log(result.output.title);

Complex Nested Schemas

const schema = z.object({
  overview: z.string(),
  sections: z.array(z.object({
    heading: z.string(),
    content: z.string(),
    subsections: z.array(z.object({
      title: z.string(),
      points: z.array(z.string()),
    })).optional(),
  })),
  metadata: z.object({
    wordCount: z.number(),
    readingTime: z.number(),
    difficulty: z.enum(['beginner', 'intermediate', 'advanced']),
  }),
});

Subagent Structured Output

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  subagents: [
    {
      name: 'researcher',
      description: 'Research and return structured findings',
      systemPrompt: 'Conduct thorough research',

      output: {
        schema: z.object({
          topic: z.string(),
          findings: z.array(z.string()),
          sources: z.array(z.string()),
          confidence: z.number(),
        }),
      },
    },
  ],
});

// Access subagent output
for await (const event of agent.streamWithEvents({ prompt: 'Research AI' })) {
  if (event.type === 'subagent-finish' && event.output) {
    console.log('Findings:', event.output.findings);
    console.log('Sources:', event.output.sources);
  }
}

Streaming with Structured Output

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  output: {
    schema: z.object({
      answer: z.string(),
      confidence: z.number(),
    }),
  },
});

for await (const event of agent.streamWithEvents({
  prompt: 'What is 2+2?',
})) {
  if (event.type === 'done' && event.output) {
    console.log('Answer:', event.output.answer);
    console.log('Confidence:', event.output.confidence);
  }
}

This document is actively maintained and updated with the latest features.

12. Human-in-the-Loop

Always Require Approval

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  interruptOn: {
    execute: true,        // Shell commands
    write_file: true,     // File writes
    edit_file: true,      // File edits
  },
});

for await (const event of agent.streamWithEvents({
  prompt: 'Create a config file',
  onApprovalRequest: async (request) => {
    console.log(`Approve ${request.toolName}?`, request.args);

    // Get user input (in CLI, readline; in web, API endpoint)
    const approved = await getUserApproval(request);

    return approved;
  },
})) {
  // Handle events
}

Conditional Approval

Auto-approve safe operations:

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  interruptOn: {
    write_file: {
      shouldApprove: (args) => {
        const filePath = (args as any).file_path;
        // Auto-approve writes to /tmp, require approval for others
        return !filePath?.startsWith('/tmp/');
      },
    },
    edit_file: {
      shouldApprove: (args) => {
        const filePath = (args as any).file_path;
        // Auto-approve edits to test files
        return !filePath?.includes('.test.');
      },
    },
    execute: true, // Always approve shell commands
  },
});

Subagent Approval

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  interruptOn: {
    task: true, // Require approval before spawning subagents
  },
});

for await (const event of agent.streamWithEvents({
  prompt: 'Delegate work',
  onApprovalRequest: async (request) => {
    if (request.toolName === 'task') {
      console.log(`Approve subagent?`, request.args);
      return await confirm('Allow subagent?');
    }
    return true;
  },
})) {
  // Handle events
}

Approval in Subagents

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  subagents: [
    {
      name: 'file-operator',
      description: 'Handles file operations',
      systemPrompt: 'Perform file operations with approval',

      interruptOn: {
        write_file: true,
        edit_file: true,
      },
    },
  ],
  interruptOn: {
    // Main agent must approve spawning file-operator
    task: {
      shouldApprove: (args) => {
        const subagentType = (args as any).subagent_type;
        return subagentType === 'file-operator';
      },
    },
  },
});

Web-Based Approval

import express from 'express';

const pendingApprovals = new Map<string, {
  resolve: (approved: boolean) => void;
  request: any;
}>();

const app = express();

app.post('/approve/:id', (req, res) => {
  const { id } = req.params;
  const { approved } = req.body;

  const approval = pendingApprovals.get(id);
  if (approval) {
    approval.resolve(approved);
    pendingApprovals.delete(id);
    res.json({ success: true });
  } else {
    res.status(404).json({ error: 'Not found' });
  }
});

const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),
  interruptOn: {
    execute: true,
    write_file: true,
  },
});

for await (const event of agent.streamWithEvents({
  prompt: 'Run build',
  onApprovalRequest: async (request) => {
    const id = crypto.randomUUID();

    return new Promise((resolve) => {
      pendingApprovals.set(id, { resolve, request });

      // Send notification to frontend
      notifyFrontend({
        type: 'approval-request',
        id,
        toolName: request.toolName,
        args: request.args,
      });

      // Timeout after 30 seconds
      setTimeout(() => {
        if (pendingApprovals.has(id)) {
          pendingApprovals.delete(id);
          resolve(false); // Deny on timeout
        }
      }, 30000);
    });
  },
})) {
  // Handle events
}

This document is actively maintained and updated with the latest features.

Advanced Combinations

Production-Ready Configuration

import {
  createDeepAgent,
  PersistentBackend,
  LocalSandbox,
  CompositeBackend,
  FilesystemBackend,
  StateBackend,
  createAgentMemoryMiddleware,
} from 'ai-sdk-deep-agent';
import { anthropic } from '@ai-sdk/anthropic';
import { Redis } from 'ioredis';

// Redis store for persistence
const redis = new Redis();
const redisStore = {
  async get(key: string) {
    const value = await redis.get(key);
    return value ? JSON.parse(value) : null;
  },
  async set(key: string, value: any) {
    await redis.set(key, JSON.stringify(value));
  },
};

// Composite backend for different storage needs
const backend = new CompositeBackend(
  new StateBackend({ todos: [], files: {} }),
  {
    '/workspace/': new LocalSandbox({ rootDir: './workspace' }),
    '/persistent/': new PersistentBackend({
      store: redisStore,
      namespace: 'app',
    }),
    '/cache/': new FilesystemBackend({ rootDir: './cache' }),
  }
);

// Agent with all features
const agent = createDeepAgent({
  model: anthropic('claude-sonnet-4-20250514'),

  // Memory and skills
  agentId: 'production-assistant',
  middleware: [
    createAgentMemoryMiddleware({
      agentId: 'production-assistant',
    }),
  ],

  // Backend
  backend,

  // Performance optimizations
  enablePromptCaching: true,
  toolResultEvictionLimit: 20000,
  summarization: {
    enabled: true,
    tokenThreshold: 150000,
    keepMessages: 8,
    model: anthropic('claude-haiku-4-5-20251001'),
  },

  // Safety
  interruptOn: {
    execute: true,
    write_file: {
      shouldApprove: (args) => {
        const path = (args as any).file_path;
        return !path?.startsWith('/tmp/');
      },
    },
  },

  // Loop control
  loopControl: {
    stopWhen: [
      stepCountIs(100),
      ({ state }) => state.todos.every(t => t.status === 'completed'),
    ],
    onStepFinish: async ({ toolCalls, usage }) => {
      // Track usage in analytics
      await analytics.track('agent_step', {
        tools: toolCalls.map(tc => tc.toolName),
        tokens: usage?.totalTokens,
      });
    },
  },

  // Generation settings
  generationOptions: {
    temperature: 0.7,
    maxOutputTokens: 4096,
  },

  // Structured output for consistency
  output: {
    schema: z.object({
      summary: z.string(),
      actions: z.array(z.string()),
      status: z.enum(['success', 'partial', 'failed']),
    }),
  },
});

This document is actively maintained and updated with the latest features.

See Also

On this page

Table of Contents1. System PromptsBasic Custom PromptCustom Prompt ComponentsReplacing Default Prompts2. Tool ConfigurationDefault ToolsDisabling Web ToolsDisabling Subagent ToolCustom Tool Selection3. Adding Custom ToolsBasic Custom ToolTool with Event EmissionTool with Backend AccessAdvanced Tool Example: Database Query4. Backend Selection and ConfigurationStateBackend (Default)FilesystemBackendPersistentBackendCompositeBackendLocalSandboxCustom Backend5. Middleware UsageLogging MiddlewareAgent Memory MiddlewareCustom Telemetry MiddlewareRate Limiting MiddlewareMultiple Middleware6. Loop Control CustomizationCustom Stop ConditionsPrepare Step HookOn Step Finish HookOn Finish Hook7. Generation OptionsTemperature and SamplingResponse Length ControlPenaltiesDeterministic OutputsStop SequencesRetry Configuration8. Subagent CustomizationBasic SubagentSubagent with Custom ToolsSubagent with Different ModelSubagent with Structured OutputSubagent with Custom Generation OptionsSubagent with HITLMultiple Specialized SubagentsDisable General-Purpose Agent9. Provider-Specific OptionsAnthropic-Specific OptionsOpenAI-Specific OptionsAzure OpenAIPrompt Caching (Anthropic)Custom Endpoint10. Agent Memory and SkillsAgent MemorySkills SystemCustom Memory with Middleware11. Structured OutputBasic Structured OutputComplex Nested SchemasSubagent Structured OutputStreaming with Structured Output12. Human-in-the-LoopAlways Require ApprovalConditional ApprovalSubagent ApprovalApproval in SubagentsWeb-Based ApprovalAdvanced CombinationsProduction-Ready ConfigurationSee Also