Class: RobotLab::Robot
- Inherits:
-
RubyLLM::Agent
- Object
- RubyLLM::Agent
- RobotLab::Robot
- Includes:
- BusMessaging, HistorySearch, MCPManagement, TemplateRendering
- Defined in:
- lib/robot_lab/robot.rb,
lib/robot_lab/robot/bus_messaging.rb,
lib/robot_lab/robot/history_search.rb,
lib/robot_lab/robot/mcp_management.rb,
lib/robot_lab/robot/template_rendering.rb
Overview
LLM-powered robot built on RubyLLM::Agent
Robot is a subclass of RubyLLM::Agent that adds:
-
Template-based prompts via prompt_manager
-
Shared memory (standalone or network)
-
Tool integration with hierarchical MCP configuration
-
SimpleFlow pipeline integration
Memory Behavior
Standalone: Robot uses its own inherent memory (‘robot.memory`). *In a Network*: Robot uses the network’s shared memory.
Defined Under Namespace
Modules: BusMessaging, HistorySearch, MCPManagement, TemplateRendering
Constant Summary
Constants included from HistorySearch
HistorySearch::MIN_SCORE_LENGTH
Constants included from TemplateRendering
TemplateRendering::FRONT_MATTER_CONFIG_KEYS, TemplateRendering::FRONT_MATTER_EXTRA_KEYS
Instance Attribute Summary collapse
-
#bus ⇒ Object
readonly
Returns the value of attribute bus.
-
#config ⇒ Object
readonly
Returns the value of attribute config.
-
#description ⇒ String?
readonly
An optional description of the robot’s purpose.
-
#input ⇒ IO
Input stream for user interaction (default: $stdin).
-
#learnings ⇒ Object
readonly
Returns the value of attribute learnings.
-
#local_tools ⇒ Array
readonly
The locally defined tools for this robot.
-
#mcp_clients ⇒ Hash<String, MCP::Client>
readonly
Connected MCP clients by server name.
-
#mcp_config ⇒ Symbol, Array
readonly
Build-time MCP configuration (raw, unresolved).
-
#mcp_tools ⇒ Array<Tool>
readonly
Tools discovered from MCP servers.
-
#memory ⇒ Memory
readonly
The robot’s inherent memory (used when standalone, not in network).
-
#name ⇒ String
readonly
The unique identifier for the robot.
-
#outbox ⇒ Object
readonly
Returns the value of attribute outbox.
-
#output ⇒ IO
Output stream for user interaction (default: $stdout).
-
#provider ⇒ Object
readonly
Returns the value of attribute provider.
-
#skills ⇒ Object
readonly
Returns the value of attribute skills.
-
#system_prompt ⇒ String?
readonly
Inline system prompt (used alone or appended to template).
-
#template ⇒ Symbol?
readonly
The prompt_manager template for the robot’s prompt.
-
#tools_config ⇒ Object
readonly
Returns the value of attribute tools_config.
-
#total_input_tokens ⇒ Object
readonly
Returns the value of attribute total_input_tokens.
-
#total_output_tokens ⇒ Object
readonly
Returns the value of attribute total_output_tokens.
Instance Method Summary collapse
-
#call(result) ⇒ SimpleFlow::Result
SimpleFlow step interface.
-
#chat ⇒ RubyLLM::Chat
Access the underlying RubyLLM::Chat instance.
-
#chat_provider ⇒ String?
Return the provider for this robot’s chat.
-
#clear_messages(keep_system: true) ⇒ self
Clear conversation messages, optionally keeping the system prompt.
-
#compress_history(recent_turns: 3, keep_threshold: 0.6, drop_threshold: 0.2, summarizer: nil) ⇒ self
Compress conversation history using TF-IDF relevance scoring.
-
#connect_mcp! ⇒ self
Eagerly connect to configured MCP servers and discover tools.
-
#delegate(to:, task:, async: false, **kwargs) ⇒ RobotResult, DelegationFuture
Delegate a task to another robot, synchronously or asynchronously.
-
#disconnect ⇒ self
Disconnect all MCP clients and bus channel.
-
#failed_mcp_server_names ⇒ Array<String>
Returns server names that failed to connect.
-
#initialize(name:, template: nil, system_prompt: nil, context: {}, description: nil, local_tools: [], model: nil, provider: nil, mcp_servers: [], mcp: :none, tools: :none, on_tool_call: nil, on_tool_result: nil, on_content: nil, enable_cache: true, bus: nil, skills: nil, temperature: nil, top_p: nil, top_k: nil, max_tokens: nil, presence_penalty: nil, frequency_penalty: nil, stop: nil, max_tool_rounds: nil, token_budget: nil, mcp_discovery: false, config: nil) ⇒ Robot
constructor
Creates a new Robot instance.
-
#inject_mcp!(clients:, tools:) ⇒ self
Inject pre-connected MCP clients and their tools into this robot.
-
#learn(text) ⇒ self
Add a learning to this robot’s accumulation store.
-
#mcp_client(server_name) ⇒ MCP::Client?
Find an MCP client by server name.
-
#messages ⇒ Array<RubyLLM::Message>
Return the conversation messages from the underlying chat.
-
#model ⇒ String?
Returns the model identifier.
-
#replace_messages(messages) ⇒ self
Replace conversation messages with a saved set (for checkpoint restore).
-
#reset_memory ⇒ self
Reset the robot’s inherent memory.
-
#reset_token_totals ⇒ self
Reset cumulative token counters to zero.
-
#run(message = nil, network: nil, network_memory: nil, network_config: nil, memory: nil, mcp: :none, tools: :none, **kwargs) {|chunk| ... } ⇒ RobotResult
Send a message and get a response, with Robot’s extended capabilities.
-
#to_h ⇒ Hash
Converts the robot to a hash representation.
-
#update(template: nil, context: nil, system_prompt: nil, model: nil, temperature: nil, **kwargs) ⇒ self
Reconfigure the robot for a new context.
Methods included from HistorySearch
Methods included from BusMessaging
#assign_bus_poller, #on_message, #send_message, #send_reply, #spawn, #with_bus
Methods included from TemplateRendering
Constructor Details
#initialize(name:, template: nil, system_prompt: nil, context: {}, description: nil, local_tools: [], model: nil, provider: nil, mcp_servers: [], mcp: :none, tools: :none, on_tool_call: nil, on_tool_result: nil, on_content: nil, enable_cache: true, bus: nil, skills: nil, temperature: nil, top_p: nil, top_k: nil, max_tokens: nil, presence_penalty: nil, frequency_penalty: nil, stop: nil, max_tool_rounds: nil, token_budget: nil, mcp_discovery: false, config: nil) ⇒ Robot
Creates a new Robot instance.
106 107 108 109 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 186 187 188 189 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 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/robot_lab/robot.rb', line 106 def initialize( name:, template: nil, system_prompt: nil, context: {}, description: nil, local_tools: [], model: nil, provider: nil, mcp_servers: [], mcp: :none, tools: :none, on_tool_call: nil, on_tool_result: nil, on_content: nil, enable_cache: true, bus: nil, skills: nil, temperature: nil, top_p: nil, top_k: nil, max_tokens: nil, presence_penalty: nil, frequency_penalty: nil, stop: nil, max_tool_rounds: nil, token_budget: nil, mcp_discovery: false, config: nil ) @name = name.to_s @name_from_constructor = (name.to_s != "robot") @template = template @system_prompt = system_prompt @build_context = context @description = description @local_tools = Array(local_tools) @skills = skills ? Array(skills).map(&:to_sym) : nil @expanded_skills = nil @mcp_discovery = mcp_discovery # Build RunConfig from explicit kwargs, merged on top of passed-in config. # Explicit constructor kwargs always override the shared config. explicit_fields = { model: model, temperature: temperature, top_p: top_p, top_k: top_k, max_tokens: max_tokens, presence_penalty: presence_penalty, frequency_penalty: frequency_penalty, stop: stop, on_tool_call: on_tool_call, on_tool_result: on_tool_result, on_content: on_content, bus: bus, enable_cache: enable_cache, max_tool_rounds: max_tool_rounds, token_budget: token_budget }.compact # Only include mcp/tools if explicitly set (not the default :none sentinel) resolved_mcp = mcp_servers.any? ? mcp_servers : mcp explicit_fields[:mcp] = resolved_mcp unless ToolConfig.none_value?(resolved_mcp) explicit_fields[:tools] = tools unless ToolConfig.none_value?(tools) explicit_config = RunConfig.new(**explicit_fields) @config = config ? config.merge(explicit_config) : explicit_config # Extract values from effective config for backward compatibility @on_tool_call = @config.on_tool_call @on_tool_result = @config.on_tool_result @on_content = @config.on_content # Store raw config values for hierarchical resolution @mcp_config = @config.mcp || :none @tools_config = @config.tools || :none # MCP state @mcp_clients = {} @mcp_tools = [] @mcp_initialized = false # Bus state (optional inter-robot communication) @bus = @config.bus @message_counter = 0 @outbox = {} @message_handler = nil @bus_poller = nil @private_bus_poller = nil @bus_poller_group = :default # Token tracking @total_input_tokens = 0 @total_output_tokens = 0 # Learning accumulation @learnings = [] # Inherent memory (used when standalone, not in a network) cache_enabled = @config.key?(:enable_cache) ? @config.enable_cache : true @memory = Memory.new(enable_cache: cache_enabled) # Restore persisted learnings from inherent memory if present persisted = @memory.get(:learnings) @learnings = Array(persisted) if persisted # Ensure config is loaded (triggers PM setup, RubyLLM config, etc.) config = RobotLab.config # Build chat kwargs for Agent's super resolved_model = @config.model || config.ruby_llm.model chat_kwargs = { model: resolved_model } # Pass provider through for local providers (Ollama, GPUStack, etc.) # RubyLLM auto-sets assume_model_exists for local providers when # provider is specified. @provider = provider if @provider chat_kwargs[:provider] = @provider chat_kwargs[:assume_model_exists] = true end # Create the persistent chat via Agent's initialize super(chat: nil, **chat_kwargs) # Dynamically delegate all with_* methods from the Chat class define_chat_delegators # Gather all skill IDs from constructor + main template front matter all_skill_ids = Array(@skills) if @template parsed_main = PM.parse(@template) fm_skills = (parsed_main.) all_skill_ids = all_skill_ids + fm_skills end if all_skill_ids.any? # Skills path: expand skills, merge config, concatenate bodies apply_skills_and_template_to_chat(all_skill_ids, context) elsif @template # Standard path: single template, no skills apply_template_to_chat(context) end @chat.with_instructions(@system_prompt) if @system_prompt # Constructor params override template front matter (use config values) @chat.with_temperature(@config.temperature) if @config.temperature # These parameters don't have dedicated with_* methods on Chat; # pass them through with_params. extra_params = { top_p: @config.top_p, top_k: @config.top_k, max_tokens: @config.max_tokens, presence_penalty: @config.presence_penalty, frequency_penalty: @config.frequency_penalty, stop: @config.stop }.compact @chat.with_params(**extra_params) if extra_params.any? # Apply callbacks @chat.on_tool_call(&@on_tool_call) if @on_tool_call @chat.on_tool_result(&@on_tool_result) if @on_tool_result # Set up bus channel if a bus was provided setup_bus_channel if @bus end |
Instance Attribute Details
#bus ⇒ Object (readonly)
Returns the value of attribute bus.
69 70 71 |
# File 'lib/robot_lab/robot.rb', line 69 def bus @bus end |
#config ⇒ Object (readonly)
Returns the value of attribute config.
69 70 71 |
# File 'lib/robot_lab/robot.rb', line 69 def config @config end |
#description ⇒ String? (readonly)
Returns an optional description of the robot’s purpose.
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#input ⇒ IO
Returns input stream for user interaction (default: $stdin).
67 68 69 |
# File 'lib/robot_lab/robot.rb', line 67 def input @input end |
#learnings ⇒ Object (readonly)
Returns the value of attribute learnings.
69 70 71 |
# File 'lib/robot_lab/robot.rb', line 69 def learnings @learnings end |
#local_tools ⇒ Array (readonly)
Returns the locally defined tools for this robot.
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#mcp_clients ⇒ Hash<String, MCP::Client> (readonly)
Returns connected MCP clients by server name.
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#mcp_config ⇒ Symbol, Array (readonly)
Returns build-time MCP configuration (raw, unresolved).
78 79 80 |
# File 'lib/robot_lab/robot.rb', line 78 def mcp_config @mcp_config end |
#mcp_tools ⇒ Array<Tool> (readonly)
Returns tools discovered from MCP servers.
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#memory ⇒ Memory (readonly)
Returns the robot’s inherent memory (used when standalone, not in network).
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#name ⇒ String (readonly)
Returns the unique identifier for the robot.
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#outbox ⇒ Object (readonly)
Returns the value of attribute outbox.
69 70 71 |
# File 'lib/robot_lab/robot.rb', line 69 def outbox @outbox end |
#output ⇒ IO
Returns output stream for user interaction (default: $stdout).
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#provider ⇒ Object (readonly)
Returns the value of attribute provider.
69 70 71 |
# File 'lib/robot_lab/robot.rb', line 69 def provider @provider end |
#skills ⇒ Object (readonly)
Returns the value of attribute skills.
69 70 71 |
# File 'lib/robot_lab/robot.rb', line 69 def skills @skills end |
#system_prompt ⇒ String? (readonly)
Returns inline system prompt (used alone or appended to template).
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#template ⇒ Symbol? (readonly)
Returns the prompt_manager template for the robot’s prompt.
67 |
# File 'lib/robot_lab/robot.rb', line 67 attr_accessor :input, :output |
#tools_config ⇒ Object (readonly)
Returns the value of attribute tools_config.
78 |
# File 'lib/robot_lab/robot.rb', line 78 attr_reader :mcp_config, :tools_config |
#total_input_tokens ⇒ Object (readonly)
Returns the value of attribute total_input_tokens.
69 70 71 |
# File 'lib/robot_lab/robot.rb', line 69 def total_input_tokens @total_input_tokens end |
#total_output_tokens ⇒ Object (readonly)
Returns the value of attribute total_output_tokens.
69 70 71 |
# File 'lib/robot_lab/robot.rb', line 69 def total_output_tokens @total_output_tokens end |
Instance Method Details
#call(result) ⇒ SimpleFlow::Result
SimpleFlow step interface
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 |
# File 'lib/robot_lab/robot.rb', line 404 def call(result) run_context = extract_run_context(result) # Extract the message from run context = run_context.delete(:message) start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) robot_result = run(, **run_context) robot_result.duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time result .with_context(@name.to_sym, robot_result) .continue(robot_result) rescue Exception => e # rubocop:disable Lint/RescueException # Catch all errors (including SecurityError, Timeout::Error, etc.) # so one failing robot doesn't crash the entire network pipeline. elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time error_result = RobotResult.new( robot_name: @name, output: [TextMessage.new(role: 'assistant', content: "Error: #{e.class}: #{e.}")] ) error_result.duration = elapsed result .with_context(@name.to_sym, error_result) .continue(error_result) end |
#chat ⇒ RubyLLM::Chat
Access the underlying RubyLLM::Chat instance. Useful for checkpoint/restore operations that need direct access to conversation state.
493 494 495 |
# File 'lib/robot_lab/robot.rb', line 493 def chat @chat end |
#chat_provider ⇒ String?
Return the provider for this robot’s chat. Useful for displaying model/provider info without reaching into chat internals.
616 617 618 619 620 621 |
# File 'lib/robot_lab/robot.rb', line 616 def chat_provider m = @chat.model m.respond_to?(:provider) ? m.provider : nil rescue StandardError nil end |
#clear_messages(keep_system: true) ⇒ self
Clear conversation messages, optionally keeping the system prompt.
508 509 510 511 512 513 514 515 516 517 |
# File 'lib/robot_lab/robot.rb', line 508 def (keep_system: true) if keep_system system_msg = @chat..find { |m| m.role == :system } @chat.instance_variable_set(:@messages, []) @chat.(system_msg) if system_msg else @chat.instance_variable_set(:@messages, []) end self end |
#compress_history(recent_turns: 3, keep_threshold: 0.6, drop_threshold: 0.2, summarizer: nil) ⇒ self
Compress conversation history using TF-IDF relevance scoring.
Old turns are tiered against the most recent context:
-
High relevance (score >= keep_threshold) → kept verbatim
-
Medium relevance (drop_threshold..keep_threshold) → summarized or dropped
-
Low relevance (score < drop_threshold) → dropped
System messages and tool call/result messages are always preserved. The most recent recent_turns pairs are also always kept verbatim.
Requires the optional ‘classifier’ gem (~> 2.3). Raises DependencyError if not installed.
547 548 549 550 551 552 553 554 555 556 |
# File 'lib/robot_lab/robot.rb', line 547 def compress_history(recent_turns: 3, keep_threshold: 0.6, drop_threshold: 0.2, summarizer: nil) compressed = HistoryCompressor.new( messages: @chat., recent_turns: recent_turns, keep_threshold: keep_threshold, drop_threshold: drop_threshold, summarizer: summarizer ).call (compressed) end |
#connect_mcp! ⇒ self
Eagerly connect to configured MCP servers and discover tools. Normally MCP connections are lazy (established on first run). Call this to connect early, e.g. to display connection status at startup.
447 448 449 450 451 |
# File 'lib/robot_lab/robot.rb', line 447 def connect_mcp! resolved_mcp = resolve_mcp_hierarchy(@mcp_config) ensure_mcp_clients(resolved_mcp) if resolved_mcp.is_a?(Array) && resolved_mcp.any? self end |
#delegate(to:, task:, async: false, **kwargs) ⇒ RobotResult, DelegationFuture
Delegate a task to another robot, synchronously or asynchronously.
Synchronous (default, async: false): blocks until the delegatee finishes and returns a RobotResult annotated with delegated_by, duration, and token counts.
Asynchronous (+async: true+): starts the delegatee in a background thread and returns a DelegationFuture immediately. Call future.value to block for the result, or future.resolved? to poll.
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 |
# File 'lib/robot_lab/robot.rb', line 586 def delegate(to:, task:, async: false, **kwargs) if async future = DelegationFuture.new(robot_name: to.name, delegated_by: @name) delegator_name = @name Thread.new do started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) result = to.run(task, **kwargs) result.duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at result.delegated_by = delegator_name future.resolve!(result) rescue => e future.reject!(e) end future else started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) result = to.run(task, **kwargs) result.duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at result.delegated_by = @name result end end |
#disconnect ⇒ self
Disconnect all MCP clients and bus channel.
465 466 467 468 469 |
# File 'lib/robot_lab/robot.rb', line 465 def disconnect @mcp_clients.each_value(&:disconnect) teardown_bus_channel if @bus self end |
#failed_mcp_server_names ⇒ Array<String>
Returns server names that failed to connect.
456 457 458 459 460 |
# File 'lib/robot_lab/robot.rb', line 456 def failed_mcp_server_names return [] unless @failed_mcp_configs @failed_mcp_configs.keys end |
#inject_mcp!(clients:, tools:) ⇒ self
Inject pre-connected MCP clients and their tools into this robot. Used by host applications (e.g. AIA) that manage MCP connections externally and need to pass them to robots without re-connecting.
481 482 483 484 485 486 |
# File 'lib/robot_lab/robot.rb', line 481 def inject_mcp!(clients:, tools:) @mcp_clients = clients @mcp_tools = tools @mcp_initialized = true self end |
#learn(text) ⇒ self
Add a learning to this robot’s accumulation store.
Deduplicates by bidirectional substring matching: a new learning is skipped if it is already contained within an existing learning, or an existing learning is contained within the new one (the new one wins and replaces the weaker entry).
Learnings are persisted to the robot’s inherent memory under :learnings.
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 |
# File 'lib/robot_lab/robot.rb', line 643 def learn(text) text = text.to_s.strip return self if text.empty? # Remove any existing learning that is a substring of the new one @learnings.reject! { |existing| text.include?(existing) } # Skip if any existing learning already covers the new one unless @learnings.any? { |existing| existing.include?(text) } @learnings << text @memory.set(:learnings, @learnings.dup) end self end |
#mcp_client(server_name) ⇒ MCP::Client?
Find an MCP client by server name.
627 628 629 |
# File 'lib/robot_lab/robot.rb', line 627 def mcp_client(server_name) @mcp_clients[server_name] end |
#messages ⇒ Array<RubyLLM::Message>
Return the conversation messages from the underlying chat.
500 501 502 |
# File 'lib/robot_lab/robot.rb', line 500 def @chat. end |
#model ⇒ String?
Returns the model identifier
271 272 273 274 275 276 |
# File 'lib/robot_lab/robot.rb', line 271 def model return nil unless @chat.respond_to?(:model) m = @chat.model m.respond_to?(:id) ? m.id : m.to_s end |
#replace_messages(messages) ⇒ self
Replace conversation messages with a saved set (for checkpoint restore).
523 524 525 526 |
# File 'lib/robot_lab/robot.rb', line 523 def () @chat.instance_variable_set(:@messages, ) self end |
#reset_memory ⇒ self
Reset the robot’s inherent memory
436 437 438 439 |
# File 'lib/robot_lab/robot.rb', line 436 def reset_memory @memory.reset self end |
#reset_token_totals ⇒ self
Reset cumulative token counters to zero.
663 664 665 666 667 |
# File 'lib/robot_lab/robot.rb', line 663 def reset_token_totals @total_input_tokens = 0 @total_output_tokens = 0 self end |
#run(message = nil, network: nil, network_memory: nil, network_config: nil, memory: nil, mcp: :none, tools: :none, **kwargs) {|chunk| ... } ⇒ RobotResult
Send a message and get a response, with Robot’s extended capabilities
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 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 369 |
# File 'lib/robot_lab/robot.rb', line 302 def run( = nil, network: nil, network_memory: nil, network_config: nil, memory: nil, mcp: :none, tools: :none, **kwargs, &block) # Determine which memory to use run_memory = resolve_active_memory(network: network, network_memory: network_memory) # Merge runtime memory if provided case memory when Memory run_memory = memory when Hash run_memory.merge!(memory) end # Set current_writer so memory notifications know who wrote the value previous_writer = run_memory.current_writer run_memory.current_writer = @name begin # Resolve hierarchical MCP and tools configuration resolved_mcp = resolve_mcp_hierarchy(mcp, network: network, network_config: network_config) resolved_tools = resolve_tools_hierarchy(tools, network: network, network_config: network_config) # Filter MCP servers by semantic relevance when discovery is enabled. # Only applies on the first run (before @mcp_initialized) so connections # are not torn down mid-conversation. if @mcp_discovery && !@mcp_initialized && resolved_mcp.is_a?(Array) resolved_mcp = MCP::ServerDiscovery.select(.to_s, from: resolved_mcp) end # Initialize or update MCP clients based on resolved config ensure_mcp_clients(resolved_mcp) # Apply filtered tools to the persistent chat filtered = filtered_tools(resolved_tools) @chat.with_tools(*filtered) if filtered.any? # Re-render template with run-time context merged into build-time context. # Template parameters (e.g. customer: null) may require values that are # only available at run time — the robot gathers them before rendering. run_context = kwargs.except(:with) rerender_template(run_context) if @template && run_context.any? # Prepend accumulated learnings to the user message = inject_learnings() # Install circuit breaker for this run if max_tool_rounds is configured install_circuit_breaker if @config.max_tool_rounds # Delegate to Agent's ask (which calls @chat.ask) ask_kwargs = kwargs.slice(:with) streaming = effective_streaming_block(block) response = ask(, **ask_kwargs, &streaming) result = build_result(response, run_memory) # Enforce token budget if configured budget = @config.token_budget if budget && @total_input_tokens + @total_output_tokens > budget raise InferenceError, "Token budget exceeded: #{@total_input_tokens + @total_output_tokens} tokens used, budget is #{budget}" end result ensure restore_tool_call_callback if @config.max_tool_rounds run_memory.current_writer = previous_writer end end |
#to_h ⇒ Hash
Converts the robot to a hash representation
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 |
# File 'lib/robot_lab/robot.rb', line 673 def to_h { name: name, description: description, template: template, skills: @skills, system_prompt: system_prompt, local_tools: local_tools.map { |t| t.respond_to?(:name) ? t.name : t.to_s }, mcp_tools: mcp_tools.map(&:name), mcp_config: @mcp_config, tools_config: @tools_config, mcp_servers: @mcp_clients.keys, model: model, config: (@config.empty? ? nil : @config.to_json_hash), bus: @bus ? true : nil }.compact end |
#update(template: nil, context: nil, system_prompt: nil, model: nil, temperature: nil, **kwargs) ⇒ self
Reconfigure the robot for a new context
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/robot_lab/robot.rb', line 380 def update(template: nil, context: nil, system_prompt: nil, model: nil, temperature: nil, **kwargs) if template @template = template ctx = context || @build_context apply_template_to_chat(ctx) end @chat.with_instructions(system_prompt) if system_prompt @chat.with_model(model) if model @chat.with_temperature(temperature) if temperature kwargs.each do |key, value| method = :"with_#{key}" @chat.public_send(method, value) if value && @chat.respond_to?(method) end self end |