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, memory, 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_overhead(val = nil) ⇒ Object
Tokens reserved in the legacy build_context path only.
-
.context_window(val = nil) ⇒ Object
Overrides the context window size used for token budget calculations.
- .create(agent_id: SecureRandom.uuid, context: nil, 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.
-
.static_knowledge(*sources) ⇒ Object
Registers one or more static knowledge sources on the agent class.
-
.static_knowledge_chunks ⇒ Array<Hash>
Returns the fetched content from all static knowledge sources.
-
.static_knowledge_refresh! ⇒ nil
Clears the class-level knowledge cache so that the next
invokecall re-fetches content from all registered static knowledge sources. -
.static_knowledge_sources ⇒ Array<Phronomy::Agent::Context::Knowledge::Base>
Returns the registered static knowledge sources.
-
.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 the hash form of .tools.
-
.tools(*args) ⇒ 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) ⇒ self
private
Registers an anonymous handoff tool class on this agent instance.
-
#_handoff_tools ⇒ Array<Class>
private
Returns handoff tool classes registered on this instance by Runner.
- #agent_root ⇒ Object
- #clear_memory! ⇒ Object
- #clear_transcript! ⇒ Object
- #close! ⇒ Object
-
#initialize(agent_id: SecureRandom.uuid, context: nil, persistence: nil, metadata: {}, load_existing: false) ⇒ Base
constructor
A new instance of Base.
- #journal_projection ⇒ Object
-
#on_tool_approval_required(&block) ⇒ self
Registers a non-blocking Application notification listener.
- #purge! ⇒ Object
- #reset_context! ⇒ Object
-
#tool_approval_policy(&block) ⇒ self
Registers the final Agent/Application authorization policy.
- #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, persistence: nil, metadata: {}, load_existing: false) ⇒ Base
Returns a new instance of Base.
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 |
# File 'lib/phronomy/agent/base.rb', line 373 def initialize( agent_id: SecureRandom.uuid, context: nil, 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, metadata: ) end end |
Instance Attribute Details
#agent_id ⇒ Object (readonly)
Returns the value of attribute agent_id.
371 372 373 |
# File 'lib/phronomy/agent/base.rb', line 371 def agent_id @agent_id end |
#persistence ⇒ Object (readonly)
Returns the value of attribute persistence.
371 372 373 |
# File 'lib/phronomy/agent/base.rb', line 371 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.
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/phronomy/agent/base.rb', line 322 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 # Walk ancestors to support anonymous runtime subclasses and abstract bases. 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
350 351 352 353 354 355 356 357 358 |
# File 'lib/phronomy/agent/base.rb', line 350 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
360 361 362 363 364 365 366 367 368 |
# File 'lib/phronomy/agent/base.rb', line 360 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.
Only has an effect when the agent also declares provider :anthropic.
The cache_control field is provider-specific (the format differs
between Anthropic direct, Bedrock, etc.), so the agent must explicitly
declare its provider via the DSL rather than having it inferred from
the model name.
261 262 263 264 265 266 267 |
# File 'lib/phronomy/agent/base.rb', line 261 def cache_instructions(enabled = nil) if enabled.nil? @cache_instructions else @cache_instructions = enabled end end |
.context_overhead(val = nil) ⇒ Object
Tokens reserved in the legacy build_context path only. Manifest-first assembly ignores this value because ContextAssembler estimates actual mandatory content for each LLM Call.
312 313 314 315 316 317 318 |
# File 'lib/phronomy/agent/base.rb', line 312 def context_overhead(val = nil) if val.nil? @context_overhead || 0 else @context_overhead = val.to_i end end |
.context_window(val = nil) ⇒ Object
Overrides the context window size used for token budget calculations. When set, this value takes precedence over the RubyLLM model registry, which is useful for locally-hosted models (e.g. LM Studio) where the actually-loaded context length may differ from the catalogue value.
295 296 297 298 299 300 301 |
# File 'lib/phronomy/agent/base.rb', line 295 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, persistence: nil, metadata: {}) ⇒ Object
342 343 344 |
# File 'lib/phronomy/agent/base.rb', line 342 def create(agent_id: SecureRandom.uuid, context: nil, persistence: nil, metadata: {}) new(agent_id: agent_id, context: context, 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
346 347 348 |
# File 'lib/phronomy/agent/base.rb', line 346 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.
185 186 187 188 189 190 191 |
# File 'lib/phronomy/agent/base.rb', line 185 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.
277 278 279 280 281 282 283 |
# File 'lib/phronomy/agent/base.rb', line 277 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).
148 149 150 151 152 153 154 155 |
# File 'lib/phronomy/agent/base.rb', line 148 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 |
.static_knowledge(*sources) ⇒ Object
Registers one or more static knowledge sources on the agent class.
Static source content is fetched and memoized at the class level
the first time invoke is called. The cache persists for the lifetime
of the process; call static_knowledge_refresh! to force a reload.
204 205 206 207 208 209 |
# File 'lib/phronomy/agent/base.rb', line 204 def static_knowledge(*sources) @static_knowledge_sources = sources.flatten # Invalidate the cached chunks so the new sources are fetched on # the next call to static_knowledge_chunks. @static_knowledge_chunks = nil end |
.static_knowledge_chunks ⇒ Array<Hash>
Returns the fetched content from all static knowledge sources. Results are cached at the class level so that each source is fetched only once regardless of how many times the agent is invoked.
223 224 225 226 227 |
# File 'lib/phronomy/agent/base.rb', line 223 def static_knowledge_chunks @static_knowledge_chunks ||= static_knowledge_sources.flat_map { |ks| ks.fetch(query: nil) } end |
.static_knowledge_refresh! ⇒ nil
Clears the class-level knowledge cache so that the next invoke call
re-fetches content from all registered static knowledge sources.
Call this method when the underlying knowledge source has been updated at runtime (e.g. a file was rewritten, a DB record changed) and you want the agent to pick up the new content without restarting the process.
241 242 243 |
# File 'lib/phronomy/agent/base.rb', line 241 def static_knowledge_refresh! @static_knowledge_chunks = nil end |
.static_knowledge_sources ⇒ Array<Phronomy::Agent::Context::Knowledge::Base>
Returns the registered static knowledge sources.
214 215 216 |
# File 'lib/phronomy/agent/base.rb', line 214 def static_knowledge_sources @static_knowledge_sources || [] end |
.temperature(val = nil) ⇒ Float?
Sets or reads the sampling temperature sent to the LLM. When nil, the provider's default is used.
167 168 169 170 171 172 173 |
# File 'lib/phronomy/agent/base.rb', line 167 def temperature(val = nil) if val @temperature = val else @temperature end end |
.tool_aliases ⇒ Hash{Class => String}
Returns the alias map registered via the hash form of .tools. Merges parent class aliases so subclasses inherit their parent's mappings. Subclass-specific aliases take precedence over parent aliases.
127 128 129 130 131 132 133 134 |
# File 'lib/phronomy/agent/base.rb', line 127 def tool_aliases own = @tool_aliases || {} if superclass.respond_to?(:tool_aliases) superclass.tool_aliases.merge(own) else own end end |
.tools(*args) ⇒ Object
Registers tool classes for this agent.
Accepts either a splat of classes (backward-compatible) or a Hash mapping each class to an explicit alias name (String) or nil (use tool's own name). The alias form is useful when two tools share the same auto-generated name (e.g. two SearchTool classes from different modules).
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/phronomy/agent/base.rb', line 104 def tools(*args) if args.empty? if instance_variable_defined?(:@tools) return @tools end return superclass.respond_to?(:tools) ? superclass.tools : [] end if args.length == 1 && args.first.is_a?(Hash) hash = args.first @tools = hash.keys @tool_aliases = hash.transform_values { |v| v&.to_s }.reject { |_, v| v.nil? } else @tools = args @tool_aliases = {} end end |
Instance Method Details
#__replace_root(root) ⇒ Object
Internal hook used after a successful Persistence transaction.
462 463 464 |
# File 'lib/phronomy/agent/base.rb', line 462 def __replace_root(root) @root = root end |
#_add_handoff_tool(tool_class) ⇒ self
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.
Registers an anonymous handoff tool class on this agent instance. Called by Runner during construction when routes are configured.
556 557 558 559 560 |
# File 'lib/phronomy/agent/base.rb', line 556 def _add_handoff_tool(tool_class) @_handoff_tools ||= [] @_handoff_tools << tool_class self end |
#_handoff_tools ⇒ Array<Class>
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.
Returns handoff tool classes registered on this instance by Runner.
565 566 567 |
# File 'lib/phronomy/agent/base.rb', line 565 def _handoff_tools @_handoff_tools || [] end |
#agent_root ⇒ Object
398 399 400 |
# File 'lib/phronomy/agent/base.rb', line 398 def agent_root @root end |
#clear_memory! ⇒ Object
420 421 422 423 424 425 426 427 428 |
# File 'lib/phronomy/agent/base.rb', line 420 def clear_memory! mutate_context!(:memory_cleared) do |root| root.with( agent_revision: root.agent_revision + 1, context_revision: root.context_revision + 1, memory_generation: root.memory_generation + 1 ) end end |
#clear_transcript! ⇒ Object
410 411 412 413 414 415 416 417 418 |
# File 'lib/phronomy/agent/base.rb', line 410 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
441 442 443 444 445 446 447 448 |
# File 'lib/phronomy/agent/base.rb', line 441 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
402 403 404 |
# File 'lib/phronomy/agent/base.rb', line 402 def journal_projection Agent::JournalProjection.new(persistence: persistence, agent_root: @root) end |
#on_tool_approval_required(&block) ⇒ self
Registers a non-blocking Application notification listener.
584 585 586 587 588 589 |
# File 'lib/phronomy/agent/base.rb', line 584 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
450 451 452 453 454 455 456 457 458 459 |
# File 'lib/phronomy/agent/base.rb', line 450 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
430 431 432 433 434 435 436 437 438 439 |
# File 'lib/phronomy/agent/base.rb', line 430 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, memory_generation: root.memory_generation + 1 ) end end |
#tool_approval_policy(&block) ⇒ self
Registers the final Agent/Application authorization policy. The block runs on the Runtime authorization pool and must return :allow, :require_approval, or :reject.
574 575 576 577 578 579 |
# File 'lib/phronomy/agent/base.rb', line 574 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
406 407 408 |
# File 'lib/phronomy/agent/base.rb', line 406 def transcript journal_projection.transcript_records end |