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 Attribute Summary collapse
-
#agents ⇒ Object
readonly
Returns the value of attribute agents.
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_chat_created(&block) ⇒ self
Register a callback for chat created events.
-
#on_llm_call_complete(&block) ⇒ self
Register a callback for LLM call completion 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, params: 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.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/agents/agent_runner.rb', line 37 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: [], llm_call_complete: [], chat_created: [] } end |
Instance Attribute Details
#agents ⇒ Object (readonly)
Returns the value of attribute agents.
32 33 34 |
# File 'lib/agents/agent_runner.rb', line 32 def agents @agents end |
Instance Method Details
#on_agent_complete(&block) ⇒ self
Register a callback for agent complete events. Called after each agent turn finishes.
166 167 168 169 170 171 |
# File 'lib/agents/agent_runner.rb', line 166 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.
130 131 132 133 134 135 |
# File 'lib/agents/agent_runner.rb', line 130 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.
118 119 120 121 122 123 |
# File 'lib/agents/agent_runner.rb', line 118 def on_agent_thinking(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:agent_thinking] << block } self end |
#on_chat_created(&block) ⇒ self
Register a callback for chat created events. Called when a RubyLLM Chat object is created or reconfigured after handoff. Useful for registering per-message hooks (e.g. on_end_message) on the chat.
191 192 193 194 195 196 |
# File 'lib/agents/agent_runner.rb', line 191 def on_chat_created(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:chat_created] << block } self end |
#on_llm_call_complete(&block) ⇒ self
Register a callback for LLM call completion events. Called after each LLM call completes with model and token usage info.
178 179 180 181 182 183 |
# File 'lib/agents/agent_runner.rb', line 178 def on_llm_call_complete(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:llm_call_complete] << block } self end |
#on_run_complete(&block) ⇒ self
Register a callback for run complete events. Called after agent execution ends (success or error).
154 155 156 157 158 159 |
# File 'lib/agents/agent_runner.rb', line 154 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.
142 143 144 145 146 147 |
# File 'lib/agents/agent_runner.rb', line 142 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.
106 107 108 109 110 111 |
# File 'lib/agents/agent_runner.rb', line 106 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.
94 95 96 97 98 99 |
# File 'lib/agents/agent_runner.rb', line 94 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, params: 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.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/agents/agent_runner.rb', line 71 def run(input, context: {}, max_turns: Runner::DEFAULT_MAX_TURNS, headers: nil, params: 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, params: params, callbacks: @callbacks ) end |