Class: AgentLoop
- Inherits:
-
Object
- Object
- AgentLoop
- Defined in:
- lib/agent_loop.rb
Overview
Not thread-safe. Callers must serialize concurrent access (e.g. AgentRequestJob uses session-level processing locks).
Orchestrates the LLM agent loop: accepts user input, runs the tool-use cycle via LLM::Client, and emits events through Events::Bus.
Extracted from TUI::Screens::Chat so the same agent logic can run from the TUI, a background job, or an Action Cable channel.
Constant Summary collapse
- STANDARD_TOOLS =
Tool classes available to all sessions by default.
[Tools::Bash, Tools::Read, Tools::Write, Tools::Edit, Tools::WebGet, Tools::Think, Tools::Remember, Tools::Recall].freeze
- ALWAYS_GRANTED_TOOLS =
Tools that bypass Session#granted_tools filtering. The agent’s reasoning depends on these regardless of task scope.
[Tools::Think].freeze
- STANDARD_TOOLS_BY_NAME =
Name-to-class mapping for tool restriction validation and registry building.
STANDARD_TOOLS.index_by(&:tool_name).freeze
Instance Attribute Summary collapse
-
#session ⇒ Session
readonly
The conversation session this loop operates on.
Instance Method Summary collapse
-
#deliver! ⇒ void
Makes the first LLM API call to verify delivery.
-
#finalize ⇒ Object
Clean up the underlying ShellSession PTY and resources.
-
#initialize(session:, shell_session: nil, client: nil, registry: nil) ⇒ AgentLoop
constructor
A new instance of AgentLoop.
-
#run ⇒ String?
Runs the LLM tool-use loop on persisted session messages.
Constructor Details
#initialize(session:, shell_session: nil, client: nil, registry: nil) ⇒ AgentLoop
Returns a new instance of AgentLoop.
33 34 35 36 37 38 39 |
# File 'lib/agent_loop.rb', line 33 def initialize(session:, shell_session: nil, client: nil, registry: nil) @session = session @shell_session = shell_session || ShellSession.new(session_id: session.id) restore_initial_cwd @client = client @registry = registry end |
Instance Attribute Details
#session ⇒ Session (readonly)
Returns the conversation session this loop operates on.
24 25 26 |
# File 'lib/agent_loop.rb', line 24 def session @session end |
Instance Method Details
#deliver! ⇒ void
This method returns an undefined value.
Makes the first LLM API call to verify delivery. Called inside the Bounce Back transaction — if this raises, the user event rolls back.
Caches the first response so the subsequent #run call can continue from it without duplicating the API call.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/agent_loop.rb', line 49 def deliver! @client ||= build_client @registry ||= build_tool_registry = @session. = @first_response = @client.provider.( model: @client.model, messages: , max_tokens: @client.max_tokens, tools: @registry.schemas, include_metrics: true, ** ) end |
#finalize ⇒ Object
Clean up the underlying ShellSession PTY and resources. Safe to call multiple times — subsequent calls are no-ops.
107 108 109 |
# File 'lib/agent_loop.rb', line 107 def finalize @shell_session&.finalize end |
#run ⇒ String?
Runs the LLM tool-use loop on persisted session messages.
When a cached first response exists (from #deliver!), continues from that response without a redundant API call. Otherwise makes a fresh call — used for pending message processing and the standard path.
Lets errors propagate — designed for callers like AgentRequestJob that handle retries and need errors to bubble up.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/agent_loop.rb', line 79 def run @client ||= build_client @registry ||= build_tool_registry = @session. = first_resp = @first_response @first_response = nil between_rounds = -> { @session. } result = @client.chat_with_tools( , registry: @registry, session_id: @session.id, first_response: first_resp, between_rounds: between_rounds, ** ) return unless result Events::Bus.emit(Events::AgentMessage.new( content: result[:text], session_id: @session.id, api_metrics: result[:api_metrics] )) result[:text] end |