claude_swarm 1.0.1 → 1.0.2

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 +6 -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 +247 -4
  267. data/EXAMPLES.md +0 -164
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Tools
5
+ # Tool for reading content from memory storage
6
+ #
7
+ # Retrieves content stored by this agent using memory_write.
8
+ # Each agent has its own isolated memory storage.
9
+ class MemoryRead < RubyLLM::Tool
10
+ description <<~DESC
11
+ Read content from your memory storage and retrieve all associated metadata.
12
+
13
+ REQUIRED: Provide the file_path parameter - the path to the memory entry you want to read.
14
+
15
+ **Parameters:**
16
+ - file_path (REQUIRED): Path to memory entry - MUST start with concept/, fact/, skill/, or experience/
17
+
18
+ **MEMORY STRUCTURE - EXACTLY 4 Top-Level Categories (NEVER create others):**
19
+ ALL paths MUST start with one of these 4 fixed categories:
20
+ - concept/{domain}/{name}.md - Abstract ideas (e.g., concept/ruby/classes.md)
21
+ - fact/{subfolder}/{name}.md - Concrete info (e.g., fact/people/john.md)
22
+ - skill/{domain}/{name}.md - Procedures (e.g., skill/debugging/api-errors.md)
23
+ - experience/{name}.md - Lessons (e.g., experience/fixed-bug.md)
24
+
25
+ INVALID: documentation/, reference/, tutorial/, parallel/, analysis/, notes/
26
+
27
+ **Returns:**
28
+ JSON with two fields:
29
+ - content: Markdown content with line numbers (same format as Read tool)
30
+ - metadata: All metadata (title, type, tags, tools, permissions, confidence, etc.)
31
+
32
+ **Examples:**
33
+ - MemoryRead(file_path: "concept/ruby/classes.md") - Read a concept
34
+ - MemoryRead(file_path: "fact/people/john.md") - Read a fact
35
+ - MemoryRead(file_path: "skill/debugging/api-errors.md") - Read a skill before loading it
36
+
37
+ **Important:**
38
+ - Always read entries before editing them with MemoryEdit or MemoryMultiEdit
39
+ - Line numbers in output are for reference only - don't include them when editing
40
+ - Each read is tracked to enforce read-before-edit patterns
41
+ DESC
42
+
43
+ param :file_path,
44
+ desc: "Path to read from memory - MUST start with concept/, fact/, skill/, or experience/ (e.g., 'concept/ruby/classes.md', 'skill/debugging/api.md')",
45
+ required: true
46
+
47
+ # Initialize with storage instance and agent name
48
+ #
49
+ # @param storage [Core::Storage] Storage instance
50
+ # @param agent_name [String, Symbol] Agent identifier
51
+ def initialize(storage:, agent_name:)
52
+ super()
53
+ @storage = storage
54
+ @agent_name = agent_name.to_sym
55
+ end
56
+
57
+ # Override name to return simple "MemoryRead"
58
+ def name
59
+ "MemoryRead"
60
+ end
61
+
62
+ # Execute the tool
63
+ #
64
+ # @param file_path [String] Path to read from
65
+ # @return [String] JSON with content and metadata
66
+ def execute(file_path:)
67
+ # Register this read in the tracker
68
+ Core::StorageReadTracker.register_read(@agent_name, file_path)
69
+
70
+ # Read full entry with metadata
71
+ entry = @storage.read_entry(file_path: file_path)
72
+
73
+ # Always return JSON format (metadata always exists - at minimum title)
74
+ format_as_json(entry)
75
+ rescue ArgumentError => e
76
+ validation_error(e.message)
77
+ end
78
+
79
+ private
80
+
81
+ def validation_error(message)
82
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
83
+ end
84
+
85
+ # Format entry as JSON with content and metadata
86
+ #
87
+ # Returns a clean JSON format separating content from metadata.
88
+ # This prevents agents from mimicking metadata format when writing.
89
+ #
90
+ # Content includes line numbers (same format as Read tool).
91
+ # Metadata always includes at least title (from Entry).
92
+ # Additional metadata comes from the metadata hash (type, tags, tools, etc.)
93
+ #
94
+ # @param entry [Core::Entry] Entry with content and metadata
95
+ # @return [String] Pretty-printed JSON
96
+ def format_as_json(entry)
97
+ # Build metadata hash with title included
98
+ metadata_hash = { "title" => entry.title }
99
+ metadata_hash.merge!(entry.metadata) if entry.metadata
100
+
101
+ result = {
102
+ content: format_with_line_numbers(entry.content),
103
+ metadata: metadata_hash,
104
+ }
105
+ JSON.pretty_generate(result)
106
+ end
107
+
108
+ # Format content with line numbers (same format as Read tool)
109
+ #
110
+ # @param content [String] Content to format
111
+ # @return [String] Content with line numbers
112
+ def format_with_line_numbers(content)
113
+ lines = content.lines
114
+ output_lines = lines.each_with_index.map do |line, idx|
115
+ line_number = idx + 1
116
+ display_line = line.chomp
117
+ "#{line_number.to_s.rjust(6)}→#{display_line}"
118
+ end
119
+ output_lines.join("\n")
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ module Tools
5
+ # Tool for writing content to memory storage
6
+ #
7
+ # Stores content and metadata in persistent, per-agent memory storage.
8
+ # Each agent has its own isolated memory storage that persists across sessions.
9
+ class MemoryWrite < RubyLLM::Tool
10
+ description <<~DESC
11
+ Store content in persistent memory with structured metadata for semantic search and retrieval.
12
+
13
+ IMPORTANT: Content must be 250 words or less. If content exceeds this limit, extract key entities: concepts, experiences, facts, skills,
14
+ then split into multiple focused memories (each under 250 words) that capture ALL important details.
15
+ Link related memories using the 'related' metadata field with memory:// URIs.
16
+
17
+ CRITICAL: ALL 8 required parameters MUST be provided. Do NOT skip any. If you're missing information, ask the user or infer reasonable defaults.
18
+
19
+ REQUIRED PARAMETERS (provide ALL 8):
20
+ 1. file_path - Where to store (e.g., 'concept/ruby/classes.md', 'skill/debugging/trace-errors.md')
21
+ 2. content - Pure markdown content (no frontmatter)
22
+ 3. title - Brief descriptive title
23
+ 4. type - Entry category: "concept", "fact", "skill", or "experience"
24
+ 5. confidence - How sure you are: "high", "medium", or "low"
25
+ 6. tags - JSON string of array of search keywords (e.g., '["ruby", "classes", "oop"]') - be comprehensive!
26
+ 7. related - JSON string of array of related memory paths (e.g., '["memory://concept/ruby/modules.md", "memory://concept/ruby/classes.md"]') or '[]' if none
27
+ 8. domain - Category like 'programming/ruby', 'people', 'debugging'
28
+ 9. source - Where this came from: "user", "documentation", "experimentation", or "inference"
29
+
30
+ OPTIONAL (for skills only):
31
+ - tools - JSON string of array of tool names needed (e.g., '["Read", "Edit", "Bash"]') or '[]' if none
32
+ - permissions - Tool restrictions hash or {}
33
+
34
+ PATH STRUCTURE (EXACTLY 4 TOP-LEVEL CATEGORIES - NEVER CREATE OTHERS):
35
+ Memory has EXACTLY 4 fixed top-level categories. ALL paths MUST start with one of these:
36
+
37
+ 1. concept/{domain}/{name}.md - Abstract ideas (e.g., concept/ruby/classes.md)
38
+ 2. fact/{subfolder}/{name}.md - Concrete info (e.g., fact/people/john.md)
39
+ 3. skill/{domain}/{name}.md - How-to procedures (e.g., skill/debugging/api-errors.md)
40
+ 4. experience/{name}.md - Lessons learned (e.g., experience/fixed-cors-bug.md)
41
+
42
+ INVALID (do NOT create): documentation/, reference/, tutorial/, knowledge/, notes/
43
+ These categories do NOT exist. Use concept/, fact/, skill/, or experience/ instead.
44
+
45
+ TAGS ARE CRITICAL: Think "What would I search for in 6 months?" For skills especially, be VERY comprehensive with tags - they're your search index.
46
+
47
+ EXAMPLES:
48
+ - For concept: tags: ['ruby', 'oop', 'classes', 'inheritance', 'methods']
49
+ - For skill: tags: ['debugging', 'api', 'http', 'errors', 'trace', 'network', 'rest']
50
+ DESC
51
+
52
+ param :file_path,
53
+ desc: "Path with .md extension (e.g., 'concept/ruby/classes.md', 'fact/people/john.md')",
54
+ required: true
55
+
56
+ param :content,
57
+ desc: "Content to store (pure markdown, no frontmatter needed)",
58
+ required: true
59
+
60
+ param :title,
61
+ desc: "Brief title describing the content",
62
+ required: true
63
+
64
+ # Metadata parameters (stored in .yml sidecar)
65
+ param :type,
66
+ desc: "Entry type: concept, fact, skill, or experience (matches category: concept/, fact/, skill/, experience/)",
67
+ required: true
68
+
69
+ param :confidence,
70
+ desc: "Confidence level: high, medium, or low (defaults to 'medium' if not specified)",
71
+ required: false
72
+
73
+ param :tags,
74
+ type: "string",
75
+ desc: "JSON string of array of tag strings for searching (e.g., '[\"ruby\", \"oop\"]')",
76
+ required: true
77
+
78
+ param :related,
79
+ type: "string",
80
+ desc: "JSON string of array of related memory path strings (e.g., '[\"memory://concept/ruby/modules.md\", \"memory://concept/ruby/classes.md\"]')",
81
+ required: true
82
+
83
+ param :domain,
84
+ desc: "Category/subcategory (e.g., 'programming/ruby', 'people')",
85
+ required: true
86
+
87
+ param :source,
88
+ desc: "Source of information: user, documentation, experimentation, or inference (defaults to 'user' if not specified)",
89
+ required: false
90
+
91
+ param :tools,
92
+ type: "string",
93
+ desc: "JSON string of array of tool name strings required for this skill (e.g., '[\"Read\", \"Edit\", \"Bash\"]'). Only for type: skill",
94
+ required: false
95
+
96
+ param :permissions,
97
+ type: "object",
98
+ desc: "Tool permission restrictions (same format as swarm config). Only for type: skill",
99
+ required: false
100
+
101
+ # Initialize with storage instance
102
+ #
103
+ # @param storage [Core::Storage] Storage instance
104
+ # @param agent_name [String, Symbol] Agent identifier
105
+ def initialize(storage:, agent_name:)
106
+ super()
107
+ @storage = storage
108
+ @agent_name = agent_name.to_sym
109
+ end
110
+
111
+ # Override name to return simple "MemoryWrite"
112
+ def name
113
+ "MemoryWrite"
114
+ end
115
+
116
+ # Execute the tool
117
+ #
118
+ # @param file_path [String] Path to store content (.md file)
119
+ # @param content [String] Content to store (pure markdown)
120
+ # @param title [String] Brief title
121
+ # @param type [String, nil] Entry type
122
+ # @param confidence [String, nil] Confidence level
123
+ # @param tags [Array, nil] Tags
124
+ # @param related [Array, nil] Related paths
125
+ # @param domain [String, nil] Domain
126
+ # @param source [String, nil] Source
127
+ # @param tools [Array, nil] Tools required (for skills)
128
+ # @param permissions [Hash, nil] Tool permissions (for skills)
129
+ # @return [String] Success message
130
+ def execute(
131
+ file_path:,
132
+ content:,
133
+ title:,
134
+ type:,
135
+ confidence: nil,
136
+ tags:,
137
+ related:,
138
+ domain:,
139
+ source: nil,
140
+ tools: nil,
141
+ permissions: nil
142
+ )
143
+ # Validate content length (250 word limit)
144
+ word_count = content.split(/\s+/).size
145
+ if word_count > 250
146
+ return validation_error(
147
+ "Content exceeds 250-word limit (#{word_count} words). " \
148
+ "Please extract the key entities and concepts from this content, then split it into multiple smaller, " \
149
+ "focused memories (each under 250 words) that still capture ALL the important details. " \
150
+ "Link related memories together using the 'related' metadata field with memory:// URIs. " \
151
+ "Each memory should cover one specific aspect or concept while preserving completeness.",
152
+ )
153
+ end
154
+
155
+ # Build metadata hash from params
156
+ # Handle both JSON strings (from LLMs) and Ruby arrays (from tests/code)
157
+ metadata = {}
158
+ metadata["type"] = type if type
159
+ metadata["confidence"] = confidence || "medium" # Default to medium
160
+ metadata["tags"] = parse_array_param(tags) if tags
161
+ metadata["related"] = parse_array_param(related) if related
162
+ metadata["domain"] = domain if domain
163
+ metadata["source"] = source || "user" # Default to user
164
+ metadata["tools"] = parse_array_param(tools) if tools
165
+ metadata["permissions"] = parse_object_param(permissions) if permissions
166
+
167
+ # Write to storage (metadata passed separately, not in content)
168
+ entry = @storage.write(
169
+ file_path: file_path,
170
+ content: content,
171
+ title: title,
172
+ metadata: metadata,
173
+ )
174
+
175
+ "Stored at memory://#{file_path} (#{format_bytes(entry.size)})"
176
+ rescue ArgumentError => e
177
+ validation_error(e.message)
178
+ rescue JSON::ParserError => e
179
+ validation_error("Invalid tool parameter JSON format: #{e.message}")
180
+ end
181
+
182
+ private
183
+
184
+ def validation_error(message)
185
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
186
+ end
187
+
188
+ # Parse array parameter (handles both JSON strings and Ruby arrays)
189
+ #
190
+ # @param value [String, Array] JSON string or Ruby array
191
+ # @return [Array] Parsed array
192
+ def parse_array_param(value)
193
+ return value if value.is_a?(Array)
194
+ return [] if value.nil? || value.to_s.strip.empty?
195
+
196
+ JSON.parse(value)
197
+ end
198
+
199
+ # Parse object parameter (handles both JSON strings and Ruby hashes)
200
+ #
201
+ # @param value [String, Hash] JSON string or Ruby hash
202
+ # @return [Hash] Parsed hash
203
+ def parse_object_param(value)
204
+ return value if value.is_a?(Hash)
205
+ return {} if value.nil? || value.to_s.strip.empty?
206
+
207
+ begin
208
+ JSON.parse(value)
209
+ rescue JSON::ParserError => e
210
+ # Handle common JSON errors gracefully
211
+ warn("Warning: Failed to parse object parameter: #{e.message}. Returning empty object.")
212
+ {}
213
+ end
214
+ end
215
+
216
+ # Format bytes to human-readable size
217
+ #
218
+ # @param bytes [Integer] Number of bytes
219
+ # @return [String] Formatted size
220
+ def format_bytes(bytes)
221
+ if bytes >= 1_000_000
222
+ "#{(bytes.to_f / 1_000_000).round(1)}MB"
223
+ elsif bytes >= 1_000
224
+ "#{(bytes.to_f / 1_000).round(1)}KB"
225
+ else
226
+ "#{bytes}B"
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ # Shared utility methods for SwarmMemory
5
+ module Utils
6
+ class << self
7
+ # Recursively convert all hash keys to strings
8
+ #
9
+ # Handles nested hashes and arrays containing hashes.
10
+ #
11
+ # @param obj [Object] Object to stringify (Hash, Array, or other)
12
+ # @return [Object] Object with stringified keys (if applicable)
13
+ #
14
+ # @example
15
+ # Utils.stringify_keys({ name: "test", config: { key: "value" } })
16
+ # # => { "name" => "test", "config" => { "key" => "value" } }
17
+ def stringify_keys(obj)
18
+ case obj
19
+ when Hash
20
+ obj.transform_keys(&:to_s).transform_values { |v| stringify_keys(v) }
21
+ when Array
22
+ obj.map { |item| stringify_keys(item) }
23
+ else
24
+ obj
25
+ end
26
+ end
27
+
28
+ # Recursively convert all hash keys to symbols
29
+ #
30
+ # Handles nested hashes and arrays containing hashes.
31
+ #
32
+ # @param obj [Object] Object to symbolize (Hash, Array, or other)
33
+ # @return [Object] Object with symbolized keys (if applicable)
34
+ #
35
+ # @example
36
+ # Utils.symbolize_keys({ "name" => "test", "config" => { "key" => "value" } })
37
+ # # => { name: "test", config: { key: "value" } }
38
+ def symbolize_keys(obj)
39
+ case obj
40
+ when Hash
41
+ obj.transform_keys(&:to_sym).transform_values { |v| symbolize_keys(v) }
42
+ when Array
43
+ obj.map { |item| symbolize_keys(item) }
44
+ else
45
+ obj
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmMemory
4
+ VERSION = "2.1.1"
5
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load dependencies first (before Zeitwerk)
4
+ require "json"
5
+ require "yaml"
6
+ require "fileutils"
7
+ require "time"
8
+ require "date"
9
+ require "set"
10
+
11
+ require "async"
12
+ require "async/semaphore"
13
+ require "swarm_sdk"
14
+ require "ruby_llm"
15
+
16
+ # Try to load informers (optional, for embeddings)
17
+ begin
18
+ require "informers"
19
+ rescue LoadError
20
+ # Informers not available - embeddings will be disabled
21
+ warn("Warning: informers gem not found. Semantic search will be unavailable. Run: gem install informers")
22
+ end
23
+
24
+ # Load errors and version first
25
+ require_relative "swarm_memory/errors"
26
+ require_relative "swarm_memory/version"
27
+
28
+ # Setup Zeitwerk loader
29
+ require "zeitwerk"
30
+ loader = Zeitwerk::Loader.new
31
+ loader.push_dir("#{__dir__}/swarm_memory", namespace: SwarmMemory)
32
+ loader.setup
33
+
34
+ # Explicitly load DSL components and extensions to inject into SwarmSDK
35
+ # These must be loaded after Zeitwerk but before anything uses them
36
+ require_relative "swarm_memory/dsl/memory_config"
37
+ require_relative "swarm_memory/dsl/builder_extension"
38
+ require_relative "swarm_memory/chat_extension"
39
+
40
+ module SwarmMemory
41
+ class << self
42
+ # Registry for custom adapters
43
+ def adapter_registry
44
+ @adapter_registry ||= {}
45
+ end
46
+
47
+ # Register a custom adapter
48
+ #
49
+ # @param name [Symbol] Adapter name
50
+ # @param klass [Class] Adapter class (must inherit from Adapters::Base)
51
+ #
52
+ # @example
53
+ # SwarmMemory.register_adapter(:activerecord, ActiveRecordMemoryAdapter)
54
+ def register_adapter(name, klass)
55
+ unless klass < Adapters::Base
56
+ raise ArgumentError, "Adapter must inherit from SwarmMemory::Adapters::Base"
57
+ end
58
+
59
+ adapter_registry[name.to_sym] = klass
60
+ end
61
+
62
+ # Get adapter class by name
63
+ #
64
+ # @param name [Symbol] Adapter name
65
+ # @return [Class] Adapter class
66
+ # @raise [ArgumentError] If adapter is not found
67
+ def adapter_for(name)
68
+ name = name.to_sym
69
+
70
+ # Check built-in adapters first
71
+ case name
72
+ when :filesystem
73
+ Adapters::FilesystemAdapter
74
+ else
75
+ # Check registry
76
+ adapter_registry[name] || raise(ArgumentError, "Unknown adapter: #{name}. Available: #{available_adapters.join(", ")}")
77
+ end
78
+ end
79
+
80
+ # Get list of available adapters
81
+ #
82
+ # @return [Array<Symbol>] List of registered adapter names
83
+ def available_adapters
84
+ [:filesystem] + adapter_registry.keys
85
+ end
86
+
87
+ # Create individual tool instance
88
+ # Called by SwarmSDK's ToolConfigurator
89
+ #
90
+ # @param tool_name [Symbol] Tool name
91
+ # @param storage [SwarmMemory::Core::Storage] Storage instance
92
+ # @param agent_name [String, Symbol] Agent identifier
93
+ # @param options [Hash] Additional options for special tools like LoadSkill
94
+ # @option options [SwarmSDK::Agent::Chat] :chat Chat instance (for LoadSkill)
95
+ # @option options [SwarmSDK::ToolConfigurator] :tool_configurator Tool configurator (for LoadSkill)
96
+ # @option options [SwarmSDK::Agent::Definition] :agent_definition Agent definition (for LoadSkill)
97
+ # @return [RubyLLM::Tool] Configured tool instance
98
+ def create_tool(tool_name, storage:, agent_name:, **options)
99
+ # Validate storage is present
100
+ if storage.nil?
101
+ raise ConfigurationError,
102
+ "Cannot create #{tool_name} tool: memory storage is nil. " \
103
+ "Did you configure memory for this agent? " \
104
+ "Add: memory { directory '.swarm/agent-memory' }"
105
+ end
106
+
107
+ case tool_name.to_sym
108
+ when :MemoryWrite
109
+ Tools::MemoryWrite.new(storage: storage, agent_name: agent_name)
110
+ when :MemoryRead
111
+ Tools::MemoryRead.new(storage: storage, agent_name: agent_name)
112
+ when :MemoryEdit
113
+ Tools::MemoryEdit.new(storage: storage, agent_name: agent_name)
114
+ when :MemoryMultiEdit
115
+ Tools::MemoryMultiEdit.new(storage: storage, agent_name: agent_name)
116
+ when :MemoryDelete
117
+ Tools::MemoryDelete.new(storage: storage)
118
+ when :MemoryGlob
119
+ Tools::MemoryGlob.new(storage: storage)
120
+ when :MemoryGrep
121
+ Tools::MemoryGrep.new(storage: storage)
122
+ when :MemoryDefrag
123
+ Tools::MemoryDefrag.new(storage: storage)
124
+ when :LoadSkill
125
+ # LoadSkill requires additional context for tool swapping
126
+ Tools::LoadSkill.new(
127
+ storage: storage,
128
+ agent_name: agent_name,
129
+ chat: options[:chat],
130
+ tool_configurator: options[:tool_configurator],
131
+ agent_definition: options[:agent_definition],
132
+ )
133
+ else
134
+ raise ConfigurationError, "Unknown memory tool: #{tool_name}"
135
+ end
136
+ end
137
+
138
+ # Convenience method for creating all memory tools at once
139
+ # Useful for direct RubyLLM usage (not via SwarmSDK)
140
+ #
141
+ # @param storage [SwarmMemory::Core::Storage] Storage instance
142
+ # @param agent_name [String, Symbol] Agent identifier
143
+ # @return [Array<RubyLLM::Tool>] All configured memory tools
144
+ def tools_for(storage:, agent_name:)
145
+ [
146
+ Tools::MemoryWrite.new(storage: storage, agent_name: agent_name),
147
+ Tools::MemoryRead.new(storage: storage, agent_name: agent_name),
148
+ Tools::MemoryEdit.new(storage: storage, agent_name: agent_name),
149
+ Tools::MemoryMultiEdit.new(storage: storage, agent_name: agent_name),
150
+ Tools::MemoryDelete.new(storage: storage),
151
+ Tools::MemoryGlob.new(storage: storage),
152
+ Tools::MemoryGrep.new(storage: storage),
153
+ Tools::MemoryDefrag.new(storage: storage),
154
+ ]
155
+ end
156
+ end
157
+ end
158
+
159
+ # Auto-register with SwarmSDK when loaded
160
+ require_relative "swarm_memory/integration/sdk_plugin"
161
+ require_relative "swarm_memory/integration/registration"
162
+ SwarmMemory::Integration::Registration.register!
163
+
164
+ # Auto-register CLI commands with SwarmCLI when loaded
165
+ require_relative "swarm_memory/integration/cli_registration"
166
+ SwarmMemory::Integration::CliRegistration.register!