# Multi-agent patterns

> Compose, parallelise, and route between agents.

Source: https://maincode.com/docs/agent-sdk-multi-agent
Section: Agent SDK · Matilda documentation

---

The SDK has no built-in orchestrator — multi-agent emerges from composition. The `Runner` is your execution primitive, and standard JavaScript patterns (chaining, `Promise.all`, tool-based delegation) build the architecture.

## Pattern 1: Sequential pipeline

Chain `run()` calls, feeding each agent's output to the next. Each stage has a single responsibility.

```ts
import { Agent, Runner } from '@maincode-ai/matilda-agent-sdk';

const runner = new Runner();

const researcher = new Agent({
  name: 'researcher',
  purpose: 'analysis',
  instructions: 'Produce a structured list of key facts for a blog post. Bullet points only.',
});

const writer = new Agent({
  name: 'writer',
  purpose: 'general',
  instructions: 'Given research notes, write an engaging blog post draft under 400 words.',
});

const editor = new Agent({
  name: 'editor',
  purpose: 'general',
  instructions: 'Polish the draft for clarity, grammar, and flow. Return the full revised post.',
});

const topic = 'Why developers are adopting AI coding assistants';

// Stage 1 → 2 → 3
const research = await runner.run(researcher, `Research this topic: ${topic}`);
const draft = await runner.run(writer, research.finalOutput);
const edited = await runner.run(editor, draft.finalOutput);

console.log(edited.finalOutput);

// Total token usage across the pipeline
const totalTokens =
  (research.usage?.output_tokens ?? 0) +
  (draft.usage?.output_tokens ?? 0) +
  (edited.usage?.output_tokens ?? 0);
console.log(`Total output tokens: ${totalTokens}`);
```

## Pattern 2: Parallel fan-out / fan-in

Run multiple specialist agents concurrently with `Promise.all()`, then feed their outputs to a synthesiser.

```ts
const securityReviewer = new Agent({
  name: 'security-reviewer',
  purpose: 'analysis',
  instructions: 'Review code for vulnerabilities. Report only security issues.',
});

const performanceReviewer = new Agent({
  name: 'performance-reviewer',
  purpose: 'analysis',
  instructions: 'Review code for efficiency. Report only performance issues.',
});

const synthesiser = new Agent({
  name: 'synthesiser',
  purpose: 'analysis',
  instructions: 'Given reviews from multiple reviewers, produce a prioritised action list.',
});

const code = 'function getUserData(userId, db) { /* ... */ }';

// Fan-out: three reviewers analyse concurrently
const [security, performance] = await Promise.all([
  runner.run(securityReviewer, `Review this code:\n\`\`\`javascript\n${code}\n\`\`\``),
  runner.run(performanceReviewer, `Review this code:\n\`\`\`javascript\n${code}\n\`\`\``),
]);

// Fan-in: synthesiser merges the reviews
const combinedInput = [
  '## Security Review', security.finalOutput,
  '## Performance Review', performance.finalOutput,
].join('\n');

const synthesis = await runner.run(synthesiser, combinedInput);
console.log(synthesis.finalOutput);
```

## Pattern 3: Router / delegator

A triage agent receives queries and decides which specialist to invoke. Each specialist is exposed as a client tool — when the agent calls a tool, the SDK handler runs the specialist agent via `run()` and returns its output.

```ts
import { Agent, Runner, type ToolHandlers, type AgentRunOptions } from '@maincode-ai/matilda-agent-sdk';

const runner = new Runner();

const billingSpecialist = new Agent({
  name: 'billing-specialist',
  instructions: 'You are a billing support specialist.',
});

const technicalSpecialist = new Agent({
  name: 'technical-specialist',
  instructions: 'You are a technical support specialist. Include code examples when relevant.',
});

const triageAgent = new Agent({
  name: 'triage',
  purpose: 'general',
  instructions: 'You are a customer support triage specialist.',
});

const triageTools = [
  {
    name: 'ask_billing_specialist',
    description: 'Route billing questions to the billing specialist.',
    parameters: { type: 'object' as const, properties: { question: { type: 'string' } }, required: ['question'] },
  },
  {
    name: 'ask_technical_specialist',
    description: 'Route technical questions to the technical specialist.',
    parameters: { type: 'object' as const, properties: { question: { type: 'string' } }, required: ['question'] },
  },
];

const toolHandlers: ToolHandlers = {
  ask_billing_specialist: async (args) => {
    const result = await runner.run(billingSpecialist, `Answer: ${args.question}`);
    return { content: result.finalOutput };
  },
  ask_technical_specialist: async (args) => {
    const result = await runner.run(technicalSpecialist, `Answer: ${args.question}`);
    return { content: result.finalOutput };
  },
};

const runOptions: AgentRunOptions = {
  toolHandlers,
  clientTools: triageTools,
  maxToolRoundtrips: 6,
  callbacks: {
    onToolCall: (name) => console.log(`Triage chose: ${name}`),
    onToolResult: (_name, result) => console.log(`Specialist responded.`),
    onToken: (delta) => process.stdout.write(delta),
  },
};

const triagePrompt = [
  'A customer asked:',
  '"I\'m getting a 401 Unauthorized error when calling the /api/chat endpoint."',
  'You MUST forward this to a specialist by calling a tool.',
  'After the specialist responds, relay their answer.',
].join('\n');

await runner.run(triageAgent, triagePrompt, runOptions);
```

> **Tip** — **Tip:** Keep the triage agent's instructions short — put the routing rules in the task prompt. If routing rules are in the `instructions` field, the server's Auto-mode system prompt may interpret them as a prompt-injection attempt rather than operating instructions.
