Tutorials · Claude Agent SDK · examples / python / p4_handoff.pyExample code

Example code · python

p4_handoff.py

Handoffs. Control AND the conversation move between specialists; a router in code decides who's next.

Raw fileDownload all examples (zip)See its output →

Used in Part 4 §7.6 · Multi-agent topologies: handoffs, swarms, supervisors, blackboards, debate

"""Pattern 4: handoffs. Control AND the conversation move between specialists; a router in code decides who's next."""

import asyncio
import re

from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
from demo_util import PROJECT

SPECIALISTS = {
    "triage": dict(tools=[], prompt=(
        "You are front-desk triage for a software team. Acknowledge the user in one sentence, then hand off to the right "
        "specialist. If the message has BOTH a billing and a technical part, hand off to billing first. "
        "End your reply with exactly one line: HANDOFF:billing, HANDOFF:tech, or HANDOFF:done.")),
    "billing": dict(tools=[], prompt=(
        "You are the billing specialist. Resolve the billing part of the conversation so far in two sentences "
        "(you may promise a refund within 5 business days). If a technical issue remains unresolved in the conversation, "
        "end with HANDOFF:tech. Otherwise end with HANDOFF:done.")),
    "tech": dict(allowed_tools=["Read", "Grep", "Glob"], prompt=(
        "You are tech support with read access to the code in this directory. Diagnose the technical issue from the "
        "conversation so far by reading src/, name the file:line and a one-sentence fix. End with HANDOFF:done.")),
}


def next_agent(text: str) -> str | None:
    m = re.search(r"HANDOFF:(\w+)", text or "")
    return m.group(1) if m else None


async def handoff_loop(user_message: str, max_hops: int = 4):
    current, session_id, total = "triage", None, 0.0
    for hop in range(max_hops):                                                   # (1) bounded
        spec = SPECIALISTS[current]
        opts = ClaudeAgentOptions(
            cwd=PROJECT, max_turns=6, setting_sources=[],
            tools=spec.get("tools"), allowed_tools=spec.get("allowed_tools", []),
            system_prompt=spec["prompt"],                                          # (2) the specialist's instructions
            resume=session_id, fork_session=session_id is not None,                # (3) the conversation travels with the handoff
        )
        prompt = user_message if hop == 0 else (
            f"[System: this conversation was handed off to the {current} specialist. "
            f"Your instructions: {spec['prompt']} Continue from the conversation so far.]")   # (4) re-inject: resumed sessions keep their recorded prompt
        text = None
        async for m in query(prompt=prompt, options=opts):
            if isinstance(m, ResultMessage):
                session_id, text, total = m.session_id, m.result, total + (m.total_cost_usd or 0)
        print(f"\n[{current}] (session {session_id[:8]}...)\n{text}")
        target = next_agent(text)
        if target in (None, "done"):
            print(f"\n-- conversation closed by {current} after {hop + 1} hop(s), ${total:.4f}")
            return
        current = target                                                           # (5) the handoff
    print("\n-- escalated to a human: too many handoffs")


asyncio.run(handoff_loop(
    "Hi, I was charged twice for my subscription last month. Also, looking up my account by name on the user page "
    "crashes the app, I think the lookup code is broken."))

58 lines · Back to the folder