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

  1. Examine the canonical example from the deepagents repository. Here’s what a subagents.yaml file 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.

  1. Set up a clean working directory for this exercise:

    mkdir -p yaml_demo
    cp utils.py yaml_demo/
    cd yaml_demo
  2. Define a simulated web_search tool:

    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
  3. 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
  4. Create a subagents.yaml file:

    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
  5. 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))
    EOF
    from 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))
  6. Run the demo:

    uv run demo.py
    Sample 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.

  1. Update subagents.yaml to 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
  2. 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))
    EOF
    from 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))
  3. Run the content pipeline:

    uv run content_pipeline.py
    Sample 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:

    1. Main agent delegates research to researcher (Haiku)

    2. Takes research results, delegates writing to writer (Sonnet)

    3. Takes draft content, delegates review to reviewer (Sonnet)

    4. 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

  1. Edit subagents.yaml to 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
  2. Re-run without changing Python:

    uv run content_pipeline.py
    Sample 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

  1. 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
  2. Re-run to see the updated writer behavior:

    uv run content_pipeline.py
    Sample 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

  1. 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
  2. Re-run to see stricter review feedback:

    uv run content_pipeline.py
    Sample 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 {NAME}_MODEL

RESEARCHER_MODEL=openai:llama-scout-17b

2

YAML model: field

model: anthropic:claude-haiku-4-5-20251001

3 (lowest)

No model set — inherits from main agent

Main agent’s MODEL value

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.

  1. Try overriding just the researcher’s model at runtime:

    RESEARCHER_MODEL=anthropic:claude-sonnet-4-6 uv run demo.py

    The researcher now uses Sonnet instead of Haiku — without touching YAML.

  2. Override multiple models at once:

    RESEARCHER_MODEL=anthropic:claude-sonnet-4-6 WRITER_MODEL=anthropic:claude-haiku-4-5-20251001 uv run content_pipeline.py

    Now 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:

  1. 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
  2. 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.

  1. 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
  2. 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
  3. 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']}")
    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']}")
  4. Run the validation test:

    uv run test_validation.py
    Sample 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
  5. 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
  6. Run the valid config test:

    uv run test_validation2.py
    Sample 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.