Handoffs from agents.yaml

Declare handoffs and HandoffConfig from YAML or CLI — no Python needed

Declare which agents can hand off to which — and their execution limits — directly in agents.yaml.

Diagram

YAML Handoffs

📄 agents.yaml
handoff: to: – billing

🔧 _wire_handoffs

🤝 Agent.handoffs
+ HandoffConfig

✅ Handoff tools

Quick Start

  1. Declare handoff: in agents.yaml

    Add a handoff: block under any agent. to: lists the target agent keys.

    yaml
    # agents.yaml
    agents:
      triage:
        role: Customer Service Triage
        goal: Route the customer to the right specialist
        handoff:
          to: [billing, tech, refund]
          timeout: 30
          max_depth: 3
          max_concurrent: 1
          detect_cycles: true
      billing:
        role: Billing Specialist
        goal: Resolve billing questions
      tech:
        role: Technical Support
        goal: Fix technical issues
      refund:
        role: Refund Specialist
        goal: Process refunds
  2. Run it

    The adapter wires the handoff: dict into core Agent.handoffs — no Python required.

    bash
    praisonai run agents.yaml

Three Surfaces, Same Wiring

YAML, CLI, and Python all populate the same Agent.handoffs with the same HandoffConfig.

YAML

yaml
# agents.yaml
agents:
  triage:
    role: Customer Service Triage
    goal: Route the customer to the right specialist
    handoff:
      to: [billing, tech, refund]
      timeout: 30
      max_depth: 3
      max_concurrent: 1
      detect_cycles: true

CLI

bash
praisonai --handoff to=billing,tech,refund \
          --handoff-timeout 30 \
          --handoff-max-depth 3 \
          --handoff-max-concurrent 1 \
          --handoff-detect-cycles

Python

python
from praisonaiagents import Agent
from praisonaiagents.agent.handoff import HandoffConfig

billing = Agent(name="billing", role="Billing Specialist", goal="Resolve billing questions")
tech = Agent(name="tech", role="Technical Support", goal="Fix technical issues")
refund = Agent(name="refund", role="Refund Specialist", goal="Process refunds")

triage = Agent(
    name="triage",
    role="Customer Service Triage",
    goal="Route the customer to the right specialist",
    handoffs=[billing, tech, refund],
    handoff_config=HandoffConfig(
        timeout_seconds=30,
        max_depth=3,
        max_concurrent=1,
        detect_cycles=True,
    ),
)

The Python API is documented in full on the Agent Handoffs page.


How It Works

The adapter runs a wiring pass after every agent is built, then resolves each to: role by name.

Diagram
Agent.handoffs_wire_handoffsPraisonAI adapteragents.yamlAgent.handoffs_wire_handoffsPraisonAI adapteragents.yamlparse specs_build_agents_and_tasksrun after all agents builtmap keys → HandoffConfigresolve to: roles by namepopulate handoffs (+ config)_process_handoffs() → tools

Unknown target roles are logged and skipped — they never raise.


Configuration Options

Every key under handoff: maps onto a field of core HandoffConfig.

YAML keyTypeDefaultHandoffConfig fieldDescription
toList[str]required— (resolved to handoffs=[…])Target agent keys. Unknown roles warned + skipped.
timeoutint (seconds)300.0timeout_secondsPer-handoff execution timeout.
max_depthint10max_depthMaximum nested handoff chain length.
max_concurrentint5max_concurrentMaximum parallel handoffs (0 = unlimited).
detect_cyclesbooltruedetect_cyclesRefuse to hand off if it would form a cycle.
policystr—(not yet mapped)Wrapper-only orchestration hint. Currently a no-op — core has no equivalent.

Common Patterns

Triage router — one agent fans out to specialists with a single-hop limit:

yaml
agents:
  triage:
    role: Triage
    goal: Send each request to the right specialist
    handoff:
      to: [billing, tech]
      max_depth: 1

Safe chains — cap depth and refuse cycles for deep delegation graphs:

yaml
agents:
  planner:
    role: Planner
    goal: Break work into steps and delegate
    handoff:
      to: [researcher, writer]
      max_depth: 3
      detect_cycles: true

Best Practices

Match to: entries to agent keys, not display names

_wire_handoffs resolves each to: value against the agent keys in the same agents: map. A typo is logged and skipped, so the handoff silently disappears — check your logs if a target never fires.

Set max_depth for router agents

The default depth is 10. For a simple triage router, set max_depth: 1 so a request cannot bounce through multiple specialists.

Keep detect_cycles on

Cycle detection defaults to true. Leave it enabled unless you have a deliberate loop — it prevents two agents from handing off to each other forever.

policy: is a no-op today

The wrapper policy: key (e.g. round-robin) has no core equivalent yet, so it is parsed but ignored. Do not rely on it to change routing behaviour.


Related