Architecture
Deep Agent architecture and components
This document provides detailed architectural information about ai-sdk-deep-agent.
Version: 0.9.2
Core Components
DeepAgent
Main agent class wrapping AI SDK v6's ToolLoopAgent with state management
State Management
Todo tracking and virtual filesystem
Backends
Pluggable storage and sandbox backends
Tools
Planning, filesystem, web, execution, and subagents
CLI
Interactive terminal interface built with Ink
Skills
Agent-specific capabilities from standardized directories
1. DeepAgent (src/agent.ts)
Main agent class that wraps AI SDK v6's ToolLoopAgent with state management.
Key features:
- Requires a
LanguageModelinstance (from AI SDK providers likeanthropic(),openai(), etc.) - Creates tools dynamically for each invocation with shared state
- Supports three generation modes:
generate(),stream(),streamWithEvents() - Handles conversation history via
messagesarray for multi-turn conversations - Implements prompt caching (Anthropic), tool result eviction, and auto-summarization
- Supports agent-specific skills and memory via
agentIdparameter - Provides middleware support via
wrapLanguageModelfor logging, telemetry, and context injection
2. State Management (src/types/)
interface DeepAgentState {
todos: TodoItem[]; // Task planning/tracking
files: Record<string, FileData>; // Virtual filesystem
}
interface TodoItem {
id: string; // Unique identifier
content: string; // Task description (max 100 chars)
status: 'pending' | 'in_progress' | 'completed' | 'cancelled';
}
interface FileData {
content: string;
created_at?: string;
updated_at?: string;
}3. Backends (src/backends/)
All backends implement BackendProtocol interface with methods: read(), write(), edit(), ls(), lsInfo(), glob(), grep()
StateBackend: In-memory storage (default, ephemeral)FilesystemBackend: Persists files to actual diskPersistentBackend: Cross-conversation memory with key-value storeCompositeBackend: Combines multiple backends (e.g., filesystem + cloud storage)
Implement SandboxBackendProtocol with execute() method:
BaseSandbox: Abstract base class for implementing sandbox backendsLocalSandbox: Executes commands in local shell with Node.js scripts- Extensible to: Modal, Runloop, Daytona, cloud providers, etc.
Sandbox backends implement all filesystem operations via shell commands, requiring only the execute() method to be implemented.
4. Tools (src/tools/)
Planning:
write_todos- Manages task lists with merge/replace strategies- Supports merge mode (update by id) or replace mode (full replacement)
- Emits
todos-changedevents
Filesystem:
ls- List files and directoriesread_file- Read file contents (configurable line limit)write_file- Write new files (error if exists)edit_file- String-based find-and-replace editingglob- Find files with glob patterns (e.g.,**/*.py)grep- Search for regex patterns in files
Web:
web_search- Tavily-powered web search (requiresTAVILY_API_KEY)http_request- Raw HTTP requests with custom headersfetch_url- Fetch and convert HTML to Markdown (uses Readability)
Execution (sandbox backends only):
execute- Run shell commands in sandbox environment- Emits
execute-startandexecute-finishevents - Includes exit code and truncation status
- Emits
Subagents:
task- Spawns isolated subagents that share filesystem with parent- Inherits parent's user tools
- Independent todos and conversation history
- Can have own system prompts and output schemas
5. CLI (src/cli/index.tsx)
- Built with Ink (React for CLI) - interactive terminal interface
- Real-time streaming with event visualization
- Slash commands:
/help,/todos,/files,/read <path>,/clear,/model <name>,/exit - Feature toggles:
/cache,/eviction,/summarize,/approve,/features - Tool approval: Safe mode (default) requires approval for write/edit/execute operations
- Uses
parseModelString()to convert string model IDs toLanguageModelinstances (backward compatibility)
6. Skills (src/skills/)
Agent-specific skills loaded from standardized directories:
Load Locations (via agentId parameter):
- User skills:
~/.deepagents/{agentId}/skills/*/SKILL.md - Project skills:
[git-root]/.deepagents/skills/*/SKILL.md
Skill Format:
---
name: skill-name
description: What this skill does
---
# Skill Content
Detailed instructions for the agent...Skills are automatically injected into the system prompt, providing agent-specific capabilities and context.
Event System
The streamWithEvents() method emits granular events during generation:
Core Events:
text: Streamed text chunksstep-start,step-finish: Agent reasoning stepsdone: Final state with conversation messages and optional structured outputerror: Error occurred
Tool Events:
tool-call,tool-result: Generic tool invocation eventstodos-changed: Todo list modificationsfile-read,file-write-start,file-written,file-edited: Filesystem changesls,glob,grep: File operation results
Execution Events:
execute-start,execute-finish: Command execution in sandboxweb-search-start,web-search-finish: Web search operationshttp-request-start,http-request-finish: HTTP requestsfetch-url-start,fetch-url-finish: URL fetching and HTML conversion
Subagent Events:
subagent-start,subagent-finish: Subagent lifecyclesubagent-step: Subagent reasoning stepstext-segment,user-message: Subagent communication
HITL Events:
approval-requested: Tool approval requested (before execution)approval-response: Approval decision received
Checkpoint Events:
checkpoint-saved: Checkpoint saved to storage (with threadId and step number)checkpoint-loaded: Checkpoint restored from storage
Message Handling
Important
messages array for conversation history. When streaming with events, always pass event.messages back to maintain context.Conversation Flow
- The
doneevent includesevent.messages- the updated conversation history - Pass this back to the next
streamWithEvents()call to maintain context - The library automatically patches "dangling tool calls" (calls without results) via
patchToolCalls()
Message Priority Logic
- Explicit
messagesarray takes highest priority promptparameter is converted to a user message (backward compatibility)threadIdloads checkpoint history- Empty
messagesarray clears checkpoint history (resets conversation)
Example:
let messages: ModelMessage[] = [];
for await (const event of agent.streamWithEvents({ prompt: "First message", messages })) {
if (event.type === 'done') {
messages = event.messages || [];
}
}
// Next turn with context
for await (const event of agent.streamWithEvents({ prompt: "Follow up", messages })) {
// Agent remembers previous context
}prompt parameter is deprecated in favor of explicit messages array. A warning is emitted in non-production environments.Performance Features
1. Prompt Caching (Anthropic only)
- Caches system prompt for faster subsequent calls
- Enabled via
enablePromptCaching: true - Uses Anthropic's
cacheControlheader for ephemeral caching - Reduces token usage and latency for repeated prompts
2. Tool Result Eviction
- Large tool results (>20k tokens default) are evicted to virtual filesystem
- Prevents context overflow in long agent loops
- Controlled via
toolResultEvictionLimitparameter - Evicted content stored in backend with summary in conversation
3. Auto-Summarization
When conversation exceeds token threshold (170k default), older messages are summarized
- Keeps recent messages (6 default) intact for context
- Uses configurable model (default: same as main model) for summarization
summarization: {
enabled: true,
tokenThreshold: 170000, // Trigger threshold
keepMessages: 6, // Recent messages to preserve
model: anthropic('claude-haiku-4-5-20250929'), // Optional: custom summarization model
}4. Loop Control
loopControl parameterstopWhen: Custom stop conditions (can combine withmaxSteps)prepareStep: Hook to modify parameters before each steponStepFinish: Callback after each tool execution steponFinish: Callback when agent completes
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
loopControl: {
stopWhen: [
stepCountIs(50),
({ state }) => state.todos.every(t => t.status === 'completed')
],
prepareStep: async ({ tools, toolResults }) => {
// Modify step parameters dynamically
return { maxSteps: 100 };
},
onStepFinish: async ({ toolCalls, toolResults }) => {
console.log(`Step completed with ${toolCalls.length} tool calls`);
},
},
});Human-in-the-Loop (HITL)
The agent supports tool approval before execution, useful for destructive operations like file writes or command execution.
Library API:
import { anthropic } from '@ai-sdk/anthropic';
import { createDeepAgent } from 'ai-sdk-deep-agent';
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
interruptOn: {
execute: true, // Always require approval
write_file: true, // Always require approval
edit_file: { // Dynamic approval based on arguments
shouldApprove: (args) => !args.file_path.startsWith('/tmp/')
},
},
});
// Handle approvals via callback
for await (const event of agent.streamWithEvents({
prompt: "Create a config file",
onApprovalRequest: async (request) => {
console.log(`Approve ${request.toolName}?`, request.args);
return true; // or false to deny
},
})) {
// Handle events
}Approval Configuration:
true: Always require approval for this toolfalse: Never require approval{ shouldApprove: (args) => boolean }: Dynamic approval based on arguments
CLI Approval Modes:
The CLI operates in two modes for tool execution:
- Prompts for approval before
execute,write_file,edit_file - Status bar shows: 🔴 Safe mode
- At approval prompt:
[Y]approve,[N]deny,[A]approve all
- All tool executions proceed without prompts
- Status bar shows: 🟢 Auto-approve
- Toggle with
/approvecommand
Model Specification
Important
LanguageModel instances instead of string-based model IDs.import { anthropic } from '@ai-sdk/anthropic';
import { openai } from '@ai-sdk/openai';
import { azure } from '@ai-sdk/azure';
import { createDeepAgent } from 'ai-sdk-deep-agent';
// Anthropic (recommended)
const agent1 = createDeepAgent({
model: anthropic('claude-sonnet-4-5-20250929'),
});
// OpenAI
const agent2 = createDeepAgent({
model: openai('gpt-5'),
});
// Azure OpenAI
const agent3 = createDeepAgent({
model: azure('gpt-5-mini', {
apiKey: process.env.AZURE_OPENAI_API_KEY,
resourceName: 'my-resource',
}),
});
// Custom configuration
const agent4 = createDeepAgent({
model: anthropic('claude-sonnet-4-5-20250929', {
apiKey: process.env.CUSTOM_API_KEY,
baseURL: 'https://custom-endpoint.com',
}),
});Supported Providers: Any provider from the Vercel AI SDK ecosystem (Anthropic, OpenAI, Azure, Bedrock, Groq, Mistral, etc.)
parseModelString() from src/utils/model-parser.ts to convert string formats like "anthropic/claude-sonnet-4-5-20250929" into LanguageModel instances. This is only for the CLI - when using the library programmatically, always pass provider instances.Structured Output
Implementation
DeepAgent supports structured output via AI SDK v6's ToolLoopAgent native output parameter:
Architecture:
- Optional
output: { schema, description? }inCreateDeepAgentParams - Pass-through to ToolLoopAgent constructor via
Output.object()helper - Output exposed in
generate(),stream(), andstreamWithEvents()results - Subagents can have their own output schemas
Type Safety:
result.outputtyped based on Zod schema- TypeScript infers types automatically
- Validation by Zod at runtime
Reference: ToolLoopAgent Output Parsing
Subagent Structured Output
Subagents can return structured output to parent agents:
- SubAgent interface: Optional
outputfield with Zod schema - Registry storage: Output config stored with subagent registration
- Result formatting: Structured output appended as JSON to text response
- Parent consumption: Parent agent receives formatted text with JSON
Example Format:
Research completed successfully.
[Structured Output]
{
"summary": "AI agents use tools to interact with external systems",
"findings": ["Tool calling", "State management", "Error handling"],
"confidence": 0.9
}Agent Memory
Agent memory provides persistent, long-term memory across conversations using markdown files. See agent-memory.md for full documentation.
Quick Start:
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
agentId: 'my-coding-assistant', // Enables memory + skills
// Memory automatically loaded from:
// - ~/.deepagents/my-coding-assistant/agent.md (user)
// - .deepagents/agent.md (project)
});Two-tier architecture:
- User-level memory:
~/.deepagents/{agentId}/agent.md - Project-level memory:
[git-root]/.deepagents/agent.md
Memory is injected into system prompt and can be updated via filesystem tools.
Skills System
Agent-specific skills provide reusable capabilities loaded from standardized directories.
Using agentId for Skills:
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
agentId: 'my-coding-assistant',
// Skills automatically loaded from:
// - ~/.deepagents/my-coding-assistant/skills/*/SKILL.md (user)
// - .deepagents/skills/*/SKILL.md (project)
});Skill Format:
Each skill is a subdirectory with a SKILL.md file:
---
name: my-skill
description: What this skill does
---
# Skill Content
Detailed instructions for the agent...Skills are automatically parsed and their content injected into the system prompt.
Advanced Options
Generation Options
Pass additional options to the underlying streamText or generateText calls:
const agent = createDeepAgent({
model: anthropic('claude-sonnet-4-20250514'),
generationOptions: {
temperature: 0.7,
maxTokens: 4096,
topP: 0.9,
},
});Advanced Options
Constants and Limits
Important default values (can be customized):
DEFAULT_MAX_STEPS: 100 (main agent)DEFAULT_SUBAGENT_MAX_STEPS: 50 (subagents)DEFAULT_EVICTION_TOKEN_LIMIT: 20,000 tokensDEFAULT_SUMMARIZATION_THRESHOLD: 170,000 tokensDEFAULT_KEEP_MESSAGES: 6 recent messagesDEFAULT_READ_LIMIT: 2,000 lines per fileCONTEXT_WINDOW: 200,000 tokens (Claude)
See src/constants/limits.ts for complete list.