What Are Agent Architectures?

Think of agent architectures as blueprints for how an AI agent thinks and acts. Just like buildings have different designs (houses, offices, hospitals), AI agents have different architectures depending on what they need to do.

Choosing the right architecture is like choosing the right tool for a job:

  • Use a hammer for nails, not screws
  • Use a simple agent for simple tasks, not complex ones
  • Use a multi-agent system when one agent can't handle everything
# The Big Picture: How Agents Work

┌─────────────────────────────────────────────────────┐
│                    USER QUERY                        │
│              "Book me a flight to Paris"             │
└─────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────┐
│               AGENT ARCHITECTURE                     │
│  (The "brain" that decides HOW to solve the task)   │
│                                                      │
│   • What steps to take?                             │
│   • What tools to use?                              │
│   • How to handle errors?                           │
│   • When to ask for help?                           │
└─────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────┐
│                    RESULT                            │
│        "Your flight is booked for March 15"         │
└─────────────────────────────────────────────────────┘

Quick Reference: Which Pattern Should I Use?

Here's a simple decision guide before we dive deep:

┌─────────────────────────────────────────────────────────────────┐
│                    PATTERN SELECTION GUIDE                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  "I need to answer questions and maybe use some tools"          │
│   → Use: ReAct Pattern                                          │
│                                                                  │
│  "I have a complex task with many steps"                        │
│   → Use: Plan-and-Execute Pattern                               │
│                                                                  │
│  "I need high-quality, verified outputs"                        │
│   → Use: Reflection Pattern                                     │
│                                                                  │
│  "Different questions need different specialists"               │
│   → Use: Router Pattern                                         │
│                                                                  │
│  "I need multiple experts working together"                     │
│   → Use: Multi-Agent Pattern (Supervisor or Hierarchical)       │
│                                                                  │
│  "Critical decisions that need human approval"                  │
│   → Use: Human-in-the-Loop Pattern                              │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

1. ReAct Pattern (Reasoning + Acting)

What Is It?

ReAct is the most common pattern. The agent thinks about what to do, does it, sees the result, and repeats until the task is done. It's like how you naturally solve problems.

# How ReAct Works - Like Your Brain!

You: "What's the weather in Tokyo and should I pack an umbrella?"

Agent's Brain:
┌──────────────────────────────────────────────────────┐
│ THOUGHT: I need to check Tokyo's weather first       │
│ ACTION:  Call weather API for Tokyo                  │
│ OBSERVE: Temperature: 18°C, Rain: 80% chance         │
├──────────────────────────────────────────────────────┤
│ THOUGHT: High rain chance means umbrella needed      │
│ ACTION:  Formulate response                          │
│ OBSERVE: Ready to respond                            │
├──────────────────────────────────────────────────────┤
│ ANSWER:  "Tokyo is 18°C with 80% rain chance.        │
│          Yes, definitely pack an umbrella!"          │
└──────────────────────────────────────────────────────┘

When To Use ReAct

  • Question answering that might need external data
  • Simple tool use - search, calculate, look up information
  • Conversational agents that help users with various tasks
  • Tasks with 1-5 steps - not too complex

When NOT To Use ReAct

  • Very complex tasks with 10+ steps (use Plan-and-Execute instead)
  • Tasks needing multiple specialists (use Multi-Agent instead)
  • When you need guaranteed step order (ReAct can be unpredictable)

Real-World Examples

Customer Support Bot

"Check order status" → Look up order → Return status

Research Assistant

"Find info about X" → Search → Summarize findings

Calculator Agent

"What's 15% tip on $85?" → Calculate → Return answer

Weather Bot

"Weather tomorrow?" → Call API → Summarize forecast

Code Example

from langchain.agents import create_react_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain.tools import Tool
from langchain import hub

# Step 1: Define tools the agent can use
def search_web(query: str) -> str:
    """Search the web for information."""
    # In real app, call actual search API
    return f"Search results for: {query}"

def get_weather(city: str) -> str:
    """Get weather for a city."""
    # In real app, call weather API
    return f"Weather in {city}: 22°C, Sunny"

tools = [
    Tool(name="search", func=search_web, description="Search the web"),
    Tool(name="weather", func=get_weather, description="Get weather for a city"),
]

# Step 2: Create the ReAct agent
llm = ChatOpenAI(model="gpt-4", temperature=0)
prompt = hub.pull("hwchase17/react")  # Standard ReAct prompt

agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# Step 3: Run it!
result = executor.invoke({
    "input": "What's the weather in Paris and find me popular attractions there"
})

# The agent will:
# 1. THINK: I need weather info and attraction info
# 2. ACT: Call weather tool for Paris
# 3. OBSERVE: Get weather result
# 4. THINK: Now I need attractions
# 5. ACT: Search for Paris attractions
# 6. OBSERVE: Get search results
# 7. RESPOND: Combine both into final answer

2. Plan-and-Execute Pattern

What Is It?

Instead of figuring things out step-by-step, this pattern creates a complete plan first, then executes each step. Like writing a recipe before cooking.

# Plan-and-Execute: Think First, Act Later

Task: "Plan a birthday party for 20 people with a $500 budget"

┌─────────────────────────────────────────────────────────────────┐
│                    PLANNING PHASE                                │
│  (Agent creates the full plan before doing anything)            │
├─────────────────────────────────────────────────────────────────┤
│  Step 1: Calculate per-person budget ($500 ÷ 20 = $25/person)  │
│  Step 2: Research venue options under $150                      │
│  Step 3: Find catering options for 20 people under $200        │
│  Step 4: List decoration ideas under $100                       │
│  Step 5: Suggest entertainment options under $50                │
│  Step 6: Create final party plan with timeline                  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                   EXECUTION PHASE                                │
│  (Agent executes each step, one by one)                         │
├─────────────────────────────────────────────────────────────────┤
│  ✓ Step 1: Done - $25 per person                                │
│  ✓ Step 2: Done - Found 3 venue options                         │
│  ✓ Step 3: Done - Found catering for $180                       │
│  → Step 4: In progress...                                       │
│  ○ Step 5: Pending                                              │
│  ○ Step 6: Pending                                              │
└─────────────────────────────────────────────────────────────────┘

When To Use Plan-and-Execute

  • Complex multi-step tasks with 5+ steps
  • Tasks requiring specific order - step 2 depends on step 1
  • Project planning - trips, events, research projects
  • When you need visibility - users can see and approve the plan

When NOT To Use Plan-and-Execute

  • Simple questions that need quick answers
  • Highly dynamic situations where the plan might change constantly
  • Real-time conversations (planning takes time)

Real-World Examples

Travel Planner

Plan flights, hotels, activities, restaurants in order

Research Report

Outline → Research each section → Write → Review

Code Refactoring

Analyze → Plan changes → Implement → Test

Event Organization

Budget → Venue → Catering → Invites → Day-of plan

Code Example

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4", temperature=0)

class PlanAndExecuteAgent:
    def __init__(self):
        self.llm = llm
        self.plan = []
        self.results = []

    def create_plan(self, task: str) -> list:
        """Phase 1: Create a detailed plan."""

        planning_prompt = ChatPromptTemplate.from_template("""
        Create a step-by-step plan to accomplish this task.
        Return ONLY a numbered list of steps.

        Task: {task}

        Steps:
        """)

        response = self.llm.invoke(planning_prompt.format(task=task))

        # Parse steps from response
        steps = []
        for line in response.content.split('\n'):
            line = line.strip()
            if line and line[0].isdigit():
                steps.append(line)

        self.plan = steps
        return steps

    def execute_step(self, step: str, previous_results: list) -> str:
        """Phase 2: Execute a single step."""

        execution_prompt = ChatPromptTemplate.from_template("""
        Execute this step of the plan.

        Current Step: {step}

        Previous Results:
        {previous_results}

        Complete this step and provide the result:
        """)

        response = self.llm.invoke(execution_prompt.format(
            step=step,
            previous_results="\n".join(previous_results) if previous_results else "None yet"
        ))

        return response.content

    def run(self, task: str) -> dict:
        """Run the full plan-and-execute cycle."""

        # Phase 1: Plan
        print("📋 PLANNING PHASE")
        print("=" * 50)
        plan = self.create_plan(task)
        for i, step in enumerate(plan, 1):
            print(f"  {step}")

        # Phase 2: Execute
        print("\n🚀 EXECUTION PHASE")
        print("=" * 50)
        results = []
        for i, step in enumerate(plan, 1):
            print(f"\n▶ Executing: {step}")
            result = self.execute_step(step, results)
            results.append(f"Step {i} Result: {result}")
            print(f"  ✓ Done: {result[:100]}...")

        return {
            "plan": plan,
            "results": results
        }

# Usage
agent = PlanAndExecuteAgent()
result = agent.run("Create a marketing plan for launching a new mobile app")

3. Reflection Pattern (Self-Critique)

What Is It?

The agent checks its own work and improves it. Like proofreading your essay before submitting. The agent generates an answer, critiques it, and refines it.

# Reflection: Check Your Own Work

Task: "Write a professional email declining a job offer"

┌─────────────────────────────────────────────────────────────────┐
│                    FIRST ATTEMPT                                 │
├─────────────────────────────────────────────────────────────────┤
│  "Hi, I don't want the job. Thanks anyway."                     │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    REFLECTION (Self-Critique)                    │
├─────────────────────────────────────────────────────────────────┤
│  Problems found:                                                 │
│  ✗ Too informal for professional context                        │
│  ✗ No gratitude expressed                                       │
│  ✗ No door left open for future opportunities                   │
│  ✗ Missing proper greeting and closing                          │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    REFINED VERSION                               │
├─────────────────────────────────────────────────────────────────┤
│  "Dear Mr. Johnson,                                              │
│                                                                  │
│  Thank you for offering me the Software Engineer position.      │
│  After careful consideration, I've decided to pursue another    │
│  opportunity that aligns more closely with my career goals.     │
│                                                                  │
│  I sincerely appreciate your time and the team's confidence    │
│  in my abilities. I hope we can stay connected for future      │
│  opportunities.                                                  │
│                                                                  │
│  Best regards,                                                   │
│  [Name]"                                                         │
└─────────────────────────────────────────────────────────────────┘

When To Use Reflection

  • Content quality matters - writing, code, analysis
  • Complex reasoning - math, logic, problem-solving
  • Professional outputs - emails, reports, documentation
  • When accuracy is critical - fact-checking, verification

When NOT To Use Reflection

  • Simple factual questions (no need to over-think "What's 2+2?")
  • Time-sensitive responses (reflection adds latency)
  • Casual conversations (over-polishing kills natural flow)

Real-World Examples

Code Review Bot

Generate code → Check for bugs → Fix issues → Verify

Essay Writer

Write draft → Critique structure → Improve → Polish

Math Solver

Solve problem → Verify answer → Correct if wrong

Legal Document Review

Analyze → Check for issues → Suggest improvements

Code Example

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4", temperature=0.7)

class ReflectionAgent:
    def __init__(self, max_iterations: int = 3):
        self.llm = llm
        self.max_iterations = max_iterations

    def generate(self, task: str) -> str:
        """Generate initial response."""
        prompt = ChatPromptTemplate.from_template(
            "Complete this task:\n{task}"
        )
        response = self.llm.invoke(prompt.format(task=task))
        return response.content

    def reflect(self, task: str, response: str) -> dict:
        """Critique the response."""
        prompt = ChatPromptTemplate.from_template("""
        Task: {task}

        Response to critique:
        {response}

        Analyze this response and provide:
        1. SCORE: Rate from 1-10
        2. PROBLEMS: List specific issues (if any)
        3. SUGGESTIONS: How to improve

        Be critical but fair.
        """)

        critique = self.llm.invoke(prompt.format(task=task, response=response))

        # Parse score (simplified)
        score = 5  # Default
        for line in critique.content.split('\n'):
            if 'SCORE' in line.upper():
                try:
                    score = int(''.join(filter(str.isdigit, line)))
                except:
                    pass

        return {
            "score": score,
            "critique": critique.content
        }

    def improve(self, task: str, response: str, critique: str) -> str:
        """Improve based on critique."""
        prompt = ChatPromptTemplate.from_template("""
        Task: {task}

        Original Response:
        {response}

        Critique:
        {critique}

        Write an improved version addressing all the feedback:
        """)

        improved = self.llm.invoke(prompt.format(
            task=task, response=response, critique=critique
        ))
        return improved.content

    def run(self, task: str) -> str:
        """Run reflection loop."""
        print("🔄 REFLECTION AGENT")
        print("=" * 50)

        # Initial generation
        response = self.generate(task)
        print(f"\n📝 Initial Response:\n{response[:200]}...")

        for i in range(self.max_iterations):
            print(f"\n🔍 Reflection Round {i + 1}")

            # Reflect
            reflection = self.reflect(task, response)
            print(f"   Score: {reflection['score']}/10")

            # If good enough, stop
            if reflection['score'] >= 8:
                print("   ✓ Quality threshold met!")
                break

            # Improve
            response = self.improve(task, response, reflection['critique'])
            print(f"   ✓ Improved version generated")

        return response

# Usage
agent = ReflectionAgent(max_iterations=3)
result = agent.run("Write a product description for wireless earbuds")

4. Router Pattern

What Is It?

A "traffic controller" that looks at the request and sends it to the right specialist. Like a hospital receptionist directing patients to the right department.

# Router: Direct Traffic to the Right Place

┌─────────────────────────────────────────────────────────────────┐
│                      USER QUESTIONS                              │
└─────────────────────────────────────────────────────────────────┘
         │                    │                    │
         ▼                    ▼                    ▼
   "Fix my code"      "Write a poem"      "Analyze this data"
         │                    │                    │
         └────────────────────┼────────────────────┘
                              │
                              ▼
                    ┌─────────────────┐
                    │     ROUTER      │
                    │  "What type of  │
                    │   request is    │
                    │     this?"      │
                    └─────────────────┘
                              │
         ┌────────────────────┼────────────────────┐
         │                    │                    │
         ▼                    ▼                    ▼
   ┌───────────┐       ┌───────────┐       ┌───────────┐
   │   CODE    │       │ CREATIVE  │       │   DATA    │
   │  EXPERT   │       │  WRITER   │       │  ANALYST  │
   └───────────┘       └───────────┘       └───────────┘
         │                    │                    │
         ▼                    ▼                    ▼
   "Here's the      "Roses are red..." "The data shows..."
    fixed code"

When To Use Router

  • Multiple specialized capabilities in one system
  • Different models for different tasks - use cheap model for simple questions, expensive for complex
  • Customer service - route to billing, tech support, sales
  • Cost optimization - use the right resource for each task

When NOT To Use Router

  • Simple single-purpose agents
  • When all requests are similar
  • When routing overhead isn't worth it

Real-World Examples

Customer Support

Route to: Billing, Technical, Sales, Returns

Coding Assistant

Route to: Python expert, JavaScript expert, DevOps

Content Creator

Route to: Blog writer, Social media, Email marketing

Cost Optimizer

Simple → GPT-3.5, Complex → GPT-4, Code → Claude

Code Example

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from enum import Enum

class RouteType(Enum):
    CODE = "code"
    CREATIVE = "creative"
    ANALYSIS = "analysis"
    GENERAL = "general"

class RouterAgent:
    def __init__(self):
        # Router uses fast, cheap model
        self.router_llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

        # Specialists can use different models
        self.specialists = {
            RouteType.CODE: ChatOpenAI(model="gpt-4", temperature=0),
            RouteType.CREATIVE: ChatOpenAI(model="gpt-4", temperature=0.9),
            RouteType.ANALYSIS: ChatOpenAI(model="gpt-4", temperature=0),
            RouteType.GENERAL: ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7),
        }

        # Specialist prompts
        self.specialist_prompts = {
            RouteType.CODE: "You are an expert programmer. Help with: {query}",
            RouteType.CREATIVE: "You are a creative writer. Help with: {query}",
            RouteType.ANALYSIS: "You are a data analyst. Help with: {query}",
            RouteType.GENERAL: "You are a helpful assistant. Help with: {query}",
        }

    def route(self, query: str) -> RouteType:
        """Determine which specialist should handle this query."""

        routing_prompt = ChatPromptTemplate.from_template("""
        Classify this query into ONE category:
        - CODE: Programming, debugging, technical implementation
        - CREATIVE: Writing, stories, poetry, creative content
        - ANALYSIS: Data analysis, statistics, insights
        - GENERAL: Everything else

        Query: {query}

        Category (respond with just the word):
        """)

        response = self.router_llm.invoke(routing_prompt.format(query=query))
        category = response.content.strip().upper()

        # Map to RouteType
        route_map = {
            "CODE": RouteType.CODE,
            "CREATIVE": RouteType.CREATIVE,
            "ANALYSIS": RouteType.ANALYSIS,
        }

        return route_map.get(category, RouteType.GENERAL)

    def process(self, query: str) -> dict:
        """Route and process the query."""

        # Step 1: Route
        route = self.route(query)
        print(f"🔀 Routed to: {route.value.upper()} specialist")

        # Step 2: Get specialist
        specialist = self.specialists[route]
        prompt = self.specialist_prompts[route]

        # Step 3: Process
        response = specialist.invoke(prompt.format(query=query))

        return {
            "route": route.value,
            "response": response.content
        }

# Usage
agent = RouterAgent()

# These go to different specialists
print(agent.process("Fix this Python code: def add(a, b) return a + b"))
# → Routed to: CODE specialist

print(agent.process("Write a haiku about programming"))
# → Routed to: CREATIVE specialist

print(agent.process("What's the average of 10, 20, 30?"))
# → Routed to: ANALYSIS specialist

5. Multi-Agent Patterns

When one agent isn't enough, we use multiple agents working together. There are several ways to organize them:

5a. Supervisor Pattern

One "boss" agent manages and coordinates worker agents.

# Supervisor: One Boss, Many Workers

Task: "Create a marketing campaign for a new product"

                    ┌─────────────────┐
                    │   SUPERVISOR    │
                    │   "I'll assign  │
                    │   tasks and     │
                    │   combine work" │
                    └─────────────────┘
                            │
         ┌──────────────────┼──────────────────┐
         │                  │                  │
         ▼                  ▼                  ▼
   ┌───────────┐     ┌───────────┐     ┌───────────┐
   │ RESEARCH  │     │  WRITER   │     │  DESIGN   │
   │  AGENT    │     │  AGENT    │     │  AGENT    │
   │           │     │           │     │           │
   │ "Analyze  │     │ "Write    │     │ "Create   │
   │ market"   │     │ copy"     │     │ visuals"  │
   └───────────┘     └───────────┘     └───────────┘
         │                  │                  │
         └──────────────────┼──────────────────┘
                            │
                            ▼
                    ┌─────────────────┐
                    │   SUPERVISOR    │
                    │  "Combining     │
                    │  all outputs    │
                    │  into final     │
                    │  campaign"      │
                    └─────────────────┘

5b. Hierarchical Pattern

Agents organized in levels, like a company org chart.

# Hierarchical: Levels of Authority

                    ┌─────────────────┐
                    │     CEO         │
                    │ (Top-level      │
                    │  Coordinator)   │
                    └─────────────────┘
                            │
              ┌─────────────┼─────────────┐
              │             │             │
              ▼             ▼             ▼
        ┌──────────┐  ┌──────────┐  ┌──────────┐
        │ MANAGER  │  │ MANAGER  │  │ MANAGER  │
        │ Research │  │  Content │  │   Tech   │
        └──────────┘  └──────────┘  └──────────┘
              │             │             │
         ┌────┴────┐   ┌────┴────┐   ┌────┴────┐
         ▼         ▼   ▼         ▼   ▼         ▼
      Worker    Worker  Worker    Worker  Worker  Worker

5c. Debate Pattern

Agents argue different perspectives to reach better conclusions.

# Debate: Agents Argue to Find Truth

Question: "Should we use microservices or monolith?"

   ┌───────────────┐                 ┌───────────────┐
   │  ADVOCATE     │   VS            │   ADVOCATE    │
   │ Microservices │  ◄────────────► │   Monolith    │
   │               │                 │               │
   │ "Better       │                 │ "Simpler,     │
   │  scaling,     │                 │  faster to    │
   │  independence"│                 │  develop"     │
   └───────────────┘                 └───────────────┘
            │                               │
            └───────────────┬───────────────┘
                            │
                            ▼
                    ┌─────────────────┐
                    │     JUDGE       │
                    │  "Based on the  │
                    │  arguments,     │
                    │  for a startup  │
                    │  I recommend    │
                    │  monolith first"│
                    └─────────────────┘

When To Use Multi-Agent

  • Complex tasks requiring different expertise
  • Need for collaboration - research, writing, review
  • Quality through debate - important decisions
  • Large projects - divide and conquer

When NOT To Use Multi-Agent

  • Simple tasks one agent can handle
  • Speed is critical (coordination takes time)
  • Budget is very tight (multiple agents = more API calls)

Code Example: Supervisor Pattern

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4", temperature=0.7)

class WorkerAgent:
    def __init__(self, name: str, role: str):
        self.name = name
        self.role = role
        self.llm = llm

    def work(self, task: str) -> str:
        prompt = ChatPromptTemplate.from_template(
            f"You are a {self.role}. Complete this task:\n{{task}}"
        )
        response = self.llm.invoke(prompt.format(task=task))
        return response.content

class SupervisorAgent:
    def __init__(self):
        self.llm = llm
        self.workers = {
            "researcher": WorkerAgent("Alex", "market research specialist"),
            "writer": WorkerAgent("Sam", "content writer"),
            "critic": WorkerAgent("Jordan", "quality reviewer"),
        }

    def plan_tasks(self, goal: str) -> list:
        """Break down the goal into tasks for workers."""
        prompt = ChatPromptTemplate.from_template("""
        You are a project supervisor. Break this goal into tasks.
        Assign each to: researcher, writer, or critic.

        Goal: {goal}

        Return as:
        TASK 1: [worker] - [task description]
        TASK 2: [worker] - [task description]
        ...
        """)

        response = self.llm.invoke(prompt.format(goal=goal))

        # Parse tasks (simplified)
        tasks = []
        for line in response.content.split('\n'):
            if 'TASK' in line.upper() and '-' in line:
                parts = line.split('-', 1)
                if len(parts) == 2:
                    worker = "researcher"  # Default
                    for w in self.workers:
                        if w in parts[0].lower():
                            worker = w
                    tasks.append({"worker": worker, "task": parts[1].strip()})

        return tasks

    def execute(self, goal: str) -> dict:
        """Execute the full workflow."""
        print(f"🎯 GOAL: {goal}")
        print("=" * 60)

        # Plan
        tasks = self.plan_tasks(goal)
        print(f"\n📋 PLANNED {len(tasks)} TASKS")

        # Execute each task
        results = []
        for i, task_info in enumerate(tasks, 1):
            worker_name = task_info["worker"]
            task = task_info["task"]

            print(f"\n👤 {worker_name.upper()} working on: {task[:50]}...")

            worker = self.workers.get(worker_name, self.workers["writer"])
            result = worker.work(task)
            results.append({"worker": worker_name, "task": task, "result": result})

            print(f"   ✓ Complete")

        # Combine results
        final = self.combine_results(goal, results)

        return {"tasks": results, "final": final}

    def combine_results(self, goal: str, results: list) -> str:
        """Combine all worker outputs into final deliverable."""
        results_text = "\n\n".join([
            f"From {r['worker']}:\n{r['result']}" for r in results
        ])

        prompt = ChatPromptTemplate.from_template("""
        As supervisor, combine these team outputs into a cohesive final result.

        Original Goal: {goal}

        Team Outputs:
        {results}

        Create the final, polished deliverable:
        """)

        response = self.llm.invoke(prompt.format(goal=goal, results=results_text))
        return response.content

# Usage
supervisor = SupervisorAgent()
result = supervisor.execute(
    "Create a product launch announcement for our new AI writing assistant"
)

6. Human-in-the-Loop Pattern

What Is It?

The agent pauses at critical points to get human approval before proceeding. Essential for high-stakes decisions.

# Human-in-the-Loop: Checkpoints for Safety

Task: "Send refund of $5,000 to customer"

┌─────────────────────────────────────────────────────────────────┐
│ AGENT: I'll process this refund. Let me verify the details...  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│ AGENT: Found the order. Refund amount matches. Ready to send.  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│ ⚠️  HUMAN APPROVAL REQUIRED                                     │
│                                                                  │
│  Action: Process refund                                         │
│  Amount: $5,000.00                                              │
│  To: customer@email.com                                         │
│  Reason: Product defect                                         │
│                                                                  │
│  [ APPROVE ]    [ REJECT ]    [ MODIFY ]                        │
└─────────────────────────────────────────────────────────────────┘
                              │
                              │ Human clicks APPROVE
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│ AGENT: Refund processed successfully. Confirmation #RF-12345   │
└─────────────────────────────────────────────────────────────────┘

When To Use Human-in-the-Loop

  • Financial transactions - payments, refunds, purchases
  • Irreversible actions - deletions, sending emails
  • External communications - customer messages, social media posts
  • Legal/compliance actions - contracts, agreements
  • Security-sensitive operations - access changes, deployments

When NOT To Use Human-in-the-Loop

  • Low-risk, easily reversible actions
  • High-volume automated tasks
  • When instant response is required

Code Example

from langchain_openai import ChatOpenAI
from enum import Enum
from typing import Callable

class RiskLevel(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"

class HumanInTheLoopAgent:
    def __init__(self, approval_callback: Callable = None):
        self.llm = ChatOpenAI(model="gpt-4", temperature=0)
        # Default: ask for input in terminal
        self.get_approval = approval_callback or self._terminal_approval

        # Define which actions need approval
        self.approval_required = {
            RiskLevel.LOW: False,       # Auto-approve
            RiskLevel.MEDIUM: False,    # Auto-approve with logging
            RiskLevel.HIGH: True,       # Needs human approval
            RiskLevel.CRITICAL: True,   # Always needs approval
        }

    def _terminal_approval(self, action: dict) -> bool:
        """Get approval via terminal input."""
        print("\n" + "=" * 60)
        print("⚠️  HUMAN APPROVAL REQUIRED")
        print("=" * 60)
        print(f"Action: {action['description']}")
        print(f"Risk Level: {action['risk_level'].value.upper()}")
        print(f"Details: {action['details']}")
        print("-" * 60)

        response = input("Approve? (yes/no): ").strip().lower()
        return response in ['yes', 'y', 'approve']

    def assess_risk(self, action: str) -> RiskLevel:
        """Assess the risk level of an action."""
        action_lower = action.lower()

        # Critical: Financial, delete, security
        if any(word in action_lower for word in
               ['payment', 'transfer', 'delete all', 'password', 'admin']):
            return RiskLevel.CRITICAL

        # High: External communication, modifications
        if any(word in action_lower for word in
               ['send email', 'post', 'publish', 'modify user']):
            return RiskLevel.HIGH

        # Medium: Internal changes
        if any(word in action_lower for word in
               ['update', 'create', 'change']):
            return RiskLevel.MEDIUM

        return RiskLevel.LOW

    def execute_with_approval(self, action: str, details: dict) -> dict:
        """Execute an action, with human approval if needed."""

        risk_level = self.assess_risk(action)

        action_info = {
            "description": action,
            "risk_level": risk_level,
            "details": details
        }

        # Check if approval is needed
        needs_approval = self.approval_required.get(risk_level, True)

        if needs_approval:
            approved = self.get_approval(action_info)

            if not approved:
                return {
                    "status": "rejected",
                    "message": "Action rejected by human operator",
                    "action": action
                }

        # Execute the action (simulated)
        result = self._execute_action(action, details)

        return {
            "status": "completed",
            "message": f"Action completed successfully",
            "action": action,
            "result": result,
            "approved_by": "human" if needs_approval else "auto"
        }

    def _execute_action(self, action: str, details: dict) -> str:
        """Actually execute the action."""
        # In real app, this would call actual APIs
        return f"Executed: {action}"

# Usage
agent = HumanInTheLoopAgent()

# Low risk - auto-approved
result = agent.execute_with_approval(
    "Search for customer order",
    {"order_id": "12345"}
)

# High risk - needs approval
result = agent.execute_with_approval(
    "Send email to customer about refund",
    {"to": "customer@email.com", "subject": "Your refund", "amount": "$500"}
)

# Critical - definitely needs approval
result = agent.execute_with_approval(
    "Process payment of $10,000",
    {"amount": 10000, "recipient": "vendor@company.com"}
)

Pattern Comparison Summary

┌─────────────────┬────────────────────────┬────────────────────────┬──────────────┐
│ PATTERN         │ BEST FOR               │ AVOID WHEN             │ COMPLEXITY   │
├─────────────────┼────────────────────────┼────────────────────────┼──────────────┤
│ ReAct           │ Q&A with tools         │ Very complex tasks     │ Low          │
│                 │ Simple assistants      │ Need strict order      │              │
├─────────────────┼────────────────────────┼────────────────────────┼──────────────┤
│ Plan & Execute  │ Complex projects       │ Simple questions       │ Medium       │
│                 │ Multi-step tasks       │ Dynamic situations     │              │
├─────────────────┼────────────────────────┼────────────────────────┼──────────────┤
│ Reflection      │ Quality content        │ Quick responses        │ Medium       │
│                 │ Code, writing          │ Simple facts           │              │
├─────────────────┼────────────────────────┼────────────────────────┼──────────────┤
│ Router          │ Multiple specialists   │ Single-purpose agents  │ Low-Medium   │
│                 │ Cost optimization      │ Uniform requests       │              │
├─────────────────┼────────────────────────┼────────────────────────┼──────────────┤
│ Multi-Agent     │ Team collaboration     │ Simple tasks           │ High         │
│                 │ Complex projects       │ Speed critical         │              │
├─────────────────┼────────────────────────┼────────────────────────┼──────────────┤
│ Human-in-Loop   │ High-stakes actions    │ Low-risk automation    │ Low-Medium   │
│                 │ Critical decisions     │ High-volume tasks      │              │
└─────────────────┴────────────────────────┴────────────────────────┴──────────────┘

Combining Patterns (Advanced)

In real applications, you often combine multiple patterns:

# Example: Customer Service Bot Architecture

                         ┌─────────────────┐
                         │     ROUTER      │  ← Pattern 4
                         │ Route by topic  │
                         └─────────────────┘
                                 │
         ┌───────────────────────┼───────────────────────┐
         │                       │                       │
         ▼                       ▼                       ▼
   ┌───────────┐          ┌───────────┐          ┌───────────┐
   │  BILLING  │          │  SUPPORT  │          │  RETURNS  │
   │   Agent   │          │   Agent   │          │   Agent   │
   │  (ReAct)  │ ← Pat 1  │(Reflection│ ← Pat 3  │(Plan&Exec)│ ← Pat 2
   └───────────┘          └───────────┘          └───────────┘
         │                       │                       │
         │                       │                       │
         ▼                       ▼                       ▼
   ┌─────────────────────────────────────────────────────────┐
   │              HUMAN-IN-THE-LOOP  ← Pattern 6             │
   │    (Approval for refunds > $100, account changes)       │
   └─────────────────────────────────────────────────────────┘

This combined system:

  • Routes requests to the right specialist (Router)
  • Billing uses ReAct for quick lookups and simple actions
  • Support uses Reflection to ensure quality responses
  • Returns uses Plan-and-Execute for multi-step processes
  • All high-value actions go through human approval

Getting Started: Which Pattern First?

If you're new to agent development, here's a suggested learning path:

  1. Start with ReAct - It's the most common and easiest to understand
  2. Add Reflection - Improve output quality
  3. Learn Router - Scale to multiple capabilities
  4. Explore Plan-and-Execute - Handle complex workflows
  5. Master Multi-Agent - Build sophisticated systems
  6. Always consider Human-in-the-Loop - For safety and trust

Master Agent Architectures

Our Agentic AI program covers all these patterns with hands-on projects. Build real agents using production-ready architectures.

Explore Agentic AI Program

Related Articles