stratus

Handoffs

Route conversations between specialized agents

Handoffs let one agent transfer a conversation to another agent mid-turn. This enables multi-agent architectures like triage → specialist routing.

Basic Handoff

Pass agents directly to handoffs. Stratus auto-generates a tool named transfer_to_{agent_name}:

handoff.ts
import { Agent, run } from "@usestratus/sdk/core";

const mathAgent = new Agent({
  name: "math",
  model,
  instructions: "You are a math expert. Solve math problems.",
});

const triageAgent = new Agent({
  name: "triage",
  model,
  instructions: "Route the user to the right specialist.",
  handoffs: [mathAgent],
});

const result = await run(triageAgent, "What is the integral of x^2?");
console.log(result.lastAgent.name); // "math"

Custom Handoff Configuration

Use the handoff() function for more control:

custom-handoff.ts
import { handoff } from "@usestratus/sdk/core";

const agent = new Agent({
  name: "triage",
  model,
  handoffs: [
    handoff({
      agent: mathAgent,
      toolName: "escalate_to_math", 
      toolDescription: "Escalate complex math problems to the math specialist",
      onHandoff: async (ctx) => { 
        console.log("Handing off to math agent");
        await logHandoff(ctx, "math");
      },
    }),
  ],
});

Handoff Config Options

PropertyTypeDescription
agentAgentRequired. The target agent
toolNamestringCustom tool name (default: transfer_to_{name})
toolDescriptionstringCustom description for the model
onHandoff(ctx) => voidCallback that fires when the handoff executes
inputTypez.ZodTypeZod schema for structured input the model sends with the handoff
inputFilterHandoffInputFilterTransform conversation history passed to the target agent
isEnabledboolean | (ctx) => booleanWhen false, the handoff is excluded from the model's tool list

Structured Handoff Input

Use inputType to let the model send structured data with a handoff. The Zod schema becomes the tool's parameter schema:

structured-handoff.ts
import { handoff } from "@usestratus/sdk/core";
import { z } from "zod";

const agent = new Agent({
  name: "triage",
  model,
  handoffs: [
    handoff({
      agent: mathAgent,
      inputType: z.object({ 
        problem: z.string().describe("The math problem to solve"), 
        difficulty: z.enum(["easy", "medium", "hard"]), 
      }), 
    }),
  ],
});

Input Filter

Use inputFilter to transform the conversation history before it's passed to the target agent. This is useful for trimming irrelevant messages or redacting sensitive content:

input-filter.ts
handoff({
  agent: specialistAgent,
  inputFilter: ({ history, input }) => { 
    // Only pass user and assistant messages (drop tool messages)
    return history.filter((m) => m.role === "user" || m.role === "assistant");
  },
});

The filter receives a HandoffInputData object:

interface HandoffInputData {
  history: ChatMessage[];  // Full conversation history
  input?: unknown;         // Parsed input (if inputType is set)
}

Conditional Handoffs (isEnabled)

Use isEnabled to dynamically include or exclude a handoff based on context:

conditional-handoff.ts
handoff({
  agent: adminAgent,
  isEnabled: (ctx: AppContext) => ctx.isAdmin, 
});

When false, the handoff tool is not sent to the model. Same pattern as conditional tools.

How Handoffs Work

Registered as tool

The handoff is registered as a tool definition alongside the agent's other tools.

Model calls the tool

When the model decides to hand off, it calls the handoff tool.

Callback fires

Stratus executes onHandoff (if provided), then replaces the current agent with the target.

System prompt swaps

The system prompt is replaced with the new agent's instructions. The model loop continues.

Handoffs can be blocked by beforeHandoff hooks returning { decision: "deny" }. See Hooks - Permission Control.

Handoffs in Sessions

session-handoff.ts
const session = createSession({
  model,
  instructions: "Route users to the right specialist.",
  handoffs: [mathAgent, writingAgent],
});

session.send("Help me write a poem");
for await (const event of session.stream()) {
  if (event.type === "content_delta") process.stdout.write(event.content);
}

const result = await session.result;
console.log(result.lastAgent.name); // "writing"

Each stream() call starts from the session's configured agent. Handoffs within a turn don't persist to the next turn.

Multi-Agent Patterns

Triage Pattern

A triage agent routes to specialists based on the user's request:

triage.ts
const orderAgent = new Agent({
  name: "orders",
  model,
  instructions: "Help with order lookups and status.",
  tools: [lookupOrder],
  handoffDescription: "Transfer for order status and tracking", 
});

const refundAgent = new Agent({
  name: "refunds",
  model,
  instructions: "Process refund requests.",
  tools: [processRefund],
  handoffDescription: "Transfer for refund processing", 
});

const triage = new Agent({
  name: "triage",
  model,
  instructions: "You are a customer support triage agent.",
  handoffs: [orderAgent, refundAgent],
});

Handoffs vs Subagents

Handoffs transfer control - the child takes over. Subagents delegate and return - the parent keeps control. See Subagents for the delegation pattern.

Edit on GitHub

Last updated on

On this page