swarm_sdk 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
  3. data/lib/swarm_sdk/agent/builder.rb +16 -42
  4. data/lib/swarm_sdk/agent/chat/context_tracker.rb +43 -0
  5. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +41 -3
  6. data/lib/swarm_sdk/agent/chat.rb +426 -61
  7. data/lib/swarm_sdk/agent/context.rb +5 -1
  8. data/lib/swarm_sdk/agent/context_manager.rb +309 -0
  9. data/lib/swarm_sdk/agent/definition.rb +57 -24
  10. data/lib/swarm_sdk/plugin.rb +147 -0
  11. data/lib/swarm_sdk/plugin_registry.rb +101 -0
  12. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +7 -1
  13. data/lib/swarm_sdk/swarm/agent_initializer.rb +80 -12
  14. data/lib/swarm_sdk/swarm/tool_configurator.rb +116 -44
  15. data/lib/swarm_sdk/swarm.rb +44 -8
  16. data/lib/swarm_sdk/tools/clock.rb +44 -0
  17. data/lib/swarm_sdk/tools/grep.rb +16 -19
  18. data/lib/swarm_sdk/tools/registry.rb +23 -12
  19. data/lib/swarm_sdk/tools/todo_write.rb +1 -1
  20. data/lib/swarm_sdk/version.rb +1 -1
  21. data/lib/swarm_sdk.rb +4 -0
  22. metadata +7 -12
  23. data/lib/swarm_sdk/prompts/memory.md.erb +0 -480
  24. data/lib/swarm_sdk/tools/memory/memory_delete.rb +0 -64
  25. data/lib/swarm_sdk/tools/memory/memory_edit.rb +0 -145
  26. data/lib/swarm_sdk/tools/memory/memory_glob.rb +0 -94
  27. data/lib/swarm_sdk/tools/memory/memory_grep.rb +0 -147
  28. data/lib/swarm_sdk/tools/memory/memory_multi_edit.rb +0 -228
  29. data/lib/swarm_sdk/tools/memory/memory_read.rb +0 -82
  30. data/lib/swarm_sdk/tools/memory/memory_write.rb +0 -90
  31. data/lib/swarm_sdk/tools/stores/memory_storage.rb +0 -300
  32. data/lib/swarm_sdk/tools/stores/storage_read_tracker.rb +0 -61
@@ -15,14 +15,14 @@ module SwarmSDK
15
15
  # embedded in Swarm#initialize_agents.
16
16
  class AgentInitializer
17
17
  # rubocop:disable Metrics/ParameterLists
18
- def initialize(swarm, agent_definitions, global_semaphore, hook_registry, scratchpad_storage, memory_storages, config_for_hooks: nil)
18
+ def initialize(swarm, agent_definitions, global_semaphore, hook_registry, scratchpad_storage, plugin_storages, config_for_hooks: nil)
19
19
  # rubocop:enable Metrics/ParameterLists
20
20
  @swarm = swarm
21
21
  @agent_definitions = agent_definitions
22
22
  @global_semaphore = global_semaphore
23
23
  @hook_registry = hook_registry
24
24
  @scratchpad_storage = scratchpad_storage
25
- @memory_storages = memory_storages
25
+ @plugin_storages = plugin_storages
26
26
  @config_for_hooks = config_for_hooks
27
27
  @agents = {}
28
28
  @agent_contexts = {}
@@ -80,21 +80,17 @@ module SwarmSDK
80
80
  # This creates the Agent::Chat instances but doesn't wire them together yet.
81
81
  # Each agent gets its own chat instance with configured tools.
82
82
  def pass_1_create_agents
83
- # Create memory storage for agents that have memory configured
84
- @agent_definitions.each do |agent_name, agent_definition|
85
- next unless agent_definition.memory_enabled?
86
-
87
- # Use configured directory or default
88
- memory_dir = agent_definition.memory.directory
89
- memory_path = File.join(memory_dir, "memory.json")
90
- @memory_storages[agent_name] = Tools::Stores::MemoryStorage.new(persist_to: memory_path)
91
- end
83
+ # Create plugin storages for agents
84
+ create_plugin_storages
92
85
 
93
- tool_configurator = ToolConfigurator.new(@swarm, @scratchpad_storage, @memory_storages)
86
+ tool_configurator = ToolConfigurator.new(@swarm, @scratchpad_storage, @plugin_storages)
94
87
 
95
88
  @agent_definitions.each do |name, agent_definition|
96
89
  chat = create_agent_chat(name, agent_definition, tool_configurator)
97
90
  @agents[name] = chat
91
+
92
+ # Notify plugins that agent was initialized
93
+ notify_plugins_agent_initialized(name, chat, agent_definition, tool_configurator)
98
94
  end
99
95
  end
100
96
 
@@ -207,6 +203,7 @@ module SwarmSDK
207
203
  def create_agent_chat(agent_name, agent_definition, tool_configurator)
208
204
  chat = Agent::Chat.new(
209
205
  definition: agent_definition.to_h,
206
+ agent_name: agent_name,
210
207
  global_semaphore: @global_semaphore,
211
208
  )
212
209
 
@@ -261,6 +258,77 @@ module SwarmSDK
261
258
  chat.with_tool(tool)
262
259
  end
263
260
  end
261
+
262
+ # Create plugin storages for all agents
263
+ #
264
+ # Iterates through all registered plugins and asks each to create
265
+ # storage for agents that need it.
266
+ #
267
+ # @return [void]
268
+ def create_plugin_storages
269
+ PluginRegistry.all.each do |plugin|
270
+ @agent_definitions.each do |agent_name, agent_definition|
271
+ # Check if this plugin needs storage for this agent
272
+ next unless plugin.storage_enabled?(agent_definition)
273
+
274
+ # Get plugin config for this agent
275
+ config = get_plugin_config(agent_definition, plugin.name)
276
+ next unless config
277
+
278
+ # Parse config through plugin
279
+ parsed_config = plugin.parse_config(config)
280
+
281
+ # Create plugin storage
282
+ storage = plugin.create_storage(agent_name: agent_name, config: parsed_config)
283
+
284
+ # Store in plugin_storages: { plugin_name => { agent_name => storage } }
285
+ @plugin_storages[plugin.name] ||= {}
286
+ @plugin_storages[plugin.name][agent_name] = storage
287
+ end
288
+ end
289
+ end
290
+
291
+ # Get plugin-specific config from agent definition
292
+ #
293
+ # Plugins can store their config in agent definition under their plugin name.
294
+ # E.g., memory plugin looks for `agent_definition.memory`
295
+ #
296
+ # @param agent_definition [Agent::Definition] Agent definition
297
+ # @param plugin_name [Symbol] Plugin name
298
+ # @return [Object, nil] Plugin config or nil
299
+ def get_plugin_config(agent_definition, plugin_name)
300
+ # Try to call method named after plugin (e.g., .memory for :memory plugin)
301
+ if agent_definition.respond_to?(plugin_name)
302
+ agent_definition.public_send(plugin_name)
303
+ end
304
+ end
305
+
306
+ # Notify all plugins that an agent was initialized
307
+ #
308
+ # Plugins can register additional tools, mark tools immutable, etc.
309
+ #
310
+ # @param agent_name [Symbol] Agent name
311
+ # @param chat [Agent::Chat] Chat instance
312
+ # @param agent_definition [Agent::Definition] Agent definition
313
+ # @param tool_configurator [ToolConfigurator] Tool configurator
314
+ # @return [void]
315
+ def notify_plugins_agent_initialized(agent_name, chat, agent_definition, tool_configurator)
316
+ PluginRegistry.all.each do |plugin|
317
+ # Get plugin storage for this agent (if any)
318
+ plugin_storages = @plugin_storages[plugin.name] || {}
319
+ storage = plugin_storages[agent_name]
320
+
321
+ # Build context for plugin
322
+ context = {
323
+ storage: storage,
324
+ agent_definition: agent_definition,
325
+ tool_configurator: tool_configurator,
326
+ }
327
+
328
+ # Notify plugin
329
+ plugin.on_agent_initialized(agent_name: agent_name, agent: chat, context: context)
330
+ end
331
+ end
264
332
  end
265
333
  end
266
334
  end
@@ -20,6 +20,7 @@ module SwarmSDK
20
20
  :TodoWrite,
21
21
  :Think,
22
22
  :WebFetch,
23
+ :Clock,
23
24
  ].freeze
24
25
 
25
26
  # Scratchpad tools (added if scratchpad is enabled)
@@ -29,21 +30,12 @@ module SwarmSDK
29
30
  :ScratchpadList,
30
31
  ].freeze
31
32
 
32
- # Memory tools (added if memory is configured for the agent)
33
- MEMORY_TOOLS = [
34
- :MemoryWrite,
35
- :MemoryRead,
36
- :MemoryEdit,
37
- :MemoryMultiEdit,
38
- :MemoryGlob,
39
- :MemoryGrep,
40
- :MemoryDelete,
41
- ].freeze
42
-
43
- def initialize(swarm, scratchpad_storage, memory_storages)
33
+ def initialize(swarm, scratchpad_storage, plugin_storages = {})
44
34
  @swarm = swarm
45
35
  @scratchpad_storage = scratchpad_storage
46
- @memory_storages = memory_storages
36
+ # Plugin storages: { plugin_name => { agent_name => storage } }
37
+ # e.g., { memory: { agent1: storage1, agent2: storage2 } }
38
+ @plugin_storages = plugin_storages
47
39
  end
48
40
 
49
41
  # Register all tools for an agent (both explicit and default)
@@ -60,16 +52,24 @@ module SwarmSDK
60
52
  #
61
53
  # File tools and TodoWrite require agent context for tracking state.
62
54
  # Scratchpad tools require shared scratchpad instance.
55
+ # Plugin tools are delegated to their respective plugins.
63
56
  #
64
57
  # This method is public for testing delegation from Swarm.
65
58
  #
66
59
  # @param tool_name [Symbol, String] Tool name
67
60
  # @param agent_name [Symbol] Agent name for context
68
61
  # @param directory [String] Agent's working directory
62
+ # @param chat [Agent::Chat, nil] Optional chat instance for tools that need it
63
+ # @param agent_definition [Agent::Definition, nil] Optional agent definition
69
64
  # @return [RubyLLM::Tool] Tool instance
70
- def create_tool_instance(tool_name, agent_name, directory)
65
+ def create_tool_instance(tool_name, agent_name, directory, chat: nil, agent_definition: nil)
71
66
  tool_name_sym = tool_name.to_sym
72
67
 
68
+ # Check if tool is provided by a plugin
69
+ if PluginRegistry.plugin_tool?(tool_name_sym)
70
+ return create_plugin_tool(tool_name_sym, agent_name, directory, chat, agent_definition)
71
+ end
72
+
73
73
  case tool_name_sym
74
74
  when :Read
75
75
  Tools::Read.new(agent_name: agent_name, directory: directory)
@@ -93,27 +93,22 @@ module SwarmSDK
93
93
  Tools::Scratchpad::ScratchpadRead.create_for_scratchpad(@scratchpad_storage)
94
94
  when :ScratchpadList
95
95
  Tools::Scratchpad::ScratchpadList.create_for_scratchpad(@scratchpad_storage)
96
- when :MemoryWrite
97
- Tools::Memory::MemoryWrite.create_for_memory(@memory_storages[agent_name])
98
- when :MemoryRead
99
- Tools::Memory::MemoryRead.create_for_memory(@memory_storages[agent_name], agent_name)
100
- when :MemoryEdit
101
- Tools::Memory::MemoryEdit.create_for_memory(@memory_storages[agent_name], agent_name)
102
- when :MemoryMultiEdit
103
- Tools::Memory::MemoryMultiEdit.create_for_memory(@memory_storages[agent_name], agent_name)
104
- when :MemoryDelete
105
- Tools::Memory::MemoryDelete.create_for_memory(@memory_storages[agent_name])
106
- when :MemoryGlob
107
- Tools::Memory::MemoryGlob.create_for_memory(@memory_storages[agent_name])
108
- when :MemoryGrep
109
- Tools::Memory::MemoryGrep.create_for_memory(@memory_storages[agent_name])
110
96
  when :Think
111
97
  Tools::Think.new
98
+ when :Clock
99
+ Tools::Clock.new
112
100
  else
113
101
  # Regular tools - get class from registry and instantiate
114
102
  tool_class = Tools::Registry.get(tool_name_sym)
115
103
  raise ConfigurationError, "Unknown tool: #{tool_name}" unless tool_class
116
104
 
105
+ # Check if tool is marked as :special but not handled in case statement
106
+ if tool_class == :special
107
+ raise ConfigurationError,
108
+ "Tool '#{tool_name}' requires special initialization but is not handled in create_tool_instance. " \
109
+ "This is a bug - #{tool_name} should be added to the case statement above."
110
+ end
111
+
117
112
  tool_class.new
118
113
  end
119
114
  end
@@ -169,34 +164,33 @@ module SwarmSDK
169
164
 
170
165
  # Register default tools for agents (unless disabled)
171
166
  #
167
+ # Note: Memory tools are registered separately and are NOT affected by
168
+ # disable_default_tools, since they're configured via memory {} block.
169
+ #
172
170
  # @param chat [AgentChat] The chat instance
173
171
  # @param agent_name [Symbol] Agent name
174
172
  # @param agent_definition [AgentDefinition] Agent definition
175
173
  def register_default_tools(chat, agent_name:, agent_definition:)
176
- # If disable_default_tools is true, skip all default tools
177
- return if agent_definition.disable_default_tools == true
178
-
179
174
  # Get explicit tool names to avoid duplicates
180
175
  explicit_tool_names = agent_definition.tools.map { |t| t[:name] }.to_set
181
176
 
182
- # Register core default tools
183
- DEFAULT_TOOLS.each do |tool_name|
184
- register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
185
- end
186
-
187
- # Register scratchpad tools if enabled
188
- if @swarm.scratchpad_enabled?
189
- SCRATCHPAD_TOOLS.each do |tool_name|
177
+ # Register core default tools (unless disabled)
178
+ if agent_definition.disable_default_tools != true
179
+ DEFAULT_TOOLS.each do |tool_name|
190
180
  register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
191
181
  end
192
- end
193
182
 
194
- # Register memory tools if configured for this agent
195
- if agent_definition.memory_enabled?
196
- MEMORY_TOOLS.each do |tool_name|
197
- register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
183
+ # Register scratchpad tools if enabled
184
+ if @swarm.scratchpad_enabled?
185
+ SCRATCHPAD_TOOLS.each do |tool_name|
186
+ register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
187
+ end
198
188
  end
199
189
  end
190
+
191
+ # Register plugin tools if plugin storage is enabled for this agent
192
+ # Plugin tools ARE affected by disable_default_tools (allows fine-grained control)
193
+ register_plugin_tools(chat, agent_name, agent_definition, explicit_tool_names)
200
194
  end
201
195
 
202
196
  # Register a tool if not already explicit or disabled
@@ -223,6 +217,84 @@ module SwarmSDK
223
217
  chat.with_tool(tool_instance)
224
218
  end
225
219
 
220
+ # Create a tool instance via plugin
221
+ #
222
+ # @param tool_name [Symbol] Tool name
223
+ # @param agent_name [Symbol] Agent name
224
+ # @param directory [String] Working directory
225
+ # @param chat [Agent::Chat, nil] Chat instance
226
+ # @param agent_definition [Agent::Definition, nil] Agent definition
227
+ # @return [RubyLLM::Tool] Tool instance
228
+ def create_plugin_tool(tool_name, agent_name, directory, chat, agent_definition)
229
+ plugin = PluginRegistry.plugin_for_tool(tool_name)
230
+ raise ConfigurationError, "Tool #{tool_name} is not provided by any plugin" unless plugin
231
+
232
+ # Get plugin storage for this agent
233
+ plugin_storages = @plugin_storages[plugin.name] || {}
234
+ storage = plugin_storages[agent_name]
235
+
236
+ # Build context for tool creation
237
+ context = {
238
+ agent_name: agent_name,
239
+ directory: directory,
240
+ storage: storage,
241
+ agent_definition: agent_definition,
242
+ chat: chat,
243
+ tool_configurator: self,
244
+ }
245
+
246
+ plugin.create_tool(tool_name, context)
247
+ end
248
+
249
+ # Register plugin-provided tools for an agent
250
+ #
251
+ # Asks all plugins if they have tools to register for this agent.
252
+ #
253
+ # @param chat [Agent::Chat] Chat instance
254
+ # @param agent_name [Symbol] Agent name
255
+ # @param agent_definition [Agent::Definition] Agent definition
256
+ # @param explicit_tool_names [Set<Symbol>] Already-registered tool names
257
+ def register_plugin_tools(chat, agent_name, agent_definition, explicit_tool_names)
258
+ PluginRegistry.all.each do |plugin|
259
+ # Check if plugin has storage enabled for this agent
260
+ next unless plugin.storage_enabled?(agent_definition)
261
+
262
+ # Get plugin storage for this agent
263
+ plugin_storages = @plugin_storages[plugin.name] || {}
264
+ plugin_storages[agent_name]
265
+
266
+ # Register each tool provided by the plugin
267
+ plugin.tools.each do |tool_name|
268
+ # Skip if already registered explicitly
269
+ next if explicit_tool_names.include?(tool_name)
270
+
271
+ # Skip if tool is disabled via disable_default_tools
272
+ next if tool_disabled?(tool_name, agent_definition.disable_default_tools)
273
+
274
+ tool_instance = create_tool_instance(
275
+ tool_name,
276
+ agent_name,
277
+ agent_definition.directory,
278
+ chat: chat,
279
+ agent_definition: agent_definition,
280
+ )
281
+
282
+ # Resolve permissions for plugin tool
283
+ permissions_config = agent_definition.agent_permissions[tool_name] ||
284
+ agent_definition.default_permissions[tool_name]
285
+
286
+ # Wrap with permissions validator if configured
287
+ tool_instance = wrap_tool_with_permissions(
288
+ tool_instance,
289
+ permissions_config,
290
+ agent_definition,
291
+ )
292
+
293
+ chat.with_tool(tool_instance)
294
+ end
295
+ end
296
+ end
297
+
226
298
  # Check if a tool should be disabled based on disable_default_tools config
227
299
  #
228
300
  # @param tool_name [Symbol] Tool name to check
@@ -151,9 +151,10 @@ module SwarmSDK
151
151
  # Use provided scratchpad storage (for testing) or create volatile one
152
152
  @scratchpad_storage = scratchpad || Tools::Stores::ScratchpadStorage.new
153
153
 
154
- # Per-agent memory storage (persistent)
154
+ # Per-agent plugin storages (persistent)
155
+ # Format: { plugin_name => { agent_name => storage } }
155
156
  # Will be populated when agents are initialized
156
- @memory_storages = {}
157
+ @plugin_storages = {}
157
158
 
158
159
  # Hook registry for named hooks and swarm defaults
159
160
  @hook_registry = Hooks::Registry.new
@@ -551,7 +552,7 @@ module SwarmSDK
551
552
  @global_semaphore,
552
553
  @hook_registry,
553
554
  @scratchpad_storage,
554
- @memory_storages,
555
+ @plugin_storages,
555
556
  config_for_hooks: @config_for_hooks,
556
557
  )
557
558
 
@@ -571,6 +572,18 @@ module SwarmSDK
571
572
  @agents.each do |agent_name, chat|
572
573
  agent_def = @agent_definitions[agent_name]
573
574
 
575
+ # Build plugin storage info for logging
576
+ plugin_storage_info = {}
577
+ @plugin_storages.each do |plugin_name, agent_storages|
578
+ next unless agent_storages.key?(agent_name)
579
+
580
+ plugin_storage_info[plugin_name] = {
581
+ enabled: true,
582
+ # Get additional info from agent definition if available
583
+ config: agent_def.respond_to?(plugin_name) ? extract_plugin_config_info(agent_def.public_send(plugin_name)) : nil,
584
+ }
585
+ end
586
+
574
587
  LogStream.emit(
575
588
  type: "agent_start",
576
589
  agent: agent_name,
@@ -581,8 +594,7 @@ module SwarmSDK
581
594
  system_prompt: agent_def.system_prompt,
582
595
  tools: chat.tools.keys,
583
596
  delegates_to: agent_def.delegates_to,
584
- memory_enabled: agent_def.memory_enabled?,
585
- memory_directory: agent_def.memory_enabled? ? agent_def.memory.directory : nil,
597
+ plugin_storages: plugin_storage_info,
586
598
  timestamp: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
587
599
  )
588
600
  end
@@ -620,12 +632,12 @@ module SwarmSDK
620
632
 
621
633
  # Create a tool instance (delegates to ToolConfigurator)
622
634
  def create_tool_instance(tool_name, agent_name, directory)
623
- ToolConfigurator.new(self, @scratchpad_storage, @memory_storages).create_tool_instance(tool_name, agent_name, directory)
635
+ ToolConfigurator.new(self, @scratchpad_storage, @plugin_storages).create_tool_instance(tool_name, agent_name, directory)
624
636
  end
625
637
 
626
638
  # Wrap tool with permissions (delegates to ToolConfigurator)
627
639
  def wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
628
- ToolConfigurator.new(self, @scratchpad_storage, @memory_storages).wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
640
+ ToolConfigurator.new(self, @scratchpad_storage, @plugin_storages).wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
629
641
  end
630
642
 
631
643
  # Build MCP transport config (delegates to McpConfigurator)
@@ -635,10 +647,34 @@ module SwarmSDK
635
647
 
636
648
  # Create delegation tool (delegates to AgentInitializer)
637
649
  def create_delegation_tool(name:, description:, delegate_chat:, agent_name:)
638
- AgentInitializer.new(self, @agent_definitions, @global_semaphore, @hook_registry, @scratchpad_storage, @memory_storages)
650
+ AgentInitializer.new(self, @agent_definitions, @global_semaphore, @hook_registry, @scratchpad_storage, @plugin_storages)
639
651
  .create_delegation_tool(name: name, description: description, delegate_chat: delegate_chat, agent_name: agent_name)
640
652
  end
641
653
 
654
+ # Extract loggable info from plugin config
655
+ #
656
+ # Attempts to extract useful information from plugin configuration
657
+ # for logging purposes. Handles MemoryConfig, Hashes, and other objects.
658
+ #
659
+ # @param config [Object] Plugin configuration object
660
+ # @return [Hash, nil] Extracted config info or nil
661
+ def extract_plugin_config_info(config)
662
+ return if config.nil?
663
+
664
+ # Handle MemoryConfig object (has directory method)
665
+ if config.respond_to?(:directory)
666
+ return { directory: config.directory }
667
+ end
668
+
669
+ # Handle Hash
670
+ if config.is_a?(Hash)
671
+ return config.slice(:directory, "directory", :adapter, "adapter")
672
+ end
673
+
674
+ # Unknown config type
675
+ nil
676
+ end
677
+
642
678
  # Register default logging hooks that emit LogStream events
643
679
  #
644
680
  # These hooks implement the standard SwarmSDK logging behavior.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ # Clock tool provides current date and time information
6
+ #
7
+ # Returns current temporal information in a consistent format.
8
+ # Agents use this when they need to know what day/time it is.
9
+ class Clock < RubyLLM::Tool
10
+ description <<~DESC
11
+ Get current date and time.
12
+
13
+ Returns:
14
+ - Current date (YYYY-MM-DD format)
15
+ - Current time (HH:MM:SS format)
16
+ - Day of week (Monday, Tuesday, etc.)
17
+ - ISO 8601 timestamp (full datetime)
18
+
19
+ Use this when you need to know what day it is, what time it is,
20
+ or to store temporal information (e.g., "As of 2025-10-20...").
21
+
22
+ No parameters needed - just call Clock() to get complete temporal information.
23
+ DESC
24
+
25
+ # No parameters needed
26
+
27
+ # Override name to return simple "Clock"
28
+ def name
29
+ "Clock"
30
+ end
31
+
32
+ def execute
33
+ now = Time.now
34
+
35
+ <<~RESULT.chomp
36
+ Current date: #{now.strftime("%Y-%m-%d")}
37
+ Current time: #{now.strftime("%H:%M:%S")}
38
+ Day of week: #{now.strftime("%A")}
39
+ ISO 8601: #{now.iso8601}
40
+ RESULT
41
+ end
42
+ end
43
+ end
44
+ end
@@ -46,15 +46,15 @@ module SwarmSDK
46
46
 
47
47
  param :type,
48
48
  type: "string",
49
- desc: "File type to search (rg --type). Common types: js, py, rust, go, java, etc.",
49
+ desc: "File type to search (rg --type). Common types: c, cpp, cs, csharp, css, dart, docker, dockercompose, elixir, erlang, go, graphql, haskell, html, java, js, json, kotlin, lua, make, markdown, md, php, py, python, ruby, rust, sass, scala, sh, sql, svelte, swift, tf, toml, ts, typescript, vim, vue, xml, yaml, zig",
50
50
  required: false
51
51
 
52
52
  param :output_mode,
53
53
  type: "string",
54
- desc: "Output mode: \"content\" shows matching lines (supports -A/-B/-C context, -n line numbers, head_limit), \"files_with_matches\" shows file paths (supports head_limit), \"count\" shows match counts (supports head_limit). Defaults to \"files_with_matches\".",
54
+ desc: "Output mode: \"content\" shows matching lines (supports context/line number options), \"files_with_matches\" shows file paths (default), \"count\" shows match counts. All modes support head_limit.",
55
55
  required: false
56
56
 
57
- param :"-i",
57
+ param :case_insensitive,
58
58
  type: "boolean",
59
59
  desc: "Case insensitive search (rg -i)",
60
60
  required: false
@@ -64,22 +64,22 @@ module SwarmSDK
64
64
  desc: "Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall)",
65
65
  required: false
66
66
 
67
- param :"-B",
67
+ param :context_before,
68
68
  type: "integer",
69
69
  desc: "Number of lines to show before each match (rg -B). Requires output_mode: \"content\", ignored otherwise.",
70
70
  required: false
71
71
 
72
- param :"-A",
72
+ param :context_after,
73
73
  type: "integer",
74
74
  desc: "Number of lines to show after each match (rg -A). Requires output_mode: \"content\", ignored otherwise.",
75
75
  required: false
76
76
 
77
- param :"-C",
77
+ param :context,
78
78
  type: "integer",
79
79
  desc: "Number of lines to show before and after each match (rg -C). Requires output_mode: \"content\", ignored otherwise.",
80
80
  required: false
81
81
 
82
- param :"-n",
82
+ param :show_line_numbers,
83
83
  type: "boolean",
84
84
  desc: "Show line numbers in output (rg -n). Requires output_mode: \"content\", ignored otherwise.",
85
85
  required: false
@@ -95,7 +95,13 @@ module SwarmSDK
95
95
  glob: nil,
96
96
  type: nil,
97
97
  output_mode: "files_with_matches",
98
- **options
98
+ case_insensitive: false,
99
+ multiline: false,
100
+ context_before: nil,
101
+ context_after: nil,
102
+ context: nil,
103
+ show_line_numbers: false,
104
+ head_limit: nil
99
105
  )
100
106
  # Validate inputs
101
107
  return validation_error("pattern is required") if pattern.nil? || pattern.empty?
@@ -108,15 +114,6 @@ module SwarmSDK
108
114
  resolve_path(path)
109
115
  end
110
116
 
111
- # Extract options with their flag names
112
- case_insensitive = options["-i"] || false
113
- multiline = options[:multiline] || false
114
- context_before = options["-B"]
115
- context_after = options["-A"]
116
- context = options["-C"]
117
- line_numbers = options["-n"] || false
118
- head_limit = options[:head_limit]
119
-
120
117
  # Validate output_mode
121
118
  valid_modes = ["content", "files_with_matches", "count"]
122
119
  unless valid_modes.include?(output_mode)
@@ -135,7 +132,7 @@ module SwarmSDK
135
132
  when "content"
136
133
  # Default mode, no special flag needed
137
134
  # Add line numbers if requested
138
- cmd << "-n" if line_numbers
135
+ cmd << "-n" if show_line_numbers
139
136
 
140
137
  # Add context flags
141
138
  cmd << "-B" << context_before.to_s if context_before
@@ -222,7 +219,7 @@ module SwarmSDK
222
219
  <system-reminder>
223
220
  You used output_mode: '#{output_mode}' which only shows #{output_mode == "files_with_matches" ? "file paths" : "match counts"}.
224
221
  To see the actual matching lines and their content, use output_mode: 'content'.
225
- You can also add -n: true and context lines (-B, -A, or -C) for better context.
222
+ You can also add show_line_numbers: true and context lines (context_before, context_after, or context) for better context.
226
223
  </system-reminder>
227
224
  REMINDER
228
225
  end
@@ -6,6 +6,9 @@ module SwarmSDK
6
6
  #
7
7
  # Maps tool names (symbols) to their RubyLLM::Tool classes.
8
8
  # Provides validation and lookup functionality for tool registration.
9
+ #
10
+ # Note: Plugin-provided tools (e.g., memory tools) are NOT in this registry.
11
+ # They are registered via SwarmSDK::PluginRegistry instead.
9
12
  class Registry
10
13
  # All available built-in tools
11
14
  BUILTIN_TOOLS = {
@@ -20,24 +23,22 @@ module SwarmSDK
20
23
  ScratchpadWrite: :special, # Requires scratchpad storage instance
21
24
  ScratchpadRead: :special, # Requires scratchpad storage instance
22
25
  ScratchpadList: :special, # Requires scratchpad storage instance
23
- MemoryWrite: :special, # Requires memory storage instance
24
- MemoryRead: :special, # Requires memory storage instance
25
- MemoryEdit: :special, # Requires memory storage instance
26
- MemoryMultiEdit: :special, # Requires memory storage instance
27
- MemoryDelete: :special, # Requires memory storage instance
28
- MemoryGlob: :special, # Requires memory storage instance
29
- MemoryGrep: :special, # Requires memory storage instance
30
26
  Think: SwarmSDK::Tools::Think,
31
27
  WebFetch: SwarmSDK::Tools::WebFetch,
28
+ Clock: SwarmSDK::Tools::Clock,
32
29
  }.freeze
33
30
 
34
31
  class << self
35
32
  # Get tool class by name
36
33
  #
34
+ # Note: Plugin-provided tools are NOT returned by this method.
35
+ # They are managed by SwarmSDK::PluginRegistry instead.
36
+ #
37
37
  # @param name [Symbol, String] Tool name
38
- # @return [Class, nil] Tool class or nil if not found
38
+ # @return [Class, Symbol, nil] Tool class, :special, or nil if not found
39
39
  def get(name)
40
- BUILTIN_TOOLS[name.to_sym]
40
+ name_sym = name.to_sym
41
+ BUILTIN_TOOLS[name_sym]
41
42
  end
42
43
 
43
44
  # Get multiple tool classes by names
@@ -48,7 +49,10 @@ module SwarmSDK
48
49
  def get_many(names)
49
50
  names.map do |name|
50
51
  tool_class = get(name)
51
- raise ConfigurationError, "Unknown tool: #{name}. Available tools: #{available_names.join(", ")}" unless tool_class
52
+ unless tool_class
53
+ raise ConfigurationError,
54
+ "Unknown tool: #{name}. Available tools: #{available_names.join(", ")}"
55
+ end
52
56
 
53
57
  tool_class
54
58
  end
@@ -56,13 +60,20 @@ module SwarmSDK
56
60
 
57
61
  # Check if a tool exists
58
62
  #
63
+ # Note: Only checks built-in tools. Plugin-provided tools are checked
64
+ # via SwarmSDK::PluginRegistry.plugin_tool?() instead.
65
+ #
59
66
  # @param name [Symbol, String] Tool name
60
67
  # @return [Boolean]
61
68
  def exists?(name)
62
- BUILTIN_TOOLS.key?(name.to_sym)
69
+ name_sym = name.to_sym
70
+ BUILTIN_TOOLS.key?(name_sym)
63
71
  end
64
72
 
65
- # Get all available tool names
73
+ # Get all available built-in tool names
74
+ #
75
+ # Note: Does NOT include plugin-provided tools. To get all available tools
76
+ # including plugins, combine with SwarmSDK::PluginRegistry.tools.
66
77
  #
67
78
  # @return [Array<Symbol>]
68
79
  def available_names