Class: SwarmSDK::Agent::Chat

Overview

Chat wraps RubyLLM::Chat to provide SwarmSDK orchestration capabilities

Architecture

This class uses composition with RubyLLM::Chat:

  • RubyLLM::Chat handles: LLM API, messages, tools, concurrent execution
  • SwarmSDK::Agent::Chat adds: hooks, reminders, semaphores, event enrichment

ChatHelpers Module Architecture

Chat is decomposed into 8 focused helper modules to manage complexity:

Core Functionality

  • EventEmitter: Multi-subscriber event callbacks for tool/lifecycle events. Provides subscribe, emit_event, clear_subscribers for observable behavior.
  • LoggingHelpers: Formatting tool call information for structured JSON logs. Converts tool calls/results to loggable hashes with sanitization.
  • LlmConfiguration: Model selection, provider setup, and API configuration. Resolves provider from model, handles model aliases, builds connection config.
  • SystemReminders: Dynamic system message injection based on agent state. Collects reminders from plugins, context trackers, and other sources.

Cross-Cutting Concerns

  • Instrumentation: LLM API request/response logging via Faraday middleware. Wraps HTTP calls to capture timing, tokens, and error information.
  • HookIntegration: Pre/post tool execution callbacks and delegation hooks. Integrates with SwarmSDK Hooks::Registry for lifecycle events.
  • TokenTracking: Usage statistics and cost calculation per conversation. Accumulates input/output tokens across all LLM calls.

State Management

  • Serialization: Snapshot/restore for session persistence. Saves/restores message history, tool states, and agent context.

Module Dependencies

EventEmitter <-- HookIntegration (event emission for hooks)
TokenTracking <-- Instrumentation (usage data collection)
SystemReminders <-- uses ContextTracker instance (not a module)
LoggingHelpers <-- EventEmitter (log event formatting)

Design Rationale

This decomposition follows Single Responsibility Principle. Each module handles one concern. They access shared Chat internals (@llm_chat, @messages, etc.) which makes them tightly coupled to Chat, but this keeps the main Chat class focused on orchestration rather than implementation details. The modules are intentionally NOT standalone - they augment Chat with specific capabilities.

Rate Limiting Strategy

Two-level semaphore system prevents API quota exhaustion in hierarchical agent trees:

  1. Global semaphore - Serializes ask() calls across entire swarm
  2. Local semaphore - Limits concurrent tool calls per agent (via RubyLLM)

Event Flow

RubyLLM events → SwarmSDK subscribes → enriches with context → emits SwarmSDK events This allows hooks to fire on SwarmSDK events with full agent context.

Defined Under Namespace

Classes: SymbolKeyHash

Instance Attribute Summary collapse

Attributes included from SwarmSDK::Agent::ChatHelpers::HookIntegration

#hook_agent_hooks, #hook_executor, #hook_swarm

Instance Method Summary collapse

Methods included from SwarmSDK::Agent::ChatHelpers::Serialization

#conversation_snapshot, #restore_conversation

Methods included from SwarmSDK::Agent::ChatHelpers::TokenTracking

#compact_context, #context_limit, #context_usage_percentage, #cumulative_cache_creation_tokens, #cumulative_cached_tokens, #cumulative_input_cost, #cumulative_input_tokens, #cumulative_output_cost, #cumulative_output_tokens, #cumulative_total_cost, #cumulative_total_tokens, #effective_input_tokens, #tokens_remaining

Methods included from SwarmSDK::Agent::ChatHelpers::SystemReminders

#collect_plugin_reminders, #collect_system_reminders

Methods included from SwarmSDK::Agent::ChatHelpers::HookIntegration

#add_hook, #check_context_warnings, #setup_hooks, #trigger_post_tool_use, #trigger_pre_tool_use

Methods included from SwarmSDK::Agent::ChatHelpers::LoggingHelpers

#calculate_cost, #format_tool_calls, #serialize_result, #zero_cost

Methods included from SwarmSDK::Agent::ChatHelpers::EventEmitter

#callback_count, #clear_callbacks, #initialize_event_emitter, #on_end_message, #on_new_message, #on_tool_call, #on_tool_result, #once, #subscribe

Constructor Details

#initialize(definition:, agent_name: nil, global_semaphore: nil, **options) ⇒ Chat

Initialize AgentChat with RubyLLM::Chat wrapper

Parameters:

  • definition (Hash)

    Agent definition containing all configuration

  • agent_name (Symbol, nil) (defaults to: nil)

    Agent identifier (for plugin callbacks)

  • global_semaphore (Async::Semaphore, nil) (defaults to: nil)

    Shared across all agents

  • options (Hash)

    Additional options



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
# File 'lib/swarm_sdk/agent/chat.rb', line 115

def initialize(definition:, agent_name: nil, global_semaphore: nil, **options)
  # Initialize event emitter system
  initialize_event_emitter

  # Extract configuration from definition
  model_id = definition[:model]
  provider_name = definition[:provider]
  context_window = definition[:context_window]
  max_concurrent_tools = definition[:max_concurrent_tools]
  base_url = definition[:base_url]
  api_version = definition[:api_version]
  request_timeout = definition[:request_timeout] || SwarmSDK.config.agent_request_timeout
  assume_model_exists = definition[:assume_model_exists]
  system_prompt = definition[:system_prompt]
  parameters = definition[:parameters]
  custom_headers = definition[:headers]

  # Agent identifier (for plugin callbacks)
  @agent_name = agent_name

  # Turn timeout (external timeout for entire ask() call)
  @turn_timeout = definition[:turn_timeout]

  # Streaming configuration
  @streaming_enabled = definition[:streaming]
  @last_chunk_type = nil # Track chunk type transitions

  # Context manager for ephemeral messages
  @context_manager = ContextManager.new

  # Rate limiting
  @global_semaphore = global_semaphore
  @explicit_context_window = context_window

  # Serialize ask() calls to prevent message corruption
  @ask_semaphore = Async::Semaphore.new(1)

  # Track TodoWrite usage for periodic reminders
  @last_todowrite_message_index = nil

  # Agent context for logging (set via setup_context)
  @agent_context = nil

  # Context tracker (created after agent_context is set)
  @context_tracker = nil

  # Tool registry for lazy tool activation (Phase 3 - Plan 025)
  @tool_registry = Agent::ToolRegistry.new

  # Track loaded skill state (Phase 2 - Plan 025)
  @skill_state = nil

  # Tool activation dependencies (set by setup_tool_activation after initialization)
  @tool_configurator = nil
  @agent_definition = nil

  # Create internal RubyLLM::Chat instance
  @llm_chat = create_llm_chat(
    model_id: model_id,
    provider_name: provider_name,
    base_url: base_url,
    api_version: api_version,
    timeout: request_timeout,
    assume_model_exists: assume_model_exists,
    max_concurrent_tools: max_concurrent_tools,
  )

  # Extract provider from RubyLLM::Chat for instrumentation
  # Must be done after create_llm_chat since with_responses_api() may swap provider
  # NOTE: RubyLLM doesn't expose provider publicly, but we need it for Faraday middleware
  # rubocop:disable Security/NoReflectionMethods
  @provider = @llm_chat.instance_variable_get(:@provider)
  # rubocop:enable Security/NoReflectionMethods

  # Try to fetch real model info for accurate context tracking
  fetch_real_model_info(model_id)

  # Configure system prompt, parameters, headers, and thinking
  configure_system_prompt(system_prompt) if system_prompt
  configure_parameters(parameters)
  configure_headers(custom_headers)
  configure_thinking(definition[:thinking])

  # Setup around_tool_execution hook for SwarmSDK orchestration
  setup_tool_execution_hook

  # Setup around_llm_request hook for ephemeral message injection
  setup_llm_request_hook

  # Setup event bridging from RubyLLM to SwarmSDK
  setup_event_bridging
end

Instance Attribute Details

#active_skill_pathString?

Get active skill path (for backward compatibility)

Returns:

  • (String, nil)

    Path to loaded skill



484
485
486
# File 'lib/swarm_sdk/agent/chat.rb', line 484

def active_skill_path
  @skill_state&.file_path
end

#agent_contextObject (readonly)

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def agent_context
  @agent_context
end

#context_managerObject (readonly)

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def context_manager
  @context_manager
end

#context_trackerObject (readonly)

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def context_tracker
  @context_tracker
end

#global_semaphoreObject (readonly)

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def global_semaphore
  @global_semaphore
end

#last_todowrite_message_indexObject

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def last_todowrite_message_index
  @last_todowrite_message_index
end

#providerObject (readonly)

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def provider
  @provider
end

#real_model_infoObject (readonly)

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def real_model_info
  @real_model_info
end

#skill_stateObject (readonly)

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def skill_state
  @skill_state
end

#tool_registryObject (readonly)

SwarmSDK-specific accessors



96
97
98
# File 'lib/swarm_sdk/agent/chat.rb', line 96

def tool_registry
  @tool_registry
end

Instance Method Details

#activate_tools_for_promptvoid

This method returns an undefined value.

Activate tools for the current prompt (Plan 025: Lazy Tool Activation)

Called before each LLM request to set active toolset based on skill state. Replaces @llm_chat.tools with active subset from registry.

This is public so it can be called during initialization to populate tools.

Logic:

  • If no skill loaded: ALL tools from registry
  • If skill restricts tools: skill's tools + non-removable tools
  • Skill permissions applied during activation (wrapping base_instance)


509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/swarm_sdk/agent/chat.rb', line 509

def activate_tools_for_prompt
  # Get active tools based on skill state
  active = @tool_registry.active_tools(
    skill_state: @skill_state,
    tool_configurator: @tool_configurator,
    agent_definition: @agent_definition,
  )

  # Replace RubyLLM::Chat tools with active subset
  # CRITICAL: RubyLLM looks up tools by SYMBOL keys, must store with symbols!
  @llm_chat.tools.clear
  active.each { |name, instance| @llm_chat.tools[name.to_sym] = instance }
end

#add_ephemeral_reminder(reminder) ⇒ void

This method returns an undefined value.

Add an ephemeral reminder to the most recent message

The reminder will be sent to the LLM but not persisted in message history. This encapsulates the internal message array access.

Parameters:

  • reminder (String)

    Reminder content to add



354
355
356
# File 'lib/swarm_sdk/agent/chat.rb', line 354

def add_ephemeral_reminder(reminder)
  @context_manager&.add_ephemeral_reminder(reminder, messages_array: @llm_chat.messages)
end

#add_message(message_or_attributes) ⇒ RubyLLM::Message

Add a message to the conversation history

Automatically extracts and strips system reminders, tracking them as ephemeral.

Parameters:

  • message_or_attributes (RubyLLM::Message, Hash)

    Message object or attributes hash

Returns:

  • (RubyLLM::Message)

    The added message



561
562
563
564
565
566
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
# File 'lib/swarm_sdk/agent/chat.rb', line 561

def add_message(message_or_attributes)
  message = if message_or_attributes.is_a?(RubyLLM::Message)
    message_or_attributes
  else
    RubyLLM::Message.new(message_or_attributes)
  end

  # Extract system reminders if present
  content_str = message.content.is_a?(RubyLLM::Content) ? message.content.text : message.content.to_s

  if @context_manager.has_system_reminders?(content_str)
    reminders = @context_manager.extract_system_reminders(content_str)
    clean_content_str = @context_manager.strip_system_reminders(content_str)

    clean_content = if message.content.is_a?(RubyLLM::Content)
      RubyLLM::Content.new(clean_content_str, message.content.attachments)
    else
      clean_content_str
    end

    clean_message = RubyLLM::Message.new(
      role: message.role,
      content: clean_content,
      tool_call_id: message.tool_call_id,
      tool_calls: message.tool_calls,
      model_id: message.model_id,
      input_tokens: message.input_tokens,
      output_tokens: message.output_tokens,
      cached_tokens: message.cached_tokens,
      cache_creation_tokens: message.cache_creation_tokens,
    )

    @llm_chat.add_message(clean_message)

    # Track reminders as ephemeral
    reminders.each do |reminder|
      @context_manager.add_ephemeral_reminder(reminder, messages_array: messages)
    end

    clean_message
  else
    @llm_chat.add_message(message)
  end
end

#add_tool(tool) ⇒ self

Add a tool to this chat

Parameters:

  • tool (Class, RubyLLM::Tool)

    Tool class or instance

Returns:

  • (self)

    for chaining



423
424
425
426
# File 'lib/swarm_sdk/agent/chat.rb', line 423

def add_tool(tool)
  @llm_chat.with_tool(tool)
  self
end

#ask(prompt, clear_context: false, **options) ⇒ RubyLLM::Message

Send a message to the LLM and get a response

This method:

  1. Serializes concurrent asks via @ask_semaphore
  2. Optionally clears conversation context (inside semaphore for safety)
  3. Adds CLEAN user message to history (no reminders)
  4. Injects system reminders as ephemeral content (sent to LLM but not stored)
  5. Triggers user_prompt hooks
  6. Acquires global semaphore for LLM call
  7. Delegates to RubyLLM::Chat for actual execution

Parameters:

  • prompt (String)

    User prompt

  • clear_context (Boolean) (defaults to: false)

    When true, clears conversation history before processing. Clearing happens inside the ask_semaphore, making it safe for concurrent callers (e.g., parallel delegations to the same agent).

  • options (Hash)

    Additional options (source: for hooks)

Returns:

  • (RubyLLM::Message)

    LLM response



542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/swarm_sdk/agent/chat.rb', line 542

def ask(prompt, clear_context: false, **options)
  @ask_semaphore.acquire do
    # Clear inside semaphore so concurrent callers don't corrupt each other's messages
    clear_conversation if clear_context

    if @turn_timeout
      execute_with_turn_timeout(prompt, options)
    else
      execute_ask(prompt, options)
    end
  end
end

#assistant_messagesArray<RubyLLM::Message>

Get all assistant messages

Returns:

  • (Array<RubyLLM::Message>)

    All assistant messages



316
317
318
# File 'lib/swarm_sdk/agent/chat.rb', line 316

def assistant_messages
  @llm_chat.messages.select { |msg| msg.role == :assistant }
end

#clear_conversationvoid

This method returns an undefined value.

Clear conversation history



491
492
493
494
# File 'lib/swarm_sdk/agent/chat.rb', line 491

def clear_conversation
  @llm_chat.reset_messages!
  @context_manager&.clear_ephemeral
end

#clear_skillvoid

This method returns an undefined value.

Clear loaded skill (return to all tools)



470
471
472
# File 'lib/swarm_sdk/agent/chat.rb', line 470

def clear_skill
  @skill_state = nil
end

#complete(**_options, &block) ⇒ RubyLLM::Message

Complete the current conversation (no additional prompt)

Delegates to RubyLLM::Chat#complete() which handles:

  • LLM API calls (with around_llm_request hook for ephemeral injection)
  • Tool execution (with around_tool_execution hook for SwarmSDK hooks)
  • Automatic tool loop (continues until no more tool calls)

SwarmSDK adds:

  • Semaphore rate limiting (ask + global)
  • Finish marker handling (finish_agent, finish_swarm)

Parameters:

  • options (Hash)

    Additional options (currently unused, for future compatibility)

  • block (Proc)

    Optional streaming block

Returns:

  • (RubyLLM::Message)

    LLM response



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/swarm_sdk/agent/chat.rb', line 442

def complete(**_options, &block)
  @ask_semaphore.acquire do
    execute_with_global_semaphore do
      result = catch(:finish_agent) do
        catch(:finish_swarm) do
          # Delegate to RubyLLM::Chat#complete()
          # Hooks handle ephemeral injection and tool orchestration
          @llm_chat.complete(&block)
        end
      end

      # Handle finish markers thrown by hooks
      handle_finish_marker(result)
    end
  end
end

#configure_system_prompt(prompt, replace: false) ⇒ self

Configure system prompt for the conversation

Parameters:

  • prompt (String)

    System prompt

  • replace (Boolean) (defaults to: false)

    Replace existing system messages if true

Returns:

  • (self)

    for chaining



414
415
416
417
# File 'lib/swarm_sdk/agent/chat.rb', line 414

def configure_system_prompt(prompt, replace: false)
  @llm_chat.with_instructions(prompt, replace: replace)
  self
end

#emit_model_lookup_warning(agent_name) ⇒ Object

Emit model lookup warning if one occurred during initialization

Parameters:

  • agent_name (Symbol, String)

    The agent name for logging context



393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/swarm_sdk/agent/chat.rb', line 393

def emit_model_lookup_warning(agent_name)
  return unless @model_lookup_error

  LogStream.emit(
    type: "model_lookup_warning",
    agent: agent_name,
    swarm_id: @agent_context&.swarm_id,
    parent_swarm_id: @agent_context&.parent_swarm_id,
    model: @model_lookup_error[:model],
    error_message: @model_lookup_error[:error_message],
    suggestions: @model_lookup_error[:suggestions].map { |s| { id: s.id, name: s.name, context_window: s.context_window } },
  )
end

#find_last_message {|msg| ... } ⇒ RubyLLM::Message?

Find the last message matching a condition

Yields:

  • (msg)

    Block to test each message

Returns:

  • (RubyLLM::Message, nil)

    Last matching message or nil



324
325
326
# File 'lib/swarm_sdk/agent/chat.rb', line 324

def find_last_message(&block)
  @llm_chat.messages.reverse.find(&block)
end

#find_last_message_index {|msg| ... } ⇒ Integer?

Find the index of last message matching a condition

Yields:

  • (msg)

    Block to test each message

Returns:

  • (Integer, nil)

    Index of last matching message or nil



332
333
334
# File 'lib/swarm_sdk/agent/chat.rb', line 332

def find_last_message_index(&block)
  @llm_chat.messages.rindex(&block)
end

#has_tool?(name) ⇒ Boolean

Tool introspection

Returns:

  • (Boolean)


232
233
234
# File 'lib/swarm_sdk/agent/chat.rb', line 232

def has_tool?(name)
  @llm_chat.tools.key?(name.to_s) || @llm_chat.tools.key?(name.to_sym)
end

#has_user_message?Boolean

Returns:

  • (Boolean)


282
283
284
# File 'lib/swarm_sdk/agent/chat.rb', line 282

def has_user_message?
  @llm_chat.messages.any? { |msg| msg.role == :user }
end

#last_assistant_messageObject



286
287
288
# File 'lib/swarm_sdk/agent/chat.rb', line 286

def last_assistant_message
  @llm_chat.messages.reverse.find { |msg| msg.role == :assistant }
end

#load_skill_state(state) ⇒ void

This method returns an undefined value.

Load skill state (called by LoadSkill tool)

Parameters:

  • state (Object, nil)

    Skill state object (from SwarmMemory), or nil to clear



463
464
465
# File 'lib/swarm_sdk/agent/chat.rb', line 463

def load_skill_state(state)
  @skill_state = state
end

#message_countObject

Message introspection



278
279
280
# File 'lib/swarm_sdk/agent/chat.rb', line 278

def message_count
  @llm_chat.messages.size
end

#messagesArray<RubyLLM::Message>

Read-only access to conversation messages

Returns a copy of the message array for safe enumeration. External code should use this instead of internal_messages.

Returns:

  • (Array<RubyLLM::Message>)

    Copy of message array



296
297
298
# File 'lib/swarm_sdk/agent/chat.rb', line 296

def messages
  @llm_chat.messages.dup
end

#model_context_windowObject



227
228
229
# File 'lib/swarm_sdk/agent/chat.rb', line 227

def model_context_window
  @real_model_info&.context_window || @llm_chat.model.context_window
end

#model_idObject

Model information



219
220
221
# File 'lib/swarm_sdk/agent/chat.rb', line 219

def model_id
  @llm_chat.model.id
end

#model_providerObject



223
224
225
# File 'lib/swarm_sdk/agent/chat.rb', line 223

def model_provider
  @llm_chat.model.provider
end

#non_delegation_tool_namesArray<String>

Get tool names that are NOT delegation tools

Returns:

  • (Array<String>)

    Non-delegation tool names



339
340
341
342
343
344
345
# File 'lib/swarm_sdk/agent/chat.rb', line 339

def non_delegation_tool_names
  if @agent_context
    @llm_chat.tools.keys.reject { |name| @agent_context.delegation_tool?(name.to_s) }
  else
    @llm_chat.tools.keys
  end
end

#remove_tool(name) ⇒ Object



244
245
246
# File 'lib/swarm_sdk/agent/chat.rb', line 244

def remove_tool(name)
  @llm_chat.tools.delete(name.to_s) || @llm_chat.tools.delete(name.to_sym)
end

#replace_messages(new_messages) ⇒ self

Atomically replace all conversation messages

Used for context compaction and state restoration. This is the safe way to manipulate messages from external code.

Parameters:

  • new_messages (Array<RubyLLM::Message>)

    New message array

Returns:

  • (self)

    for chaining



307
308
309
310
311
# File 'lib/swarm_sdk/agent/chat.rb', line 307

def replace_messages(new_messages)
  @llm_chat.messages.clear
  new_messages.each { |msg| @llm_chat.messages << msg }
  self
end

#setup_context(context) ⇒ Object

Setup agent context

Parameters:



363
364
365
366
# File 'lib/swarm_sdk/agent/chat.rb', line 363

def setup_context(context)
  @agent_context = context
  @context_tracker = ChatHelpers::ContextTracker.new(self, context)
end

#setup_loggingvoid

This method returns an undefined value.

Setup logging callbacks

Raises:



371
372
373
374
375
376
# File 'lib/swarm_sdk/agent/chat.rb', line 371

def setup_logging
  raise StateError, "Agent context not set. Call setup_context first." unless @agent_context

  @context_tracker.setup_logging
  inject_llm_instrumentation
end

#setup_tool_activation(tool_configurator:, agent_definition:) ⇒ void

This method returns an undefined value.

Setup tool activation dependencies (Plan 025)

Must be called after tool registration to enable permission wrapping during activation.

Parameters:

  • tool_configurator (ToolConfigurator)

    Tool configuration helper

  • agent_definition (Agent::Definition)

    Agent definition object



385
386
387
388
# File 'lib/swarm_sdk/agent/chat.rb', line 385

def setup_tool_activation(tool_configurator:, agent_definition:)
  @tool_configurator = tool_configurator
  @agent_definition = agent_definition
end

#skill_loaded?Boolean

Check if a skill is currently loaded

Returns:

  • (Boolean)

    True if a skill has been loaded



477
478
479
# File 'lib/swarm_sdk/agent/chat.rb', line 477

def skill_loaded?
  !@skill_state.nil?
end

#streaming_enabled?Boolean

Check if streaming is enabled for this agent

Returns:

  • (Boolean)

    true if streaming is enabled



214
215
216
# File 'lib/swarm_sdk/agent/chat.rb', line 214

def streaming_enabled?
  @streaming_enabled
end

#tool_countObject



240
241
242
# File 'lib/swarm_sdk/agent/chat.rb', line 240

def tool_count
  @llm_chat.tools.size
end

#tool_namesObject



236
237
238
# File 'lib/swarm_sdk/agent/chat.rb', line 236

def tool_names
  @llm_chat.tools.values.map(&:name).sort
end

#toolsHash

Direct access to tools hash for advanced operations

Use with caution - prefer has_tool?, tool_names, remove_tool for most cases. This is provided for:

  • Direct tool execution in tests
  • Advanced tool manipulation

Returns a hash wrapper that supports both string and symbol keys for test convenience.

Returns:

  • (Hash)

    Tool name to tool instance mapping (supports symbol and string keys)



258
259
260
261
# File 'lib/swarm_sdk/agent/chat.rb', line 258

def tools
  # Return a fresh wrapper each time (since @llm_chat.tools may change)
  SymbolKeyHash.new(@llm_chat.tools)
end