Class: Phronomy::Agent::Base
- Inherits:
-
Object
- Object
- Phronomy::Agent::Base
- Includes:
- AsyncEventApi, Concerns::BeforeLLMInput, Concerns::ErrorTranslation, Concerns::Filterable, Runnable
- Defined in:
- lib/phronomy/agent/base.rb
Overview
Base class for all Phronomy agents.
Subclass this to create a conversational agent powered by an LLM. DSL class methods configure the model, instructions, tools, and execution hooks. Instance methods handle invocation.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#agent_id ⇒ Object
readonly
Returns the value of attribute agent_id.
-
#persistence ⇒ Object
readonly
Returns the value of attribute persistence.
Attributes included from Concerns::BeforeLLMInput
Class Method Summary collapse
-
.agent_definition(id: nil, version: nil) ⇒ Object
Defines or reads the stable Agent definition identity.
- .approve(execution_id, approval_request_id:, persistence:, approved: true, config: {}) ⇒ Object
- .approve_async(execution_id, approval_request_id:, persistence:, approved: true, config: {}) ⇒ Object
-
.cache_instructions(enabled = nil) ⇒ Object
When enabled, attaches Anthropic prompt-cache markers to the system message so that the fixed instructions are served from cache on subsequent turns, reducing input-token costs.
-
.context_window(val = nil) ⇒ Object
Overrides the context window size used for token budget calculations.
- .create(agent_id: SecureRandom.uuid, context: nil, knowledge: [], persistence: nil, metadata: {}) ⇒ Object
-
.instructions(text = nil) { ... } ⇒ String, ...
Sets or reads the system instructions for this agent.
- .load(agent_id, persistence:) ⇒ Object
-
.max_iterations(val = nil) ⇒ Integer
Sets or reads the maximum number of LLM call cycles for ReAct agents.
-
.max_output_tokens(val = nil) ⇒ Object
Tokens to reserve for the model's output.
-
.model(name = nil) ⇒ String?
Sets or reads the LLM model identifier for this agent.
-
.provider(name = nil) ⇒ Symbol?
Sets or reads the LLM provider for this agent.
-
.temperature(val = nil) ⇒ Float?
Sets or reads the sampling temperature sent to the LLM.
-
.tool_aliases ⇒ Hash{Class => String}
Returns the alias map registered via .tools.
-
.tools(definitions = nil) ⇒ Object
Registers tool classes for this agent.
Instance Method Summary collapse
-
#__replace_root(root) ⇒ Object
Internal hook used after a successful Persistence transaction.
- #_add_handoff_tool(tool_class) ⇒ Object
- #_handoff_tools ⇒ Object
-
#add_knowledge(content, metadata: {}) ⇒ Object
Appends persistent Knowledge to the Agent Journal.
- #agent_root ⇒ Object
-
#clear_knowledge! ⇒ Object
Logically clears all persistent Knowledge registered before this point.
- #clear_transcript! ⇒ Object
- #close! ⇒ Object
-
#initialize(agent_id: SecureRandom.uuid, context: nil, knowledge: [], persistence: nil, metadata: {}, load_existing: false) ⇒ Base
constructor
A new instance of Base.
- #journal_projection ⇒ Object
- #on_tool_approval_required(&block) ⇒ Object
- #purge! ⇒ Object
- #reset_context! ⇒ Object
- #tool_approval_policy(&block) ⇒ Object
- #transcript ⇒ Object
Methods included from Concerns::BeforeLLMInput
Methods included from Concerns::Filterable
#add_input_filter, #add_output_filter, #add_tool_result_filter, included
Methods included from Runnable
#batch, #invoke, #stream, #trace
Methods included from AsyncEventApi
#approve, #approve_async, #invoke, #invoke_async, #stream, #stream_async
Constructor Details
#initialize(agent_id: SecureRandom.uuid, context: nil, knowledge: [], persistence: nil, metadata: {}, load_existing: false) ⇒ Base
Returns a new instance of Base.
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/phronomy/agent/base.rb', line 278 def initialize( agent_id: SecureRandom.uuid, context: nil, knowledge: [], persistence: nil, metadata: {}, load_existing: false ) @persistence = persistence || Phronomy::Persistence::InMemory.new @agent_id = agent_id.to_s.freeze @root = if load_existing loaded = @persistence.agents.load(@agent_id) definition = self.class.agent_definition unless loaded.agent_definition_id == definition.fetch(:id) && loaded.definition_version == definition.fetch(:version) raise Phronomy::ConfigurationError, "Agent definition mismatch for #{@agent_id}: stored " \ "#{loaded.agent_definition_id}@#{loaded.definition_version}, runtime " \ "#{definition.fetch(:id)}@#{definition.fetch(:version)}" end loaded else create_agent_root!(context: context, knowledge: knowledge, metadata: ) end end |
Instance Attribute Details
#agent_id ⇒ Object (readonly)
Returns the value of attribute agent_id.
276 277 278 |
# File 'lib/phronomy/agent/base.rb', line 276 def agent_id @agent_id end |
#persistence ⇒ Object (readonly)
Returns the value of attribute persistence.
276 277 278 |
# File 'lib/phronomy/agent/base.rb', line 276 def persistence @persistence end |
Class Method Details
.agent_definition(id: nil, version: nil) ⇒ Object
Defines or reads the stable Agent definition identity. Subclass with no explicit declaration inherits the parent's definition.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/phronomy/agent/base.rb', line 222 def agent_definition(id: nil, version: nil) if id || version raise ArgumentError, "agent_definition requires id: and version:" unless id && version @agent_definition = {id: id.to_s.freeze, version: Integer(version)}.freeze end return @agent_definition if @agent_definition klass = superclass while klass.respond_to?(:agent_definition, true) && klass < Phronomy::Agent::Base defn = klass.instance_variable_get(:@agent_definition) return defn if defn klass = klass.superclass end raise Phronomy::ConfigurationError, "#{name || self} must declare agent_definition id: ..., version: ..." end |
.approve(execution_id, approval_request_id:, persistence:, approved: true, config: {}) ⇒ Object
255 256 257 258 259 260 261 262 263 |
# File 'lib/phronomy/agent/base.rb', line 255 def approve(execution_id, approval_request_id:, persistence:, approved: true, config: {}) approve_async( execution_id, approval_request_id: approval_request_id, approved: approved, config: config, persistence: persistence ).wait_result end |
.approve_async(execution_id, approval_request_id:, persistence:, approved: true, config: {}) ⇒ Object
265 266 267 268 269 270 271 272 273 |
# File 'lib/phronomy/agent/base.rb', line 265 def approve_async(execution_id, approval_request_id:, persistence:, approved: true, config: {}) execution = persistence.executions.load(execution_id) load(execution.agent_id, persistence: persistence).approve_async( execution_id, approval_request_id: approval_request_id, approved: approved, config: config ) end |
.cache_instructions(enabled = nil) ⇒ Object
When enabled, attaches Anthropic prompt-cache markers to the system message so that the fixed instructions are served from cache on subsequent turns, reducing input-token costs.
191 192 193 194 195 196 197 |
# File 'lib/phronomy/agent/base.rb', line 191 def cache_instructions(enabled = nil) if enabled.nil? @cache_instructions else @cache_instructions = enabled end end |
.context_window(val = nil) ⇒ Object
Overrides the context window size used for token budget calculations.
212 213 214 215 216 217 218 |
# File 'lib/phronomy/agent/base.rb', line 212 def context_window(val = nil) if val.nil? @context_window else @context_window = val.to_i end end |
.create(agent_id: SecureRandom.uuid, context: nil, knowledge: [], persistence: nil, metadata: {}) ⇒ Object
241 242 243 244 245 246 247 248 249 |
# File 'lib/phronomy/agent/base.rb', line 241 def create(agent_id: SecureRandom.uuid, context: nil, knowledge: [], persistence: nil, metadata: {}) new( agent_id: agent_id, context: context, knowledge: knowledge, persistence: persistence, metadata: ) end |
.instructions(text = nil) { ... } ⇒ String, ...
Sets or reads the system instructions for this agent. Accepts a String, a Context::Instruction::PromptTemplate, or a block (Proc). When used as a reader (no argument, no block), returns the stored value.
78 79 80 81 82 83 84 85 |
# File 'lib/phronomy/agent/base.rb', line 78 def instructions(text = nil, &block) if text || block_given? @instructions = text || block else return @instructions if instance_variable_defined?(:@instructions) superclass.respond_to?(:instructions) ? superclass.instructions : nil end end |
.load(agent_id, persistence:) ⇒ Object
251 252 253 |
# File 'lib/phronomy/agent/base.rb', line 251 def load(agent_id, persistence:) new(agent_id: agent_id, persistence: persistence, load_existing: true) end |
.max_iterations(val = nil) ⇒ Integer
Sets or reads the maximum number of LLM call cycles for ReAct agents. Each tool call and follow-up counts as one iteration. Defaults to 10.
179 180 181 182 183 184 185 |
# File 'lib/phronomy/agent/base.rb', line 179 def max_iterations(val = nil) if val @max_iterations = val else @max_iterations || 10 end end |
.max_output_tokens(val = nil) ⇒ Object
Tokens to reserve for the model's output. When nil, the model's max_output_tokens from the registry is used.
202 203 204 205 206 207 208 |
# File 'lib/phronomy/agent/base.rb', line 202 def max_output_tokens(val = nil) if val.nil? @max_output_tokens else @max_output_tokens = val.to_i end end |
.model(name = nil) ⇒ String?
Sets or reads the LLM model identifier for this agent. When called without an argument, returns the stored model or the global default from Phronomy.configuration.
54 55 56 57 58 59 60 |
# File 'lib/phronomy/agent/base.rb', line 54 def model(name = nil) if name @model = name else @model || Phronomy.configuration.default_model end end |
.provider(name = nil) ⇒ Symbol?
Sets or reads the LLM provider for this agent. Required when using a model not registered in RubyLLM's model registry (e.g. locally-hosted models via LM Studio or Ollama).
142 143 144 145 146 147 148 149 |
# File 'lib/phronomy/agent/base.rb', line 142 def provider(name = nil) if name @provider = name else return @provider if instance_variable_defined?(:@provider) superclass.respond_to?(:provider) ? superclass.provider : nil end end |
.temperature(val = nil) ⇒ Float?
Sets or reads the sampling temperature sent to the LLM. When nil, the provider's default is used.
161 162 163 164 165 166 167 |
# File 'lib/phronomy/agent/base.rb', line 161 def temperature(val = nil) if val @temperature = val else @temperature end end |
.tool_aliases ⇒ Hash{Class => String}
Returns the alias map registered via .tools. Merges parent class aliases so subclasses inherit their parent's mappings. Subclass-specific aliases take precedence over parent aliases.
121 122 123 124 125 126 127 128 |
# File 'lib/phronomy/agent/base.rb', line 121 def tool_aliases own = @tool_aliases || {} if superclass.respond_to?(:tool_aliases) superclass.tool_aliases.merge(own) else own end end |
.tools(definitions = nil) ⇒ Object
Registers tool classes for this agent.
The setter accepts one Hash mapping each Tool class to an explicit alias name (String) or nil (use the Tool's own name). Calling without an argument returns the registered Tool classes.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/phronomy/agent/base.rb', line 100 def tools(definitions = nil) if definitions.nil? return @tools if instance_variable_defined?(:@tools) return superclass.respond_to?(:tools) ? superclass.tools : [] end unless definitions.is_a?(Hash) raise ArgumentError, "tools expects a Hash of ToolClass => alias_or_nil" end @tools = definitions.keys @tool_aliases = definitions.transform_values { |value| value&.to_s } .reject { |_, value| value.nil? } end |
Instance Method Details
#__replace_root(root) ⇒ Object
Internal hook used after a successful Persistence transaction.
401 402 403 |
# File 'lib/phronomy/agent/base.rb', line 401 def __replace_root(root) @root = root end |
#_add_handoff_tool(tool_class) ⇒ Object
516 517 518 519 520 |
# File 'lib/phronomy/agent/base.rb', line 516 def _add_handoff_tool(tool_class) @_handoff_tools ||= [] @_handoff_tools << tool_class self end |
#_handoff_tools ⇒ Object
522 523 524 |
# File 'lib/phronomy/agent/base.rb', line 522 def _handoff_tools @_handoff_tools || [] end |
#add_knowledge(content, metadata: {}) ⇒ Object
Appends persistent Knowledge to the Agent Journal. Knowledge is an optional Context candidate; it is not part of #transcript.
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/phronomy/agent/base.rb', line 339 def add_knowledge(content, metadata: {}) next_root = nil persistence.transaction do |tx| tx.executions.assert_idle!(agent_id) current = tx.agents.load(agent_id) record = build_knowledge_record( tx: tx, root: current, content: content, metadata: ) appended = tx.journals.append( agent_id, expected_position: current.journal_position, records: [record] ) next_root = current.with( agent_revision: current.agent_revision + 1, context_revision: current.context_revision + 1, journal_position: current.journal_position + appended.length ) tx.agents.save( agent_id, expected_revision: current.agent_revision, root: next_root ) end @root = next_root self end |
#agent_root ⇒ Object
304 305 306 |
# File 'lib/phronomy/agent/base.rb', line 304 def agent_root @root end |
#clear_knowledge! ⇒ Object
Logically clears all persistent Knowledge registered before this point. Raw Journal records remain append-only and are not deleted.
328 329 330 331 332 333 334 335 |
# File 'lib/phronomy/agent/base.rb', line 328 def clear_knowledge! mutate_context!(:knowledge_cleared) do |root| root.with( agent_revision: root.agent_revision + 1, context_revision: root.context_revision + 1 ) end end |
#clear_transcript! ⇒ Object
316 317 318 319 320 321 322 323 324 |
# File 'lib/phronomy/agent/base.rb', line 316 def clear_transcript! mutate_context!(:transcript_cleared) do |root| root.with( agent_revision: root.agent_revision + 1, context_revision: root.context_revision + 1, transcript_generation: root.transcript_generation + 1 ) end end |
#close! ⇒ Object
380 381 382 383 384 385 386 387 |
# File 'lib/phronomy/agent/base.rb', line 380 def close! mutate_context!(:agent_closed, context_affecting: false) do |root| root.with( agent_revision: root.agent_revision + 1, lifecycle_status: :closed ) end end |
#journal_projection ⇒ Object
308 309 310 |
# File 'lib/phronomy/agent/base.rb', line 308 def journal_projection Agent::JournalProjection.new(persistence: persistence, agent_root: @root) end |
#on_tool_approval_required(&block) ⇒ Object
533 534 535 536 537 538 |
# File 'lib/phronomy/agent/base.rb', line 533 def on_tool_approval_required(&block) raise ArgumentError, "on_tool_approval_required requires a block" unless block _approval_configuration_mutex.synchronize { @tool_approval_listener = block } self end |
#purge! ⇒ Object
389 390 391 392 393 394 395 396 397 398 |
# File 'lib/phronomy/agent/base.rb', line 389 def purge! persistence.transaction do |tx| tx.executions.assert_idle!(agent_id) tx.journals.delete(agent_id) tx.executions.delete_for_agent(agent_id) tx.agents.delete(agent_id) end @root = nil true end |
#reset_context! ⇒ Object
370 371 372 373 374 375 376 377 378 |
# File 'lib/phronomy/agent/base.rb', line 370 def reset_context! mutate_context!(:context_reset) do |root| root.with( agent_revision: root.agent_revision + 1, context_revision: root.context_revision + 1, transcript_generation: root.transcript_generation + 1 ) end end |
#tool_approval_policy(&block) ⇒ Object
526 527 528 529 530 531 |
# File 'lib/phronomy/agent/base.rb', line 526 def tool_approval_policy(&block) raise ArgumentError, "tool_approval_policy requires a block" unless block _approval_configuration_mutex.synchronize { @tool_approval_policy = block } self end |
#transcript ⇒ Object
312 313 314 |
# File 'lib/phronomy/agent/base.rb', line 312 def transcript journal_projection.transcript_records end |