Cookbook
handoffs
Triage agent → billing/support specialist. Demonstrates the
handoffs
Triage agent → billing/support specialist. Demonstrates the
handoffs: [] declarative API.
Run (Ollama)
ollama serve &
ollama pull llama3.2:3b
pnpm install
pnpm run runRun (OpenRouter cloud)
export OPENROUTER_API_KEY=sk-or-...
pnpm run runWhat it shows
Agent.create({ handoffs: [billing, support] })declares peer-to-peer transfers (ADRs D214-D229).RECOMMENDED_HANDOFF_PROMPT_PREFIXexported as a constant; including it in the sender's system prompt makes the LLM use the transfer tools reliably.Handoff.create(target, { toolDescription })shows the customization escape hatch.- Each handoff is exposed to the LLM as
transfer_to_<receiver_name>; the LLM decides based on the user's intent.
Model quality dependency (EC-14)
Handoffs require reliable function-calling. Tested combinations:
| Model | Reliability |
|---|---|
openai/gpt-4o-mini (cloud) | ✅ Excellent |
anthropic/claude-3-5-haiku (cloud) | ✅ Excellent |
ollama/llama3.2:3b (local) | ⚠️ Inconsistent — small models often skip the transfer tool |
ollama/qwen2.5:7b (local) | ✅ Good |
ollama/llama3.1:8b (local) | ✅ Good |
ollama/mistral:7b (local) | ✅ Good |
Rule of thumb: local models under ~7B params struggle with the handoff
tool-call decision (~30% miss rate observed). For local development, prefer
7B+ models OR test with Agent.handoffTo imperative as fallback.
Cost tradeoff for deep chains (EC-12)
Full conversation history is passed to each receiver by default (D216). For
chains depth > 2, consider Handoff.create(target, { inputFilter: summarize })
to bound token cost. Token totals stack across hops; a 3-hop chain on a
5-message history roughly triples the prompt tokens.
Loop protection
maxHandoffDepth: 5persend()(default; D218). Override viaAgent.create({ maxHandoffDepth: N }).- Set
maxHandoffDepth: 0to disable handoffs entirely (EC-8 — tools never fire). - Pair single-flight (D221): A → B → A within the same
send()throwsHandoffPairLoopError. Use a 3rd agent for legitimate "back to triage" patterns.
Code
/**
* Example: triage agent transfers to billing OR support based on intent.
*
* ollama serve & ollama pull llama3.2:3b
* pnpm install
* pnpm run run
*
* Or with cloud:
* export OPENROUTER_API_KEY=sk-or-...
* pnpm run run
*/
import {
Agent,
Handoff,
RECOMMENDED_HANDOFF_PROMPT_PREFIX,
} from "@theokit/sdk";
const useCloud = typeof process.env.OPENROUTER_API_KEY === "string";
const baseAgentConfig = useCloud
? {
apiKey: process.env.OPENROUTER_API_KEY ?? "",
model: { id: "openai/gpt-4o-mini" },
local: { cwd: process.cwd(), sandboxOptions: { enabled: false } as const },
providers: {
routes: [{ capability: "chat" as const, provider: "openrouter" }],
fallback: ["openrouter"],
},
}
: {
apiKey: "ollama-local",
model: { id: "ollama/llama3.2:3b" },
local: { cwd: process.cwd(), sandboxOptions: { enabled: false } as const },
};
// Specialists
const billing = await Agent.create({
...baseAgentConfig,
name: "billing",
systemPrompt:
"You are a billing specialist. Answer questions about invoices, charges, and payments concisely.",
});
const support = await Agent.create({
...baseAgentConfig,
name: "support",
systemPrompt:
"You are a technical support specialist. Answer questions about installation, configuration, and troubleshooting.",
});
// Triage routes to the right specialist
const triage = await Agent.create({
...baseAgentConfig,
name: "triage",
systemPrompt: `${RECOMMENDED_HANDOFF_PROMPT_PREFIX}
You are a triage agent. Listen to the user's question and IMMEDIATELY transfer
the conversation to the right specialist:
- billing questions → transfer_to_billing
- technical/install questions → transfer_to_support
Do NOT answer the user directly. Use exactly ONE transfer_to_* tool per turn.`,
handoffs: [billing, Handoff.create(support, { toolDescription: "Transfer to support for install/config issues" })],
});
console.log(`Triage agent ready (mode: ${useCloud ? "cloud" : "ollama"}).`);
const questions = [
"I have a question about my bill.",
"How do I install the SDK?",
];
for (const q of questions) {
console.log(`\n=== User: ${q}`);
const run = await triage.send(q);
const result = await run.wait();
console.log(`=== Triage status: ${result.status}`);
console.log(`=== Response:`);
console.log(result.result ?? `(${result.status}${result.error ? `: ${result.error.message}` : ""})`);
}
await triage.dispose();
await billing.dispose();
await support.dispose();
console.log("\nDone.");
Run
cd examples/handoffs
cp .env.example .env # fill in keys
pnpm install
pnpm run run