YAML Subagents & the Loader Pattern
In Module 5, you defined subagents as Python dictionaries. That works well for small examples, but as your agent team grows, mixing configuration with code becomes hard to maintain. Changes require editing Python files, redeploying, and carefully tracking which subagent does what.
The content-builder-agent example demonstrates a better pattern: defining subagents in YAML files and loading them dynamically. This separates configuration from code, making it easy to iterate on agent behavior, swap models, and refine prompts without touching your application logic.
This module walks you through the YAML subagent pattern—a core technique for building maintainable, scalable agent systems.
Exercise 1: The content-builder-agent Pattern
-
Examine the canonical example from the deepagents repository. Here’s what a
subagents.yamlfile looks like:
researcher:
description: >
ALWAYS use this first to research any topic before writing content.
Searches the web for current information, statistics, and sources.
model: anthropic:claude-haiku-4-5-20251001
system_prompt: |
You are a research assistant. You have access to web_search
and write_file tools.
## Your Tools
- web_search(query, max_results=5, topic="general") - Search the web
- write_file(file_path, content) - Save your findings
## Your Process
1. Use web_search to find information on the topic
2. Make 2-3 targeted searches with specific queries
3. Synthesize findings into a clear summary
4. Save your research to a file for the main agent
tools:
- web_search
Let’s break down each field:
researcher (top-level key): The subagent’s name. This is used for routing via the task tool’s subagent_type parameter.
description: The routing signal. Notice the directive language: "ALWAYS use this first…". This isn’t just documentation—it actively guides the main agent’s delegation decisions. Strong, imperative descriptions lead to better routing.
model: The model identifier. Here, the researcher uses Haiku for fast, cost-effective web searches. You could use Sonnet for more complex reasoning or Opus for maximum capability.
system_prompt: The subagent’s instructions. Uses YAML’s | syntax for multi-line strings. This prompt defines the subagent’s persona, available tools, and expected workflow.
tools: A list of tool names the subagent can use. These are strings that reference tool objects in your Python code—you’ll map them during loading.
+ This structure is clean, readable, and completely separate from your application code. You can version it, review it, and iterate on it without touching Python.
Exercise 2: Build a load_subagents() Helper
The core of the YAML subagent pattern is a loader function that reads the YAML file and converts it to the subagent dictionary format that create_deep_agent() expects.
-
Set up a clean working directory for this exercise:
mkdir -p yaml_demo cp utils.py yaml_demo/ cd yaml_demo -
Define a simulated
web_searchtool:cat > tools.py << 'EOF' from langchain_core.tools import tool @tool def web_search(query: str, max_results: int = 5) -> str: """Search the web for information on a topic.""" return f"[Simulated search results for: {query}]\n- Result 1: {query} overview\n- Result 2: {query} best practices\n- Result 3: {query} common patterns" EOF -
Create the loader function:
cat > loader.py << 'EOF' import os import yaml from pathlib import Path from tools import web_search def load_subagents(config_path: Path) -> list: """Load subagent definitions from a YAML file. Maps tool name strings in YAML to actual tool objects. This is a custom utility—deepagents doesn't natively load subagents from files. Model precedence (highest wins): 1. Environment variable: {NAME}_MODEL (e.g. RESEARCHER_MODEL) 2. YAML model: field 3. No model — inherits from the main agent """ # Registry of available tools available_tools = { "web_search": web_search, } with open(config_path) as f: config = yaml.safe_load(f) subagents = [] for name, spec in config.items(): subagent = { "name": name, "description": spec["description"], "system_prompt": spec["system_prompt"], } # Model: check env var override first, then YAML, then inherit # e.g. "researcher" checks RESEARCHER_MODEL # "fact-checker" checks FACT_CHECKER_MODEL env_key = f"{name.upper().replace('-', '_')}_MODEL" model = os.environ.get(env_key, spec.get("model")) if model: subagent["model"] = model # Optional: tools if "tools" in spec: # Map tool name strings to actual tool objects subagent["tools"] = [available_tools[t] for t in spec["tools"]] subagents.append(subagent) return subagents EOF -
Create a
subagents.yamlfile:cat > subagents.yaml << 'EOF' researcher: description: > ALWAYS use this first to research any topic before writing content. Searches the web for current information, statistics, and sources. model: anthropic:claude-haiku-4-5-20251001 system_prompt: | You are a research assistant. You have access to web_search tool. ## Your Tools - web_search(query, max_results=5) - Search the web ## Your Process 1. Use web_search to find information on the topic 2. Make 2-3 targeted searches with specific queries 3. Synthesize findings into a clear summary tools: - web_search EOF -
Create a demo script that uses the loader:
-
Run
-
Code Preview
cat > demo.py << 'EOF' import os from pathlib import Path from deepagents import create_deep_agent from loader import load_subagents from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") # Load subagents from YAML subagents = load_subagents(Path("subagents.yaml")) agent = create_deep_agent( model=MODEL, subagents=subagents, ) result = agent.invoke({"messages": [("user", "Research the key benefits of YAML for configuration management." )]}) print(agent_response(result)) EOFfrom pathlib import Path from deepagents import create_deep_agent from loader import load_subagents from utils import agent_response # Load subagents from YAML subagents = load_subagents(Path("subagents.yaml")) agent = create_deep_agent( model="anthropic:claude-sonnet-4-6", subagents=subagents, ) result = agent.invoke({"messages": [("user", "Research the key benefits of YAML for configuration management." )]}) print(agent_response(result)) -
-
Run the demo:
uv run demo.pySample output (your results may vary)I'll help you research the key benefits of YAML for configuration management. [Task delegation to researcher subagent...] Based on the research findings, here are the key benefits of YAML for configuration management: 1. Human-readable format: YAML uses indentation and minimal syntax, making it easy to read and write 2. Language-agnostic: Works across multiple programming languages and platforms 3. Supports complex data structures: Handles nested objects, arrays, and references naturally 4. Reduces configuration errors: Clear syntax and validation help catch issues early 5. Version control friendly: Plain text format works well with git and diff tools
The key insight: your Python code is generic. All the agent behavior is defined in
subagents.yaml. To change the researcher’s model, prompt, or tools, you just edit YAML—no Python changes needed.
Exercise 3: Multi-Subagent YAML
Let’s extend the pattern with a full content creation pipeline: researcher, writer, and reviewer.
-
Update
subagents.yamlto include three subagents:cat > subagents.yaml << 'EOF' researcher: description: > ALWAYS use this first to research any topic before writing content. Searches the web for current information, statistics, and sources. model: anthropic:claude-haiku-4-5-20251001 system_prompt: | You are a research assistant. You have access to web_search tool. ## Your Tools - web_search(query, max_results=5) - Search the web ## Your Process 1. Use web_search to find information on the topic 2. Make 2-3 targeted searches with specific queries 3. Synthesize findings into a clear summary tools: - web_search writer: description: > Use this to write clear, engaging content based on research. Transforms facts and findings into readable prose. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a technical writer. Your goal is to create clear, engaging content that explains complex topics accessibly. ## Your Process 1. Review the research provided 2. Organize information logically 3. Write in a conversational, friendly tone 4. Use examples and analogies when helpful 5. Keep paragraphs short and scannable reviewer: description: > Use this to review content for quality, accuracy, and completeness. Provides constructive feedback and suggests improvements. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a content reviewer. Your goal is to ensure quality, accuracy, and readability. ## Your Review Criteria - Accuracy: Are facts correct and well-supported? - Clarity: Is the writing clear and easy to follow? - Completeness: Does it cover the topic thoroughly? - Tone: Is it appropriate for the audience? Provide specific, actionable feedback. EOF -
Create a content creation demo:
-
Run
-
Code Preview
cat > content_pipeline.py << 'EOF' import os from pathlib import Path from deepagents import create_deep_agent from loader import load_subagents from utils import agent_response, delegation_trace MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") # Load all three subagents subagents = load_subagents(Path("subagents.yaml")) agent = create_deep_agent( model=MODEL, subagents=subagents, ) result = agent.invoke({"messages": [("user", "Create a 3-paragraph explanation of microservices architecture. " "First research the topic, then write the content, then review it for quality." )]}) # Show which subagents were called, their models, and in what order print(delegation_trace(result, subagents=subagents)) print(agent_response(result)) EOFfrom pathlib import Path from deepagents import create_deep_agent from loader import load_subagents from utils import agent_response # Load all three subagents subagents = load_subagents(Path("subagents.yaml")) agent = create_deep_agent( model="anthropic:claude-sonnet-4-6", subagents=subagents, ) result = agent.invoke({"messages": [("user", "Create a 3-paragraph explanation of microservices architecture. " "First research the topic, then write the content, then review it for quality." )]}) print(agent_response(result)) -
-
Run the content pipeline:
uv run content_pipeline.pySample output (your results may vary)=== Delegation Trace === Step 1: researcher (anthropic:claude-sonnet-4-6) Research microservices architecture: key concepts, benefits, trade-offs, ... Step 2: writer (anthropic:claude-sonnet-4-6) Write a 3-paragraph explanation of microservices architecture based on th... Step 3: reviewer (anthropic:claude-sonnet-4-6) Review the following content for accuracy, clarity, and completeness... Total: 3 delegation(s) Microservices architecture is a software design pattern that structures an application as a collection of loosely coupled, independently deployable services... ... This approach enables teams to work autonomously, scale components individually, and maintain system resilience even when individual services fail.The delegation trace shows the orchestration clearly:
-
Main agent delegates research to
researcher(Haiku) -
Takes research results, delegates writing to
writer(Sonnet) -
Takes draft content, delegates review to
reviewer(Sonnet) -
Integrates feedback and presents final result
Each subagent has fresh context and clear responsibilities. The main agent handles orchestration. All behavior is defined in YAML.
-
Exercise 4: Iterating via Config
Here’s where the pattern truly shines: config-driven iteration without code changes.
Change 1: Upgrade the researcher’s model
-
Edit
subagents.yamlto change the researcher’s model from Haiku to Sonnet:cat > subagents.yaml << 'EOF' researcher: description: > ALWAYS use this first to research any topic before writing content. Searches the web for current information, statistics, and sources. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a research assistant. You have access to web_search tool. ## Your Tools - web_search(query, max_results=5) - Search the web ## Your Process 1. Use web_search to find information on the topic 2. Make 2-3 targeted searches with specific queries 3. Synthesize findings into a clear summary tools: - web_search writer: description: > Use this to write clear, engaging content based on research. Transforms facts and findings into readable prose. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a technical writer. Your goal is to create clear, engaging content that explains complex topics accessibly. ## Your Process 1. Review the research provided 2. Organize information logically 3. Write in a conversational, friendly tone 4. Use examples and analogies when helpful 5. Keep paragraphs short and scannable reviewer: description: > Use this to review content for quality, accuracy, and completeness. Provides constructive feedback and suggests improvements. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a content reviewer. Your goal is to ensure quality, accuracy, and readability. ## Your Review Criteria - Accuracy: Are facts correct and well-supported? - Clarity: Is the writing clear and easy to follow? - Completeness: Does it cover the topic thoroughly? - Tone: Is it appropriate for the audience? Provide specific, actionable feedback. EOF -
Re-run without changing Python:
uv run content_pipeline.pySample output (your results may vary)[Delegating to researcher subagent with Sonnet model...] [Conducting more detailed research...] Microservices architecture represents a fundamental shift in how we build and deploy software systems... [More comprehensive and nuanced content due to Sonnet's enhanced capabilities...]
The researcher now uses Sonnet, producing deeper, more nuanced research.
Change 2: Refine the writer’s tone
-
Edit the writer’s description to be more specific:
cat > subagents.yaml << 'EOF' researcher: description: > ALWAYS use this first to research any topic before writing content. Searches the web for current information, statistics, and sources. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a research assistant. You have access to web_search tool. ## Your Tools - web_search(query, max_results=5) - Search the web ## Your Process 1. Use web_search to find information on the topic 2. Make 2-3 targeted searches with specific queries 3. Synthesize findings into a clear summary tools: - web_search writer: description: > Use this to write technical documentation in a practical, example-driven style. Focus on actionable insights and concrete use cases. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a technical writer specializing in practical, example-driven content. ## Your Process 1. Review the research provided 2. Lead with practical use cases 3. Use concrete examples and code snippets when relevant 4. Keep it actionable—readers should know what to do next 5. Avoid abstract explanations; prefer specifics reviewer: description: > Use this to review content for quality, accuracy, and completeness. Provides constructive feedback and suggests improvements. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a content reviewer. Your goal is to ensure quality, accuracy, and readability. ## Your Review Criteria - Accuracy: Are facts correct and well-supported? - Clarity: Is the writing clear and easy to follow? - Completeness: Does it cover the topic thoroughly? - Tone: Is it appropriate for the audience? Provide specific, actionable feedback. EOF -
Re-run to see the updated writer behavior:
uv run content_pipeline.pySample output (your results may vary)Microservices architecture allows you to build applications as independent services. Here's a practical example: Consider an e-commerce platform. Instead of one monolithic app, you'd create separate services: - Product catalog service (handles inventory, search) - Order processing service (manages cart, checkout) - User authentication service (login, permissions) ...
The writer now produces more practical, example-driven content.
Change 3: Stricter reviews
-
Add a constraint to the reviewer’s prompt:
cat > subagents.yaml << 'EOF' researcher: description: > ALWAYS use this first to research any topic before writing content. Searches the web for current information, statistics, and sources. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a research assistant. You have access to web_search tool. ## Your Tools - web_search(query, max_results=5) - Search the web ## Your Process 1. Use web_search to find information on the topic 2. Make 2-3 targeted searches with specific queries 3. Synthesize findings into a clear summary tools: - web_search writer: description: > Use this to write technical documentation in a practical, example-driven style. Focus on actionable insights and concrete use cases. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a technical writer specializing in practical, example-driven content. ## Your Process 1. Review the research provided 2. Lead with practical use cases 3. Use concrete examples and code snippets when relevant 4. Keep it actionable—readers should know what to do next 5. Avoid abstract explanations; prefer specifics reviewer: description: > Use this to review content for quality, accuracy, and completeness. Provides constructive feedback and suggests improvements. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a strict content reviewer. Your goal is to ensure the highest quality, accuracy, and readability. ## Your Review Criteria - Accuracy: Are facts correct and well-supported? - Clarity: Is the writing clear and easy to follow? - Completeness: Does it cover the topic thoroughly? - Tone: Is it appropriate for the audience? - Examples: Are there enough concrete examples? ## Your Standards - Flag any vague or abstract statements - Require concrete examples for all claims - Demand clarity in every sentence Provide specific, actionable feedback with high standards. EOF -
Re-run to see stricter review feedback:
uv run content_pipeline.pySample output (your results may vary)[Delegating to reviewer with stricter standards...] REVIEW FEEDBACK: - Paragraph 1: "loosely coupled" needs a concrete example - Missing specific metrics for "scale components individually" - The statement about "autonomous teams" is too abstract - provide an example - Need code snippet or configuration example for clarity ...
The reviewer now provides stricter, more detailed feedback.
All three changes were YAML-only. No Python edits. This is the power of the loader pattern: your application code is generic and stable, while your agent behavior is data-driven and easy to iterate.
Exercise 5: Runtime Model Overrides
You may have noticed that the loader function checks for environment variables before using the model from YAML. This is a powerful pattern worth understanding.
Your YAML defines sensible defaults — the researcher uses Haiku for speed, the writer uses Sonnet for quality. But what if you want to test the whole pipeline with a different provider? Or run the same config against a local model? You shouldn’t have to edit YAML for that.
The loader resolves models with this precedence:
| Priority | Source | Example |
|---|---|---|
1 (highest) |
Environment variable |
|
2 |
YAML |
|
3 (lowest) |
No model set — inherits from main agent |
Main agent’s |
The naming convention is straightforward: take the subagent name, uppercase it, replace hyphens with underscores, append _MODEL. So researcher becomes RESEARCHER_MODEL and fact-checker becomes FACT_CHECKER_MODEL.
-
Try overriding just the researcher’s model at runtime:
RESEARCHER_MODEL=anthropic:claude-sonnet-4-6 uv run demo.pyThe researcher now uses Sonnet instead of Haiku — without touching YAML.
-
Override multiple models at once:
RESEARCHER_MODEL=anthropic:claude-sonnet-4-6 WRITER_MODEL=anthropic:claude-haiku-4-5-20251001 uv run content_pipeline.pyNow the researcher uses the more capable model while the writer uses the faster one — the opposite of the YAML defaults.
This pattern is essential for real-world agent systems:
-
Development — test with cheap, fast models while iterating on prompts
-
Staging — validate with production models before deploying
-
Production — swap providers or models without redeploying code or editing config
-
Debugging — temporarily upgrade a subagent’s model to see if quality improves
The YAML captures your intended configuration. Environment variables provide the operational flexibility to override it.
Exercise 6: Structuring Larger Projects
As your agent system grows, you’ll need patterns for organizing subagent configurations.
Single YAML for small teams (up to 5-7 subagents)
The approach we’ve used so far works well for small teams. Keep all subagents in subagents.yaml:
researcher:
description: ...
system_prompt: ...
writer:
description: ...
system_prompt: ...
reviewer:
description: ...
system_prompt: ...
editor:
description: ...
system_prompt: ...
fact_checker:
description: ...
system_prompt: ...
Directory of YAML files for larger teams
For 8+ subagents, split into separate files:
-
Create a subagents directory with individual YAML files:
mkdir -p subagents cat > subagents/researcher.yaml << 'EOF' description: > ALWAYS use this first to research any topic before writing content. Searches the web for current information, statistics, and sources. model: anthropic:claude-haiku-4-5-20251001 system_prompt: | You are a research assistant... tools: - web_search EOF cat > subagents/writer.yaml << 'EOF' description: > Use this to write clear, engaging content based on research. model: anthropic:claude-sonnet-4-6 system_prompt: | You are a technical writer... EOF -
Update the loader to read from a directory:
cat > loader_v2.py << 'EOF' import os import yaml from pathlib import Path from tools import web_search def load_subagents_from_dir(dir_path: Path) -> list: """Load subagent definitions from a directory of YAML files. Same env var override as loader.py — {NAME}_MODEL overrides YAML. """ available_tools = { "web_search": web_search, } subagents = [] for yaml_file in sorted(dir_path.glob("*.yaml")): with open(yaml_file) as f: spec = yaml.safe_load(f) name = yaml_file.stem # filename without extension subagent = { "name": name, "description": spec["description"], "system_prompt": spec["system_prompt"], } # Model: env var override, then YAML, then inherit env_key = f"{name.upper().replace('-', '_')}_MODEL" model = os.environ.get(env_key, spec.get("model")) if model: subagent["model"] = model if "tools" in spec: subagent["tools"] = [available_tools[t] for t in spec["tools"]] subagents.append(subagent) return subagents EOF
Config validation with Pydantic V2
As your YAML configs grow, validation becomes critical. Use Pydantic V2 to catch misconfigurations early.
-
Create a validation module:
cat > validation.py << 'EOF' import yaml from pathlib import Path from pydantic import BaseModel, Field, RootModel, ValidationError class SubAgentConfig(BaseModel): """Schema for a single subagent in YAML.""" description: str = Field(min_length=10) system_prompt: str = Field(min_length=20) model: str | None = None tools: list[str] = Field(default_factory=list) class SubAgentsConfig(RootModel[dict[str, SubAgentConfig]]): """Schema for the entire subagents.yaml file.""" pass def validate_subagent_config(config_path: Path) -> dict: """Load and validate a subagents YAML file. Raises ValidationError if config is invalid. """ with open(config_path) as f: raw_config = yaml.safe_load(f) # Validate with Pydantic V2 validated = SubAgentsConfig(raw_config) return validated.model_dump() # Test with valid config try: config = validate_subagent_config(Path("subagents.yaml")) print("✓ Config is valid") except ValidationError as e: print("✗ Config validation failed:") print(e) EOF -
Create an invalid config to test validation:
cat > invalid_subagents.yaml << 'EOF' researcher: description: "Too short" # Less than 10 chars system_prompt: "Also short" # Less than 20 chars model: anthropic:claude-haiku-4-5-20251001 EOF -
Create a test script for validation:
-
Run
-
Code Preview
cat > test_validation.py << 'EOF' from pathlib import Path from validation import validate_subagent_config from pydantic import ValidationError # Test invalid config try: config = validate_subagent_config(Path("invalid_subagents.yaml")) print("✓ Config is valid") except ValidationError as e: print("✗ Config validation failed:") for error in e.errors(): print(f" - {error['loc']}: {error['msg']}") EOFfrom pathlib import Path from validation import validate_subagent_config from pydantic import ValidationError # Test invalid config try: config = validate_subagent_config(Path("invalid_subagents.yaml")) print("✓ Config is valid") except ValidationError as e: print("✗ Config validation failed:") for error in e.errors(): print(f" - {error['loc']}: {error['msg']}") -
-
Run the validation test:
uv run test_validation.pySample output (your results may vary)✗ Config validation failed: - ('researcher', 'description'): String should have at least 10 characters - ('researcher', 'system_prompt'): String should have at least 20 characters -
Test with the valid config:
cat > test_validation2.py << 'EOF' from pathlib import Path from validation import validate_subagent_config from pydantic import ValidationError # Test valid config try: config = validate_subagent_config(Path("subagents.yaml")) print("✓ Config is valid") print(f" Loaded {len(config)} subagents") except ValidationError as e: print("✗ Config validation failed:") for error in e.errors(): print(f" - {error['loc']}: {error['msg']}") EOF -
Run the valid config test:
uv run test_validation2.pySample output (your results may vary)✓ Config is valid Loaded 3 subagents
Validation passes. This catches misconfigurations at load time, before your agent runs.
Best practices for larger projects:
-
Use Pydantic V2 validation on all YAML configs
-
Split subagents into separate files for teams > 7
-
Version control your YAML configs alongside code
-
Use YAML anchors and aliases for shared prompts:
_shared_review_criteria: &review_criteria |
- Accuracy: Are facts correct?
- Clarity: Is it easy to follow?
- Completeness: Does it cover the topic?
reviewer:
description: Review content for quality
system_prompt: |
You are a reviewer.
Criteria:
*review_criteria
fact_checker:
description: Verify factual accuracy
system_prompt: |
You verify facts.
Criteria:
*review_criteria
Module Summary
You’ve accomplished:
-
Understanding the YAML subagent pattern from content-builder-agent
-
Building a
load_subagents()helper to map YAML to Python -
Creating multi-subagent YAML configurations
-
Iterating on agent behavior via config-only changes
-
Overriding models at runtime via environment variables
-
Structuring larger projects with directory-based configs and validation
Key takeaways:
-
Separate configuration from code for easier iteration
-
Use directive descriptions ("ALWAYS use this first…") as routing signals
-
Environment variables override YAML models — same config works across dev, staging, and production
-
Validate configs with Pydantic V2 to catch errors early
-
Single YAML works for small teams; directories scale better for large teams
-
YAML-only changes let you refine behavior without redeploying code
The YAML subagent pattern is foundational for maintainable agent systems. As you build more complex agents, you’ll find that externalizing configuration dramatically improves your iteration speed and code quality.
Next, you’ll explore advanced orchestration patterns and learn how to build agents that coordinate multiple subagents for complex workflows.