Skip to main content
A multi-agent system where a supervisor agent analyzes incoming messages and routes them to specialized agents — sales, support, or billing. Each specialist has its own tools, knowledge base, and personality. This pattern is ideal when a single agent can’t handle all scenarios well.

Project Structure

multi-agent/
├── main.ts
├── supervisor.ts
├── agents/
│   ├── sales.ts
│   ├── support.ts
│   └── billing.ts
├── tools/
│   ├── index.ts
│   ├── search-orders.ts
│   ├── create-ticket.ts
│   └── check-invoice.ts
├── prompts/
│   └── index.ts
├── .runflow/
│   └── rf.json
├── package.json
└── tsconfig.json

Step 1: Specialist Agents

Each specialist handles a specific domain with its own tools and instructions.

Sales Agent

agents/sales.ts
import { Agent, openai } from '@runflow-ai/sdk';

export const salesAgentConfig = {
  name: 'Sales Specialist',
  instructions: `You are a sales specialist for ACME Corp.

## Behavior
- Help with pricing questions, plan comparisons, and purchasing
- Be consultative — understand needs before recommending plans
- For Enterprise inquiries, offer to schedule a demo
- Always provide clear pricing information

## Response Style
- Be enthusiastic but not pushy
- Use concrete numbers and comparisons
- End with a clear next step`,
  model: openai('gpt-4o'),
};

Support Agent

agents/support.ts
import { Agent, openai } from '@runflow-ai/sdk';

export const supportAgentConfig = {
  name: 'Technical Support',
  instructions: `You are a technical support specialist for ACME Corp.

## Behavior
- Search the knowledge base before answering technical questions
- Walk users through solutions step by step
- Create a ticket if the issue can't be resolved here
- Always confirm the user's problem before suggesting solutions

## Tools
- Use **search-orders** to look up order details
- Use **create-ticket** for issues needing human follow-up`,
  model: openai('gpt-4o'),
};

Billing Agent

agents/billing.ts
import { Agent, openai } from '@runflow-ai/sdk';

export const billingAgentConfig = {
  name: 'Billing Specialist',
  instructions: `You are a billing specialist for ACME Corp.

## Behavior
- Help with invoices, payment issues, and subscription changes
- Always verify the account before making changes
- For refund requests, check the policy first, then proceed

## Tools
- Use **check-invoice** to look up invoice details
- Use **create-ticket** for complex billing issues that need manual review`,
  model: openai('gpt-4o'),
};

Step 2: Supervisor Agent

The supervisor uses the built-in agents config to automatically route to specialists:
supervisor.ts
import { Agent, openai } from '@runflow-ai/sdk';
import { salesAgentConfig } from './agents/sales';
import { supportAgentConfig } from './agents/support';
import { billingAgentConfig } from './agents/billing';
import { searchOrdersTool, createTicketTool, checkInvoiceTool } from './tools';

export const supervisorAgent = new Agent({
  name: 'Customer Service Supervisor',
  instructions: `You route customer requests to the right specialist.

## Routing Rules
- **Sales**: pricing, plans, purchasing, upgrades, demos, discounts
- **Support**: technical issues, bugs, how-to questions, order status
- **Billing**: invoices, payments, refunds, subscription changes, charges

## Behavior
- Analyze the customer's message to determine intent
- Route to the most appropriate specialist
- If the intent is ambiguous, ask the customer to clarify
- If a conversation switches topics (e.g., support → billing), re-route

## Important
- You do NOT answer questions directly — you route to specialists
- Never make up information about pricing, policies, or account details`,

  model: openai('gpt-4o-mini'), // Cheap model for routing

  // Specialist agents — Runflow handles routing automatically
  agents: {
    sales: salesAgentConfig,
    support: {
      ...supportAgentConfig,
      tools: {
        searchOrders: searchOrdersTool,
        createTicket: createTicketTool,
      },
      rag: {
        vectorStore: 'support-docs',
        k: 5,
        threshold: 0.7,
      },
    },
    billing: {
      ...billingAgentConfig,
      tools: {
        checkInvoice: checkInvoiceTool,
        createTicket: createTicketTool,
      },
    },
  },

  memory: {
    maxTurns: 30,
    summarizeAfter: 20,
    summarizePrompt: 'Summarize: customer intent, which specialist handled it, actions taken, pending issues',
  },

  observability: 'full',
});

Step 3: Tools

Shared tools used by multiple specialists:
tools/search-orders.ts
import { createTool } from '@runflow-ai/sdk';
import { z } from 'zod';

export const searchOrdersTool = createTool({
  id: 'search-orders',
  description: 'Search customer orders by order ID or email',
  inputSchema: z.object({
    orderId: z.string().optional().describe('Order ID (e.g., ORD-12345)'),
    email: z.string().email().optional().describe('Customer email'),
  }),
  execute: async ({ context }) => {
    try {
      const response = await fetch(
        `https://api.yourcompany.com/orders?id=${context.orderId || ''}&email=${context.email || ''}`,
        { headers: { 'Authorization': `Bearer ${process.env.ORDERS_API_KEY}` } }
      );
      const orders = await response.json();

      if (!orders.length) {
        return { found: false, query: context.orderId || context.email };
      }

      return {
        found: true,
        orders: orders.map((o: any) => ({
          id: o.id,
          status: o.status,
          total: o.total,
          createdAt: o.createdAt,
        })),
      };
    } catch {
      return { found: false, error: 'Service unavailable' };
    }
  },
});
tools/check-invoice.ts
import { createTool } from '@runflow-ai/sdk';
import { z } from 'zod';

export const checkInvoiceTool = createTool({
  id: 'check-invoice',
  description: 'Look up invoice details by invoice ID or customer email',
  inputSchema: z.object({
    invoiceId: z.string().optional().describe('Invoice ID'),
    email: z.string().email().optional().describe('Customer email'),
  }),
  execute: async ({ context }) => {
    try {
      const invoices = await fetchInvoices(context.invoiceId, context.email);

      if (!invoices.length) {
        return { found: false };
      }

      return {
        found: true,
        invoices: invoices.map((inv: any) => ({
          id: inv.id,
          amount: inv.amount,
          status: inv.status,
          dueDate: inv.dueDate,
          paidAt: inv.paidAt,
        })),
      };
    } catch {
      return { found: false, error: 'Could not retrieve invoices' };
    }
  },
});
tools/create-ticket.ts
import { createTool } from '@runflow-ai/sdk';
import { track } from '@runflow-ai/sdk/observability';
import { z } from 'zod';

export const createTicketTool = createTool({
  id: 'create-ticket',
  description: 'Create a support ticket for issues needing human follow-up',
  inputSchema: z.object({
    subject: z.string(),
    description: z.string(),
    priority: z.enum(['low', 'medium', 'high']),
    department: z.enum(['sales', 'support', 'billing']),
  }),
  execute: async ({ context }) => {
    try {
      const ticket = { id: `TICKET-${Date.now()}`, ...context };

      track('ticket_created', {
        department: context.department,
        priority: context.priority,
      });

      return { success: true, ticketId: ticket.id };
    } catch {
      return { success: false, error: 'Failed to create ticket' };
    }
  },
});
tools/index.ts
export { searchOrdersTool } from './search-orders';
export { createTicketTool } from './create-ticket';
export { checkInvoiceTool } from './check-invoice';

Step 4: Main Entry Point

main.ts
import { identify, track } from '@runflow-ai/sdk/observability';
import { supervisorAgent } from './supervisor';

export async function main(input: any) {
  if (!input?.message) {
    return { error: 'message is required' };
  }

  identify(input.email || input.phone || input.userId || 'anonymous');

  try {
    const result = await supervisorAgent.process({
      message: input.message,
      sessionId: input.sessionId,
    });

    // Track routing metrics
    track('customer_request', {
      channel: input.channel || 'api',
      routedTo: result.metadata?.routedTo || 'unknown',
    });

    return {
      message: result.message,
      metadata: result.metadata,
    };
  } catch (error) {
    console.error('[multi-agent] Error:', error);
    return { error: 'An error occurred. Please try again.' };
  }
}

How It Works

Customer message arrives

┌─────────────────────┐
│     Supervisor       │  gpt-4o-mini analyzes intent
│   (routing only)     │
└──────┬──────────────┘

   What intent?
  ╱     │     ╲
Sales  Support  Billing
  ↓      ↓       ↓
gpt-4o  gpt-4o  gpt-4o
+ no    + RAG   + invoice
  tools + tools   tools

   Response back
   to customer

Key Patterns

Cheap Supervisor, Quality Specialists

The supervisor only needs to classify intent — use gpt-4o-mini (fast, cheap). Specialists do the real work — use gpt-4o for quality responses.

Built-in agents Config

Runflow’s agents config handles routing automatically. You define specialists inline and the supervisor routes based on its instructions:
const supervisor = new Agent({
  instructions: 'Route to sales, support, or billing...',
  model: openai('gpt-4o-mini'),
  agents: {
    sales: { name: 'Sales', instructions: '...', model: openai('gpt-4o') },
    support: { name: 'Support', instructions: '...', model: openai('gpt-4o') },
  },
});

Shared Tools Across Specialists

Some tools (like create-ticket) are used by multiple specialists. Define them once in tools/ and assign to each agent that needs them.

Conversation Continuity

Memory is shared across the supervisor session. If a customer starts with a support question and then asks about billing, the context carries over — the billing agent knows what was discussed before.

When to Use Multi-Agent

ScenarioUse
Single-purpose bot (FAQ, scheduling)Single agent
Multiple domains with different tools/knowledgeMulti-agent
Complex routing with fallbacksMulti-agent
Different response styles per departmentMulti-agent

Next Steps

Agents

Supervisor pattern details

Customer Support

Single-agent support example

Sales Automation

Sales workflow example

Best Practices

Tips for effective agents