The Problem
One agent trying to do everything is an antipattern. The system prompt bloats, context windows fill up with unrelated tool definitions, and the model's reliability degrades. The fix isn't more instructions — it's the right architecture. The question isn't "can I do this in a single agent?" It's "should I?"
Multi-agent systems add coordination overhead: extra model calls, more tokens, more failure modes. They are only justified when you have concrete evidence they're needed. This lesson gives you the three triggers that justify the investment — and the anti-patterns that don't.
The three triggers for multi-agent
Every decision to go multi-agent should be driven by at least one of three concrete triggers.
Complexity trigger: The task exceeds one agent's reliable reasoning capacity. Rule of thumb: more than 15 distinct steps, or more than 20 tool definitions in a single agent, is a signal to consider splitting. When a context window fills with tool definitions for unrelated domains — email tools, database tools, web tools, calendar tools — the model struggles to choose correctly among them.
Parallelism trigger: Subtasks are independent and can run simultaneously. A research agent, a writer agent, and a reviewer agent can each start working as soon as they have their input — they don't need to wait for each other. In a single-agent system, these run sequentially. Multi-agent with parallel execution can cut wall-clock time by 3–5× on tasks with independent branches.
Specialization trigger: Different subtasks need fundamentally different system prompts, tool sets, or even model choices. A code review agent needs a precise, detail-oriented persona with code analysis tools. A customer support agent needs an empathetic tone and CRM tools. Cramming both into one agent produces a system that's mediocre at both. Each trigger can justify multi-agent independently — you don't need all three.
A team wants to build an agent that: (1) scrapes competitor prices, (2) analyzes market position, (3) drafts a pricing recommendation email. Should this be single-agent or multi-agent?
Apply the three triggers: Complexity? No — 3 steps is simple. Parallelism? No — steps 2 and 3 depend on step 1 sequentially. Specialization? Minor — all tasks can run with the same base model and similar tool sets. The coordination overhead of 3 agents would cost more (tokens, latency, complexity) than it saves. Single agent wins here.
The coordination overhead cost
Multi-agent is not free. Before splitting a system, estimate the cost of coordination.
Each supervisor call costs approximately 500–2,000 tokens per task dispatch. If the task itself takes 200 tokens of work, you're paying a 5–10× overhead for coordination. For simple tasks that complete quickly, this makes multi-agent economically irrational.
Rule of thumb:
- Single agent: fewer than 5 steps and fewer than 10 tools
- Multi-agent: complex workflows with clear specialization or demonstrated parallelism benefit
The cost isn't just tokens. Multi-agent systems have more failure modes (what happens when one agent fails?), harder debugging (which agent made the wrong decision?), and more code to maintain. These are real costs that must be offset by concrete benefits.
You're building a system that processes 100 customer reports daily. Each report needs: NLP classification (fast), structured data extraction (medium), and a human-readable summary (medium). What architecture makes sense?
Parallelism trigger applies: 100 reports are independent and can be processed concurrently. A single sequential agent processes 1 report at a time. A dispatcher + N worker agents processes N reports simultaneously. This is the batch processing multi-agent pattern — not about task complexity, but about parallel throughput.
Anti-patterns: when people go multi-agent wrongly
Knowing when not to use multi-agent is as important as knowing when to use it.
- Using multi-agent to avoid fixing a bad system prompt. If your single agent is performing poorly, the first fix is the prompt — not splitting into multiple agents. Multi-agent doesn't fix bad instructions; it replicates them across agents.
- Using multi-agent for sequential tasks with no parallelism opportunity. If step 2 must wait for step 1, and step 3 must wait for step 2, there is zero throughput benefit from multiple agents. You've added coordination overhead for nothing.
- Building multi-agent before understanding single-agent limits. Engineers who jump to multi-agent before shipping a single-agent version rarely know what problem they're solving. Build simple first. Add agents when you hit concrete limits.
- Using 5 agents when 2 would do. Each additional agent adds coordination paths exponentially. Keep your agent count minimal. The goal is "exactly as complex as the problem requires" — no more.
The principle: start with a single agent. Add agents when you hit a concrete, demonstrable limit — not when it seems like it might help.
Build
Build it
Before diving into the full estimator, try a minimal version of the trigger logic below — it applies the same three triggers to a handful of tasks and prints the recommendation.
# Minimal three-trigger decision check (no dependencies)
def recommend(steps, tools, parallel, domains):
triggers = []
if steps > 15 or tools > 20:
triggers.append("complexity")
if parallel and steps >= 3:
triggers.append("parallelism")
if len(set(domains)) >= 2:
triggers.append("specialization")
decision = "multi_agent" if triggers else "single_agent"
return decision, triggers
tasks = [
{"name": "Scrape + analyze + email", "steps": 5, "tools": 4, "parallel": False, "domains": ["scraping", "analysis"]},
{"name": "Knowledge base builder", "steps": 22, "tools": 25, "parallel": True, "domains": ["ingest", "extract", "index"]},
{"name": "Support + billing lookup", "steps": 3, "tools": 3, "parallel": False, "domains": ["support", "billing"]},
{"name": "Parallel research report", "steps": 5, "tools": 3, "parallel": True, "domains": ["research"]},
]
for t in tasks:
decision, triggers = recommend(t["steps"], t["tools"], t["parallel"], t["domains"])
print(f"{t['name']:<28} -> {decision:<13} triggers={triggers or ['none']}")
The AgentCapacityEstimator class below implements the three-trigger decision framework as code. It estimates coordination overhead cost for a given task and recommends single vs. multi-agent with explicit reasoning.
"""
lesson_04_01_when_multi_agent.py
AgentCapacityEstimator: decision framework for single vs. multi-agent architecture.
Implements the three triggers (complexity, parallelism, specialization) and estimates
coordination overhead cost.
"""
import os
import json
from dataclasses import dataclass, field
from typing import Optional
import anthropic
ANTHROPIC_KEY = os.getenv("ANTHROPIC_API_KEY")
@dataclass
class TaskSpec:
"""Specification of a task to evaluate for single vs. multi-agent."""
name: str
steps: list[str]
tools_required: list[str]
subtasks_independent: bool # Can any subtasks run in parallel?
domains: list[str] # e.g. ["customer_support", "code_review", "billing"]
estimated_steps: int
description: str
@dataclass
class ArchitectureDecision:
"""Result of the capacity estimator."""
recommendation: str # "single_agent" | "multi_agent"
triggers_fired: list[str] # Which of the three triggers applied
reasoning: str
estimated_coordination_tokens: int
estimated_task_tokens: int
coordination_overhead_ratio: float # coordination / task
agent_count_suggestion: Optional[int] = None
agent_roles: list[str] = field(default_factory=list)
class AgentCapacityEstimator:
"""
Applies the three-trigger framework to recommend single vs. multi-agent.
The three triggers:
1. Complexity: >15 steps or >20 tools
2. Parallelism: independent subtasks that can run simultaneously
3. Specialization: different domains requiring different system prompts/tools
"""
COMPLEXITY_STEP_THRESHOLD = 15
COMPLEXITY_TOOL_THRESHOLD = 20
COORDINATION_TOKENS_PER_DISPATCH = 1000 # average supervisor call cost
SIMPLE_TASK_TOKENS = 500 # baseline for a simple task
def evaluate(self, spec: TaskSpec) -> ArchitectureDecision:
triggers_fired = []
# --- Trigger 1: Complexity ---
complexity_trigger = (
spec.estimated_steps > self.COMPLEXITY_STEP_THRESHOLD
or len(spec.tools_required) > self.COMPLEXITY_TOOL_THRESHOLD
)
if complexity_trigger:
triggers_fired.append("complexity")
# --- Trigger 2: Parallelism ---
if spec.subtasks_independent and len(spec.steps) >= 3:
triggers_fired.append("parallelism")
# --- Trigger 3: Specialization ---
if len(set(spec.domains)) >= 2:
triggers_fired.append("specialization")
# --- Decision ---
go_multi = len(triggers_fired) > 0
# Estimate token costs
n_agents = max(len(spec.domains), 2) if go_multi else 1
coordination_tokens = n_agents * self.COORDINATION_TOKENS_PER_DISPATCH if go_multi else 0
task_tokens = spec.estimated_steps * self.SIMPLE_TASK_TOKENS
overhead_ratio = coordination_tokens / task_tokens if task_tokens > 0 else 0
# Override: if overhead ratio > 1.5 and only one trigger, single agent wins
if go_multi and overhead_ratio > 1.5 and len(triggers_fired) == 1:
go_multi = False
reasoning = (
f"Trigger '{triggers_fired[0]}' fired but coordination overhead ratio "
f"({overhead_ratio:.1f}x) exceeds benefit. Use single agent."
)
triggers_fired = []
elif go_multi:
reasoning = (
f"Triggers fired: {', '.join(triggers_fired)}. "
f"Multi-agent justified. Coordination overhead: {coordination_tokens} tokens "
f"({overhead_ratio:.1f}x task cost) — acceptable given benefits."
)
else:
reasoning = (
"No triggers fired. Task is within single-agent capacity. "
"Multi-agent would add coordination overhead without concrete benefit."
)
# Suggest agent roles
agent_roles = list(set(spec.domains)) if go_multi else []
return ArchitectureDecision(
recommendation="multi_agent" if go_multi else "single_agent",
triggers_fired=triggers_fired,
reasoning=reasoning,
estimated_coordination_tokens=coordination_tokens,
estimated_task_tokens=task_tokens,
coordination_overhead_ratio=overhead_ratio,
agent_count_suggestion=n_agents if go_multi else 1,
agent_roles=agent_roles,
)
def explain(self, decision: ArchitectureDecision) -> str:
lines = [
f"RECOMMENDATION: {decision.recommendation.upper().replace('_', ' ')}",
f"Triggers fired: {decision.triggers_fired or ['none']}",
f"Reasoning: {decision.reasoning}",
f"",
f"Cost estimate:",
f" Task tokens (estimated): {decision.estimated_task_tokens:,}",
f" Coordination overhead (tokens): {decision.estimated_coordination_tokens:,}",
f" Overhead ratio: {decision.coordination_overhead_ratio:.2f}x",
]
if decision.agent_count_suggestion and decision.agent_count_suggestion > 1:
lines += [
f"",
f"Suggested agent count: {decision.agent_count_suggestion}",
f"Suggested roles: {', '.join(decision.agent_roles)}",
]
return "\n".join(lines)
def demo_comparison():
"""Compare single vs. multi-agent on representative tasks."""
estimator = AgentCapacityEstimator()
tasks = [
TaskSpec(
name="Competitor Price Scraper + Report",
description="Scrape 3 competitor sites, analyze prices, draft email",
steps=["scrape_site_1", "scrape_site_2", "scrape_site_3", "analyze", "draft_email"],
tools_required=["http_get", "html_parser", "llm_analyze", "email_draft"],
subtasks_independent=False, # analysis depends on scraping
domains=["web_scraping", "analysis"],
estimated_steps=5,
),
TaskSpec(
name="Enterprise Knowledge Base Builder",
description="Crawl 500 docs, extract entities, build graph, generate summaries, index",
steps=[f"step_{i}" for i in range(22)],
tools_required=[f"tool_{i}" for i in range(25)],
subtasks_independent=True,
domains=["ingestion", "extraction", "graph_construction", "summarization", "indexing"],
estimated_steps=22,
),
TaskSpec(
name="Customer Support + Billing Lookup",
description="Handle customer question that requires support + billing context",
steps=["classify_intent", "lookup_billing", "draft_response"],
tools_required=["billing_api", "crm_api", "response_generator"],
subtasks_independent=False,
domains=["customer_support", "billing"],
estimated_steps=3,
),
TaskSpec(
name="Parallel Research Report",
description="Research 4 independent topics simultaneously, merge into one report",
steps=["research_A", "research_B", "research_C", "research_D", "merge"],
tools_required=["web_search", "doc_reader", "summarizer"],
subtasks_independent=True, # research branches are independent
domains=["research", "synthesis"],
estimated_steps=5,
),
]
print("=" * 60)
print("AGENT ARCHITECTURE DECISION FRAMEWORK")
print("=" * 60)
for task in tasks:
print(f"\n--- Task: {task.name} ---")
print(f"Description: {task.description}")
decision = estimator.evaluate(task)
print(estimator.explain(decision))
# LLM-assisted analysis: ask Claude to evaluate a task description
if ANTHROPIC_KEY:
print("\n" + "=" * 60)
print("LLM-ASSISTED EVALUATION")
print("=" * 60)
client = anthropic.Anthropic(api_key=ANTHROPIC_KEY)
task_description = """
We need to build an AI system that:
1. Monitors 50 Slack channels for mentions of our product
2. Classifies each mention as positive/negative/neutral
3. For negative mentions, generates a draft response
4. For positive mentions, flags for the marketing team
5. Sends a daily digest email with statistics
"""
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=600,
messages=[{
"role": "user",
"content": (
f"Evaluate this task for single vs. multi-agent architecture. "
f"Apply the three triggers: complexity, parallelism, specialization. "
f"Give a clear recommendation with reasoning.\n\nTask:\n{task_description}"
)
}]
)
print(response.content[0].text)
if __name__ == "__main__":
demo_comparison()