Class: Legate::Agent
- Inherits:
-
Object
- Object
- Legate::Agent
- Defined in:
- lib/legate/agent.rb
Overview
Agent class represents an AI agent that can perform tasks using tools and a planner. It operates within the context of a session managed by a SessionService.
Direct Known Subclasses
Legate::Agents::LoopAgent, Legate::Agents::ParallelAgent, Legate::Agents::SequentialAgent
Constant Summary collapse
- DEFAULT_MODEL =
Default Gemini model (supports structured output)
'gemini-3.5-flash'
Instance Attribute Summary collapse
-
#after_agent_callback ⇒ Object
readonly
— Callback Instance Variables —.
-
#after_model_callback ⇒ Object
readonly
— Callback Instance Variables —.
-
#after_tool_callback ⇒ Object
readonly
— Callback Instance Variables —.
-
#auth_credential_assignments ⇒ Object
readonly
— Authentication Instance Variables —.
-
#auth_credential_names ⇒ Object
readonly
— Authentication Instance Variables —.
-
#auth_scheme_assignments ⇒ Object
readonly
— Authentication Instance Variables —.
-
#auth_url_mappings ⇒ Object
readonly
— Authentication Instance Variables —.
-
#before_agent_callback ⇒ Object
readonly
— Callback Instance Variables —.
-
#before_model_callback ⇒ Object
readonly
— Callback Instance Variables —.
-
#before_tool_callback ⇒ Object
readonly
— Callback Instance Variables —.
-
#definition ⇒ Object
readonly
Added session_service to attr_reader.
-
#description ⇒ Object
readonly
Added session_service to attr_reader.
-
#fallback_mode ⇒ Object
readonly
Added session_service to attr_reader.
-
#instruction ⇒ Object
readonly
Added session_service to attr_reader.
-
#logger ⇒ Object
readonly
Added session_service to attr_reader.
-
#model_name ⇒ Object
readonly
Added session_service to attr_reader.
-
#name ⇒ Object
readonly
Added session_service to attr_reader.
-
#parent_agent ⇒ Object
readonly
MAS Attributes.
-
#planner ⇒ Object
readonly
Added session_service to attr_reader.
-
#session_service ⇒ Object
readonly
Added session_service to attr_reader.
-
#state ⇒ Object
readonly
Added session_service to attr_reader.
-
#sub_agents ⇒ Object
readonly
Added session_service to attr_reader.
-
#tool_registry ⇒ Object
readonly
Added session_service to attr_reader.
Class Method Summary collapse
-
.define {|a| ... } ⇒ Legate::AgentDefinition
— Class Method for Configuration DSL — Provides a block-based DSL for configuring and creating an Agent instance.
Instance Method Summary collapse
-
#add_tool(tool) ⇒ Boolean
Adds a tool instance OR class to the agent’s registry.
-
#apply_pending_state(callback_context, session_id, session_service, clear: false) ⇒ Object
Flushes a callback’s accumulated state delta into the session via the session service.
-
#ask(user_input, user_id: 'default', session_id: nil) {|event| ... } ⇒ Legate::Event
One-shot convenience runner: starts the agent if needed, creates (or reuses) a session on the agent’s own session service, runs the task, and returns the final event.
-
#available_tools_metadata ⇒ Object
Returns the list of available tool metadata (names, descriptions, parameters) from the agent’s specific tool registry.
-
#find_agent(name_sym) ⇒ Legate::Agent?
Finds an agent with the given name in the hierarchy using DFS.
-
#find_sub_agent(name_sym) ⇒ Legate::Agent?
Finds a direct sub-agent with the given name.
-
#find_tool(tool_name) ⇒ Legate::Tool?
Finds a tool instance by name.
-
#find_tool_class(tool_name) ⇒ Class<Legate::Tool>?
Finds a tool class by name from the agent’s specific tool registry.
-
#initialize(definition:, session_service: nil, planner_override: nil, sub_agents: nil) ⇒ Agent
constructor
Initializes a new agent instance.
-
#record_error_event(session_id, session_service, message) ⇒ Legate::Event
Builds an agent error event, records it in the session history (best-effort: a failed append must not mask the original error), and returns it.
-
#register_tool_class(tool_class) ⇒ Boolean
Registers a tool class with the agent’s specific registry.
-
#root_agent ⇒ Legate::Agent
Returns the root agent in the hierarchy (the topmost agent with no parent).
-
#run_task(session_id:, user_input:, session_service:, on_event: nil) ⇒ Legate::Event
The final agent event.
- #running? ⇒ Boolean
-
#start ⇒ Object
— Runtime State Methods (unchanged) —.
- #stop ⇒ Object
-
#tools ⇒ Array<Legate::Tool>
Returns the list of tools registered with this agent.
-
#transfer_to(target_agent_name, task, session_id, session_service) ⇒ Hash
Transfers control to another agent, executing a task with the same session context.
Constructor Details
#initialize(definition:, session_service: nil, planner_override: nil, sub_agents: nil) ⇒ Agent
Initializes a new agent instance. An agent MUST be initialized with a valid Legate::AgentDefinition object.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/legate/agent.rb', line 110 def initialize(definition:, session_service: nil, planner_override: nil, sub_agents: nil) unless definition.is_a?(Legate::AgentDefinition) raise ArgumentError, "Agent must be initialized with an Legate::AgentDefinition object. Received: #{definition.class}" end # Perform a more thorough check if it looks like a definition unless definition.respond_to?(:name) && definition.respond_to?(:description) && definition.respond_to?(:instruction) && definition.respond_to?(:tool_names) && definition.respond_to?(:model_name) && definition.respond_to?(:fallback_mode) && definition.respond_to?(:mcp_servers) raise ArgumentError, 'Provided definition object does not appear to be a valid Legate::AgentDefinition (missing required attributes/methods).' end @definition = definition @name = definition.name # --- Initialize Callbacks from Definition --- @before_agent_callback = definition.before_agent_callback @after_agent_callback = definition.after_agent_callback @before_model_callback = definition.before_model_callback @after_model_callback = definition.after_model_callback @before_tool_callback = definition.before_tool_callback @after_tool_callback = definition.after_tool_callback # --- End Initialize Callbacks --- # --- Initialize Authentication Config from Definition --- @auth_credential_names = definition.auth_credential_names || Set.new @auth_url_mappings = definition.auth_url_mappings || [] @auth_scheme_assignments = definition.auth_scheme_assignments || {} @auth_credential_assignments = definition.auth_credential_assignments || {} # --- End Initialize Authentication Config --- # Check for direct self-references in the definition's sub_agent_names raise Legate::ConfigurationError, "Circular dependency detected: Agent '#{@name}' cannot include itself as a sub-agent" if definition.respond_to?(:sub_agent_names) && definition.sub_agent_names&.any? && definition.sub_agent_names.include?(@name) @description = definition.description @instruction = definition.instruction @model_name = definition.model_name || DEFAULT_MODEL @fallback_mode = definition.fallback_mode # Assumes :error is default in AgentDefinition @selected_tool_names = definition.tool_names.to_a # Tool names are directly from definition # MAS Attributes Initialization @parent_agent = nil # Will be set by parent if this is a sub-agent @sub_agents = [] # Will be populated if this agent has sub-agents defined @session_service = session_service || Legate.config.session_service @state = :idle Legate.logger.info("Initializing agent '#{@name}' from provided definition object...") setup_tool_registry(definition) setup_mcp_config(definition) @selected_tool_names = @definition.tool_names.to_a @mcp_manager = Legate::Mcp::ConnectionManager.new( tool_registry: @tool_registry, selected_tool_names: @selected_tool_names, agent_name: @name ) @plan_executor = Legate::PlanExecutor.new(self) @planner = planner_override || Legate::Planner.new(agent: self, model_name: @model_name) unless @session_service&.respond_to?(:get_session) && @session_service.respond_to?(:append_event) raise ConfigurationError, "Agent '#{@name}' requires a valid Session Service (must respond to :get_session, :append_event)." end raise ConfigurationError, "Agent '#{@name}' requires a valid Planner (must respond to :plan)." unless @planner&.respond_to?(:plan) Legate.logger.debug { "Agent '#{@name}' initialized with #{@tool_registry.tools.count} tools: [#{@tool_registry.tools.keys.join(', ')}]" } setup_sub_agents(definition, sub_agents) end |
Instance Attribute Details
#after_agent_callback ⇒ Object (readonly)
— Callback Instance Variables —
36 37 38 |
# File 'lib/legate/agent.rb', line 36 def after_agent_callback @after_agent_callback end |
#after_model_callback ⇒ Object (readonly)
— Callback Instance Variables —
36 37 38 |
# File 'lib/legate/agent.rb', line 36 def after_model_callback @after_model_callback end |
#after_tool_callback ⇒ Object (readonly)
— Callback Instance Variables —
36 37 38 |
# File 'lib/legate/agent.rb', line 36 def after_tool_callback @after_tool_callback end |
#auth_credential_assignments ⇒ Object (readonly)
— Authentication Instance Variables —
43 44 45 |
# File 'lib/legate/agent.rb', line 43 def auth_credential_assignments @auth_credential_assignments end |
#auth_credential_names ⇒ Object (readonly)
— Authentication Instance Variables —
43 44 45 |
# File 'lib/legate/agent.rb', line 43 def auth_credential_names @auth_credential_names end |
#auth_scheme_assignments ⇒ Object (readonly)
— Authentication Instance Variables —
43 44 45 |
# File 'lib/legate/agent.rb', line 43 def auth_scheme_assignments @auth_scheme_assignments end |
#auth_url_mappings ⇒ Object (readonly)
— Authentication Instance Variables —
43 44 45 |
# File 'lib/legate/agent.rb', line 43 def auth_url_mappings @auth_url_mappings end |
#before_agent_callback ⇒ Object (readonly)
— Callback Instance Variables —
36 37 38 |
# File 'lib/legate/agent.rb', line 36 def before_agent_callback @before_agent_callback end |
#before_model_callback ⇒ Object (readonly)
— Callback Instance Variables —
36 37 38 |
# File 'lib/legate/agent.rb', line 36 def before_model_callback @before_model_callback end |
#before_tool_callback ⇒ Object (readonly)
— Callback Instance Variables —
36 37 38 |
# File 'lib/legate/agent.rb', line 36 def before_tool_callback @before_tool_callback end |
#definition ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def definition @definition end |
#description ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def description @description end |
#fallback_mode ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def fallback_mode @fallback_mode end |
#instruction ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def instruction @instruction end |
#logger ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def logger @logger end |
#model_name ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def model_name @model_name end |
#name ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def name @name end |
#parent_agent ⇒ Object (readonly)
MAS Attributes
33 34 35 |
# File 'lib/legate/agent.rb', line 33 def parent_agent @parent_agent end |
#planner ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def planner @planner end |
#session_service ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def session_service @session_service end |
#state ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def state @state end |
#sub_agents ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def sub_agents @sub_agents end |
#tool_registry ⇒ Object (readonly)
Added session_service to attr_reader
31 32 33 |
# File 'lib/legate/agent.rb', line 31 def tool_registry @tool_registry end |
Class Method Details
.define {|a| ... } ⇒ Legate::AgentDefinition
— Class Method for Configuration DSL — Provides a block-based DSL for configuring and creating an Agent instance.
The DSL is positional (method-call style), not assignment. The resulting definition is registered globally in GlobalDefinitionRegistry as a side effect, then returned.
68 69 70 71 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 98 99 100 |
# File 'lib/legate/agent.rb', line 68 def self.define(&block) raise ArgumentError, 'Legate::Agent.define requires a block.' unless block_given? # 1. Create a new AgentDefinition definition = Legate::AgentDefinition.new # 2. Evaluate the block within the definition's proxy DSL # Use the definition instance's define method which takes the block # This also handles internal validation via validate! begin definition.define(&block) rescue ArgumentError => e # Re-raise DSL validation errors immediately raise e end # 3. Register the validated definition in the GlobalDefinitionRegistry begin GlobalDefinitionRegistry.register(definition) agent_name = definition.instance_variable_get(:@name) Legate.logger.info("Agent definition '#{agent_name}' registered in GlobalDefinitionRegistry.") rescue ArgumentError => e agent_name_for_log = definition.instance_variable_get(:@name) || 'unknown' Legate.logger.error("Failed to register definition '#{agent_name_for_log}': #{e.class} - #{e.}") raise e rescue StandardError => e agent_name_for_log = definition.instance_variable_get(:@name) || 'unknown' Legate.logger.error("Unexpected error registering definition '#{agent_name_for_log}': #{e.class} - #{e.}") raise Legate::StoreError, "Unexpected error registering definition '#{agent_name_for_log}': #{e.}" end definition # Return the definition instance end |
Instance Method Details
#add_tool(tool) ⇒ Boolean
Adds a tool instance OR class to the agent’s registry
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/legate/agent.rb', line 190 def add_tool(tool) # Check if it's a valid tool instance or class is_tool_instance = tool.is_a?(Legate::Tool) is_tool_class = tool.is_a?(Class) && tool < Legate::Tool unless is_tool_instance || is_tool_class Legate.logger.error("Agent '#{name}' add_tool: Attempted to add invalid tool: #{tool.inspect}") return false end # Determine the actual tool class tool_class = is_tool_class ? tool : tool.class # --- Determine Tool Name with Fallbacks --- # tool_name = get_tool_name_from_class(tool_class) # Use the new helper # --- End Determine Tool Name --- # # Validate name was found unless tool_name # The helper returns nil if no valid name is found Legate.logger.error("Agent '#{name}' add_tool: Could not determine tool name for class #{tool_class}. Cannot add tool.") return false # Explicitly return false end # Check for overwrite Legate.logger.warn("Agent '#{name}': Tool '#{tool_name}' already added. Overwriting with class #{tool_class}.") if @tool_registry.find_class(tool_name) # Register the class using the determined name Legate.logger.debug("Agent '#{name}' add_tool: Registering tool_name=#{tool_name.inspect} with class=#{tool_class.inspect} in registry=#{@tool_registry.object_id}") registration_result = @tool_registry.register(tool_name, tool_class) Legate.logger.debug("Agent '#{name}' add_tool: Registry after registration for #{tool_name.inspect}: #{@tool_registry.tools.keys.inspect}") # Explicitly return the boolean result from the registry registration_result end |
#apply_pending_state(callback_context, session_id, session_service, clear: false) ⇒ Object
Flushes a callback’s accumulated state delta into the session via the session service. Optionally clears the delta afterward (when execution continues and the same context will be reused).
497 498 499 500 501 502 503 504 |
# File 'lib/legate/agent.rb', line 497 def apply_pending_state(callback_context, session_id, session_service, clear: false) return if callback_context.pending_state_delta.empty? callback_context.pending_state_delta.each do |key, value| session_service.set_state(session_id: session_id, key: key, value: value) end callback_context.clear_pending_state_delta! if clear end |
#ask(user_input, user_id: 'default', session_id: nil) {|event| ... } ⇒ Legate::Event
One-shot convenience runner: starts the agent if needed, creates (or reuses) a session on the agent’s own session service, runs the task, and returns the final event. The friendly path over the explicit start/create_session/run_task/stop dance.
answer = agent.ask('What is 2 + 2?').answer
agent.ask('Search ruby') { |event| puts event.role } # live progress (R3)
Lazy-starts but does NOT auto-stop — stopping tears down MCP connections that are costly to re-establish, and an agent typically answers many asks. Call #stop when done (or let process exit reclaim it).
335 336 337 338 339 340 |
# File 'lib/legate/agent.rb', line 335 def ask(user_input, user_id: 'default', session_id: nil, &on_event) start unless running? session_id ||= @session_service.create_session(app_name: name.to_s, user_id: user_id).id run_task(session_id: session_id, user_input: user_input, session_service: @session_service, on_event: on_event) end |
#available_tools_metadata ⇒ Object
Returns the list of available tool metadata (names, descriptions, parameters) from the agent’s specific tool registry.
307 308 309 |
# File 'lib/legate/agent.rb', line 307 def @tool_registry.list_tools end |
#find_agent(name_sym) ⇒ Legate::Agent?
Finds an agent with the given name in the hierarchy using DFS
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 |
# File 'lib/legate/agent.rb', line 530 def find_agent(name_sym) # Convert to symbol if string provided name_sym = name_sym.to_sym if name_sym.is_a?(String) # Check if this is the agent we're looking for return self if @name.to_sym == name_sym # Search sub-agents recursively @sub_agents.each do |sub_agent| found = sub_agent.find_agent(name_sym) return found if found end # Not found in this branch nil end |
#find_sub_agent(name_sym) ⇒ Legate::Agent?
Finds a direct sub-agent with the given name
550 551 552 553 554 555 556 557 |
# File 'lib/legate/agent.rb', line 550 def find_sub_agent(name_sym) # Convert to symbol if string provided name_sym = name_sym.to_sym if name_sym.is_a?(String) return nil unless @sub_agents.is_a?(Array) @sub_agents.find { |sub_agent| sub_agent.name.to_sym == name_sym } end |
#find_tool(tool_name) ⇒ Legate::Tool?
Finds a tool instance by name
244 245 246 |
# File 'lib/legate/agent.rb', line 244 def find_tool(tool_name) @tool_registry.create_instance(tool_name.to_sym) end |
#find_tool_class(tool_name) ⇒ Class<Legate::Tool>?
Finds a tool class by name from the agent’s specific tool registry.
314 315 316 |
# File 'lib/legate/agent.rb', line 314 def find_tool_class(tool_name) @tool_registry.find_class(tool_name.to_sym) end |
#record_error_event(session_id, session_service, message) ⇒ Legate::Event
Builds an agent error event, records it in the session history (best-effort: a failed append must not mask the original error), and returns it.
509 510 511 512 513 514 515 516 517 |
# File 'lib/legate/agent.rb', line 509 def record_error_event(session_id, session_service, ) event = Legate::Event.new(role: :agent, content: { status: :error, error_message: }) begin session_service.append_event(session_id: session_id, event: event) rescue StandardError => e Legate.logger.error { "Agent '#{@name}': failed to record error event in session: #{e.}" } end event end |
#register_tool_class(tool_class) ⇒ Boolean
Registers a tool class with the agent’s specific registry.
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/legate/agent.rb', line 251 def register_tool_class(tool_class) Legate.logger.debug("[register_tool_class] Registering class: #{tool_class.inspect} (Object ID: #{tool_class.object_id})") # Basic validation unless tool_class < Legate::Tool Legate.logger.error("Agent '#{name}': Attempted to register invalid object (must inherit from Legate::Tool): #{tool_class.inspect}") return false end # Get name via metadata method tool_name = get_tool_name_from_class(tool_class) # Use the new helper Legate.logger.debug("[register_tool_class] Determined tool name: #{tool_name.inspect} for class #{tool_class.inspect}") unless tool_name # Helper returns nil if no valid name # Use logger method, not direct access Legate.logger.error("Agent '#{name}': Could not determine tool name for class #{tool_class}. Cannot register.") # Consistent error message return false end Legate.logger.warn("Agent '#{name}': Tool '#{tool_name}' already registered. Overwriting.") if @tool_registry.find_class(tool_name) # Register with the instance registry @tool_registry.register(tool_name, tool_class) true # Return true on success end |
#root_agent ⇒ Legate::Agent
Returns the root agent in the hierarchy (the topmost agent with no parent)
521 522 523 524 525 |
# File 'lib/legate/agent.rb', line 521 def root_agent return self if @parent_agent.nil? @parent_agent.root_agent end |
#run_task(session_id:, user_input:, session_service:, on_event: nil) ⇒ Legate::Event
Returns The final agent event.
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'lib/legate/agent.rb', line 347 def run_task(session_id:, user_input:, session_service:, on_event: nil) # --- Pre-execution Checks --- # unless running? err_msg = "Agent '#{name}' is not running. Call agent.start before run_task, " \ 'or use agent.ask (which starts automatically).' Legate.logger.error(err_msg) return Legate::Event.new(role: :agent, content: { status: :error, error_message: err_msg }) end session = session_service.get_session(session_id: session_id) unless session err_msg = "Session not found: #{session_id}" Legate.logger.error(err_msg) # Even if session isn't found, return an event for consistency? return Legate::Event.new(role: :agent, content: { status: :error, error_message: err_msg }) end # ----------------- # # Generate invocation_id for this run and create callback context invocation_id = SecureRandom.uuid callback_context = nil # R3: stream lifecycle events to the optional on_event callback as they're # appended. Torn down in the ensure below so the subscription can't leak. event_subscription = subscribe_events(session_service, session_id, on_event) begin # Create callback context for callbacks to use callback_context = Legate::Callbacks::CallbackContext.new( agent_name: @name, invocation_id: invocation_id, session_id: session_id, user_id: session.user_id, app_name: session.app_name, session_service: session_service ) # Execute before_agent_callback if defined if @definition.respond_to?(:before_agent_callback) && @definition.before_agent_callback Legate.logger.debug { "Agent '#{@name}': Executing before_agent_callback." } # Execute the callback and check if it returns a result begin override_result = @definition.before_agent_callback.call(callback_context) # If the callback returns a result (not nil), use it instead of normal execution if override_result Legate.logger.info { "Agent '#{@name}': before_agent_callback provided an override result." } # Apply any pending state changes from the callback apply_pending_state(callback_context, session_id, session_service) # Create an agent event with the override result final_agent_event = Legate::Event.new(role: :agent, content: override_result) session_service.append_event(session_id: session_id, event: final_agent_event) # Store the output if configured _store_output_in_session(final_agent_event, session_id, session_service) return final_agent_event end rescue StandardError => e Legate.logger.error { "Agent '#{@name}': Error in before_agent_callback: #{e.}\n#{e.backtrace.join("\n")}" } return record_error_event(session_id, session_service, "Error in before_agent_callback: #{e.}") end # Apply any pending state changes from the callback if execution continues apply_pending_state(callback_context, session_id, session_service, clear: true) end # --- Normal Execution Flow --- # # Create a user-message event for this turn = Legate::Event.new( role: :user, content: user_input ) session_service.append_event(session_id: session_id, event: ) # Produce the result via the configured strategy. :plan (default) asks # the planner for one upfront plan and runs it; :react drives an agentic # observe->think->act loop. Both return the same { details:, last_result: } # shape, so the final-event handling below is strategy-agnostic. result_hash = if react_strategy? run_react_loop(user_input, session, session_service, invocation_id) else plan = @planner.plan(user_input, invocation_id) execute_plan(plan, session, session_service, invocation_id) end # Create an agent event with the result final_agent_event = Legate::Event.new(role: :agent, content: result_hash[:last_result] || result_hash) session_service.append_event(session_id: session_id, event: final_agent_event) # Execute after_agent_callback if defined if @definition.respond_to?(:after_agent_callback) && @definition.after_agent_callback Legate.logger.debug { "Agent '#{@name}': Executing after_agent_callback." } begin # Execute the callback and let it modify the result if needed # Pass the actual result (last_result) to the callback, not the full hash with details modified_result = @definition.after_agent_callback.call(callback_context, result_hash[:last_result] || result_hash) # If the callback returned a modified result, use it if modified_result && modified_result != (result_hash[:last_result] || result_hash) Legate.logger.info { "Agent '#{@name}': after_agent_callback modified the result." } # Create a new agent event with the modified result final_agent_event = Legate::Event.new(role: :agent, content: modified_result) session_service.append_event(session_id: session_id, event: final_agent_event) end rescue StandardError => e Legate.logger.error { "Agent '#{@name}': Error in after_agent_callback: #{e.}\n#{e.backtrace.join("\n")}" } # Don't override the result completely on error, just log it end # Apply the callback's pending state changes exactly once (whether or # not it modified the result). apply_pending_state(callback_context, session_id, session_service) end # Store the output if configured _store_output_in_session(final_agent_event, session_id, session_service) # Return the final agent event final_agent_event rescue StandardError => e # Handle any other errors during execution. Record the failure in the # session so its history reflects what the caller saw (the success and # callback paths already append their events). Legate.logger.error { "Agent '#{@name}' runtime error: #{e.}\n#{e.backtrace.join("\n")}" } record_error_event(session_id, session_service, e.) ensure session_service.unsubscribe(event_subscription) if event_subscription && session_service.respond_to?(:unsubscribe) end end |
#running? ⇒ Boolean
301 302 303 |
# File 'lib/legate/agent.rb', line 301 def running? @state == :running end |
#start ⇒ Object
— Runtime State Methods (unchanged) —
277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/legate/agent.rb', line 277 def start return if running? # Prevent starting multiple times Legate.logger.info("Starting agent '#{name}' runtime...") @state = :running # Connect to MCP Servers and register tools connect_mcp_servers Legate.logger.info("Agent '#{name}' runtime started.") end |
#stop ⇒ Object
289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/legate/agent.rb', line 289 def stop return unless running? Legate.logger.info("Stopping agent '#{name}' runtime...") @state = :stopped # Disconnect MCP Clients disconnect_mcp_servers Legate.logger.info("Agent '#{name}' runtime stopped.") end |
#tools ⇒ Array<Legate::Tool>
Returns the list of tools registered with this agent
227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/legate/agent.rb', line 227 def tools @tool_registry.tools.values.map do |tool_class| # Get name reliably using the new helper method tool_name = get_tool_name_from_class(tool_class) if tool_name @tool_registry.create_instance(tool_name) else # This branch should ideally not be hit frequently if registration robustly requires a name. Legate.logger.warn("Agent '#{name}': Skipping tool instance creation for class #{tool_class} as its name could not be determined post-registration.") nil end end.compact end |
#transfer_to(target_agent_name, task, session_id, session_service) ⇒ Hash
Transfers control to another agent, executing a task with the same session context. This is a public version of the private transfer_to method
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 |
# File 'lib/legate/agent.rb', line 567 def transfer_to(target_agent_name, task, session_id, session_service) # Verify the target agent is in the delegation_targets list if defined if @definition.respond_to?(:delegation_targets) && @definition.delegation_targets&.any? && !@definition.delegation_targets.include?(target_agent_name) error_msg = "Agent '#{target_agent_name}' is not in the delegation targets for '#{@name}'" Legate.logger.error(error_msg) return { status: :error, error_message: error_msg, error_class: 'InvalidDelegationTarget' } end # Find the target agent in the agent hierarchy, starting from the root target_agent = root_agent.find_agent(target_agent_name) # If not found in hierarchy, try to instantiate from definition store unless target_agent Legate.logger.info("Target agent '#{target_agent_name}' not found in hierarchy. Attempting to load from definition store.") begin # Try to find the definition in the global registry target_def = Legate::GlobalDefinitionRegistry.find(target_agent_name) unless target_def error_msg = "Target agent definition '#{target_agent_name}' not found in registry" Legate.logger.error(error_msg) return { status: :error, error_message: error_msg, error_class: 'AgentDefinitionNotFound' } end # Create a new agent instance from the definition target_agent = Legate::Agent.new( definition: target_def, session_service: session_service ) rescue StandardError => e error_msg = "Failed to instantiate target agent '#{target_agent_name}': #{e.}" Legate.logger.error("#{error_msg}\n#{e.backtrace.join("\n")}") return { status: :error, error_message: error_msg, error_class: e.class.name } end end # Verify the target agent exists unless target_agent error_msg = "Target agent '#{target_agent_name}' not found in hierarchy or definition store" Legate.logger.error(error_msg) return { status: :error, error_message: error_msg, error_class: 'AgentNotFound' } end # Start the target agent if it's not already running target_agent.start unless target_agent.running? # Execute the delegated task begin Legate.logger.info("Executing delegated task on agent '#{target_agent_name}': #{task}") # Call run_task with the same session context result_event = target_agent.run_task( session_id: session_id, user_input: task, session_service: session_service ) # Extract and format the result result_content = result_event.respond_to?(:content) ? result_event.content : result_event { status: :success, target_agent: target_agent_name.to_s, result: result_content } rescue StandardError => e error_msg = "Error executing task on target agent '#{target_agent_name}': #{e.}" Legate.logger.error("#{error_msg}\n#{e.backtrace.join("\n")}") { status: :error, error_message: error_msg, error_class: e.class.name } end end |