Class: Crimson::Agent
- Inherits:
-
Object
- Object
- Crimson::Agent
- Defined in:
- lib/crimson/agent.rb,
lib/crimson/agent/events.rb,
lib/crimson/agent/steering.rb,
lib/crimson/agent/event_emitter.rb,
lib/crimson/agent/tool_executor.rb
Overview
Core agent loop managing conversation history, tool execution, session persistence, and event emission.
Defined Under Namespace
Modules: Events Classes: EventEmitter, SteeringManager, ToolExecutor
Constant Summary collapse
- MAX_ITERATIONS =
Maximum iterations per user prompt before forcing a break.
50- HISTORY_FILE =
File name for saving/loading conversation history.
".crimson_history"- NEEDS_TOOL_PATTERNS =
Keywords that signal tool usage may be needed.
%w[ read write edit create fix bug test run exec command search find file files directory folder install update delete remove patch config setup deploy build compile lint format check verify gem npm pip cargo bundle make git docker ls cat touch mkdir rm mv cp grep rg sed awk head tail wc diff code project src spec explain why how where when who which refactor implement list show look open ].freeze
- TRIVIAL_PATTERNS =
Patterns that indicate a trivial greeting that doesn’t need tools.
%w[hi hello hey thanks thank ok yes no bye goodbye sure].freeze
Instance Attribute Summary collapse
- #compactor ⇒ String, ... readonly
- #config ⇒ Config
- #cost_tracker ⇒ String, ... readonly
- #define_system_prompt ⇒ Object writeonly private
- #events ⇒ ToolRegistry, ... readonly
- #session_cwd ⇒ String, ... readonly
- #session_id ⇒ String, ... readonly
- #steering ⇒ ToolRegistry, ... readonly
- #token_usage ⇒ ToolRegistry, ... readonly
- #tool_registry ⇒ ToolRegistry, ... readonly
Instance Method Summary collapse
-
#abort! ⇒ void
Abort the current agent execution.
-
#after_tool_call {|tool_call, result, is_error, history| ... } ⇒ void
Register a hook that runs after each tool call.
-
#before_tool_call {|tool_call, args, history| ... } ⇒ void
Register a hook that runs before each tool call.
-
#compact! ⇒ String
Force compaction of the conversation history.
-
#continue ⇒ void
Continue the agent loop after a manual break.
-
#enable_compaction!(client:, max_context_tokens: 100_000, model: nil, provider: nil) ⇒ void
Enable context compaction with the given client for summarization.
-
#follow_up(message) ⇒ void
Inject a follow-up message into the current turn.
-
#history ⇒ Array<Message::Base>
A copy of the conversation history.
- #history=(new_history) ⇒ Object
-
#initialize(client:, tool_registry:, system_prompt:, skill_router: nil) ⇒ Agent
constructor
A new instance of Agent.
-
#load_history ⇒ String
Load conversation history from a JSON file.
-
#on(event_type) { ... } ⇒ void
Subscribe to an agent event.
-
#prompt(user_input) ⇒ void
Process user input through the agent loop.
-
#reset ⇒ void
Reset conversation history and token usage.
-
#resume_session(session_id, cwd:, session_manager: SessionManager.new) ⇒ void
Resume an existing session by loading its history.
-
#save_history ⇒ String
Save conversation history to a JSON file.
-
#start_session(cwd:, session_manager: SessionManager.new) ⇒ void
Start a new session for the given working directory.
-
#steer(message) ⇒ void
Inject a steering message into the current turn.
-
#switch_model(model_id) ⇒ void
Switch to a different model, recreating the client adapter.
Constructor Details
#initialize(client:, tool_registry:, system_prompt:, skill_router: nil) ⇒ Agent
Returns a new instance of Agent.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/crimson/agent.rb', line 72 def initialize(client:, tool_registry:, system_prompt:, skill_router: nil) @client = client @tool_registry = tool_registry @system_prompt = system_prompt @system_prompt_builder = nil @skill_router = skill_router || SkillRouter.new @active_skills = ["coding"] @config = Crimson.config @history = [] @events = Agent::EventEmitter.new @steering = Agent::SteeringManager.new @token_usage = { prompt: 0, completion: 0, total: 0 } @before_tool_call = nil @after_tool_call = nil @abort_controller = false @abort_signal = AbortSignal.new @session_manager = nil @session_id = nil @session_cwd = nil @last_entry_id = nil @session_buffer = [] @compactor = nil @cost_tracker = CostTracker.new @cached_tool_defs = nil @cached_system_msg = nil end |
Instance Attribute Details
#compactor ⇒ String, ... (readonly)
62 63 64 |
# File 'lib/crimson/agent.rb', line 62 def compactor @compactor end |
#cost_tracker ⇒ String, ... (readonly)
62 63 64 |
# File 'lib/crimson/agent.rb', line 62 def cost_tracker @cost_tracker end |
#define_system_prompt=(value) ⇒ Object (writeonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
66 67 68 |
# File 'lib/crimson/agent.rb', line 66 def define_system_prompt=(value) @define_system_prompt = value end |
#events ⇒ ToolRegistry, ... (readonly)
57 58 59 |
# File 'lib/crimson/agent.rb', line 57 def events @events end |
#session_cwd ⇒ String, ... (readonly)
62 63 64 |
# File 'lib/crimson/agent.rb', line 62 def session_cwd @session_cwd end |
#session_id ⇒ String, ... (readonly)
62 63 64 |
# File 'lib/crimson/agent.rb', line 62 def session_id @session_id end |
#steering ⇒ ToolRegistry, ... (readonly)
57 58 59 |
# File 'lib/crimson/agent.rb', line 57 def steering @steering end |
#token_usage ⇒ ToolRegistry, ... (readonly)
57 58 59 |
# File 'lib/crimson/agent.rb', line 57 def token_usage @token_usage end |
#tool_registry ⇒ ToolRegistry, ... (readonly)
57 58 59 |
# File 'lib/crimson/agent.rb', line 57 def tool_registry @tool_registry end |
Instance Method Details
#abort! ⇒ void
This method returns an undefined value.
Abort the current agent execution.
209 210 211 212 |
# File 'lib/crimson/agent.rb', line 209 def abort! @abort_signal.abort! @abort_controller = true end |
#after_tool_call {|tool_call, result, is_error, history| ... } ⇒ void
This method returns an undefined value.
Register a hook that runs after each tool call.
122 123 124 |
# File 'lib/crimson/agent.rb', line 122 def after_tool_call(&block) @after_tool_call = block end |
#before_tool_call {|tool_call, args, history| ... } ⇒ void
This method returns an undefined value.
Register a hook that runs before each tool call.
112 113 114 |
# File 'lib/crimson/agent.rb', line 112 def before_tool_call(&block) @before_tool_call = block end |
#compact! ⇒ String
Force compaction of the conversation history.
168 169 170 171 172 173 174 |
# File 'lib/crimson/agent.rb', line 168 def compact! return "Compaction not enabled" unless @compactor return "History too short to compact" if @history.length <= 5 @history = @compactor.compact(@history, system_prompt: resolved_system_prompt) "Compacted history to #{@history.length} messages" end |
#continue ⇒ void
This method returns an undefined value.
Continue the agent loop after a manual break.
189 190 191 |
# File 'lib/crimson/agent.rb', line 189 def continue run_loop end |
#enable_compaction!(client:, max_context_tokens: 100_000, model: nil, provider: nil) ⇒ void
This method returns an undefined value.
Enable context compaction with the given client for summarization.
157 158 159 160 161 162 163 164 |
# File 'lib/crimson/agent.rb', line 157 def enable_compaction!(client:, max_context_tokens: 100_000, model: nil, provider: nil) @compactor = Compactor.new( client: client, max_context_tokens: max_context_tokens, model: model || Crimson.config&.model, provider: provider || Crimson.config&.provider ) end |
#follow_up(message) ⇒ void
This method returns an undefined value.
Inject a follow-up message into the current turn.
203 204 205 |
# File 'lib/crimson/agent.rb', line 203 def follow_up() @steering.follow_up(Message::User.new()) end |
#history ⇒ Array<Message::Base>
Returns a copy of the conversation history.
241 242 243 |
# File 'lib/crimson/agent.rb', line 241 def history @history.dup end |
#history=(new_history) ⇒ Object
246 247 248 |
# File 'lib/crimson/agent.rb', line 246 def history=(new_history) @history = new_history.dup end |
#load_history ⇒ String
Load conversation history from a JSON file.
263 264 265 266 267 268 269 270 271 272 |
# File 'lib/crimson/agent.rb', line 263 def load_history return "No saved conversation found." unless File.exist?(HISTORY_FILE) data = JSON.parse(File.read(HISTORY_FILE), symbolize_names: true) @history = data[:history].map { |msg| (msg) }.compact @token_usage = data[:token_usage] || { prompt: 0, completion: 0, total: 0 } "Loaded #{@history.length} messages" rescue => e "Error loading history: #{e.}" end |
#on(event_type) { ... } ⇒ void
This method returns an undefined value.
Subscribe to an agent event.
103 104 105 |
# File 'lib/crimson/agent.rb', line 103 def on(event_type, &handler) @events.on(event_type, &handler) end |
#prompt(user_input) ⇒ void
This method returns an undefined value.
Process user input through the agent loop.
179 180 181 182 183 184 185 |
# File 'lib/crimson/agent.rb', line 179 def prompt(user_input) @history << Message::User.new(user_input) append_to_session(@history.last) @events.emit(Agent::Events::MESSAGE_START, message: @history.last) @events.emit(Agent::Events::MESSAGE_END, message: @history.last) run_loop end |
#reset ⇒ void
This method returns an undefined value.
Reset conversation history and token usage.
233 234 235 236 237 238 |
# File 'lib/crimson/agent.rb', line 233 def reset @history.clear @token_usage = { prompt: 0, completion: 0, total: 0 } @steering.clear_all @cost_tracker.reset end |
#resume_session(session_id, cwd:, session_manager: SessionManager.new) ⇒ void
This method returns an undefined value.
Resume an existing session by loading its history.
142 143 144 145 146 147 148 149 |
# File 'lib/crimson/agent.rb', line 142 def resume_session(session_id, cwd:, session_manager: SessionManager.new) @session_manager = session_manager entries = @session_manager.load(session_id, cwd: cwd) @session_id = session_id @session_cwd = cwd @history = entries.map(&:to_message).compact @last_entry_id = entries.last&.id end |
#save_history ⇒ String
Save conversation history to a JSON file.
252 253 254 255 256 257 258 259 |
# File 'lib/crimson/agent.rb', line 252 def save_history data = { history: @history.map { |msg| (msg) }, token_usage: @token_usage } File.write(HISTORY_FILE, JSON.pretty_generate(data)) "Conversation saved to #{HISTORY_FILE}" end |
#start_session(cwd:, session_manager: SessionManager.new) ⇒ void
This method returns an undefined value.
Start a new session for the given working directory.
130 131 132 133 134 135 |
# File 'lib/crimson/agent.rb', line 130 def start_session(cwd:, session_manager: SessionManager.new) @session_manager = session_manager @session_id = @session_manager.create(cwd: cwd) @session_cwd = cwd @last_entry_id = nil end |
#steer(message) ⇒ void
This method returns an undefined value.
Inject a steering message into the current turn.
196 197 198 |
# File 'lib/crimson/agent.rb', line 196 def steer() @steering.steer(Message::User.new()) end |
#switch_model(model_id) ⇒ void
This method returns an undefined value.
Switch to a different model, recreating the client adapter.
217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/crimson/agent.rb', line 217 def switch_model(model_id) @config = Config.new( provider: @config.provider, model: model_id, api_key: @config.api_key, base_url: @config.base_url, max_tokens: @config.max_tokens, thinking_level: @config.thinking_level ) @client = Crimson::Client.create(@config) @cached_tool_defs = nil @cached_system_msg = nil end |