Loading lesson...
Loading lesson...

Research landmark · October 2023 and beyond
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 supervisor pattern (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.
With the learning outcomes established, this module begins by examining why multiple agents: the case for and against in depth.
A single agent with twenty tools suffers from tool 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 handoff 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.
“Agentic networks introduce new attack surfaces through agent-to-agent communication, making it critical that each agent operates with the minimum permissions necessary to complete its assigned task.”
NIST AI Risk Management Framework 1.0 - Govern 1.2: Roles and responsibilities in AI systems
In a multi-agent system, a compromised or hallucinating worker agent can pass bad data to the supervisor, which then presents it as a verified conclusion. Least-privilege design (each agent sees only what it needs) limits the blast radius of a worker failure.
With an understanding of why multiple agents: the case for and against in place, the discussion can now turn to structured handoffs: the foundation of reliable coordination, which builds directly on these foundations.
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)“Multi-agent conversation enables long and complex task solving through collaboration, division of labour, and specialisation. The key design challenge is defining clear interfaces between agents.”
Wu et al., 2023 - AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation. arXiv:2308.08155
The AutoGen paper found that clear interfaces between agents (what goes in, what comes out) mattered more than the capabilities of individual agents. Ambiguous interfaces led to cascading failures. Precise interfaces led to composable, testable systems.
With an understanding of structured handoffs: the foundation of reliable coordination in place, the discussion can now turn to the supervisor pattern in practice, which builds directly on these foundations.
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.
With an understanding of the supervisor pattern in practice in place, the discussion can now turn to parallel worker execution, which builds directly on these foundations.
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}With an understanding of parallel worker execution in place, the discussion can now turn to conflict resolution between workers, which builds directly on these foundations.
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: Multi-agent patterns
docs.anthropic.com/en/docs/build-with-claude/agents
Anthropic's official guidance on orchestrator-subagent patterns, trust boundaries, and when to escalate from single to 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)
Cited in Section 12.5 for the risk 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 12 of 25 · Practical Building