claude_swarm 1.0.1 → 1.0.4

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 (267) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +1 -1
  3. data/.claude/hooks/lint-code-files.rb +65 -0
  4. data/.rubocop.yml +22 -2
  5. data/CHANGELOG.md +14 -1
  6. data/CLAUDE.md +1 -1
  7. data/CONTRIBUTING.md +69 -0
  8. data/README.md +27 -2
  9. data/Rakefile +71 -3
  10. data/analyze_coverage.rb +94 -0
  11. data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
  12. data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
  13. data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
  14. data/docs/v2/README.md +308 -0
  15. data/docs/v2/guides/claude-code-agents.md +262 -0
  16. data/docs/v2/guides/complete-tutorial.md +3088 -0
  17. data/docs/v2/guides/getting-started.md +1456 -0
  18. data/docs/v2/guides/memory-adapters.md +998 -0
  19. data/docs/v2/guides/plugins.md +816 -0
  20. data/docs/v2/guides/quick-start-cli.md +1745 -0
  21. data/docs/v2/guides/rails-integration.md +1902 -0
  22. data/docs/v2/guides/swarm-memory.md +599 -0
  23. data/docs/v2/reference/cli.md +729 -0
  24. data/docs/v2/reference/ruby-dsl.md +2154 -0
  25. data/docs/v2/reference/yaml.md +1835 -0
  26. data/docs-team-swarm.yml +2222 -0
  27. data/examples/learning-assistant/assistant.md +7 -0
  28. data/examples/learning-assistant/example-memories/concept-example.md +90 -0
  29. data/examples/learning-assistant/example-memories/experience-example.md +66 -0
  30. data/examples/learning-assistant/example-memories/fact-example.md +76 -0
  31. data/examples/learning-assistant/example-memories/memory-index.md +78 -0
  32. data/examples/learning-assistant/example-memories/skill-example.md +168 -0
  33. data/examples/learning-assistant/learning_assistant.rb +34 -0
  34. data/examples/learning-assistant/learning_assistant.yml +20 -0
  35. data/examples/v2/dsl/01_basic.rb +44 -0
  36. data/examples/v2/dsl/02_core_parameters.rb +59 -0
  37. data/examples/v2/dsl/03_capabilities.rb +71 -0
  38. data/examples/v2/dsl/04_llm_parameters.rb +56 -0
  39. data/examples/v2/dsl/05_advanced_flags.rb +73 -0
  40. data/examples/v2/dsl/06_permissions.rb +80 -0
  41. data/examples/v2/dsl/07_mcp_server.rb +62 -0
  42. data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
  43. data/examples/v2/dsl/09_agent_hooks.rb +67 -0
  44. data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
  45. data/examples/v2/dsl/11_delegation.rb +60 -0
  46. data/examples/v2/dsl/12_complete_integration.rb +137 -0
  47. data/examples/v2/file_tools_swarm.yml +102 -0
  48. data/examples/v2/hooks/01_basic_hooks.rb +133 -0
  49. data/examples/v2/hooks/02_usage_tracking.rb +201 -0
  50. data/examples/v2/hooks/03_production_monitoring.rb +429 -0
  51. data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
  52. data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
  53. data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
  54. data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
  55. data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
  56. data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
  57. data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
  58. data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
  59. data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
  60. data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
  61. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
  62. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
  63. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
  64. data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
  65. data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
  66. data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
  67. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
  68. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
  69. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
  70. data/examples/v2/hooks/swarm_summary.sh +44 -0
  71. data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
  72. data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
  73. data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
  74. data/examples/v2/hooks/validate_bash.rb +59 -0
  75. data/examples/v2/multi_directory_permissions.yml +221 -0
  76. data/examples/v2/node_context_demo.rb +127 -0
  77. data/examples/v2/node_workflow.rb +173 -0
  78. data/examples/v2/path_resolution_demo.rb +216 -0
  79. data/examples/v2/simple-swarm-v2.rb +90 -0
  80. data/examples/v2/simple-swarm-v2.yml +62 -0
  81. data/examples/v2/swarm.yml +71 -0
  82. data/examples/v2/swarm_with_hooks.yml +61 -0
  83. data/examples/v2/swarm_with_hooks_simple.yml +25 -0
  84. data/examples/v2/think_tool_demo.rb +62 -0
  85. data/exe/swarm +6 -0
  86. data/lib/claude_swarm/claude_mcp_server.rb +0 -6
  87. data/lib/claude_swarm/cli.rb +10 -3
  88. data/lib/claude_swarm/commands/ps.rb +19 -20
  89. data/lib/claude_swarm/commands/show.rb +1 -1
  90. data/lib/claude_swarm/configuration.rb +10 -12
  91. data/lib/claude_swarm/mcp_generator.rb +10 -1
  92. data/lib/claude_swarm/orchestrator.rb +73 -49
  93. data/lib/claude_swarm/system_utils.rb +37 -11
  94. data/lib/claude_swarm/version.rb +1 -1
  95. data/lib/claude_swarm/worktree_manager.rb +1 -0
  96. data/lib/claude_swarm/yaml_loader.rb +22 -0
  97. data/lib/claude_swarm.rb +7 -2
  98. data/lib/swarm_cli/cli.rb +201 -0
  99. data/lib/swarm_cli/command_registry.rb +61 -0
  100. data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
  101. data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
  102. data/lib/swarm_cli/commands/migrate.rb +55 -0
  103. data/lib/swarm_cli/commands/run.rb +173 -0
  104. data/lib/swarm_cli/config_loader.rb +97 -0
  105. data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
  106. data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
  107. data/lib/swarm_cli/interactive_repl.rb +918 -0
  108. data/lib/swarm_cli/mcp_serve_options.rb +44 -0
  109. data/lib/swarm_cli/mcp_tools_options.rb +59 -0
  110. data/lib/swarm_cli/migrate_options.rb +54 -0
  111. data/lib/swarm_cli/migrator.rb +132 -0
  112. data/lib/swarm_cli/options.rb +151 -0
  113. data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
  114. data/lib/swarm_cli/ui/components/content_block.rb +120 -0
  115. data/lib/swarm_cli/ui/components/divider.rb +57 -0
  116. data/lib/swarm_cli/ui/components/panel.rb +62 -0
  117. data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
  118. data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
  119. data/lib/swarm_cli/ui/formatters/number.rb +58 -0
  120. data/lib/swarm_cli/ui/formatters/text.rb +77 -0
  121. data/lib/swarm_cli/ui/formatters/time.rb +73 -0
  122. data/lib/swarm_cli/ui/icons.rb +59 -0
  123. data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
  124. data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
  125. data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
  126. data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
  127. data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
  128. data/lib/swarm_cli/version.rb +5 -0
  129. data/lib/swarm_cli.rb +44 -0
  130. data/lib/swarm_memory/adapters/base.rb +141 -0
  131. data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
  132. data/lib/swarm_memory/chat_extension.rb +34 -0
  133. data/lib/swarm_memory/cli/commands.rb +306 -0
  134. data/lib/swarm_memory/core/entry.rb +37 -0
  135. data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
  136. data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
  137. data/lib/swarm_memory/core/path_normalizer.rb +75 -0
  138. data/lib/swarm_memory/core/semantic_index.rb +244 -0
  139. data/lib/swarm_memory/core/storage.rb +288 -0
  140. data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
  141. data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
  142. data/lib/swarm_memory/dsl/memory_config.rb +113 -0
  143. data/lib/swarm_memory/embeddings/embedder.rb +36 -0
  144. data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
  145. data/lib/swarm_memory/errors.rb +21 -0
  146. data/lib/swarm_memory/integration/cli_registration.rb +30 -0
  147. data/lib/swarm_memory/integration/configuration.rb +43 -0
  148. data/lib/swarm_memory/integration/registration.rb +31 -0
  149. data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
  150. data/lib/swarm_memory/optimization/analyzer.rb +244 -0
  151. data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
  152. data/lib/swarm_memory/prompts/memory.md.erb +109 -0
  153. data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
  154. data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
  155. data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
  156. data/lib/swarm_memory/search/semantic_search.rb +112 -0
  157. data/lib/swarm_memory/search/text_search.rb +42 -0
  158. data/lib/swarm_memory/search/text_similarity.rb +80 -0
  159. data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
  160. data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
  161. data/lib/swarm_memory/tools/load_skill.rb +313 -0
  162. data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
  163. data/lib/swarm_memory/tools/memory_delete.rb +99 -0
  164. data/lib/swarm_memory/tools/memory_edit.rb +185 -0
  165. data/lib/swarm_memory/tools/memory_glob.rb +160 -0
  166. data/lib/swarm_memory/tools/memory_grep.rb +247 -0
  167. data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
  168. data/lib/swarm_memory/tools/memory_read.rb +123 -0
  169. data/lib/swarm_memory/tools/memory_write.rb +231 -0
  170. data/lib/swarm_memory/utils.rb +50 -0
  171. data/lib/swarm_memory/version.rb +5 -0
  172. data/lib/swarm_memory.rb +166 -0
  173. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
  174. data/lib/swarm_sdk/agent/builder.rb +461 -0
  175. data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
  176. data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
  177. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
  178. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
  179. data/lib/swarm_sdk/agent/chat.rb +1159 -0
  180. data/lib/swarm_sdk/agent/context.rb +112 -0
  181. data/lib/swarm_sdk/agent/context_manager.rb +309 -0
  182. data/lib/swarm_sdk/agent/definition.rb +556 -0
  183. data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
  184. data/lib/swarm_sdk/configuration.rb +296 -0
  185. data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
  186. data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
  187. data/lib/swarm_sdk/context_compactor.rb +340 -0
  188. data/lib/swarm_sdk/hooks/adapter.rb +359 -0
  189. data/lib/swarm_sdk/hooks/context.rb +197 -0
  190. data/lib/swarm_sdk/hooks/definition.rb +80 -0
  191. data/lib/swarm_sdk/hooks/error.rb +29 -0
  192. data/lib/swarm_sdk/hooks/executor.rb +146 -0
  193. data/lib/swarm_sdk/hooks/registry.rb +147 -0
  194. data/lib/swarm_sdk/hooks/result.rb +150 -0
  195. data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
  196. data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
  197. data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
  198. data/lib/swarm_sdk/log_collector.rb +51 -0
  199. data/lib/swarm_sdk/log_stream.rb +69 -0
  200. data/lib/swarm_sdk/markdown_parser.rb +75 -0
  201. data/lib/swarm_sdk/model_aliases.json +5 -0
  202. data/lib/swarm_sdk/models.json +1 -0
  203. data/lib/swarm_sdk/models.rb +120 -0
  204. data/lib/swarm_sdk/node/agent_config.rb +49 -0
  205. data/lib/swarm_sdk/node/builder.rb +439 -0
  206. data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
  207. data/lib/swarm_sdk/node_context.rb +170 -0
  208. data/lib/swarm_sdk/node_orchestrator.rb +384 -0
  209. data/lib/swarm_sdk/permissions/config.rb +239 -0
  210. data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
  211. data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
  212. data/lib/swarm_sdk/permissions/validator.rb +173 -0
  213. data/lib/swarm_sdk/permissions_builder.rb +122 -0
  214. data/lib/swarm_sdk/plugin.rb +147 -0
  215. data/lib/swarm_sdk/plugin_registry.rb +101 -0
  216. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
  217. data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
  218. data/lib/swarm_sdk/result.rb +97 -0
  219. data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
  220. data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
  221. data/lib/swarm_sdk/swarm/builder.rb +586 -0
  222. data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
  223. data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
  224. data/lib/swarm_sdk/swarm.rb +982 -0
  225. data/lib/swarm_sdk/tools/bash.rb +274 -0
  226. data/lib/swarm_sdk/tools/clock.rb +44 -0
  227. data/lib/swarm_sdk/tools/delegate.rb +164 -0
  228. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
  229. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
  230. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
  231. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
  232. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
  233. data/lib/swarm_sdk/tools/edit.rb +150 -0
  234. data/lib/swarm_sdk/tools/glob.rb +158 -0
  235. data/lib/swarm_sdk/tools/grep.rb +228 -0
  236. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
  237. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
  238. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
  239. data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
  240. data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
  241. data/lib/swarm_sdk/tools/read.rb +251 -0
  242. data/lib/swarm_sdk/tools/registry.rb +93 -0
  243. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
  244. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
  245. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
  246. data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
  247. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
  248. data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
  249. data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
  250. data/lib/swarm_sdk/tools/think.rb +95 -0
  251. data/lib/swarm_sdk/tools/todo_write.rb +216 -0
  252. data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
  253. data/lib/swarm_sdk/tools/write.rb +117 -0
  254. data/lib/swarm_sdk/utils.rb +50 -0
  255. data/lib/swarm_sdk/version.rb +5 -0
  256. data/lib/swarm_sdk.rb +157 -0
  257. data/llm.v2.txt +13407 -0
  258. data/rubocop/cop/security/no_reflection_methods.rb +47 -0
  259. data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
  260. data/swarm_cli.gemspec +57 -0
  261. data/swarm_memory.gemspec +28 -0
  262. data/swarm_sdk.gemspec +41 -0
  263. data/team.yml +1 -1
  264. data/team_full.yml +1875 -0
  265. data/{team_v2.yml → team_sdk.yml} +121 -52
  266. metadata +249 -6
  267. data/EXAMPLES.md +0 -164
@@ -0,0 +1,382 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Tools
5
+ # Tool for analyzing and optimizing memory storage
6
+ #
7
+ # Provides defragmentation operations to maintain memory quality.
8
+ # Each agent has its own isolated memory storage.
9
+ class MemoryDefrag < RubyLLM::Tool
10
+ description <<~DESC
11
+ Analyze and optimize your memory storage for better precision, recall, and organization.
12
+
13
+ THINK BEFORE CALLING: This tool has many actions and parameters. Choose the right action for your goal.
14
+
15
+ **When to Run Defrag:**
16
+ - Every 15-20 new memory entries created
17
+ - Memory searches returning too many irrelevant results
18
+ - Trouble finding specific information
19
+ - Before major new tasks (check memory health)
20
+ - Periodically as maintenance (every ~50 entries minimum)
21
+
22
+ ## READ-ONLY ANALYSIS ACTIONS (Safe - No Changes)
23
+
24
+ **1. analyze** - Get overall memory health report
25
+ ```
26
+ MemoryDefrag(action: "analyze")
27
+ ```
28
+ - Shows entry counts, quality scores, metadata coverage
29
+ - Provides health score (0-100) - aim for 70+
30
+ - ALWAYS run this first to understand memory state
31
+ - No parameters needed
32
+
33
+ **2. find_duplicates** - Identify similar/duplicate entries
34
+ ```
35
+ MemoryDefrag(action: "find_duplicates", similarity_threshold: 0.85)
36
+ ```
37
+ - Uses text and semantic similarity to find near-duplicates
38
+ - similarity_threshold: 0.0-1.0 (default: 0.85) - higher = more strict
39
+ - Start with 0.85, adjust based on results
40
+ - Helps identify entries to merge
41
+
42
+ **3. find_low_quality** - Find entries with poor metadata
43
+ ```
44
+ MemoryDefrag(action: "find_low_quality", confidence_filter: "low")
45
+ ```
46
+ - confidence_filter: 'low', 'medium', or 'high' (default: 'low')
47
+ - Identifies entries missing metadata, tags, or with low confidence
48
+ - Helps identify entries to improve or delete
49
+
50
+ **4. find_archival_candidates** - Find old, unused entries
51
+ ```
52
+ MemoryDefrag(action: "find_archival_candidates", age_days: 90)
53
+ ```
54
+ - age_days: Threshold in days (default: 90)
55
+ - Lists entries not updated in N days
56
+ - Candidates for deletion or archival
57
+
58
+ **5. find_related** - Discover entries that should be cross-linked
59
+ ```
60
+ MemoryDefrag(action: "find_related", min_similarity: 0.60, max_similarity: 0.85)
61
+ ```
62
+ - Finds pairs with 60-85% semantic similarity (related but not duplicates)
63
+ - Shows current linking status (unlinked, one-way, bidirectional)
64
+ - Suggests which links to create
65
+ - Pure semantic similarity (no keyword boost) - finds content relationships
66
+
67
+ ## ACTIVE OPTIMIZATION ACTIONS (Modify Memory)
68
+
69
+ **IMPORTANT:** All active operations default to dry_run=true for safety. Set dry_run=false to actually perform changes.
70
+
71
+ **6. link_related** - Create bidirectional links between related entries
72
+ ```
73
+ # Preview first
74
+ MemoryDefrag(action: "link_related", min_similarity: 0.60, max_similarity: 0.85, dry_run: true)
75
+
76
+ # Execute after reviewing
77
+ MemoryDefrag(action: "link_related", min_similarity: 0.60, max_similarity: 0.85, dry_run: false)
78
+ ```
79
+ Parameters:
80
+ - min_similarity: 0.0-1.0 (default: 0.60) - minimum relationship threshold
81
+ - max_similarity: 0.0-1.0 (default: 0.85) - maximum (above = duplicates)
82
+ - dry_run: true (preview) or false (execute)
83
+
84
+ What it does:
85
+ - Finds related but not duplicate entries (60-85% similarity)
86
+ - Creates bidirectional links in 'related' metadata
87
+ - Skips already-linked pairs
88
+ - Builds your knowledge graph automatically
89
+
90
+ **7. merge_duplicates** - Merge similar entries
91
+ ```
92
+ # Preview first (dry_run defaults to true)
93
+ MemoryDefrag(action: "merge_duplicates", similarity_threshold: 0.85, dry_run: true)
94
+
95
+ # Execute after reviewing preview
96
+ MemoryDefrag(action: "merge_duplicates", similarity_threshold: 0.85, dry_run: false)
97
+ ```
98
+ Parameters:
99
+ - similarity_threshold: 0.0-1.0 (default: 0.85) - matching threshold
100
+ - merge_strategy: 'keep_newer', 'keep_larger', 'combine' (default: 'keep_newer')
101
+ - dry_run: true (preview) or false (execute)
102
+
103
+ What it does:
104
+ - Merges similar entries to reduce duplication
105
+ - Creates stub files with auto-redirect to merged entry
106
+ - Preserves access to old paths via redirects
107
+
108
+ **8. cleanup_stubs** - Remove old redirect stub files
109
+ ```
110
+ MemoryDefrag(action: "cleanup_stubs", age_days: 30, max_hits: 3, dry_run: false)
111
+ ```
112
+ Parameters:
113
+ - age_days: Minimum age to delete (default: 90)
114
+ - max_hits: Maximum access count to delete (default: 10)
115
+ - dry_run: true (preview) or false (execute)
116
+
117
+ What it does:
118
+ - Deletes stub files that are old AND rarely accessed
119
+ - Keeps frequently-accessed stubs even if old
120
+ - Reduces clutter from obsolete redirects
121
+
122
+ **9. compact** - Delete low-value entries
123
+ ```
124
+ MemoryDefrag(action: "compact", min_quality_score: 20, min_age_days: 30, max_hits: 0, dry_run: false)
125
+ ```
126
+ Parameters:
127
+ - min_quality_score: Minimum quality threshold (default: 20)
128
+ - min_age_days: Minimum age in days (default: 30)
129
+ - max_hits: Maximum access count (default: 10)
130
+ - dry_run: true (preview) or false (execute)
131
+
132
+ What it does:
133
+ - Permanently deletes entries matching ALL criteria
134
+ - Targets low-quality, old, unused entries
135
+ - Frees up memory space
136
+
137
+ **10. full** - Complete optimization workflow
138
+ ```
139
+ # Preview full optimization
140
+ MemoryDefrag(action: "full", dry_run: true)
141
+
142
+ # Execute full optimization
143
+ MemoryDefrag(action: "full", dry_run: false)
144
+ ```
145
+ What it does:
146
+ - Runs: merge_duplicates → cleanup_stubs → compact
147
+ - Shows health score improvement (before/after)
148
+ - Most comprehensive optimization
149
+ - ALWAYS preview first!
150
+ - Does NOT include link_related (run separately if desired)
151
+
152
+ ## BEST PRACTICES
153
+
154
+ **1. Always Preview First:**
155
+ - Use dry_run=true to see what would happen
156
+ - Review the preview carefully
157
+ - Only proceed if changes look correct
158
+
159
+ **2. Start with Analysis:**
160
+ ```
161
+ # Step 1: Check health
162
+ MemoryDefrag(action: "analyze")
163
+
164
+ # Step 2: Find issues
165
+ MemoryDefrag(action: "find_duplicates")
166
+ MemoryDefrag(action: "find_low_quality")
167
+
168
+ # Step 3: Preview fixes
169
+ MemoryDefrag(action: "merge_duplicates", dry_run: true)
170
+
171
+ # Step 4: Execute if preview looks good
172
+ MemoryDefrag(action: "merge_duplicates", dry_run: false)
173
+
174
+ # Step 5: Verify improvement
175
+ MemoryDefrag(action: "analyze")
176
+ ```
177
+
178
+ **3. Conservative Thresholds:**
179
+ - similarity_threshold: Start at 0.85 or higher
180
+ - age_days: Start at 90+ days (don't delete recent entries)
181
+ - min_quality_score: Start at 20 or lower (only worst entries)
182
+
183
+ **4. Regular Maintenance Schedule:**
184
+ - Light check: Every 15-20 new entries (just analyze)
185
+ - Medium check: Every 50 entries (analyze + find_*)
186
+ - Heavy maintenance: Every 100 entries (full optimization)
187
+
188
+ **5. Safety Checklist:**
189
+ - ✓ Preview with dry_run=true first
190
+ - ✓ Review what will be changed
191
+ - ✓ Use conservative thresholds initially
192
+ - ✓ Re-analyze after to verify improvement
193
+ - ✓ Don't delete recent or frequently-accessed entries
194
+
195
+ ## PARAMETER REFERENCE
196
+
197
+ All parameters are optional and have sensible defaults:
198
+
199
+ - action: 'analyze', 'find_duplicates', 'find_low_quality', 'find_archival_candidates', 'find_related', 'link_related', 'merge_duplicates', 'cleanup_stubs', 'compact', 'full' (default: 'analyze')
200
+ - dry_run: true (preview) or false (execute) - default: true for safety
201
+ - similarity_threshold: 0.0-1.0 (default: 0.85) - for merge_duplicates
202
+ - min_similarity: 0.0-1.0 (default: 0.60) - for find_related/link_related
203
+ - max_similarity: 0.0-1.0 (default: 0.85) - for find_related/link_related
204
+ - merge_strategy: 'keep_newer', 'keep_larger', 'combine' (default: 'keep_newer')
205
+ - age_days: Days threshold (default: 90)
206
+ - max_hits: Access count threshold (default: 10)
207
+ - min_quality_score: Quality threshold (default: 20)
208
+ - confidence_filter: 'low', 'medium', 'high' (default: 'low')
209
+ DESC
210
+
211
+ param :action,
212
+ desc: "Action: 'analyze', 'find_duplicates', 'find_low_quality', 'find_archival_candidates', 'find_related', 'link_related', 'merge_duplicates', 'cleanup_stubs', 'compact', 'full' (default: 'analyze')",
213
+ required: false
214
+
215
+ param :dry_run,
216
+ desc: "Preview mode - show what would be done without doing it (default: true for safety)",
217
+ required: false
218
+
219
+ param :similarity_threshold,
220
+ desc: "Similarity threshold for duplicate detection 0.0-1.0 (default: 0.85)",
221
+ required: false
222
+
223
+ param :min_similarity,
224
+ desc: "Minimum similarity for find_related/link_related 0.0-1.0 (default: 0.60)",
225
+ required: false
226
+
227
+ param :max_similarity,
228
+ desc: "Maximum similarity for find_related/link_related 0.0-1.0 (default: 0.85)",
229
+ required: false
230
+
231
+ param :merge_strategy,
232
+ desc: "Merge strategy: 'keep_newer', 'keep_larger', 'combine' (default: 'keep_newer')",
233
+ required: false
234
+
235
+ param :age_days,
236
+ desc: "Age threshold for archival/cleanup in days (default: 90)",
237
+ required: false
238
+
239
+ param :max_hits,
240
+ desc: "Maximum hits threshold for cleanup/archive (default: 10)",
241
+ required: false
242
+
243
+ param :min_quality_score,
244
+ desc: "Minimum quality score for compact (default: 20)",
245
+ required: false
246
+
247
+ param :confidence_filter,
248
+ desc: "Confidence level to filter: 'low', 'medium', 'high' (default: 'low')",
249
+ required: false
250
+
251
+ # Initialize with storage instance
252
+ #
253
+ # @param storage [Core::Storage] Storage instance
254
+ def initialize(storage:)
255
+ super()
256
+ @storage = storage
257
+ @defragmenter = nil # Lazy load
258
+ end
259
+
260
+ # Override name to return simple "MemoryDefrag"
261
+ def name
262
+ "MemoryDefrag"
263
+ end
264
+
265
+ # Execute the tool
266
+ #
267
+ # @param action [String] Action to perform
268
+ # @param dry_run [Boolean] Preview mode (default: true)
269
+ # @param similarity_threshold [Float] Duplicate detection threshold
270
+ # @param min_similarity [Float] Minimum for relationship detection
271
+ # @param max_similarity [Float] Maximum for relationship detection
272
+ # @param merge_strategy [String] Merge strategy
273
+ # @param age_days [Integer] Age threshold
274
+ # @param max_hits [Integer] Maximum hits threshold
275
+ # @param min_quality_score [Integer] Minimum quality score
276
+ # @param confidence_filter [String] Confidence level filter
277
+ # @return [String] Operation report
278
+ def execute(
279
+ action: "analyze",
280
+ dry_run: true,
281
+ similarity_threshold: 0.85,
282
+ min_similarity: 0.60,
283
+ max_similarity: 0.85,
284
+ merge_strategy: "keep_newer",
285
+ age_days: 90,
286
+ max_hits: 10,
287
+ min_quality_score: 20,
288
+ confidence_filter: "low"
289
+ )
290
+ ensure_defragmenter_loaded
291
+
292
+ case action.to_s.downcase
293
+ # Read-only operations
294
+ when "analyze"
295
+ @defragmenter.health_report
296
+ when "find_duplicates"
297
+ @defragmenter.find_duplicates_report(threshold: similarity_threshold.to_f)
298
+ when "find_low_quality"
299
+ @defragmenter.find_low_quality_report(confidence_filter: confidence_filter.to_s.downcase)
300
+ when "find_archival_candidates"
301
+ @defragmenter.find_archival_candidates_report(age_days: age_days.to_i)
302
+ when "find_related"
303
+ @defragmenter.find_related_report(min_threshold: min_similarity.to_f, max_threshold: max_similarity.to_f)
304
+
305
+ # Active operations (modify memory)
306
+ when "link_related"
307
+ @defragmenter.link_related_active(
308
+ min_threshold: min_similarity.to_f,
309
+ max_threshold: max_similarity.to_f,
310
+ dry_run: dry_run,
311
+ )
312
+ when "merge_duplicates"
313
+ @defragmenter.merge_duplicates_active(
314
+ threshold: similarity_threshold.to_f,
315
+ strategy: merge_strategy.to_sym,
316
+ dry_run: dry_run,
317
+ )
318
+ when "cleanup_stubs"
319
+ @defragmenter.cleanup_stubs_active(
320
+ min_age_days: age_days.to_i,
321
+ max_hits: max_hits.to_i,
322
+ dry_run: dry_run,
323
+ )
324
+ when "compact"
325
+ @defragmenter.compact_active(
326
+ min_quality_score: min_quality_score.to_i,
327
+ min_age_days: age_days.to_i,
328
+ max_hits: max_hits.to_i,
329
+ dry_run: dry_run,
330
+ )
331
+ when "full"
332
+ # Full can be read-only analysis OR active optimization
333
+ if dry_run
334
+ # Just analysis
335
+ @defragmenter.full_analysis(
336
+ similarity_threshold: similarity_threshold.to_f,
337
+ age_days: age_days.to_i,
338
+ confidence_filter: confidence_filter.to_s.downcase,
339
+ )
340
+ else
341
+ # Active optimization
342
+ @defragmenter.full_optimization(dry_run: dry_run)
343
+ end
344
+ else
345
+ validation_error("Invalid action: #{action}. Must be one of: analyze, find_duplicates, find_low_quality, find_archival_candidates, find_related, link_related, merge_duplicates, cleanup_stubs, compact, full")
346
+ end
347
+ rescue ArgumentError => e
348
+ validation_error(e.message)
349
+ rescue StandardError => e
350
+ # Provide detailed error information
351
+ error_msg = "Defrag error: #{e.class.name} - #{e.message}\n\n"
352
+ error_msg += "Backtrace:\n"
353
+ error_msg += e.backtrace.first(10).join("\n")
354
+ validation_error(error_msg)
355
+ end
356
+
357
+ private
358
+
359
+ def validation_error(message)
360
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
361
+ end
362
+
363
+ # Lazy load defragmenter with embedder if available
364
+ #
365
+ # @return [void]
366
+ def ensure_defragmenter_loaded
367
+ return if @defragmenter
368
+
369
+ # Don't load embedder automatically - it can be slow (model download)
370
+ # Defrag works fine without embeddings (uses text similarity instead)
371
+ embedder = nil
372
+
373
+ @defragmenter = Optimization::Defragmenter.new(
374
+ adapter: @storage.adapter,
375
+ embedder: embedder,
376
+ )
377
+ rescue StandardError => e
378
+ raise EmbeddingError, "Failed to initialize defragmenter: #{e.message}\n#{e.backtrace.first(5).join("\n")}"
379
+ end
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Tools
5
+ # Tool for deleting content from memory storage
6
+ #
7
+ # Removes entries that are no longer relevant.
8
+ # Each agent has its own isolated memory storage.
9
+ class MemoryDelete < RubyLLM::Tool
10
+ description <<~DESC
11
+ Delete entries from your memory storage when they're no longer needed.
12
+
13
+ REQUIRED: Provide the file_path parameter - the path to the entry you want to delete.
14
+
15
+ **Parameters:**
16
+ - file_path (REQUIRED): Path to memory entry - MUST start with concept/, fact/, skill/, or experience/
17
+
18
+ **MEMORY STRUCTURE (4 Fixed Categories Only):**
19
+ - concept/{domain}/{name}.md - Abstract ideas
20
+ - fact/{subfolder}/{name}.md - Concrete information
21
+ - skill/{domain}/{name}.md - Procedures
22
+ - experience/{name}.md - Lessons
23
+ INVALID: documentation/, reference/, analysis/, parallel/, temp/, notes/
24
+
25
+ **When to Delete:**
26
+ - Outdated information that's been superseded
27
+ - Completed tasks that are no longer relevant
28
+ - Duplicate entries (after consolidating)
29
+ - Test data or temporary calculations
30
+ - Low-quality entries with minimal value
31
+
32
+ **IMPORTANT WARNINGS:**
33
+ - Deletion is PERMANENT - content cannot be recovered
34
+ - Think carefully before deleting - consider if it might be useful later
35
+ - Use MemoryDefrag to identify candidates for deletion (find_archival_candidates, compact)
36
+ - Consider reading the entry first to verify you're deleting the right thing
37
+
38
+ **Examples:**
39
+ ```
40
+ # Delete outdated concept
41
+ MemoryDelete(file_path: "concept/old-api/deprecated.md")
42
+
43
+ # Delete completed experience
44
+ MemoryDelete(file_path: "experience/temp-experiment.md")
45
+
46
+ # Delete obsolete fact
47
+ MemoryDelete(file_path: "fact/orgs/defunct-company.md")
48
+ ```
49
+
50
+ **Best Practices:**
51
+ 1. Read entry first with MemoryRead to confirm it's the right one
52
+ 2. Use MemoryDefrag(action: "find_archival_candidates") to find old, unused entries
53
+ 3. Delete in batches during memory maintenance sessions
54
+ 4. Keep entries that might provide historical context
55
+ 5. Don't delete skills unless you're certain they won't be needed
56
+
57
+ **Alternative to Deletion:**
58
+ Instead of deleting, consider:
59
+ - Updating entries to mark them as archived
60
+ - Consolidating multiple entries into one comprehensive entry
61
+ - Moving entries to an "archive/" hierarchy for later reference
62
+ DESC
63
+
64
+ param :file_path,
65
+ desc: "Path to delete from memory - MUST start with concept/, fact/, skill/, or experience/ (e.g., 'concept/old-api/deprecated.md', 'experience/temp.md')",
66
+ required: true
67
+
68
+ # Initialize with storage instance
69
+ #
70
+ # @param storage [Core::Storage] Storage instance
71
+ def initialize(storage:)
72
+ super()
73
+ @storage = storage
74
+ end
75
+
76
+ # Override name to return simple "MemoryDelete"
77
+ def name
78
+ "MemoryDelete"
79
+ end
80
+
81
+ # Execute the tool
82
+ #
83
+ # @param file_path [String] Path to delete from
84
+ # @return [String] Success message
85
+ def execute(file_path:)
86
+ @storage.delete(file_path: file_path)
87
+ "Deleted memory://#{file_path}"
88
+ rescue ArgumentError => e
89
+ validation_error(e.message)
90
+ end
91
+
92
+ private
93
+
94
+ def validation_error(message)
95
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Tools
5
+ # Tool for editing memory entries with exact string replacement
6
+ #
7
+ # Performs exact string replacements in memory content.
8
+ # Each agent has its own isolated memory storage.
9
+ class MemoryEdit < RubyLLM::Tool
10
+ description <<~DESC
11
+ Perform exact string replacements in memory entries (works like Edit tool but for memory content).
12
+
13
+ REQUIRED: Provide ALL THREE parameters - file_path, old_string, and new_string.
14
+
15
+ **Required Parameters:**
16
+ - file_path (REQUIRED): Path to memory entry - MUST start with concept/, fact/, skill/, or experience/
17
+ - old_string (REQUIRED): The exact text to replace - must match exactly including all whitespace and indentation
18
+ - new_string (REQUIRED): The replacement text - must be different from old_string
19
+
20
+ **MEMORY STRUCTURE (4 Fixed Categories Only):**
21
+ - concept/{domain}/{name}.md - Abstract ideas
22
+ - fact/{subfolder}/{name}.md - Concrete information
23
+ - skill/{domain}/{name}.md - Procedures
24
+ - experience/{name}.md - Lessons
25
+ INVALID: documentation/, reference/, analysis/, parallel/, tutorial/
26
+
27
+ **Optional Parameters:**
28
+ - replace_all: Set to true to replace all occurrences (default: false, replaces only first occurrence)
29
+
30
+ **CRITICAL - Before Using This Tool:**
31
+ 1. You MUST use MemoryRead on the entry first - edits without reading will FAIL
32
+ 2. Copy text exactly from MemoryRead output, EXCLUDING the line number prefix
33
+ 3. Line number format: " 123→actual content" - only use text AFTER the arrow
34
+ 4. Preserve exact indentation and whitespace from the original content
35
+
36
+ **How It Works:**
37
+ - If old_string appears once: replacement succeeds
38
+ - If old_string appears multiple times: FAILS unless replace_all=true
39
+ - If old_string not found: FAILS with helpful error
40
+ - Make old_string unique by including more surrounding context
41
+
42
+ **Examples:**
43
+ ```
44
+ # Update a fact
45
+ MemoryEdit(
46
+ file_path: "fact/people/john.md",
47
+ old_string: "status: active",
48
+ new_string: "status: inactive"
49
+ )
50
+
51
+ # Update a skill
52
+ MemoryEdit(
53
+ file_path: "skill/debugging/api-errors.md",
54
+ old_string: "TODO: Add error codes",
55
+ new_string: "Common error codes: 401, 403, 404, 500",
56
+ replace_all: true
57
+ )
58
+
59
+ # Update a concept
60
+ MemoryEdit(
61
+ file_path: "concept/ruby/classes.md",
62
+ old_string: "def calculate_total(items)\n return sum(items)\nend",
63
+ new_string: "def compute_sum(items)\n items.sum\nend"
64
+ )
65
+ ```
66
+
67
+ **Common Mistakes to Avoid:**
68
+ - Including line numbers in old_string or new_string
69
+ - Not reading the entry first with MemoryRead
70
+ - Not matching whitespace exactly
71
+ - Trying to replace non-unique text without replace_all
72
+ DESC
73
+
74
+ param :file_path,
75
+ desc: "Path to memory entry - MUST start with concept/, fact/, skill/, or experience/ (e.g., 'concept/ruby/classes.md', 'skill/debugging/api.md')",
76
+ required: true
77
+
78
+ param :old_string,
79
+ desc: "The exact text to replace (must match exactly including whitespace)",
80
+ required: true
81
+
82
+ param :new_string,
83
+ desc: "The text to replace it with (must be different from old_string)",
84
+ required: true
85
+
86
+ param :replace_all,
87
+ desc: "Replace all occurrences of old_string (default false)",
88
+ required: false
89
+
90
+ # Initialize with storage instance and agent name
91
+ #
92
+ # @param storage [Core::Storage] Storage instance
93
+ # @param agent_name [String, Symbol] Agent identifier
94
+ def initialize(storage:, agent_name:)
95
+ super()
96
+ @storage = storage
97
+ @agent_name = agent_name.to_sym
98
+ end
99
+
100
+ # Override name to return simple "MemoryEdit"
101
+ def name
102
+ "MemoryEdit"
103
+ end
104
+
105
+ # Execute the tool
106
+ #
107
+ # @param file_path [String] Path to memory entry
108
+ # @param old_string [String] Text to replace
109
+ # @param new_string [String] Replacement text
110
+ # @param replace_all [Boolean] Replace all occurrences
111
+ # @return [String] Success message or error
112
+ def execute(file_path:, old_string:, new_string:, replace_all: false)
113
+ # Validate inputs
114
+ return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty?
115
+ return validation_error("old_string is required") if old_string.nil? || old_string.empty?
116
+ return validation_error("new_string is required") if new_string.nil?
117
+
118
+ # old_string and new_string must be different
119
+ if old_string == new_string
120
+ return validation_error("old_string and new_string must be different. They are currently identical.")
121
+ end
122
+
123
+ # Read current content (this will raise ArgumentError if entry doesn't exist)
124
+ content = @storage.read(file_path: file_path)
125
+
126
+ # Enforce read-before-edit
127
+ unless Core::StorageReadTracker.entry_read?(@agent_name, file_path)
128
+ return validation_error(
129
+ "Cannot edit memory entry without reading it first. " \
130
+ "You must use MemoryRead on 'memory://#{file_path}' before editing it. " \
131
+ "This ensures you have the current content to match against.",
132
+ )
133
+ end
134
+
135
+ # Check if old_string exists in content
136
+ unless content.include?(old_string)
137
+ return validation_error(<<~ERROR.chomp)
138
+ old_string not found in memory entry. Make sure it matches exactly, including all whitespace and indentation.
139
+ Do not include line number prefixes from MemoryRead tool output.
140
+ ERROR
141
+ end
142
+
143
+ # Count occurrences
144
+ occurrences = content.scan(old_string).count
145
+
146
+ # If not replace_all and multiple occurrences, error
147
+ if !replace_all && occurrences > 1
148
+ return validation_error(<<~ERROR.chomp)
149
+ Found #{occurrences} occurrences of old_string.
150
+ Either provide more surrounding context to make the match unique, or use replace_all: true to replace all occurrences.
151
+ ERROR
152
+ end
153
+
154
+ # Perform replacement
155
+ new_content = if replace_all
156
+ content.gsub(old_string, new_string)
157
+ else
158
+ content.sub(old_string, new_string)
159
+ end
160
+
161
+ # Get existing entry metadata
162
+ entry = @storage.read_entry(file_path: file_path)
163
+
164
+ # Write updated content back (preserving the title)
165
+ @storage.write(
166
+ file_path: file_path,
167
+ content: new_content,
168
+ title: entry.title,
169
+ )
170
+
171
+ # Build success message
172
+ replaced_count = replace_all ? occurrences : 1
173
+ "Successfully replaced #{replaced_count} occurrence(s) in memory://#{file_path}"
174
+ rescue ArgumentError => e
175
+ validation_error(e.message)
176
+ end
177
+
178
+ private
179
+
180
+ def validation_error(message)
181
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
182
+ end
183
+ end
184
+ end
185
+ end