Skip to content

Orchestration: Handoff

Key Points

  • Agent decides to hand off to another specialist agent.
  • Triage agent routes by user intent; specialists handle.
  • Best for support / multi-domain assistants: greeter → tech-support / billing / sales.
  • Each agent has a "transfer to X" tool. Conversation continues with new agent.
  • Originated in OpenAI Swarm; productized in Agent Framework.

Concept

[User: "I want to upgrade my plan"]
[Triage Agent] — "Sounds like billing. Handing off."
   ▼ transfer_to_billing
[Billing Agent] — "Let's pick a plan..."

Code

var billing = new ChatClientAgent(chat)
{
    Name = "Billing",
    Instructions = "Help with billing, plans, payments."
};

var techSupport = new ChatClientAgent(chat)
{
    Name = "TechSupport",
    Instructions = "Help with technical issues."
};

var triage = new ChatClientAgent(chat)
{
    Name = "Triage",
    Instructions = """
        Greet the user, understand their intent, and hand off to:
        - Billing for billing questions
        - TechSupport for technical issues
        Use transfer_to_X tools.
        """,
    Tools = [
        AIFunctionFactory.Create(() => HandoffTo("Billing")),
        AIFunctionFactory.Create(() => HandoffTo("TechSupport"))
    ]
};

var orchestration = new HandoffOrchestration(
    initial: triage,
    agents: new() { ["Billing"] = billing, ["TechSupport"] = techSupport });

var response = await orchestration.InvokeAsync("I want to cancel my subscription");

Implementation pattern

public class HandoffOrchestration(Agent initial, Dictionary<string, Agent> agents)
{
    public async Task<AgentResponse> InvokeAsync(string input, CancellationToken ct)
    {
        var current = initial;
        var messages = new List<ChatMessage> { new(ChatRole.User, input) };

        while (true)
        {
            var resp = await current.InvokeAsync(messages, ct);
            messages.AddRange(resp.Messages);

            if (resp.HandoffTarget is { } targetName && agents.TryGetValue(targetName, out var next))
            {
                current = next;
                continue;
            }
            return resp;
        }
    }
}

(Real Agent Framework provides this pattern out of the box; details vary.)

Bidirectional handoff

A specialist can hand BACK to triage if user changes topic mid-conversation:

billing.Tools.Add(AIFunctionFactory.Create(() => HandoffTo("Triage")));

Memory across handoff

Conversation history flows forward. Specialist sees the user's original intent + triage's understanding.

Use cases

  • Customer support: greeter → specialist.
  • Multi-domain assistants: research → coding → writing.
  • Sales funnel: qualifying → demo → closing.
  • Game NPCs: different characters react.

When NOT handoff

  • Linear pipeline → sequential.
  • Independent perspectives → concurrent.
  • Single domain → single agent.

Cost

Lower than group chat (one agent at a time). Comparable to single-agent + tools.

Senior considerations

  • Clear handoff conditions: in instructions, list exact criteria for handing off.
  • Specialist agents have constrained tools: don't let billing agent run system commands.
  • Test transitions: each handoff path is a test case.
  • Trace handoffs: tag spans with current agent name.

Anti-patterns

  • ❌ Triage hands off everything to one agent.
  • ❌ Specialist refuses to hand back when topic shifts.
  • ❌ Too many specialists confuse triage.

Cross-references