Replaces the deprecated
allow_delegation=True — see Legacy Agent Parameters.- Python
- TypeScript
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 sethandoffs, 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
Handoff context threading. The messages produced by the configured
context_policy and input_filter are seeded onto the target agent’s chat_history for the duration of the handoff, then the target’s original history is restored on exit. Seeding is invocation-scoped: it does not leak into later handoffs or ordinary chats that reuse the same target, and sequential handoffs do not accumulate context. See Handoff Filters and Handoff Config.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.parallel_handoffs (aliased parallelHandoffs), imported top-level from praisonai:
HandoffTimeoutError is always retryable in TypeScript too — error.isRetryable === true — because a timeout may succeed on a second attempt. Cycle and depth guards (HandoffCycleError, HandoffDepthError) carry context.cycle_path / context.max_depth. The handoff context accepts the back-compat aliases chain, depth, and max_depth. As of PR #4836, TypeScript honours timeoutSeconds, maxDepth, and detectCycles per handoff — the same knobs Python exposes. See TypeScript Handoffs.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.
asyncio.run() more than once keeps working.
The same per-instance limit works in TypeScript — pass maxConcurrent in each handoff’s config:
Handoff Results
handoff_to() returns a HandoffResult describing the outcome.
Best Practices
Give each agent one clear responsibility
Give each agent one clear responsibility
A specialist with a focused role routes cleanly. Overlapping responsibilities make the routing agent’s tool choice ambiguous.
Filter history to cut tokens
Filter history to cut tokens
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.Use TypedHandoff for structured data
Use TypedHandoff for structured data
When a specialist needs typed fields, use
TypedHandoff(agent=..., input_schema=Model) — the framework validates the payload at the boundary.Keep a fallback path
Keep a fallback path
Let the routing agent answer requests it cannot route rather than failing silently.
Related
Handoff Tool Policy
Secure tool boundaries during handoff
Typed Handoffs
Schema-validated handoffs with Pydantic models

