swarm_memory 2.2.7 → 2.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab7cec394382b667f3b48cbe15e62112082eb7ff1a10ff46879ba54f973e9ff0
4
- data.tar.gz: 0cf05d7b5b2e78cbca336e5ac904a968f8698c9119508f9dfe168b1661afbcbc
3
+ metadata.gz: 30179963b0bf2e8b4558189e1a5719d6b206cc80a08d101b648e3d2e2fc253df
4
+ data.tar.gz: bdc60e7faa0e2360a6c3e8a236cd1b15a972d37a99cc18db808a4a208a484e7f
5
5
  SHA512:
6
- metadata.gz: f095a013ac1103d933613cd4e3b44f434b73415f05e9073fc43dda14304cc0d795fa5617375e820822a60ae3d92ff96f8b8a728d1e0e00d68badd819ed5224ab
7
- data.tar.gz: f0b59d40b498f9e741f62444084b3d2e9436e672b5573895cba7eec3edc5fb4aefa37901f98d3f90d1df0efd1723ddbe028dcbc955c9dcaae1cf0d566415c679
6
+ metadata.gz: 81b71bbf9fbd4ec14c03610f5dc7288ef4167686035e91bb934319ee6f2a973ed8998aad20303d8b97c16f802910e65988869f73b34681e0652b8afca657f95d
7
+ data.tar.gz: a6d4d533d2b19e92a6e0c89aa46e99ef667dccc0e6cc457ca9150fe0145b3aa4cf2ac5a79bc168e53e58549be54f8b1d2920efd1f7e22c768197b059adf3bae1
@@ -142,6 +142,7 @@ module SwarmMemory
142
142
  {
143
143
  adapter: @adapter_type,
144
144
  mode: @mode,
145
+ loadskill_preserve_delegation: @loadskill_preserve_delegation,
145
146
  **@adapter_options,
146
147
  }
147
148
  end
@@ -201,24 +201,6 @@ module SwarmMemory
201
201
  ERB.new(template_content).result(agent_definition.instance_eval { binding })
202
202
  end
203
203
 
204
- # Tools that should be marked immutable (mode-aware)
205
- #
206
- # Memory tools for the current mode plus LoadSkill (if applicable) are immutable.
207
- # This prevents LoadSkill from accidentally removing memory tools.
208
- #
209
- # @param mode [Symbol] Memory mode
210
- # @return [Array<Symbol>] Immutable tool names for this mode
211
- def immutable_tools_for_mode(mode)
212
- base_tools = tools_for_mode(mode)
213
-
214
- # LoadSkill only for read_write and full_access modes (not read_only)
215
- if mode == :read_only
216
- base_tools
217
- else
218
- base_tools + [:LoadSkill]
219
- end
220
- end
221
-
222
204
  # Check if memory is configured for this agent
223
205
  #
224
206
  # Delegates adapter-specific validation to the adapter itself.
@@ -332,7 +314,14 @@ module SwarmMemory
332
314
 
333
315
  # Pass through all custom adapter options
334
316
  # Handle both symbol and string keys (YAML may have either)
335
- standard_keys = [:directory, :adapter, :mode, "directory", "adapter", "mode"]
317
+ standard_keys = [
318
+ :directory,
319
+ :adapter,
320
+ :mode,
321
+ "directory",
322
+ "adapter",
323
+ "mode",
324
+ ]
336
325
  custom_keys = memory_config.keys - standard_keys
337
326
  custom_keys.each do |key|
338
327
  option(key.to_sym, memory_config[key]) # Normalize to symbol
@@ -379,17 +368,16 @@ module SwarmMemory
379
368
  @modes[base_name] = mode # ← Changed from agent_name to base_name
380
369
  @threshold_configs[base_name] = extract_threshold_config(memory_config)
381
370
 
382
- # Get mode-specific tools
383
- allowed_tools = tools_for_mode(mode)
384
-
385
- # Get all registered memory tool names
386
- all_memory_tools = tools # Returns all possible memory tools
371
+ # NOTE: Memory tools are already registered by ToolConfigurator.register_plugin_tools
372
+ # We need to unregister tools not allowed in this mode (Plan 025)
387
373
 
388
- # Remove tools not allowed in this mode
374
+ all_memory_tools = tools
375
+ allowed_tools = tools_for_mode(mode)
389
376
  tools_to_remove = all_memory_tools - allowed_tools
390
377
 
378
+ # Unregister tools not allowed in this mode
391
379
  tools_to_remove.each do |tool_name|
392
- agent.remove_tool(tool_name)
380
+ agent.tool_registry.unregister(tool_name.to_s)
393
381
  end
394
382
 
395
383
  # Create and register LoadSkill tool (NOT for read_only mode)
@@ -403,11 +391,15 @@ module SwarmMemory
403
391
  agent_definition: agent_definition,
404
392
  )
405
393
 
406
- agent.add_tool(load_skill_tool)
394
+ # Register in tool registry (Plan 025)
395
+ agent.tool_registry.register(
396
+ load_skill_tool,
397
+ source: :plugin,
398
+ metadata: { plugin_name: :memory, mode: mode },
399
+ )
407
400
  end
408
401
 
409
- # Mark mode-specific memory tools + LoadSkill as immutable
410
- agent.mark_tools_immutable(immutable_tools_for_mode(mode).map(&:to_s))
402
+ # NOTE: No need to mark tools immutable - they declare removable? themselves (Plan 025)
411
403
  end
412
404
 
413
405
  # Lifecycle: User message
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ # Immutable representation of a loaded skill's state
5
+ #
6
+ # This object encapsulates all skill-related state for clean management.
7
+ # Immutability prevents accidental mutation bugs during activation.
8
+ #
9
+ # @example Creating skill state
10
+ # state = SkillState.new(
11
+ # file_path: "skill/security/audit.md",
12
+ # tools: ["Read", "Grep", "WorkWithBackend"],
13
+ # permissions: { "Bash" => { deny_commands: ["rm"] } }
14
+ # )
15
+ #
16
+ # @example Checking if skill restricts tools
17
+ # state.restricts_tools? # => true (has tool list)
18
+ #
19
+ # @example Checking if tool is allowed
20
+ # state.allows_tool?("Read") # => true
21
+ # state.allows_tool?("Write") # => false
22
+ class SkillState
23
+ attr_reader :file_path, :tools, :permissions
24
+
25
+ # Create a new SkillState
26
+ #
27
+ # @param file_path [String] Path to the skill in memory
28
+ # @param tools [Array<String>, nil] Required tools (nil = no tool restriction)
29
+ # @param permissions [Hash] Tool permission overrides
30
+ #
31
+ # @example No tool restriction
32
+ # SkillState.new(file_path: "skill/debug.md") # All tools available
33
+ #
34
+ # @example Specific tools only
35
+ # SkillState.new(
36
+ # file_path: "skill/audit.md",
37
+ # tools: ["Read", "Grep"]
38
+ # )
39
+ #
40
+ # @example With permissions
41
+ # SkillState.new(
42
+ # file_path: "skill/safe.md",
43
+ # tools: ["Bash"],
44
+ # permissions: { "Bash" => { deny_commands: ["rm", "sudo"] } }
45
+ # )
46
+ def initialize(file_path:, tools: nil, permissions: {})
47
+ @file_path = file_path
48
+ @tools = tools&.map(&:to_s)&.freeze # Normalize and freeze
49
+ @permissions = permissions.freeze
50
+ freeze # Make entire object immutable
51
+ end
52
+
53
+ # Check if skill specifies a tool restriction
54
+ #
55
+ # Returns true if the skill has a NON-EMPTY tools array.
56
+ # Both nil and empty array mean "no restriction" (don't swap tools).
57
+ #
58
+ # @return [Boolean] True if skill restricts toolset
59
+ #
60
+ # @example No restriction (nil)
61
+ # state = SkillState.new(file_path: "skill/debug.md")
62
+ # state.restricts_tools? # => false (nil = no restriction)
63
+ #
64
+ # @example No restriction (empty array)
65
+ # state = SkillState.new(file_path: "skill/minimal.md", tools: [])
66
+ # state.restricts_tools? # => false (empty = no restriction)
67
+ #
68
+ # @example Restriction (specific tools)
69
+ # state = SkillState.new(file_path: "skill/audit.md", tools: ["Read"])
70
+ # state.restricts_tools? # => true (has specific tools)
71
+ def restricts_tools?
72
+ !@tools.nil? && !@tools.empty?
73
+ end
74
+
75
+ # Check if a specific tool is allowed by this skill
76
+ #
77
+ # If the skill has no tool restriction (tools is nil), all tools are allowed.
78
+ # If the skill has a tool list, only tools in that list are allowed.
79
+ #
80
+ # @param name [String, Symbol] Tool name
81
+ # @return [Boolean] True if tool is in skill's list (or no restriction)
82
+ #
83
+ # @example No restriction
84
+ # state = SkillState.new(file_path: "skill/debug.md")
85
+ # state.allows_tool?("AnyTool") # => true (no restriction)
86
+ #
87
+ # @example Restricted
88
+ # state = SkillState.new(file_path: "skill/audit.md", tools: ["Read"])
89
+ # state.allows_tool?("Read") # => true
90
+ # state.allows_tool?("Write") # => false
91
+ def allows_tool?(name)
92
+ @tools.nil? || @tools.include?(name.to_s)
93
+ end
94
+
95
+ # Get permission config for a tool
96
+ #
97
+ # Returns the permission configuration hash for a specific tool,
98
+ # or nil if no custom permissions are set.
99
+ #
100
+ # @param name [String, Symbol] Tool name
101
+ # @return [Hash, nil] Permission config or nil
102
+ #
103
+ # @example No custom permissions
104
+ # state = SkillState.new(file_path: "skill/debug.md")
105
+ # state.permissions_for("Bash") # => nil
106
+ #
107
+ # @example Custom permissions
108
+ # state = SkillState.new(
109
+ # file_path: "skill/safe.md",
110
+ # permissions: { "Bash" => { deny_commands: ["rm"] } }
111
+ # )
112
+ # state.permissions_for("Bash") # => { deny_commands: ["rm"] }
113
+ def permissions_for(name)
114
+ @permissions[name.to_s] || @permissions[name.to_sym]
115
+ end
116
+ end
117
+ end
@@ -13,7 +13,8 @@ module SwarmMemory
13
13
  # - Have type: 'skill' in metadata
14
14
  # - Include tools array in metadata (optional)
15
15
  # - Include permissions hash in metadata (optional)
16
- class LoadSkill < RubyLLM::Tool
16
+ class LoadSkill < SwarmSDK::Tools::Base
17
+ removable false # LoadSkill is always available
17
18
  description <<~DESC
18
19
  Load a skill from memory and dynamically adapt your toolset to execute it.
19
20
 
@@ -127,33 +128,20 @@ module SwarmMemory
127
128
  desc: "Path to skill - MUST start with 'skill/' (one of 4 fixed memory categories). Examples: 'skill/debugging/api-errors.md', 'skill/meta/deep-learning.md'",
128
129
  required: true
129
130
 
130
- # Initialize with all context needed for tool swapping
131
+ # Initialize with storage and chat context
131
132
  #
132
133
  # @param storage [Core::Storage] Memory storage
133
134
  # @param agent_name [Symbol] Agent identifier
134
135
  # @param chat [SwarmSDK::Agent::Chat] The agent's chat instance
135
- # @param tool_configurator [SwarmSDK::ToolConfigurator] For creating tools
136
- # @param agent_definition [SwarmSDK::Agent::Definition] For permissions
137
- def initialize(storage:, agent_name:, chat:, tool_configurator:, agent_definition:)
136
+ # @param tool_configurator [SwarmSDK::ToolConfigurator] For creating tools (unused in Plan 025)
137
+ # @param agent_definition [SwarmSDK::Agent::Definition] For permissions (unused in Plan 025)
138
+ def initialize(storage:, agent_name:, chat:, tool_configurator: nil, agent_definition: nil)
138
139
  super()
139
140
  @storage = storage
140
141
  @agent_name = agent_name
141
142
  @chat = chat
142
- @tool_configurator = tool_configurator
143
- @agent_definition = agent_definition
144
-
145
- # Mark memory tools and LoadSkill as immutable
146
- # This ensures they won't be removed during skill swapping
147
- @chat.mark_tools_immutable(
148
- "MemoryWrite",
149
- "MemoryRead",
150
- "MemoryEdit",
151
- "MemoryDelete",
152
- "MemoryGlob",
153
- "MemoryGrep",
154
- "MemoryDefrag",
155
- "LoadSkill",
156
- )
143
+ # NOTE: tool_configurator and agent_definition kept for API compatibility
144
+ # but unused in Plan 025 (tools come from registry, not recreation)
157
145
  end
158
146
 
159
147
  # Override name to return simple "LoadSkill"
@@ -161,7 +149,7 @@ module SwarmMemory
161
149
  "LoadSkill"
162
150
  end
163
151
 
164
- # Execute the tool
152
+ # Execute the tool (Plan 025: Simplified - no tool swapping)
165
153
  #
166
154
  # @param file_path [String] Path to skill in memory
167
155
  # @return [String] Skill content with line numbers, or error message
@@ -184,30 +172,42 @@ module SwarmMemory
184
172
  return validation_error("memory://#{file_path} is not a skill (type: #{type})")
185
173
  end
186
174
 
187
- # 4. Extract tool requirements
175
+ # 4. Extract tool requirements and permissions
188
176
  required_tools = entry.metadata["tools"]
189
177
  permissions = entry.metadata["permissions"] || {}
190
178
 
191
- # 5. Validate and swap tools (only if tools are specified)
179
+ # 5. Validate tools exist in registry (only if tools are specified and non-empty)
192
180
  if required_tools && !required_tools.empty?
193
181
  begin
194
- swap_tools(required_tools, permissions)
182
+ validate_skill_tools(required_tools)
195
183
  rescue ArgumentError => e
196
184
  return validation_error(e.message)
197
185
  end
198
186
  end
199
- # If no tools specified (nil or []), keep current tools (no swap)
200
187
 
201
- # 6. Mark skill as loaded
202
- @chat.mark_skill_loaded(file_path)
188
+ # 6. Create and set skill state
189
+ # Note: tools: nil or tools: [] both mean "no restriction" (keep all tools)
190
+ skill_state = SwarmMemory::SkillState.new(
191
+ file_path: file_path,
192
+ tools: required_tools, # May be nil or [] (no restriction)
193
+ permissions: permissions,
194
+ )
195
+ @chat.load_skill_state(skill_state)
196
+
197
+ # 7. Activate tools if skill restricts them
198
+ # If no restriction (nil or []), tools remain unchanged
199
+ if skill_state.restricts_tools?
200
+ @chat.activate_tools_for_prompt
201
+ end
202
+ # Otherwise, tools stay as-is (no swap)
203
203
 
204
- # 7. Return content with confirmation message
204
+ # 8. Return content with confirmation message
205
205
  title = entry.title || "Untitled Skill"
206
206
  result = "Loaded skill: #{title}\n\n"
207
207
  result += format_with_line_numbers(entry.content)
208
208
 
209
- # 8. Add system reminder if tools were swapped
210
- if required_tools && !required_tools.empty?
209
+ # 9. Add system reminder if tools were restricted
210
+ if skill_state.restricts_tools?
211
211
  result += "\n\n"
212
212
  result += build_toolset_update_reminder(required_tools)
213
213
  end
@@ -217,75 +217,46 @@ module SwarmMemory
217
217
 
218
218
  private
219
219
 
220
- # Build system reminder for toolset updates
220
+ # Validate tools exist in registry (Plan 025)
221
221
  #
222
- # @param new_tools [Array<String>] Tools that were added
222
+ # @param required_tools [Array<String>] Tools needed by the skill
223
+ # @raise [ArgumentError] If any tool is not available
224
+ # @return [void]
225
+ def validate_skill_tools(required_tools)
226
+ required_tools.each do |tool_name|
227
+ next if @chat.tool_registry.has_tool?(tool_name)
228
+
229
+ available = @chat.tool_registry.tool_names.join(", ")
230
+ raise ArgumentError,
231
+ "Skill requires tool '#{tool_name}' but it's not available for this agent. " \
232
+ "Available tools: #{available}"
233
+ end
234
+ end
235
+
236
+ # Build system reminder for toolset updates (Plan 025)
237
+ #
238
+ # @param new_tools [Array<String>] Tools that were loaded
223
239
  # @return [String] System reminder message
224
240
  def build_toolset_update_reminder(new_tools)
225
- # Get current tool list from chat
226
- # Handle both real Chat (hash) and MockChat (array)
227
- tools_collection = @chat.tools
228
- current_tools = if tools_collection.is_a?(Hash)
229
- tools_collection.values.map(&:name).sort
230
- else
231
- tools_collection.map(&:name).sort
232
- end
241
+ # Get non-removable tools that are always included
242
+ non_removable = @chat.tool_registry.non_removable_tool_names.sort
233
243
 
234
244
  reminder = "<system-reminder>\n"
235
- reminder += "Your available tools have been updated.\n\n"
236
- reminder += "New tools loaded from skill:\n"
245
+ reminder += "Your available tools have been updated by loading this skill.\n\n"
246
+ reminder += "Tools specified by skill:\n"
237
247
  new_tools.each do |tool_name|
238
248
  reminder += " - #{tool_name}\n"
239
249
  end
240
- reminder += "\nYour complete toolset is now:\n"
241
- current_tools.each do |tool_name|
250
+ reminder += "\nNon-removable tools (always available):\n"
251
+ non_removable.each do |tool_name|
242
252
  reminder += " - #{tool_name}\n"
243
253
  end
244
- reminder += "\nOnly use tools from this list. Do not attempt to use tools that are not listed here.\n"
254
+ reminder += "\nOnly use tools from these lists. Other tools have been deactivated for this skill.\n"
245
255
  reminder += "</system-reminder>"
246
256
 
247
257
  reminder
248
258
  end
249
259
 
250
- # Swap agent tools to match skill requirements
251
- #
252
- # @param required_tools [Array<String>] Tools needed by the skill
253
- # @param permissions [Hash] Tool permissions from skill metadata
254
- # @return [void]
255
- # @raise [ArgumentError] If validation fails
256
- def swap_tools(required_tools, permissions)
257
- # Future: Could validate tool availability against agent's configured tools
258
- # For now, all tools in SwarmSDK are available (unless bypassed by permissions)
259
-
260
- # Remove all mutable tools (keeps immutable tools)
261
- @chat.remove_mutable_tools
262
-
263
- # Add required tools from skill
264
- required_tools.each do |tool_name|
265
- tool_sym = tool_name.to_sym
266
-
267
- # Get permissions for this tool (skill overrides agent permissions)
268
- tool_permissions = permissions[tool_name] || permissions[tool_sym.to_s]
269
-
270
- # Create tool instance
271
- tool_instance = @tool_configurator.create_tool_instance(
272
- tool_sym,
273
- @agent_name,
274
- @agent_definition.directory,
275
- )
276
-
277
- # Wrap with permissions (unless bypassed)
278
- tool_instance = @tool_configurator.wrap_tool_with_permissions(
279
- tool_instance,
280
- tool_permissions,
281
- @agent_definition,
282
- )
283
-
284
- # Add to chat
285
- @chat.add_tool(tool_instance)
286
- end
287
- end
288
-
289
260
  # Format validation error message
290
261
  #
291
262
  # @param message [String] Error message
@@ -6,7 +6,8 @@ module SwarmMemory
6
6
  #
7
7
  # Provides defragmentation operations to maintain memory quality.
8
8
  # Each agent has its own isolated memory storage.
9
- class MemoryDefrag < RubyLLM::Tool
9
+ class MemoryDefrag < SwarmSDK::Tools::Base
10
+ removable false # Memory tools are always available
10
11
  description <<~DESC
11
12
  Analyze and optimize your memory storage for better precision, recall, and organization.
12
13
 
@@ -6,7 +6,8 @@ module SwarmMemory
6
6
  #
7
7
  # Removes entries that are no longer relevant.
8
8
  # Each agent has its own isolated memory storage.
9
- class MemoryDelete < RubyLLM::Tool
9
+ class MemoryDelete < SwarmSDK::Tools::Base
10
+ removable false # Memory tools are always available
10
11
  description <<~DESC
11
12
  Delete entries from your memory storage when they're no longer needed.
12
13
 
@@ -6,7 +6,8 @@ module SwarmMemory
6
6
  #
7
7
  # Performs exact string replacements in memory content.
8
8
  # Each agent has its own isolated memory storage.
9
- class MemoryEdit < RubyLLM::Tool
9
+ class MemoryEdit < SwarmSDK::Tools::Base
10
+ removable false # Memory tools are always available
10
11
  description <<~DESC
11
12
  Perform exact string replacements in memory entries (works like Edit tool but for memory content).
12
13
 
@@ -6,7 +6,8 @@ module SwarmMemory
6
6
  #
7
7
  # Finds memory entries matching a glob pattern (like filesystem glob).
8
8
  # Each agent has its own isolated memory storage.
9
- class MemoryGlob < RubyLLM::Tool
9
+ class MemoryGlob < SwarmSDK::Tools::Base
10
+ removable false # Memory tools are always available
10
11
  description <<~DESC
11
12
  Search your memory entries using glob patterns (like filesystem glob).
12
13
 
@@ -6,7 +6,9 @@ module SwarmMemory
6
6
  #
7
7
  # Searches content stored in memory entries using regex patterns.
8
8
  # Each agent has its own isolated memory storage.
9
- class MemoryGrep < RubyLLM::Tool
9
+ class MemoryGrep < SwarmSDK::Tools::Base
10
+ removable false # Memory tools are always available
11
+
10
12
  include TitleLookup
11
13
 
12
14
  description <<~DESC
@@ -6,7 +6,9 @@ module SwarmMemory
6
6
  #
7
7
  # Retrieves content stored by this agent using memory_write.
8
8
  # Each agent has its own isolated memory storage.
9
- class MemoryRead < RubyLLM::Tool
9
+ class MemoryRead < SwarmSDK::Tools::Base
10
+ removable false # Memory tools are always available
11
+
10
12
  include TitleLookup
11
13
 
12
14
  description <<~DESC
@@ -7,7 +7,8 @@ module SwarmMemory
7
7
  # Searches content stored in memory using AI embeddings to find
8
8
  # conceptually related entries even when exact keywords don't match.
9
9
  # Each agent has its own isolated memory storage.
10
- class MemorySearch < RubyLLM::Tool
10
+ class MemorySearch < SwarmSDK::Tools::Base
11
+ removable false # Memory tools are always available
11
12
  description <<~DESC
12
13
  Perform semantic search across memory entries using natural language queries.
13
14
 
@@ -6,7 +6,8 @@ module SwarmMemory
6
6
  #
7
7
  # Stores content and metadata in persistent, per-agent memory storage.
8
8
  # Each agent has its own isolated memory storage that persists across sessions.
9
- class MemoryWrite < RubyLLM::Tool
9
+ class MemoryWrite < SwarmSDK::Tools::Base
10
+ removable false # Memory tools are always available
10
11
  description <<~DESC
11
12
  Store content in persistent memory with structured metadata for semantic search and retrieval.
12
13
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmMemory
4
- VERSION = "2.2.7"
4
+ VERSION = "2.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swarm_memory
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.7
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '2.6'
74
+ version: '2.7'
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '2.6'
81
+ version: '2.7'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: zeitwerk
84
84
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +130,7 @@ files:
130
130
  - lib/swarm_memory/search/semantic_search.rb
131
131
  - lib/swarm_memory/search/text_search.rb
132
132
  - lib/swarm_memory/search/text_similarity.rb
133
+ - lib/swarm_memory/skill_state.rb
133
134
  - lib/swarm_memory/skills/meta/deep-learning.md
134
135
  - lib/swarm_memory/skills/meta/deep-learning.yml
135
136
  - lib/swarm_memory/tools/load_skill.rb