
Multi Agent Patterns
- 153 installs
- 31 repo stars
- Updated August 2, 2026
- shipshitdev/library
Architect multi-agent workflows with clear roles, handoffs, memory, and tool boundaries when building autonomous coding or ops agents.
About
multi-agent-patterns teaches proven orchestration designs for LLM agent systems including role separation, delegation, memory, and tool governance. It helps builders implement reliable multi-agent pipelines with clear boundaries, retries, and observability instead of fragile single-prompt monoliths.
- Supervisor-worker and planner-executor patterns
- Handoff protocols and shared memory design
- Tool scoping and failure isolation
- Concurrency, retries, and human-in-the-loop gates
- Evaluation hooks for agent reliability
Multi Agent Patterns by the numbers
- 153 all-time installs (skills.sh)
- +3 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #3,306 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/shipshitdev/library --skill multi-agent-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 153 |
|---|---|
| repo stars | ★ 31 |
| Last updated | August 2, 2026 |
| Repository | shipshitdev/library ↗ |
What it does
Architect multi-agent workflows with clear roles, handoffs, memory, and tool boundaries when building autonomous coding or ops agents.
Files
Multi-Agent Architecture Patterns
Multi-agent architectures distribute work across multiple language model instances, each with its own context window. When designed well, this distribution enables capabilities beyond single-agent limits. When designed poorly, it introduces coordination overhead that negates benefits. The critical insight is that sub-agents exist primarily to isolate context, not to anthropomorphize role division.
When to Activate
Activate this skill when:
- Single-agent context limits constrain task complexity
- Tasks decompose naturally into parallel subtasks
- Different subtasks require different tool sets or system prompts
- Building systems that must handle multiple domains simultaneously
- Scaling agent capabilities beyond single-context limits
- Designing production agent systems with multiple specialized components
Do not activate this skill for adjacent work owned by other skills:
- Designing the tools each agent exposes:
tool-design.
Core Concepts
Use multi-agent patterns when a single agent's context window cannot hold all task-relevant information. Context isolation is the primary benefit — each agent operates in a clean context without accumulated noise from other subtasks, preventing the telephone game problem where information degrades through repeated summarization.
Choose among three dominant patterns based on coordination needs, not organizational metaphor:
- Supervisor/orchestrator — Use for centralized control when tasks have clear decomposition and human oversight matters. A single coordinator delegates to specialists and synthesizes results.
- Peer-to-peer/swarm — Use for flexible exploration when rigid planning is counterproductive. Any agent can transfer control to any other through explicit handoff mechanisms.
- Hierarchical — Use for large-scale projects with layered abstraction (strategy, planning, execution). Each layer operates at a different level of detail with its own context structure.
Design every multi-agent system around explicit coordination protocols, consensus mechanisms that resist sycophancy, and failure handling that prevents error propagation cascades.
Detailed Topics
Why Multi-Agent Architectures
The Context Bottleneck Reach for multi-agent architectures when a single agent's context fills with accumulated history, retrieved documents, and tool outputs to the point where performance degrades. Recognize three degradation signals: the lost-in-middle effect (attention weakens for mid-context content), attention scarcity (too many competing items), and context poisoning (irrelevant content displaces useful content).
Partition work across multiple context windows so each agent operates in a clean context focused on its subtask. Aggregate results at a coordination layer without any single context bearing the full burden.
The Token Economics Reality Budget for substantially higher token costs. Production data shows multi-agent systems can cost far more tokens than single-agent chat (claim-multi-agent-token-multiplier):
| Architecture | Token Multiplier | Use Case |
|---|---|---|
| Single agent chat | Baseline | Simple queries |
| Single agent with tools | Higher than baseline | Tool-using tasks |
| Multi-agent system | Much higher than baseline | Complex research/coordination |
Browsing-agent evaluation research suggests token usage, tool calls, and model choice dominate performance variance (claim-evaluation-browsecomp-variance). This supports measuring multi-agent setups against single-agent baselines instead of assuming extra agents help.
Prioritize model selection alongside architecture design — upgrading to better models often provides larger performance gains than doubling token budgets. BrowseComp data shows that model quality improvements frequently outperform raw token increases. Treat model selection and multi-agent architecture as complementary strategies.
The Parallelization Argument Assign parallelizable subtasks to dedicated agents with fresh contexts rather than processing them sequentially in a single agent. A research task requiring searches across multiple independent sources, analysis of different documents, or comparison of competing approaches benefits from parallel execution. Total real-world time approaches the duration of the longest subtask rather than the sum of all subtasks.
The Specialization Argument Configure each agent with only the system prompt, tools, and context it needs for its specific subtask. A general-purpose agent must carry all possible configurations in context, diluting attention. Specialized agents carry only what they need, operating with lean context optimized for their domain. Route from a coordinator to specialized agents to achieve specialization without combinatorial explosion.
Architectural Patterns
Pattern 1: Supervisor/Orchestrator Deploy a central agent that maintains global state and trajectory, decomposes user objectives into subtasks, and routes to appropriate workers.
User Query -> Supervisor -> [Specialist, Specialist, Specialist] -> Aggregation -> Final OutputChoose this pattern when: tasks have clear decomposition, coordination across domains is needed, or human oversight is important.
Expect these trade-offs: strict workflow control and easier human-in-the-loop interventions, but the supervisor context becomes a bottleneck, supervisor failures cascade to all workers, and the "telephone game" problem emerges where supervisors paraphrase sub-agent responses incorrectly.
The Telephone Game Problem and Solution Anticipate that supervisor architectures initially perform approximately 50% worse than optimized versions due to the telephone game problem (LangGraph benchmarks). Supervisors paraphrase sub-agent responses, losing fidelity with each pass.
Fix this by implementing a forward_message tool that allows sub-agents to pass responses directly to users:
def forward_message(message: str, to_user: bool = True):
"""
Forward sub-agent response directly to user without supervisor synthesis.
Use when:
- Sub-agent response is final and complete
- Supervisor synthesis would lose important details
- Response format must be preserved exactly
"""
if to_user:
return {"type": "direct_response", "content": message}
return {"type": "supervisor_input", "content": message}Prefer swarm architectures over supervisors when sub-agents can respond directly to users, as this eliminates translation errors entirely.
Pattern 2: Peer-to-Peer/Swarm Remove central control and allow agents to communicate directly based on predefined protocols. Any agent transfers control to any other through explicit handoff mechanisms.
def transfer_to_agent_b():
return agent_b # Handoff via function return
agent_a = Agent(
name="Agent A",
functions=[transfer_to_agent_b]
)Choose this pattern when: tasks require flexible exploration, rigid planning is counterproductive, or requirements emerge dynamically and defy upfront decomposition.
Expect these trade-offs: no single point of failure and effective breadth-first scaling, but coordination complexity increases with agent count, divergence risk rises without a central state keeper, and robust convergence constraints become essential.
Define explicit handoff protocols with state passing. Ensure agents communicate their context needs to receiving agents.
Pattern 3: Hierarchical Organize agents into layers of abstraction: strategy (goal definition), planning (task decomposition), and execution (atomic tasks).
Strategy Layer (Goal Definition) -> Planning Layer (Task Decomposition) -> Execution Layer (Atomic Tasks)Choose this pattern when: projects have clear hierarchical structure, workflows involve management layers, or tasks require both high-level planning and detailed execution.
Expect these trade-offs: clear separation of concerns and support for different context structures at different levels, but coordination overhead between layers, potential strategy-execution misalignment, and complex error propagation paths.
Context Isolation as Design Principle
Treat context isolation as the primary purpose of multi-agent architectures. Each sub-agent should operate in a clean context window focused on its subtask without carrying accumulated context from other subtasks.
Isolation Mechanisms Select the right isolation mechanism for each subtask:
- Full context delegation — Share the planner's entire context with the sub-agent. Use for complex tasks where the sub-agent needs complete understanding. The sub-agent has its own tools and instructions but receives full context for its decisions. Note: this partially defeats the purpose of context isolation.
- Instruction passing — Create instructions via function call; the sub-agent receives only what it needs. Use for simple, well-defined subtasks. Maintains isolation but limits sub-agent flexibility.
- File system memory — Agents read and write to persistent storage. Use for complex tasks requiring shared state. The file system serves as the coordination mechanism, avoiding context bloat from shared state passing. Introduces latency and consistency challenges but scales better than message-passing.
Choose based on task complexity, coordination needs, and acceptable latency. Default to instruction passing and escalate to file system memory when shared state is needed. Avoid full context delegation unless the subtask genuinely requires it.
Consensus and Coordination
The Voting Problem Avoid simple majority voting — it treats hallucinations from weak models as equal to reasoning from strong models. Without intervention, multi-agent discussions devolve into consensus on false premises due to inherent bias toward agreement.
Weighted Voting Weight agent votes by confidence or expertise. Agents with higher confidence or domain expertise should carry more weight in final decisions.
Debate Protocols Structure agents to critique each other's outputs over multiple rounds. Adversarial critique often yields higher accuracy on complex reasoning than collaborative consensus. Guard against sycophantic convergence where agents agree to be agreeable rather than correct.
Trigger-Based Intervention Monitor multi-agent interactions for behavioral markers. Activate stall triggers when discussions make no progress. Detect sycophancy triggers when agents mimic each other's answers without unique reasoning.
Framework Considerations
Different frameworks implement these patterns with different philosophies. LangGraph uses graph-based state machines with explicit nodes and edges. AutoGen uses conversational/event-driven patterns with GroupChat. CrewAI uses role-based process flows with hierarchical crew structures.
Practical Guidance
Failure Modes and Mitigations
Failure: Supervisor Bottleneck The supervisor accumulates context from all workers, becoming susceptible to saturation and degradation.
Mitigate by constraining worker output schemas so workers return only distilled summaries. Use checkpointing to persist supervisor state without carrying full history in context.
Failure: Coordination Overhead Agent communication consumes tokens and introduces latency. Complex coordination can negate parallelization benefits.
Mitigate by minimizing communication through clear handoff protocols. Batch results where possible. Use asynchronous communication patterns. Measure whether multi-agent coordination actually saves time versus a single agent with a longer context.
Failure: Divergence Agents pursuing different goals without central coordination drift from intended objectives.
Mitigate by defining clear objective boundaries for each agent. Implement convergence checks that verify progress toward shared goals. Set time-to-live limits on agent execution to prevent unbounded exploration.
Failure: Error Propagation Errors in one agent's output propagate to downstream agents that consume that output, compounding into increasingly wrong results.
Mitigate by validating agent outputs before passing to consumers. Implement retry logic with circuit breakers. Use idempotent operations where possible. Consider adding a verification agent that cross-checks critical outputs before they enter the pipeline.
Examples
Example 1: Research Team Architecture
Supervisor
├── Researcher (web search, document retrieval)
├── Analyzer (data analysis, statistics)
├── Fact-checker (verification, validation)
└── Writer (report generation, formatting)Example 2: Handoff Protocol
def handle_customer_request(request):
if request.type == "billing":
return transfer_to(billing_agent)
elif request.type == "technical":
return transfer_to(technical_agent)
elif request.type == "sales":
return transfer_to(sales_agent)
else:
return handle_general(request)Dispatching Parallel Agents
Use parallel dispatch when facing 2+ independent tasks that can proceed without shared state or sequential dependencies.
When to Dispatch
- 3+ failing components with different root causes (each needs separate investigation)
- Multiple subsystems breaking independently (frontend, backend, infra — no shared cause)
- Research tasks spanning unrelated domains
- Any set of tasks where no agent's output is another agent's input
When NOT to Dispatch
- Failures share a root cause (one fix resolves all)
- Task N requires Task N-1's output
- Agents would write to the same files or shared state
- Full system context is required — splitting loses the picture
Dispatch Pattern
1. Group by domain — identify independent subtasks with clear boundaries 2. Craft focused prompts — each agent gets: specific scope, clear goal, explicit constraints, expected output format 3. Dispatch concurrently — launch all agents in parallel 4. Review summaries — read each agent's output report 5. Verify no conflicts — check for overlapping changes before integrating 6. Integrate — merge results, resolve any boundary collisions
Effective Agent Prompts
Every dispatched agent prompt must be:
- Self-contained — no references to "the conversation above" or shared state
- Domain-focused — one clear problem area, not "look at everything"
- Explicit about deliverables — "return a list of X" not "investigate and report"
- Scoped with constraints — what files/directories to touch, what to leave alone
Avoid: overly broad scopes ("fix the backend"), vague constraints ("be careful"), missing context (agent cannot understand the problem from the prompt alone).
Guidelines
1. Design for context isolation as the primary benefit of multi-agent systems 2. Choose architecture pattern based on coordination needs, not organizational metaphor 3. Implement explicit handoff protocols with state passing 4. Use weighted voting or debate protocols for consensus 5. Monitor for supervisor bottlenecks and implement checkpointing 6. Validate outputs before passing between agents 7. Set time-to-live limits to prevent infinite loops 8. Test failure scenarios explicitly
Gotchas
1. Supervisor bottleneck scaling — Supervisor context pressure grows non-linearly with worker count. At 5+ workers, the supervisor spends more tokens processing summaries than workers spend on actual tasks. Set a hard cap on workers per supervisor (3-5) and add a second supervisor tier rather than overloading one. 2. Token cost underestimation — Multi-agent runs cost approximately 15x baseline. Teams consistently underbudget because they estimate per-agent costs without accounting for coordination overhead, retries, and consensus rounds. Budget for 15x and treat anything less as a bonus. 3. Sycophantic consensus — Agents in debate patterns tend to converge on agreeable answers, not correct ones. LLMs have an inherent bias toward agreement. Counter this by assigning explicit adversarial roles and requiring agents to state disagreements before convergence is allowed. 4. Agent sprawl — Adding more agents past 3-5 shows diminishing returns and increases coordination overhead. Each additional agent adds communication channels quadratically. Start with the minimum viable number of agents and add only when a clear context isolation benefit exists. 5. Telephone game in message-passing — Information degrades through repeated summarization as it passes between agents. Each agent paraphrases and loses nuance. Use filesystem coordination instead of message-passing for state that multiple agents need to access faithfully. 6. Error propagation cascades — One agent's hallucination becomes another agent's "fact." Downstream agents have no way to distinguish upstream hallucinations from genuine information. Add validation checkpoints between agents and never trust upstream output without verification. 7. Over-decomposition — Splitting tasks too finely creates more coordination overhead than the task itself. A 10-step pipeline with 10 agents spends more tokens on handoffs than on actual work. Decompose only when subtasks genuinely benefit from separate contexts. 8. Missing shared state — Agents operating without a shared filesystem or state store duplicate work, produce inconsistent outputs, and lose track of what has already been accomplished. Establish shared persistent storage before building multi-agent workflows.
Integration
This skill owns agent topology and coordination protocols. Adjacent skills own shared state and tool contracts:
memory-systems: shared persistent state across agents.tool-design: tool specialization and spawn/status tool contracts.context-optimization: partitioning as one token-efficiency tactic.evaluation: measuring whether multiple agents improve outcomes after coordination cost.
References
Internal reference:
- Frameworks Reference - Read when: implementing a specific multi-agent pattern in LangGraph, AutoGen, or CrewAI and needing framework-specific code examples
Related skills in this collection:
- context-fundamentals - Read when: needing to understand context window mechanics before designing agent partitioning
- memory-systems - Read when: agents need to share state across context boundaries or persist information between runs
- context-optimization - Read when: individual agent contexts are too large and need partitioning or compression strategies
External resources:
- LangGraph Documentation - Read when: building graph-based multi-agent workflows with explicit state machines
- AutoGen Framework - Read when: implementing conversational GroupChat patterns or event-driven agent coordination
- CrewAI Documentation - Read when: designing role-based hierarchical agent processes
- Research on Multi-Agent Coordination - Read when: needing academic grounding on multi-agent system theory and evaluation
---
Skill Metadata
Created: 2025-12-20 Last Updated: 2026-05-15 Author: Agent Skills for Context Engineering Contributors Version: 2.1.0
{
"name": "multi-agent-patterns",
"version": "1.1.0",
"description": "Design multi-agent architectures for complex tasks. Use when single-agent context limits are exceede",
"author": {
"name": "Ship Shit Dev",
"email": "hello@shipshit.dev",
"url": "https://shipshit.dev"
},
"license": "MIT",
"skills": "."
}
multi-agent-patterns
Design multi-agent architectures (supervisor, swarm, hierarchical) that isolate context across instances without anthropomorphizing role division.
Upstream
Derived from [muratcankoylan/Agent-Skills-for-Context-Engineering](https://github.com/muratcankoylan/Agent-Skills-for-Context-Engineering) (MIT).
| Field | Value |
|---|---|
| Source | `skills/multi-agent-patterns/SKILL.md` |
| Upstream ref | main |
| Synced at commit | 25e1fa79a33f |
| Last synced | 2026-06-13 |
| License | MIT |
Local modifications: Imported 2026-01-20 (this repo's commit ef42a98) from muratcankoylan/Agent-Skills-for-Context-Engineering at v1.0.0-era content. Ported forward 2026-06-13 to upstream HEAD (commit 25e1fa79a33f); local body now tracks upstream v2.1.0 — carried the 8-entry Gotchas section, claim-* IDs, the 'Do not activate' routing block, the imperative rewrite of Detailed Topics, and the de-specified qualitative token-multiplier table. References to siblings not vendored here (project-development, hosted-agents, latent-briefing) were stripped; the cross-link to tool-design (vendored) is retained. Local divergence preserved: the 'Dispatching Parallel Agents' section does not exist upstream and was kept intact. references/frameworks.md is byte-identical to upstream. A 2026-06-13 review-hardening pass (CodeRabbit on PR #21) further diverges scripts/coordination.py: the three destructive-inbox call sites now re-queue non-target messages instead of dropping them, and submit_vote validates agent identity, the selection against the topic's options, and the confidence range — candidates to push upstream. To diff: compare the upstream path on main since commit 25e1fa79a33f.
Checking for upstream changes: when upstream has moved ahead of the synced marker above, diff `skills/multi-agent-patterns/SKILL.md` on main since commit 25e1fa79a33f, port anything worth bringing home, then bump metadata.upstream_commit (or metadata.upstream_version) and metadata.last_synced in SKILL.md and this table.
Multi-Agent Patterns: Technical Reference
This document provides implementation details for multi-agent architectures across different frameworks.
Supervisor Pattern
LangGraph Supervisor Implementation
Implement a supervisor that routes to worker nodes:
from typing import TypedDict, Union
from langgraph.graph import StateGraph, END
class AgentState(TypedDict):
task: str
current_agent: str
task_output: dict
messages: list
def supervisor_node(state: AgentState) -> AgentState:
"""
Supervisor decides which worker to invoke next.
Returns routing decision and updates state.
"""
task = state["task"]
messages = state.get("messages", [])
# Determine next agent based on task and history
if "research" in task.lower():
next_agent = "researcher"
elif "write" in task.lower() or "create" in task.lower():
next_agent = "writer"
elif "review" in task.lower() or "analyze" in task.lower():
next_agent = "reviewer"
else:
next_agent = "coordinator"
return {
"task": task,
"current_agent": next_agent,
"task_output": {},
"messages": messages + [{"supervisor": f"Routing to {next_agent}"}]
}
def researcher_node(state: AgentState) -> AgentState:
"""Research worker that gathers information."""
# Perform research task
output = perform_research(state["task"])
return {
"task": state["task"],
"current_agent": "researcher",
"task_output": output,
"messages": state["messages"] + [{"researcher": "Research complete"}]
}
def writer_node(state: AgentState) -> AgentState:
"""Writer worker that creates content based on research."""
output = create_content(state["task"], state["task_output"])
return {
"task": state["task"],
"current_agent": "writer",
"task_output": output,
"messages": state["messages"] + [{"writer": "Content created"}]
}
def build_supervisor_graph():
"""Build the supervisor workflow graph."""
workflow = StateGraph(AgentState)
# Add nodes
workflow.add_node("supervisor", supervisor_node)
workflow.add_node("researcher", researcher_node)
workflow.add_node("writer", writer_node)
# Add edges
workflow.add_edge("supervisor", "researcher")
workflow.add_edge("researcher", "supervisor")
workflow.add_edge("supervisor", "writer")
workflow.add_edge("writer", "supervisor")
# Set entry point
workflow.set_entry_point("supervisor")
return workflow.compile()AutoGen Supervisor
Implement supervisor using GroupChat pattern:
from autogen import AssistantAgent, UserProxyAgent, GroupChat
# Define specialized agents
researcher = AssistantAgent(
name="researcher",
system_message="""You are a research specialist.
Your goal is to gather accurate, comprehensive information
on topics assigned by the supervisor. Always cite sources
and note confidence levels.""",
llm_config=llm_config
)
writer = AssistantAgent(
name="writer",
system_message="""You are a content creation specialist.
Your goal is to create well-structured content based on
research provided by the supervisor. Follow style guidelines
and ensure factual accuracy.""",
llm_config=llm_config
)
# Define supervisor
supervisor = AssistantAgent(
name="supervisor",
system_message="""You are the project supervisor.
Your goal is to coordinate researchers and writers to
complete tasks efficiently.
Process:
1. Break down the task into research and writing phases
2. Route to appropriate specialists
3. Synthesize results into final output
4. Ensure quality before completing""",
llm_config=llm_config
)
# Configure group chat
group_chat = GroupChat(
agents=[supervisor, researcher, writer],
messages=[],
max_round=20
)
manager = GroupChatManager(
groupchat=group_chat,
llm_config=llm_config
)Swarm Pattern Implementation
LangGraph Swarms
Implement peer-to-peer handoffs:
def create_agent(name, system_prompt, tools):
"""Create an agent node for the swarm."""
def agent_node(state):
# Process current state with agent
response = invoke_agent(name, system_prompt, state["input"], tools)
# Check for handoff
if "handoff" in response:
return {"next_agent": response["handoff"], "output": response["output"]}
else:
return {"next_agent": END, "output": response["output"]}
return agent_node
def build_swarm():
"""Build a peer-to-peer agent swarm."""
workflow = StateGraph(State)
# Create agents
triage = create_agent("triage", TRIAGE_PROMPT, [search, read])
research = create_agent("research", RESEARCH_PROMPT, [search, browse, read])
analysis = create_agent("analysis", ANALYSIS_PROMPT, [calculate, compare])
writing = create_agent("writing", WRITING_PROMPT, [write, edit])
# Add to graph
workflow.add_node("triage", triage)
workflow.add_node("research", research)
workflow.add_node("analysis", analysis)
workflow.add_node("writing", writing)
# Define handoff edges
workflow.add_edge("triage", "research")
workflow.add_edge("triage", "analysis")
workflow.add_edge("research", "writing")
workflow.add_edge("analysis", "writing")
workflow.set_entry_point("triage")
return workflow.compile()Hierarchical Pattern Implementation
CrewAI-Style Hierarchy
class ManagerAgent:
def __init__(self, name, system_prompt, llm):
self.name = name
self.system_prompt = system_prompt
self.llm = llm
self.workers = []
def add_worker(self, worker):
"""Add a worker agent to the team."""
self.workers.append(worker)
def delegate(self, task):
"""
Analyze task and delegate to appropriate worker.
Returns work assignment and expected output format.
"""
# Analyze task requirements
requirements = analyze_task_requirements(task)
# Select best worker
best_worker = select_worker(self.workers, requirements)
# Create assignment
assignment = {
"worker": best_worker.name,
"task": task,
"context": self.get_relevant_context(task),
"output_format": requirements.output_format,
"deadline": requirements.deadline
}
return assignment
def review_output(self, worker_output, requirements):
"""
Review worker output against requirements.
Returns approval or revision request.
"""
quality_score = assess_quality(worker_output, requirements)
if quality_score >= requirements.threshold:
return {"status": "approved", "output": worker_output}
else:
return {
"status": "revision_requested",
"feedback": generate_feedback(worker_output, requirements),
"revise_worker": requirements.revise_worker
}Context Isolation Patterns
Full Context Delegation
def delegate_with_full_context(planner_state, subagent):
"""
Pass entire planner context to subagent.
Use for complex tasks requiring complete understanding.
"""
return {
"context": planner_state,
"subagent": subagent,
"isolation_mode": "full"
}Instruction Passing
def delegate_with_instructions(task_spec, subagent):
"""
Pass only instructions to subagent.
Use for simple, well-defined subtasks.
"""
return {
"instructions": {
"objective": task_spec.objective,
"constraints": task_spec.constraints,
"inputs": task_spec.inputs,
"outputs": task_spec.output_schema
},
"subagent": subagent,
"isolation_mode": "minimal"
}File System Coordination
class FileSystemCoordination:
def __init__(self, workspace_path):
self.workspace = workspace_path
def write_shared_state(self, key, value):
"""Write state accessible to all agents."""
path = f"{self.workspace}/{key}.json"
with open(path, 'w') as f:
json.dump(value, f)
return path
def read_shared_state(self, key):
"""Read state written by any agent."""
path = f"{self.workspace}/{key}.json"
with open(path, 'r') as f:
return json.load(f)
def acquire_lock(self, resource, agent_id):
"""Prevent concurrent access to shared resources."""
lock_path = f"{self.workspace}/locks/{resource}.lock"
if os.path.exists(lock_path):
return False
with open(lock_path, 'w') as f:
f.write(agent_id)
return TrueConsensus Mechanisms
Weighted Voting
def weighted_consensus(agent_outputs, weights):
"""
Calculate weighted consensus from agent outputs.
Weight = verbalized_confidence * domain_expertise
"""
weighted_sum = sum(
output.vote * weights[output.agent_id]
for output in agent_outputs
)
total_weight = sum(weights[output.agent_id] for output in agent_outputs)
return weighted_sum / total_weightDebate Protocol
class DebateProtocol:
def __init__(self, agents, max_rounds=5):
self.agents = agents
self.max_rounds = max_rounds
self.history = []
def run_debate(self, topic):
"""Execute structured debate on topic."""
# Initial statements
statements = {agent.name: agent.initial_statement(topic)
for agent in self.agents}
for round_num in range(self.max_rounds):
# Generate critiques
critiques = {}
for agent in self.agents:
critiques[agent.name] = agent.critique(
topic,
statements,
exclude=[agent.name]
)
# Update statements with critique integration
for agent in self.agents:
statements[agent.name] = agent.integrate_critique(
statements[agent.name],
critiques
)
# Check for convergence
if self.check_convergence(statements):
break
# Final evaluation
return self.evaluate_final(statements)Failure Recovery
Circuit Breaker
class AgentCircuitBreaker:
def __init__(self, failure_threshold=3, timeout_seconds=60):
self.failure_count = {}
self.failure_threshold = failure_threshold
self.timeout_seconds = timeout_seconds
def call(self, agent, task):
"""Execute agent task with circuit breaker protection."""
if self.is_open(agent.name):
raise CircuitBreakerOpen(f"Agent {agent.name} temporarily unavailable")
try:
result = agent.execute(task)
self.record_success(agent.name)
return result
except Exception as e:
self.record_failure(agent.name)
if self.failure_count[agent.name] >= self.failure_threshold:
self.open_circuit(agent.name)
raiseCheckpoint and Resume
class CheckpointManager:
def __init__(self, checkpoint_dir):
self.checkpoint_dir = checkpoint_dir
os.makedirs(checkpoint_dir, exist_ok=True)
def save_checkpoint(self, workflow_id, step, state):
"""Save workflow state for potential resume."""
checkpoint = {
"workflow_id": workflow_id,
"step": step,
"state": state,
"timestamp": time.time()
}
path = f"{self.checkpoint_dir}/{workflow_id}.json"
with open(path, 'w') as f:
json.dump(checkpoint, f)
def load_checkpoint(self, workflow_id):
"""Load last saved checkpoint for workflow."""
path = f"{self.checkpoint_dir}/{workflow_id}.json"
with open(path, 'r') as f:
return json.load(f)"""
Multi-Agent Coordination Utilities
Provides reusable building blocks for multi-agent coordination patterns:
supervisor/orchestrator, peer-to-peer handoffs, consensus mechanisms,
and failure handling with circuit breakers.
Use when: building multi-agent systems that need structured communication,
task delegation, consensus voting, or fault-tolerant agent coordination.
Designed for composability — import individual classes or use the
``if __name__ == "__main__"`` demo to see all patterns in action.
"""
from typing import Dict, List, Any, Optional
from dataclasses import dataclass, field
from enum import Enum
import time
import uuid
__all__ = [
"MessageType",
"AgentMessage",
"AgentCommunication",
"SupervisorAgent",
"HandoffProtocol",
"ConsensusManager",
"AgentFailureHandler",
]
class MessageType(Enum):
"""Types of messages exchanged between agents."""
REQUEST = "request"
RESPONSE = "response"
HANDOVER = "handover"
FEEDBACK = "feedback"
ALERT = "alert"
@dataclass
class AgentMessage:
"""Message exchanged between agents.
Use when: agents need a structured envelope for inter-agent communication
that carries sender/receiver identity, type, priority, and payload.
"""
sender: str
receiver: str
message_type: MessageType
content: Dict[str, Any]
timestamp: float = field(default_factory=time.time)
message_id: str = field(default_factory=lambda: str(uuid.uuid4()))
requires_response: bool = False
priority: int = 0 # 0 = normal, higher = more urgent
class AgentCommunication:
"""Communication channel for multi-agent systems.
Use when: multiple agents need an in-process message bus for sending,
receiving, and broadcasting messages with history tracking.
"""
def __init__(self) -> None:
self.inbox: Dict[str, List[AgentMessage]] = {}
self.outbox: List[AgentMessage] = []
self.message_history: List[AgentMessage] = []
def send(self, message: AgentMessage) -> None:
"""Send a message to an agent."""
if message.receiver not in self.inbox:
self.inbox[message.receiver] = []
self.inbox[message.receiver].append(message)
self.outbox.append(message)
self.message_history.append(message)
def receive(self, agent_id: str) -> List[AgentMessage]:
"""Receive all messages for an agent, clearing its inbox."""
messages = self.inbox.get(agent_id, [])
self.inbox[agent_id] = []
return messages
def broadcast(
self,
sender: str,
message_type: MessageType,
content: Dict[str, Any],
receivers: List[str],
) -> None:
"""Broadcast a message to multiple agents."""
for receiver in receivers:
self.send(
AgentMessage(
sender=sender,
receiver=receiver,
message_type=message_type,
content=content,
)
)
# ---------------------------------------------------------------------------
# Supervisor Pattern
# ---------------------------------------------------------------------------
class SupervisorAgent:
"""Central supervisor agent that coordinates worker agents.
Use when: tasks have clear decomposition and a single coordinator should
delegate subtasks, track worker status, and aggregate results.
"""
def __init__(self, name: str, communication: AgentCommunication) -> None:
self.name = name
self.communication = communication
self.workers: Dict[str, Dict[str, Any]] = {}
self.task_queue: List[Dict[str, Any]] = []
self.completed_tasks: List[Dict[str, Any]] = []
self.current_state: Dict[str, Any] = {}
def register_worker(self, worker_id: str, capabilities: List[str]) -> None:
"""Register a worker agent with the supervisor."""
self.workers[worker_id] = {
"capabilities": capabilities,
"status": "available",
"current_task": None,
"metrics": {"tasks_completed": 0, "avg_response_time": 0.0},
}
def decompose_task(self, task: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Decompose a task into subtasks.
Use when: a high-level task needs to be broken into assignable units.
In production, replace the rule-based logic with LLM-driven planning.
"""
subtasks: List[Dict[str, Any]] = []
task_type = task.get("type", "general")
if task_type == "research":
subtasks = [
{"type": "search", "description": "Gather information"},
{"type": "analyze", "description": "Analyze findings"},
{"type": "synthesize", "description": "Synthesize results"},
]
elif task_type == "create":
subtasks = [
{"type": "plan", "description": "Create plan"},
{"type": "draft", "description": "Draft content"},
{"type": "review", "description": "Review and refine"},
]
else:
subtasks = [
{
"type": "execute",
"description": task.get("description", "Execute task"),
}
]
for subtask in subtasks:
subtask["parent_task"] = task.get("id")
subtask["priority"] = task.get("priority", 0)
return subtasks
def assign_task(self, subtask: Dict[str, Any], worker_id: str) -> None:
"""Assign a subtask to a worker agent."""
if worker_id not in self.workers:
raise ValueError(f"Unknown worker: {worker_id}")
self.workers[worker_id]["status"] = "busy"
self.workers[worker_id]["current_task"] = subtask.get("id")
self._send(
AgentMessage(
sender=self.name,
receiver=worker_id,
message_type=MessageType.REQUEST,
content={"action": "execute_task", "task": subtask},
requires_response=True,
priority=subtask.get("priority", 0),
)
)
def select_worker(self, subtask: Dict[str, Any]) -> str:
"""Select the best available worker for a subtask.
Use when: the supervisor needs capability-aware routing with
load-balancing (fewest completed tasks chosen first).
"""
required_capability = subtask.get("type", "general")
candidates = [
wid
for wid, info in self.workers.items()
if info["status"] == "available"
and required_capability in info["capabilities"]
]
if not candidates:
candidates = [
wid
for wid, info in self.workers.items()
if info["status"] == "available"
]
if not candidates:
raise ValueError("No available workers")
return min(
candidates,
key=lambda w: self.workers[w]["metrics"]["tasks_completed"],
)
def aggregate_results(
self, subtask_results: List[Dict[str, Any]]
) -> Dict[str, Any]:
"""Aggregate results from completed subtasks."""
summaries = [
r.get("summary", "")
for r in subtask_results
if r.get("success")
]
successful = sum(
1 for r in subtask_results if r.get("success", False)
)
quality = successful / len(subtask_results) if subtask_results else 0.0
return {
"results": subtask_results,
"summary": " | ".join(summaries),
"quality_score": quality,
}
def run_workflow(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""Execute a complete workflow with supervision.
Use when: running an end-to-end supervised pipeline that decomposes
a task, assigns subtasks, collects results, and aggregates them.
Note: This is a synchronous simulation. Workers do not execute
asynchronously — each subtask is simulated inline. In production,
replace ``_simulate_worker_response`` with actual async worker
execution and message passing.
"""
subtasks = self.decompose_task(task)
results: List[Dict[str, Any]] = []
for subtask in subtasks:
worker = self.select_worker(subtask)
self.assign_task(subtask, worker)
# Simulate worker executing and responding
response = self._simulate_worker_response(worker, subtask)
self.communication.send(
AgentMessage(
sender=worker,
receiver=self.name,
message_type=MessageType.RESPONSE,
content=response,
)
)
self.workers[worker]["status"] = "available"
self.workers[worker]["metrics"]["tasks_completed"] += 1
messages = self.communication.receive(self.name)
for msg in messages:
if msg.message_type == MessageType.RESPONSE:
results.append(msg.content)
else:
# Re-queue non-RESPONSE messages to avoid dropping them
self.communication.send(msg)
final_result = self.aggregate_results(results)
return {
"task": task,
"subtask_results": results,
"final_result": final_result,
"success": final_result["quality_score"] >= 0.8,
}
def _simulate_worker_response(
self, worker_id: str, subtask: Dict[str, Any]
) -> Dict[str, Any]:
"""Simulate a worker completing a subtask.
In production, replace with actual agent execution that sends
the subtask to a worker process and awaits a real response.
"""
return {
"success": True,
"summary": f"{worker_id} completed: {subtask.get('description', subtask.get('type', 'task'))}",
"worker": worker_id,
"subtask_type": subtask.get("type"),
}
def _send(self, message: AgentMessage) -> None:
"""Send message through the communication channel."""
self.communication.send(message)
# ---------------------------------------------------------------------------
# Handoff Protocol
# ---------------------------------------------------------------------------
class HandoffProtocol:
"""Protocol for agent-to-agent handoffs.
Use when: implementing peer-to-peer or swarm patterns where agents
transfer control and task state to one another.
"""
def __init__(self, communication: AgentCommunication) -> None:
self.communication = communication
def create_handoff(
self,
from_agent: str,
to_agent: str,
context: Dict[str, Any],
reason: str,
) -> AgentMessage:
"""Create a handoff message with transferred context."""
return AgentMessage(
sender=from_agent,
receiver=to_agent,
message_type=MessageType.HANDOVER,
content={
"handoff_reason": reason,
"transferred_context": context,
"handoff_timestamp": time.time(),
},
priority=1,
)
def accept_handoff(self, agent_id: str) -> Optional[AgentMessage]:
"""Accept the first pending handoff for an agent, if any."""
messages = self.communication.receive(agent_id)
handoff: Optional[AgentMessage] = None
for msg in messages:
if handoff is None and msg.message_type == MessageType.HANDOVER:
handoff = msg
else:
# Re-queue non-HANDOVER (and extra HANDOVER) messages to avoid dropping them
self.communication.send(msg)
return handoff
def transfer_with_state(
self,
from_agent: str,
to_agent: str,
state: Dict[str, Any],
task: Dict[str, Any],
) -> bool:
"""Transfer task state from one agent to another.
Use when: a handoff must carry full task state and progress so the
receiving agent can resume without re-deriving context.
Returns True if the receiving agent acknowledged the handoff.
"""
handoff = self.create_handoff(
from_agent=from_agent,
to_agent=to_agent,
context={
"task_state": state,
"task_details": task,
"progress": state.get("progress", 0),
},
reason="task_transfer",
)
self.communication.send(handoff)
# In production, replace sleep with async await + timeout
time.sleep(0.1)
ack = self.communication.receive(from_agent)
ack_received = False
for m in ack:
if (
m.message_type == MessageType.RESPONSE
and m.content.get("status") == "handoff_received"
):
ack_received = True
else:
# Re-queue non-ACK messages to avoid dropping them
self.communication.send(m)
return ack_received
# ---------------------------------------------------------------------------
# Consensus Mechanism
# ---------------------------------------------------------------------------
class ConsensusManager:
"""Manager for multi-agent consensus building.
Use when: multiple agents must vote on a decision and the system needs
weighted consensus that accounts for confidence and expertise rather
than naive majority voting.
"""
def __init__(self) -> None:
self.votes: Dict[str, List[Dict[str, Any]]] = {}
self.debates: Dict[str, List[Dict[str, Any]]] = {}
def initiate_vote(
self, topic_id: str, agents: List[str], options: List[str]
) -> None:
"""Initiate a voting round on a topic."""
self.votes[topic_id] = [
{
"agent": agent,
"topic": topic_id,
"options": options,
"status": "pending",
}
for agent in agents
]
def submit_vote(
self,
topic_id: str,
agent_id: str,
selection: str,
confidence: float,
) -> None:
"""Submit a vote for a topic with a confidence weight."""
if topic_id not in self.votes:
raise ValueError(f"Unknown topic: {topic_id}")
if not (0.0 <= confidence <= 1.0):
raise ValueError(
f"confidence must be between 0.0 and 1.0, got {confidence}"
)
for vote in self.votes[topic_id]:
if vote["agent"] == agent_id:
allowed = vote.get("options", [])
if selection not in allowed:
raise ValueError(
f"Invalid selection '{selection}' for topic '{topic_id}'. "
f"Allowed options: {allowed}"
)
vote["status"] = "cast"
vote["selection"] = selection
vote["confidence"] = confidence
return
raise ValueError(
f"Agent '{agent_id}' is not registered for topic '{topic_id}'"
)
def calculate_weighted_consensus(self, topic_id: str) -> Dict[str, Any]:
"""Calculate weighted consensus from cast votes.
Use when: votes are in and the system needs to determine a winner
weighted by each agent's confidence rather than simple majority.
Weight = confidence * expertise_factor.
"""
if topic_id not in self.votes:
raise ValueError(f"Unknown topic: {topic_id}")
votes = [
v for v in self.votes[topic_id] if v.get("status") == "cast"
]
if not votes:
return {"status": "no_votes", "result": None}
# Group by selection
selections: Dict[str, List[Dict[str, Any]]] = {}
for vote in votes:
selection = vote["selection"]
if selection not in selections:
selections[selection] = []
selections[selection].append(vote)
# Calculate weighted score for each selection
results: Dict[str, Dict[str, Any]] = {}
for selection, selection_votes in selections.items():
weighted_sum = sum(v["confidence"] for v in selection_votes)
avg_confidence = (
weighted_sum / len(selection_votes) if selection_votes else 0.0
)
results[selection] = {
"weighted_score": weighted_sum,
"avg_confidence": avg_confidence,
"vote_count": len(selection_votes),
}
winner = max(results.keys(), key=lambda s: results[s]["weighted_score"])
return {
"status": "complete",
"result": winner,
"details": results,
"consensus_strength": (
results[winner]["weighted_score"] / len(votes) if votes else 0.0
),
}
# ---------------------------------------------------------------------------
# Failure Handling
# ---------------------------------------------------------------------------
class AgentFailureHandler:
"""Handler for agent failures in multi-agent systems.
Use when: agents may fail and the system needs retry logic with
exponential backoff, circuit breakers, and automatic rerouting to
backup agents.
"""
def __init__(
self,
communication: AgentCommunication,
max_retries: int = 3,
) -> None:
self.communication = communication
self.max_retries = max_retries
self.failure_counts: Dict[str, int] = {}
self.circuit_breakers: Dict[str, float] = {} # agent -> unlock time
def handle_failure(
self, agent_id: str, task_id: str, error: str
) -> Dict[str, Any]:
"""Handle a failure from an agent.
Use when: an agent reports an error and the system must decide
whether to retry (with backoff) or reroute to a backup agent.
"""
self.failure_counts[agent_id] = (
self.failure_counts.get(agent_id, 0) + 1
)
if self.failure_counts[agent_id] >= self.max_retries:
self._activate_circuit_breaker(agent_id)
return {
"action": "reroute",
"reason": "circuit_breaker_activated",
"alternative": self._find_alternative_agent(agent_id),
}
return {
"action": "retry",
"reason": error,
"retry_count": self.failure_counts[agent_id],
"delay": min(2 ** self.failure_counts[agent_id], 60),
}
def _activate_circuit_breaker(self, agent_id: str) -> None:
"""Temporarily disable an agent (1-minute cooldown)."""
self.circuit_breakers[agent_id] = time.time() + 60
def _find_alternative_agent(self, failed_agent: str) -> str:
"""Find an alternative agent to handle the task.
In production, check agent capabilities and availability.
"""
return "default_backup_agent"
def is_available(self, agent_id: str) -> bool:
"""Check if an agent is available (circuit breaker not active)."""
if agent_id in self.circuit_breakers:
if time.time() < self.circuit_breakers[agent_id]:
return False
del self.circuit_breakers[agent_id]
self.failure_counts[agent_id] = 0
return True
def record_success(self, agent_id: str) -> None:
"""Record a successful task completion, resetting failure count."""
self.failure_counts[agent_id] = 0
# ---------------------------------------------------------------------------
# Demo / CLI entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
print("=== Multi-Agent Coordination Demo ===\n")
# 1. Communication channel
comm = AgentCommunication()
print("1. Created communication channel")
# 2. Supervisor pattern
supervisor = SupervisorAgent("supervisor", comm)
supervisor.register_worker("researcher", ["search", "analyze"])
supervisor.register_worker("writer", ["synthesize", "draft"])
print("2. Registered supervisor with 2 workers: researcher, writer")
# 3. Handoff protocol
protocol = HandoffProtocol(comm)
handoff_msg = protocol.create_handoff(
from_agent="researcher",
to_agent="writer",
context={"findings": ["item1", "item2"]},
reason="research_complete",
)
comm.send(handoff_msg)
received = protocol.accept_handoff("writer")
print(
f"3. Handoff from researcher -> writer: "
f"{'accepted' if received else 'none pending'}"
)
# 4. Consensus mechanism
consensus = ConsensusManager()
consensus.initiate_vote("best_approach", ["agent_a", "agent_b", "agent_c"], ["A", "B"])
consensus.submit_vote("best_approach", "agent_a", "A", confidence=0.9)
consensus.submit_vote("best_approach", "agent_b", "B", confidence=0.6)
consensus.submit_vote("best_approach", "agent_c", "A", confidence=0.8)
result = consensus.calculate_weighted_consensus("best_approach")
print(
f"4. Consensus result: {result['result']} "
f"(strength: {result['consensus_strength']:.2f})"
)
# 5. Failure handling
handler = AgentFailureHandler(comm, max_retries=3)
action1 = handler.handle_failure("flaky_agent", "task_1", "timeout")
action2 = handler.handle_failure("flaky_agent", "task_1", "timeout")
action3 = handler.handle_failure("flaky_agent", "task_1", "timeout")
print(f"5. After 3 failures: action={action3['action']}")
print(f" Agent available? {handler.is_available('flaky_agent')}")
print("\n=== Demo Complete ===")