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_handoff(&block) ⇒ self
Register a callback for agent handoff events.
-
#on_agent_thinking(&block) ⇒ self
Register a callback for agent thinking 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) ⇒ 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 |
# 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 = { tool_start: [], tool_complete: [], agent_thinking: [], agent_handoff: [] } end |
Instance Method Details
#on_agent_handoff(&block) ⇒ self
Register a callback for agent handoff events. Called when control is transferred from one agent to another.
120 121 122 123 124 125 |
# File 'lib/agents/agent_runner.rb', line 120 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.
108 109 110 111 112 113 |
# File 'lib/agents/agent_runner.rb', line 108 def on_agent_thinking(&block) return self unless block @callbacks_mutex.synchronize { @callbacks[:agent_thinking] << block } self end |
#on_tool_complete(&block) ⇒ self
Register a callback for tool completion events. Called when an agent has finished executing a tool.
96 97 98 99 100 101 |
# File 'lib/agents/agent_runner.rb', line 96 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.
84 85 86 87 88 89 |
# File 'lib/agents/agent_runner.rb', line 84 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) ⇒ 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.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/agents/agent_runner.rb', line 62 def run(input, context: {}, max_turns: Runner::DEFAULT_MAX_TURNS) # 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, callbacks: @callbacks ) end |