Shirube

Documentation

Handoffs

Transfer the current run from one agent to another. Conversation stays; the specialist’s instructions and tools take over.

What they are

A handoff transfers the *current run* from one agent to another. The conversation so far is kept. The specialist’s instructions and tools take over. It is not a nested run() that throws away context.

Use when one agent should not do everything: a triage bot for “where is my package?” vs a billing agent for “double charge”.

Do not use handoffs for a simple tool call. If billing is one HTTP API, make a refund tool. Handoffs are for a different *policy and tool set*.

How to wire them

Build the specialist first, then pass it into the parent:

triage.ts
const billing = Agent.builder()
  .name("billing")
  .instructions("You resolve invoices and charges. Be precise about amounts.")
  .apiKey(key)
  .tools([lookupInvoice, issueCredit])
  .build();

const triage = Agent.builder()
  .name("triage")
  .instructions(
    "Greet the user. If the issue is billing or invoices, transfer to billing. Otherwise help yourself.",
  )
  .apiKey(key)
  .handoffs([billing])
  .maxHandoffs(3)
  .build();

const result = await triage.run("I was charged twice for March.");
console.log(result.handoffs);
console.log(result.agentName);
console.log(result.output);

The model on triage sees a tool named transfer_to_billing with a reason argument. Shirube executes the transfer; you do not call billing.run() yourself.

Loop prevention

GuardBehavior
maxHandoffs (default 4)Further transfers are refused; the current agent is told why and must answer
Visited setThe same agent cannot be entered twice in one run (A → B → A is blocked)

Blocked handoffs are not thrown as user-facing errors. They become a system note so the model can finish.

Observability

  • result.handoffs — strings like triage->billing
  • Trace event type: "handoff"
  • Runtime event handoff.started (from, to, reason)

Use these in logs when a ticket “suddenly” changed personality.