Class: SwarmSDK::Agent::ToolRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/swarm_sdk/agent/tool_registry.rb

Overview

Per-agent tool registry managing available and active tools

Architecture

  • Available tools: All tool instances the agent CAN use (registry)
  • Active tools: Subset sent to LLM based on skill state

Thread Safety

Registry access is protected by Async::Semaphore for fiber-safe operations.

Examples:

Registering tools

registry = ToolRegistry.new
registry.register(Read.new, source: :builtin)
registry.register(delegate_tool, source: :delegation, metadata: { delegate_name: :backend })

Getting active tools (no skill)

active = registry.active_tools(skill_state: nil)
# Returns ALL available tools

Getting active tools (with skill)

skill_state = SkillState.new(  # From SwarmMemory plugin
  file_path: "skill/audit.md",
  tools: ["Read", "Grep"],
  permissions: { "Bash" => { deny_commands: ["rm"] } }
)
active = registry.active_tools(skill_state: skill_state)
# Returns: skill's tools + non-removable tools

Defined Under Namespace

Classes: ToolEntry

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeToolRegistry

Returns a new instance of ToolRegistry.



43
44
45
46
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 43

def initialize
  @available_tools = {} # String name => ToolEntry
  @mutex = Async::Semaphore.new(1) # Fiber-safe mutex
end

Instance Attribute Details

#base_instanceObject (readonly)

Unwrapped tool instance (for skill permission override)



41
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 41

ToolEntry = Data.define(:instance, :base_instance, :removable, :source, :metadata)

#instanceObject (readonly)

Tool instance (possibly wrapped with permissions)



41
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 41

ToolEntry = Data.define(:instance, :base_instance, :removable, :source, :metadata)

#metadataObject (readonly)

Source-specific metadata



41
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 41

ToolEntry = Data.define(:instance, :base_instance, :removable, :source, :metadata)

#removableObject (readonly)

Can be deactivated by skills



41
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 41

ToolEntry = Data.define(:instance, :base_instance, :removable, :source, :metadata)

#sourceObject (readonly)

Tool source (:builtin, :delegation, :mcp, :plugin, :custom)



41
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 41

ToolEntry = Data.define(:instance, :base_instance, :removable, :source, :metadata)

Instance Method Details

#active_tools(skill_state: nil, tool_configurator: nil, agent_definition: nil) ⇒ Hash{String => RubyLLM::Tool}

Get active tools based on skill state

Returns Hash of tool instances ready for RubyLLM::Chat.

Logic:

  • If skill_state is nil: Return ALL available tools
  • If skill_state restricts tools: Return skill's tools + non-removable tools
  • Skill permissions are applied during activation (wrapping base_instance)

Examples:

No skill loaded - all tools

registry.active_tools(skill_state: nil)
# => { "Read" => <Read>, "WorkWithBackend" => <Delegate>, ... }

Skill loaded with focused toolset

registry.active_tools(skill_state: skill_state)
# => { "Read" => <Read>, "WorkWithBackend" => <Delegate>, "Think" => <Think>, "MemoryRead" => <MemoryRead> }
# Includes: requested tools + non-removable tools

Parameters:

  • skill_state (Object, nil) (defaults to: nil)

    Skill state object (from plugin), or nil for all

  • tool_configurator (ToolConfigurator, nil) (defaults to: nil)

    For permission wrapping

  • agent_definition (Agent::Definition, nil) (defaults to: nil)

    For permission wrapping

Returns:

  • (Hash{String => RubyLLM::Tool})

    name => instance mapping



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

def active_tools(skill_state: nil, tool_configurator: nil, agent_definition: nil)
  @mutex.acquire do
    result = if skill_state&.restricts_tools?
      # Skill loaded with tool restriction - only skill's tools + non-removable
      filtered = {}

      # Always include non-removable tools (use wrapped instance)
      @available_tools.each do |name, entry|
        filtered[name] = entry.instance unless entry.removable
      end

      # Add requested tools from skill
      skill_state.tools.each do |name|
        entry = @available_tools[name.to_s]
        next unless entry

        # Check if skill has custom permissions for this tool
        skill_permissions = skill_state.permissions_for(name)

        if skill_permissions && tool_configurator && agent_definition
          # Skill overrides permissions - wrap the BASE instance
          wrapped = tool_configurator.wrap_tool_with_permissions(
            entry.base_instance,
            skill_permissions,
            agent_definition,
          )
          filtered[name.to_s] = wrapped
        else
          # No skill permission override - use registered instance
          filtered[name.to_s] = entry.instance
        end
      end

      filtered
    else
      # No skill OR skill doesn't restrict tools - all available tools
      @available_tools.transform_values(&:instance)
    end

    result
  end
end

#get(name) ⇒ ToolEntry?

Get tool entry with metadata

Parameters:

  • name (String, Symbol)

    Tool name

Returns:



177
178
179
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 177

def get(name)
  @available_tools[name.to_s]
end

#has_tool?(name) ⇒ Boolean

Check if tool exists in registry

Parameters:

  • name (String, Symbol)

    Tool name

Returns:

  • (Boolean)


162
163
164
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 162

def has_tool?(name)
  @available_tools.key?(name.to_s)
end

#non_removable_tool_namesArray<String>

Get all non-removable tool names

Returns:

  • (Array<String>)


184
185
186
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 184

def non_removable_tool_names
  @available_tools.select { |_name, entry| !entry.removable }.keys
end

#register(tool, base_tool: nil, source:, metadata: {}) ⇒ void

This method returns an undefined value.

Register a tool in the available tools registry

Examples:

Register builtin tool

registry.register(Read.new, source: :builtin)

Register delegation tool

registry.register(delegate_tool, source: :delegation, metadata: { delegate_name: :backend })

Register MCP tool

registry.register(mcp_tool, source: :mcp, metadata: { server_name: "codebase" })

Register with permission wrapper

wrapped_tool = PermissionWrapper.new(base_tool, permissions)
registry.register(wrapped_tool, base_tool: base_tool, source: :builtin)

Parameters:

  • tool (RubyLLM::Tool)

    Tool instance (possibly wrapped)

  • base_tool (RubyLLM::Tool, nil) (defaults to: nil)

    Unwrapped instance (for permission override)

  • source (Symbol)

    Tool source (:builtin, :delegation, :mcp, :plugin, :custom)

  • metadata (Hash) (defaults to: {})

    Source-specific metadata (server_name, plugin_name, etc.)



68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 68

def register(tool, base_tool: nil, source:, metadata: {})
  @mutex.acquire do
    # Infer removability from tool class
    removable = tool.respond_to?(:removable?) ? tool.removable? : true

    @available_tools[tool.name] = ToolEntry.new(
      instance: tool,
      base_instance: base_tool || tool, # If no base, use same instance
      removable: removable,
      source: source,
      metadata: ,
    )
  end
end

#tool_namesArray<String>

Get all available tool names

Returns:

  • (Array<String>)


169
170
171
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 169

def tool_names
  @available_tools.keys
end

#unregister(name) ⇒ ToolEntry?

Unregister a tool (for testing/cleanup)

Parameters:

  • name (String, Symbol)

    Tool name

Returns:



87
88
89
90
91
# File 'lib/swarm_sdk/agent/tool_registry.rb', line 87

def unregister(name)
  @mutex.acquire do
    @available_tools.delete(name.to_s)
  end
end