0Pricing
AI Engineering Academy · Lesson

Orchestrator-Subagent Pattern

Implement the orchestrator-subagent pattern where a planner agent breaks tasks into subtasks, delegates to specialist agents, and synthesizes their results into a final output.

Orchestrator-Subagent Pattern is a free AI Engineering Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Engineering Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Orchestrator-Subagent Mental Model

The orchestrator-subagent pattern is the most widely used multi-agent architecture. An orchestrator agent receives a high-level goal, decomposes it into subtasks, delegates each subtask to a specialized subagent, collects the results, and synthesizes a final output. Think of the orchestrator as a project manager and the subagents as domain experts on the team.

Responsibilities of the Orchestrator

The orchestrator has three core responsibilities: decomposition (breaking the goal into concrete, actionable subtasks), delegation (assigning each subtask to the right specialist), and synthesis (combining the subagent outputs into a coherent result). The orchestrator itself does minimal domain-specific work — its value is in coordination.

from openai import OpenAI

client = OpenAI()

def orchestrator(goal: str) -> dict:
    # Step 1: Decompose
    plan = client.chat.completions.create(
        model='gpt-4o',
        messages=[
            {'role': 'system', 'content': 'You are a task planner. Decompose the goal into subtasks. Return JSON with keys: researcher_task, writer_task, coder_task.'},
            {'role': 'user', 'content': goal}
        ],
        response_format={'type': 'json_object'}
    )
    return plan.choices[0].message.content

Designing Specialized Subagents

Each subagent is optimized for a single responsibility. Specialization means: a focused system prompt that defines the role, a small set of tools (3-5) that are relevant only to that role, and a context window that only contains information needed for that subtask. Focused agents make better decisions and are easier to evaluate and debug.

def create_researcher_agent():
    return {
        'model': 'gpt-4o',
        'system': '''You are a research specialist. Your only job is to find accurate,
            cited information. Return structured findings with sources.
            Do NOT write prose or code - only research findings.''',
        'tools': ['search_web', 'fetch_url', 'query_arxiv'],  # 3 tools only
        'max_iterations': 10
    }

def create_writer_agent():
    return {
        'model': 'gpt-4o',
        'system': '''You are a technical writer. Transform research findings into clear,
            engaging prose. You receive structured data and return polished text.''',
        'tools': ['format_markdown', 'check_readability'],  # 2 tools only
        'max_iterations': 5
    }

The Delegation Protocol

For delegation to work, the orchestrator must pass well-formed task packets to each subagent. A task packet includes: the specific goal for this subagent, the relevant context it needs (not the full conversation history), the expected output format, and any constraints. Poorly formed task packets are the most common source of orchestrator-subagent failures.

from dataclasses import dataclass
from typing import Optional

@dataclass
class TaskPacket:
    task_id: str
    assignee: str          # which subagent receives this
    goal: str              # specific, actionable goal
    context: str           # only relevant background
    output_format: str     # JSON schema or description
    constraints: list[str] # e.g. ['max 500 words', 'cite sources']
    deadline_steps: int    # max iterations allowed

def delegate(packet: TaskPacket, subagent_fn) -> str:
    prompt = f'Goal: {packet.goal}\nContext: {packet.context}\nOutput format: {packet.output_format}\nConstraints: {packet.constraints}'
    return subagent_fn(prompt)

Collecting and Synthesizing Results

Once subagents complete their work, the orchestrator must synthesize the results. This is not just concatenation. The orchestrator must resolve conflicts between subagent outputs, fill gaps, maintain a consistent voice or format, and produce a final result that is coherent as a whole. The synthesis step is where the orchestrator's reasoning power is most needed.

def synthesize_results(research: str, draft: str, code: str, goal: str) -> str:
    synthesis_prompt = f'''You are given outputs from three specialist agents.
    Your job is to synthesize them into one coherent final answer for this goal: {goal}

    RESEARCH FINDINGS:
    {research}

    WRITTEN DRAFT:
    {draft}

    CODE EXAMPLES:
    {code}

    Resolve any conflicts, fill gaps, and produce a unified final response.
    '''
    response = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': synthesis_prompt}]
    )
    return response.choices[0].message.content

Sequential vs Parallel Delegation

Orchestrators can delegate tasks sequentially (one subagent's output feeds the next) or in parallel (multiple subagents work simultaneously). Sequential delegation is simpler and works when later steps depend on earlier results. Parallel delegation is faster when subtasks are independent and can run concurrently, often reducing wall-clock time by 3-5x.

import asyncio

# Sequential: writer needs research first
async def sequential_pipeline(goal):
    research = await researcher_agent(goal)
    draft = await writer_agent(research)  # depends on research
    return draft

# Parallel: all three tasks are independent
async def parallel_pipeline(topics):
    tasks = [
        researcher_agent(topics[0]),
        researcher_agent(topics[1]),
        researcher_agent(topics[2])
    ]
    results = await asyncio.gather(*tasks)  # runs simultaneously
    return orchestrator_synthesize(results)

Handling Subagent Failures

Subagents will occasionally fail — they may return malformed output, exceed their iteration limit, or encounter an error. The orchestrator must handle these failures gracefully. Common strategies include: retry with a clarified task packet, fallback to a simpler approach, skip the failed subtask and note it in the final output, or escalate to human review.

def delegate_with_retry(packet: TaskPacket, subagent_fn, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            result = subagent_fn(packet)
            validate_output(result, packet.output_format)
            return result
        except ValidationError as e:
            if attempt < max_retries:
                # Clarify the task for retry
                packet.goal += f'\n\nPrevious attempt failed: {str(e)}. Please fix.'
                print(f'Retry {attempt + 1} for task {packet.task_id}')
            else:
                return {'error': str(e), 'task_id': packet.task_id, 'status': 'failed'}

Prompting the Orchestrator Well

The orchestrator's system prompt is critical to the quality of the whole pipeline. It should clearly define: what subagents are available and what each specializes in, how to format task packets, when to run subtasks in parallel vs. sequentially, how to handle incomplete or conflicting results, and what constitutes a successful final output.

ORCHESTRATOR_SYSTEM_PROMPT = '''
You coordinate a team of specialist agents to complete complex tasks.

Available agents:
- researcher: Finds and cites factual information. Input: question string. Output: JSON with findings and sources.
- writer: Writes polished prose from structured data. Input: JSON findings. Output: markdown text.
- coder: Writes Python code. Input: natural language spec. Output: Python code string.

Workflow:
1. Analyze the goal and decide which agents are needed.
2. Identify dependencies: can any tasks run in parallel?
3. Delegate tasks with clear, specific goals.
4. Validate each result before proceeding.
5. Synthesize all results into a unified final answer.
'''

Context Isolation Is a Feature

One major advantage of the orchestrator-subagent pattern is context isolation. Each subagent sees only the information relevant to its subtask, not the full history of the entire project. This keeps each subagent's context window small, preventing the context exhaustion that plagues single agents. The orchestrator maintains the global view while subagents maintain focused local views.

Tracing and Observability

Multi-agent systems are harder to debug than single agents because failures can occur at any delegation step. Always add structured logging to track: which tasks were delegated, to which subagent, what the input and output were, how long each took, and whether retries were needed. Tools like LangSmith and Langfuse support multi-agent trace visualization.

import time

def logged_delegate(packet: TaskPacket, subagent_fn):
    start = time.time()
    print(f'[DELEGATE] task_id={packet.task_id} assignee={packet.assignee}')
    print(f'[INPUT] {packet.goal[:100]}...')
    
    result = subagent_fn(packet)
    elapsed = time.time() - start
    
    print(f'[RESULT] task_id={packet.task_id} elapsed={elapsed:.2f}s')
    print(f'[OUTPUT] {str(result)[:100]}...')
    return result

Real-World Pattern: Report Generator

A concrete example of the orchestrator-subagent pattern is a competitive analysis report generator. The orchestrator receives 'Write a competitor analysis for Company X'. It delegates: research subtask to a researcher agent (finds pricing, features, reviews), analysis subtask to an analyst agent (identifies strengths/weaknesses), and formatting subtask to a writer agent (produces the final report). Each specialist does what it does best.

Quick Check

Test your understanding of the orchestrator-subagent pattern from this lesson.

Lesson Recap

In this lesson you learned: the orchestrator-subagent pattern uses a coordinator agent to decompose and delegate work to specialists, task packets must clearly define goal, context, and expected output format for delegation to work, and context isolation keeps each subagent's window small while the orchestrator maintains the global view. Next up we build multi-agent pipelines with LangGraph.

Frequently asked questions

Is the “Orchestrator-Subagent Pattern” lesson free?

Yes — the full text of “Orchestrator-Subagent Pattern” is free to read here on the web, and the AI Engineering Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Engineering Academy course, upgrade to CoddyKit PRO.

What will I learn in “Orchestrator-Subagent Pattern”?

Implement the orchestrator-subagent pattern where a planner agent breaks tasks into subtasks, delegates to specialist agents, and synthesizes their results into a final output. You practise AI Engineering Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Engineering Academy?

No prior experience is required. AI Engineering Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Orchestrator-Subagent Pattern” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Engineering Academy lesson?

Yes. Every AI Engineering Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Why Single Agents Hit a Wall
  2. Orchestrator-Subagent Pattern
  3. Building Multi-Agent Pipelines with LangGraph
  4. Shared Memory and Inter-Agent Communication
← Back to AI Engineering Academy