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-pattern | Fix |
|---|---|
| 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:
- Explain the platform's capabilities
- Prompt the user to be more specific
- 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,
agentIdis 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-miniis optimized for fast, cheap classification tasks — routing is a single intent-classification call, not a reasoning tasktemperature: 0makes 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