Orchestration Patterns: Coordinator, Swarm, and Pipeline

James Phoenix
James Phoenix

Summary

Multi-agent systems require orchestration to coordinate work across specialized agents. Three fundamental patterns address different coordination needs: the Coordinator pattern uses a central orchestrator to delegate tasks to specialists and synthesize results; the Swarm pattern deploys multiple agents in parallel for breadth or redundancy; the Pipeline pattern chains agents sequentially where each stage transforms output for the next. Choosing the right pattern depends on task structure, latency requirements, and quality goals.

The Problem

When tasks exceed what a single agent handles well, you need multiple agents working together. But coordination introduces new challenges:

  • Delegation decisions: How does work get assigned to specialists?
  • Result synthesis: How do outputs from multiple agents combine?
  • Error propagation: What happens when one agent fails mid-workflow?
  • Context isolation: How do agents share relevant information without pollution?
  • Latency vs quality: When is parallel execution worth the coordination overhead?

Without clear orchestration patterns, multi-agent systems become tangled webs of ad-hoc communication.

The Three Patterns

Pattern 1: Coordinator (Hub and Spoke)

A central orchestrator receives tasks, delegates to specialists, and synthesizes final output.

                    ┌───────────────────┐
                    │   ORCHESTRATOR    │
                    │  (Coordinator)    │
                    └─────────┬─────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
        ▼                     ▼                     ▼
┌───────────────┐    ┌───────────────┐    ┌───────────────┐
│    Backend    │    │   Frontend    │    │      QA       │
│   Engineer    │    │   Engineer    │    │   Engineer    │
└───────────────┘    └───────────────┘    └───────────────┘
        │                     │                     │
        └─────────────────────┼─────────────────────┘
                              │
                              ▼
                    ┌───────────────────┐
                    │   Final Result    │
                    └───────────────────┘

How it works:

  1. Orchestrator receives the high-level task
  2. Orchestrator analyzes task and identifies required specialists
  3. Orchestrator delegates sub-tasks with appropriate context
  4. Specialists complete work and return results
  5. Orchestrator synthesizes results into cohesive output

Implementation:

interface CoordinatorConfig {
  specialists: Map<string, AgentConfig>;
  synthesisStrategy: 'merge' | 'sequence' | 'vote';
}

async function coordinatorPattern(
  task: string,
  config: CoordinatorConfig
): Promise<Result<string>> {
  // Step 1: Analyze task
  const analysis = await analyzeTask(task);
  const requiredSpecialists = analysis.identifySpecialists();

  // Step 2: Delegate to specialists
  const delegations = requiredSpecialists.map(specialist => ({
    agent: config.specialists.get(specialist.role),
    subTask: specialist.extractedTask,
    context: specialist.relevantContext,
  }));

  // Step 3: Execute (can be parallel or sequential)
  const results = await Promise.all(
    delegations.map(d => executeAgent(d.agent, d.subTask, d.context))
  );

  // Step 4: Synthesize
  return synthesizeResults(results, config.synthesisStrategy);
}

When to use:

Udemy Bestseller

Learn Prompt Engineering

My O'Reilly book adapted for hands-on learning. Build production-ready prompts with practical exercises.

4.5/5 rating
306,000+ learners
View Course
  • Tasks require multiple distinct specialties
  • Final output needs coherent synthesis
  • You want centralized error handling and retry logic
  • Task decomposition benefits from high-level understanding

Example: Feature implementation

// User request: "Add user authentication with OAuth"

const coordinator = async (task: string) => {
  // Delegate to specialists
  const backendResult = await spawnAgent('backend-engineer', {
    task: 'Implement OAuth routes and token management',
    context: loadContext(['root', 'backend', 'auth']),
  });

  const frontendResult = await spawnAgent('frontend-engineer', {
    task: 'Create login/logout UI components',
    context: loadContext(['root', 'frontend', 'ui']),
  });

  const qaResult = await spawnAgent('qa-engineer', {
    task: 'Write authentication tests',
    context: loadContext(['root', 'testing', 'auth']),
    dependencies: [backendResult, frontendResult],
  });

  // Synthesize into final report
  return synthesize([backendResult, frontendResult, qaResult]);
};

Pattern 2: Swarm (Parallel Execution)

Multiple agents work simultaneously on related aspects of a task, providing breadth or redundancy.

              ┌────────────────────────────────────┐
              │         TASK DISPATCHER            │
              └──────────────┬─────────────────────┘
                             │
     ┌───────────┬───────────┼───────────┬───────────┐
     │           │           │           │           │
     ▼           ▼           ▼           ▼           ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Agent 1 │ │ Agent 2 │ │ Agent 3 │ │ Agent 4 │ │ Agent 5 │
│ (API)   │ │ (Auth)  │ │ (DB)    │ │ (UI)    │ │ (Tests) │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
     │           │           │           │           │
     └───────────┴───────────┼───────────┴───────────┘
                             │
                             ▼
                    ┌───────────────────┐
                    │     AGGREGATOR    │
                    │  (Merge Results)  │
                    └───────────────────┘

How it works:

  1. Dispatcher divides work into parallel chunks
  2. Multiple agents execute simultaneously
  3. Aggregator collects and merges results
  4. Conflicts are resolved by voting, priority, or synthesis

Two swarm variants:

Variant A: Many perspectives (different agents, same task)

// Get multiple perspectives on code review
async function swarmReview(code: string): Promise<ReviewResult> {
  const agents = [
    { role: 'security-auditor', focus: 'vulnerabilities' },
    { role: 'performance-reviewer', focus: 'efficiency' },
    { role: 'maintainability-reviewer', focus: 'readability' },
    { role: 'architecture-reviewer', focus: 'patterns' },
  ];

  const reviews = await Promise.all(
    agents.map(agent =>
      spawnAgent(agent.role, {
        task: `Review this code for ${agent.focus}`,
        code,
      })
    )
  );

  // Aggregate unique findings
  return aggregateReviews(reviews);
}

Variant B: Same perspective, multiple times (redundancy for reliability)

// Run same analysis 3 times, take majority vote
async function reliableAnalysis(task: string): Promise<AnalysisResult> {
  const results = await Promise.all([
    spawnAgent('analyzer', { task, temperature: 0.1 }),
    spawnAgent('analyzer', { task, temperature: 0.3 }),
    spawnAgent('analyzer', { task, temperature: 0.5 }),
  ]);

  // Majority vote on conclusions
  return majorityVote(results);
}

When to use:

  • Task benefits from multiple perspectives
  • You need confidence through redundancy
  • Work is naturally parallelizable (no dependencies)
  • Latency is critical and subtasks are independent

Pattern 3: Pipeline (Sequential Stages)

Agents process work in sequence, each stage transforming output for the next.

┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│ Stage 1 │────▶│ Stage 2 │────▶│ Stage 3 │────▶│ Stage 4 │
│ (Plan)  │     │ (Code)  │     │ (Test)  │     │ (Review)│
└─────────┘     └─────────┘     └─────────┘     └─────────┘
     │               │               │               │
     ▼               ▼               ▼               ▼
  [Plan]          [Code]         [Tested]       [Approved]

How it works:

  1. Task enters first stage
  2. Each stage transforms input and passes output to next
  3. Later stages can reject and send work back
  4. Final stage produces finished output

Implementation:

interface PipelineStage {
  agent: AgentConfig;
  validate: (output: unknown) => boolean;
  canReject: boolean;
}

async function pipelinePattern(
  input: string,
  stages: PipelineStage[]
): Promise<Result<string>> {
  let current = input;

  for (const stage of stages) {
    const result = await executeAgent(stage.agent, current);

    if (!result.success) {
      return result; // Pipeline fails
    }

    if (!stage.validate(result.data)) {
      if (stage.canReject) {
        // Send back to previous stage with feedback
        return pipelinePattern(
          addFeedback(current, result.feedback),
          stages
        );
      }
      return { success: false, error: 'Validation failed' };
    }

    current = result.data;
  }

  return { success: true, data: current };
}

Classic pipeline: Actor-Critic

// Writer produces, Critic reviews, iterate until approved
async function actorCriticPipeline(task: string): Promise<string> {
  let draft = await spawnAgent('writer', { task });
  let approved = false;
  let iterations = 0;
  const maxIterations = 5;

  while (!approved && iterations < maxIterations) {
    const critique = await spawnAgent('critic', {
      task: 'Review this code for issues',
      code: draft,
      readOnly: true, // Critic cannot modify, only report
    });

    if (critique.issues.length === 0) {
      approved = true;
    } else {
      // Writer addresses critique
      draft = await spawnAgent('writer', {
        task: 'Fix these issues',
        code: draft,
        issues: critique.issues,
      });
    }

    iterations++;
  }

  return draft;
}

When to use:

  • Work has natural sequential dependencies
  • Later stages need earlier outputs as input
  • You want quality gates between stages
  • Feedback loops improve output quality

Combining Patterns

Real systems often combine patterns:

// Coordinator delegates to specialists (some run as pipelines, some as swarms)

async function hybridOrchestration(feature: string): Promise<Result<string>> {
  // Coordinator decomposes task
  const plan = await planFeature(feature);

  // Backend: Pipeline (code -> test -> review)
  const backendResult = await pipelinePattern(plan.backend, [
    { agent: 'backend-coder', validate: compiles },
    { agent: 'backend-tester', validate: testsPass },
    { agent: 'backend-reviewer', validate: noIssues, canReject: true },
  ]);

  // Security: Swarm (multiple auditors in parallel)
  const securityResult = await swarmPattern(backendResult.data, [
    'owasp-auditor',
    'injection-auditor',
    'auth-auditor',
  ]);

  // Coordinator synthesizes
  return synthesize([backendResult, securityResult]);
}

Pattern Selection Guide

Criterion Coordinator Swarm Pipeline
Task structure Multiple specialties Parallelizable Sequential stages
Dependencies Mixed None Strong
Latency priority Medium High Low
Quality priority Medium High (redundancy) High (gates)
Error handling Centralized Per-agent Stage-by-stage
Context needs High (synthesis) Low (isolated) Medium (chained)

Common Pitfalls

Pitfall 1: Over-Coordination

// BAD: Coordinator for simple task
const overkill = async (task: string) => {
  const coordinator = spawnAgent('orchestrator', task);
  // Simple task doesn't need delegation overhead
};

// GOOD: Direct execution for simple tasks
const appropriate = async (task: string) => {
  if (isSimpleTask(task)) {
    return spawnAgent('generalist', task);
  }
  return coordinatorPattern(task);
};

Pitfall 2: Swarm Without Aggregation Strategy

// BAD: No conflict resolution
const results = await Promise.all(agents.map(a => execute(a)));
return results; // What if they disagree?

// GOOD: Explicit aggregation
const results = await Promise.all(agents.map(a => execute(a)));
return aggregateWithVoting(results, { threshold: 0.6 });

Pitfall 3: Pipeline Without Exit Conditions

// BAD: Infinite loop risk
while (!approved) {
  draft = await improve(draft);
  approved = await review(draft);
}

// GOOD: Bounded iterations
let iterations = 0;
while (!approved && iterations < MAX_ITERATIONS) {
  draft = await improve(draft);
  approved = await review(draft);
  iterations++;
}

Related

References

Topics
Agent ArchitectureAgent CoordinationCoordinator PatternDelegationMulti AgentOrchestrationParallel ExecutionPipeline PatternSequential ProcessingSwarm Pattern

More Insights

Cover Image for Own Your Control Plane

Own Your Control Plane

If you use someone else’s task manager, you inherit all of their abstractions. In a world where LLMs make software a solved problem, the cost of ownership has flipped.

James Phoenix
James Phoenix
Cover Image for Indexed PRD and Design Doc Strategy

Indexed PRD and Design Doc Strategy

A documentation-driven development pattern where a single `index.md` links all PRDs and design documents, creating navigable context for both humans and AI agents.

James Phoenix
James Phoenix