htm 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.tbls.yml +30 -0
  4. data/CHANGELOG.md +30 -0
  5. data/SETUP.md +132 -101
  6. data/db/migrate/20250125000001_add_content_hash_to_nodes.rb +14 -0
  7. data/db/migrate/20250125000002_create_robot_nodes.rb +35 -0
  8. data/db/migrate/20250125000003_remove_source_and_robot_id_from_nodes.rb +28 -0
  9. data/db/migrate/20250126000001_create_working_memories.rb +19 -0
  10. data/db/migrate/20250126000002_remove_unused_columns.rb +12 -0
  11. data/db/schema.sql +226 -43
  12. data/docs/api/database.md +20 -232
  13. data/docs/api/embedding-service.md +1 -7
  14. data/docs/api/htm.md +195 -449
  15. data/docs/api/index.md +1 -7
  16. data/docs/api/long-term-memory.md +342 -590
  17. data/docs/architecture/adrs/001-postgresql-timescaledb.md +1 -1
  18. data/docs/architecture/adrs/003-ollama-embeddings.md +1 -1
  19. data/docs/architecture/adrs/010-redis-working-memory-rejected.md +2 -27
  20. data/docs/architecture/adrs/index.md +2 -13
  21. data/docs/architecture/hive-mind.md +165 -166
  22. data/docs/architecture/index.md +2 -2
  23. data/docs/architecture/overview.md +5 -171
  24. data/docs/architecture/two-tier-memory.md +1 -35
  25. data/docs/assets/images/adr-010-current-architecture.svg +37 -0
  26. data/docs/assets/images/adr-010-proposed-architecture.svg +48 -0
  27. data/docs/assets/images/adr-dependency-tree.svg +93 -0
  28. data/docs/assets/images/class-hierarchy.svg +55 -0
  29. data/docs/assets/images/exception-hierarchy.svg +45 -0
  30. data/docs/assets/images/htm-architecture-overview.svg +83 -0
  31. data/docs/assets/images/htm-complete-memory-flow.svg +160 -0
  32. data/docs/assets/images/htm-context-assembly-flow.svg +148 -0
  33. data/docs/assets/images/htm-eviction-process.svg +141 -0
  34. data/docs/assets/images/htm-memory-addition-flow.svg +138 -0
  35. data/docs/assets/images/htm-memory-recall-flow.svg +152 -0
  36. data/docs/assets/images/htm-node-states.svg +123 -0
  37. data/docs/assets/images/project-structure.svg +78 -0
  38. data/docs/assets/images/test-directory-structure.svg +38 -0
  39. data/{dbdoc → docs/database}/README.md +5 -3
  40. data/{dbdoc → docs/database}/public.node_tags.md +4 -5
  41. data/docs/database/public.node_tags.svg +106 -0
  42. data/{dbdoc → docs/database}/public.nodes.md +3 -8
  43. data/docs/database/public.nodes.svg +152 -0
  44. data/docs/database/public.robot_nodes.md +44 -0
  45. data/docs/database/public.robot_nodes.svg +121 -0
  46. data/{dbdoc → docs/database}/public.robots.md +1 -2
  47. data/docs/database/public.robots.svg +106 -0
  48. data/docs/database/public.working_memories.md +40 -0
  49. data/docs/database/public.working_memories.svg +112 -0
  50. data/{dbdoc → docs/database}/schema.json +342 -110
  51. data/docs/database/schema.svg +223 -0
  52. data/docs/development/index.md +1 -29
  53. data/docs/development/schema.md +84 -324
  54. data/docs/development/testing.md +1 -9
  55. data/docs/getting-started/index.md +47 -0
  56. data/docs/{installation.md → getting-started/installation.md} +2 -2
  57. data/docs/{quick-start.md → getting-started/quick-start.md} +5 -5
  58. data/docs/guides/adding-memories.md +221 -655
  59. data/docs/guides/search-strategies.md +85 -51
  60. data/docs/images/htm-er-diagram.svg +156 -0
  61. data/docs/index.md +16 -31
  62. data/docs/multi_framework_support.md +4 -4
  63. data/examples/basic_usage.rb +18 -16
  64. data/examples/cli_app/htm_cli.rb +86 -8
  65. data/examples/custom_llm_configuration.rb +1 -2
  66. data/examples/example_app/app.rb +11 -14
  67. data/examples/sinatra_app/Gemfile +1 -0
  68. data/examples/sinatra_app/Gemfile.lock +166 -0
  69. data/examples/sinatra_app/app.rb +219 -24
  70. data/lib/htm/active_record_config.rb +10 -3
  71. data/lib/htm/configuration.rb +265 -78
  72. data/lib/htm/{sinatra.rb → integrations/sinatra.rb} +87 -12
  73. data/lib/htm/job_adapter.rb +10 -3
  74. data/lib/htm/long_term_memory.rb +220 -57
  75. data/lib/htm/models/node.rb +36 -7
  76. data/lib/htm/models/robot.rb +30 -4
  77. data/lib/htm/models/robot_node.rb +50 -0
  78. data/lib/htm/models/tag.rb +52 -0
  79. data/lib/htm/models/working_memory_entry.rb +88 -0
  80. data/lib/htm/tasks.rb +4 -0
  81. data/lib/htm/version.rb +1 -1
  82. data/lib/htm.rb +34 -13
  83. data/lib/tasks/htm.rake +32 -1
  84. data/lib/tasks/jobs.rake +7 -3
  85. data/lib/tasks/tags.rake +34 -0
  86. data/mkdocs.yml +56 -9
  87. metadata +61 -31
  88. data/dbdoc/public.node_tags.svg +0 -112
  89. data/dbdoc/public.nodes.svg +0 -118
  90. data/dbdoc/public.robots.svg +0 -90
  91. data/dbdoc/schema.svg +0 -154
  92. /data/{dbdoc → docs/database}/public.node_stats.md +0 -0
  93. /data/{dbdoc → docs/database}/public.node_stats.svg +0 -0
  94. /data/{dbdoc → docs/database}/public.nodes_tags.md +0 -0
  95. /data/{dbdoc → docs/database}/public.nodes_tags.svg +0 -0
  96. /data/{dbdoc → docs/database}/public.ontology_structure.md +0 -0
  97. /data/{dbdoc → docs/database}/public.ontology_structure.svg +0 -0
  98. /data/{dbdoc → docs/database}/public.operations_log.md +0 -0
  99. /data/{dbdoc → docs/database}/public.operations_log.svg +0 -0
  100. /data/{dbdoc → docs/database}/public.relationships.md +0 -0
  101. /data/{dbdoc → docs/database}/public.relationships.svg +0 -0
  102. /data/{dbdoc → docs/database}/public.robot_activity.md +0 -0
  103. /data/{dbdoc → docs/database}/public.robot_activity.svg +0 -0
  104. /data/{dbdoc → docs/database}/public.schema_migrations.md +0 -0
  105. /data/{dbdoc → docs/database}/public.schema_migrations.svg +0 -0
  106. /data/{dbdoc → docs/database}/public.tags.md +0 -0
  107. /data/{dbdoc → docs/database}/public.tags.svg +0 -0
  108. /data/{dbdoc → docs/database}/public.topic_relationships.md +0 -0
  109. /data/{dbdoc → docs/database}/public.topic_relationships.svg +0 -0
@@ -299,7 +299,7 @@ ALTER TABLE nodes SET (
299
299
  - [pgvector Documentation](https://github.com/pgvector/pgvector)
300
300
  - [PostgreSQL Full-Text Search](https://www.postgresql.org/docs/current/textsearch.html)
301
301
  - [HTM Database Schema Guide](../../development/schema.md)
302
- - [HTM Configuration Guide](../../installation.md)
302
+ - [HTM Configuration Guide](../../getting-started/installation.md)
303
303
 
304
304
  ---
305
305
 
@@ -404,7 +404,7 @@ htm = HTM.new(
404
404
  - [pgvector Documentation](https://github.com/pgvector/pgvector)
405
405
  - [pgai Documentation](https://github.com/timescale/pgai)
406
406
  - [ADR-011: Database-Side Embedding Generation with pgai](011-pgai-integration.md) - **Supersedes this ADR**
407
- - [HTM Setup Guide](../../installation.md)
407
+ - [HTM Setup Guide](../../getting-started/installation.md)
408
408
 
409
409
  ---
410
410
 
@@ -30,18 +30,7 @@ During architectural review, we identified that working memory is currently vola
30
30
 
31
31
  ### Current Architecture (Two-Tier)
32
32
 
33
- ```
34
- ┌─────────────────┐
35
- │ HTM Instance │
36
- │ │
37
- │ ┌───────────┐ │ ┌──────────────┐
38
- │ │ Working │ │────>│ PostgreSQL │
39
- │ │ Memory │ │ │ (Long-Term) │
40
- │ │ (Hash) │ │ │ │
41
- │ └───────────┘ │ └──────────────┘
42
- │ volatile │ persistent
43
- └─────────────────┘
44
- ```
33
+ ![Current Two-Tier Architecture](../../assets/images/adr-010-current-architecture.svg)
45
34
 
46
35
  **How it works**:
47
36
  1. `add_node()` saves **immediately** to PostgreSQL
@@ -53,21 +42,7 @@ During architectural review, we identified that working memory is currently vola
53
42
 
54
43
  ### Proposed Architecture (Three-Tier)
55
44
 
56
- ```
57
- ┌─────────────────┐
58
- │ HTM Instance │
59
- │ │
60
- │ ││ │
61
- │ ││ │
62
- │ ▼▼ │
63
- │ ┌───────────┐ │ ┌──────────────┐
64
- │ │ Redis │ │────>│ PostgreSQL │
65
- │ │ (Working) │ │ │ (Long-Term) │
66
- │ │ │ │ │ │
67
- │ └───────────┘ │ └──────────────┘
68
- │ persistent │ persistent
69
- └─────────────────┘
70
- ```
45
+ ![Proposed Three-Tier Architecture (Rejected)](../../assets/images/adr-010-proposed-architecture.svg)
71
46
 
72
47
  **Proposed changes**:
73
48
  - Store working memory in Redis (shared across processes)
@@ -174,24 +174,13 @@ Proposal to add Redis as a persistent storage layer for working memory was thoro
174
174
 
175
175
  ## ADR Dependencies
176
176
 
177
- ```
178
- ADR-001 (Storage)
179
- └─> ADR-002 (Two-Tier Memory)
180
- ├─> ADR-007 (Eviction Strategy)
181
- ├─> ADR-009 (Never-Forget)
182
- └─> ADR-010 (Redis WM - Rejected Alternative)
183
- └─> ADR-003 (Embeddings)
184
- └─> ADR-005 (RAG Retrieval)
185
- └─> ADR-004 (Hive Mind)
186
- └─> ADR-008 (Robot ID)
187
- └─> ADR-006 (Context Assembly)
188
- ```
177
+ ![ADR Dependency Tree](../../assets/images/adr-dependency-tree.svg)
189
178
 
190
179
  ## Related Documentation
191
180
 
192
181
  - [HTM API Guide](../../api/index.md)
193
182
  - [Database Schema](../../development/schema.md)
194
- - [Configuration Guide](../../installation.md)
183
+ - [Configuration Guide](../../getting-started/installation.md)
195
184
  - [Development Workflow](../../development/index.md)
196
185
 
197
186
  ## Contributing to ADRs
@@ -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