By Sagar Shankaran, Founder of CallSphere
Learn how to build an orchestrator agent that breaks complex tasks into subtasks, delegates to specialist agents, aggregates results, and delivers a unified response using the OpenAI Agents SDK.
Key takeaways
The orchestrator pattern places a single "manager" agent at the top of the hierarchy. This orchestrator does not do the work itself. Instead, it analyzes the incoming request, decomposes it into subtasks, delegates each subtask to a specialist agent, collects the results, and synthesizes a final answer.
This is the most common multi-agent architecture because it maps directly to how human organizations work. A project manager does not write code, design UIs, and configure infrastructure personally. They understand the goal, identify what needs to be done, assign the right people, and combine everyone's output into a deliverable.
A sequential pipeline — agent A passes to agent B passes to agent C — works when the steps are linear and predictable. But most real tasks are not linear. A user asks "Compare the pricing, features, and customer reviews of products X and Y." This requires three parallel research streams that must be synthesized afterward. A sequential chain would process them one at a time, and the last agent would lack visibility into the first agent's work.
flowchart TD
INPUT(["Task input"])
SUPER["Supervisor agent<br/>plans plus monitors"]
W1["Worker 1<br/>research"]
W2["Worker 2<br/>code"]
W3["Worker 3<br/>writing"]
CRITIC{"Output meets<br/>rubric?"}
REWORK["Rework or<br/>retry path"]
SHARED[("Shared scratchpad<br/>and memory")]
OUT(["Final result"])
INPUT --> SUPER
SUPER --> W1 --> CRITIC
SUPER --> W2 --> CRITIC
SUPER --> W3 --> CRITIC
W1 --> SHARED
W2 --> SHARED
W3 --> SHARED
SHARED --> SUPER
CRITIC -->|Pass| OUT
CRITIC -->|Fail| REWORK --> SUPER
style SUPER fill:#4f46e5,stroke:#4338ca,color:#fff
style CRITIC fill:#f59e0b,stroke:#d97706,color:#1f2937
style OUT fill:#059669,stroke:#047857,color:#fff
style SHARED fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
The orchestrator pattern solves this because the orchestrator sees the full picture. It knows which subtasks are needed, which can run in parallel, and how to merge the results.
Here is a practical example — a research orchestrator that delegates to a web researcher and a data analyst:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
from agents import Agent, Runner, function_tool, handoff
@function_tool
def search_web(query: str) -> str:
"""Search the web for information on a topic."""
return f"Web results for '{query}': [article1, article2, article3]"
@function_tool
def analyze_data(dataset: str, question: str) -> str:
"""Analyze a dataset and answer a question about it."""
return f"Analysis of {dataset}: The trend shows 15% growth YoY"
researcher = Agent(
name="Web Researcher",
instructions="""You are a web researcher. Use the search tool to find
relevant information. Return a structured summary of your findings
with sources.""",
tools=[search_web],
)
analyst = Agent(
name="Data Analyst",
instructions="""You are a data analyst. Use the analysis tool to
examine datasets and extract insights. Return findings with
supporting numbers.""",
tools=[analyze_data],
)
orchestrator = Agent(
name="Research Orchestrator",
instructions="""You are a research manager. When given a research
question:
1. Identify whether it needs web research, data analysis, or both.
2. Delegate to the appropriate specialist(s).
3. After receiving specialist outputs, synthesize a unified report.
Always delegate — never try to answer research questions yourself.""",
handoffs=[handoff(researcher), handoff(analyst)],
)
result = Runner.run_sync(
orchestrator,
"What are the latest trends in renewable energy investment?"
)
print(result.final_output)
The orchestrator reads the question, decides it needs web research, and hands off to the Web Researcher. The researcher calls its tools, produces findings, and returns control. The orchestrator then synthesizes the final answer.
The orchestrator's intelligence lies in its routing decisions. There are two approaches to routing:
LLM-based routing — The orchestrator uses the model's reasoning to decide which agent to call. This is flexible but adds latency for the routing decision.
Structured routing with tool calls — You give the orchestrator a routing tool that explicitly selects the target agent:
from agents import Agent, Runner, function_tool, handoff
from pydantic import BaseModel
class TaskPlan(BaseModel):
needs_research: bool
needs_analysis: bool
research_query: str = ""
analysis_question: str = ""
@function_tool
def create_task_plan(
needs_research: bool,
needs_analysis: bool,
research_query: str = "",
analysis_question: str = "",
) -> str:
"""Create a structured plan for which specialists to engage."""
parts = []
if needs_research:
parts.append(f"RESEARCH: {research_query}")
if needs_analysis:
parts.append(f"ANALYSIS: {analysis_question}")
return " | ".join(parts) if parts else "No specialist needed"
orchestrator = Agent(
name="Orchestrator",
instructions="""First call create_task_plan to decide which
specialists are needed. Then delegate to the appropriate agents
based on the plan.""",
tools=[create_task_plan],
handoffs=[handoff(researcher), handoff(analyst)],
)
This gives you an auditable routing decision. You can log the task plan and understand exactly why the orchestrator chose each specialist.
After specialists complete their work, the orchestrator must combine their outputs. The SDK handles this naturally — when a specialist finishes, control returns to the orchestrator with the specialist's output in the conversation history. The orchestrator can then reason about the combined results and produce a final synthesis.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
For more complex aggregation, you can give the orchestrator a formatting tool:
@function_tool
def format_report(
research_findings: str,
analysis_findings: str,
executive_summary: str,
) -> str:
"""Format specialist findings into a structured report."""
return f"""## Executive Summary
{executive_summary}
## Research Findings
{research_findings}
## Data Analysis
{analysis_findings}"""
This ensures consistent output formatting regardless of how the specialists phrase their results.
Production orchestrators need to handle specialist failures gracefully. If the data analyst cannot process a dataset, the orchestrator should still deliver the web research results rather than failing entirely:
orchestrator = Agent(
name="Resilient Orchestrator",
instructions="""You are a research manager. Delegate tasks to
specialists. If a specialist reports that it cannot complete a task,
acknowledge the gap in your final report rather than failing.
Always deliver whatever results are available.
Example: 'Research findings are included below. Data analysis could
not be completed because the dataset was unavailable.'""",
handoffs=[handoff(researcher), handoff(analyst)],
)
Add explicit instructions like "Never answer research questions directly. Always delegate to a specialist." You can reinforce this with a guardrail that checks whether the orchestrator's output contains raw research rather than a synthesis of specialist results.
Yes. This creates a hierarchical architecture where a top-level orchestrator delegates to sub-orchestrators, each managing their own team of specialists. This is useful for very complex tasks like "build a complete market analysis" where each section (competitive landscape, financial projections, customer sentiment) deserves its own orchestration layer.
Keep it under seven. Just like human managers, orchestrator agents become less effective as the number of direct reports grows. The model must reason about which of its many specialists to invoke, and tool/handoff selection accuracy degrades with volume. If you need more than seven specialists, introduce sub-orchestrators.
#OrchestratorPattern #MultiAgentSystems #OpenAIAgentsSDK #TaskDelegation #AgentDesignPatterns #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
Five proven multi-agent architecture patterns built on A2A — orchestrator, peer mesh, hub-and-spoke, marketplace, and tiered specialist.
How to build a safety eval pipeline that runs known jailbreak corpora, prompt-injection attacks, and tool-misuse scenarios on every release — and gates merges on it.
OpenAI's parallel function calling can cut latency in half — or burn money on dependent calls. The architecture, code, and an eval that proves the win.
Honest principal-engineer comparison of the OpenAI Agents SDK and the legacy Assistants API, with a migration checklist and eval-parity strategy so you don't ship regressions.
Langgraph multi-agent supervisor handoffs docs: the supervisor pattern in LangGraph for coordinating specialist agents, with full code, an eval pipeline that scores routing accuracy, and the failure modes to watch for.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.