Skip to main content
AI Agent Tutorial: Concepts to Architecture

Agent Architecture Patterns

A survey of the recurring architecture patterns behind production agents -- single-agent loops, planner-executor splits, orchestrator-worker teams, and reflection -- and when each one earns its added complexity.

Intermediate16 minBy ToolDix Editorial

Learning objectives

  • Compare four recurring agent architecture patterns and their tradeoffs
  • Match a task's shape to the simplest pattern that can handle it
  • Explain why Anthropic's own guidance favors composable patterns over one large framework

ToolDix original visual

AI Agent Tutorial practice loop
1

Frame

Name the outcome and constraints.

2

Build

Try one bounded workflow.

3

Review

Keep evidence, revise, and share.

Start simple, add structure only when a task demands it

Anthropic's own engineering guidance on agents is blunt about this: the most successful implementations use simple, composable patterns rather than complex frameworks, and added structure should be justified by a specific task need, not adopted by default. The four patterns below go roughly in order of increasing structure and cost.

Four recurring patterns

ToolDix original diagram
Four patterns, four shapes
Single-agent loop
Model ↻
Planner-executor
Planner
Step 1 -> 2 -> 3
Orchestrator-workers
Lead agent
W1W2W3
Reflection
Draft
Self-critique
PatternShapeGood fit forCost
Single-agent loopOne model, one tool set, one loop (the pattern from the previous lesson)Most tasks: research, coding help, single-workflow automationLowest -- easiest to debug and reason about
Planner-executorOne call produces a plan (a list of steps); a second loop executes each stepTasks with a knowable, mostly-fixed sequence of sub-tasksModerate -- plan can go stale if execution reveals new information
Orchestrator-workersA lead agent delegates sub-tasks to specialized worker agents and combines their resultsBroad tasks that split cleanly into independent sub-tasks (e.g. research across several sources)Higher -- more moving parts, more coordination overhead and token cost
Reflection / self-critiqueThe agent (or a second call) reviews its own output against criteria before finishingTasks where correctness is hard to verify externally but the model can spot its own errors on a second passHigher -- doubles a chunk of the token cost for a quality gain

The orchestrator-workers pattern shows up often in "deep research" style products because broad research questions decompose naturally: a lead agent can spin off several worker agents to each investigate one sub-question in parallel, then synthesize their findings into a single answer. The tradeoff is real, though -- running several agents means several times the token usage of a single-agent loop, so this pattern is usually reserved for tasks where breadth genuinely matters more than cost.

Real-world case study: a market research firm's pattern choice

A B2B market research startup was building an agent to answer questions like "What are the top 5 vendors in the data integration space, and how do their pricing tiers compare?" Early attempts used a single-agent loop: retrieve vendor list, then loop through each vendor to look up pricing, features, and reviews. This worked but was slow (serial) and expensive (the model had to juggle all five vendors' info in context).

They switched to an orchestrator-worker pattern: the orchestrator received the question, produced a list of five vendors to research, spawned five parallel workers (each researching one vendor), and combined their findings. This was 3-4x faster in wall-clock time.

But there was a cost: token usage nearly doubled. Each worker was a separate model call, and they sometimes duplicated searches or contradicted each other on facts. The startup ended up using a hybrid: workers for independent research subtasks, but a single-agent loop within each worker for focused investigation of one vendor. The result was a sensible tradeoff: faster than serial, cheaper than pure orchestrator-worker.

Reflection is a targeted tool, not a default

Adding a reflection step -- having the model (or a separate call) check its own draft output against the original goal before returning it -- can catch errors a single pass misses, particularly for tasks with checkable criteria (does this code compile? does this summary cover every required section?). It is not free: it roughly doubles the cost of the step it wraps, so it earns its place only where verification meaningfully improves the result.

Pattern effectiveness comparison table

The table below shows illustrative estimates of when each pattern is cost-effective, based on hypothetical production data. Note that "cost" includes both token usage and wall-clock latency; "correctness gain" is the percentage improvement in output quality vs. a baseline single-agent loop.

| Pattern | Token multiplier | Latency vs. single loop | Best for | Correctness gain | Worth it if? | |---------|---------|---------|---------|---------|---------| | Single-agent loop | 1x | 1x (baseline) | Simple, sequential tasks | N/A (baseline) | Almost always | | Planner-executor | 1.4x | 1.2x | Known sequence of steps | +5–10% | Breakeven if sequence is often wrong | | Orchestrator-workers (3 workers) | 3.0x | 0.5x (parallel) | Independent subtasks | +15–25% (breadth) | Only if breadth significantly matters | | Reflection | 2.0x | 1.0x (sequential) | High-stakes single task | +20–30% (correctness) | If task is verification-heavy |

For example: a document summarization task might see only 2% improvement with reflection (most summaries are already good), making it not worth the 2x cost. But a code-writing task might see 25% improvement (more bugs caught), making reflection worthwhile even at 2x cost.

Matching a task to a pattern, worked through

Take three different requests and notice how their shape, not their difficulty, points to a different pattern. "Summarize this document" has one clear sequence -- read, condense, return -- so a single-agent loop handles it with no added structure. "Migrate this codebase's error handling to the new logging library" has a knowable, mostly-fixed sequence of files to touch, which is exactly what a planner-executor split is for: produce the file list once, then execute file-by-file, revisiting the plan only if execution turns up something the plan missed. "Research how three competitors price their enterprise tier" splits cleanly into three independent lookups that don't depend on each other's results, which is the specific shape orchestrator-workers is built for -- three workers running in parallel, each investigating one competitor, combined by a lead agent at the end.

A fourth request, "write a function that passes these test cases," has a built-in verification signal -- the tests either pass or they don't -- which is exactly when reflection earns its extra cost: the model can check its own draft against the tests before returning, catching a wrong answer before the user ever sees it, rather than only after.

Patterns compose

These four patterns aren't mutually exclusive tiers you pick one from -- production systems often nest them. An orchestrator-workers setup might have each individual worker running a plain single-agent loop internally, and the lead agent might apply a reflection pass to the combined result before returning it. The lesson from Anthropic's guidance isn't "always use pattern one," it's "add exactly the structure a specific, observed task need justifies, and no more" -- which sometimes means composing two patterns, and often means not needing to.

Planner-executor in detail

The planner-executor pattern splits responsibility: one model call produces a plan (a sequence of steps), then a second loop executes those steps. Example: "migrate this codebase to Python 3.12."

Planner output:

{
  "plan": [
    "Step 1: Audit all imports and dependencies for Python 3.12 compatibility",
    "Step 2: Update setup.py and pyproject.toml to require Python >= 3.12",
    "Step 3: Migrate type hints to use modern syntax (PEP 585)",
    "Step 4: Test entire test suite",
    "Step 5: Update CI/CD configuration"
  ]
}

Then the executor loop takes each step as a goal: the model is given step 1, generates a plan to audit imports, executes it (reads files, checks versions), observes the results, and reports back. The orchestrator then hands step 2 to a fresh iteration, and so on.

Advantage: the plan is explicit and traceable. You can see exactly what the model intended to do.

Disadvantage: plans can go stale. If step 1 reveals something the planner didn't account for, the executor is still bound to the original plan and might waste effort on steps that are now unnecessary or in the wrong order.

Orchestrator-worker in detail

The orchestrator-worker pattern is designed for tasks that split cleanly into independent subtasks. Example: "research three competitors' pricing strategies and compare them."

The orchestrator might reason: "I need information about Competitor A, B, and C. These are independent lookups; I can send them to three worker agents in parallel."

It spawns:

  • Worker 1: Research Competitor A's pricing tier structure
  • Worker 2: Research Competitor B's pricing tier structure
  • Worker 3: Research Competitor C's pricing tier structure

Each worker independently searches, reads documentation, and summarizes. The orchestrator collects their three summaries, then produces a final comparison.

Advantage: true parallelism; three workers operating at the same time complete much faster than one agent doing three sequential steps.

Disadvantage: each worker is a separate model call (or multiple calls), so total token cost is roughly 3x higher than a single-agent approach. Workers can also duplicate effort if they don't coordinate, or contradict each other if they find different information.

Orchestrator-worker implementation patterns

import asyncio

async def orchestrator_worker_agent(task: str, subtasks: list[str]):
    """
    Spawn parallel workers, collect results, synthesize answer.
    """
    # Each worker is an independent agent call
    async def worker_agent(subtask: str) -> dict:
        result = await call_model(
            system_prompt="You are a research worker. Be concise and factual.",
            user_prompt=subtask
        )
        return {"subtask": subtask, "result": result}

    # Launch all workers in parallel
    worker_results = await asyncio.gather(
        *[worker_agent(subtask) for subtask in subtasks]
    )

    # Synthesis: combine all results
    synthesis_prompt = f"""
    You are given the results of {len(subtasks)} parallel research tasks:

    {chr(10).join([f"- {r['subtask']}: {r['result']}" for r in worker_results])}

    Synthesize these results into a single, coherent answer to the original question: {task}
    """

    final_answer = await call_model(
        system_prompt="You are a synthesis expert. Integrate all findings.",
        user_prompt=synthesis_prompt
    )

    return final_answer

This approach ensures workers don't block each other and can search in parallel, but requires careful synthesis to avoid contradictions.

Reflection in code

Reflection means having the model verify its own work. In its simplest form:

# First pass: generate the answer
first_draft = call_model(context=goal_and_context)

# Second pass: check the draft
reflection = call_model(
    context=[
        goal_and_context,
        f"Your draft answer was: {first_draft}",
        "Does this answer fully address the original goal? "
        "Are there any errors, missing information, or logical inconsistencies?"
    ]
)

# If reflection says it's good, return first_draft
# If reflection finds issues, generate a revised answer and re-check
if reflection.is_acceptable:
    return first_draft
else:
    revised = call_model(context=[goal_and_context, reflection.issues])
    return revised

This pattern is particularly valuable for tasks with objective correctness criteria: does a code snippet compile? Does it pass the test cases? Does a summary cover all required points? The model can often catch its own mistakes on a second pass.

Real-world example: choosing the right pattern for a research task

Say you're building an agent to "analyze a customer's complaint and recommend which team should handle it (support, billing, product), then draft a response." Here's the decision process:

  1. Is this a single, linear sequence? Somewhat -- read complaint, classify, draft response. But drafting a good response might require asking for clarification about the complaint, so there's branching. Single-agent loop seems reasonable.

  2. Would a planner help? Not much. The steps are simple (read, classify, draft) and can't be sequenced in advance -- you can't classify before reading, and the draft depends on the classification, so the order is fixed.

  3. Do subtasks split independently? No. All steps depend on the same complaint.

  4. Would reflection help? Yes. A second pass where the model checks "does my response actually address the customer's concern?" or "did I recommend the right team?" would catch errors. Add a reflection step to the single-agent loop.

Result: Single-agent loop + reflection. One agent reads and classifies, drafts a response, then a second call reflects on it before returning.

Planner-executor in practice: handling plan invalidation

Plans often go stale. If step 1 (audit dependencies) reveals that a library was deprecated, the remaining steps might become redundant or need reordering. A production planner-executor should detect this:

async def planner_executor_with_plan_update(task: str, max_plan_replans: int = 2):
    """
    Planner-executor that can re-plan if execution reveals new information.
    """
    plan = await generate_plan(task)
    replans_left = max_plan_replans
    current_step_index = 0

    while current_step_index < len(plan.steps):
        step = plan.steps[current_step_index]

        # Execute the current step
        result = await execute_step(step)

        # Check if the step's assumption is still valid
        if result.assumption_violated:
            # Re-plan from this point onward
            if replans_left > 0:
                print(f"Plan invalidated: {result.reason}. Replanning...")
                remaining_task = f"{task}. New context: {result.new_context}"
                plan = await generate_plan(remaining_task)
                current_step_index = 0
                replans_left -= 1
            else:
                # Out of replans; return partial result
                return {"status": "incomplete", "partial_result": result}
        else:
            # Step succeeded; move to next
            current_step_index += 1

    return {"status": "complete", "final_result": result}

This balances the planner-executor's benefit (explicit plan) with its weakness (plans can go stale) by allowing controlled replanning.

Composition: mixing patterns

These patterns are not mutually exclusive. A sophisticated agent might:

  • Use a planner to outline steps (planner-executor)
  • Delegate independent research tasks to workers (orchestrator-worker)
  • Have each worker internally run its own single-agent loop
  • Apply reflection to the final synthesized answer

The principle remains: add only the structure a specific task need justifies. Don't reach for orchestrator-worker just because it sounds impressive; reach for it because the task genuinely has independent subtasks that benefit from parallel execution.

Edge case: when composition breaks down

Mixing patterns can create hidden complexity. Consider a planner-executor where the executor is an orchestrator-worker setup:

  1. Planner produces 5 steps
  2. For each step, an orchestrator spawns 3 workers in parallel
  3. Total: 15 model calls per agent run, vs. 2–3 for a simple loop

If only step 2 actually needs parallelism (the other 4 steps are sequential), you've added 12 unnecessary model calls. The fix: profile and measure. Only add structure where you've observed a bottleneck or inefficiency. A common mistake is composing patterns "just in case," which wastes cost without improving latency or quality.

Cost breakdown: why orchestrator-worker is expensive

Here's a concrete example showing why multi-agent patterns cost more. Assume a 5-step research task on three competitors:

Single-agent loop approach:

  • 1 orchestrator call to reason + search for all 3 competitors + synthesize: 5 turns × 1,000 tokens = 5,000 tokens total
  • Latency: 5–10 seconds (sequential turns)

Orchestrator-worker approach:

  • 1 orchestrator call to decompose: 500 tokens
  • 3 worker calls, each doing research on 1 competitor: 3 × 2,000 tokens = 6,000 tokens
  • 1 synthesis call: 1,500 tokens
  • Total: 8,000 tokens (60% more expensive)
  • Latency: 5–10 seconds (parallel workers, but each worker also has internal loops)

The orchestrator-worker saves wall-clock time if workers can run in parallel while the orchestrator is waiting. But total token cost is higher, and you only save latency if you have genuine I/O parallelism (e.g., three workers hitting three different APIs at once). If the bottleneck is model thinking time, not I/O, orchestrator-worker doesn't help and just costs more.

Common mistake

Choosing an orchestrator-worker or multi-agent pattern because it sounds more sophisticated, when the task is really a single, sequential job that a single-agent loop handles fine. Multi-agent coordination adds real failure surface -- workers can duplicate effort, contradict each other, or wait on each other unnecessarily -- and that complexity should be paid for by a task that actually benefits from parallel, independent sub-work. Start simple. Add complexity only when you have data showing a simpler pattern isn't working.

Sources and license context

These references informed the lesson. ToolDix adds its own explanation, workflow, and practice rather than reproducing source material. Every link below leaves ToolDix and opens the publisher's own site in a new tab.

Keep going

Read these next on ToolDix.

Original lessons that build on what you just read.