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
@@ -250,95 +250,106 @@ end
250
250
  !!! info "Related ADR"
251
251
  See [ADR-008: Robot Identification System](adrs/008-robot-identification.md) for detailed design decisions.
252
252
 
253
- ## Memory Attribution
253
+ ## Memory Attribution and Deduplication
254
254
 
255
- Every memory node stores the `robot_id` of the robot that created it, enabling attribution tracking and analysis.
255
+ HTM uses a many-to-many relationship between robots and nodes, enabling both content deduplication and attribution tracking.
256
256
 
257
257
  ### Attribution Schema
258
258
 
259
259
  ```sql
260
+ -- Nodes are content-deduplicated via SHA-256 hash
260
261
  CREATE TABLE nodes (
261
262
  id BIGSERIAL PRIMARY KEY,
262
- key TEXT UNIQUE NOT NULL,
263
- value TEXT NOT NULL,
264
- robot_id TEXT NOT NULL REFERENCES robots(id), -- Attribution!
263
+ content TEXT NOT NULL,
264
+ content_hash VARCHAR(64) UNIQUE, -- SHA-256 for deduplication
265
265
  ...
266
266
  );
267
267
 
268
- -- Index for robot-specific queries
269
- CREATE INDEX idx_nodes_robot_id ON nodes(robot_id);
268
+ -- Robot-node relationships tracked in join table
269
+ CREATE TABLE robot_nodes (
270
+ id BIGSERIAL PRIMARY KEY,
271
+ robot_id BIGINT NOT NULL REFERENCES robots(id),
272
+ node_id BIGINT NOT NULL REFERENCES nodes(id),
273
+ first_remembered_at TIMESTAMPTZ, -- When robot first saw this content
274
+ last_remembered_at TIMESTAMPTZ, -- When robot last tried to remember
275
+ remember_count INTEGER DEFAULT 1 -- How many times robot remembered this
276
+ );
277
+
278
+ -- Indexes for efficient queries
279
+ CREATE UNIQUE INDEX idx_robot_nodes_unique ON robot_nodes(robot_id, node_id);
280
+ CREATE INDEX idx_robot_nodes_robot_id ON robot_nodes(robot_id);
281
+ CREATE INDEX idx_robot_nodes_node_id ON robot_nodes(node_id);
270
282
  ```
271
283
 
272
- ### Attribution Tracking
284
+ ### Content Deduplication
273
285
 
274
- When a robot adds a memory:
286
+ When a robot remembers content:
275
287
 
276
288
  ```ruby
277
- def add_node(key, value, ...)
278
- # Store with attribution
279
- node_id = @long_term_memory.add(
280
- key: key,
281
- value: value,
282
- robot_id: @robot_id, # Attribution
283
- ...
284
- )
289
+ def remember(content, tags: [])
290
+ # 1. Compute SHA-256 hash of content
291
+ content_hash = Digest::SHA256.hexdigest(content)
292
+
293
+ # 2. Check if node with same hash exists
294
+ existing_node = HTM::Models::Node.find_by(content_hash: content_hash)
295
+
296
+ if existing_node
297
+ # 3a. Link robot to existing node (or update remember_count)
298
+ link_robot_to_node(robot_id: @robot_id, node: existing_node)
299
+ return existing_node.id
300
+ else
301
+ # 3b. Create new node and link robot
302
+ node = create_new_node(content, content_hash)
303
+ link_robot_to_node(robot_id: @robot_id, node: node)
304
+ return node.id
305
+ end
285
306
  end
286
307
  ```
287
308
 
288
309
  ### Attribution Queries
289
310
 
290
- #### Which robot said this?
311
+ #### Which robots remember this content?
291
312
 
292
313
  ```ruby
293
- def which_robot_said(topic, limit: 100)
294
- results = @long_term_memory.search_fulltext(
295
- timeframe: (Time.at(0)..Time.now),
296
- query: topic,
297
- limit: limit
298
- )
299
-
300
- results.group_by { |n| n['robot_id'] }
301
- .transform_values(&:count)
314
+ # Find all robots that have remembered a specific node
315
+ def robots_for_node(node_id)
316
+ HTM::Models::RobotNode
317
+ .where(node_id: node_id)
318
+ .includes(:robot)
319
+ .map do |rn|
320
+ {
321
+ robot_name: rn.robot.name,
322
+ first_remembered_at: rn.first_remembered_at,
323
+ remember_count: rn.remember_count
324
+ }
325
+ end
302
326
  end
303
327
 
304
- # Example usage
305
- breakdown = htm.which_robot_said("PostgreSQL")
306
- # => { "robot-abc123" => 15, "robot-xyz789" => 8 }
307
-
308
- # Get robot names
309
- breakdown.map do |robot_id, count|
310
- robot = db.query("SELECT name FROM robots WHERE id = $1", [robot_id]).first
311
- "#{robot['name']}: #{count} mentions"
312
- end
313
- # => ["Code Helper: 15 mentions", "Research Bot: 8 mentions"]
328
+ # Example
329
+ robots_for_node(123)
330
+ # => [
331
+ # { robot_name: "Code Helper", first_remembered_at: "2025-01-15", remember_count: 3 },
332
+ # { robot_name: "Research Bot", first_remembered_at: "2025-01-16", remember_count: 1 }
333
+ # ]
314
334
  ```
315
335
 
316
- #### Conversation timeline
336
+ #### Nodes shared by multiple robots
317
337
 
318
338
  ```ruby
319
- def conversation_timeline(topic, limit: 50)
320
- results = @long_term_memory.search_fulltext(
321
- timeframe: (Time.at(0)..Time.now),
322
- query: topic,
323
- limit: limit
324
- )
325
-
326
- results.sort_by { |n| n['created_at'] }
327
- .map { |n| {
328
- timestamp: n['created_at'],
329
- robot: n['robot_id'],
330
- content: n['value'],
331
- type: n['type']
332
- }}
339
+ # Find content that multiple robots have remembered
340
+ def shared_memories(min_robots: 2, limit: 50)
341
+ HTM::Models::Node
342
+ .joins(:robot_nodes)
343
+ .group('nodes.id')
344
+ .having('COUNT(DISTINCT robot_nodes.robot_id) >= ?', min_robots)
345
+ .order('COUNT(DISTINCT robot_nodes.robot_id) DESC')
346
+ .limit(limit)
347
+ .map(&:attributes)
333
348
  end
334
349
 
335
- # Example usage
336
- timeline = htm.conversation_timeline("HTM design", limit: 20)
337
- # => [
338
- # { timestamp: "2025-10-20 10:00:00", robot: "robot-abc123", content: "Let's use PostgreSQL", ... },
339
- # { timestamp: "2025-10-20 10:15:00", robot: "robot-xyz789", content: "I agree, TimescaleDB is perfect", ... },
340
- # ...
341
- # ]
350
+ # Example
351
+ shared_memories(min_robots: 2)
352
+ # => Nodes that 2+ robots have remembered
342
353
  ```
343
354
 
344
355
  #### Robot activity
@@ -349,18 +360,19 @@ SELECT id, name, last_active
349
360
  FROM robots
350
361
  ORDER BY last_active DESC;
351
362
 
352
- -- Which robot contributed most memories?
353
- SELECT r.name, COUNT(n.id) as memory_count
363
+ -- Which robot has remembered the most nodes?
364
+ SELECT r.name, COUNT(rn.node_id) as memory_count
354
365
  FROM robots r
355
- LEFT JOIN nodes n ON n.robot_id = r.id
366
+ LEFT JOIN robot_nodes rn ON rn.robot_id = r.id
356
367
  GROUP BY r.id, r.name
357
368
  ORDER BY memory_count DESC;
358
369
 
359
- -- What has a specific robot been doing?
360
- SELECT operation, created_at, details
361
- FROM operations_log
362
- WHERE robot_id = 'robot-abc123'
363
- ORDER BY created_at DESC
370
+ -- What has a specific robot remembered recently?
371
+ SELECT n.content, rn.first_remembered_at, rn.remember_count
372
+ FROM robot_nodes rn
373
+ JOIN nodes n ON n.id = rn.node_id
374
+ WHERE rn.robot_id = 1
375
+ ORDER BY rn.last_remembered_at DESC
364
376
  LIMIT 50;
365
377
  ```
366
378
 
@@ -374,80 +386,74 @@ A user works with Robot A in one session, then Robot B in another session. Robot
374
386
 
375
387
  ```ruby
376
388
  # Session 1 - Robot A (Code Helper)
377
- htm_a = HTM.new(robot_id: "robot-abc123", robot_name: "Code Helper A")
378
- htm_a.add_node(
379
- "user_pref_001",
380
- "User prefers debug_me over puts for debugging",
381
- type: :preference,
382
- importance: 9.0
383
- )
384
- # Stored in long-term memory with robot_id: "robot-abc123"
389
+ htm_a = HTM.new(robot_name: "Code Helper A")
390
+ htm_a.remember("User prefers debug_me over puts for debugging")
391
+ # Stored in long-term memory, linked to robot A via robot_nodes
385
392
 
386
393
  # === User logs out, logs in next day ===
387
394
 
388
395
  # Session 2 - Robot B (different process, same or different machine)
389
- htm_b = HTM.new(robot_id: "robot-xyz789", robot_name: "Code Helper B")
396
+ htm_b = HTM.new(robot_name: "Code Helper B")
390
397
 
391
398
  # Robot B recalls preferences
392
- memories = htm_b.recall(timeframe: "last week", topic: "debugging preference")
399
+ memories = htm_b.recall("debugging preference", timeframe: "last week")
393
400
  # => Finds preference from Robot A!
394
- # => [{ "key" => "user_pref_001", "robot_id" => "robot-abc123", ... }]
395
401
 
396
402
  # Robot B knows user preference without being told
397
403
  ```
398
404
 
399
- ### Use Case 2: Collaborative Development
405
+ ### Use Case 2: Collaborative Development with Deduplication
400
406
 
401
- Different robots working on different aspects of a project can build on each other's knowledge.
407
+ Different robots working on the same content automatically share nodes.
402
408
 
403
409
  ```ruby
404
410
  # Robot A (Architecture discussion)
405
411
  htm_a = HTM.new(robot_name: "Architect Bot")
406
- htm_a.add_node(
407
- "decision_001",
408
- "We decided to use PostgreSQL with TimescaleDB for HTM storage",
409
- type: :decision,
410
- importance: 10.0,
412
+ node_id = htm_a.remember(
413
+ "We decided to use PostgreSQL with pgvector for HTM storage",
411
414
  tags: ["architecture", "database"]
412
415
  )
416
+ # => node_id: 123 (new node created)
413
417
 
414
- # Robot B (Implementation)
418
+ # Robot B learns the same fact independently
415
419
  htm_b = HTM.new(robot_name: "Code Bot")
416
- memories = htm_b.recall(timeframe: "today", topic: "database decision")
417
- # => Finds architectural decision from Robot A
418
-
419
- # Robot B implements based on Robot A's decision
420
- htm_b.add_node(
421
- "implementation_001",
422
- "Implemented Database class with ConnectionPool for PostgreSQL",
423
- type: :code,
424
- importance: 7.0,
425
- related_to: ["decision_001"], # Link to Robot A's decision
426
- tags: ["implementation", "database"]
420
+ node_id = htm_b.remember(
421
+ "We decided to use PostgreSQL with pgvector for HTM storage"
427
422
  )
423
+ # => node_id: 123 (same node! Content hash matched)
424
+
425
+ # Both robots now linked to the same node
426
+ # Robot A: remember_count = 1
427
+ # Robot B: remember_count = 1
428
+
429
+ # Check shared ownership
430
+ rns = HTM::Models::RobotNode.where(node_id: 123)
431
+ rns.each { |rn| puts "Robot #{rn.robot_id}: #{rn.remember_count} times" }
432
+ # => Robot 1: 1 times
433
+ # => Robot 2: 1 times
428
434
  ```
429
435
 
430
- ### Use Case 3: Multi-Robot Conversation Analysis
436
+ ### Use Case 3: Finding Shared Knowledge
431
437
 
432
- Analyze contributions from different robots across a conversation:
438
+ Analyze what content is shared across robots:
433
439
 
434
440
  ```ruby
435
- # Get all mentions of "TimescaleDB"
436
- breakdown = htm.which_robot_said("TimescaleDB", limit: 100)
437
- # => {
438
- # "robot-abc123" => 25, # Architect Bot
439
- # "robot-xyz789" => 12, # Code Bot
440
- # "robot-def456" => 8 # Research Bot
441
- # }
442
-
443
- # Get chronological conversation
444
- timeline = htm.conversation_timeline("TimescaleDB design", limit: 50)
445
- # => [
446
- # { timestamp: "2025-10-20 10:00", robot: "robot-abc123", content: "Let's explore TimescaleDB" },
447
- # { timestamp: "2025-10-20 10:15", robot: "robot-xyz789", content: "I'll implement the connection" },
448
- # { timestamp: "2025-10-20 10:30", robot: "robot-def456", content: "Here's the research on compression" },
449
- # ...
450
- # ]
441
+ # Find nodes remembered by multiple robots
442
+ shared_nodes = HTM::Models::Node
443
+ .joins(:robot_nodes)
444
+ .group('nodes.id')
445
+ .having('COUNT(DISTINCT robot_nodes.robot_id) >= 2')
446
+ .select('nodes.*, COUNT(DISTINCT robot_nodes.robot_id) as robot_count')
447
+
448
+ shared_nodes.each do |node|
449
+ puts "Node #{node.id}: #{node.robot_count} robots"
450
+ puts " Content: #{node.content[0..80]}..."
451
+
452
+ # Show which robots
453
+ node.robot_nodes.each do |rn|
454
+ puts " - Robot #{rn.robot.name}: remembered #{rn.remember_count}x"
455
+ end
456
+ end
451
457
  ```
452
458
 
453
459
  ## Robot Activity Tracking
@@ -639,47 +645,47 @@ WHERE rtm.team_id = $1;
639
645
 
640
646
  ## Code Examples
641
647
 
642
- ### Example 1: Persistent Robot with Stable Identity
648
+ ### Example 1: Persistent Robot with Named Identity
643
649
 
644
650
  ```ruby
645
- # Store robot ID in config or environment
646
- ROBOT_ID = ENV.fetch('ROBOT_ID', 'code-helper-001')
647
-
648
- # Initialize with persistent identity
651
+ # Initialize with persistent name
649
652
  htm = HTM.new(
650
- robot_id: ROBOT_ID,
651
653
  robot_name: "Code Helper",
652
654
  working_memory_size: 128_000
653
655
  )
654
656
 
655
- # Add memories (attributed to this robot)
656
- htm.add_node("arch_decision", "Use PostgreSQL", importance: 10.0)
657
+ # Add memories (linked to this robot via robot_nodes)
658
+ htm.remember("Use PostgreSQL for ACID guarantees and pgvector support")
657
659
 
658
- # All memories from this robot_id across sessions
660
+ # Robot ID is stored in database, robot_name is human-readable
661
+ puts "Robot ID: #{htm.robot_id}"
662
+ puts "Robot name: #{htm.robot_name}"
659
663
  ```
660
664
 
661
- ### Example 2: Multi-Robot Collaboration
665
+ ### Example 2: Multi-Robot Collaboration with Deduplication
662
666
 
663
667
  ```ruby
664
668
  # Robot A: Architecture discussion
665
- robot_a = HTM.new(robot_id: "arch-001", robot_name: "Architect")
666
- robot_a.add_node(
667
- "db_choice",
669
+ robot_a = HTM.new(robot_name: "Architect")
670
+ node_id = robot_a.remember(
668
671
  "PostgreSQL chosen for ACID guarantees and pgvector support",
669
- type: :decision,
670
- importance: 10.0
672
+ tags: ["architecture:database", "decision"]
671
673
  )
672
674
 
673
675
  # Robot B: Implementation (different process, accesses same LTM)
674
- robot_b = HTM.new(robot_id: "code-001", robot_name: "Coder")
675
- decisions = robot_b.recall(timeframe: "today", topic: "database")
676
+ robot_b = HTM.new(robot_name: "Coder")
677
+ decisions = robot_b.recall("database decision", timeframe: "today")
676
678
  # => Finds Robot A's decision automatically
677
679
 
678
- robot_b.add_node(
679
- "db_impl",
680
+ # If Robot B remembers the same content, it links to existing node
681
+ same_node_id = robot_b.remember(
682
+ "PostgreSQL chosen for ACID guarantees and pgvector support"
683
+ )
684
+ # => same_node_id == node_id (deduplication!)
685
+
686
+ robot_b.remember(
680
687
  "Implemented Database class with connection pooling",
681
- type: :code,
682
- related_to: ["db_choice"] # Link to Robot A's decision
688
+ tags: ["implementation:database", "code:ruby"]
683
689
  )
684
690
  ```
685
691
 
@@ -687,41 +693,34 @@ robot_b.add_node(
687
693
 
688
694
  ```ruby
689
695
  # Get all robots and their activity
690
- stats = {}
691
-
692
- robots = db.query("SELECT * FROM robots ORDER BY last_active DESC")
693
-
694
- robots.each do |robot|
695
- # Count memories
696
- memory_count = db.query(<<~SQL, [robot['id']]).first['count'].to_i
697
- SELECT COUNT(*) FROM nodes WHERE robot_id = $1
698
- SQL
699
-
700
- # Recent operations
701
- recent_ops = db.query(<<~SQL, [robot['id']]).to_a
702
- SELECT operation, COUNT(*) as count
703
- FROM operations_log
704
- WHERE robot_id = $1 AND timestamp > NOW() - INTERVAL '7 days'
705
- GROUP BY operation
706
- SQL
707
-
708
- stats[robot['name']] = {
709
- id: robot['id'],
710
- last_active: robot['last_active'],
711
- total_memories: memory_count,
712
- recent_operations: recent_ops
696
+ stats = []
697
+
698
+ HTM::Models::Robot.order(last_active: :desc).each do |robot|
699
+ # Count memories via robot_nodes
700
+ memory_count = robot.robot_nodes.count
701
+
702
+ # Get remember statistics
703
+ remember_stats = robot.robot_nodes.group(:node_id).count.size # Unique nodes
704
+ total_remembers = robot.robot_nodes.sum(:remember_count) # Total remembers
705
+
706
+ stats << {
707
+ name: robot.name,
708
+ id: robot.id,
709
+ last_active: robot.last_active,
710
+ unique_memories: memory_count,
711
+ total_remembers: total_remembers
713
712
  }
714
713
  end
715
714
 
716
715
  # Display dashboard
717
- stats.each do |name, data|
718
- puts "#{name} (#{data[:id]})"
716
+ puts "=" * 60
717
+ puts "Robot Activity Dashboard"
718
+ puts "=" * 60
719
+ stats.each do |data|
720
+ puts "#{data[:name]} (ID: #{data[:id]})"
719
721
  puts " Last active: #{data[:last_active]}"
720
- puts " Total memories: #{data[:total_memories]}"
721
- puts " Recent operations:"
722
- data[:recent_operations].each do |op|
723
- puts " #{op['operation']}: #{op['count']}"
724
- end
722
+ puts " Unique memories: #{data[:unique_memories]}"
723
+ puts " Total remembers: #{data[:total_remembers]}"
725
724
  puts
726
725
  end
727
726
  ```
@@ -332,8 +332,8 @@ Explore detailed architecture documentation:
332
332
 
333
333
  ## Related Documentation
334
334
 
335
- - [Installation Guide](../installation.md) - Setup PostgreSQL, TimescaleDB, and dependencies
336
- - [Quick Start](../quick-start.md) - Get started with HTM in 5 minutes
335
+ - [Installation Guide](../getting-started/installation.md) - Setup PostgreSQL, TimescaleDB, and dependencies
336
+ - [Quick Start](../getting-started/quick-start.md) - Get started with HTM in 5 minutes
337
337
  - [API Reference](../api/htm.md) - Complete API documentation
338
338
  - [Architecture Decision Records](adrs/index.md) - Detailed decision history
339
339
 
@@ -98,41 +98,7 @@ This diagram shows the complete flow of adding a new memory node to HTM with **c
98
98
  !!! info "Architecture Note"
99
99
  With client-side generation (October 2025), embeddings are generated in Ruby before database insertion. This provides reliable, cross-platform operation.
100
100
 
101
- ```mermaid
102
- graph TD
103
- A[User: add_message] -->|1. Request| B[HTM]
104
- B -->|2. Count tokens| C[EmbeddingService]
105
- C -->|3. Return count| B
106
-
107
- B -->|4. Generate embedding| C
108
- C -->|5. HTTP call| D[Ollama/OpenAI]
109
- D -->|6. Return vector| C
110
- C -->|7. Return embedding| B
111
-
112
- B -->|8. Persist with embedding| E[LongTermMemory]
113
- E -->|9. INSERT nodes with embedding| F[PostgreSQL]
114
- F -->|10. Return node_id| E
115
- E -->|11. Return node_id| B
116
-
117
- B -->|12. Check space| G[WorkingMemory]
118
- G -->|13. Space available?| H{Has Space?}
119
- H -->|No| I[Evict nodes]
120
- I -->|14. Mark evicted| E
121
- H -->|Yes| J[Add to WM]
122
- I --> J
123
-
124
- J -->|15. Success| B
125
- B -->|16. Log operation| E
126
- B -->|17. Return node_id| A
127
-
128
- style A fill:rgba(76,175,80,0.3)
129
- style B fill:rgba(33,150,243,0.3)
130
- style C fill:rgba(255,152,0,0.3)
131
- style D fill:rgba(255,193,7,0.3)
132
- style E fill:rgba(156,39,176,0.3)
133
- style F fill:rgba(156,39,176,0.3)
134
- style G fill:rgba(33,150,243,0.3)
135
- ```
101
+ ![Memory Addition Flow](../assets/images/htm-memory-addition-flow.svg)
136
102
 
137
103
  ### Memory Recall Flow
138
104
 
@@ -141,79 +107,13 @@ This diagram illustrates the RAG-based retrieval process with **client-side quer
141
107
  !!! info "Architecture Note"
142
108
  With client-side generation, query embeddings are generated in Ruby before being passed to SQL for vector similarity search.
143
109
 
144
- ```mermaid
145
- graph TD
146
- A[User: recall] -->|1. Request| B[HTM]
147
- B -->|2. Parse timeframe| C[Parse Natural Language]
148
- C -->|3. Return range| B
149
-
150
- B -->|4. Generate query embedding| D[EmbeddingService]
151
- D -->|5. HTTP call| E[Ollama/OpenAI]
152
- E -->|6. Return vector| D
153
- D -->|7. Return embedding| B
154
-
155
- B -->|8. Search with embedding| F[LongTermMemory]
156
- F -->|9. Vector similarity| G{Search Strategy}
157
- G -->|:vector| H[Vector Search]
158
- G -->|:fulltext| I[Full-Text Search]
159
- G -->|:hybrid| J[Hybrid Search]
160
-
161
- H -->|10. pgvector HNSW| K[Return results]
162
- I -->|10. ts_rank GIN| K
163
- J -->|10. Hybrid + RRF| K
164
-
165
- K -->|11. Results| F
166
- F -->|12. Results| B
167
-
168
- B -->|13. For each result| L[WorkingMemory]
169
- L -->|14. Add to WM| M{Has Space?}
170
- M -->|No| N[Evict old nodes]
171
- P -->|Yes| R[Add node]
172
- Q --> R
173
-
174
- R -->|12. Log operation| E
175
- B -->|13. Return memories| A
176
-
177
- style A fill:rgba(76,175,80,0.3)
178
- style B fill:rgba(33,150,243,0.3)
179
- style E fill:rgba(156,39,176,0.3)
180
- style J fill:rgba(255,193,7,0.3)
181
- style O fill:rgba(33,150,243,0.3)
182
- ```
110
+ ![Memory Recall Flow](../assets/images/htm-memory-recall-flow.svg)
183
111
 
184
112
  ### Context Assembly Flow
185
113
 
186
114
  This diagram shows how working memory assembles context for LLM consumption using different strategies.
187
115
 
188
- ```mermaid
189
- graph TD
190
- A[User: create_context] -->|1. Request with strategy| B[HTM]
191
- B -->|2. Assemble| C[WorkingMemory]
192
-
193
- C -->|3. Strategy?| D{Strategy Type}
194
- D -->|:recent| E[Sort by access order]
195
- D -->|:important| F[Sort by importance]
196
- D -->|:balanced| G[Hybrid score]
197
-
198
- E --> H[Sorted nodes]
199
- F --> H
200
- G --> H
201
-
202
- H -->|4. Build context| I[Token budget loop]
203
- I -->|5. Check tokens| J{Tokens < max?}
204
- J -->|Yes| K[Add node to context]
205
- J -->|No| L[Stop, return context]
206
- K --> I
207
-
208
- L -->|6. Join nodes| M[Assembled context string]
209
- M -->|7. Return| C
210
- C -->|8. Return| B
211
- B -->|9. Return| A
212
-
213
- style A fill:rgba(76,175,80,0.3)
214
- style C fill:rgba(33,150,243,0.3)
215
- style G fill:rgba(255,193,7,0.3)
216
- ```
116
+ ![Context Assembly Flow](../assets/images/htm-context-assembly-flow.svg)
217
117
 
218
118
  ## Memory Lifecycle
219
119
 
@@ -221,79 +121,13 @@ graph TD
221
121
 
222
122
  A memory node transitions through several states during its lifetime in HTM:
223
123
 
224
- ```mermaid
225
- stateDiagram-v2
226
- [*] --> Created: add_node()
227
-
228
- Created --> InBothMemories: Initial state
229
- InBothMemories --> WorkingMemoryOnly: Evicted from WM
230
- InBothMemories --> LongTermMemoryOnly: WM cleared
231
-
232
- WorkingMemoryOnly --> InBothMemories: Recalled
233
- LongTermMemoryOnly --> InBothMemories: Recalled
234
-
235
- InBothMemories --> Forgotten: forget(confirm: :confirmed)
236
- WorkingMemoryOnly --> Forgotten: forget(confirm: :confirmed)
237
- LongTermMemoryOnly --> Forgotten: forget(confirm: :confirmed)
238
-
239
- Forgotten --> [*]
240
-
241
- note right of InBothMemories
242
- Node exists in:
243
- - Working Memory (fast access)
244
- - Long-Term Memory (persistent)
245
- in_working_memory = TRUE
246
- end note
247
-
248
- note right of WorkingMemoryOnly
249
- Node exists only in:
250
- - Long-Term Memory
251
- in_working_memory = FALSE
252
- (Evicted due to token limit)
253
- end note
254
-
255
- note right of Forgotten
256
- Node permanently deleted
257
- from both memories
258
- (Explicit user action)
259
- end note
260
- ```
124
+ ![Node States](../assets/images/htm-node-states.svg)
261
125
 
262
126
  ### Eviction Process
263
127
 
264
128
  When working memory reaches its token limit, the eviction process runs to free up space:
265
129
 
266
- ```mermaid
267
- sequenceDiagram
268
- participant User
269
- participant HTM
270
- participant WM as WorkingMemory
271
- participant LTM as LongTermMemory
272
- participant DB as Database
273
-
274
- User->>HTM: add_node(large_memory)
275
- HTM->>WM: add(key, value, token_count)
276
- WM->>WM: Check: token_count + current > max?
277
-
278
- alt Space Available
279
- WM->>WM: Add node directly
280
- WM-->>HTM: Success
281
- else No Space
282
- WM->>WM: Sort by [importance, -recency]
283
- WM->>WM: Evict low-importance old nodes
284
- Note over WM: Free enough tokens
285
-
286
- WM->>HTM: Return evicted nodes
287
- HTM->>LTM: mark_evicted(keys)
288
- LTM->>DB: UPDATE in_working_memory = FALSE
289
- DB-->>LTM: Updated
290
-
291
- WM->>WM: Add new node
292
- WM-->>HTM: Success
293
- end
294
-
295
- HTM-->>User: node_id
296
- ```
130
+ ![Eviction Process](../assets/images/htm-eviction-process.svg)
297
131
 
298
132
  ## Database Schema
299
133