Skip to main content
Handoffs let one agent transfer a conversation to a specialist agent based on the request.
Replaces the deprecated allow_delegation=True — see Legacy Agent Parameters.
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.
2

Configure the handoff tool

Use handoff() to rename the transfer tool or steer when the agent should call it.

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.

Which Handoff Setup to Use?


Configuration Options

handoff() builds a configured transfer tool for a target agent.
Handoff Tool Policy — tool security boundary options

Common Patterns

Pattern 1 — Callback on handoff

Pattern 2 — Filter passed history

Pattern 3 — Type-safe handoff


Running handoffs in parallel

parallel_handoffs runs several handoffs at once with concurrency control.
1

List the targets

Pass (agent, prompt) tuples — each runs as its own task.
2

Run them concurrently

Await parallel_handoffs and inspect each HandoffResult.
Sibling tasks spawned by parallel_handoffs (or any asyncio.gather over handoff_to_async) each get an isolated handoff chain. Cycle detection and max_depth are enforced per task, not shared across siblings. See Handoff chain isolation under asyncio.gather.
A handoff rejected by a safety check (e.g. allowed_agents block, cycle guard, max_depth) does not consume a chain slot. The parent agent’s cycle detection and max_depth counters remain accurate for any subsequent handoffs it attempts in the same turn.

Per-Handoff concurrency

max_concurrent is enforced by a semaphore private to each Handoff instance, not shared across the process. Two Handoffs with different max_concurrent values each get their own limit — the earlier one no longer clamps the later one.
The billing lane runs up to 10 concurrent handoffs; the refunds lane serializes to one at a time. Each semaphore also rebinds automatically to the current event loop, so a process that calls asyncio.run() more than once keeps working.
RuntimeError: bound to a different event loop used to appear when a process called asyncio.run() a second time — the shared semaphore stayed bound to the first loop. The per-instance semaphore now rebinds to the current loop automatically. If you still hit this on an older version, run pip install -U praisonaiagents.

Handoff Results

handoff_to() returns a HandoffResult describing the outcome.

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