← Back to Table of Contents

Chapter 11 β€” Agentic Design Patterns

β€œThe difference between a demo agent and a production agent is design patterns.”

Why Patterns Matter

Building a single agent is easy. Building a reliable, maintainable, cost-effective agent system is hard. Design patterns are proven solutions to common agent architecture problems. Master these and you can design any agentic system.

The Six Core Patterns

Agentic Design Patterns
πŸ”€
Router
Route to specialized handlers
πŸ‘”
Orchestrator-Worker
Manager delegates to workers
πŸ”
Evaluator-Optimizer
Generate, evaluate, improve
⚑
Parallelization
Run tasks concurrently
πŸ‘€
Human-in-the-Loop
Pause for human approval
πŸ›‘οΈ
Guardrails
Constrain agent behavior

Pattern 1: Router

A router classifies the input and directs it to the right specialist. It’s the simplest pattern and the foundation of customer support, coding assistants, and knowledge systems.

Router Pattern
πŸ“₯ User Input
πŸ”€ Router: classify intent
Technical
Coding agent
Creative
Writing agent
Factual
Research agent
def router(user_input: str) -> str:
    """Classify intent and route to the right agent."""
    classification = llm.invoke(f"""
    Classify this user request into one category:
    - technical: coding, debugging, architecture questions
    - creative: writing, brainstorming, content creation  
    - factual: research, data, current events
    
    Request: {user_input}
    
    Respond with just the category name.
    """)
    
    agents = {
        "technical": coding_agent,
        "creative": writing_agent,
        "factual": research_agent,
    }
    
    return agents[classification.content.strip()](user_input)

When to use: Multiple distinct capabilities, high request volume, different SLAs per category.

Pattern 2: Orchestrator-Worker

A central orchestrator breaks down complex tasks and delegates sub-tasks to specialized workers.

Orchestrator-Worker Pattern
πŸ“‹ "Build me a landing page"
🎯 Orchestrator: decomposes into subtasks
Worker 1
Design the layout
Worker 2
Write the HTML/CSS
Worker 3
Write the copy
🎯 Orchestrator: assembles final result

When to use: Complex tasks with multiple sub-skills, where each sub-task benefits from a focused specialist.

Pattern 3: Evaluator-Optimizer

Generate a draft β†’ evaluate its quality β†’ improve it. Repeat until quality threshold is met. This is how coding agents fix bugs and how writing agents polish prose.

Evaluator-Optimizer Loop
✏️ Generator: produce output
πŸ” Evaluator: score quality (0-10)
Score β‰₯ 8? β†’ Done βœ… | Score < 8? β†’ ↓
πŸ’‘ Feedback: specific improvement suggestions
πŸ”„ Generator: revise using feedback
def evaluator_optimizer(task: str, max_rounds: int = 3, threshold: float = 8.0):
    output = generator.invoke(task)
    
    for round in range(max_rounds):
        # Evaluate
        evaluation = evaluator.invoke(f"""
        Task: {task}
        Output: {output}
        
        Rate the quality from 0-10 and provide specific feedback for improvement.
        Format: SCORE: X\nFEEDBACK: ...
        """)
        
        score = parse_score(evaluation)
        if score >= threshold:
            return output
        
        feedback = parse_feedback(evaluation)
        
        # Optimize
        output = generator.invoke(f"""
        Original task: {task}
        Previous output: {output}
        Feedback: {feedback}
        
        Improve the output based on the feedback.
        """)
    
    return output

When to use: Code generation (write β†’ test β†’ fix), content creation, data analysis reports.

Pattern 4: Parallelization

Run independent tasks simultaneously to reduce latency. Two flavors:

Sectioning (Split task)
  • One task, split into independent parts
  • "Research 3 companies" β†’ 3 parallel searches
  • Results are combined at the end
  • Linear speedup with # of sections
Voting (Redundancy)
  • Same task, run multiple times
  • "Is this code secure?" β†’ 3 agents vote
  • Majority wins or disagreements flagged
  • Higher reliability, higher cost
import asyncio

async def parallel_research(topics: list[str]):
    """Research multiple topics in parallel."""
    tasks = [research_agent.ainvoke(topic) for topic in topics]
    results = await asyncio.gather(*tasks)
    return combine_results(results)

# Sectioning: 3x faster than sequential
results = await parallel_research([
    "Apple Q4 2025 earnings",
    "Google Q4 2025 earnings",
    "Microsoft Q4 2025 earnings",
])

Pattern 5: Human-in-the-Loop

Pause agent execution at critical checkpoints for human approval. Essential for actions with real consequences.

Human-in-the-Loop Pattern
πŸ€– Agent: plan generated
⏸️ Checkpoint: "I want to send an email to 500 customers"
πŸ‘€ Human reviews and approves/rejects
βœ… Approved β†’ Continue | ❌ Rejected β†’ Replan
# LangGraph human-in-the-loop
from langgraph.graph import StateGraph

def should_continue(state):
    """Check if the next action requires human approval."""
    last_action = state["pending_action"]
    
    REQUIRES_APPROVAL = ["send_email", "delete_data", "deploy", "purchase"]
    
    if last_action["tool"] in REQUIRES_APPROVAL:
        return "human_review"
    return "execute"

graph.add_conditional_edges("plan", should_continue, {
    "human_review": "wait_for_approval",
    "execute": "execute_action",
})

When to use: Any action that costs money, sends communications, modifies production data, or is irreversible.

Pattern 6: Guardrails

Constrain what the agent can do β€” before it acts, not after.

πŸ”’
Input Guardrails
Validate user input before the agent sees it. Block prompt injection, off-topic requests, PII exposure.
πŸ›‘οΈ
Output Guardrails
Validate agent output before the user sees it. Block harmful content, hallucinated URLs, leaked secrets.
πŸ”§
Tool Guardrails
Restrict which tools can be called, with what arguments, and how often. Rate limits, allowlists.
πŸ’°
Budget Guardrails
Cap total token usage, API costs, and execution time. Prevent runaway agents.
# OpenAI Agents SDK guardrails
from agents import Agent, GuardrailFunctionOutput, input_guardrail

@input_guardrail  
async def block_off_topic(ctx, agent, input):
    """Reject requests that aren't related to research."""
    result = await llm.invoke(f"""
    Is this request related to research, analysis, or information gathering?
    Request: {input}
    Respond with YES or NO.
    """)
    
    if "NO" in result.content:
        return GuardrailFunctionOutput(
            output_info="I can only help with research-related questions.",
            tripwire_triggered=True,
        )

agent = Agent(
    name="Researcher",
    instructions="...",
    input_guardrails=[block_off_topic],
)

Pattern Combinations

Real systems combine multiple patterns:

System Patterns Used
Customer support bot Router + Swarm (handoffs) + Human-in-the-loop
Coding assistant Evaluator-Optimizer (write β†’ test β†’ fix) + Guardrails
Research pipeline Orchestrator-Worker + Parallelization + Evaluator
Content platform Router + Parallelization + Human-in-the-loop

Decision Framework

Which Pattern Do You Need?
What's your challenge?
Many request types
β†’ Router
Complex task
β†’ Orchestrator-Worker
Quality matters
β†’ Evaluator-Optimizer
Slow response
β†’ Parallelization

What’s Next

Patterns tell you how to build. But how do you make agents reliable and observable in production? That’s deployment.

Next: Chapter 12 β€” Deployment & Production β†’


← Previous: Chapter 10 β€” Build Your First Agent Β· Next: Chapter 12 β€” Deployment & Production β†’

Last updated: April 2026