Multi-Agent Orchestration

Use an orchestrator agent to delegate tasks to specialists

Tutti supports multi-agent workflows where an orchestrator routes tasks to specialist agents. Each specialist has its own system prompt, tools, and constraints.

The pattern

User → Orchestrator → [Coder, PM, QA] → Orchestrator → User

The orchestrator receives the user’s request, decides which specialist to delegate to, and summarizes the results.

Define the score

import { AnthropicProvider, defineScore } from "@tuttiai/core";
import { FilesystemVoice } from "@tuttiai/filesystem";

export default defineScore({
  name: "dev-team",
  provider: new AnthropicProvider(),
  default_model: "claude-sonnet-4-20250514",
  entry: "orchestrator",
  agents: {
    orchestrator: {
      name: "Orchestrator",
      role: "orchestrator",
      system_prompt: `You receive user requests and delegate them to the right specialist.
Think about which specialist is best suited before delegating.
Summarize results clearly.`,
      voices: [],
      delegates: ["coder", "pm", "qa"],
    },
    coder: {
      name: "Coder",
      role: "specialist",
      system_prompt: `You are a senior TypeScript developer.
Write clean, tested, production-ready code.`,
      voices: [new FilesystemVoice()],
      permissions: ["filesystem"],
    },
    pm: {
      name: "Product Manager",
      role: "specialist",
      system_prompt: `You are a senior product manager.
Write clear specs, break down features, think about UX.`,
      voices: [],
    },
    qa: {
      name: "QA Engineer",
      role: "specialist",
      system_prompt: `You are a thorough QA engineer.
Think about edge cases, write test plans, identify bugs.`,
      voices: [],
    },
  },
});

Run with the AgentRouter

import { AgentRouter } from "@tuttiai/core";
import score from "./tutti.score.js";

const router = new AgentRouter(score);

// Subscribe to delegation events
router.events.on("delegate:start", (e) => {
  console.log(`[delegation] ${e.from} → ${e.to}: "${e.task}"`);
});
router.events.on("delegate:end", (e) => {
  console.log(`[delegation] ${e.to} finished (${e.output.length} chars)`);
});

const result = await router.run(
  "Write a function that reverses a string and save it to reverse.ts",
);
console.log(result.output);

How it works

  1. AgentRouter wraps TuttiRuntime and injects a delegate_to_agent tool into the orchestrator
  2. The orchestrator’s system prompt is enhanced with descriptions of available specialists
  3. When the orchestrator calls delegate_to_agent({ agent_id: "coder", task: "..." }), the router runs the specialist agent and returns its output
  4. The orchestrator can delegate to multiple specialists in sequence

Delegation events

Listen to these events to track the flow:

EventFieldsWhen
delegate:startfrom, to, taskOrchestrator delegates to a specialist
delegate:endfrom, to, outputSpecialist returns its result

Tips

:::tip Give each specialist a clear, focused system prompt. The orchestrator works best when it can match tasks to well-defined roles. :::

:::caution Each delegation is a separate agent run with its own token usage. Set budget on specialists to prevent runaway costs. :::

Parallel fan-out

When you want multiple agents to look at the same input simultaneously (e.g. a bull analyst and a bear analyst, or several reviewers), use parallel execution instead of sequential delegation.

Declarative parallel entry

Set entry to a ParallelEntryConfig and router.run(input) will fan the input out to every listed agent at once, returning a merged AgentResult:

export default defineScore({
  provider: new AnthropicProvider(),
  entry: { type: "parallel", agents: ["bull", "bear"] },
  agents: {
    bull: { name: "Bull", system_prompt: "...", voices: [] },
    bear: { name: "Bear", system_prompt: "...", voices: [] },
  },
});

Programmatic runParallel()

For per-agent inputs, timeouts, and rich rollup metrics, call runParallel directly:

const router = new AgentRouter(score);

const results = await router.runParallel(
  [
    { agent_id: "bull", input: "AAPL at current valuation" },
    { agent_id: "bear", input: "AAPL at current valuation" },
  ],
  { timeout_ms: 30_000 },
);

for (const [agentId, result] of results) {
  console.log(`${agentId}: ${result.output}`);
}
  • All agents start at once via Promise.all; each gets its own session.
  • If one agent fails, the others still complete — the failure is surfaced as a synthetic [error] result in the returned map (so callers can see which agent broke).
  • timeout_ms caps wall-clock time for any single agent and cancels stragglers.

For the full aggregate (merged output + total usage + total cost + duration) use runParallelWithSummary, which returns a ParallelAgentResult.

Parallel events

EventFieldsWhen
parallel:startagentsBefore the batch dispatches
parallel:completeresults (agent IDs that succeeded)After every agent has settled

Edit this page on GitHub →