htm 0.0.1 → 0.0.10

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/.aigcm_msg +1 -0
  3. data/.architecture/reviews/comprehensive-codebase-review.md +577 -0
  4. data/.claude/settings.local.json +92 -0
  5. data/.envrc +1 -0
  6. data/.irbrc +283 -80
  7. data/.tbls.yml +31 -0
  8. data/CHANGELOG.md +314 -16
  9. data/CLAUDE.md +603 -0
  10. data/README.md +76 -5
  11. data/Rakefile +5 -0
  12. data/SETUP.md +132 -101
  13. data/db/migrate/{20250101000001_enable_extensions.rb → 00001_enable_extensions.rb} +0 -1
  14. data/db/migrate/00002_create_robots.rb +11 -0
  15. data/db/migrate/00003_create_file_sources.rb +20 -0
  16. data/db/migrate/00004_create_nodes.rb +65 -0
  17. data/db/migrate/00005_create_tags.rb +13 -0
  18. data/db/migrate/00006_create_node_tags.rb +18 -0
  19. data/db/migrate/00007_create_robot_nodes.rb +26 -0
  20. data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +12 -0
  21. data/db/schema.sql +390 -36
  22. data/docs/api/database.md +19 -232
  23. data/docs/api/embedding-service.md +1 -7
  24. data/docs/api/htm.md +305 -364
  25. data/docs/api/index.md +1 -7
  26. data/docs/api/long-term-memory.md +342 -590
  27. data/docs/api/yard/HTM/ActiveRecordConfig.md +23 -0
  28. data/docs/api/yard/HTM/AuthorizationError.md +11 -0
  29. data/docs/api/yard/HTM/CircuitBreaker.md +92 -0
  30. data/docs/api/yard/HTM/CircuitBreakerOpenError.md +34 -0
  31. data/docs/api/yard/HTM/Configuration.md +175 -0
  32. data/docs/api/yard/HTM/Database.md +99 -0
  33. data/docs/api/yard/HTM/DatabaseError.md +14 -0
  34. data/docs/api/yard/HTM/EmbeddingError.md +18 -0
  35. data/docs/api/yard/HTM/EmbeddingService.md +58 -0
  36. data/docs/api/yard/HTM/Error.md +11 -0
  37. data/docs/api/yard/HTM/JobAdapter.md +39 -0
  38. data/docs/api/yard/HTM/LongTermMemory.md +342 -0
  39. data/docs/api/yard/HTM/NotFoundError.md +17 -0
  40. data/docs/api/yard/HTM/Observability.md +107 -0
  41. data/docs/api/yard/HTM/QueryTimeoutError.md +19 -0
  42. data/docs/api/yard/HTM/Railtie.md +27 -0
  43. data/docs/api/yard/HTM/ResourceExhaustedError.md +13 -0
  44. data/docs/api/yard/HTM/TagError.md +18 -0
  45. data/docs/api/yard/HTM/TagService.md +67 -0
  46. data/docs/api/yard/HTM/Timeframe/Result.md +24 -0
  47. data/docs/api/yard/HTM/Timeframe.md +40 -0
  48. data/docs/api/yard/HTM/TimeframeExtractor/Result.md +24 -0
  49. data/docs/api/yard/HTM/TimeframeExtractor.md +45 -0
  50. data/docs/api/yard/HTM/ValidationError.md +20 -0
  51. data/docs/api/yard/HTM/WorkingMemory.md +131 -0
  52. data/docs/api/yard/HTM.md +80 -0
  53. data/docs/api/yard/index.csv +179 -0
  54. data/docs/api/yard-reference.md +51 -0
  55. data/docs/architecture/adrs/001-postgresql-timescaledb.md +1 -1
  56. data/docs/architecture/adrs/003-ollama-embeddings.md +1 -1
  57. data/docs/architecture/adrs/010-redis-working-memory-rejected.md +2 -27
  58. data/docs/architecture/adrs/index.md +2 -13
  59. data/docs/architecture/hive-mind.md +165 -166
  60. data/docs/architecture/index.md +2 -2
  61. data/docs/architecture/overview.md +5 -171
  62. data/docs/architecture/two-tier-memory.md +1 -35
  63. data/docs/assets/images/adr-010-current-architecture.svg +37 -0
  64. data/docs/assets/images/adr-010-proposed-architecture.svg +48 -0
  65. data/docs/assets/images/adr-dependency-tree.svg +93 -0
  66. data/docs/assets/images/class-hierarchy.svg +55 -0
  67. data/docs/assets/images/exception-hierarchy.svg +45 -0
  68. data/docs/assets/images/htm-architecture-overview.svg +83 -0
  69. data/docs/assets/images/htm-complete-memory-flow.svg +160 -0
  70. data/docs/assets/images/htm-context-assembly-flow.svg +148 -0
  71. data/docs/assets/images/htm-eviction-process.svg +141 -0
  72. data/docs/assets/images/htm-memory-addition-flow.svg +138 -0
  73. data/docs/assets/images/htm-memory-recall-flow.svg +152 -0
  74. data/docs/assets/images/htm-node-states.svg +123 -0
  75. data/docs/assets/images/project-structure.svg +78 -0
  76. data/docs/assets/images/test-directory-structure.svg +38 -0
  77. data/{dbdoc → docs/database}/README.md +127 -125
  78. data/docs/database/public.file_sources.md +42 -0
  79. data/docs/database/public.file_sources.svg +211 -0
  80. data/{dbdoc → docs/database}/public.node_tags.md +7 -8
  81. data/docs/database/public.node_tags.svg +239 -0
  82. data/{dbdoc → docs/database}/public.nodes.md +22 -17
  83. data/docs/database/public.nodes.svg +271 -0
  84. data/docs/database/public.robot_nodes.md +46 -0
  85. data/docs/database/public.robot_nodes.svg +243 -0
  86. data/{dbdoc → docs/database}/public.robots.md +2 -3
  87. data/docs/database/public.robots.svg +161 -0
  88. data/docs/database/public.tags.svg +139 -0
  89. data/{dbdoc → docs/database}/schema.json +941 -630
  90. data/docs/database/schema.svg +282 -0
  91. data/docs/development/index.md +1 -29
  92. data/docs/development/schema.md +134 -309
  93. data/docs/development/testing.md +1 -9
  94. data/docs/getting-started/index.md +47 -0
  95. data/docs/{installation.md → getting-started/installation.md} +2 -2
  96. data/docs/{quick-start.md → getting-started/quick-start.md} +5 -5
  97. data/docs/guides/adding-memories.md +295 -643
  98. data/docs/guides/recalling-memories.md +36 -1
  99. data/docs/guides/search-strategies.md +85 -51
  100. data/docs/images/htm-er-diagram.svg +156 -0
  101. data/docs/index.md +16 -31
  102. data/docs/multi_framework_support.md +4 -4
  103. data/examples/README.md +280 -0
  104. data/examples/basic_usage.rb +18 -16
  105. data/examples/cli_app/htm_cli.rb +146 -8
  106. data/examples/cli_app/temp.log +93 -0
  107. data/examples/custom_llm_configuration.rb +1 -2
  108. data/examples/example_app/app.rb +11 -14
  109. data/examples/file_loader_usage.rb +177 -0
  110. data/examples/robot_groups/lib/robot_group.rb +419 -0
  111. data/examples/robot_groups/lib/working_memory_channel.rb +140 -0
  112. data/examples/robot_groups/multi_process.rb +286 -0
  113. data/examples/robot_groups/robot_worker.rb +136 -0
  114. data/examples/robot_groups/same_process.rb +229 -0
  115. data/examples/sinatra_app/Gemfile +1 -0
  116. data/examples/sinatra_app/Gemfile.lock +166 -0
  117. data/examples/sinatra_app/app.rb +219 -24
  118. data/examples/timeframe_demo.rb +276 -0
  119. data/lib/htm/active_record_config.rb +10 -3
  120. data/lib/htm/circuit_breaker.rb +202 -0
  121. data/lib/htm/configuration.rb +313 -80
  122. data/lib/htm/database.rb +67 -36
  123. data/lib/htm/embedding_service.rb +39 -2
  124. data/lib/htm/errors.rb +131 -11
  125. data/lib/htm/{sinatra.rb → integrations/sinatra.rb} +87 -12
  126. data/lib/htm/job_adapter.rb +10 -3
  127. data/lib/htm/jobs/generate_embedding_job.rb +5 -4
  128. data/lib/htm/jobs/generate_tags_job.rb +4 -0
  129. data/lib/htm/loaders/markdown_loader.rb +263 -0
  130. data/lib/htm/loaders/paragraph_chunker.rb +112 -0
  131. data/lib/htm/long_term_memory.rb +601 -321
  132. data/lib/htm/models/file_source.rb +99 -0
  133. data/lib/htm/models/node.rb +116 -12
  134. data/lib/htm/models/robot.rb +53 -4
  135. data/lib/htm/models/robot_node.rb +51 -0
  136. data/lib/htm/models/tag.rb +302 -0
  137. data/lib/htm/observability.rb +395 -0
  138. data/lib/htm/tag_service.rb +60 -3
  139. data/lib/htm/tasks.rb +29 -0
  140. data/lib/htm/timeframe.rb +194 -0
  141. data/lib/htm/timeframe_extractor.rb +307 -0
  142. data/lib/htm/version.rb +1 -1
  143. data/lib/htm/working_memory.rb +165 -70
  144. data/lib/htm.rb +352 -133
  145. data/lib/tasks/doc.rake +300 -0
  146. data/lib/tasks/files.rake +299 -0
  147. data/lib/tasks/htm.rake +188 -2
  148. data/lib/tasks/jobs.rake +10 -12
  149. data/lib/tasks/tags.rake +194 -0
  150. data/mkdocs.yml +91 -9
  151. data/notes/ARCHITECTURE_REVIEW.md +1167 -0
  152. data/notes/IMPLEMENTATION_SUMMARY.md +606 -0
  153. data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +451 -0
  154. data/notes/next_steps.md +100 -0
  155. data/notes/plan.md +627 -0
  156. data/notes/tag_ontology_enhancement_ideas.md +222 -0
  157. data/notes/timescaledb_removal_summary.md +200 -0
  158. metadata +177 -37
  159. data/db/migrate/20250101000002_create_robots.rb +0 -14
  160. data/db/migrate/20250101000003_create_nodes.rb +0 -42
  161. data/db/migrate/20250101000005_create_tags.rb +0 -38
  162. data/db/migrate/20250101000007_add_node_vector_indexes.rb +0 -30
  163. data/dbdoc/public.node_tags.svg +0 -112
  164. data/dbdoc/public.nodes.svg +0 -118
  165. data/dbdoc/public.robots.svg +0 -90
  166. data/dbdoc/public.tags.svg +0 -60
  167. data/dbdoc/schema.svg +0 -154
  168. data/{dbdoc → docs/database}/public.node_stats.md +0 -0
  169. data/{dbdoc → docs/database}/public.node_stats.svg +0 -0
  170. data/{dbdoc → docs/database}/public.nodes_tags.md +0 -0
  171. data/{dbdoc → docs/database}/public.nodes_tags.svg +0 -0
  172. data/{dbdoc → docs/database}/public.ontology_structure.md +0 -0
  173. data/{dbdoc → docs/database}/public.ontology_structure.svg +0 -0
  174. data/{dbdoc → docs/database}/public.operations_log.md +0 -0
  175. data/{dbdoc → docs/database}/public.operations_log.svg +0 -0
  176. data/{dbdoc → docs/database}/public.relationships.md +0 -0
  177. data/{dbdoc → docs/database}/public.relationships.svg +0 -0
  178. data/{dbdoc → docs/database}/public.robot_activity.md +0 -0
  179. data/{dbdoc → docs/database}/public.robot_activity.svg +0 -0
  180. data/{dbdoc → docs/database}/public.schema_migrations.md +0 -0
  181. data/{dbdoc → docs/database}/public.schema_migrations.svg +0 -0
  182. data/{dbdoc → docs/database}/public.tags.md +3 -3
  183. /data/{dbdoc → docs/database}/public.topic_relationships.md +0 -0
  184. /data/{dbdoc → docs/database}/public.topic_relationships.svg +0 -0
data/docs/api/htm.md CHANGED
@@ -7,15 +7,15 @@ The main interface for HTM's intelligent memory management system.
7
7
  `HTM` is the primary class that orchestrates the two-tier memory system:
8
8
 
9
9
  - **Working Memory**: Token-limited active context for immediate LLM use
10
- - **Long-term Memory**: Durable PostgreSQL storage
10
+ - **Long-term Memory**: Durable PostgreSQL storage with vector embeddings
11
11
 
12
12
  Key features:
13
13
 
14
14
  - Never forgets unless explicitly told (`forget`)
15
15
  - RAG-based retrieval (temporal + semantic search)
16
- - Multi-robot "hive mind" - all robots share global memory
17
- - Relationship graphs for knowledge connections
18
- - Time-series optimized with TimescaleDB
16
+ - Multi-robot "hive mind" - all robots share global memory via content deduplication
17
+ - Hierarchical tagging for knowledge organization
18
+ - Tag-enhanced hybrid search for improved relevance
19
19
 
20
20
  ## Class Definition
21
21
 
@@ -34,11 +34,12 @@ Create a new HTM instance.
34
34
  ```ruby
35
35
  HTM.new(
36
36
  working_memory_size: 128_000,
37
- robot_id: nil,
38
37
  robot_name: nil,
39
38
  db_config: nil,
40
- embedding_service: :ollama,
41
- embedding_model: 'gpt-oss'
39
+ db_pool_size: 5,
40
+ db_query_timeout: 30_000,
41
+ db_cache_size: 1000,
42
+ db_cache_ttl: 300
42
43
  )
43
44
  ```
44
45
 
@@ -47,11 +48,12 @@ HTM.new(
47
48
  | Parameter | Type | Default | Description |
48
49
  |-----------|------|---------|-------------|
49
50
  | `working_memory_size` | Integer | `128_000` | Maximum tokens for working memory |
50
- | `robot_id` | String, nil | Auto-generated UUID | Unique identifier for this robot |
51
- | `robot_name` | String, nil | `"robot_#{id[0..7]}"` | Human-readable name |
51
+ | `robot_name` | String, nil | `"robot_#{uuid[0..7]}"` | Human-readable name |
52
52
  | `db_config` | Hash, nil | From `ENV['HTM_DBURL']` | Database configuration |
53
- | `embedding_service` | Symbol | `:ollama` | Embedding provider (`:ollama`, `:openai`, `:cohere`, `:local`) |
54
- | `embedding_model` | String | `'gpt-oss'` | Model name for embeddings |
53
+ | `db_pool_size` | Integer | `5` | Database connection pool size |
54
+ | `db_query_timeout` | Integer | `30_000` | Query timeout in milliseconds |
55
+ | `db_cache_size` | Integer | `1000` | Query cache size (0 to disable) |
56
+ | `db_cache_ttl` | Integer | `300` | Cache TTL in seconds |
55
57
 
56
58
  #### Returns
57
59
 
@@ -69,14 +71,7 @@ htm = HTM.new(
69
71
  working_memory_size: 256_000
70
72
  )
71
73
 
72
- # OpenAI embeddings
73
- htm = HTM.new(
74
- robot_name: "Research Bot",
75
- embedding_service: :openai,
76
- embedding_model: 'text-embedding-3-small'
77
- )
78
-
79
- # Custom database
74
+ # Custom database configuration
80
75
  htm = HTM.new(
81
76
  db_config: {
82
77
  host: 'localhost',
@@ -86,6 +81,12 @@ htm = HTM.new(
86
81
  password: 'secret'
87
82
  }
88
83
  )
84
+
85
+ # With caching disabled
86
+ htm = HTM.new(
87
+ robot_name: "No Cache Bot",
88
+ db_cache_size: 0
89
+ )
89
90
  ```
90
91
 
91
92
  ---
@@ -94,13 +95,13 @@ htm = HTM.new(
94
95
 
95
96
  ### `robot_id` {: #robot_id }
96
97
 
97
- Unique identifier for this robot instance.
98
+ Unique integer identifier for this robot instance.
98
99
 
99
- - **Type**: String (UUID format)
100
+ - **Type**: Integer
100
101
  - **Read-only**: Yes
101
102
 
102
103
  ```ruby
103
- htm.robot_id # => "a1b2c3d4-e5f6-..."
104
+ htm.robot_id # => 42
104
105
  ```
105
106
 
106
107
  ### `robot_name` {: #robot_name }
@@ -140,106 +141,102 @@ htm.long_term_memory.stats # => {...}
140
141
 
141
142
  ## Public Methods
142
143
 
143
- ### `add_node(key, value, **options)` {: #add_node }
144
+ ### `remember(content, tags:, metadata:)` {: #remember }
144
145
 
145
- Add a new memory node to both working and long-term memory.
146
+ Remember new information by storing it in long-term memory.
146
147
 
147
148
  ```ruby
148
- add_node(key, value,
149
- type: nil,
150
- category: nil,
151
- importance: 1.0,
152
- related_to: [],
153
- tags: []
154
- )
149
+ remember(content, tags: [], metadata: {})
155
150
  ```
156
151
 
157
152
  #### Parameters
158
153
 
159
154
  | Parameter | Type | Default | Description |
160
155
  |-----------|------|---------|-------------|
161
- | `key` | String | *required* | Unique identifier for this node |
162
- | `value` | String | *required* | Content of the memory |
163
- | `type` | Symbol, nil | `nil` | Memory type (`:fact`, `:context`, `:code`, `:preference`, `:decision`, `:question`) |
164
- | `category` | String, nil | `nil` | Optional category for organization |
165
- | `importance` | Float | `1.0` | Importance score (0.0-10.0) |
166
- | `related_to` | Array\<String\> | `[]` | Keys of related nodes |
167
- | `tags` | Array\<String\> | `[]` | Tags for categorization |
156
+ | `content` | String | *required* | The information to remember |
157
+ | `tags` | Array\<String\> | `[]` | Manual tags to assign (in addition to auto-extracted tags) |
158
+ | `metadata` | Hash | `{}` | Arbitrary key-value metadata stored as JSONB. Keys must be strings or symbols. |
168
159
 
169
160
  #### Returns
170
161
 
171
- - `Integer` - Database ID of the created node
162
+ - `Integer` - Database ID of the memory node
172
163
 
173
164
  #### Side Effects
174
165
 
175
- - Stores node in PostgreSQL with vector embedding
166
+ - Stores node in PostgreSQL with content deduplication (via SHA-256 hash)
167
+ - Creates/updates `robot_nodes` association for this robot
176
168
  - Adds node to working memory (evicts if needed)
177
- - Creates relationships to `related_to` nodes
178
- - Adds tags to the node
179
- - Logs operation to `operations_log` table
169
+ - Enqueues background job for embedding generation (new nodes only)
170
+ - Enqueues background job for tag extraction (new nodes only)
180
171
  - Updates robot activity timestamp
181
172
 
173
+ #### Content Deduplication
174
+
175
+ When `remember()` is called:
176
+
177
+ 1. A SHA-256 hash of the content is computed
178
+ 2. If a node with the same hash exists, the existing node is reused
179
+ 3. A new `robot_nodes` association is created (or `remember_count` is incremented)
180
+ 4. This ensures identical memories are stored once but can be "remembered" by multiple robots
181
+
182
182
  #### Examples
183
183
 
184
184
  ```ruby
185
- # Simple fact
186
- htm.add_node("db_choice", "We chose PostgreSQL for its reliability")
187
-
188
- # Architectural decision
189
- htm.add_node(
190
- "api_gateway_decision",
191
- "Decided to use Kong as API gateway for rate limiting and auth",
192
- type: :decision,
193
- importance: 9.0,
194
- tags: ["architecture", "api", "gateway"],
195
- related_to: ["microservices_architecture"]
185
+ # Basic usage
186
+ node_id = htm.remember("PostgreSQL supports vector similarity search via pgvector")
187
+
188
+ # With manual tags
189
+ node_id = htm.remember(
190
+ "Time-series data works great with hypertables",
191
+ tags: ["database:timescaledb", "performance"]
196
192
  )
197
193
 
198
- # Code snippet
199
- code = <<~RUBY
200
- def calculate_total(items)
201
- items.sum(&:price)
202
- end
203
- RUBY
204
-
205
- htm.add_node(
206
- "total_calculation_v1",
207
- code,
208
- type: :code,
209
- category: "helpers",
210
- importance: 5.0,
211
- tags: ["ruby", "calculation"]
194
+ # With metadata
195
+ node_id = htm.remember(
196
+ "User prefers dark mode for all interfaces",
197
+ metadata: { category: "preference", priority: "high", source_app: "settings" }
212
198
  )
213
199
 
214
- # User preference
215
- htm.add_node(
216
- "user_123_timezone",
217
- "User prefers UTC timezone for all timestamps",
218
- type: :preference,
219
- category: "user_settings",
220
- importance: 6.0
200
+ # With both tags and metadata
201
+ node_id = htm.remember(
202
+ "API rate limit is 1000 requests per minute",
203
+ tags: ["api:rate-limiting", "infrastructure"],
204
+ metadata: { environment: "production", version: 2 }
221
205
  )
206
+
207
+ # Multiple robots remembering the same content
208
+ robot1 = HTM.new(robot_name: "assistant_1")
209
+ robot2 = HTM.new(robot_name: "assistant_2")
210
+
211
+ # Both robots remember the same fact - stored once, linked to both
212
+ robot1.remember("Ruby 3.3 was released in December 2023")
213
+ robot2.remember("Ruby 3.3 was released in December 2023")
214
+ # Same node_id returned, remember_count incremented for robot2
222
215
  ```
223
216
 
224
217
  #### Notes
225
218
 
226
- - The `key` must be unique across all nodes
227
- - Embeddings are generated automatically
228
- - Token count is calculated automatically
229
- - If working memory is full, less important nodes are evicted
219
+ - Embeddings and hierarchical tags are generated asynchronously via background jobs
220
+ - Empty content returns the ID of the most recent node without creating a duplicate
221
+ - Token count is calculated automatically using the configured token counter
222
+ - Metadata is stored in a JSONB column with a GIN index for efficient queries
230
223
 
231
224
  ---
232
225
 
233
- ### `recall(timeframe:, topic:, **options)` {: #recall }
226
+ ### `recall(topic, **options)` {: #recall }
234
227
 
235
- Recall memories from a timeframe and topic using RAG-based retrieval.
228
+ Recall memories from long-term storage using RAG-based retrieval.
236
229
 
237
230
  ```ruby
238
231
  recall(
239
- timeframe:,
240
- topic:,
232
+ topic,
233
+ timeframe: "last 7 days",
241
234
  limit: 20,
242
- strategy: :vector
235
+ strategy: :vector,
236
+ with_relevance: false,
237
+ query_tags: [],
238
+ metadata: {},
239
+ raw: false
243
240
  )
244
241
  ```
245
242
 
@@ -247,10 +244,14 @@ recall(
247
244
 
248
245
  | Parameter | Type | Default | Description |
249
246
  |-----------|------|---------|-------------|
250
- | `timeframe` | String, Range | *required* | Time range (e.g., `"last week"`, `7.days.ago..Time.now`) |
251
247
  | `topic` | String | *required* | Topic to search for |
248
+ | `timeframe` | String, Range | `"last 7 days"` | Time range |
252
249
  | `limit` | Integer | `20` | Maximum number of nodes to retrieve |
253
250
  | `strategy` | Symbol | `:vector` | Search strategy (`:vector`, `:fulltext`, `:hybrid`) |
251
+ | `with_relevance` | Boolean | `false` | Include dynamic relevance scores |
252
+ | `query_tags` | Array\<String\> | `[]` | Tags to boost relevance |
253
+ | `metadata` | Hash | `{}` | Filter results by metadata (uses JSONB `@>` containment) |
254
+ | `raw` | Boolean | `false` | Return full node hashes instead of content strings |
254
255
 
255
256
  #### Timeframe Formats
256
257
 
@@ -274,27 +275,36 @@ Range format:
274
275
  |----------|-------------|----------|
275
276
  | `:vector` | Semantic similarity using embeddings | Find conceptually related content |
276
277
  | `:fulltext` | PostgreSQL full-text search | Find exact terms and phrases |
277
- | `:hybrid` | Fulltext prefilter + vector ranking | Best accuracy + semantic understanding |
278
+ | `:hybrid` | Vector + fulltext + tag matching | Best accuracy with tag boosting |
279
+
280
+ #### Tag-Enhanced Hybrid Search
281
+
282
+ When using `:hybrid` strategy, the search automatically:
283
+
284
+ 1. Finds tags matching query terms (words 3+ chars)
285
+ 2. Includes nodes with matching tags in the candidate pool
286
+ 3. Calculates combined score: `(similarity × 0.7) + (tag_boost × 0.3)`
287
+ 4. Returns results sorted by combined score
278
288
 
279
289
  #### Returns
280
290
 
281
- - `Array<Hash>` - Retrieved memory nodes
291
+ - `Array<String>` - Content strings (when `raw: false`, default)
292
+ - `Array<Hash>` - Full node hashes (when `raw: true`)
282
293
 
283
- Each hash contains:
294
+ When `raw: true`, each hash contains:
284
295
 
285
296
  ```ruby
286
297
  {
287
298
  "id" => 123, # Database ID
288
- "key" => "node_key", # Node identifier
289
- "value" => "content...", # Node content
290
- "type" => "fact", # Node type
291
- "category" => "architecture", # Category
292
- "importance" => 8.0, # Importance score
299
+ "content" => "...", # Node content
300
+ "content_hash" => "abc123...", # SHA-256 hash
301
+ "access_count" => 5, # Times accessed
293
302
  "created_at" => "2025-01-15...", # Creation timestamp
294
- "robot_id" => "abc123...", # Robot that created it
295
303
  "token_count" => 125, # Token count
296
- "similarity" => 0.87 # Similarity score (vector/hybrid only)
297
- # or "rank" => 0.456 # Rank score (fulltext only)
304
+ "metadata" => { "category" => "preference", "priority" => "high" }, # JSONB metadata
305
+ "similarity" => 0.87, # Similarity score (hybrid/vector)
306
+ "tag_boost" => 0.3, # Tag boost score (hybrid only)
307
+ "combined_score" => 0.79 # Combined score (hybrid only)
298
308
  }
299
309
  ```
300
310
 
@@ -302,118 +312,96 @@ Each hash contains:
302
312
 
303
313
  - Adds recalled nodes to working memory
304
314
  - Evicts existing nodes if working memory is full
305
- - Logs operation to `operations_log` table
306
315
  - Updates robot activity timestamp
307
316
 
308
317
  #### Examples
309
318
 
310
319
  ```ruby
320
+ # Basic usage (returns content strings)
321
+ memories = htm.recall("PostgreSQL")
322
+ # => ["PostgreSQL supports vector search...", "PostgreSQL with pgvector..."]
323
+
324
+ # Get full node hashes
325
+ nodes = htm.recall("PostgreSQL", raw: true)
326
+ # => [{"id" => 1, "content" => "...", "similarity" => 0.92, ...}, ...]
327
+
311
328
  # Vector semantic search
312
329
  memories = htm.recall(
330
+ "database performance optimization",
313
331
  timeframe: "last week",
314
- topic: "database performance optimization"
332
+ strategy: :vector
315
333
  )
316
334
 
317
335
  # Fulltext search for exact phrases
318
336
  memories = htm.recall(
337
+ "PostgreSQL connection pooling",
319
338
  timeframe: "last 30 days",
320
- topic: "PostgreSQL connection pooling",
321
339
  strategy: :fulltext,
322
340
  limit: 10
323
341
  )
324
342
 
325
- # Hybrid search (best of both)
343
+ # Hybrid search with tag boosting (recommended)
326
344
  memories = htm.recall(
345
+ "API rate limiting implementation",
327
346
  timeframe: "this month",
328
- topic: "API rate limiting implementation",
329
347
  strategy: :hybrid,
330
- limit: 15
348
+ limit: 15,
349
+ raw: true
331
350
  )
332
351
 
352
+ # Check matching tags for a query
353
+ matching_tags = htm.long_term_memory.find_query_matching_tags("PostgreSQL")
354
+ # => ["database:postgresql", "database:postgresql:extensions"]
355
+
333
356
  # Custom time range
334
357
  start_time = Time.new(2025, 1, 1)
335
358
  end_time = Time.now
336
359
 
337
360
  memories = htm.recall(
361
+ "security vulnerabilities",
338
362
  timeframe: start_time..end_time,
339
- topic: "security vulnerabilities",
340
363
  limit: 50
341
364
  )
342
365
 
343
- # Process results
344
- memories.each do |memory|
345
- puts "#{memory['created_at']}: #{memory['value']}"
346
- puts " Similarity: #{memory['similarity']}" if memory['similarity']
347
- puts " Robot: #{memory['robot_id']}"
348
- end
366
+ # Filter by metadata
367
+ memories = htm.recall(
368
+ "user preferences",
369
+ metadata: { category: "preference" }
370
+ )
371
+ # => Returns only nodes with metadata containing { category: "preference" }
372
+
373
+ # Combine metadata with other filters
374
+ memories = htm.recall(
375
+ "API configuration",
376
+ timeframe: "last month",
377
+ strategy: :hybrid,
378
+ metadata: { environment: "production", version: 2 },
379
+ raw: true
380
+ )
381
+ # => Returns production configs with version 2, sorted by relevance
349
382
  ```
350
383
 
351
384
  #### Performance Notes
352
385
 
353
386
  - Vector search: Best for semantic understanding, requires embedding generation
354
387
  - Fulltext search: Fastest for exact matches, no embedding overhead
355
- - Hybrid search: Slower but most accurate, combines both approaches
388
+ - Hybrid search: Most accurate, combines vector + fulltext + tags with weighted scoring
356
389
 
357
390
  ---
358
391
 
359
- ### `retrieve(key)` {: #retrieve }
360
-
361
- Retrieve a specific memory node by its key.
362
-
363
- ```ruby
364
- retrieve(key)
365
- ```
366
-
367
- #### Parameters
368
-
369
- | Parameter | Type | Description |
370
- |-----------|------|-------------|
371
- | `key` | String | Key of the node to retrieve |
372
-
373
- #### Returns
374
-
375
- - `Hash` - Node data if found
376
- - `nil` - If node doesn't exist
377
-
378
- #### Side Effects
379
-
380
- - Updates `last_accessed` timestamp for the node
381
- - Logs operation to `operations_log` table
382
-
383
- #### Examples
384
-
385
- ```ruby
386
- # Retrieve a node
387
- node = htm.retrieve("api_decision_001")
388
-
389
- if node
390
- puts node['value']
391
- puts "Created: #{node['created_at']}"
392
- puts "Importance: #{node['importance']}"
393
- else
394
- puts "Node not found"
395
- end
396
-
397
- # Use retrieved data
398
- config = htm.retrieve("database_config")
399
- db_url = JSON.parse(config['value'])['url'] if config
400
- ```
401
-
402
- ---
403
-
404
- ### `forget(key, confirm:)` {: #forget }
392
+ ### `forget(node_id, confirm:)` {: #forget }
405
393
 
406
394
  Explicitly delete a memory node. Requires confirmation to prevent accidental deletion.
407
395
 
408
396
  ```ruby
409
- forget(key, confirm: :confirmed)
397
+ forget(node_id, confirm: :confirmed)
410
398
  ```
411
399
 
412
400
  #### Parameters
413
401
 
414
402
  | Parameter | Type | Description |
415
403
  |-----------|------|-------------|
416
- | `key` | String | Key of the node to delete |
404
+ | `node_id` | Integer | ID of the node to delete |
417
405
  | `confirm` | Symbol | Must be `:confirmed` to proceed |
418
406
 
419
407
  #### Returns
@@ -423,27 +411,28 @@ forget(key, confirm: :confirmed)
423
411
  #### Raises
424
412
 
425
413
  - `ArgumentError` - If `confirm` is not `:confirmed`
414
+ - `ArgumentError` - If `node_id` is nil
415
+ - `HTM::NotFoundError` - If node doesn't exist
426
416
 
427
417
  #### Side Effects
428
418
 
429
419
  - Deletes node from PostgreSQL
430
420
  - Removes node from working memory
431
- - Logs operation before deletion
432
421
  - Updates robot activity timestamp
433
422
 
434
423
  #### Examples
435
424
 
436
425
  ```ruby
437
426
  # Correct usage
438
- htm.forget("temp_note_123", confirm: :confirmed)
427
+ htm.forget(123, confirm: :confirmed)
439
428
 
440
429
  # This will raise ArgumentError
441
- htm.forget("temp_note_123") # Missing confirm parameter
430
+ htm.forget(123) # Missing confirm parameter
442
431
 
443
432
  # Safe deletion with verification
444
- if htm.retrieve("old_data")
445
- htm.forget("old_data", confirm: :confirmed)
446
- puts "Deleted old_data"
433
+ if htm.long_term_memory.exists?(node_id)
434
+ htm.forget(node_id, confirm: :confirmed)
435
+ puts "Deleted node #{node_id}"
447
436
  end
448
437
  ```
449
438
 
@@ -451,248 +440,166 @@ end
451
440
 
452
441
  - This is the **only** way to delete data from HTM
453
442
  - Deletion is permanent and cannot be undone
454
- - Related relationships and tags are also deleted (CASCADE)
443
+ - Related robot_nodes, node_tags are also deleted (CASCADE)
444
+ - Other robots' associations to this node are also removed
455
445
 
456
446
  ---
457
447
 
458
- ### `create_context(strategy:, max_tokens:)` {: #create_context }
448
+ ### `load_file(path, force: false)` {: #load_file }
459
449
 
460
- Create a context string from working memory for LLM consumption.
450
+ Load a markdown file into long-term memory with automatic chunking and source tracking.
461
451
 
462
452
  ```ruby
463
- create_context(strategy: :balanced, max_tokens: nil)
453
+ load_file(path, force: false)
464
454
  ```
465
455
 
466
456
  #### Parameters
467
457
 
468
458
  | Parameter | Type | Default | Description |
469
459
  |-----------|------|---------|-------------|
470
- | `strategy` | Symbol | `:balanced` | Assembly strategy |
471
- | `max_tokens` | Integer, nil | Working memory max | Optional token limit |
460
+ | `path` | String | *required* | Path to the markdown file to load |
461
+ | `force` | Boolean | `false` | Force re-sync even if file hasn't changed |
472
462
 
473
- #### Assembly Strategies
463
+ #### Returns
474
464
 
475
- | Strategy | Behavior | Use Case |
476
- |----------|----------|----------|
477
- | `:recent` | Most recently accessed first | Prioritize latest information |
478
- | `:important` | Highest importance scores first | Focus on critical information |
479
- | `:balanced` | Weighted by importance × recency | Best general-purpose strategy |
465
+ - `Hash` with keys:
466
+ - `file_source_id` - ID of the FileSource record
467
+ - `chunks_created` - Number of new nodes created
468
+ - `chunks_updated` - Number of existing nodes updated
469
+ - `chunks_deleted` - Number of nodes soft-deleted
480
470
 
481
- #### Returns
471
+ #### Side Effects
482
472
 
483
- - `String` - Assembled context with nodes separated by `"\n\n"`
473
+ - Creates or updates a FileSource record for tracking
474
+ - Parses YAML frontmatter and stores as metadata
475
+ - Chunks content by paragraph, preserving code blocks
476
+ - Creates nodes for each chunk with `source_id` linking to file
477
+ - Triggers async embedding and tag extraction for new nodes
484
478
 
485
479
  #### Examples
486
480
 
487
481
  ```ruby
488
- # Balanced context (default)
489
- context = htm.create_context(strategy: :balanced)
490
-
491
- # Recent context with token limit
492
- context = htm.create_context(
493
- strategy: :recent,
494
- max_tokens: 50_000
495
- )
496
-
497
- # Important context only
498
- context = htm.create_context(strategy: :important)
482
+ # Load a file
483
+ result = htm.load_file("docs/guide.md")
484
+ # => { file_source_id: 1, chunks_created: 5, chunks_updated: 0, chunks_deleted: 0 }
499
485
 
500
- # Use in LLM prompt
501
- prompt = <<~PROMPT
502
- You are a helpful assistant.
486
+ # Force reload even if unchanged
487
+ result = htm.load_file("docs/guide.md", force: true)
503
488
 
504
- Context from memory:
505
- #{context}
506
-
507
- User question: #{user_input}
508
- PROMPT
489
+ # File with frontmatter
490
+ # ---
491
+ # title: User Guide
492
+ # tags: [documentation, tutorial]
493
+ # ---
494
+ # Content here...
495
+ result = htm.load_file("docs/guide.md")
496
+ # Frontmatter stored in FileSource.frontmatter
509
497
  ```
510
498
 
511
- #### Notes
512
-
513
- - Nodes are concatenated with double newlines
514
- - Token limits are respected (stops adding when limit reached)
515
- - Empty string if working memory is empty
516
-
517
499
  ---
518
500
 
519
- ### `memory_stats()` {: #memory_stats }
501
+ ### `load_directory(path, pattern: '**/*.md', force: false)` {: #load_directory }
520
502
 
521
- Get comprehensive statistics about memory usage.
503
+ Load all matching files in a directory into long-term memory.
522
504
 
523
505
  ```ruby
524
- memory_stats()
506
+ load_directory(path, pattern: '**/*.md', force: false)
525
507
  ```
526
508
 
527
- #### Returns
509
+ #### Parameters
528
510
 
529
- - `Hash` - Statistics hash
511
+ | Parameter | Type | Default | Description |
512
+ |-----------|------|---------|-------------|
513
+ | `path` | String | *required* | Directory path to scan |
514
+ | `pattern` | String | `'**/*.md'` | Glob pattern for matching files |
515
+ | `force` | Boolean | `false` | Force re-sync all files |
530
516
 
531
- Structure:
517
+ #### Returns
532
518
 
533
- ```ruby
534
- {
535
- robot_id: "abc123...",
536
- robot_name: "Assistant",
537
-
538
- # Long-term memory stats
539
- total_nodes: 1234,
540
- nodes_by_robot: {
541
- "robot-1" => 500,
542
- "robot-2" => 734
543
- },
544
- nodes_by_type: [
545
- {"type" => "fact", "count" => 400},
546
- {"type" => "decision", "count" => 200},
547
- ...
548
- ],
549
- total_relationships: 567,
550
- total_tags: 890,
551
- oldest_memory: "2025-01-01 12:00:00",
552
- newest_memory: "2025-01-15 14:30:00",
553
- active_robots: 3,
554
- robot_activity: [...],
555
- database_size: 12345678,
556
-
557
- # Working memory stats
558
- working_memory: {
559
- current_tokens: 45234,
560
- max_tokens: 128000,
561
- utilization: 35.34,
562
- node_count: 23
563
- }
564
- }
565
- ```
519
+ - `Array<Hash>` - Results for each file loaded, each containing:
520
+ - `file_path` - Path of the loaded file
521
+ - `file_source_id` - ID of the FileSource record
522
+ - `chunks_created` - Number of new nodes created
523
+ - `chunks_updated` - Number of existing nodes updated
524
+ - `chunks_deleted` - Number of nodes soft-deleted
566
525
 
567
526
  #### Examples
568
527
 
569
528
  ```ruby
570
- stats = htm.memory_stats
571
-
572
- puts "Total memories: #{stats[:total_nodes]}"
573
- puts "Working memory: #{stats[:working_memory][:utilization]}% full"
574
- puts "Active robots: #{stats[:active_robots]}"
529
+ # Load all markdown files
530
+ results = htm.load_directory("docs/")
575
531
 
576
- # Check if working memory is getting full
577
- if stats[:working_memory][:utilization] > 80
578
- puts "Warning: Working memory is #{stats[:working_memory][:utilization]}% full"
579
- end
532
+ # Load with custom pattern
533
+ results = htm.load_directory("content/", pattern: "**/*.md")
580
534
 
581
- # Display by robot
582
- stats[:nodes_by_robot].each do |robot_id, count|
583
- puts "#{robot_id}: #{count} nodes"
584
- end
535
+ # Force reload all
536
+ results = htm.load_directory("docs/", force: true)
585
537
  ```
586
538
 
587
539
  ---
588
540
 
589
- ### `which_robot_said(topic, limit:)` {: #which_robot_said }
541
+ ### `nodes_from_file(file_path)` {: #nodes_from_file }
590
542
 
591
- Find which robots have discussed a specific topic.
543
+ Get all nodes loaded from a specific file.
592
544
 
593
545
  ```ruby
594
- which_robot_said(topic, limit: 100)
546
+ nodes_from_file(file_path)
595
547
  ```
596
548
 
597
549
  #### Parameters
598
550
 
599
- | Parameter | Type | Default | Description |
600
- |-----------|------|---------|-------------|
601
- | `topic` | String | *required* | Topic to search for |
602
- | `limit` | Integer | `100` | Maximum results to consider |
551
+ | Parameter | Type | Description |
552
+ |-----------|------|-------------|
553
+ | `file_path` | String | Path of the source file |
603
554
 
604
555
  #### Returns
605
556
 
606
- - `Hash` - Robot IDs mapped to mention counts
607
-
608
- ```ruby
609
- {
610
- "robot-abc123" => 15,
611
- "robot-def456" => 8,
612
- "robot-ghi789" => 3
613
- }
614
- ```
557
+ - `Array<HTM::Models::Node>` - Nodes from the file, ordered by chunk position
615
558
 
616
559
  #### Examples
617
560
 
618
561
  ```ruby
619
- # Find who discussed deployment
620
- robots = htm.which_robot_said("deployment")
621
- # => {"robot-1" => 12, "robot-2" => 5}
622
-
623
- # Top contributor
624
- top_robot, count = robots.max_by { |robot, count| count }
625
- puts "#{top_robot} mentioned it #{count} times"
626
-
627
- # Check if specific robot discussed it
628
- if robots.key?("robot-123")
629
- puts "Robot-123 discussed deployment #{robots['robot-123']} times"
562
+ nodes = htm.nodes_from_file("docs/guide.md")
563
+ nodes.each do |node|
564
+ puts "Chunk #{node.chunk_position}: #{node.content[0..50]}..."
630
565
  end
631
566
  ```
632
567
 
633
568
  ---
634
569
 
635
- ### `conversation_timeline(topic, limit:)` {: #conversation_timeline }
570
+ ### `unload_file(file_path)` {: #unload_file }
636
571
 
637
- Get a chronological timeline of conversation about a topic.
572
+ Remove a file from memory by soft-deleting all its chunks and the file source.
638
573
 
639
574
  ```ruby
640
- conversation_timeline(topic, limit: 50)
575
+ unload_file(file_path)
641
576
  ```
642
577
 
643
578
  #### Parameters
644
579
 
645
- | Parameter | Type | Default | Description |
646
- |-----------|------|---------|-------------|
647
- | `topic` | String | *required* | Topic to search for |
648
- | `limit` | Integer | `50` | Maximum results |
580
+ | Parameter | Type | Description |
581
+ |-----------|------|-------------|
582
+ | `file_path` | String | Path of the source file to unload |
649
583
 
650
584
  #### Returns
651
585
 
652
- - `Array<Hash>` - Timeline entries sorted by timestamp
586
+ - `true` if file was found and unloaded
587
+ - `false` if file was not found
653
588
 
654
- Structure:
589
+ #### Side Effects
655
590
 
656
- ```ruby
657
- [
658
- {
659
- timestamp: "2025-01-15 10:30:00",
660
- robot: "robot-abc123",
661
- content: "We should consider PostgreSQL...",
662
- type: "decision"
663
- },
664
- {
665
- timestamp: "2025-01-15 11:45:00",
666
- robot: "robot-def456",
667
- content: "Agreed, PostgreSQL has better...",
668
- type: "fact"
669
- },
670
- ...
671
- ]
672
- ```
591
+ - Soft-deletes all nodes from the file (sets `deleted_at`)
592
+ - Destroys the FileSource record
673
593
 
674
594
  #### Examples
675
595
 
676
596
  ```ruby
677
- # Get timeline
678
- timeline = htm.conversation_timeline("API design", limit: 20)
679
-
680
- # Display timeline
681
- timeline.each do |entry|
682
- puts "[#{entry[:timestamp]}] #{entry[:robot]}:"
683
- puts " #{entry[:content]}"
684
- puts " (#{entry[:type]})"
685
- puts
686
- end
597
+ # Unload a file
598
+ htm.unload_file("docs/guide.md")
687
599
 
688
- # Find first mention
689
- first = timeline.first
690
- puts "First discussed by #{first[:robot]} at #{first[:timestamp]}"
691
-
692
- # Group by robot
693
- by_robot = timeline.group_by { |e| e[:robot] }
694
- by_robot.each do |robot, entries|
695
- puts "#{robot}: #{entries.size} contributions"
600
+ # Check if file is loaded
601
+ if htm.nodes_from_file("docs/guide.md").empty?
602
+ puts "File not loaded"
696
603
  end
697
604
  ```
698
605
 
@@ -704,12 +611,24 @@ end
704
611
 
705
612
  ```ruby
706
613
  # Invalid confirm parameter
707
- htm.forget("key")
614
+ htm.forget(123)
708
615
  # => ArgumentError: Must pass confirm: :confirmed to delete
709
616
 
617
+ # Nil node_id
618
+ htm.forget(nil, confirm: :confirmed)
619
+ # => ArgumentError: node_id cannot be nil
620
+
710
621
  # Invalid timeframe
711
- htm.recall(timeframe: nil, topic: "test")
712
- # => ArgumentError: Invalid timeframe: nil
622
+ htm.recall("test", timeframe: 123)
623
+ # => ValidationError: Timeframe must be a Range or String
624
+ ```
625
+
626
+ ### HTM::NotFoundError
627
+
628
+ ```ruby
629
+ # Node doesn't exist
630
+ htm.forget(999999, confirm: :confirmed)
631
+ # => HTM::NotFoundError: Node not found: 999999
713
632
  ```
714
633
 
715
634
  ### PG::Error
@@ -718,73 +637,95 @@ htm.recall(timeframe: nil, topic: "test")
718
637
  # Database connection issues
719
638
  htm = HTM.new(db_config: { host: 'invalid' })
720
639
  # => PG::ConnectionBad: could not translate host name...
721
-
722
- # Duplicate key
723
- htm.add_node("existing_key", "value")
724
- # => PG::UniqueViolation: duplicate key value...
725
640
  ```
726
641
 
727
642
  ## Best Practices
728
643
 
729
- ### Memory Organization
644
+ ### Content Organization
730
645
 
731
646
  ```ruby
732
- # Use consistent key naming
733
- htm.add_node("decision_20250115_api_gateway", ...)
734
- htm.add_node("fact_20250115_database_choice", ...)
735
-
736
- # Use importance strategically
737
- htm.add_node(key, value, importance: 9.0) # Critical
738
- htm.add_node(key, value, importance: 5.0) # Normal
739
- htm.add_node(key, value, importance: 2.0) # Low priority
740
-
741
- # Build knowledge graphs
742
- htm.add_node(
743
- "api_v2_implementation",
744
- "...",
745
- related_to: ["api_v1_design", "authentication_decision"]
647
+ # Use meaningful content that stands alone
648
+ htm.remember("PostgreSQL was chosen for its reliability and pgvector support")
649
+
650
+ # Add hierarchical tags for organization
651
+ htm.remember(
652
+ "Rate limiting implemented using Redis sliding window algorithm",
653
+ tags: ["architecture:api:rate-limiting", "database:redis"]
746
654
  )
655
+
656
+ # Let the system extract tags automatically for most content
657
+ htm.remember("The authentication system uses JWT tokens with 1-hour expiry")
658
+ # Auto-extracted tags might include: security:authentication, technology:jwt
747
659
  ```
748
660
 
749
661
  ### Search Strategies
750
662
 
751
663
  ```ruby
664
+ # Use hybrid for best results (recommended)
665
+ memories = htm.recall(
666
+ "security vulnerability",
667
+ strategy: :hybrid # Combines vector + fulltext + tags
668
+ )
669
+
752
670
  # Use vector for semantic understanding
753
671
  memories = htm.recall(
754
- timeframe: "last month",
755
- topic: "performance issues",
672
+ "performance issues",
756
673
  strategy: :vector # Finds "slow queries", "optimization", etc.
757
674
  )
758
675
 
759
676
  # Use fulltext for exact terms
760
677
  memories = htm.recall(
761
- timeframe: "this week",
762
- topic: "PostgreSQL EXPLAIN ANALYZE",
678
+ "PostgreSQL EXPLAIN ANALYZE",
763
679
  strategy: :fulltext # Exact match
764
680
  )
681
+ ```
765
682
 
766
- # Use hybrid for best results
767
- memories = htm.recall(
768
- timeframe: "last week",
769
- topic: "security vulnerability",
770
- strategy: :hybrid # Accurate + semantic
771
- )
683
+ ### Leveraging Tag-Enhanced Search
684
+
685
+ ```ruby
686
+ # Check what tags exist for a topic
687
+ tags = htm.long_term_memory.find_query_matching_tags("database")
688
+ # => ["database:postgresql", "database:redis", "database:timescaledb"]
689
+
690
+ # Hybrid search automatically boosts nodes with matching tags
691
+ memories = htm.recall("database optimization", strategy: :hybrid, raw: true)
692
+ memories.each do |m|
693
+ puts "Score: #{m['combined_score']} (sim: #{m['similarity']}, tag: #{m['tag_boost']})"
694
+ end
695
+ ```
696
+
697
+ ### Multi-Robot Memory Sharing
698
+
699
+ ```ruby
700
+ # Content is deduplicated across robots
701
+ assistant = HTM.new(robot_name: "assistant")
702
+ researcher = HTM.new(robot_name: "researcher")
703
+
704
+ # Both robots remember the same fact
705
+ assistant.remember("Ruby 3.3 supports YJIT by default")
706
+ researcher.remember("Ruby 3.3 supports YJIT by default")
707
+ # Node stored once, linked to both robots
708
+
709
+ # Any robot can recall shared memories
710
+ memories = assistant.recall("Ruby YJIT")
711
+ # Returns the shared memory
772
712
  ```
773
713
 
774
714
  ### Resource Management
775
715
 
776
716
  ```ruby
777
717
  # Check working memory before large operations
778
- stats = htm.memory_stats
779
- if stats[:working_memory][:utilization] > 90
780
- # Maybe explicitly recall less
718
+ stats = htm.working_memory.stats
719
+ if stats[:utilization] > 90
720
+ # Consider clearing working memory or using smaller limits
781
721
  end
782
722
 
783
723
  # Use appropriate limits
784
- htm.recall(topic: "common_topic", limit: 10) # Not 1000
724
+ htm.recall("common_topic", limit: 10) # Not 1000
785
725
 
786
- # Monitor database size
787
- if stats[:database_size] > 1_000_000_000 # 1GB
726
+ # Monitor node counts
727
+ node_count = HTM::Models::Node.count
728
+ if node_count > 1_000_000
788
729
  # Consider archival strategy
789
730
  end
790
731
  ```