Multi-Agent Orchestration: Supervisor and Subagent Patterns
How to split complex tasks across specialized agents without losing control of state, context, or your sanity.
Multi-Agent Orchestration: Supervisor and Subagent Patterns
How to split complex tasks across specialized agents without losing control of state, context, or your sanity.
A single agent that tries to handle everything is the monolith of the agentic world. It works well until the workflow becomes too complex for a single context window to manage. At that point, performance starts to degrade. The model loses context, makes inconsistent decisions, and may hallucinate instead of acknowledging uncertainty.
Multi-agent systems solve this problem by assigning each agent a focused, well-defined responsibility. The real challenge, however, is orchestration. Someone still needs to decide which agent should act next, how information flows between agents, how shared state is maintained, and what happens when an agent fails.
The Core Architecture
There are two primary orchestration patterns used when building multi-agent systems with LangGraph.
Network Topology
Every agent can communicate with every other agent. This approach offers maximum flexibility, but state management quickly becomes more complex because every interaction must be coordinated across multiple execution paths.
Supervisor Topology
A single supervisor receives the incoming task, decides which worker agent should execute next, and combines the results into a final response. This approach is more predictable, easier to debug, and significantly simpler to test in production environments.
In this article, we’ll focus on the Supervisor pattern, as it maps naturally to most real-world product requirements.
┌──────────────────┐
│ Supervisor │
│ (orchestrator) │
└────────┬─────────┘
│ routes to one of:
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌──────────┐
│ Research │ │ Writer │ │ Reviewer │
│ Agent │ │ Agent │ │ Agent │
└────┬─────┘ └────┬────┘ └────┬─────┘
│ │ │
└─────────────┴───────────┘
│ returns to Supervisor
▼
┌───────────────┐
│ Shared State │
│ (TypedDict) │
└───────────────┘
Shared State Schema
Every agent reads from and writes to a shared state object. Designing this schema early makes the workflow easier to maintain. Adding new fields later is straightforward, but removing or restructuring existing fields often requires changes throughout the graph.
from typing import Annotated, Literal, TypedDict
from langgraph.graph.message import add_messages
class MultiAgentState(TypedDict):
messages: Annotated[list, add_messages]
task: str
research_output: str
draft: str
review_notes: str
next_agent: str
iteration: int
final_output: str | None
The next_agent field is written by the supervisor to indicate which worker should execute next. This enables dynamic routing without relying on hard-coded execution paths.
Building the Supervisor
The supervisor is implemented as an LLM with a structured output schema. It reads the current state, evaluates the workflow, and decides which worker agent should execute next.
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel
class SupervisorDecision(BaseModel):
next: Literal["researcher", "writer", "reviewer", "FINISH"]
reasoning: str
supervisor_prompt = ChatPromptTemplate.from_messages([
("system", """You are a supervisor managing a team of AI agents.
Your team:
- researcher: gathers facts and data on the topic
- writer: drafts content based on research
- reviewer: checks the draft for accuracy and clarity
Current state:
- Research: {research_output}
- Draft: {draft}
- Review notes: {review_notes}
- Iteration: {iteration}
Decide which agent should act next, or FINISH if the output is ready.
Never call the same agent twice in a row.
Maximum 3 iterations."""),
("human", "Task: {task}"),
])
llm = ChatAnthropic(model="claude-sonnet-4-6")
supervisor_chain = supervisor_prompt | llm.with_structured_output(SupervisorDecision)
def supervisor_node(state: MultiAgentState) -> MultiAgentState:
decision = supervisor_chain.invoke({
"task": state["task"],
"research_output": state.get("research_output", "none yet"),
"draft": state.get("draft", "none yet"),
"review_notes": state.get("review_notes", "none yet"),
"iteration": state.get("iteration", 0),
})
return {
"next_agent": decision.next,
"iteration": state.get("iteration", 0) + 1,
}
Using with_structured_output() ensures that the model always returns a valid SupervisorDecision object. This eliminates manual string parsing and prevents the model from inventing unsupported agent names.

Article Link: https://www.yogprajapati.site/writing/multi-agent-orchestration-langgraph
메타데이터
- post_id
- b2ca49b5924f
- slug
- multi-agent-orchestration-supervisor-and-subagent-patterns-b2ca49b5924f
- url
- https://medium.com/@yog.devmail/multi-agent-orchestration-supervisor-and-subagent-patterns-b2ca49b5924f
- canonical_url
- https://medium.com/@yog.devmail/multi-agent-orchestration-supervisor-and-subagent-patterns-b2ca49b5924f
- author_url
- https://medium.com/@yog.devmail
- status
- ok
- fetched_at
- 2026-07-19 14:44:42