Class: RubyPi::Agent::Loop
- Inherits:
-
Object
- Object
- RubyPi::Agent::Loop
- Defined in:
- lib/ruby_pi/agent/loop.rb
Overview
Executes the think-act-observe cycle against a given State, emitting events through the provided EventEmitter-compatible emitter. Returns an Agent::Result when the cycle terminates.
The cycle:
1. THINK — Call the LLM with current messages and tools. Apply
transform_context if present. Emit :turn_start and stream
:text_delta events.
2. ACT — If the LLM returned tool calls, execute them via
Tools::Executor. Fire before_tool_call / after_tool_call hooks
and emit :tool_execution_start / :tool_execution_end events.
3. OBSERVE — Append tool results to messages and loop back to THINK.
4. DONE — Return when finish_reason == "stop" (no more tool calls)
or max_iterations is reached.
Instance Method Summary collapse
-
#initialize(state:, emitter:, compaction: nil) ⇒ Loop
constructor
Creates a new Loop bound to the given state and event emitter.
-
#run ⇒ RubyPi::Agent::Result
Runs the think-act-observe cycle until completion or max iterations.
Constructor Details
#initialize(state:, emitter:, compaction: nil) ⇒ Loop
Creates a new Loop bound to the given state and event emitter.
40 41 42 43 44 45 46 |
# File 'lib/ruby_pi/agent/loop.rb', line 40 def initialize(state:, emitter:, compaction: nil) @state = state @emitter = emitter @compaction = compaction @tool_calls_made = [] @total_usage = { input_tokens: 0, output_tokens: 0 } end |
Instance Method Details
#run ⇒ RubyPi::Agent::Result
Runs the think-act-observe cycle until completion or max iterations. Returns an Agent::Result capturing the final content, messages, tool calls, usage, and turn count.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/ruby_pi/agent/loop.rb', line 53 def run loop do # Check iteration limit before starting a new turn if @state.max_iterations_reached? return build_result(content: last_assistant_content) end # Apply context compaction if configured and needed compact_if_needed! # THINK: Call the LLM response = think # Track usage from this turn accumulate_usage(response.usage) # Increment iteration counter @state.increment_iteration! if response.tool_calls? # ACT: Execute tool calls act(response) # OBSERVE: Tool results have been added to messages; loop continues @emitter.emit(:turn_end, turn: @state.iteration, has_tool_calls: true) else # No tool calls — the LLM is done @emitter.emit(:turn_end, turn: @state.iteration, has_tool_calls: false) return build_result(content: response.content) end end rescue StandardError => e @emitter.emit(:error, error: e, source: :agent_loop) Result.new( content: nil, messages: @state., tool_calls_made: @tool_calls_made, usage: @total_usage, turns: @state.iteration, error: e ) end |