Lesson 18: orchestrators - managing agent control flow
Introduction
Section titled “Introduction”In previous lessons, we covered what agents are, how they use tools, and how multiple agents can work together. But we have not yet looked closely at the layer that makes it all run - the orchestration layer.
The orchestrator is the control system that decides: What happens next? Which agent runs? What goes into the context? When do we stop? It is the part of the system that sits between the user’s goal and the actual execution, coordinating everything.
If agents are the workers, the orchestrator is the project manager.
ELI5: Think of an orchestrator like a film director
Section titled “ELI5: Think of an orchestrator like a film director”A film director does not act, operate the camera, or do lighting. Instead, they coordinate all the specialists: “Camera operator, get a close-up. Actor, deliver the line. Sound engineer, add the music.” They decide the sequence, handle problems when a scene does not work, and keep everything moving toward the finished film.
An agent orchestrator does the same thing - it coordinates which agents run, in what order, with what inputs, and decides what to do when something goes wrong.
Key takeaway: The orchestrator manages the control flow of your agent system. Choosing the right orchestration pattern is one of the most important architectural decisions you will make.
What does an orchestrator actually do?
Section titled “What does an orchestrator actually do?”The orchestrator manages four core concerns:
1. Control flow
Section titled “1. Control flow”Deciding what happens next. Should the agent call a tool? Hand off to another agent? Ask the user for clarification? Stop because the goal is met?
2. Context assembly
Section titled “2. Context assembly”Building the right context for each step. This means selecting which information goes into the LLM’s context window - system instructions, relevant memory, tool results, conversation history - and keeping the window from overflowing.
3. State management
Section titled “3. State management”Tracking what has been done, what still needs to happen, what has succeeded, and what has failed. In multi-agent systems, this also means managing shared state between agents.
4. Error handling
Section titled “4. Error handling”Deciding what to do when things go wrong. Should the agent retry? Try a different approach? Fall back to a simpler method? Escalate to a human?
The orchestrator runs the core agent loop:
+----> Assemble Context | | | v Receive Goal | Invoke LLM (Reason) | | | v | Execute Action (Act) | | | v +---- Observe Result | Goal met? ---> Return ResultEach iteration through this loop is one “step.” The orchestrator decides when to loop and when to stop.
Two types of orchestration
Section titled “Two types of orchestration”The most fundamental design decision is where your orchestrator falls on the spectrum between deterministic and dynamic control.
Deterministic (workflow-based)
Section titled “Deterministic (workflow-based)”The control flow is predefined. The orchestrator follows a fixed blueprint - it does not consult an LLM to decide what happens next. Steps execute in a predetermined order with predetermined conditions.
Strengths:
- Predictable behavior - you know exactly what will happen
- Easy to debug - step through the workflow like regular code
- Fast - no LLM calls for orchestration decisions
- Reliable - no risk of the orchestrator going off-track
Limitations:
- Cannot handle novel situations the workflow was not designed for
- Requires upfront knowledge of all possible paths
- Changes to the workflow require code changes
Example: A document processing pipeline that always runs: extract text, classify document type, extract entities, validate, store.
Dynamic (LLM-driven)
Section titled “Dynamic (LLM-driven)”The orchestrator uses an LLM to decide what happens next. At each step, it reasons about the current state and chooses the next action. This is the classic ReAct loop.
Strengths:
- Handles novel and open-ended tasks
- Can adapt when plans fail
- Can work on tasks the developer did not anticipate
Limitations:
- Less predictable - the same input can produce different execution paths
- Harder to debug - “why did the agent do that?”
- More expensive - LLM calls for orchestration add up
- Can get stuck in loops or make poor routing decisions
Example: A research assistant that dynamically decides whether to search the web, query a database, read a document, or ask the user for clarification based on what it has learned so far.
Hybrid (the practical choice)
Section titled “Hybrid (the practical choice)”Most production systems combine both approaches. They use deterministic orchestration for the overall structure while allowing LLM-driven flexibility within individual steps.
Example: A customer support system with a deterministic outer flow (receive ticket, classify, route to specialist, verify resolution, close) where each step internally uses an LLM agent that can reason freely about how to handle its specific task.
Core orchestration patterns
Section titled “Core orchestration patterns”Here are the most widely used patterns, with guidance on when each one fits:
Sequential (pipeline)
Section titled “Sequential (pipeline)”Agents execute one after another in a defined order. Each agent’s output becomes the next agent’s input.
Input --> [Agent A] --> [Agent B] --> [Agent C] --> OutputWhen to use:
- Tasks with clear stages that build on each other
- Refinement workflows (draft, review, edit)
- Data processing pipelines (extract, transform, validate)
When to avoid:
- When stages are independent and could run in parallel
- When you need to backtrack (Agent C’s failure requires re-running Agent A)
Example: Code generation pipeline: requirement analysis agent produces a spec, coding agent writes the implementation, testing agent writes tests, review agent checks for issues.
With the Claude Agent SDK, you write this pipeline directly in Python: each stage is its own query() call, with the previous stage’s output feeding the next prompt.
import anyiofrom claude_agent_sdk import query, ClaudeAgentOptions
async def run_pipeline(task: str): options = ClaudeAgentOptions(allowed_tools=["Read", "Write", "Bash"])
spec = "" async for message in query(prompt=f"Write a spec for: {task}", options=options): spec += str(message)
implementation = "" async for message in query(prompt=f"Implement this spec:\n{spec}", options=options): implementation += str(message)
async for message in query(prompt=f"Write tests for:\n{implementation}", options=options): print(message)
anyio.run(run_pipeline, "a CSV to JSON converter")See the Claude Agent SDK documentation for the full API surface.
Parallel (fan-out / gather)
Section titled “Parallel (fan-out / gather)”Multiple agents execute at the same time on the same input. Results are collected and combined.
+--> [Agent A] --+ | |Input ------+--> [Agent B] --+--> Combine --> Output | | +--> [Agent C] --+When to use:
- Independent analysis from multiple perspectives
- Latency-sensitive tasks where parallel execution saves time
- Getting diverse viewpoints on the same input
When to avoid:
- When agents need each other’s output to do their work
- When results might conflict and you have no resolution strategy
Example: Code review where a security agent, performance agent, and style agent all review the same PR simultaneously. Results are merged into a single review.
With the Claude Agent SDK, you run multiple query() calls concurrently - for example using anyio’s task groups - and gather the results.
import anyiofrom claude_agent_sdk import query, ClaudeAgentOptions
async def review_agent(role: str, pr_diff: str, results: dict, options): text = "" async for message in query(prompt=f"Review this diff for {role} issues:\n{pr_diff}", options=options): text += str(message) results[role] = text
async def review_pr(pr_diff: str): options = ClaudeAgentOptions(allowed_tools=["Read"]) results = {} async with anyio.create_task_group() as tg: for role in ["security", "performance", "style"]: tg.start_soon(review_agent, role, pr_diff, results, options) return results
anyio.run(review_pr, pr_diff_text)See the Claude Agent SDK documentation for the full API surface.
Loop (iterative refinement)
Section titled “Loop (iterative refinement)”An agent executes repeatedly until a condition is met. This includes two important sub-patterns:
Generator-Critic (Maker-Checker): One agent produces output, another evaluates it, and the loop continues until the evaluator approves.
+--> [Generator Agent] --> [Critic Agent] --+| | || Not good enough -----+ || |+-------------------------------------------+ | Good enough --> OutputProgressive Refinement: A single agent improves its output through multiple passes, like an author revising a draft.
When to use:
- Quality-sensitive tasks where first attempts are rarely good enough
- Tasks with clear acceptance criteria
- Iterative improvement workflows
When to avoid:
- When you cannot define clear stopping criteria (risk of infinite loops)
- When the first attempt is usually good enough
Important: Always set a maximum iteration count. Without it, a loop can run forever if the critic never approves.
A loop is a graph with a cycle: a conditional edge sends control back to the generator node until the critic approves, or until a maximum iteration count is reached. This is the shape LangGraph is built for - the cycle and its exit condition are visible as graph structure instead of a Python for/while loop.
import osfrom typing import TypedDictfrom langchain_anthropic import ChatAnthropicfrom langgraph.graph import StateGraph, END
model = ChatAnthropic( model="aws/claude-5-sonnet", base_url=os.environ["ANTHROPIC_BASE_URL"], # the LiteLLM proxy)
class RefineState(TypedDict): draft: str approved: bool iterations: int
def generate(state: RefineState) -> RefineState: response = model.invoke(f"Write or improve:\n{state['draft']}") return {"draft": response.content, "iterations": state["iterations"] + 1}
def critique(state: RefineState) -> RefineState: response = model.invoke(f"Does this meet the bar? Answer yes or no:\n{state['draft']}") return {"approved": "yes" in response.content.lower()}
def should_continue(state: RefineState) -> str: if state["approved"] or state["iterations"] >= 5: return END return "generate"
graph = StateGraph(RefineState)graph.add_node("generate", generate)graph.add_node("critique", critique)graph.set_entry_point("generate")graph.add_edge("generate", "critique")graph.add_conditional_edges("critique", should_continue)app = graph.compile()
result = app.invoke({"draft": "a product description", "approved": False, "iterations": 0})See the LangGraph documentation for the full API surface.
Routing (handoff / dispatch)
Section titled “Routing (handoff / dispatch)”An input is classified and directed to a specialized agent. Only one agent handles each request.
+--> [Billing Agent] |Input --> [Router] +--> [Technical Support Agent] | +--> [General Inquiry Agent]Routing can be:
- Deterministic: Rule-based classification (if the message contains “invoice,” route to billing)
- LLM-driven: The router agent uses reasoning to pick the best specialist
When to use:
- Customer support with specialized departments
- Multi-domain systems where different inputs need different expertise
- When you want full control transfer (one active agent at a time)
When to avoid:
- When the request does not fit neatly into categories
- When multiple agents need to collaborate on the same request
This is one of the clearest matches for LangGraph: a router node classifies the input, and a conditional edge sends control to exactly one specialist node.
import osfrom typing import Literal, TypedDictfrom langchain_anthropic import ChatAnthropicfrom langgraph.graph import StateGraph, END
model = ChatAnthropic( model="aws/claude-5-sonnet", base_url=os.environ["ANTHROPIC_BASE_URL"], # the LiteLLM proxy)
class TicketState(TypedDict): message: str category: str response: str
def classify(state: TicketState) -> TicketState: result = model.invoke(f"Classify as billing, technical, or general:\n{state['message']}") return {"category": result.content.strip().lower()}
def route(state: TicketState) -> Literal["billing", "technical", "general"]: return state["category"]
def billing(state: TicketState) -> TicketState: return {"response": model.invoke(f"Handle this billing request:\n{state['message']}").content}
def technical(state: TicketState) -> TicketState: return {"response": model.invoke(f"Handle this technical request:\n{state['message']}").content}
def general(state: TicketState) -> TicketState: return {"response": model.invoke(f"Handle this general request:\n{state['message']}").content}
graph = StateGraph(TicketState)graph.add_node("classify", classify)graph.add_node("billing", billing)graph.add_node("technical", technical)graph.add_node("general", general)graph.set_entry_point("classify")graph.add_conditional_edges("classify", route)for node in ["billing", "technical", "general"]: graph.add_edge(node, END)app = graph.compile()
result = app.invoke({"message": "My invoice looks wrong", "category": "", "response": ""})See the LangGraph documentation for the full API surface.
Hierarchical (Coordinator-Worker)
Section titled “Hierarchical (Coordinator-Worker)”A lead agent coordinates the process while delegating tasks to specialized sub-agents.
+---> [Research Agent] |[Coordinator] --+---> [Analysis Agent] | +---> [Writing Agent]The coordinator:
- Breaks the overall goal into subtasks
- Assigns subtasks to the right specialist
- Monitors progress and handles dependencies
- Combines results into a final output
When to use:
- Complex tasks that require multiple types of expertise
- Tasks where the plan is not known upfront and must be developed
- Research-style work where findings from one area inform what to explore next
When to avoid:
- Simple tasks that do not warrant the coordination overhead
- When a sequential pipeline would work just as well
The Claude Agent SDK supports this natively through subagents: define specialist subagents (for example in .claude/agents/), each with its own system prompt, tools, and permissions, and delegate to them via the Task tool. Each subagent runs in its own isolated context window and reports back only its result, keeping the coordinator’s context clean even after a large research task.
Group chat (roundtable)
Section titled “Group chat (roundtable)”Multiple agents participate in a shared conversation, coordinated by a chat manager.
[Chat Manager] | +---> [Agent A]: "I think we should..." | +---> [Agent B]: "Building on that..." | +---> [Agent C]: "I disagree because..." | +---> [Agent A]: "Good point, let me revise..."When to use:
- Consensus building
- Brainstorming where diverse perspectives improve the outcome
- Iterative validation (multiple experts review and refine)
When to avoid:
- When efficiency matters more than thoroughness (group chat is expensive in tokens)
- When more than three agents participate (conversations become chaotic)
Choosing the right pattern
Section titled “Choosing the right pattern”| Pattern | Predictability | Flexibility | Token Cost | Best For |
|---|---|---|---|---|
| Sequential | High | Low | Low | Clear step-by-step processes |
| Parallel | High | Low | Medium (concurrent) | Independent analysis tasks |
| Loop | Medium | Medium | Variable | Quality refinement |
| Routing | High | Medium | Low | Multi-domain classification |
| Hierarchical | Medium | High | Higher | Complex multi-step research |
| Group Chat | Low | High | Highest | Consensus and brainstorming |
A decision flowchart:
Is the task a clear step-by-step process? Yes --> Sequential
Are there independent subtasks that can run simultaneously? Yes --> Parallel
Does the output need iterative improvement? Yes --> Loop
Does the task type determine which specialist handles it? Yes --> Routing
Is the task complex and requires planning and delegation? Yes --> Hierarchical
Does the task benefit from multiple perspectives and debate? Yes --> Group ChatOrchestration Pattern Playground
Click a pattern to see it animate. Select two patterns to see how they compose.
Select two patterns above to see how they combine. 0 / 2 selected
Composing patterns
Section titled “Composing patterns”Real systems often nest patterns. Here is an example of a content creation system:
content_pipeline (sequential) | +-- research (parallel) | +-- web_search query() | +-- database_query query() | +-- document_review query() | +-- writer query() | (uses research results to draft content) | +-- refinement (loop, max_iterations=5) +-- editor query() +-- fact_checker query() (loops until both approve)This combines parallel research, sequential progression, and iterative refinement into one system. With the Claude Agent SDK, each stage is ordinary Python calling query() - a task group for the parallel stage, a loop with a max iteration count for refinement - composed however your control flow needs. The same composition maps onto a single LangGraph graph too: the parallel research stage becomes a fan-out of nodes, and the refinement stage becomes a cycle with a conditional edge - worth it when you want the whole pipeline’s state to be inspectable, checkpointed, or reviewed as a first-class artifact rather than as nested Python.
Orchestration with the Claude Agent SDK
Section titled “Orchestration with the Claude Agent SDK”The Claude Agent SDK packages the same agent loop that powers Claude Code - tools, permissions, subagents, hooks, and MCP - as a library you can call from your own code. Rather than providing separate declarative classes for each control-flow pattern, it gives you the primitives and lets you compose them in plain Python.
query() as the building block
Section titled “query() as the building block”Sequential and parallel composition, and hierarchical delegation via subagents, can be built by calling query() one or more times and wiring the results together with ordinary Python control flow (function calls, for loops, anyio task groups), as shown in the examples above. There is no dedicated SequentialAgent or ParallelAgent class to learn - orchestration logic reads like the rest of your codebase.
For patterns built around cycles or branching - loop and routing - the LangGraph examples earlier in this lesson show the complementary approach: modeling the control flow explicitly as a graph rather than as nested Python.
Subagents for hierarchical delegation
Section titled “Subagents for hierarchical delegation”For hierarchical (coordinator-worker) orchestration specifically, Claude Code and the Agent SDK have first-class support: define specialist subagents (for example in .claude/agents/), each with its own system prompt, tools, and permissions, and delegate to them via the Task tool. Each subagent runs in an isolated context window and reports back only its result, keeping the coordinator’s context clean.
Hooks for control and validation
Section titled “Hooks for control and validation”Hooks let you intercept the agent loop at defined points (before or after a tool call, on user prompt submission, and more) to validate output between steps, enforce policy, or log for observability - turning the “validate between steps” and “instrument for observability” practices covered later in this lesson into configuration rather than custom orchestration code.
Permissions as a safety boundary
Section titled “Permissions as a safety boundary”permission_mode and allowed_tools, as used in the examples above, constrain what each query() call - and each subagent - is allowed to do. This is a direct mitigation for the “excessive permissions” and “shared mutable state” anti-patterns covered later in this lesson.
MCP for external tools
Section titled “MCP for external tools”Like Claude Code, the Agent SDK can connect to MCP servers, so any orchestration pattern you build has the same access to external tools and data covered in Lesson 16: MCP deep dive.
Because the SDK inherits Claude Code’s configuration and authentication and honors ANTHROPIC_BASE_URL, it works through your company’s LiteLLM proxy with no extra setup. For the full API surface, see the Claude Agent SDK documentation.
Orchestration with LangGraph
Section titled “Orchestration with LangGraph”LangGraph models an agent system as an explicit graph: a StateGraph of nodes (plain functions or agents) connected by edges - including conditional edges chosen by a routing function or an LLM - operating over a shared, typed state object. This is the natural fit for patterns with cycles or branching, like the loop and routing examples earlier in this lesson, because the control flow is visible as data (nodes and edges) rather than buried in nested Python control flow.
Install it with:
uv add langgraph langchain-anthropicFor a single ReAct-style agent, the prebuilt create_react_agent skips writing the graph by hand:
import osfrom langchain_anthropic import ChatAnthropicfrom langgraph.prebuilt import create_react_agent
model = ChatAnthropic( model="aws/claude-5-sonnet", base_url=os.environ["ANTHROPIC_BASE_URL"], # the LiteLLM proxy)agent = create_react_agent(model, tools=[search_docs, get_weather])result = agent.invoke({"messages": [("user", "What's the weather in Tokyo?")]})Because LangGraph talks to models through langchain_anthropic.ChatAnthropic (or the equivalent OpenAI-compatible client), the same graph runs unchanged against the OpenAI-series models your company’s LiteLLM proxy also exposes - just swap the model string. LangGraph also supports checkpointing state to a persistence layer, which matters for long-running or human-in-the-loop orchestration: a run can be paused, inspected, and resumed later without losing state. Subgraphs let you nest one graph inside another, which is one way to build hierarchical orchestration entirely in LangGraph rather than with Agent SDK subagents. See the LangGraph documentation for the full API.
Choosing between the Claude Agent SDK and LangGraph
Section titled “Choosing between the Claude Agent SDK and LangGraph”Both appear throughout this lesson because they solve different problems, not competing ones:
- Claude Agent SDK - the fastest path when the agent is Claude driving tools directly. It inherits Claude Code’s tool implementations, permission system, hooks, and MCP configuration, so a
query()call behaves like Claude Code itself. Reach for it when you’re building a Claude-native agent, as in Lesson 13: Building your first agent - orchestration is ordinary Python aroundquery()calls and subagents. - LangGraph - the company’s standard for production orchestration. It makes state, branching, and cycles explicit as a graph rather than as nested Python, and it isn’t tied to a single model provider: the same graph runs against Claude or against the OpenAI-series models the LiteLLM proxy also exposes. Reach for it when you need durable, inspectable control flow, when more than one model or provider is in play, or when the orchestration itself is worth reviewing and testing as a first-class artifact.
The two compose rather than compete: a LangGraph node can itself be a Claude Agent SDK query() call, so a coordinator built in LangGraph can delegate a step to a fully-tooled Claude-native subagent.
Framework comparison
Section titled “Framework comparison”LangGraph and the Claude Agent SDK are two of several frameworks and libraries used for agent orchestration. Here is how they compare to other major options:
| Framework | Approach | Strengths | Considerations |
|---|---|---|---|
| LangGraph | Graph-based workflow with typed state, nodes, and conditional edges | Explicit, inspectable control flow with native support for cycles and branching. Provider-independent - the same graph runs against Claude and the OpenAI-series models exposed by the LiteLLM proxy. This company’s standard for production orchestration. | Steeper learning curve than plain Python |
| Claude Agent SDK | Orchestrator-worker with isolated context windows | Subagents use isolated context, sending only relevant info back. Inherits Claude Code’s tools, permissions, and auth. | Claude-native - the natural choice when Claude is driving the tools directly, but not provider-independent |
| Google ADK | Three deterministic primitives (Sequential, Parallel, Loop) + LLM-driven coordination | Clean separation of workflow vs. reasoning. Deployment to Vertex AI Agent Engine. | Newer framework, smaller community than LangChain |
| CrewAI | Role-based model where agents are defined like team members | Fastest time-to-value. Intuitive YAML-driven configuration. | May lack sophistication for complex enterprise scenarios |
| AutoGen (Microsoft) | Conversational architecture with dynamic role-playing | Good for human-in-the-loop and multi-party conversations. | Significant setup complexity for production |
This company uses LangGraph and the Claude Agent SDK for different jobs, not as competitors - see Choosing between the Claude Agent SDK and LangGraph above. Reach for ADK if you specifically need Vertex AI Agent Engine deployment, or CrewAI if you want the fastest possible time-to-value on a small role-based team of agents.
Long-running agents: orchestrating across sessions
Section titled “Long-running agents: orchestrating across sessions”Everything above assumes the orchestration finishes within one run. But an increasingly common shape of work is the agent that makes forward progress on a goal across many sessions - hours, days, sometimes weeks. Think of a large migration, a recurring triage job, or a feature that takes an agent several context windows to complete. Orchestrating that well means designing around three walls every agent eventually hits:
- Finite context windows. Even million-token windows fill up, and performance degrades from context rot well before the hard limit (see Lesson 5).
- No persistent state. A new session starts blank. Without deliberate design, every context window boundary is a cliff where the agent forgets everything - like a project staffed by engineers working in shifts, where each new engineer arrives with no memory of the previous shift.
- Self-verification failure. Models over-report their own completion. An agent left alone will happily declare success without external evidence.
The design response to all three is the same: move the state out of the model and the verification out of the agent.
- Externalize state to durable artifacts. A task list file, a progress log, git commits. The agent stays amnesiac; the filesystem remembers. Each session starts by reading the current state from disk, not by being told it in a prompt.
- Write explicit done-conditions before starting. “Improve the test coverage” invites the agent to declare victory early. “All items in
tasks.jsonmarked done, withpytestpassing” is a stopping condition a fresh session can check. - Treat context resets as first-class events. Do not fight the window - plan for full teardown and reconstruction from the handoff files. If the agent cannot resume from what is on disk, the state files are incomplete.
- Keep the evaluator outside the generator. The generator-critic pattern from earlier applies with more force here: verification must come from tests, CI, or a separate agent, never from the agent grading its own work.
The simplest working implementation of all this is a pattern practitioners call the Ralph loop: a dumb outer loop around a fresh agent session per task.
while tasks remain in tasks.json: task = next unfinished task prompt = task + relevant context + notes from progress.txt run agent session (fresh context) run verification (tests, lint) append outcome to progress.txt mark task done only if verification passedNo memory tricks, no giant context - continuity comes entirely from external files and git history. LangGraph’s checkpointing (covered above) is the framework version of the same idea: state that survives the process, so a run can pause, be inspected, and resume.
Best practices
Section titled “Best practices”Start simple, add complexity when needed
Section titled “Start simple, add complexity when needed”A single agent with good tools often outperforms a multi-agent system with poor orchestration. Start with the simplest approach that works:
- Single agent with tools
- Sequential pipeline (if you need stages)
- Parallel execution (if you need speed)
- Full multi-agent coordination (if you need specialization)
Do not jump to a hierarchical multi-agent system because it sounds impressive. Add agents only when a single agent demonstrably cannot handle the task.
Match the model to the task
Section titled “Match the model to the task”Not every agent in your orchestration needs the same model. A classification router can use a fast, cheap model (Claude Haiku 4.5). A complex reasoning agent should use a capable model (Claude Opus 4.8). This saves a significant amount over using the most capable model for every step.
Set iteration limits
Section titled “Set iteration limits”Any loop or recursive orchestration must have a maximum iteration count. Without it, an agent that never satisfies its own criteria will run forever. A good default is 3-5 iterations for refinement loops.
Validate between steps
Section titled “Validate between steps”In a sequential pipeline, validate each agent’s output before passing it to the next. A malformed or off-topic result from Agent A will cascade through Agents B and C, wasting tokens and producing garbage.
Manage context across agents
Section titled “Manage context across agents”In multi-agent systems, context windows grow fast. Strategies to keep this under control:
- Summarize outputs before passing between agents
- Use external state stores for large shared data
- Give each agent only the context it needs, not everything
- Use context compaction (sliding windows, summarization) for long-running tasks
Instrument for observability
Section titled “Instrument for observability”Track performance per agent and per orchestration run:
- Latency per step
- Token usage per agent
- Success/failure rates per step
- End-to-end task completion rate
Use distributed tracing (e.g., OpenTelemetry) to follow a request through multiple agents. This is essential for debugging when things go wrong.
If you are using the Claude Agent SDK, hooks are a natural place to emit this instrumentation without threading it through your own orchestration code. See the Claude Agent SDK documentation for details.
Design for failure
Section titled “Design for failure”Agents fail. Tools return errors. LLMs hallucinate. Your orchestrator needs to handle this gracefully:
- Retry with backoff for transient errors (API timeouts, rate limits)
- Fallback strategies for persistent failures (try a different tool, use a simpler model)
- Circuit breakers to prevent cascading failures
- Human escalation as the last resort for critical tasks
Common Anti-Patterns
Section titled “Common Anti-Patterns”| Anti-Pattern | Problem | Fix |
|---|---|---|
| Orchestration overkill | Using a multi-agent system for a task a single agent can handle | Start with one agent, add more only when needed |
| Agents without specialization | Multiple agents that all do roughly the same thing | Give each agent a clearly distinct role and expertise |
| Shared mutable state | Concurrent agents writing to the same state, causing race conditions | Use immutable messages or proper state locking |
| No iteration limits | Loops that run forever when the exit condition is never met | Always set max_iterations |
| Context window bloat | Passing full conversation history through every agent in a pipeline | Summarize and prune between steps |
| Deterministic when dynamic needed | Using a fixed pipeline for tasks that require adaptive reasoning | Use LLM-driven routing for unpredictable task types |
| Dynamic when deterministic works | Using LLM routing for tasks with a clear, known sequence | Use workflow agents to save cost and improve reliability |
Key takeaways
Section titled “Key takeaways”- The orchestrator manages control flow, context assembly, state, and error handling
- Two fundamental types: deterministic (predictable, cheap, limited) and LLM-driven (flexible, expensive, less predictable)
- Most production systems use a hybrid - deterministic structure with LLM flexibility within steps
- Core patterns: sequential, parallel, loop, routing, hierarchical, group chat
- Patterns compose - nest them to build complex systems from simple pieces
- The Claude Agent SDK gives you
query(), subagents, hooks, and MCP as building blocks for Claude-native agents; LangGraph gives you an explicit, provider-independent graph for orchestration with cycles and branching, and is this company’s standard for production orchestration - use each for what it’s best at, not as competitors - Start simple. A single well-equipped agent is better than a poorly orchestrated team.
- For work that outlives one session, externalize state to durable files, define explicit done-conditions, and keep verification outside the agent
- Set iteration limits, validate between steps, manage context aggressively, and design for failure
Further reading
Section titled “Further reading”- Claude Agent SDK documentation
- Anthropic engineering - Building Effective AI Agents
- Microsoft Azure - AI Agent Orchestration Patterns
- LangGraph Documentation
- CrewAI Documentation
- Long-running Agents - Addy Osmani - the three walls and production patterns for multi-session agents
- Loop Engineering - Addy Osmani - designing the loop that prompts the agent so you do not have to
Previous Lesson: Agent Skills | Next Lesson: Where to Go From Here ->