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,998 @@
1
+ # Memory Adapter Development Guide
2
+
3
+ Learn how to build custom storage adapters for SwarmMemory using the plugin architecture.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ SwarmMemory uses a **plugin adapter pattern** for storage, allowing you to implement custom backends that fit your infrastructure:
10
+
11
+ - **FilesystemAdapter** (built-in) - Stores in `.md/.yml/.emb` files, Git-friendly
12
+ - **Custom Adapters** (via registry) - PostgreSQL, MySQL, MongoDB, Qdrant, or any storage you need
13
+
14
+ **Plugin Architecture Benefits:**
15
+ - ✅ SwarmMemory stays lightweight (no forced dependencies)
16
+ - ✅ Use your existing ORM (ActiveRecord, Sequel, etc.)
17
+ - ✅ Optimize for your infrastructure
18
+ - ✅ Community can publish adapters as gems
19
+
20
+ **Any adapter** that implements the `Adapters::Base` interface will work seamlessly with:
21
+ - All memory tools (MemoryWrite, MemoryRead, etc.)
22
+ - Semantic search
23
+ - Defrag operations
24
+ - CLI commands
25
+
26
+ ---
27
+
28
+ ## Adapter Registry
29
+
30
+ SwarmMemory provides a registry system for custom adapters:
31
+
32
+ ```ruby
33
+ # Register your adapter when your app boots
34
+ SwarmMemory.register_adapter(:my_adapter, MyCustomAdapter)
35
+
36
+ # Now use it in configuration
37
+ agent :researcher do
38
+ memory do
39
+ adapter :my_adapter
40
+ option :custom_option, "value"
41
+ end
42
+ end
43
+
44
+ # Check available adapters
45
+ SwarmMemory.available_adapters
46
+ # => [:filesystem, :my_adapter]
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Adapter Interface
52
+
53
+ ### Required Methods
54
+
55
+ All adapters MUST inherit from `SwarmMemory::Adapters::Base` and implement these methods:
56
+
57
+ ```ruby
58
+ module SwarmMemory
59
+ module Adapters
60
+ class MyAdapter < Base
61
+ # Write an entry
62
+ #
63
+ # @param file_path [String] Logical path (e.g., "concept/ruby/classes.md")
64
+ # @param content [String] Markdown content
65
+ # @param title [String] Entry title
66
+ # @param embedding [Array<Float>, nil] Optional 384-dim embedding vector
67
+ # @param metadata [Hash, nil] Metadata (type, tags, confidence, etc.)
68
+ # @return [Core::Entry] The created entry
69
+ def write(file_path:, content:, title:, embedding: nil, metadata: nil)
70
+ # Implement storage logic
71
+ end
72
+
73
+ # Read entry content only
74
+ #
75
+ # @param file_path [String] Logical path
76
+ # @return [String] Content
77
+ # @raise [ArgumentError] If not found
78
+ def read(file_path:)
79
+ # Implement retrieval logic
80
+ end
81
+
82
+ # Read full entry with metadata
83
+ #
84
+ # @param file_path [String] Logical path
85
+ # @return [Core::Entry] Full entry object
86
+ # @raise [ArgumentError] If not found
87
+ def read_entry(file_path:)
88
+ # Implement full retrieval logic
89
+ end
90
+
91
+ # Delete an entry
92
+ #
93
+ # @param file_path [String] Logical path
94
+ # @return [void]
95
+ # @raise [ArgumentError] If not found
96
+ def delete(file_path:)
97
+ # Implement deletion logic
98
+ end
99
+
100
+ # List all entries
101
+ #
102
+ # @param prefix [String, nil] Optional prefix filter
103
+ # @return [Array<Hash>] Array of {path:, title:, size:, updated_at:}
104
+ def list(prefix: nil)
105
+ # Implement listing logic
106
+ end
107
+
108
+ # Search by glob pattern
109
+ #
110
+ # @param pattern [String] Glob pattern (e.g., "skill/**/*.md")
111
+ # @return [Array<Hash>] Matching entries
112
+ def glob(pattern:)
113
+ # Implement glob search
114
+ end
115
+
116
+ # Search by content regex
117
+ #
118
+ # @param pattern [String] Regex pattern
119
+ # @param case_insensitive [Boolean] Case-insensitive flag
120
+ # @param output_mode [String] "files_with_matches", "content", or "count"
121
+ # @return [Array] Results in requested format
122
+ def grep(pattern:, case_insensitive: false, output_mode: "files_with_matches")
123
+ # Implement grep search
124
+ end
125
+
126
+ # Clear all entries
127
+ #
128
+ # @return [void]
129
+ def clear
130
+ # Implement clear logic
131
+ end
132
+
133
+ # Get total storage size in bytes
134
+ #
135
+ # @return [Integer] Total size
136
+ def total_size
137
+ # Implement size calculation
138
+ end
139
+
140
+ # Get number of entries
141
+ #
142
+ # @return [Integer] Entry count
143
+ def size
144
+ # Implement count
145
+ end
146
+
147
+ # Get all entries (for defrag operations)
148
+ #
149
+ # @return [Hash<String, Core::Entry>] All entries keyed by path
150
+ def all_entries
151
+ # Implement bulk retrieval
152
+ end
153
+
154
+ # Semantic search by embedding vector (REQUIRED for semantic search)
155
+ #
156
+ # @param embedding [Array<Float>] Query embedding (384-dim)
157
+ # @param top_k [Integer] Number of results
158
+ # @param threshold [Float] Minimum similarity (0.0-1.0)
159
+ # @return [Array<Hash>] Results with :path, :similarity, :title, :metadata
160
+ def semantic_search(embedding:, top_k: 10, threshold: 0.0)
161
+ # Implement semantic search
162
+ end
163
+ end
164
+ end
165
+ end
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Configuration DSL
171
+
172
+ The memory configuration DSL supports passing options to custom adapters:
173
+
174
+ ```ruby
175
+ # Filesystem adapter (built-in)
176
+ agent :researcher do
177
+ memory do
178
+ adapter :filesystem
179
+ directory ".swarm/memory/researcher"
180
+ end
181
+ end
182
+
183
+ # Custom adapter with options
184
+ agent :researcher do
185
+ memory do
186
+ adapter :activerecord
187
+ option :namespace, "researcher"
188
+ option :table_name, "memory_entries"
189
+ end
190
+ end
191
+
192
+ # Options are passed to adapter's initialize method
193
+ # MyAdapter.new(namespace: "researcher", table_name: "memory_entries")
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Example: ActiveRecord Adapter (PostgreSQL/Rails)
199
+
200
+ Complete example using Rails with ActiveRecord and PostgreSQL:
201
+
202
+ ### Step 1: Migration
203
+
204
+ ```ruby
205
+ # db/migrate/20250126_create_memory_entries.rb
206
+ class CreateMemoryEntries < ActiveRecord::Migration[7.1]
207
+ def change
208
+ # Enable pgvector extension for embeddings
209
+ enable_extension 'vector'
210
+
211
+ create_table :memory_entries, id: false do |t|
212
+ t.string :namespace, null: false
213
+ t.string :path, null: false
214
+ t.text :content, null: false
215
+ t.string :title, null: false
216
+ t.column :embedding, :vector, limit: 384
217
+ t.jsonb :metadata, default: {}
218
+ t.integer :size, null: false
219
+ t.integer :hits, default: 0
220
+ t.timestamps
221
+ end
222
+
223
+ # Primary key on namespace + path
224
+ add_index :memory_entries, [:namespace, :path], unique: true
225
+
226
+ # Indexes for performance
227
+ add_index :memory_entries, :namespace
228
+ add_index :memory_entries, :metadata, using: :gin
229
+ add_index :memory_entries, :embedding, using: :hnsw, opclass: :vector_cosine_ops
230
+
231
+ # Full-text search index
232
+ execute <<-SQL
233
+ CREATE INDEX index_memory_entries_content_tsvector
234
+ ON memory_entries
235
+ USING GIN(to_tsvector('english', content))
236
+ SQL
237
+ end
238
+ end
239
+ ```
240
+
241
+ ### Step 2: Model
242
+
243
+ ```ruby
244
+ # app/models/memory_entry.rb
245
+ class MemoryEntry < ApplicationRecord
246
+ validates :path, :namespace, :content, :title, presence: true
247
+ validates :path, uniqueness: { scope: :namespace }
248
+
249
+ scope :in_namespace, ->(ns) { where(namespace: ns) }
250
+
251
+ scope :matching_content, ->(pattern, case_insensitive) {
252
+ operator = case_insensitive ? "~*" : "~"
253
+ where("content #{operator} ?", pattern)
254
+ }
255
+
256
+ def self.similar_to(embedding, threshold: 0.0, limit: 10)
257
+ where("embedding IS NOT NULL")
258
+ .where("1 - (embedding <=> ?) >= ?", embedding.to_s, threshold)
259
+ .order(Arel.sql("embedding <=> '#{embedding}'"))
260
+ .limit(limit)
261
+ end
262
+
263
+ def increment_hits!
264
+ increment!(:hits)
265
+ end
266
+ end
267
+ ```
268
+
269
+ ### Step 3: Adapter Implementation
270
+
271
+ See the complete ActiveRecord adapter implementation in the separate documentation file:
272
+ **[docs/v2/swarm_memory_adapters.md](../swarm_memory_adapters.md)**
273
+
274
+ The adapter includes:
275
+ - Full CRUD operations using ActiveRecord
276
+ - PostgreSQL full-text search for fast grep
277
+ - pgvector integration for semantic search
278
+ - Proper error handling and validation
279
+ - ~300 lines of production-ready code
280
+
281
+ ###Step 4: Register the Adapter
282
+
283
+ ```ruby
284
+ # config/initializers/swarm_memory.rb
285
+ require_relative '../../lib/activerecord_memory_adapter'
286
+
287
+ SwarmMemory.register_adapter(:activerecord, ActiveRecordMemoryAdapter)
288
+ ```
289
+
290
+ ### Step 5: Configure Your Agent
291
+
292
+ ```ruby
293
+ # swarm.rb
294
+ agent :researcher do
295
+ memory do
296
+ adapter :activerecord
297
+ option :namespace, "researcher"
298
+ end
299
+ end
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Legacy Example: Qdrant Adapter
305
+
306
+ > **Note**: This is a conceptual example. For production use with vector databases,
307
+ > implement and register your own adapter using the pattern above.
308
+
309
+ ```ruby
310
+ # Conceptual example - not maintained
311
+ class QdrantAdapter < SwarmMemory::Adapters::Base
312
+ def initialize(url:, collection:, api_key: nil)
313
+ super()
314
+ @client = Qdrant::Client.new(url: url, api_key: api_key)
315
+ @collection = collection
316
+ @total_size = 0
317
+
318
+ # Ensure collection exists
319
+ ensure_collection_exists
320
+ end
321
+
322
+ def write(file_path:, content:, title:, embedding: nil, metadata: nil)
323
+ # Calculate size
324
+ content_size = content.bytesize
325
+
326
+ # Store in Qdrant
327
+ @client.upsert(
328
+ collection_name: @collection,
329
+ points: [{
330
+ id: file_path, # Use path as ID
331
+ vector: embedding || [],
332
+ payload: {
333
+ content: content,
334
+ title: title,
335
+ size: content_size,
336
+ updated_at: Time.now.to_i,
337
+ metadata: metadata || {}
338
+ }
339
+ }]
340
+ )
341
+
342
+ @total_size += content_size
343
+
344
+ # Return entry
345
+ Core::Entry.new(
346
+ content: content,
347
+ title: title,
348
+ updated_at: Time.now,
349
+ size: content_size,
350
+ embedding: embedding,
351
+ metadata: metadata
352
+ )
353
+ end
354
+
355
+ def read(file_path:)
356
+ result = @client.retrieve(
357
+ collection_name: @collection,
358
+ ids: [file_path]
359
+ )
360
+
361
+ raise ArgumentError, "memory://#{file_path} not found" if result.empty?
362
+
363
+ result.first.payload["content"]
364
+ end
365
+
366
+ def read_entry(file_path:)
367
+ result = @client.retrieve(
368
+ collection_name: @collection,
369
+ ids: [file_path]
370
+ )
371
+
372
+ raise ArgumentError, "memory://#{file_path} not found" if result.empty?
373
+
374
+ point = result.first
375
+ payload = point.payload
376
+
377
+ Core::Entry.new(
378
+ content: payload["content"],
379
+ title: payload["title"],
380
+ updated_at: Time.at(payload["updated_at"]),
381
+ size: payload["size"],
382
+ embedding: point.vector,
383
+ metadata: payload["metadata"]
384
+ )
385
+ end
386
+
387
+ def delete(file_path:)
388
+ result = @client.delete(
389
+ collection_name: @collection,
390
+ points: [file_path]
391
+ )
392
+
393
+ raise ArgumentError, "memory://#{file_path} not found" unless result.ok?
394
+
395
+ entry = read_entry(file_path: file_path)
396
+ @total_size -= entry.size
397
+ end
398
+
399
+ def list(prefix: nil)
400
+ # Scroll through all points
401
+ results = @client.scroll(
402
+ collection_name: @collection,
403
+ limit: 1000
404
+ )
405
+
406
+ entries = results.points.map do |point|
407
+ {
408
+ path: point.id,
409
+ title: point.payload["title"],
410
+ size: point.payload["size"],
411
+ updated_at: Time.at(point.payload["updated_at"])
412
+ }
413
+ end
414
+
415
+ # Filter by prefix if provided
416
+ if prefix
417
+ entries.select { |e| e[:path].start_with?(prefix) }
418
+ else
419
+ entries
420
+ end
421
+ end
422
+
423
+ def glob(pattern:)
424
+ # Convert glob to regex
425
+ regex = glob_to_regex(pattern)
426
+
427
+ list.select { |entry| regex.match?(entry[:path]) }
428
+ end
429
+
430
+ def grep(pattern:, case_insensitive: false, output_mode: "files_with_matches")
431
+ flags = case_insensitive ? Regexp::IGNORECASE : 0
432
+ regex = Regexp.new(pattern, flags)
433
+
434
+ all_entries = all_entries()
435
+
436
+ case output_mode
437
+ when "files_with_matches"
438
+ all_entries.keys.select { |path| regex.match?(all_entries[path].content) }
439
+ when "content"
440
+ # Return matching lines with line numbers
441
+ all_entries.map do |path, entry|
442
+ matches = entry.content.lines.each_with_index.select { |line, _| regex.match?(line) }
443
+ next if matches.empty?
444
+
445
+ {
446
+ path: path,
447
+ matches: matches.map { |line, idx| { line_number: idx + 1, content: line.chomp } }
448
+ }
449
+ end.compact
450
+ when "count"
451
+ all_entries.map do |path, entry|
452
+ count = entry.content.scan(regex).size
453
+ next if count <= 0
454
+
455
+ { path: path, count: count }
456
+ end.compact
457
+ end
458
+ end
459
+
460
+ def clear
461
+ @client.delete_collection(collection_name: @collection)
462
+ ensure_collection_exists
463
+ @total_size = 0
464
+ end
465
+
466
+ def total_size
467
+ @total_size
468
+ end
469
+
470
+ def size
471
+ list.size
472
+ end
473
+
474
+ def all_entries
475
+ results = @client.scroll(
476
+ collection_name: @collection,
477
+ limit: 10000
478
+ )
479
+
480
+ results.points.each_with_object({}) do |point, hash|
481
+ hash[point.id] = Core::Entry.new(
482
+ content: point.payload["content"],
483
+ title: point.payload["title"],
484
+ updated_at: Time.at(point.payload["updated_at"]),
485
+ size: point.payload["size"],
486
+ embedding: point.vector,
487
+ metadata: point.payload["metadata"]
488
+ )
489
+ end
490
+ end
491
+
492
+ # Semantic search (Qdrant's strength!)
493
+ def semantic_search(embedding:, top_k: 10, threshold: 0.0)
494
+ result = @client.search(
495
+ collection_name: @collection,
496
+ vector: embedding,
497
+ limit: top_k,
498
+ score_threshold: threshold
499
+ )
500
+
501
+ result.map do |hit|
502
+ {
503
+ path: hit.id,
504
+ similarity: hit.score,
505
+ title: hit.payload["title"],
506
+ size: hit.payload["size"],
507
+ updated_at: Time.at(hit.payload["updated_at"]),
508
+ metadata: hit.payload["metadata"]
509
+ }
510
+ end
511
+ end
512
+
513
+ private
514
+
515
+ def ensure_collection_exists
516
+ @client.create_collection(
517
+ collection_name: @collection,
518
+ vectors: {
519
+ size: 384, # all-MiniLM-L6-v2 dimensions
520
+ distance: "Cosine"
521
+ }
522
+ )
523
+ rescue Qdrant::Errors::ApiError => e
524
+ # Collection already exists
525
+ raise unless e.message.include?("already exists")
526
+ end
527
+
528
+ def glob_to_regex(pattern)
529
+ # Convert glob wildcards to regex
530
+ regex_pattern = pattern
531
+ .gsub("**", "DOUBLE_STAR")
532
+ .gsub("*", "[^/]*")
533
+ .gsub("DOUBLE_STAR", ".*")
534
+ .gsub("?", ".")
535
+
536
+ Regexp.new("^#{regex_pattern}$")
537
+ end
538
+ end
539
+ end
540
+ end
541
+ ```
542
+
543
+ ---
544
+
545
+ ## Adapter Comparison
546
+
547
+ | Feature | FilesystemAdapter (built-in) | ActiveRecord (example) | Vector DB (custom) |
548
+ |---------|------------------------------|------------------------|-------------------|
549
+ | **Storage** | Local files | PostgreSQL/MySQL | Qdrant/Milvus/Weaviate |
550
+ | **Semantic Search** | In-memory cosine | pgvector extension | Native vector search |
551
+ | **Scalability** | ~5K entries | Hundreds of thousands | Millions of entries |
552
+ | **Setup** | Zero config | Rails + migration | External service |
553
+ | **Dependencies** | None | activerecord, pg/mysql2 | qdrant-ruby, etc. |
554
+ | **Performance** | Good (<5K entries) | Good (with indexes) | Excellent (any size) |
555
+ | **Cost** | Free | Managed DB (~$10/mo) | Self-hosted or cloud |
556
+ | **Availability** | Built-in | You implement | You implement |
557
+
558
+ > **Note**: Only FilesystemAdapter is built-in. All other adapters are examples you implement and register using `SwarmMemory.register_adapter`.
559
+
560
+ ---
561
+
562
+ ## Testing Adapters
563
+
564
+ ### Unit Tests
565
+
566
+ ```ruby
567
+ class MyAdapterTest < Minitest::Test
568
+ def setup
569
+ @adapter = MyAdapter.new(...)
570
+ end
571
+
572
+ def test_write_and_read
573
+ entry = @adapter.write(
574
+ file_path: "test/entry.md",
575
+ content: "Test content",
576
+ title: "Test",
577
+ metadata: { "type" => "concept" }
578
+ )
579
+
580
+ assert_equal "Test content", @adapter.read(file_path: "test/entry.md")
581
+ end
582
+
583
+ def test_semantic_search
584
+ # Write entries with embeddings
585
+ @adapter.write(
586
+ file_path: "test/entry1.md",
587
+ content: "Ruby classes",
588
+ title: "Classes",
589
+ embedding: [0.1, 0.2, ...], # 384-dim vector
590
+ metadata: { "type" => "concept" }
591
+ )
592
+
593
+ # Search
594
+ query_embedding = [0.1, 0.2, ...] # Similar vector
595
+ results = @adapter.semantic_search(
596
+ embedding: query_embedding,
597
+ top_k: 5,
598
+ threshold: 0.5
599
+ )
600
+
601
+ assert_equal 1, results.size
602
+ assert_equal "test/entry1.md", results.first[:path]
603
+ assert results.first[:similarity] > 0.5
604
+ end
605
+
606
+ def test_glob_search
607
+ @adapter.write(file_path: "concept/ruby/classes.md", ...)
608
+ @adapter.write(file_path: "concept/ruby/modules.md", ...)
609
+
610
+ results = @adapter.glob(pattern: "concept/ruby/*")
611
+
612
+ assert_equal 2, results.size
613
+ end
614
+ end
615
+ ```
616
+
617
+ ### Integration Tests
618
+
619
+ ```ruby
620
+ def test_adapter_works_with_storage
621
+ adapter = MyAdapter.new(...)
622
+ embedder = SwarmMemory::Embeddings::InformersEmbedder.new
623
+ storage = SwarmMemory::Core::Storage.new(adapter: adapter, embedder: embedder)
624
+
625
+ # Test via Storage API
626
+ storage.write(
627
+ file_path: "test/entry.md",
628
+ content: "Test",
629
+ title: "Test"
630
+ )
631
+
632
+ content = storage.read(file_path: "test/entry.md")
633
+ assert_equal "Test", content
634
+ end
635
+
636
+ def test_adapter_works_with_memory_tools
637
+ adapter = MyAdapter.new(...)
638
+ storage = SwarmMemory::Core::Storage.new(adapter: adapter)
639
+
640
+ tool = SwarmMemory::Tools::MemoryWrite.new(storage: storage, agent_name: :test)
641
+
642
+ result = tool.execute(
643
+ file_path: "test/entry.md",
644
+ content: "Content",
645
+ title: "Title",
646
+ type: "concept",
647
+ # ... all required params
648
+ )
649
+
650
+ assert_match /Stored at memory/, result
651
+ end
652
+ ```
653
+
654
+ ---
655
+
656
+ ## FilesystemAdapter Deep Dive
657
+
658
+ Study the reference implementation:
659
+
660
+ ### File Structure
661
+
662
+ ```
663
+ .swarm/memory/
664
+ ├── concept--ruby--classes.md # Markdown content
665
+ ├── concept--ruby--classes.yml # Metadata (YAML)
666
+ ├── concept--ruby--classes.emb # Embedding (binary)
667
+ └── .lock # File lock
668
+ ```
669
+
670
+ **Path Flattening:**
671
+ - Logical: `concept/ruby/classes.md`
672
+ - Disk: `concept--ruby--classes.md`
673
+ - Why: Git-friendly, avoids nested directories
674
+
675
+ ### Key Implementation Details
676
+
677
+ ```ruby
678
+ class FilesystemAdapter < Base
679
+ def initialize(directory:)
680
+ @directory = File.expand_path(directory)
681
+ @semaphore = Async::Semaphore.new(1) # Fiber-safe locking
682
+ @lock_file_path = File.join(@directory, ".lock")
683
+ @index = build_index # In-memory index for fast lookups
684
+ end
685
+
686
+ def write(file_path:, content:, title:, embedding: nil, metadata: nil)
687
+ with_write_lock do
688
+ @semaphore.acquire do
689
+ # Flatten path for disk storage
690
+ disk_path = flatten_path(file_path)
691
+
692
+ # Write content (.md file)
693
+ File.write(File.join(@directory, "#{disk_path}.md"), content)
694
+
695
+ # Write metadata (.yml file)
696
+ yaml_data = {
697
+ title: title,
698
+ file_path: file_path,
699
+ updated_at: Time.now,
700
+ size: content.bytesize,
701
+ metadata: metadata,
702
+ embedding_checksum: embedding ? checksum(embedding) : nil
703
+ }
704
+ File.write(File.join(@directory, "#{disk_path}.yml"), YAML.dump(yaml_data))
705
+
706
+ # Write embedding (.emb file, binary)
707
+ if embedding
708
+ File.write(File.join(@directory, "#{disk_path}.emb"), embedding.pack("f*"))
709
+ end
710
+
711
+ # Update in-memory index
712
+ @index[file_path] = {...}
713
+ end
714
+ end
715
+ end
716
+
717
+ # Cross-process file locking
718
+ def with_write_lock
719
+ File.open(@lock_file_path, File::RDWR | File::CREAT) do |lock_file|
720
+ lock_file.flock(File::LOCK_EX) # Exclusive lock
721
+ yield
722
+ ensure
723
+ lock_file.flock(File::LOCK_UN) # Release
724
+ end
725
+ end
726
+ end
727
+ ```
728
+
729
+ **Optimizations:**
730
+ - In-memory index for fast lookups
731
+ - File locking for concurrent access
732
+ - Binary embeddings (not JSON)
733
+ - Lazy loading (index built on init)
734
+
735
+ ---
736
+
737
+ ## Vector Database Adapters
738
+
739
+ ### Qdrant Example (Production-Ready)
740
+
741
+ See full example above in "Example: QdrantAdapter" section.
742
+
743
+ **Benefits:**
744
+ - Native vector search (faster, more scalable)
745
+ - Built-in similarity algorithms
746
+ - Filtering by metadata
747
+ - Horizontal scaling
748
+
749
+ **Trade-offs:**
750
+ - Requires external service
751
+ - More complex setup
752
+ - Additional dependency
753
+
754
+ ### Milvus Adapter
755
+
756
+ ```ruby
757
+ class MilvusAdapter < Base
758
+ def initialize(host:, port:, collection:)
759
+ @client = Milvus::Client.new(host: host, port: port)
760
+ @collection = collection
761
+ end
762
+
763
+ def semantic_search(embedding:, top_k:, threshold:)
764
+ @client.search(
765
+ collection_name: @collection,
766
+ vectors: [embedding],
767
+ top_k: top_k,
768
+ params: { nprobe: 10 }
769
+ ).map do |result|
770
+ {
771
+ path: result.id,
772
+ similarity: result.distance,
773
+ # ... map other fields
774
+ }
775
+ end
776
+ end
777
+ end
778
+ ```
779
+
780
+ ---
781
+
782
+ ## Relational Database Adapters
783
+
784
+ ### PostgreSQL with pgvector
785
+
786
+ ```ruby
787
+ class PostgresAdapter < Base
788
+ def initialize(connection_string:)
789
+ @conn = PG.connect(connection_string)
790
+
791
+ # Ensure pgvector extension and table exist
792
+ @conn.exec("CREATE EXTENSION IF NOT EXISTS vector")
793
+ @conn.exec(<<~SQL)
794
+ CREATE TABLE IF NOT EXISTS memories (
795
+ file_path TEXT PRIMARY KEY,
796
+ content TEXT NOT NULL,
797
+ title TEXT NOT NULL,
798
+ embedding vector(384),
799
+ metadata JSONB,
800
+ updated_at TIMESTAMP DEFAULT NOW()
801
+ )
802
+ SQL
803
+
804
+ # Create index for vector similarity search
805
+ @conn.exec("CREATE INDEX IF NOT EXISTS memories_embedding_idx ON memories USING ivfflat (embedding vector_cosine_ops)")
806
+ end
807
+
808
+ def write(file_path:, content:, title:, embedding: nil, metadata: nil)
809
+ @conn.exec_params(
810
+ "INSERT INTO memories (file_path, content, title, embedding, metadata, updated_at)
811
+ VALUES ($1, $2, $3, $4, $5, $6)
812
+ ON CONFLICT (file_path) DO UPDATE
813
+ SET content = $2, title = $3, embedding = $4, metadata = $5, updated_at = $6",
814
+ [file_path, content, title, embedding&.to_s, metadata.to_json, Time.now]
815
+ )
816
+
817
+ # Return entry
818
+ Core::Entry.new(...)
819
+ end
820
+
821
+ def semantic_search(embedding:, top_k:, threshold:)
822
+ # pgvector cosine similarity
823
+ result = @conn.exec_params(
824
+ "SELECT file_path, title, metadata,
825
+ 1 - (embedding <=> $1::vector) AS similarity
826
+ FROM memories
827
+ WHERE (1 - (embedding <=> $1::vector)) >= $2
828
+ ORDER BY embedding <=> $1::vector
829
+ LIMIT $3",
830
+ [embedding.to_s, threshold, top_k]
831
+ )
832
+
833
+ result.map do |row|
834
+ {
835
+ path: row["file_path"],
836
+ similarity: row["similarity"].to_f,
837
+ title: row["title"],
838
+ metadata: JSON.parse(row["metadata"])
839
+ }
840
+ end
841
+ end
842
+
843
+ def glob(pattern:)
844
+ # Convert glob to SQL LIKE pattern
845
+ like_pattern = pattern.gsub("**", "%").gsub("*", "%").gsub("?", "_")
846
+
847
+ result = @conn.exec_params(
848
+ "SELECT file_path, title, LENGTH(content) as size, updated_at
849
+ FROM memories
850
+ WHERE file_path LIKE $1",
851
+ [like_pattern]
852
+ )
853
+
854
+ result.map do |row|
855
+ {
856
+ path: row["file_path"],
857
+ title: row["title"],
858
+ size: row["size"].to_i,
859
+ updated_at: Time.parse(row["updated_at"])
860
+ }
861
+ end
862
+ end
863
+ end
864
+ ```
865
+
866
+ ---
867
+
868
+ ## Adapter Checklist
869
+
870
+ When building an adapter, ensure:
871
+
872
+ ### Functional Requirements
873
+
874
+ - [ ] All 14 required methods implemented
875
+ - [ ] Raises `ArgumentError` when entry not found
876
+ - [ ] Returns `Core::Entry` objects from read_entry
877
+ - [ ] Handles nil embeddings gracefully
878
+ - [ ] Handles nil metadata gracefully
879
+ - [ ] Supports prefix filtering in list()
880
+ - [ ] Glob patterns work correctly
881
+ - [ ] Grep supports all 3 output modes
882
+ - [ ] semantic_search returns sorted by similarity (descending)
883
+
884
+ ### Performance Requirements
885
+
886
+ - [ ] Lookups use indexes (not full scans)
887
+ - [ ] Writes are atomic (no partial updates)
888
+ - [ ] Concurrent access handled safely
889
+ - [ ] Embeddings stored efficiently (binary, not JSON)
890
+ - [ ] list() is paginated or limited (for large datasets)
891
+
892
+ ### Quality Requirements
893
+
894
+ - [ ] Thread-safe (or document as single-threaded only)
895
+ - [ ] Fiber-safe (if using Async)
896
+ - [ ] Errors have helpful messages
897
+ - [ ] Cleanup on adapter destruction
898
+ - [ ] Configuration validated on init
899
+ - [ ] Total size tracking (if possible)
900
+
901
+ ---
902
+
903
+ ## Using Custom Adapters
904
+
905
+ ### Step 1: Implement Your Adapter
906
+
907
+ ```ruby
908
+ # lib/my_custom_adapter.rb
909
+ class MyCustomAdapter < SwarmMemory::Adapters::Base
910
+ def initialize(url:, api_key: nil, **options)
911
+ super()
912
+ # Your initialization code
913
+ end
914
+
915
+ # Implement all required methods...
916
+ # See Adapters::Base for full interface
917
+ end
918
+ ```
919
+
920
+ ### Step 2: Register Your Adapter
921
+
922
+ ```ruby
923
+ # config/initializers/swarm_memory.rb (Rails)
924
+ # or at the top of your swarm.rb file
925
+
926
+ require_relative 'lib/my_custom_adapter'
927
+
928
+ SwarmMemory.register_adapter(:my_adapter, MyCustomAdapter)
929
+ ```
930
+
931
+ ### Step 3: Configure Agent to Use It
932
+
933
+ ```ruby
934
+ # swarm.rb
935
+ agent :assistant do
936
+ memory do
937
+ adapter :my_adapter # Uses registered adapter
938
+ option :url, "http://localhost:8080"
939
+ option :api_key, ENV["API_KEY"]
940
+ end
941
+ end
942
+ ```
943
+
944
+ ### With Storage Directly (Without SwarmSDK)
945
+
946
+ ```ruby
947
+ require 'swarm_memory'
948
+
949
+ # Create your adapter
950
+ adapter = MyCustomAdapter.new(url: "http://localhost:6333")
951
+
952
+ # Create storage with embedder
953
+ embedder = SwarmMemory::Embeddings::InformersEmbedder.new
954
+ storage = SwarmMemory::Core::Storage.new(adapter: adapter, embedder: embedder)
955
+
956
+ # Use memory tools directly
957
+ tools = SwarmMemory.tools_for(storage: storage, agent_name: :test)
958
+ ```
959
+
960
+ ---
961
+
962
+ ## Publishing Your Adapter
963
+
964
+ Consider publishing your adapter as a gem for community use:
965
+
966
+ ```ruby
967
+ # my_adapter_gem.gemspec
968
+ Gem::Specification.new do |spec|
969
+ spec.name = "swarm_memory_my_adapter"
970
+ spec.version = "1.0.0"
971
+ spec.summary = "MyAdapter for SwarmMemory"
972
+
973
+ spec.add_dependency "swarm_memory", "~> 1.0"
974
+ spec.add_dependency "my_database_client", "~> 2.0"
975
+ end
976
+ ```
977
+
978
+ Users can then:
979
+ ```bash
980
+ gem install swarm_memory_my_adapter
981
+ ```
982
+
983
+ ```ruby
984
+ # In their app
985
+ require 'swarm_memory_my_adapter'
986
+ SwarmMemory.register_adapter(:my_adapter, MyCustomAdapter)
987
+ ```
988
+
989
+ ---
990
+
991
+ ## See Also
992
+
993
+ - **Complete ActiveRecord Example:** [docs/v2/swarm_memory_adapters.md](../swarm_memory_adapters.md)
994
+ - **Base Class:** `lib/swarm_memory/adapters/base.rb` - Interface definition
995
+ - **Reference Implementation:** `lib/swarm_memory/adapters/filesystem_adapter.rb`
996
+ - **Entry Class:** `lib/swarm_memory/core/entry.rb` - Entry object spec
997
+ - **Storage Class:** `lib/swarm_memory/core/storage.rb` - Storage orchestration
998
+ - **Registry API:** `lib/swarm_memory.rb` - register_adapter, adapter_for, available_adapters