Theo Docs
TheoKitTheoKit-SDKTheoUITheo PaaS
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 run

Run (OpenRouter cloud)

export OPENROUTER_API_KEY=sk-or-...
pnpm run run

What it shows

  • Agent.create({ handoffs: [billing, support] }) declares peer-to-peer transfers (ADRs D214-D229).
  • RECOMMENDED_HANDOFF_PROMPT_PREFIX exported 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:

ModelReliability
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: 5 per send() (default; D218). Override via Agent.create({ maxHandoffDepth: N }).
  • Set maxHandoffDepth: 0 to disable handoffs entirely (EC-8 — tools never fire).
  • Pair single-flight (D221): A → B → A within the same send() throws HandoffPairLoopError. Use a 3rd agent for legitimate "back to triage" patterns.

Code

run.ts
/**
 * 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

Repository

examples/handoffs

On this page