Skip to main content
Handoffs let one agent transfer a conversation to a specialist agent based on the request.
from praisonaiagents import Agent

billing = Agent(name="Billing", instructions="Handle billing inquiries.")
refund = Agent(name="Refunds", instructions="Process refund requests.")
triage = Agent(
    name="Triage",
    instructions="Route customer inquiries to the right specialist.",
    handoffs=[billing, refund],
)

triage.start("I need a refund for my last order")
The user describes their issue; the triage agent delegates to a specialist via a handoff tool call.
Handoffs are secure by default — the target agent only inherits tools shared with the source agent. See Handoff Tool Policy.

Quick Start

1

Pass agents directly

Pass specialist agents to handoffs and the routing agent gets a transfer tool for each.
from praisonaiagents import Agent

billing = Agent(name="Billing", instructions="Handle billing inquiries and payments.")
refund = Agent(name="Refunds", instructions="Process refund requests.")

triage = Agent(
    name="Triage",
    instructions="Route customer inquiries to the right specialist.",
    handoffs=[billing, refund],
)
triage.start("I need a refund for my order")
2

Configure the handoff tool

Use handoff() to rename the transfer tool or steer when the agent should call it.
from praisonaiagents import Agent, handoff

escalation = Agent(name="Manager", instructions="Handle escalations.")

triage = Agent(
    name="Triage",
    instructions="Route inquiries; escalate hard cases.",
    handoffs=[
        handoff(
            escalation,
            tool_name_override="escalate_to_manager",
            tool_description_override="Escalate complex issues to a manager.",
        ),
    ],
)
triage.start("This is my third failed delivery — I want a manager.")

How It Works

When you set handoffs, PraisonAI converts each target into a transfer_to_<agent> tool, adds routing instructions to the agent’s prompt, and passes conversation history when control transfers.
PhaseWhat happens
1. DetectThe routing agent decides a specialist is needed
2. TransferThe matching handoff tool is called
3. RunThe target agent runs its full chat() pipeline
4. ReturnThe specialist’s response flows back to the user

Which Handoff Setup to Use?


Configuration Options

handoff() builds a configured transfer tool for a target agent.

Handoff Tool Policy — tool security boundary options
OptionTypeDefaultDescription
agentAgentrequiredTarget agent to hand off to
tool_name_overridestr | NoneNoneCustom tool name (default transfer_to_<agent>)
tool_description_overridestr | NoneNoneCustom tool description shown to the LLM
on_handoffCallable | NoneNoneCallback run when the handoff is invoked
input_typetype | NoneNoneExpected input type for structured data
input_filterCallable | NoneNoneFunction to filter/transform passed history
tool_policy_mode"intersect" | "passthrough""intersect"Which tools the target inherits
blocked_toolslist[str] | NoneNoneTools always stripped from the target

Common Patterns

Pattern 1 — Callback on handoff

from praisonaiagents import Agent, handoff

def log_handoff(from_agent, to_agent, context):
    print(f"Transfer: {from_agent.name} -> {to_agent.name}")

billing = Agent(name="Billing", instructions="Handle billing inquiries.")

triage = Agent(
    name="Triage",
    instructions="Route billing questions to the specialist.",
    handoffs=[handoff(billing, on_handoff=log_handoff)],
)
triage.start("Why was I charged twice?")

Pattern 2 — Filter passed history

from praisonaiagents import Agent, handoff
from praisonaiagents import handoff_filters

technical = Agent(name="TechSupport", instructions="Solve technical problems.")

triage = Agent(
    name="Triage",
    instructions="Route technical issues to support.",
    handoffs=[handoff(technical, input_filter=handoff_filters.remove_all_tools)],
)
triage.start("The app crashes when I upload a file.")

Pattern 3 — Type-safe handoff

from praisonaiagents import Agent
from praisonaiagents.agent.handoff import TypedHandoff
from pydantic import BaseModel

class TaskData(BaseModel):
    priority: int
    description: str

specialist = Agent(name="Specialist", instructions="Resolve prioritised tasks.")

triage = Agent(
    name="Triage",
    instructions="Route tasks to the specialist with priority and description.",
    handoffs=[TypedHandoff(agent=specialist, input_schema=TaskData)],
)
triage.start("Escalate: priority 1, database is down.")

Handoff Results

handoff_to() returns a HandoffResult describing the outcome.
from praisonaiagents import Agent

billing = Agent(name="Billing", instructions="Handle billing issues.")
support = Agent(name="Support", instructions="Front-line support.", handoffs=[billing])

result = support.handoff_to(billing, "Handle this billing issue")

if result.outcome.status == "success":
    print(result.outcome.output)
elif result.outcome.status == "timeout":
    print(f"Timed out: {result.outcome.error}")
else:
    print(f"Failed: {result.outcome.error}")
FieldTypeDescription
successboolWhether the handoff completed successfully
responsestrResponse from the target agent
target_agentstrName of the agent that received the handoff
source_agentstrName of the agent that initiated the handoff
duration_secondsfloatTime taken for the handoff
errorstrError message if the handoff failed
outcomeAgentRunOutcomeTyped outcome with .status and .is_retryable()

Best Practices

A specialist with a focused role routes cleanly. Overlapping responsibilities make the routing agent’s tool choice ambiguous.
Pass input_filter=handoff_filters.remove_all_tools or handoff_filters.keep_last_n_messages(5) so the target agent gets only the context it needs.
When a specialist needs typed fields, use TypedHandoff(agent=..., input_schema=Model) — the framework validates the payload at the boundary.
Let the routing agent answer requests it cannot route rather than failing silently.

Handoff Tool Policy

Secure tool boundaries during handoff

Typed Handoffs

Schema-validated handoffs with Pydantic models