From Zero to AI

Lesson 5.3: Multi-Agent Systems

Duration: 75 minutes

Learning Objectives

By the end of this lesson, you will be able to:

  1. Explain when and why to use multiple agents
  2. Design agent architectures with specialized roles
  3. Implement communication patterns between agents
  4. Build a multi-agent system for complex tasks

Why Multiple Agents

Single agents work well for focused tasks, but complex problems benefit from specialization. Just as a company has departments with different expertise, a multi-agent system has agents with different capabilities.

Benefits of multi-agent systems:

  1. Specialization: Each agent can be optimized for specific tasks
  2. Parallelization: Multiple agents can work simultaneously
  3. Quality Control: Agents can review each other's work
  4. Modularity: Add or modify agents without changing the entire system
  5. Scalability: Handle larger problems by adding more agents

Multi-Agent Architectures

1. Hierarchical Architecture

A supervisor agent coordinates specialized worker agents:

                    ┌────────────────┐
                    │   Supervisor   │
                    │     Agent      │
                    └───────┬────────┘
                            │
            ┌───────────────┼───────────────┐
            │               │               │
            ▼               ▼               ▼
     ┌──────────┐    ┌──────────┐    ┌──────────┐
     │ Research │    │  Writer  │    │ Reviewer │
     │  Agent   │    │  Agent   │    │  Agent   │
     └──────────┘    └──────────┘    └──────────┘

2. Collaborative Architecture

Agents work together as peers, passing work between them:

     ┌──────────┐         ┌──────────┐
     │ Research │ ──────▶ │  Writer  │
     │  Agent   │         │  Agent   │
     └──────────┘         └────┬─────┘
           ▲                   │
           │                   ▼
           │              ┌──────────┐
           └───────────── │ Reviewer │
              feedback    │  Agent   │
                          └──────────┘

3. Debate Architecture

Agents argue different perspectives to reach better conclusions:

     ┌──────────┐                    ┌──────────┐
     │  Agent A │ ──── debate ────── │  Agent B │
     │  (Pro)   │                    │  (Con)   │
     └────┬─────┘                    └────┬─────┘
          │                               │
          └───────────┬───────────────────┘
                      │
                      ▼
               ┌──────────┐
               │   Judge  │
               │  Agent   │
               └──────────┘

Building Agent Communication

First, define how agents communicate:

interface AgentMessage {
  from: string;
  to: string;
  type: 'request' | 'response' | 'broadcast';
  content: string;
  metadata?: Record<string, unknown>;
}

interface Agent {
  name: string;
  role: string;
  process(message: AgentMessage): Promise<AgentMessage | null>;
}

class MessageBus {
  private agents: Map<string, Agent> = new Map();
  private history: AgentMessage[] = [];

  register(agent: Agent): void {
    this.agents.set(agent.name, agent);
  }

  async send(message: AgentMessage): Promise<AgentMessage | null> {
    this.history.push(message);

    if (message.to === 'broadcast') {
      // Send to all agents except sender
      const responses: AgentMessage[] = [];
      for (const [name, agent] of this.agents) {
        if (name !== message.from) {
          const response = await agent.process(message);
          if (response) responses.push(response);
        }
      }
      return responses.length > 0 ? responses[0] : null;
    }

    const targetAgent = this.agents.get(message.to);
    if (!targetAgent) {
      throw new Error(`Agent ${message.to} not found`);
    }

    return targetAgent.process(message);
  }

  getHistory(): AgentMessage[] {
    return [...this.history];
  }
}

Implementing Specialized Agents

Create agents with specific roles:

import OpenAI from 'openai';

class SpecializedAgent implements Agent {
  name: string;
  role: string;
  private client: OpenAI;
  private model: string;
  private systemPrompt: string;

  constructor(
    client: OpenAI,
    config: { name: string; role: string; model: string; systemPrompt: string }
  ) {
    this.client = client;
    this.name = config.name;
    this.role = config.role;
    this.model = config.model;
    this.systemPrompt = config.systemPrompt;
  }

  async process(message: AgentMessage): Promise<AgentMessage | null> {
    const response = await this.client.chat.completions.create({
      model: this.model,
      messages: [
        { role: 'system', content: this.systemPrompt },
        {
          role: 'user',
          content: `Message from ${message.from}:\n${message.content}`,
        },
      ],
    });

    return {
      from: this.name,
      to: message.from,
      type: 'response',
      content: response.choices[0].message.content || '',
    };
  }
}

// Create specialized agents
const client = new OpenAI();

const researchAgent = new SpecializedAgent(client, {
  name: 'researcher',
  role: 'Research Specialist',
  model: 'gpt-4o',
  systemPrompt: `You are a research specialist. Your job is to:
- Gather and analyze information on given topics
- Identify key facts and data points
- Provide well-sourced, accurate information
- Structure findings in a clear format

Focus on accuracy and comprehensiveness.`,
});

const writerAgent = new SpecializedAgent(client, {
  name: 'writer',
  role: 'Content Writer',
  model: 'gpt-4o',
  systemPrompt: `You are a professional content writer. Your job is to:
- Transform research into engaging, readable content
- Maintain clear structure and flow
- Use appropriate tone for the audience
- Create compelling narratives from facts

Focus on clarity and engagement.`,
});

const reviewerAgent = new SpecializedAgent(client, {
  name: 'reviewer',
  role: 'Quality Reviewer',
  model: 'gpt-4o',
  systemPrompt: `You are a quality reviewer. Your job is to:
- Check content for accuracy and consistency
- Identify gaps or unclear sections
- Suggest specific improvements
- Verify claims are supported

Be constructive but thorough in your feedback.`,
});

Supervisor Pattern

The supervisor coordinates the workflow:

class SupervisorAgent {
  private client: OpenAI;
  private model: string;
  private messageBus: MessageBus;
  private workers: string[];

  constructor(client: OpenAI, model: string, messageBus: MessageBus, workers: string[]) {
    this.client = client;
    this.model = model;
    this.messageBus = messageBus;
    this.workers = workers;
  }

  async orchestrate(task: string): Promise<string> {
    // Step 1: Plan the work
    const plan = await this.createPlan(task);
    console.log('Work plan:', plan);

    // Step 2: Execute each step
    let context = '';
    for (const step of plan.steps) {
      const result = await this.delegateStep(step, context);
      context += `\n\n${step.agent}: ${result}`;
    }

    // Step 3: Synthesize final result
    return this.synthesize(task, context);
  }

  private async createPlan(task: string): Promise<{
    steps: Array<{ agent: string; instruction: string }>;
  }> {
    const response = await this.client.chat.completions.create({
      model: this.model,
      messages: [
        {
          role: 'system',
          content: `You are a task supervisor. Plan how to accomplish a task using these workers: ${this.workers.join(', ')}.

Respond with JSON:
{
  "steps": [
    { "agent": "worker_name", "instruction": "what they should do" },
    ...
  ]
}`,
        },
        {
          role: 'user',
          content: `Plan this task: ${task}`,
        },
      ],
      response_format: { type: 'json_object' },
    });

    return JSON.parse(response.choices[0].message.content || '{"steps":[]}');
  }

  private async delegateStep(
    step: { agent: string; instruction: string },
    context: string
  ): Promise<string> {
    const message: AgentMessage = {
      from: 'supervisor',
      to: step.agent,
      type: 'request',
      content: `${step.instruction}\n\nContext from previous work:${context || '\nNone yet.'}`,
    };

    const response = await this.messageBus.send(message);
    return response?.content || 'No response';
  }

  private async synthesize(task: string, workResults: string): Promise<string> {
    const response = await this.client.chat.completions.create({
      model: this.model,
      messages: [
        {
          role: 'system',
          content:
            'You are a supervisor. Synthesize the work done by your team into a final deliverable.',
        },
        {
          role: 'user',
          content: `Original task: ${task}\n\nWork completed:${workResults}\n\nCreate the final output.`,
        },
      ],
    });

    return response.choices[0].message.content || '';
  }
}

Usage:

// Set up the system
const bus = new MessageBus();
bus.register(researchAgent);
bus.register(writerAgent);
bus.register(reviewerAgent);

const supervisor = new SupervisorAgent(client, 'gpt-4o', bus, ['researcher', 'writer', 'reviewer']);

// Run a complex task
const result = await supervisor.orchestrate(
  'Create a blog post about the benefits of TypeScript for large projects'
);

console.log(result);

Debate Pattern

Agents with opposing viewpoints produce better analysis:

interface DebateRound {
  proArgument: string;
  conArgument: string;
}

class DebateSystem {
  private client: OpenAI;
  private model: string;

  constructor(client: OpenAI, model: string) {
    this.client = client;
    this.model = model;
  }

  async debate(topic: string, rounds: number = 3): Promise<string> {
    const debateHistory: DebateRound[] = [];

    // Initial positions
    let proPosition = await this.getArgument(topic, 'pro', []);
    let conPosition = await this.getArgument(topic, 'con', []);

    debateHistory.push({
      proArgument: proPosition,
      conArgument: conPosition,
    });

    // Debate rounds
    for (let i = 1; i < rounds; i++) {
      proPosition = await this.getArgument(topic, 'pro', debateHistory);
      conPosition = await this.getArgument(topic, 'con', debateHistory);

      debateHistory.push({
        proArgument: proPosition,
        conArgument: conPosition,
      });
    }

    // Judge the debate
    return this.judge(topic, debateHistory);
  }

  private async getArgument(
    topic: string,
    side: 'pro' | 'con',
    history: DebateRound[]
  ): Promise<string> {
    const historyText = history
      .map((round, i) => `Round ${i + 1}:\nPro: ${round.proArgument}\nCon: ${round.conArgument}`)
      .join('\n\n');

    const response = await this.client.chat.completions.create({
      model: this.model,
      messages: [
        {
          role: 'system',
          content: `You are arguing ${side === 'pro' ? 'IN FAVOR OF' : 'AGAINST'} the topic. 
          
Make strong, logical arguments. If there's debate history, respond to the opponent's points.
Be concise but persuasive.`,
        },
        {
          role: 'user',
          content: `Topic: ${topic}

${historyText ? `Previous rounds:\n${historyText}\n\n` : ''}Present your ${side === 'pro' ? 'supporting' : 'opposing'} argument.`,
        },
      ],
    });

    return response.choices[0].message.content || '';
  }

  private async judge(topic: string, debate: DebateRound[]): Promise<string> {
    const debateText = debate
      .map((round, i) => `Round ${i + 1}:\nPro: ${round.proArgument}\nCon: ${round.conArgument}`)
      .join('\n\n');

    const response = await this.client.chat.completions.create({
      model: this.model,
      messages: [
        {
          role: 'system',
          content: `You are an impartial judge. Analyze the debate and provide:
1. A summary of the strongest arguments from each side
2. An assessment of which side made more compelling points
3. A balanced conclusion that incorporates insights from both perspectives`,
        },
        {
          role: 'user',
          content: `Topic: ${topic}\n\nDebate:\n${debateText}\n\nProvide your judgment.`,
        },
      ],
    });

    return response.choices[0].message.content || '';
  }
}

Usage:

const debateSystem = new DebateSystem(client, 'gpt-4o');

const analysis = await debateSystem.debate(
  'Remote work is better than office work for software developers',
  3
);

console.log(analysis);

Collaborative Pipeline

Agents pass work through a pipeline with feedback loops:

interface PipelineStage {
  agent: Agent;
  nextStage?: string;
  feedbackTo?: string;
}

class CollaborativePipeline {
  private stages: Map<string, PipelineStage> = new Map();
  private messageBus: MessageBus;

  constructor(messageBus: MessageBus) {
    this.messageBus = messageBus;
  }

  addStage(name: string, stage: PipelineStage): void {
    this.stages.set(name, stage);
    this.messageBus.register(stage.agent);
  }

  async run(input: string, startStage: string): Promise<string> {
    let currentStage = startStage;
    let currentContent = input;
    const maxIterations = 10;
    let iteration = 0;

    while (currentStage && iteration < maxIterations) {
      const stage = this.stages.get(currentStage);
      if (!stage) break;

      console.log(`\n--- Stage: ${currentStage} ---`);

      const message: AgentMessage = {
        from: 'pipeline',
        to: stage.agent.name,
        type: 'request',
        content: currentContent,
      };

      const response = await this.messageBus.send(message);

      if (!response) {
        console.log(`No response from ${currentStage}`);
        break;
      }

      currentContent = response.content;
      console.log(`Output: ${currentContent.substring(0, 200)}...`);

      // Check for feedback request
      if (this.needsFeedback(currentContent) && stage.feedbackTo) {
        currentStage = stage.feedbackTo;
      } else {
        currentStage = stage.nextStage || '';
      }

      iteration++;
    }

    return currentContent;
  }

  private needsFeedback(content: string): boolean {
    const feedbackIndicators = [
      'needs revision',
      'requires improvement',
      'feedback:',
      'issues found',
    ];
    return feedbackIndicators.some((indicator) => content.toLowerCase().includes(indicator));
  }
}

Usage:

const pipeline = new CollaborativePipeline(bus);

pipeline.addStage('research', {
  agent: researchAgent,
  nextStage: 'write',
});

pipeline.addStage('write', {
  agent: writerAgent,
  nextStage: 'review',
  feedbackTo: 'write',
});

pipeline.addStage('review', {
  agent: reviewerAgent,
  feedbackTo: 'write',
});

const result = await pipeline.run('Create content about machine learning basics', 'research');

Agent Team Patterns

Common patterns for organizing agents:

Expert Panel

Multiple specialists provide perspectives on a topic:

class ExpertPanel {
  private experts: Agent[];
  private moderator: OpenAI;
  private model: string;

  constructor(experts: Agent[], moderator: OpenAI, model: string) {
    this.experts = experts;
    this.moderator = moderator;
    this.model = model;
  }

  async consult(question: string): Promise<string> {
    // Gather all expert opinions
    const opinions = await Promise.all(
      this.experts.map(async (expert) => {
        const message: AgentMessage = {
          from: 'moderator',
          to: expert.name,
          type: 'request',
          content: question,
        };
        const response = await expert.process(message);
        return { expert: expert.name, opinion: response?.content || '' };
      })
    );

    // Synthesize opinions
    const opinionsText = opinions.map((o) => `${o.expert}:\n${o.opinion}`).join('\n\n---\n\n');

    const response = await this.moderator.chat.completions.create({
      model: this.model,
      messages: [
        {
          role: 'system',
          content:
            'You are a moderator. Synthesize expert opinions into a comprehensive answer that highlights consensus and notes any disagreements.',
        },
        {
          role: 'user',
          content: `Question: ${question}\n\nExpert Opinions:\n${opinionsText}`,
        },
      ],
    });

    return response.choices[0].message.content || '';
  }
}

Quality Chain

Each agent improves on the previous work:

class QualityChain {
  private agents: Agent[];
  private messageBus: MessageBus;

  constructor(agents: Agent[], messageBus: MessageBus) {
    this.agents = agents;
    this.messageBus = messageBus;
    agents.forEach((agent) => messageBus.register(agent));
  }

  async process(input: string): Promise<string> {
    let content = input;

    for (const agent of this.agents) {
      const message: AgentMessage = {
        from: 'chain',
        to: agent.name,
        type: 'request',
        content: `Improve this content:\n\n${content}`,
      };

      const response = await this.messageBus.send(message);
      if (response) {
        content = response.content;
      }
    }

    return content;
  }
}

Handling Agent Conflicts

When agents disagree, resolve conflicts systematically:

class ConflictResolver {
  private client: OpenAI;
  private model: string;

  constructor(client: OpenAI, model: string) {
    this.client = client;
    this.model = model;
  }

  async resolve(
    topic: string,
    positions: Array<{ agent: string; position: string }>
  ): Promise<string> {
    const positionsText = positions.map((p) => `${p.agent}: ${p.position}`).join('\n\n');

    const response = await this.client.chat.completions.create({
      model: this.model,
      messages: [
        {
          role: 'system',
          content: `You are a conflict resolver. When agents have different positions:

1. Identify the core disagreement
2. Evaluate the merits of each position
3. Find common ground where possible
4. Make a reasoned decision or synthesis
5. Explain your reasoning

Be fair and evidence-based.`,
        },
        {
          role: 'user',
          content: `Topic: ${topic}\n\nPositions:\n${positionsText}\n\nResolve this conflict.`,
        },
      ],
    });

    return response.choices[0].message.content || '';
  }
}

Best Practices for Multi-Agent Systems

1. Define Clear Roles

Each agent should have a specific, non-overlapping responsibility.

2. Limit Communication

Too much inter-agent communication creates overhead. Design efficient communication patterns.

3. Set Boundaries

Define what each agent can and cannot do. Prevent scope creep.

4. Handle Failures

Design for agents that fail or produce poor output. Include fallbacks.

5. Monitor Conversations

Log all agent interactions for debugging and improvement.

6. Test Components

Test each agent individually before combining them.


Key Takeaways

  1. Multi-agent systems excel at complex tasks requiring diverse expertise
  2. Architectures vary: hierarchical, collaborative, and debate patterns each have uses
  3. Communication between agents needs clear protocols and message formats
  4. Specialization allows each agent to be optimized for its role
  5. Conflict resolution is essential when agents produce contradictory outputs

Practice Exercise

Build a multi-agent system with:

  • A research agent that gathers information
  • A writing agent that creates content
  • A critic agent that finds issues
  • A supervisor that coordinates them

Test with: "Write a balanced analysis of cryptocurrency adoption"

The system should:

  1. Research the topic from multiple angles
  2. Write an initial draft
  3. Get criticism and feedback
  4. Revise based on feedback
  5. Produce a final version

Resources

Resource Type Level
LangGraph Multi-Agent Documentation Intermediate
AutoGen Repository Intermediate
CrewAI Repository Intermediate

Next Lesson

In the next lesson, you will learn about LangChain.js - a framework that simplifies building agents with pre-built components and patterns.

Continue to Lesson 5.4: LangChain.js Basics