Skip to main content

Multi-Agent Orchestration

Complex tasks often require multiple specialized agents working together. AgentLIB provides the Orchestrator class to coordinate agent interactions, manage state, and create sophisticated multi-agent workflows.

Overview

The Orchestrator pattern uses:
  • Planner Agent: A meta-agent that coordinates the workflow
  • Sub-Agents: Specialized agents for specific tasks (research, coding, review, etc.)
  • Dynamic Routing: The planner decides which agents to invoke and when
  • State Management: Centralized tracking of agent interactions and outputs

Basic Orchestrator Setup

Here’s a complete example from the AgentLIB source:
import { createAgent } from '@agentlib/core'
import { openai } from '@agentlib/openai'
import { Orchestrator } from '@agentlib/orchestrator'
import '@agentlib/reasoning' // Load reasoning engines

const provider = openai({
  apiKey: process.env.OPENAI_API_KEY!,
  model: 'gpt-4o',
  baseURL: process.env.OPENAI_BASE_URL
})

// 1. Define specialized sub-agents
const researcher = createAgent({
  name: 'researcher',
  description: 'Finds detailed information about specific topics.',
  systemPrompt: 'You are a researcher. Provide concise summaries of facts.'
})
  .provider(provider)
  .reasoning('react')

const coder = createAgent({
  name: 'coder',
  description: 'Writes efficient code based on requirements.',
  systemPrompt: 'You are an expert coder. Wrap your code in markdown blocks.'
})
  .provider(provider)
  .reasoning('react')

const critic = createAgent({
  name: 'critic',
  description: 'Reviews work and provides a score from 0 to 1.',
  systemPrompt: 'You are a critical reviewer. Always end your message with "Score: X.X"'
})
  .provider(provider)
  .reasoning('react')

// 2. Define the planner (meta-agent)
const planner = createAgent({
  name: 'planner',
  description: 'Main coordinator that uses sub-agents to solve complex tasks.',
  systemPrompt: 'You are a project manager. Use the available agents to research, code, and review.'
})
  .provider(provider)
  .reasoning('react')

// 3. Initialize the Orchestrator
const orchestrator = new Orchestrator(planner, {
  agents: {
    researcher,
    coder,
    critic
  },
  maxSteps: 10,
  summarize: true,
  exposeAgentsAsTools: true,
  context: {
    company: 'ACME Corp',
    constraints: 'Follow ACME Corp coding standards'
  },
  onStep: (step) => {
    console.log(`[Step] Agent: ${step.agent} | Type: ${step.type}`)
    
    // Conditional abort based on step content
    if (step.agent === 'critic' && step.content?.includes('Score: 0.3')) {
      console.warn('Low quality work detected! Aborting...')
      return 'abort'
    }
  }
})

// 4. Add event listeners
orchestrator.on('agent:invoke', ({ agent, prompt }) => {
  console.log(`\n🚀 Invoking agent: ${agent}`)
  console.log(`Prompt: ${prompt}`)
})

orchestrator.on('agent:completed', ({ agent, output }) => {
  console.log(`\n✅ Agent ${agent} finished!`)
  console.log(`Output: ${output.substring(0, 100)}...`)
})

// 5. Run the orchestrator
console.log('--- Starting Orchestration ---')
await orchestrator.run('Design and implement a scalable image processing system.')
console.log('\n--- Orchestration Finished ---')

// 6. Access final states
const states = orchestrator.getAllStates()
console.log('\nFinal States:', states.map(s => `${s.name}: ${s.status}`))

Orchestrator Configuration

Core Options

interface OrchestratorOptions {
  // Map of available sub-agents
  agents: Record<string, AgentInstance>
  
  // Maximum steps before auto-termination
  maxSteps?: number
  
  // Auto-summarize sub-agent outputs for context efficiency
  summarize?: boolean
  
  // Expose sub-agents as tools to the planner
  exposeAgentsAsTools?: boolean
  
  // Shared context available to all agents
  context?: Record<string, any>
  
  // Step-by-step callback for monitoring/control
  onStep?: (step: OrchestrationStep) => void | 'abort' | 'continue'
}

Example 1: Research Pipeline

A multi-stage research workflow:
import { createAgent } from '@agentlib/core'
import { openai } from '@agentlib/openai'
import { Orchestrator } from '@agentlib/orchestrator'
import { defineTool } from '@agentlib/core'

const provider = openai({ apiKey: process.env.OPENAI_API_KEY! })

// Define specialized tools
const webSearchTool = defineTool({
  schema: {
    name: 'web_search',
    description: 'Search the web for current information',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string', description: 'Search query' }
      },
      required: ['query']
    }
  },
  async execute({ query }) {
    // Integration with search API
    return { results: [`Information about ${query}`] }
  }
})

const databaseQueryTool = defineTool({
  schema: {
    name: 'query_database',
    description: 'Query internal company database',
    parameters: {
      type: 'object',
      properties: {
        sql: { type: 'string', description: 'SQL query' }
      },
      required: ['sql']
    }
  },
  async execute({ sql }) {
    // Execute query against DB
    return { rows: [] }
  }
})

// Create specialized agents
const webResearcher = createAgent({
  name: 'web-researcher',
  description: 'Searches the web for current information',
  systemPrompt: 'You search the web and summarize findings.'
})
  .provider(provider)
  .tool(webSearchTool)
  .reasoning('react')

const dataAnalyst = createAgent({
  name: 'data-analyst',
  description: 'Queries and analyzes internal data',
  systemPrompt: 'You analyze company data and provide insights.'
})
  .provider(provider)
  .tool(databaseQueryTool)
  .reasoning('react')

const synthesizer = createAgent({
  name: 'synthesizer',
  description: 'Combines research from multiple sources',
  systemPrompt: 'You synthesize information from multiple sources into coherent reports.'
})
  .provider(provider)
  .reasoning('cot')

const planner = createAgent({
  name: 'research-coordinator',
  systemPrompt: `You coordinate research tasks. Available agents:
- web-researcher: For current web information
- data-analyst: For internal company data
- synthesizer: For combining findings into reports

Always gather information before synthesizing.`
})
  .provider(provider)
  .reasoning('planner')

const orchestrator = new Orchestrator(planner, {
  agents: {
    'web-researcher': webResearcher,
    'data-analyst': dataAnalyst,
    synthesizer
  },
  maxSteps: 15,
  summarize: true,
  exposeAgentsAsTools: true,
  onStep: (step) => {
    console.log(`[${step.agent}] ${step.type}:`, step.content?.substring(0, 80))
  }
})

const result = await orchestrator.run(
  'Research market trends for AI agents in 2025, including both public data and our internal sales figures.'
)

console.log('Research Report:', result.output)

Example 2: Code Generation Pipeline

A system that designs, implements, tests, and reviews code:
import { createAgent } from '@agentlib/core'
import { openai } from '@agentlib/openai'
import { Orchestrator } from '@agentlib/orchestrator'

const provider = openai({ apiKey: process.env.OPENAI_API_KEY! })

// Architect designs the system
const architect = createAgent({
  name: 'architect',
  description: 'Designs system architecture and API contracts',
  systemPrompt: 'You design software architecture. Output JSON specifications.'
})
  .provider(provider)
  .reasoning('cot')

// Developer implements the code
const developer = createAgent({
  name: 'developer',
  description: 'Implements code based on specifications',
  systemPrompt: 'You write clean, well-documented code following specifications.'
})
  .provider(provider)
  .reasoning('react')

// Tester creates test cases
const tester = createAgent({
  name: 'tester',
  description: 'Creates comprehensive test cases',
  systemPrompt: 'You write thorough test cases covering edge cases.'
})
  .provider(provider)
  .reasoning('react')

// Reviewer provides code review
const reviewer = createAgent({
  name: 'reviewer',
  description: 'Reviews code for quality, security, and best practices',
  systemPrompt: 'You review code critically. Provide scores and actionable feedback.'
})
  .provider(provider)
  .reasoning('reflect')

// Engineering manager coordinates
const engineeringManager = createAgent({
  name: 'engineering-manager',
  systemPrompt: `You manage the development pipeline:
1. architect designs the system
2. developer implements
3. tester creates tests
4. reviewer provides feedback
5. If score < 0.7, iterate with developer

Only proceed when quality standards are met.`
})
  .provider(provider)
  .reasoning('planner')

const orchestrator = new Orchestrator(engineeringManager, {
  agents: { architect, developer, tester, reviewer },
  maxSteps: 20,
  context: {
    language: 'TypeScript',
    framework: 'Node.js',
    qualityThreshold: 0.7
  },
  onStep: (step) => {
    // Track quality scores
    if (step.agent === 'reviewer') {
      const scoreMatch = step.content?.match(/Score: ([0-9.]+)/)
      if (scoreMatch) {
        const score = parseFloat(scoreMatch[1])
        console.log(`📊 Quality Score: ${score}`)
        if (score < 0.7) {
          console.log('⚠️  Below quality threshold, revision needed')
        }
      }
    }
  }
})

const result = await orchestrator.run(
  'Create a REST API for user authentication with JWT tokens'
)

Example 3: Dynamic Agent Routing

Route requests to appropriate specialists based on content:
import { createAgent } from '@agentlib/core'
import { openai } from '@agentlib/openai'
import { Orchestrator } from '@agentlib/orchestrator'

const provider = openai({ apiKey: process.env.OPENAI_API_KEY! })

// Domain specialists
const specialists = {
  finance: createAgent({
    name: 'finance-expert',
    description: 'Expert in financial analysis and accounting',
    systemPrompt: 'You are a financial expert. Provide detailed financial analysis.'
  }).provider(provider).reasoning('cot'),
  
  legal: createAgent({
    name: 'legal-expert',
    description: 'Expert in legal compliance and contracts',
    systemPrompt: 'You are a legal expert. Analyze from a legal compliance perspective.'
  }).provider(provider).reasoning('cot'),
  
  technical: createAgent({
    name: 'technical-expert',
    description: 'Expert in technical implementation',
    systemPrompt: 'You are a technical expert. Focus on implementation details.'
  }).provider(provider).reasoning('react'),
  
  marketing: createAgent({
    name: 'marketing-expert',
    description: 'Expert in marketing strategy',
    systemPrompt: 'You are a marketing expert. Analyze market positioning and strategy.'
  }).provider(provider).reasoning('cot')
}

const router = createAgent({
  name: 'router',
  systemPrompt: `You route questions to the appropriate specialist:
- finance-expert: Financial, accounting, budgets
- legal-expert: Contracts, compliance, regulations
- technical-expert: Implementation, architecture, code
- marketing-expert: Marketing, branding, positioning

You may consult multiple specialists if needed.`
})
  .provider(provider)
  .reasoning('react')

const orchestrator = new Orchestrator(router, {
  agents: specialists,
  maxSteps: 10,
  exposeAgentsAsTools: true,
  summarize: true,
  onStep: (step) => {
    if (step.type === 'agent:invoke') {
      console.log(`🎯 Routing to: ${step.agent}`)
    }
  }
})

// The router will automatically select the right specialist(s)
await orchestrator.run('What are the tax implications of our new SaaS pricing model?')
// Expected: Routes to finance-expert and legal-expert

await orchestrator.run('How should we architect our microservices deployment?')
// Expected: Routes to technical-expert

Monitoring and Control

Step Monitoring

const orchestrator = new Orchestrator(planner, {
  agents: { agent1, agent2 },
  onStep: (step) => {
    // Log all steps
    console.log(`[${step.timestamp}] ${step.agent}: ${step.type}`)
    
    // Conditional abort
    if (step.content?.includes('ERROR')) {
      return 'abort'
    }
    
    // Collect metrics
    metrics.recordStep(step)
    
    return 'continue'
  }
})

Event Listeners

// Agent lifecycle events
orchestrator.on('agent:invoke', ({ agent, prompt }) => {
  console.log(`Invoking ${agent}`)
})

orchestrator.on('agent:completed', ({ agent, output }) => {
  console.log(`${agent} completed`)
})

orchestrator.on('agent:failed', ({ agent, error }) => {
  console.error(`${agent} failed:`, error)
})

// Orchestration events
orchestrator.on('orchestration:start', ({ input }) => {
  console.log('Starting orchestration')
})

orchestrator.on('orchestration:complete', ({ output }) => {
  console.log('Orchestration complete')
})

State Inspection

// Get all agent states
const states = orchestrator.getAllStates()
states.forEach(state => {
  console.log(`${state.name}:`, {
    status: state.status,
    invocations: state.invocations,
    lastOutput: state.lastOutput?.substring(0, 50)
  })
})

// Get specific agent state
const researcherState = orchestrator.getAgentState('researcher')
console.log('Researcher invoked:', researcherState.invocations, 'times')

Best Practices

  1. Clear Agent Descriptions: The planner uses descriptions to decide which agent to invoke
  2. Appropriate Reasoning Engines: Match engines to agent roles (e.g., planner for coordinators, react for tool-users)
  3. Set Reasonable Step Limits: Prevent infinite loops with maxSteps
  4. Use Summarization: Enable summarize: true for long-running workflows to manage context
  5. Monitor Progress: Use onStep callback for visibility and control
  6. Handle Failures Gracefully: Add error handling in event listeners
  7. Share Context: Use the context option for shared state across agents

Advanced Patterns

Hierarchical Orchestration

// Sub-orchestrators for complex workflows
const researchOrchestrator = new Orchestrator(researchPlanner, {
  agents: { webResearcher, dataAnalyst }
})

const developmentOrchestrator = new Orchestrator(devPlanner, {
  agents: { architect, developer, tester }
})

// Main orchestrator coordinates sub-orchestrators
const mainOrchestrator = new Orchestrator(mainPlanner, {
  agents: {
    research: researchOrchestrator,
    development: developmentOrchestrator
  }
})

Conditional Workflows

const orchestrator = new Orchestrator(planner, {
  agents: { agent1, agent2, agent3 },
  onStep: (step) => {
    // Route based on intermediate results
    if (step.agent === 'agent1' && step.content?.includes('complex')) {
      // Inject additional context for complex cases
      return { continueWith: 'agent3', skipAgent2: true }
    }
  }
})

Next Steps

Build docs developers (and LLMs) love