Multiple specialized agents collaborating to solve complex tasks
Why Multi-Agent Systems
One agent with many tools gets confused. It tries to do everything and does nothing well.
Single agent approach:
Agent has 20 tools → confused about which to use
Long tasks → context window fills up with tool calls
Complex tasks → agent loses track of what it's doing
Quality suffers on every individual sub-task
Multi-agent approach:
Research Agent → only knows how to search and gather info
Writer Agent → only knows how to write and format
Math Agent → only knows how to calculate
Supervisor → decides who does what and when
Each agent is focused → better at its specific job
Complex tasks broken into parts → each part handled by specialist
The Supervisor Pattern — How It Works
User Task
↓
SUPERVISOR AGENT
→ Reads the task
→ Decides which specialist to delegate to
→ Sends task to specialist
↓
SPECIALIST AGENT (research / math / writing)
→ Does its specific job
→ Returns result to supervisor
↓
SUPERVISOR AGENT
→ Reviews result
→ Decides next step:
→ Send to another specialist?
→ Task is done → give final answer?
↓
Final Answer to User
Project Setup
mkdir multi-agent-demo cd multi-agent-demo npm init -y
Update package.json:
{ "name": "multi-agent-demo", "version": "1.0.0", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { "@langchain/core": "^1.2.4", "@langchain/langgraph": "^1.4.9", "@langchain/langgraph-supervisor": "^1.1.1", "@langchain/openai": "^1.5.5", "dotenv": "^17.4.2", "langchain": "^1.5.4", "zod": "^4.4.3" } }
Install packages — note the exact imports from official docs:
npm install @langchain/langgraph-supervisor @langchain/langgraph @langchain/core @langchain/openai langchain zod dotenv
@langchain/langgraph-supervisor → createSupervisor function
@langchain/langgraph/prebuilt → createReactAgent for worker agents
@langchain/core/tools → tool() function
Create .env:
OPENAI_API_KEY=sk-proj-your-key-here
Part 1 — Basic Supervisor with Two Agents
Create src/01_basic_supervisor.js:
import { ChatOpenAI } from "@langchain/openai"; // ChatOpenAI = the LLM — used by both supervisor and worker agents
import { createSupervisor } from "@langchain/langgraph-supervisor"; // createSupervisor = creates a supervisor that routes between worker agents // returns a StateGraph (not yet compiled — call .compile() on it)
import { createReactAgent } from "@langchain/langgraph/prebuilt"; // createReactAgent = creates a worker agent with the ReAct pattern // each worker has its own tools and system prompt // note: this is DIFFERENT from createAgent from "langchain" // createReactAgent is from langgraph/prebuilt — more low-level, better for multi-agent
import { tool } from "@langchain/core/tools"; // tool = wraps a JS function into a LangChain tool // note: import from "@langchain/core/tools" not from "langchain" // both work but @langchain/core/tools is the canonical import in 2025
import { z } from "zod"; import { MemorySaver } from "@langchain/langgraph"; import * as dotenv from "dotenv"; dotenv.config();
// ───────────────────────────────────────── // SHARED LLM // Both supervisor and worker agents use this // ─────────────────────────────────────────
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 }); // temperature: 0 = deterministic — important for routing decisions // supervisor must consistently route to the right agent
// ───────────────────────────────────────── // MATH AGENT TOOLS // Only math-related tools — no research // ─────────────────────────────────────────
const addTool = tool( async (args) => { // args = { a: number, b: number } return String(args.a + args.b); // return string — LLM reads text, not numbers // example: args = { a: 25, b: 48 } → returns "73" }, { name: "add", description: "Adds two numbers together.", schema: z.object({ a: z.number().describe("first number"), b: z.number().describe("second number"), }), } );
const multiplyTool = tool( async (args) => { return String(args.a * args.b); // example: args = { a: 1847, b: 23 } → returns "42481" }, { name: "multiply", description: "Multiplies two numbers.", schema: z.object({ a: z.number().describe("first number"), b: z.number().describe("second number"), }), } );
const percentageTool = tool( async (args) => { const result = (args.value * args.percentage) / 100; return `${args.percentage}% of ${args.value} = ${result}`; // example: args = { value: 8500, percentage: 15 } // returns: "15% of 8500 = 1275" }, { name: "calculate_percentage", description: "Calculates a percentage of a value.", schema: z.object({ value: z.number().describe("the base value"), percentage: z.number().describe("the percentage to calculate"), }), } );
// ───────────────────────────────────────── // RESEARCH AGENT TOOLS // Only research/lookup tools — no math // ─────────────────────────────────────────
const searchTool = tool( async (args) => { // Simulated search — in production use Tavily, SerpAPI, etc. console.log(` 🔍 Research Agent searching: "${args.query}"`);
const knowledge = { "FAANG salaries India 2025": "Average FAANG software engineer salary in India: Google ₹45-85 LPA, Meta ₹40-75 LPA, Amazon ₹35-65 LPA, Apple ₹40-70 LPA, Netflix ₹50-90 LPA.", "LangChain vs LangGraph": "LangChain provides high-level components (chains, agents, tools). LangGraph is a stateful graph framework for complex agent workflows. LangGraph is built by the same team and is now the recommended way to build production agents.", "AI agent market size": "The AI agent market is projected to reach $28.5 billion by 2028, growing at 43% CAGR. Enterprise adoption is driven by automation and productivity gains.", "React vs Next.js": "React is a UI library. Next.js is a full-stack framework built on React. Next.js adds server-side rendering, file-based routing, and API routes. Use Next.js for production web applications.", };
// find a matching knowledge entry const matchKey = Object.keys(knowledge).find(k => args.query.toLowerCase().includes(k.toLowerCase().split(" ")[0]) );
return knowledge[matchKey] || `Research results for "${args.query}": This topic involves multiple factors. Key points: [simulated research data about ${args.query}]`; }, { name: "web_search", description: "Searches the web for factual information, current data, news, and research.", schema: z.object({ query: z.string().describe("the search query"), }), } );
const getFactTool = tool( async (args) => { console.log(` 📚 Research Agent looking up: "${args.topic}"`); return `Key facts about "${args.topic}": [simulated factual information retrieved from knowledge base about this topic]`; }, { name: "get_fact", description: "Gets specific factual information about a topic from a knowledge base.", schema: z.object({ topic: z.string().describe("topic to get facts about"), }), } );
// ───────────────────────────────────────── // CREATE WORKER AGENTS // Each agent is specialized — narrow focus // ─────────────────────────────────────────
const mathAgent = createReactAgent({ // createReactAgent from "@langchain/langgraph/prebuilt" // creates a ReAct agent — think, act, observe loop // lower level than createAgent from "langchain" // required for multi-agent supervisor pattern
llm: model, // the language model this agent uses
tools: [addTool, multiplyTool, percentageTool], // ONLY math tools — this agent cannot search the web // focused agent = better at its specific job
name: "math_expert", // name = how the supervisor refers to this agent // supervisor will say "I'll send this to math_expert" // must be unique across all agents
prompt: "You are a math expert. Use tools to solve all calculations precisely. Always use a tool — never calculate in your head.", // focused system prompt — tells agent exactly what its job is // "always use a tool" = prevents mental math errors }); // mathAgent = compiled ReAct agent ready to receive tasks // internally it's a LangGraph StateGraph
const researchAgent = createReactAgent({ llm: model, tools: [searchTool, getFactTool], // ONLY research tools — this agent cannot do math name: "research_expert", prompt: "You are a research expert. Search for accurate information to answer questions. Always search before answering — never make up facts.", });
// ───────────────────────────────────────── // CREATE SUPERVISOR // Coordinates between worker agents // ─────────────────────────────────────────
const supervisorWorkflow = createSupervisor({ agents: [researchAgent, mathAgent], // list of worker agents the supervisor can delegate to // supervisor knows each agent's name and capabilities
llm: model, // the LLM that powers the supervisor's reasoning // supervisor uses this to decide routing decisions
prompt: `You are a team supervisor managing two specialist agents: - research_expert: Use for finding information, facts, data, current events - math_expert: Use for ALL calculations, percentages, arithmetic
Your job: 1. Read the user's task 2. Decide which agent(s) should handle it 3. Delegate to the right agent 4. Collect results and provide a final comprehensive answer
If a task needs both research AND math → use both agents in the right order. Always provide a clear final answer after all agents complete their work.`, // prompt = how the supervisor decides routing // clear agent descriptions = better routing decisions });
// supervisorWorkflow = StateGraph (not compiled yet) // must call .compile() before using
// ───────────────────────────────────────── // COMPILE WITH MEMORY // ─────────────────────────────────────────
const checkpointer = new MemorySaver();
const supervisorApp = supervisorWorkflow.compile({ checkpointer, // adds conversation memory per thread_id // supervisor remembers previous turns in the same thread }); // supervisorApp = compiled, ready to invoke // this is the final multi-agent system
// ───────────────────────────────────────── // HELPER — run one task through the system // ─────────────────────────────────────────
async function runTask(task, threadId = "default") { console.log("\n" + "=".repeat(55)); console.log("Task:", task); console.log("=".repeat(55));
const result = await supervisorApp.invoke( { messages: [{ role: "user", content: task }], // messages = the task sent to the supervisor }, { configurable: { thread_id: threadId }, // thread_id = which conversation thread to use } );
// result.messages = all messages produced during this run // includes: supervisor thoughts, agent responses, tool calls const lastMessage = result.messages[result.messages.length - 1]; // last message = supervisor's final answer to the user
console.log("\n✅ Final Answer:"); console.log("─".repeat(55)); console.log(lastMessage.content); }
// ───────────────────────────────────────── // RUN EXAMPLES // ─────────────────────────────────────────
async function main() { console.log("🤖 MULTI-AGENT SUPERVISOR DEMO\n");
// Task 1 — Pure math → should go to math_expert only await runTask( "What is 15% of 85000, and then multiply that result by 12?", "thread_001" ); // Expected routing: supervisor → math_expert → supervisor → answer
// Task 2 — Pure research → should go to research_expert only await runTask( "What are the average FAANG salaries for software engineers in India in 2025?", "thread_002" ); // Expected routing: supervisor → research_expert → supervisor → answer
// Task 3 — Both research AND math await runTask( "Research the AI agent market size and then calculate what 43% annual growth from $5 billion would be after 3 years.", "thread_003" ); // Expected routing: supervisor → research_expert → math_expert → supervisor → answer
console.log("\n✅ All tasks complete!"); }
main().catch(console.error);
Run:
node src/01_basic_supervisor.js
Part 2 — Three-Agent Content Pipeline
A real-world example — research → write → review pipeline:
Create src/02_content_pipeline.js:
import { ChatOpenAI } from "@langchain/openai"; import { createSupervisor } from "@langchain/langgraph-supervisor"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; import { MemorySaver } from "@langchain/langgraph"; import * as dotenv from "dotenv"; dotenv.config();
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0.2 });
// ───────────────────────────────────────── // AGENT 1 — RESEARCHER // Gathers facts and information // ─────────────────────────────────────────
const gatherFactsTool = tool( async (args) => { console.log(` 📚 Researcher gathering facts about: "${args.topic}"`); // Simulated research return `Research findings on "${args.topic}": - Key fact 1: ${args.topic} has been growing rapidly in 2025 - Key fact 2: Major players include Google, Microsoft, Anthropic, OpenAI - Key fact 3: Enterprise adoption is accelerating with measurable ROI - Key statistic: Market projected at $500B by 2030 - Recent development: Agentic AI is becoming the dominant paradigm`; }, { name: "gather_facts", description: "Gathers facts and research on a topic.", schema: z.object({ topic: z.string() }), } );
const researcherAgent = createReactAgent({ llm: model, tools: [gatherFactsTool], name: "researcher", prompt: `You are a researcher. Your job is to gather accurate facts and information. When given a topic: 1. Use gather_facts to collect research 2. Organize the facts clearly 3. Return a structured research summary
Output format: KEY FACTS: [bullet points of main facts] STATISTICS: [any numbers or data points] CONTEXT: [background information]`, });
// ───────────────────────────────────────── // AGENT 2 — WRITER // Takes research and writes content // ─────────────────────────────────────────
const writeDraftTool = tool( async (args) => { console.log(` ✍️ Writer creating ${args.type} about: "${args.topic}"`); return `[DRAFT ${args.type.toUpperCase()}]
Title: ${args.topic} — A Complete Overview
Introduction: ${args.topic} represents one of the most significant developments in technology today. Based on the research provided: ${args.research.substring(0, 100)}...
Main Body: The landscape is rapidly evolving with key players driving innovation. Market statistics indicate strong growth momentum.
Conclusion: Organizations that adopt ${args.topic} early will gain competitive advantages.
[END DRAFT]`; }, { name: "write_draft", description: "Writes a draft article, blog post, or report based on research.", schema: z.object({ topic: z.string().describe("the topic to write about"), type: z.enum(["blog_post", "report", "summary"]).describe("type of content"), research: z.string().describe("the research findings to base the writing on"), }), } );
const writerAgent = createReactAgent({ llm: model, tools: [writeDraftTool], name: "writer", prompt: `You are a professional content writer. When given research findings: 1. Use write_draft to create well-structured content 2. Make it engaging and clear 3. Ensure all facts from the research are included
Always base your writing on the provided research — don't add unverified facts.`, });
// ───────────────────────────────────────── // AGENT 3 — REVIEWER // Reviews content for quality and accuracy // ─────────────────────────────────────────
const reviewContentTool = tool( async (args) => { console.log(` 🔍 Reviewer checking content quality...`);
const wordCount = args.content.split(" ").length; // count words in the content
const hasTitle = args.content.includes("Title:"); const hasIntro = args.content.toLowerCase().includes("introduction"); const hasConclusion = args.content.toLowerCase().includes("conclusion");
const score = (hasTitle ? 30 : 0) + (hasIntro ? 35 : 0) + (hasConclusion ? 35 : 0); // calculate quality score based on structure
const feedback = []; if (!hasTitle) feedback.push("Missing clear title"); if (!hasIntro) feedback.push("Missing introduction section"); if (!hasConclusion) feedback.push("Missing conclusion section"); if (wordCount < 50) feedback.push("Content too short — needs more detail");
return `REVIEW RESULTS: Quality Score: ${score}/100 Word Count: ${wordCount} Structure Check: ${hasTitle && hasIntro && hasConclusion ? "PASS ✅" : "NEEDS IMPROVEMENT ⚠️"} Issues Found: ${feedback.length === 0 ? "None — content looks good!" : feedback.join(", ")} Recommendation: ${score >= 70 ? "APPROVED — ready to publish" : "REVISE — address the issues above"}`; }, { name: "review_content", description: "Reviews content for quality, structure, and accuracy. Returns a quality score and feedback.", schema: z.object({ content: z.string().describe("the content to review"), }), } );
const reviewerAgent = createReactAgent({ llm: model, tools: [reviewContentTool], name: "reviewer", prompt: `You are a content quality reviewer. When given content to review: 1. Use review_content to assess quality 2. Report the score and issues found 3. Provide a clear APPROVED or REVISE recommendation
Be thorough but constructive in your feedback.`, });
// ───────────────────────────────────────── // SUPERVISOR — Manages the pipeline // ─────────────────────────────────────────
const contentWorkflow = createSupervisor({ agents: [researcherAgent, writerAgent, reviewerAgent], llm: model, prompt: `You are a content production supervisor managing a three-agent pipeline: - researcher: Gathers facts and information on any topic - writer: Creates written content based on research - reviewer: Reviews content for quality and gives approval
PIPELINE ORDER — always follow this sequence: 1. Send to researcher first → get facts 2. Send research to writer → get draft 3. Send draft to reviewer → get quality score 4. Report final status to user with: research summary, draft, and review score
Do not skip any step. Each agent must complete its task before moving to the next.`, outputMode: "last_message", // outputMode: "last_message" = only include final agent message in output // "full_history" = include all messages from all agents (more verbose) });
const contentApp = contentWorkflow.compile({ checkpointer: new MemorySaver(), });
// ───────────────────────────────────────── // RUN THE PIPELINE // ─────────────────────────────────────────
async function runContentPipeline(topic) { console.log("\n" + "=".repeat(55)); console.log("Content Pipeline Task:", topic); console.log("Pipeline: researcher → writer → reviewer"); console.log("=".repeat(55));
const result = await contentApp.invoke( { messages: [{ role: "user", content: `Create a blog post about: ${topic}` }] }, { configurable: { thread_id: `pipeline_${Date.now()}` } } );
const lastMsg = result.messages[result.messages.length - 1]; console.log("\n📋 Pipeline Complete — Final Report:"); console.log("─".repeat(55)); console.log(lastMsg.content); }
async function main() { console.log("🏭 CONTENT PIPELINE DEMO\n"); console.log("Three agents: Researcher → Writer → Reviewer\n");
await runContentPipeline("AI Agents in 2025");
console.log("\n✅ Pipeline complete!"); }
main().catch(console.error);
Run:
node src/02_content_pipeline.js
Multi-Agent Architecture Patterns
SUPERVISOR (what we built):
Supervisor
/ | \
Agent1 Agent2 Agent3
Best for: tasks with clear roles
predictable pipelines
structured workflows
NETWORK / SWARM (agents call each other):
Agent1 ←→ Agent2 ←→ Agent3
Best for: exploratory tasks
no fixed order needed
agents decide handoffs dynamically
HIERARCHICAL (supervisors managing supervisors):
Top Supervisor
/ \
Sub-Supervisor Sub-Supervisor
/ \ / \
Agent1 Agent2 Agent3 Agent4
Best for: very large complex tasks
enterprise systems
multiple departments
When to Use Multi-Agent vs Single Agent
Use SINGLE AGENT when:
→ Task fits in one context window
→ 3-5 tools is enough
→ Task is straightforward
→ Speed matters more than quality
→ Cost is a concern
(each agent handoff = extra LLM call = extra cost)
Use MULTI-AGENT when:
→ Task is too complex for one agent
→ Different parts need different expertise
→ Parallel work would speed things up
→ You need specialist quality on each sub-task
→ Pipeline has clear stages (research → write → review)
Warning from 2025 research:
→ Multi-agent = 5-10x more LLM calls than single agent
→ Start with single agent
→ Add agents only when quality genuinely improves
3-Line Summary
- Multi-agent systems use a Supervisor that reads the task, routes it to specialist Worker agents based on their descriptions, collects results, and compiles the final answer — each worker has a narrow focus (only math tools, only research tools) which makes it better at its specific job.
createSupervisorfrom@langchain/langgraph-supervisorcombined withcreateReactAgentfrom@langchain/langgraph/prebuiltis the 2025 standard pattern —createSupervisorreturns aStateGraphthat you compile with.compile({ checkpointer })before using.- Multi-agent systems cost 5-10x more in LLM calls than a single agent — always start with a single well-designed agent and only move to multi-agent when you genuinely need specialist quality on different parts of the task.
Module 8.4 — Complete ✅
Coming up — Module 8.5 — Project: Resume Analyzer Agent
A real project using everything from Phase 8 — an agent that reads a resume PDF, analyzes skills, matches against a job description, scores the candidate, and gives actionable improvement suggestions. End-to-end working project.