Loading lesson...
Loading lesson...
Each topology buys one strength and creates one risk, so the six contract points beneath, owner, handoff schema, shared state, allowed tools, delegation depth and escalation, are settled before the second agent rather than after the first tangled failure.
More agents do more work and create more failure modes. Pick the topology before adding the second agent. OpenAI Agents SDK handoffs and OWASP Agentic AI name the six contract points.
In August 2023, Microsoft Research published AutoGen, a framework for building multi-agent systems where agents converse with each other to solve complex tasks. The paper demonstrated that dividing work across specialised agents with defined roles produced better results on coding, mathematics, and planning tasks than a single agent with access to all capabilities.
A year later, in October 2024, OpenAI released Swarm, an experimental lightweight framework making the same point in code: agents should hand off to other agents when a task requires a different speciality, and those handoffs should be structured, not conversational. Both projects converged on the same insight: the (a planner that delegates to specialists and synthesises results) outperforms a single agent doing everything.
The coordination overhead is real. Agent-to-agent communication costs tokens, adds latency, and introduces failure modes that do not exist in single-agent systems. Multi-agent architecture is justified only when a single agent has demonstrably hit a capability limit.
What does a supervisor actually do that a single agent with more tools cannot? And what goes wrong when agents start passing unstructured natural language to each other?
A single agent has limits. When a task requires different capabilities - research, analysis, writing, validation - multiple specialised agents can collaborate. This module covers the Supervisor and Swarm patterns for coordinating agent teams.
Next: Why Multiple Agents: the Case for and Against.
A single agent with twenty tools suffers from confusion: the model has too many choices and picks poorly. Context windows grow large and expensive as task history accumulates. A single failure stops the entire task. Multi-agent systems address all three: each agent has a narrow tool set, its context stays focused, and failures are contained to the responsible sub-agent.
The trade-off is coordination overhead. Every agent-to-agent costs tokens and adds latency. Worker errors can propagate silently to the supervisor, which may incorporate incorrect data into its synthesis. Start with a single agent. Move to multi-agent architecture only when you have measured a specific failure that coordination solves.
Each column starts from a number measured on the single agent and spends the cheaper fix before the split, and the topology on the bottom row is only what remains once that fix has already failed.
The topology answers a measurement, not a preference. Each limit has a cheaper single-agent fix that is spent first, and the second agent is what remains when that fix has already failed.
Next: Structured Handoffs: the Foundation of Reliable Coordination.
Agents communicate via text. Unstructured natural language handoffs ("Go research the market for me") create ambiguity: the receiving agent does not know the expected output format, what context to use, or what to do if it cannot complete the task. Structured handoffs, expressed as JSON objects, eliminate that ambiguity.
A well-formed handoff includes four fields: the specific sub-task, the context the receiving agent needs (background information it would not otherwise have), the expected output format (so the supervisor can parse the result without natural language interpretation), and a fallback instruction (what to return if the task cannot be completed).
from dataclasses import dataclass
import json
@dataclass
class AgentHandoff:
task_id: str
assigned_to: str
task_description: str
context: dict
expected_output_format: str
fallback_instruction: str
def format_handoff(handoff: AgentHandoff) -> str:
return json.dumps({
"task_id": handoff.task_id,
"assigned_to": handoff.assigned_to,
"task": handoff.task_description,
"context": handoff.context,
"expected_output": handoff.expected_output_format,
"fallback": handoff.fallback_instruction
}, indent=2)Next: The Supervisor Pattern in Practice.
The supervisor pattern proceeds through three phases:
Use a capable model (such as Claude Opus) for the supervisor: it handles planning and synthesis, where reasoning quality matters most. Use cheaper, faster models (such as Claude Haiku) for workers doing routine extraction, classification, or research. This asymmetry can reduce costs by five to ten times without meaningful quality loss.
import anthropic, json
client = anthropic.Anthropic()
def run_specialist(agent_name: str, system_prompt: str, task: str) -> str:
"""Run a specialist agent and return its output as text."""
response = client.messages.create(
model="claude-haiku-4-5-20251001", # cheaper model for workers
max_tokens=2048,
system=system_prompt,
messages=[{"role": "user", "content": task}]
)
text_blocks = [b for b in response.content if hasattr(b, "text")]
return text_blocks[0].text if text_blocks else "No output produced."
def run_supervisor(user_request: str) -> str:
# Phase 1: plan
plan_response = client.messages.create(
model="claude-opus-4-6", # capable model for planning
max_tokens=1024,
system="Break the request into research_task, finance_task, tech_task. Return JSON.",
messages=[{"role": "user", "content": user_request}]
)
tasks = json.loads(plan_response.content[0].text)
# Phase 2: run workers
research = run_specialist("Research", RESEARCH_PROMPT, tasks["research_task"])
finance = run_specialist("Finance", FINANCE_PROMPT, tasks["finance_task"])
tech = run_specialist("Tech", TECH_PROMPT, tasks["tech_task"])
# Phase 3: synthesise
synthesis = client.messages.create(
model="claude-opus-4-6",
max_tokens=3000,
system="Synthesise specialist reports into a coherent recommendation.",
messages=[{"role": "user", "content": f"Research: {research}
Finance: {finance}
Tech: {tech}"}]
)
return synthesis.content[0].textCommon misconception
“All agents in a multi-agent system should use the same model.”
Worker agents doing routine extraction or classification do not need the most capable model. Use Claude Haiku or GPT-4o mini for workers doing defined, bounded tasks. Reserve Claude Opus or GPT-4o for supervisors doing planning and synthesis. This can reduce costs by 5-10x without meaningful quality loss on worker tasks.
Next: Parallel Worker Execution.
Running workers sequentially when their tasks are independent wastes time. If the research agent, finance agent, and tech agent each take 10 seconds and their tasks do not depend on each other, sequential execution takes 30 seconds. Parallel execution takes 10 seconds plus a small coordination overhead.
Python's asyncio.gather() runs independent coroutines concurrently. Replace the sequential worker calls in the supervisor with async versions and gather them in a single call.
import asyncio
async def run_specialist_async(agent_name: str, system_prompt: str, task: str) -> tuple[str, str]:
aclient = anthropic.AsyncAnthropic()
response = await aclient.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=2048,
system=system_prompt,
messages=[{"role": "user", "content": task}]
)
text = response.content[0].text if response.content else ""
return agent_name, text
async def run_parallel_workers(tasks: dict) -> dict:
coroutines = [
run_specialist_async("Research", RESEARCH_PROMPT, tasks["research_task"]),
run_specialist_async("Finance", FINANCE_PROMPT, tasks["finance_task"]),
run_specialist_async("Tech", TECH_PROMPT, tasks["tech_task"])
]
results = await asyncio.gather(*coroutines)
return {name: result for name, result in results}Next: Conflict Resolution Between Workers.
When workers produce conflicting outputs (the research agent finds a large market; the finance agent finds it unprofitable), the supervisor must reconcile the disagreement rather than arbitrarily pick one. A dedicated judge agent pattern passes both claims and the relevant context to a separate model call, which evaluates the reasoning and provides a conclusion.
Worker outputs must include structured success or error indicators. A worker that returns the string "I completed the task" gives the supervisor nothing to parse. A worker that returns a JSON object with status, result, and optionally confidence allows the supervisor to handle partial results and errors programmatically.
You are building a multi-agent content pipeline: a research agent gathers information, a writer agent drafts the article, and an editor agent reviews it. The editor frequently contradicts the writer, and the supervisor cannot determine which to trust. What pattern should you add?
Your research, finance, and tech worker agents each take 15 seconds and their tasks are independent. What is the most efficient execution strategy?
A worker agent returns the string 'I completed the task successfully.' What is wrong with this output format?
You discover that one of your worker agents is delegating subtasks back to the supervisor when it encounters difficulty. What failure mode is this, and how do you prevent it?
Wu, Q. et al. (2023). AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation
arXiv:2308.08155, Section 3: Multi-agent conversations
Research foundation for the supervisor-worker pattern. Demonstrates that structured inter-agent communication outperforms single-agent approaches on complex tasks.
Anthropic, 'Building Effective Agents' (2024)
anthropic.com/engineering/building-effective-agents, Orchestrator-workers
Anthropic's official guidance on orchestrator-worker patterns and when to escalate from a single agent to a multi-agent design.
NIST AI Risk Management Framework (AI RMF 1.0), January 2023
Govern 1.2: Roles and responsibilities in AI systems
Cited in Section 12.1 for the principle of least privilege in multi-agent systems. Defines accountability boundaries relevant to supervisor-worker architectures.
OWASP Top 10 for Large Language Model Applications 2025
LLM09: Misinformation (worker hallucination propagation)
Background for the risk, raised in Section 12.1, of worker hallucinations propagating silently to the supervisor and being presented as verified conclusions.
docs.python.org/3/library/asyncio-task.html: asyncio.gather()
Reference for parallel coroutine execution used in Section 12.4. Defines gather() semantics for concurrent worker execution.
Module 16 of 40 · Practical Building