Agents¶
Almost everything you do with pydantic-deep starts at one function:
create_deep_agent. It returns a fully
wired agent — planning, filesystem, web, memory, sub-agents — and then gets out
of your way. This page is the tour of what you can turn on, pass in, and read
back out.
Basic usage¶
The smallest possible agent takes no arguments at all:
from pydantic_deep import create_deep_agent, DeepAgentDeps, StateBackend
# Create an agent with all the sensible defaults.
agent = create_deep_agent()
# Dependencies decide *where* state lives (here: in memory).
deps = DeepAgentDeps(backend=StateBackend())
result = await agent.run("Hello!", deps=deps)
Two objects, one split
Keep the mental model simple: the agent is what to do (model,
instructions, which tools), and DeepAgentDeps is the world it acts in
(the backend, todos, uploads). The same agent can run against many different
deps.
Configuration Options¶
Model Selection¶
# Anthropic (default)
agent = create_deep_agent(model="anthropic:claude-sonnet-4-6")
# OpenAI
agent = create_deep_agent(model="openai:gpt-4.1")
# For testing (no API calls)
from pydantic_ai.models.test import TestModel
agent = create_deep_agent(model=TestModel())
Custom Instructions¶
The instructions parameter sets the agent's system prompt. When provided, it replaces the built-in BASE_PROMPT entirely:
agent = create_deep_agent(
instructions="""
You are a Python expert specializing in data science.
When writing code:
- Use type hints
- Include docstrings
- Prefer pandas for data manipulation
"""
)
To build on top of the default behavior instead of replacing it, import BASE_PROMPT and compose with an f-string:
from pydantic_deep import BASE_PROMPT, create_deep_agent
agent = create_deep_agent(
instructions=f"""{BASE_PROMPT}
## Extra Guidelines
You are a Python expert. Always use type hints and docstrings.
"""
)
Subagent instructions work differently
The instructions field in SubAgentConfig is always appended to BASE_PROMPT automatically — you only write the specialized part. This keeps subagent configs concise.
Enabling/Disabling Features¶
agent = create_deep_agent(
# Core features (all default: True)
include_todo=True, # Planning tools
include_filesystem=True, # File operations
include_subagents=True, # Task delegation
include_skills=True, # Skill packages
include_plan=True, # Plan mode subagent
include_builtin_subagents=True, # Built-in subagents (research)
# Optional features (disabled by default)
include_checkpoints=False, # Conversation checkpointing & rewind
include_teams=False, # Agent teams with shared todos
# Enabled by default
include_memory=True, # Persistent agent memory (MEMORY.md)
web_search=True, # WebSearch capability
web_fetch=True, # WebFetch capability
thinking="high", # Thinking/reasoning effort
patch_tool_calls=True, # Fix orphaned tool calls on resume
eviction_token_limit=20_000, # Save large tool outputs to files
cost_tracking=True, # Token/USD cost tracking
context_manager=True, # Token tracking + auto-compression
)
Output Styles¶
Control agent tone and response format:
# Built-in styles: concise, explanatory, formal, conversational
agent = create_deep_agent(output_style="concise")
# Custom style
from pydantic_deep.styles import OutputStyle
agent = create_deep_agent(
output_style=OutputStyle(
name="technical",
description="Deep technical detail",
content="Always include implementation details...",
),
)
# Load from directory
agent = create_deep_agent(output_style="my-style", styles_dir="/path/to/styles")
See Output Styles for more details.
Context Files¶
Inject project context into the system prompt:
# Explicit paths
agent = create_deep_agent(
context_files=["/project/AGENTS.md", "/project/SOUL.md"],
)
# Auto-discover AGENTS.md, SOUL.md
agent = create_deep_agent(context_discovery=True)
See Context Files for more details.
Persistent Memory¶
Give agents memory that persists across sessions:
See Memory for more details.
Checkpointing¶
Save conversation state and rewind:
from pydantic_deep import InMemoryCheckpointStore
agent = create_deep_agent(
include_checkpoints=True,
checkpoint_frequency="every_tool", # every_tool | every_turn | manual_only
max_checkpoints=20,
checkpoint_store=InMemoryCheckpointStore(),
)
See Checkpointing for more details.
Agent Teams¶
Enable multi-agent collaboration:
See Teams for more details.
Hooks¶
Claude Code-style lifecycle hooks:
from pydantic_deep import Hook, HookEvent
agent = create_deep_agent(
hooks=[
Hook(
event=HookEvent.PRE_TOOL_USE,
command="python scripts/security_check.py",
matcher="execute|write_file",
),
],
)
See Hooks for more details.
Cost Tracking & Budgets¶
agent = create_deep_agent(
cost_tracking=True, # Default: True
cost_budget_usd=5.00, # Optional budget limit
on_cost_update=lambda info: print(f"${info.cumulative_cost_usd:.4f}"),
)
See Cost Tracking for more details.
Capabilities & Middleware¶
Register additional pydantic-ai capabilities via the capabilities parameter, or
legacy middleware via middleware:
from pydantic_ai.capabilities import AbstractCapability
agent = create_deep_agent(
capabilities=[MyCapability()],
)
To gate sensitive tools behind approval, use interrupt_on (see
Human-in-the-Loop). See Capabilities
for more details.
Eviction¶
Automatically save large tool outputs to files (handled by
EvictionCapability):
See Eviction for more details.
Context Manager¶
Automatic token tracking and compression (enabled by default):
agent = create_deep_agent(
context_manager=True, # Default
context_manager_max_tokens=200_000, # Token budget
on_context_update=lambda pct, cur, mx: print(f"{pct:.0%} used"),
)
See History Processors for more details.
Human-in-the-Loop¶
Require approval for sensitive operations:
agent = create_deep_agent(
interrupt_on={
"execute": True, # Require approval for command execution
"write_file": True, # Require approval for file writes
"edit_file": True, # Require approval for file edits
}
)
Structured Output¶
Get type-safe responses with Pydantic models:
from pydantic import BaseModel
class TaskAnalysis(BaseModel):
summary: str
priority: str
estimated_hours: float
agent = create_deep_agent(output_type=TaskAnalysis)
result = await agent.run("Analyze this task: implement auth", deps=deps)
print(result.output.priority) # Type-safe access
See Structured Output for more details.
Context Management¶
Automatically summarize long conversations:
from pydantic_deep.processors import create_summarization_processor
processor = create_summarization_processor(
trigger=("tokens", 100000),
keep=("messages", 20),
)
agent = create_deep_agent(history_processors=[processor])
See History Processors for more details.
Advanced Agent Configuration¶
The create_deep_agent() function accepts **agent_kwargs which are passed directly to the underlying Pydantic AI Agent. This allows you to configure advanced options:
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
# Advanced pydantic-ai options via **agent_kwargs
retries=3, # Number of retries on failure
result_retries=2, # Retries for result validation
end_strategy="early", # Stop strategy: "early" or "exhaustive"
defer_model_check=True, # Defer model validation
name="my-agent", # Agent name for logging
)
Common **agent_kwargs options:
| Parameter | Type | Description |
|---|---|---|
retries |
int |
Number of retries on LLM errors (default: 1) |
result_retries |
int |
Retries for result validation failures |
end_strategy |
str |
"early" stops at first valid result, "exhaustive" tries all |
defer_model_check |
bool |
Defer model availability check until first use |
name |
str |
Agent name for logging and debugging |
See Pydantic AI documentation for all available options.
Dynamic System Prompts¶
Pydantic Deep Agents uses a dynamic system prompt mechanism that automatically composes context from multiple sources. The system prompt is generated at runtime based on current state and enabled features.
Prompt composition order:
- Uploaded Files Summary - Files uploaded via
deps.upload_file()are listed first - Todo Prompt - Current task list and progress from the todo toolset
- Console Prompt - File operation instructions from the filesystem toolset
- Subagent Prompt - Available subagents and delegation instructions
- Skills Prompt - Available skills that can be loaded
# The agent automatically includes relevant prompts based on enabled features
agent = create_deep_agent(
instructions="You are a Python expert.", # Replaces BASE_PROMPT
include_todo=True, # Adds todo prompt
include_filesystem=True, # Adds console prompt
include_subagents=True, # Adds subagent prompt
include_skills=True, # Adds skills prompt
)
# At runtime, the agent sees:
# 1. Static instructions: "You are a Python expert." (or BASE_PROMPT if instructions=None)
# 2. Uploaded files: "## Uploaded Files\n- /uploads/data.csv (1024 bytes, 50 lines)"
# 3. Todo prompt: "## Task Management\nUse write_todos to..." (static — pass
# include_current_todos=True to also inject the live list, at the cost of the
# provider's prompt cache)
# 4. Console prompt: "## File Operations\nYou can use ls, read_file, write_file..."
# 5. Subagent prompt: "## Available Subagents\n- code-reviewer: Reviews code..."
# 6. Skills prompt: "## Available Skills\n- git: Git operations..."
Several prompt generators can be used standalone:
from pydantic_deep import get_console_system_prompt
from pydantic_ai_todo import get_todo_system_prompt
from subagents_pydantic_ai import get_subagent_system_prompt
# Generate individual prompts
console_prompt = get_console_system_prompt()
todo_prompt = get_todo_system_prompt(deps)
subagent_prompt = get_subagent_system_prompt(subagents)
The skills prompt is produced by the SkillsToolset itself via its
get_instructions() method, which pydantic-ai calls automatically.
Multi-User Considerations¶
All stateful features (memory, checkpoints, plans, evicted files) write to ctx.deps.backend. In multi-user web apps, create a separate backend and checkpoint store per user to prevent state sharing. See the Multi-User Guide for isolation patterns.
Dependencies¶
The DeepAgentDeps class holds all runtime state:
from dataclasses import dataclass
from pydantic_deep import BackendProtocol, Todo, UploadedFile
@dataclass
class DeepAgentDeps:
backend: BackendProtocol # File storage
files: dict[str, FileData] # File cache
todos: list[Todo] # Task list
subagents: dict[str, Any] # Preconfigured agents
uploads: dict[str, UploadedFile] # Uploaded files metadata
Creating Dependencies¶
# Simple - in-memory storage
deps = DeepAgentDeps(backend=StateBackend())
# With filesystem storage
from pydantic_ai_backends import LocalBackend
deps = DeepAgentDeps(backend=LocalBackend("/workspace"))
# With initial todos
from pydantic_deep import Todo
deps = DeepAgentDeps(
backend=StateBackend(),
todos=[
Todo(content="Review code", status="pending", active_form="Reviewing code"),
]
)
Uploading Files¶
Upload files for agent processing:
# Upload a file
deps.upload_file("data.csv", csv_bytes)
# File stored at /uploads/data.csv
# Custom upload directory
deps.upload_file("config.json", config_bytes, upload_dir="/configs")
# File stored at /configs/config.json
# Check uploads
for path, info in deps.uploads.items():
print(f"{path}: {info['size']} bytes, {info['line_count']} lines")
Or use the run_with_files helper:
from pydantic_deep import run_with_files
result = await run_with_files(
agent,
"Analyze this data",
deps,
files=[("data.csv", csv_bytes)],
)
See File Uploads for more details.
Running Agents¶
Basic Run¶
result = await agent.run("Create a calculator module", deps=deps)
print(result.output) # Agent's text response
Streaming¶
from pydantic_ai._agent_graph import CallToolsNode
async with agent.iter("Create a calculator", deps=deps) as run:
async for node in run:
if isinstance(node, CallToolsNode):
# Get tool calls from the response
for part in node.model_response.parts:
if hasattr(part, 'tool_name'):
print(f"Calling: {part.tool_name}")
result = run.result
Continuing Conversations¶
# First interaction
result1 = await agent.run("Create a file", deps=deps)
# Continue with history
result2 = await agent.run(
"Now modify it",
deps=deps,
message_history=result1.all_messages(),
)
Adding Custom Tools¶
Function Tools¶
from pydantic_ai import RunContext
async def get_weather(
ctx: RunContext[DeepAgentDeps],
city: str,
) -> str:
"""Get current weather for a city.
Args:
city: Name of the city.
Returns:
Weather description.
"""
return f"Weather in {city}: Sunny, 22°C"
agent = create_deep_agent(tools=[get_weather])
Accessing Dependencies in Tools¶
async def save_report(
ctx: RunContext[DeepAgentDeps],
content: str,
) -> str:
"""Save a report to the filesystem."""
# Access the backend through dependencies
result = ctx.deps.backend.write("/reports/latest.md", content)
return f"Saved to {result.path}"
Subagent Configuration¶
Pre-configure specialized subagents:
from pydantic_deep import SubAgentConfig
subagents = [
SubAgentConfig(
name="code-reviewer",
description="Reviews code for quality and security issues",
instructions="""
You are an expert code reviewer. Focus on:
- Security vulnerabilities
- Performance issues
- Code style
""",
),
SubAgentConfig(
name="test-writer",
description="Generates pytest test cases",
instructions="Generate comprehensive pytest tests...",
),
]
agent = create_deep_agent(subagents=subagents)
The main agent can then delegate:
# Agent can call: task(description="Review the calculator module", subagent_type="code-reviewer")
Skills Configuration¶
Load skills from directories:
agent = create_deep_agent(
skill_directories=[
{"path": "~/.pydantic-deep/skills", "recursive": True},
{"path": "./project-skills", "recursive": False},
]
)
Or provide skills directly via a SkillsToolset:
from pydantic_deep.features.skills import Skill, SkillsToolset
skill = Skill(name="code-review", description="Review code for quality", content="...")
agent = create_deep_agent(
toolsets=[SkillsToolset(skills=[skill])],
include_skills=False, # avoid duplicate skills toolset
)
Usage Statistics¶
result = await agent.run("Create a module", deps=deps)
usage = result.usage()
print(f"Input tokens: {usage.input_tokens}")
print(f"Output tokens: {usage.output_tokens}")
print(f"Total requests: {usage.requests}")
Error Handling¶
Basic Error Handling¶
try:
result = await agent.run(prompt, deps=deps)
except Exception as e:
print(f"Agent error: {e}")
Common Exceptions¶
| Exception | Source | Cause |
|---|---|---|
ModelRetry |
pydantic-ai | Model requested retry (validation failed) |
UnexpectedModelBehavior |
pydantic-ai | Model produced unexpected output |
UserError |
pydantic-ai | Invalid user input or configuration |
FileNotFoundError |
Backend | File doesn't exist |
PermissionError |
Backend | Access denied |
TimeoutError |
Execution | Command exceeded timeout |
docker.errors.DockerException |
DockerSandbox | Docker operation failed |
Handling Tool Errors¶
Tools should return informative error strings rather than raising exceptions:
async def my_tool(ctx: RunContext[DeepAgentDeps], path: str) -> str:
try:
content = ctx.deps.backend.read(path)
return content
except FileNotFoundError:
return f"Error: File '{path}' not found"
except PermissionError:
return f"Error: Permission denied for '{path}'"
Retry Configuration¶
Configure retries for transient failures:
agent = create_deep_agent(
retries=3, # Retry LLM calls up to 3 times
result_retries=2, # Retry validation failures
)
Graceful Degradation¶
async def run_with_fallback(agent, prompt, deps):
try:
return await agent.run(prompt, deps=deps)
except Exception as e:
# Log error, notify user, or try simpler approach
logger.error(f"Agent failed: {e}")
return f"I encountered an error: {e}. Please try again."
Recap¶
create_deep_agent()is the one entry point — call it with no arguments for a capable default, or layer on features with keyword flags.- The agent (what to do) and
DeepAgentDeps(the world to do it in) are separate; the same agent runs against many deps. - Your own
asyncfunctions become typed tools, withctx.depsinjected. - Long conversations and large outputs are managed for you (context manager + eviction), so you can focus on the task, not the token budget.