Multi-Agent Architecture Basics
9 examples to get you started with Multi-Agent Architecture - 6 basic and 3 intermediate.
Search across all documentation pages
9 examples to get you started with Multi-Agent Architecture - 6 basic and 3 intermediate.
These sketches are conceptual and provider-agnostic. They show control flow for handoffs, not a full production SDK.
Write the boundary in one sentence each.
roles = {
"orchestrator": "Own user goal, pick specialist, merge results, enforce global budget",
"researcher": "Search and summarize sources; no write tools; return citations",
}
assert "orchestrator" in roles and "researcher" in rolesRelated: Why Split One Agent Into Many Specialized Agents - when to split
Pass a structured packet, not the whole chat dump.
def make_packet(goal, constraints, inputs, budget):
return {
"subgoal": goal,
"constraints": constraints, # policies, language, disallowed actions
"inputs": inputs, # ids, paths, prior summaries
"success": ["summary", "citations"],
"budget": budget, # max_turns, timeout_s
}Related: Context Handoff: Passing State Between Agents Cleanly - full packet recipe
Force a status the orchestrator can branch on.
def specialist_return(status, summary, artifacts=None, errors=None):
assert status in {"ok", "failed", "needs_human"}
return {
"status": status,
"summary": summary,
"artifacts": artifacts or {},
"errors": errors or [],
}needs_human is a first-class outcome, not an afterthought.Related: Architecture Guide: Designing a Multi-Agent System's Communication Contract - formal contract
Orchestrator builds a packet, specialist runs its own loop, orchestrator merges.
def run_two_agent(user_goal, orchestrator, researcher, tools, max_global_turns=12):
packet = orchestrator.plan_handoff(user_goal)
result = researcher.run(packet, tools=tools["researcher"]) # own max_turns
if result["status"] == "ok":
return orchestrator.finalize(user_goal, result)
if result["status"] == "needs_human":
return orchestrator.escalate(result)
return orchestrator.fail_closed(result)researcher.run and a global budget outside.ok; do not invent success from partial chatter.Related: Orchestrator-Worker Patterns for Coordinating Specialist Agents - dispatch patterns
Nested agents without nested budgets thrash forever.
def run_specialist(packet, llm, tools, max_turns=6):
state = [packet]
for turn in range(max_turns):
state = llm.step(state, tools)
if llm.is_done(state):
return llm.to_return_contract(state)
return {
"status": "failed",
"summary": "max_turns",
"artifacts": {},
"errors": [{"code": "max_turns", "turns": max_turns}],
}Related: Multi-Agent Architecture Best Practices - bounds checklist
Trace parent/child so "the system failed" becomes debuggable.
def handoff_with_trace(run_id, from_agent, to_agent, packet, call):
span = {
"run_id": run_id,
"from": from_agent,
"to": to_agent,
"packet_keys": sorted(packet.keys()),
"subgoal": packet.get("subgoal"),
}
result = call(packet)
span["status"] = result.get("status")
log(span) # your tracer / logger
return resultRelated: Avoiding Context Loss and Duplication Across Agent Handoffs - loss/dupe checks
Deterministic rules first; LLM routing only when needed.
def pick_specialist(goal: str) -> str:
g = goal.lower()
if any(k in g for k in ("bug", "stack trace", "pr", "code")):
return "coder"
if any(k in g for k in ("source", "research", "compare vendors")):
return "researcher"
return "researcher" # safe default with read-only toolsRelated: Orchestrator-Worker Patterns for Coordinating Specialist Agents - routing recipes
Pass artifact refs through a small shared record instead of growing transcripts.
def pipeline(goal, orchestrator, researcher, writer):
task = {"goal": goal, "artifacts": {}, "history": []}
r = researcher.run(make_packet("gather sources", [], task, {"max_turns": 5}))
task["artifacts"]["sources"] = r["artifacts"]
task["history"].append({"agent": "researcher", "status": r["status"]})
if r["status"] != "ok":
return orchestrator.fail_closed(r)
w = writer.run(make_packet("draft brief", [], task, {"max_turns": 4}))
return orchestrator.finalize(goal, w)Related: Context Handoff: Passing State Between Agents Cleanly - shared store vs message pass
Answer these before picking LangGraph, CrewAI, or a custom host.
checklist = {
"roles": ["orchestrator", "specialist_1"],
"tools_per_role": "least privilege documented",
"packet_schema": ["subgoal", "constraints", "inputs", "success", "budget"],
"return_schema": ["status", "summary", "artifacts", "errors"],
"budgets": ["per_specialist_max_turns", "global_max_turns", "timeout"],
"escalation": "needs_human path defined",
"trace": "run_id + agent_id + handoff status",
}Related: Architecture Guide: Designing a Multi-Agent System's Communication Contract - ADR-style contract | Multi-Agent Architecture Best Practices - ten practices
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.
Reviewed by Chris St. John·Last updated Jul 16, 2026