The OpenAI Agents SDK at its sweet spot: one triage front door, three specialists, ~120 lines of Python.
Agents Module 4 OpenAI Agents SDK Handoffs
Hits every condition on L07 Slide 11's “Pick it when” list.
A customer-support triage agent that classifies an incoming ticket and hands off to one of three specialists — Billing, Technical, or Account — each of whom resolves the request using its own small toolset.
Real example ticket: “My invoice for March is double what I expected and the auto-renewal won't turn off.”
The triage agent reads the ticket once and routes — the work happens inside the specialist that picks it up.
| Agent | Instructions in one line | Tools |
|---|---|---|
triage | Read the ticket, hand off to the best specialist. | (none — only handoffs) |
billing | Resolve billing tickets. | lookup_invoice, issue_refund |
technical | Resolve technical / outage tickets. | check_service_status, open_incident |
account | Resolve account / auth tickets. | lookup_account, reset_password |
@function_tool-decorated normal Python functions — the same shape Module 2 used.You are the front-line triage agent for customer support. Read the ticket
and decide which specialist should handle it:
- billing / refunds / invoices -> hand off to Billing.
- outage / errors / "not working" -> hand off to Technical.
- login / password / account access -> hand off to Account.
Do NOT try to resolve the ticket yourself. Hand off on the first turn.
Triage's only job is routing. It has zero tools — the only thing it can do is call one of the three synthetic transfer_to_* handoff tools.
triage = Agent(
name="Triage",
instructions=TRIAGE_INSTRUCTIONS,
model="gpt-4o-mini",
handoffs=[billing, technical, account],
input_guardrails=[block_pii],
)
Three handoff targets become three synthetic tools on triage: transfer_to_billing, transfer_to_technical, transfer_to_account. The LLM picks one per ticket.
InputGuardrailTripwireTriggered exception with output_info={"reason": "pii_detected"}.ticket: "My invoice for March is double what I expected..."
[step 1] agent=Triage tools=[transfer_to_billing]
[handoff] Triage -> Billing
[step 2] agent=Billing tools=[lookup_invoice]
[step 3] agent=Billing tools=[issue_refund]
[step 4] agent=Billing tools=[] (final_output)
Turn 1 is purely a handoff; the work happens in turns 2–4 inside the Billing agent. result.last_agent.name == "Billing".
class Resolution(BaseModel):
ticket_summary: str
specialist: str # 'billing' | 'technical' | 'account'
actions_taken: list[str]
follow_up_needed: bool
Applied to each specialist agent via output_type=Resolution. Triage doesn't need one — it just hands off; whichever specialist answers fills in the type.
last_agent audit trailresult.last_agent.name tells you who resolved ("Billing" / "Technical" / "Account").result.new_items includes HandoffOutputItem records so you can reconstruct the routing decision after the fact.gpt-4o-mini. ~3–5 second latency.Plan F (Module 6) wires this exact agent.py into agents/_eval/run_all.py as the OpenAI SDK row of the cross-framework scorecard.
The same shape of agent (triage + 3 specialists) gets rebuilt-in-spirit across the other frameworks: Claude SDK does it with a single-agent + tool-discriminator, LangGraph as a routed DAG, CrewAI as a crew of role-based agents.
Apples-to-apples comparison in Module 6.
handoffs=[other] entry compiles to a synthetic transfer_to_<name> tool the LLM picks like any other.