Agent Spec (declarative)¶
So far you've built agents in Python, passing keyword arguments to create_deep_agent(). Sometimes you'd rather keep that configuration in a file — to version it in git, share it across a team, or let a non-developer tweak the model without touching code.
That's what an agent spec is: a YAML or JSON file that describes an agent declaratively. You load it with one call and get back the same agent you'd have built by hand.
The smallest spec that works¶
Create a file called agent.yaml:
Load it in Python:
import asyncio
from pydantic_deep import DeepAgent
async def main():
agent, deps = DeepAgent.from_file("agent.yaml")
result = await agent.run("Create a Python script that prints the time.", deps=deps)
print(result.output)
asyncio.run(main())
Run it¶
You get the exact same agent as create_deep_agent(model="anthropic:claude-sonnet-4-6", instructions="…") — files, shell, planning, web search, sub-agents, memory, all the defaults. The only difference is where the configuration lives.
Defaults come for free
Your spec only listed two keys, but the agent has every default capability
enabled. The spec is layered on top of the same defaults as
create_deep_agent() — you only
write down what you want to change.
Step by step¶
Step 1: write the spec¶
Every key here maps 1:1 to a create_deep_agent() parameter. model: is model=, instructions: is instructions=. If you know one, you know the other.
Step 2: load it¶
DeepAgent.from_file() reads the file, validates it, and hands you back a (agent, deps) tuple — already wired and ready to run. The file extension picks the parser: .yaml/.yml for YAML, .json for JSON.
Step 3: run it¶
From here it's an ordinary agent. deps comes pre-built with an in-memory StateBackend, so the spec runs with zero extra setup — see Files & the shell to point it at real disk instead.
Turning on more features¶
Adding a capability is just adding a key. Here's a richer spec:
model: anthropic:claude-sonnet-4-6
instructions: You are a helpful coding assistant.
include_memory: true
memory_dir: .pydantic-deep
include_checkpoints: true
retries: 3
model_settings:
temperature: 0.7
Each key is the same flag you'd pass in Python. The full set mirrors create_deep_agent(), including include_todo, include_filesystem, include_subagents, include_skills, include_plan, web_search, web_fetch, thinking, context_manager, cost_tracking, and more.
Sub-agents in YAML too
You can declare sub-agents inline — each one is just a small mapping:
Serializable vs. runtime params¶
A spec file can only hold things a file can hold: strings, numbers, booleans, lists, and mappings. Live Python objects and callbacks can't be written to YAML — so you pass those as keyword overrides when you load.
from pydantic_ai_backends import LocalBackend
agent, deps = DeepAgent.from_file(
"agent.yaml",
backend=LocalBackend(root_dir="/workspace"),
on_cost_update=my_cost_callback,
)
Overrides take precedence over the file, and they're the only way to supply non-serializable params:
| In the spec file (serializable) | As a keyword override (runtime) |
|---|---|
model, instructions, retries |
backend |
include_* feature flags |
tools, toolsets |
model_settings, thinking |
hooks, middleware, history_processors |
subagents, skill_directories |
output_type, checkpoint_store |
memory_dir, context_files |
on_cost_update, on_context_update, on_eviction, on_before_compress, on_after_compress |
Backend defaults to in-memory
If you don't pass backend=, the loaded deps uses a StateBackend so the
agent runs out of the box. Pass a LocalBackend or DockerSandbox override
the moment you want files to land somewhere real.
Loading from a dict¶
If your config already lives in Python — say it came from a database or an HTTP request — skip the file and use from_spec() directly:
agent, deps = DeepAgent.from_spec(
{
"model": "anthropic:claude-sonnet-4-6",
"include_memory": True,
"memory_dir": ".pydantic-deep",
},
backend=LocalBackend(root_dir="/workspace"),
)
from_file() is just from_spec() with a file read in front of it — same rules, same overrides.
Saving a spec¶
Going the other way, DeepAgent.to_file() writes a config out. Only non-default values are saved, so the file stays small and readable — it records your changes, not the whole surface area.
DeepAgent.to_file(
"agent.yaml",
model="anthropic:claude-sonnet-4-6",
include_memory=True,
memory_dir=".pydantic-deep",
)
Non-serializable params handed to to_file() are silently dropped — they can't round-trip through a file, so they don't belong in one.
JSON works too¶
Everything above works identically in JSON; the extension decides the format.
{
"model": "anthropic:claude-sonnet-4-6",
"include_memory": true,
"memory_dir": ".pydantic-deep",
"model_settings": {
"temperature": 0.7
}
}
YAML needs PyYAML
YAML support is an optional extra. If you see an ImportError, install it
with pip install 'pydantic-deep[yaml]'. JSON has no extra dependency.
Recap¶
You can now define agents without writing Python:
- An agent spec is a YAML or JSON file whose keys map 1:1 to
create_deep_agent()parameters. DeepAgent.from_file()(orfrom_spec()for a dict) returns a ready(agent, deps)tuple, layered on the same defaults as the factory.- Serializable params (models, flags,
model_settings,subagents) live in the file; runtime params (backend,tools, callbacks,output_type) are passed as keyword overrides — and overrides win. DeepAgent.to_file()writes a minimal spec containing only your non-default values.
Next, run the same agent for many users at once.