Docs
Customizing the Orchestrator

Customizing the Orchestrator

How to tune routing behavior, improve agent descriptions, and avoid common mis-routing pitfalls.

How the Routing Prompt Is Built

The orchestrator builds its system prompt entirely from AGENT_REGISTRY:

system: `
  You are the core Orchestrator Router. Your ONLY job is to analyze the user's message and determine the domain.
  Today's date is ${today}.
  Domains:
    ${Object.entries(AGENT_REGISTRY)
      .map(([key, meta]) => `- ${key}: ${meta.description}`)
      .join("\n")}
  Return the chosen domain.
`,

The model sees nothing else about your agents — only their keys and descriptions. The description field is the entire routing logic.

Writing Non-Overlapping Descriptions

The single most common cause of mis-routing is overlapping descriptions. The model has to pick one — if two descriptions could both match the user's message, you get random behavior.

Anti-patternFix
Two agents both say "creates content"Specify the medium: "social posts" vs "long-form scripts"
"Handles documents" and "analyzes files"Be explicit: "scans receipt images" vs "analyzes legal PDFs for risks"
"AI assistant" and "helps users"Use task-specific verbs: "writes scripts" vs "generates thumbnails"

Example of well-separated descriptions from the default registry:

content-creation: Expert at writing copy, SEO, and generating social assets.
reel-generator:   Creates complete short-form video content packages for Instagram Reels, TikTok, and YouTube Shorts.
script-writer:    Writes long-form scripts for YouTube videos, podcasts, webinars, and ad copy.
thumbnail-creator: Generates thumbnail design concepts and creates actual images with GPT Image.
receipt-scanner:  Scans receipt images and extracts structured line-item data including totals and payment info.
legal-analyzer:   Analyzes legal documents (PDFs) for risks, obligations, key dates, and can email the analysis to a client.

Each description answers a different question. No overlap.

The general Agent as Fallback

If the orchestrator can't confidently classify a message, it routes to general. The general agent's system prompt is designed to:

  1. Explain the platform's capabilities
  2. Prompt the user to be more specific
  3. Never fulfill content creation requests itself

This means vague messages naturally loop back to a more specific request on the next turn — which the router can then classify correctly.

Bypassing the Router

Pass agentId in the request body to skip the LLM routing call entirely. This is used for two cases:

  • The agent selector in the chat UI — when the user picks a specific agent from the dropdown, agentId is sent with every message in that conversation
  • Dedicated agent pages — pages built around a single agent (e.g. a standalone Receipt Scanner page) hardcode the ID so routing never runs
const response = await fetch("/api/assistant/router", {
  method: "POST",
  body: JSON.stringify({
    messages,
    agentId: "receipt-scanner", // always routes here, no LLM call
  }),
});

This saves one gpt-4o-mini call per request and guarantees correct routing whenever the agent is already known.

The Orchestrator Model

The orchestrator is hardcoded to openai("gpt-4o-mini") with temperature: 0. This is the recommended setup for cost efficiency:

  • gpt-4o-mini is optimized for fast, cheap classification tasks — routing is a single intent-classification call, not a reasoning task
  • temperature: 0 makes routing deterministic and reproducible
  • Upgrading to a larger model (like gpt-4o) would add latency and cost to every single chat message with no meaningful improvement in routing accuracy