Class: Agents::AgentRunner
- Inherits:
-
Object
- Object
- Agents::AgentRunner
- Defined in:
- lib/agents/agent_runner.rb
Overview
Thread-safe agent execution manager that provides a clean API for multi-agent conversations. This class is designed to be created once and reused across multiple threads safely.
The key insight here is separating agent registry/configuration (this class) from execution state (Runner instances). This allows the same AgentRunner to be used concurrently without thread safety issues.
## Usage Pattern
# Create once (typically at application startup)
runner = Agents::Runner.with_agents(triage_agent, billing_agent, support_agent)
.on_tool_start { |tool_name, args| broadcast_event('tool_start', tool_name, args) }
.on_tool_complete { |tool_name, result| broadcast_event('tool_complete', tool_name, result) }
# Use safely from multiple threads
result = runner.run("I need billing help") # New conversation
result = runner.run("More help", context: context) # Continue conversation
## Thread Safety Design
-
All instance variables are frozen after initialization (immutable state)
-
Agent registry is built once and never modified
-
Each run() call creates independent execution context
-
No shared mutable state between concurrent executions
## Callback Thread Safety Callback registration is thread-safe using internal synchronization. Multiple threads can safely register callbacks concurrently without data races.
Instance Method Summary collapse
-
#initialize(agents) ⇒ AgentRunner
constructor
Initialize with a list of agents.
-
#on_agent_complete(&block) ⇒ self
Register a callback for agent complete events.
-
#on_agent_handoff(&block) ⇒ self
Register a callback for agent handoff events.
-
#on_agent_thinking(&block) ⇒ self
Register a callback for agent thinking events.
-
#on_run_complete(&block) ⇒ self
Register a callback for run complete events.
-
#on_run_start(&block) ⇒ self
Register a callback for run start events.
-
#on_tool_complete(&block) ⇒ self
Register a callback for tool completion events.
-
#on_tool_start(&block) ⇒ self
Register a callback for tool start events.
-
#run(input, context: {}, max_turns: Runner::DEFAULT_MAX_TURNS, headers: nil) ⇒ RunResult
Execute a conversation turn with automatic agent selection.
Constructor Details
#initialize(agents) ⇒ AgentRunner
Initialize with a list of agents. The first agent becomes the default entry point.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/agents/agent_runner.rb', line 35 def initialize(agents) raise ArgumentError, "At least one agent must be provided" if agents.empty? @agents = agents.dup.freeze @callbacks_mutex = Mutex.new @default_agent = agents.first # Build simple registry from provided agents - developer controls what's available @registry = build_registry(agents).freeze # Initialize callback storage - use thread-safe arrays @callbacks = { run_start: [], run_complete: [], agent_complete: [], tool_start: [], tool_complete: [], agent_thinking: [], agent_handoff: [] } end |
Instance Method Details
#on_agent_complete(&block) ⇒ self
Register a callback for agent complete events. Called after each agent turn finishes.
160 161 162 163 164 165 |
# File 'lib/agents/agent_runner.rb', line 160 def on_agent_complete(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:agent_complete] << block } self end |
#on_agent_handoff(&block) ⇒ self
Register a callback for agent handoff events. Called when control is transferred from one agent to another.
124 125 126 127 128 129 |
# File 'lib/agents/agent_runner.rb', line 124 def on_agent_handoff(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:agent_handoff] << block } self end |
#on_agent_thinking(&block) ⇒ self
Register a callback for agent thinking events. Called when an agent is about to make an LLM call.
112 113 114 115 116 117 |
# File 'lib/agents/agent_runner.rb', line 112 def on_agent_thinking(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:agent_thinking] << block } self end |
#on_run_complete(&block) ⇒ self
Register a callback for run complete events. Called after agent execution ends (success or error).
148 149 150 151 152 153 |
# File 'lib/agents/agent_runner.rb', line 148 def on_run_complete(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:run_complete] << block } self end |
#on_run_start(&block) ⇒ self
Register a callback for run start events. Called before agent execution begins.
136 137 138 139 140 141 |
# File 'lib/agents/agent_runner.rb', line 136 def on_run_start(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:run_start] << block } self end |
#on_tool_complete(&block) ⇒ self
Register a callback for tool completion events. Called when an agent has finished executing a tool.
100 101 102 103 104 105 |
# File 'lib/agents/agent_runner.rb', line 100 def on_tool_complete(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:tool_complete] << block } self end |
#on_tool_start(&block) ⇒ self
Register a callback for tool start events. Called when an agent is about to execute a tool.
88 89 90 91 92 93 |
# File 'lib/agents/agent_runner.rb', line 88 def on_tool_start(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:tool_start] << block } self end |
#run(input, context: {}, max_turns: Runner::DEFAULT_MAX_TURNS, headers: nil) ⇒ RunResult
Execute a conversation turn with automatic agent selection. For new conversations, uses the default agent (first in the list). For continuing conversations, determines the appropriate agent from conversation history.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/agents/agent_runner.rb', line 66 def run(input, context: {}, max_turns: Runner::DEFAULT_MAX_TURNS, headers: nil) # Determine which agent should handle this conversation # Uses conversation history to maintain continuity across handoffs current_agent = determine_conversation_agent(context) # Execute using stateless Runner - each execution is independent and thread-safe # Pass callbacks to enable real-time event notifications Runner.new.run( current_agent, input, context: context, registry: @registry, max_turns: max_turns, headers: headers, callbacks: @callbacks ) end |