Lesson 11 Multi-Agent Systems
Theme:
Lesson 11 · Slide 1

Multi-Agent Systems

Go beyond single-agent loops by coordinating multiple specialised agents. This lesson covers orchestration, message passing, shared state, and error propagation across agent boundaries.

Learning Objectives

Lesson 11 · Slide 2

Why Multi-Agent?

A single agent with many tools works well for moderate tasks. But some problems benefit from splitting into distinct roles:

ConcernSingle AgentMulti-Agent
Tool overloadOne agent sees 20+ tools; confused selectionEach agent sees only its 3–5 relevant tools
Role claritySystem prompt must cover all rolesEach agent has a focused system prompt
ParallelismSequential tool callsFan-out sub-agents in parallel
Context budgetOne long contextEach agent has a fresh, short context

Rule of thumb: Start with a single agent. Only move to multi-agent when you observe tool confusion, context overflow, or you need genuinely different expertise in each role.

Lesson 11 · Slide 3

Orchestrator / Sub-Agent Pattern

The most common multi-agent architecture. One orchestrator breaks the task into sub-tasks and delegates each to a specialised sub-agent.

User Task
    |
    v
+-------------------+
|   ORCHESTRATOR    |   plans, delegates, merges
+-------------------+
    |         |
    v         v
+----------+ +--------+
| Researcher| | Writer |
+----------+ +--------+
    |            |
    v            v
  facts        draft
    \          /
     v        v
   Final Answer

The orchestrator is itself an LLM call with a system prompt that says: “You are a project manager. Break the task into sub-tasks and delegate each to the appropriate agent.”

Key benefit: Each sub-agent has a narrow system prompt, a small tool set, and a fresh context window — leading to better focus and fewer errors.

Lesson 11 · Slide 4

Message Passing

Agents communicate by passing structured messages. Keep the contract explicit so agents do not misinterpret each other.

from dataclasses import dataclass

@dataclass
class AgentMessage:
    sender: str          # "orchestrator", "researcher", "writer"
    receiver: str
    content: str         # natural-language payload
    metadata: dict       # structured data (e.g. citations, confidence)

# The orchestrator sends a request to the researcher
msg = AgentMessage(
    sender="orchestrator",
    receiver="researcher",
    content="Find 3 key facts about Python GIL.",
    metadata={"max_sources": 3}
)

Patterns

Lesson 11 · Slide 5

Shared State

Sometimes agents need to read and write a common data structure — a shared scratchpad, a knowledge base, or a document draft.

import time

class SharedState:
    """Thread-safe shared state for multi-agent systems."""
    def __init__(self):
        self.data = {}

    def write(self, key: str, value: str, author: str):
        self.data[key] = {
            "value": value,
            "author": author,
            "updated_at": time.time()
        }

    def read(self, key: str) -> str | None:
        entry = self.data.get(key)
        return entry["value"] if entry else None

    def read_all(self) -> dict:
        return {k: v["value"] for k, v in self.data.items()}

Best practice: Expose shared state through tool definitions so each agent interacts with it via the standard tool-use API rather than through ad-hoc side channels.

Lesson 11 · Slide 6

Error Propagation

In a multi-agent system, a sub-agent failure must bubble up to the orchestrator gracefully.

What Can Go Wrong

  • Sub-agent hits max_steps without finishing
  • Sub-agent returns low-confidence or empty results
  • API rate limit hit mid-pipeline
  • Sub-agent hallucinates data

Mitigations

  • Return status + partial result from every agent call
  • Orchestrator checks status before proceeding
  • Retry with a different prompt or model
  • Circuit-breaker: skip agent after N failures
@dataclass
class AgentResult:
    status: str      # "success", "partial", "error"
    content: str     # the actual output
    error: str = ""  # error message if status != "success"
    steps_used: int = 0
Lesson 11 · Slide 7

Demo

Live demonstration: an orchestrator coordinating a Researcher and a Writer agent.

Demo Steps

  1. Define an Agent class with name, system_prompt, and tools
  2. Create two agents: Researcher (search + read) and Writer (draft + revise)
  3. Build an orchestrate(task, agents) function that plans, delegates, and merges
  4. Run: “Write a technical summary of Python async programming”
  5. Show message flow: orchestrator → researcher → orchestrator → writer → final
  6. Trigger an error in the researcher and show graceful fallback
Notes: Use mock tools so the demo is deterministic. Print the full message passing trace so students can see how context flows between agents.
Lesson 11 · Slide 8

Lab & Homework

Lab Exercises

  1. Agent Class — build an Agent class and create Researcher + Writer agents
  2. Orchestrator — implement orchestrate(task, agents) that plans and delegates
  3. End-to-End Test — run a full pipeline and inspect message traces

Homework Tasks

  1. Add a Critic agent that reviews the Writer’s output
  2. Implement parallel fan-out with asyncio.gather
  3. Add error retry logic to the orchestrator
1 / ?