Routing & Handoffs
When a conversation can go to multiple specialists, you need a router. Kuralle’s routes + routing setup handles the decision and the handoff without surfacing triage logic to the user.
Defining a router
Section titled “Defining a router”Add routes and routing to defineAgent:
import { openai } from '@ai-sdk/openai';import { defineAgent } from '@kuralle-agents/core';
const triage = defineAgent({ id: 'triage', routes: [ { agent: 'billing', when: 'billing question or payment issue' }, { agent: 'support', when: 'general product support or anything else' }, ], routing: { model: openai('gpt-4o-mini') },});Each routes entry needs a when description and a target — either an agent (a destination agent’s id) or a flow (a flow on this agent). The model uses the when descriptions to select the right route.
Derived routing behavior
Section titled “Derived routing behavior”Routing is derived from which fields you populate — there is no routing.mode:
- Pure dispatcher —
routes/agents/handoffsonly, with no local answering surface (noinstructions,tools,flows, etc.). The runtime runs a silent internal classifier and dispatches without speaking. - Answering agent — has
instructions,flows,tools, or other local affordances. Host-control tools (enter_flow,transfer_to_agent) fold routing into the speaking turn; a lazy model-reasoned guard runs only when the turn produces no answer and no control tool, catching the case where the model neither answered nor routed.
Set routing: { model } to classify with a separate (e.g. cheaper) control model. For compliance text that must never stream before dispatch resolves, set routing: { dispatch: 'strict' }.
Model every fallback as a normal route with a semantic when (e.g. “general support”) — there is no routing.default.
Handoffs
Section titled “Handoffs”A handoff moves the conversation to another agent and carries full session context. The receiving agent picks up with the complete message history.
For tool-based handoffs — where the agent decides explicitly when to transfer — declare which agent IDs the agent can hand off to with handoffs, and wire a handoff tool:
const transferToSpanish = defineTool({ name: 'transfer_to_spanish', description: 'Transfer to a Spanish-speaking agent when the user requests Spanish.', input: z.object({ reason: z.string() }), execute: async ({ reason }) => ({ __handoff: true, targetAgentId: 'spanish-agent', reason, }),});
const englishAgent = defineAgent({ id: 'english', instructions: 'Friendly assistant. If the user speaks Spanish, use transfer_to_spanish.', model: openai('gpt-4o-mini'), handoffs: ['spanish-agent'], agents: [spanishAgent], tools: { transfer_to_spanish: transferToSpanish },});The runtime intercepts { __handoff: true, targetAgentId } in the tool result and routes the session to the target agent. Kuralle also ships a createHandoffTool helper that builds this transfer tool for you.
Example: triage router
Section titled “Example: triage router”import { openai } from '@ai-sdk/openai';import { defineAgent, createRuntime } from '@kuralle-agents/core';
const billingAgent = defineAgent({ id: 'billing', instructions: 'You handle billing questions and payment issues.', model: openai('gpt-4o-mini'),});
const supportAgent = defineAgent({ id: 'support', instructions: 'You handle product support requests.', model: openai('gpt-4o-mini'),});
const triage = defineAgent({ id: 'triage', routes: [ { agent: 'billing', when: 'billing question or payment issue' }, { agent: 'support', when: 'product support or general help' }, ], routing: { model: openai('gpt-4o-mini') },});
const runtime = createRuntime({ agents: [triage, billingAgent, supportAgent], defaultAgentId: 'triage',});