Class: Agents::HandoffTool

Inherits:
Tool
  • Object
show all
Defined in:
lib/agents/handoff.rb

Overview

A special tool that enables agents to transfer conversations to other specialized agents. Handoffs are implemented as tools (following OpenAI’s pattern) because this allows the LLM to naturally decide when to transfer based on the conversation context.

## How Handoffs Work

  1. Agent A is configured with handoff_agents: [Agent B, Agent C]

  2. This automatically creates HandoffTool instances for B and C

  3. The LLM can call these tools like any other tool

  4. The tool signals the handoff through context

  5. The Runner detects this and switches to the new agent

## Loop Prevention The library prevents infinite handoff loops by processing only the first handoff tool call in any LLM response. This is handled automatically by the Chat class which detects handoff tools and processes them separately from regular tools.

## Why Tools Instead of Instructions Using tools for handoffs has several advantages:

  • LLMs reliably use tools when appropriate

  • Clear schema tells the LLM when each handoff is suitable

  • No parsing of free text needed

  • Works consistently across different LLM providers

Examples:

Basic handoff setup

billing_agent = Agent.new(name: "Billing", instructions: "Handle payments")
support_agent = Agent.new(name: "Support", instructions: "Technical help")

triage = Agent.new(
  name: "Triage",
  instructions: "Route users to the right team",
  handoff_agents: [billing_agent, support_agent]
)
# Creates tools: handoff_to_billing, handoff_to_support

How the LLM sees it

# User: "I can't pay my bill"
# LLM thinks: "This is a payment issue, I should transfer to billing"
# LLM calls: handoff_to_billing()
# Runner switches to billing_agent for the next turn

Multiple handoff handling

# Single LLM response with multiple handoff calls:
# Call 1: handoff_to_support() -> Processed and executed
# Call 2: handoff_to_billing() -> Ignored (only first handoff processed)
# Result: Only transfers to Support Agent

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Tool

#execute

Constructor Details

#initialize(target_agent) ⇒ HandoffTool

Returns a new instance of HandoffTool.



52
53
54
55
56
57
58
59
60
# File 'lib/agents/handoff.rb', line 52

def initialize(target_agent)
  @target_agent = target_agent

  # Set up the tool with a standardized name and description
  @tool_name = "handoff_to_#{target_agent.name.downcase.gsub(/\s+/, "_")}"
  @tool_description = "Transfer conversation to #{target_agent.name}"

  super()
end

Instance Attribute Details

#target_agentObject (readonly)

Returns the value of attribute target_agent.



50
51
52
# File 'lib/agents/handoff.rb', line 50

def target_agent
  @target_agent
end

Instance Method Details

#descriptionObject

Override the description



68
69
70
# File 'lib/agents/handoff.rb', line 68

def description
  @tool_description
end

#nameObject

Override the auto-generated name to use our specific name



63
64
65
# File 'lib/agents/handoff.rb', line 63

def name
  @tool_name
end

#perform(tool_context) ⇒ Object

Use RubyLLM’s halt mechanism to stop continuation after handoff Store handoff info in context for Runner to detect and process



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/agents/handoff.rb', line 74

def perform(tool_context)
  # Store handoff information in context for Runner to detect
  # TODO: The following is a race condition that needs to be addressed in future versions
  # If multiple handoff tools execute concurrently, they overwrite each other's pending_handoff data.
  tool_context.run_context.context[:pending_handoff] = {
    target_agent: @target_agent,
    timestamp: Time.now
  }

  # Return halt to stop LLM continuation
  halt("I'll transfer you to #{@target_agent.name} who can better assist you with this.")
end