Class: AgentLoop
- Inherits:
-
Object
- Object
- AgentLoop
- Defined in:
- lib/agent_loop.rb
Overview
Not thread-safe. Callers must serialize concurrent calls to #process (e.g. TUI uses a loading flag, future callers should use session-level 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].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
-
#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.
-
#process(input) ⇒ String?
Runs the agent loop for a single user input.
-
#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.
36 37 38 39 40 41 |
# File 'lib/agent_loop.rb', line 36 def initialize(session:, shell_session: nil, client: nil, registry: nil) @session = session @shell_session = shell_session || ShellSession.new(session_id: session.id) @client = client @registry = registry end |
Instance Attribute Details
#session ⇒ Session (readonly)
Returns the conversation session this loop operates on.
27 28 29 |
# File 'lib/agent_loop.rb', line 27 def session @session end |
Instance Method Details
#finalize ⇒ Object
Clean up the underlying ShellSession PTY and resources. Safe to call multiple times — subsequent calls are no-ops.
91 92 93 |
# File 'lib/agent_loop.rb', line 91 def finalize @shell_session&.finalize end |
#process(input) ⇒ String?
Runs the agent loop for a single user input.
Emits Events::UserMessage immediately, then delegates to #run. On error emits Events::AgentMessage with the error text.
50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/agent_loop.rb', line 50 def process(input) text = input.to_s.strip return if text.empty? Events::Bus.emit(Events::UserMessage.new(content: text, session_id: @session.id)) run rescue => error = "#{error.class}: #{error.}" Events::Bus.emit(Events::AgentMessage.new(content: , session_id: @session.id)) end |
#run ⇒ String
Runs the LLM tool-use loop on persisted session messages.
Unlike #process, does not emit Events::UserMessage and lets errors propagate — designed for callers like AgentRequestJob that handle retries and need errors to bubble up.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/agent_loop.rb', line 71 def run @client ||= LLM::Client.new @registry ||= build_tool_registry = @session. = {} unless @session.sub_agent? env_context = EnvironmentProbe.to_prompt(@shell_session.pwd) end prompt = @session.system_prompt(environment_context: env_context) [:system] = prompt if prompt response = @client.chat_with_tools(, registry: @registry, session_id: @session.id, **) Events::Bus.emit(Events::AgentMessage.new(content: response, session_id: @session.id)) response end |