htm 0.0.14 → 0.0.15

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +269 -79
  4. data/db/migrate/00003_create_file_sources.rb +5 -0
  5. data/db/migrate/00004_create_nodes.rb +17 -0
  6. data/db/migrate/00005_create_tags.rb +7 -0
  7. data/db/migrate/00006_create_node_tags.rb +2 -0
  8. data/db/migrate/00007_create_robot_nodes.rb +7 -0
  9. data/db/schema.sql +41 -29
  10. data/docs/api/yard/HTM/Configuration.md +54 -0
  11. data/docs/api/yard/HTM/Database.md +13 -10
  12. data/docs/api/yard/HTM/EmbeddingService.md +5 -1
  13. data/docs/api/yard/HTM/LongTermMemory.md +18 -277
  14. data/docs/api/yard/HTM/PropositionError.md +18 -0
  15. data/docs/api/yard/HTM/PropositionService.md +66 -0
  16. data/docs/api/yard/HTM/QueryCache.md +88 -0
  17. data/docs/api/yard/HTM/RobotGroup.md +481 -0
  18. data/docs/api/yard/HTM/SqlBuilder.md +108 -0
  19. data/docs/api/yard/HTM/TagService.md +4 -0
  20. data/docs/api/yard/HTM/Telemetry/NullInstrument.md +13 -0
  21. data/docs/api/yard/HTM/Telemetry/NullMeter.md +15 -0
  22. data/docs/api/yard/HTM/Telemetry.md +109 -0
  23. data/docs/api/yard/HTM/WorkingMemoryChannel.md +176 -0
  24. data/docs/api/yard/HTM.md +11 -23
  25. data/docs/api/yard/index.csv +102 -25
  26. data/docs/api/yard-reference.md +8 -0
  27. data/docs/assets/images/multi-provider-failover.svg +51 -0
  28. data/docs/assets/images/robot-group-architecture.svg +65 -0
  29. data/docs/database/README.md +3 -3
  30. data/docs/database/public.file_sources.svg +29 -21
  31. data/docs/database/public.node_tags.md +2 -0
  32. data/docs/database/public.node_tags.svg +53 -41
  33. data/docs/database/public.nodes.md +2 -0
  34. data/docs/database/public.nodes.svg +52 -40
  35. data/docs/database/public.robot_nodes.md +2 -0
  36. data/docs/database/public.robot_nodes.svg +30 -22
  37. data/docs/database/public.robots.svg +16 -12
  38. data/docs/database/public.tags.md +3 -0
  39. data/docs/database/public.tags.svg +41 -33
  40. data/docs/database/schema.json +66 -0
  41. data/docs/database/schema.svg +60 -48
  42. data/docs/development/index.md +13 -0
  43. data/docs/development/rake-tasks.md +1068 -0
  44. data/docs/getting-started/quick-start.md +144 -155
  45. data/docs/guides/adding-memories.md +2 -3
  46. data/docs/guides/context-assembly.md +185 -184
  47. data/docs/guides/getting-started.md +154 -148
  48. data/docs/guides/index.md +7 -0
  49. data/docs/guides/long-term-memory.md +60 -92
  50. data/docs/guides/mcp-server.md +617 -0
  51. data/docs/guides/multi-robot.md +249 -345
  52. data/docs/guides/recalling-memories.md +153 -163
  53. data/docs/guides/robot-groups.md +604 -0
  54. data/docs/guides/search-strategies.md +61 -58
  55. data/docs/guides/working-memory.md +103 -136
  56. data/docs/index.md +30 -26
  57. data/examples/robot_groups/robot_worker.rb +1 -2
  58. data/examples/robot_groups/same_process.rb +1 -4
  59. data/lib/htm/robot_group.rb +721 -0
  60. data/lib/htm/version.rb +1 -1
  61. data/lib/htm/working_memory_channel.rb +250 -0
  62. data/lib/htm.rb +2 -0
  63. data/mkdocs.yml +2 -0
  64. metadata +18 -9
  65. data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +0 -12
  66. data/db/migrate/00010_add_soft_delete_to_associations.rb +0 -29
  67. data/db/migrate/00011_add_performance_indexes.rb +0 -21
  68. data/db/migrate/00012_add_tags_trigram_index.rb +0 -18
  69. data/db/migrate/00013_enable_lz4_compression.rb +0 -43
  70. data/examples/robot_groups/lib/robot_group.rb +0 -419
  71. data/examples/robot_groups/lib/working_memory_channel.rb +0 -140
@@ -23,173 +23,174 @@ In HTM, all robots share the same long-term memory database but maintain separat
23
23
  # Robot 1: Research Assistant
24
24
  research_bot = HTM.new(
25
25
  robot_name: "Research Assistant",
26
- robot_id: "research-001",
27
26
  working_memory_size: 128_000
28
27
  )
28
+ # Each robot gets a unique robot_id automatically
29
29
 
30
30
  # Robot 2: Code Helper
31
31
  code_bot = HTM.new(
32
32
  robot_name: "Code Helper",
33
- robot_id: "code-001",
34
33
  working_memory_size: 128_000
35
34
  )
36
35
 
37
36
  # Robot 3: Documentation Writer
38
37
  docs_bot = HTM.new(
39
38
  robot_name: "Docs Writer",
40
- robot_id: "docs-001",
41
39
  working_memory_size: 64_000
42
40
  )
43
41
 
44
- # Each robot can access shared knowledge
45
- research_bot.add_node(
46
- "finding_001",
42
+ # Each robot can access shared knowledge through the shared database
43
+ research_bot.remember(
47
44
  "Research shows PostgreSQL outperforms MongoDB for ACID workloads",
48
- type: :fact,
49
- importance: 8.0,
50
- tags: ["research", "database"]
45
+ tags: ["research", "database:comparison"]
51
46
  )
52
47
 
53
- # Code bot can access this finding
48
+ # Code bot can access research findings (shared long-term memory)
54
49
  findings = code_bot.recall(
55
- timeframe: "last hour",
56
- topic: "database performance"
50
+ "database performance",
51
+ timeframe: "last hour"
57
52
  )
58
53
 
59
- # Docs bot can document it
60
- docs_bot.add_node(
61
- "doc_001",
54
+ # Docs bot can document findings
55
+ docs_bot.remember(
62
56
  "PostgreSQL performance documented based on research findings",
63
- type: :context,
64
- importance: 6.0,
65
57
  tags: ["documentation", "database"],
66
- related_to: ["finding_001"]
58
+ metadata: { source: "research" }
67
59
  )
68
60
  ```
69
61
 
70
62
  ## Robot Identification
71
63
 
72
- ### Session IDs vs Persistent IDs
64
+ ### Naming Strategies
73
65
 
74
- Choose the right identification strategy:
66
+ Choose a consistent naming strategy for robot identification:
75
67
 
76
68
  ```ruby
77
69
  # Strategy 1: Persistent Robot (recommended for production)
70
+ # Use descriptive names - robot_id is assigned automatically
78
71
  persistent_bot = HTM.new(
79
- robot_name: "Production Assistant",
80
- robot_id: "prod-assistant-001" # Fixed, reusable
72
+ robot_name: "Production Assistant"
81
73
  )
74
+ puts "Robot ID: #{persistent_bot.robot_id}" # e.g., 1
75
+ puts "Robot Name: #{persistent_bot.robot_name}" # "Production Assistant"
82
76
 
83
77
  # Strategy 2: Session-based Robot (for temporary workflows)
84
- session_id = SecureRandom.uuid
78
+ session_id = SecureRandom.uuid[0..7]
85
79
  session_bot = HTM.new(
86
- robot_name: "Temp Session",
87
- robot_id: "session-#{session_id}" # Unique per session
80
+ robot_name: "Session #{session_id}"
88
81
  )
89
82
 
90
83
  # Strategy 3: User-specific Robot
91
84
  user_id = "alice"
92
85
  user_bot = HTM.new(
93
- robot_name: "Alice's Assistant",
94
- robot_id: "user-#{user_id}-assistant"
86
+ robot_name: "#{user_id}'s Assistant"
95
87
  )
96
88
  ```
97
89
 
98
90
  !!! tip "Naming Conventions"
99
- - **Production robots**: `service-purpose-001` (e.g., `api-assistant-001`)
100
- - **User robots**: `user-{user_id}-{purpose}` (e.g., `user-alice-assistant`)
101
- - **Session robots**: `session-{uuid}` (e.g., `session-abc123...`)
102
- - **Team robots**: `team-{name}-{purpose}` (e.g., `team-eng-reviewer`)
91
+ Use descriptive `robot_name` values to identify robots:
92
+ - **Production robots**: `"Production Assistant"`, `"API Helper"`
93
+ - **User robots**: `"Alice's Assistant"`, `"User 123 Bot"`
94
+ - **Session robots**: `"Session abc123"`, `"Temp Session"`
95
+ - **Team robots**: `"Engineering Reviewer"`, `"QA Bot"`
103
96
 
104
97
  ### Robot Registry
105
98
 
106
- All robots are automatically registered:
99
+ All robots are automatically registered in the database:
107
100
 
108
101
  ```ruby
109
102
  # Robots are registered when created
110
- bot = HTM.new(robot_name: "My Bot", robot_id: "bot-001")
111
-
112
- # Query robot registry
113
- config = HTM::Database.default_config
114
- conn = PG.connect(config)
115
-
116
- result = conn.exec("SELECT * FROM robots ORDER BY last_active DESC")
103
+ bot = HTM.new(robot_name: "My Bot")
104
+ puts "Registered with ID: #{bot.robot_id}"
117
105
 
106
+ # Query robot registry using ActiveRecord
118
107
  puts "Registered robots:"
119
- result.each do |row|
120
- puts "#{row['name']} (#{row['id']})"
121
- puts " Created: #{row['created_at']}"
122
- puts " Last active: #{row['last_active']}"
108
+ HTM::Models::Robot.order(last_active_at: :desc).each do |robot|
109
+ puts "#{robot.name} (ID: #{robot.id})"
110
+ puts " Created: #{robot.created_at}"
111
+ puts " Last active: #{robot.last_active_at}"
123
112
  puts
124
113
  end
125
-
126
- conn.close
127
114
  ```
128
115
 
129
116
  ## Attribution Tracking
130
117
 
131
118
  ### Who Said What?
132
119
 
133
- Track which robot contributed which memories:
120
+ Track which robot contributed which memories using the robot_nodes join table:
134
121
 
135
122
  ```ruby
136
123
  # Add memories from different robots
137
- alpha = HTM.new(robot_name: "Alpha", robot_id: "alpha")
138
- beta = HTM.new(robot_name: "Beta", robot_id: "beta")
124
+ alpha = HTM.new(robot_name: "Alpha")
125
+ beta = HTM.new(robot_name: "Beta")
139
126
 
140
- alpha.add_node("alpha_001", "Alpha's insight about caching", type: :fact)
141
- beta.add_node("beta_001", "Beta's approach to testing", type: :fact)
127
+ alpha.remember("Alpha's insight about caching", tags: ["caching"])
128
+ beta.remember("Beta's approach to testing", tags: ["testing"])
142
129
 
143
- # Query by robot
130
+ # Query memories linked to a specific robot via RobotNode
144
131
  def memories_by_robot(robot_id)
145
- config = HTM::Database.default_config
146
- conn = PG.connect(config)
147
-
148
- result = conn.exec_params(
149
- "SELECT key, value, type FROM nodes WHERE robot_id = $1",
150
- [robot_id]
151
- )
152
-
153
- memories = result.to_a
154
- conn.close
155
- memories
132
+ HTM::Models::RobotNode.where(robot_id: robot_id)
133
+ .includes(:node)
134
+ .map { |rn| rn.node.attributes }
156
135
  end
157
136
 
158
- alpha_memories = memories_by_robot("alpha")
137
+ alpha_memories = memories_by_robot(alpha.robot_id)
159
138
  puts "Alpha contributed #{alpha_memories.length} memories"
160
139
  ```
161
140
 
162
- ### Which Robot Said...?
141
+ ### Tracking Contributions
163
142
 
164
- Use HTM's built-in attribution tracking:
143
+ Query which robots have contributed to specific topics:
165
144
 
166
145
  ```ruby
167
- # Find which robots discussed a topic
168
- breakdown = htm.which_robot_said("PostgreSQL")
146
+ # Find nodes matching a topic and see which robots contributed
147
+ def robots_discussing(topic)
148
+ # Get matching nodes
149
+ nodes = HTM::Models::Node.where("content ILIKE ?", "%#{topic}%")
150
+
151
+ # Count contributions by robot
152
+ robot_counts = Hash.new(0)
153
+ nodes.each do |node|
154
+ node.robot_nodes.each do |rn|
155
+ robot = HTM::Models::Robot.find(rn.robot_id)
156
+ robot_counts[robot.name] += 1
157
+ end
158
+ end
169
159
 
170
- puts "Robots that discussed PostgreSQL:"
171
- breakdown.each do |robot_id, count|
172
- puts " #{robot_id}: #{count} mentions"
160
+ robot_counts
173
161
  end
174
162
 
175
- # Example output:
176
- # Robots that discussed PostgreSQL:
177
- # research-001: 15 mentions
178
- # code-001: 8 mentions
179
- # docs-001: 3 mentions
163
+ breakdown = robots_discussing("PostgreSQL")
164
+
165
+ puts "Robots that discussed PostgreSQL:"
166
+ breakdown.each do |robot_name, count|
167
+ puts " #{robot_name}: #{count} mentions"
168
+ end
180
169
  ```
181
170
 
182
- ### Conversation Timeline
171
+ ### Memory Timeline
183
172
 
184
- See the chronological conversation across robots:
173
+ See the chronological contributions across robots:
185
174
 
186
175
  ```ruby
187
- timeline = htm.conversation_timeline("architecture decisions", limit: 50)
176
+ # Get recent nodes with robot attribution
177
+ def memory_timeline(topic, limit: 50)
178
+ HTM::Models::Node
179
+ .where("content ILIKE ?", "%#{topic}%")
180
+ .order(created_at: :desc)
181
+ .limit(limit)
182
+ .includes(:robot_nodes)
183
+ end
184
+
185
+ timeline = memory_timeline("architecture decisions", limit: 50)
188
186
 
189
187
  puts "Architecture discussion timeline:"
190
- timeline.each do |entry|
191
- puts "#{entry[:timestamp]} - #{entry[:robot]}"
192
- puts " [#{entry[:type]}] #{entry[:content][0..100]}..."
188
+ timeline.each do |node|
189
+ robot_ids = node.robot_nodes.map(&:robot_id)
190
+ robots = HTM::Models::Robot.where(id: robot_ids).pluck(:name)
191
+
192
+ puts "#{node.created_at} - #{robots.join(', ')}"
193
+ puts " #{node.content[0..100]}..."
193
194
  puts
194
195
  end
195
196
  ```
@@ -203,59 +204,38 @@ Each robot has a specific role and expertise:
203
204
  ```ruby
204
205
  class MultiRobotSystem
205
206
  def initialize
206
- @researcher = HTM.new(
207
- robot_name: "Researcher",
208
- robot_id: "researcher-001"
209
- )
210
-
211
- @developer = HTM.new(
212
- robot_name: "Developer",
213
- robot_id: "developer-001"
214
- )
215
-
216
- @reviewer = HTM.new(
217
- robot_name: "Reviewer",
218
- robot_id: "reviewer-001"
219
- )
207
+ @researcher = HTM.new(robot_name: "Researcher")
208
+ @developer = HTM.new(robot_name: "Developer")
209
+ @reviewer = HTM.new(robot_name: "Reviewer")
220
210
  end
221
211
 
222
212
  def process_feature_request(feature)
223
213
  # 1. Researcher gathers requirements
224
- @researcher.add_node(
225
- "research_#{feature}",
214
+ @researcher.remember(
226
215
  "Research findings for #{feature}",
227
- type: :fact,
228
- importance: 8.0,
229
- tags: ["research", feature]
216
+ tags: ["research", feature],
217
+ metadata: { category: "fact", priority: "high" }
230
218
  )
231
219
 
232
220
  # 2. Developer recalls research and implements
233
221
  research = @developer.recall(
234
- timeframe: "last hour",
235
- topic: "research #{feature}"
222
+ "research #{feature}",
223
+ timeframe: "last hour"
236
224
  )
237
225
 
238
- @developer.add_node(
239
- "impl_#{feature}",
226
+ @developer.remember(
240
227
  "Implementation plan based on research",
241
- type: :decision,
242
- importance: 9.0,
243
228
  tags: ["implementation", feature],
244
- related_to: ["research_#{feature}"]
229
+ metadata: { category: "decision", priority: "critical" }
245
230
  )
246
231
 
247
232
  # 3. Reviewer checks work
248
- work = @reviewer.recall(
249
- timeframe: "last hour",
250
- topic: feature
251
- )
233
+ work = @reviewer.recall(feature, timeframe: "last hour")
252
234
 
253
- @reviewer.add_node(
254
- "review_#{feature}",
235
+ @reviewer.remember(
255
236
  "Code review findings",
256
- type: :context,
257
- importance: 7.0,
258
- tags: ["review", feature]
237
+ tags: ["review", feature],
238
+ metadata: { category: "context" }
259
239
  )
260
240
  end
261
241
  end
@@ -275,15 +255,12 @@ class ShiftHandoff
275
255
  end
276
256
 
277
257
  def start_shift(shift_name)
278
- @current_shift = HTM.new(
279
- robot_name: "#{shift_name} Bot",
280
- robot_id: "shift-#{shift_name.downcase}"
281
- )
258
+ @current_shift = HTM.new(robot_name: "#{shift_name} Bot")
282
259
 
283
260
  # Recall context from previous shift
284
261
  handoff = @current_shift.recall(
262
+ "shift handoff urgent",
285
263
  timeframe: "last 24 hours",
286
- topic: "shift handoff urgent",
287
264
  strategy: :hybrid,
288
265
  limit: 20
289
266
  )
@@ -296,12 +273,10 @@ class ShiftHandoff
296
273
 
297
274
  def end_shift(summary)
298
275
  # Document shift handoff
299
- @current_shift.add_node(
300
- "handoff_#{Time.now.to_i}",
276
+ @current_shift.remember(
301
277
  summary,
302
- type: :context,
303
- importance: 9.0,
304
- tags: ["shift-handoff", "urgent"]
278
+ tags: ["shift-handoff", "urgent"],
279
+ metadata: { category: "context", priority: "critical" }
305
280
  )
306
281
 
307
282
  puts "Shift handoff documented"
@@ -329,24 +304,12 @@ Specialized experts provide knowledge:
329
304
  class ExpertSystem
330
305
  def initialize
331
306
  @experts = {
332
- database: HTM.new(
333
- robot_name: "Database Expert",
334
- robot_id: "expert-database"
335
- ),
336
- security: HTM.new(
337
- robot_name: "Security Expert",
338
- robot_id: "expert-security"
339
- ),
340
- performance: HTM.new(
341
- robot_name: "Performance Expert",
342
- robot_id: "expert-performance"
343
- )
307
+ database: HTM.new(robot_name: "Database Expert"),
308
+ security: HTM.new(robot_name: "Security Expert"),
309
+ performance: HTM.new(robot_name: "Performance Expert")
344
310
  }
345
311
 
346
- @general = HTM.new(
347
- robot_name: "General Assistant",
348
- robot_id: "assistant-general"
349
- )
312
+ @general = HTM.new(robot_name: "General Assistant")
350
313
  end
351
314
 
352
315
  def consult(topic)
@@ -354,23 +317,21 @@ class ExpertSystem
354
317
  expert_type = determine_expert(topic)
355
318
  expert = @experts[expert_type]
356
319
 
357
- # Get expert knowledge
320
+ # Get expert knowledge (use raw: true for full node data)
358
321
  knowledge = expert.recall(
322
+ topic,
359
323
  timeframe: "all time",
360
- topic: topic,
361
324
  strategy: :hybrid,
362
- limit: 10
325
+ limit: 10,
326
+ raw: true
363
327
  )
364
328
 
365
329
  # General assistant learns from expert
366
330
  knowledge.each do |k|
367
- @general.add_node(
368
- "learned_#{SecureRandom.hex(4)}",
369
- "Learned from #{expert_type} expert: #{k['value']}",
370
- type: :fact,
371
- importance: k['importance'],
331
+ @general.remember(
332
+ "Learned from #{expert_type} expert: #{k['content']}",
372
333
  tags: ["learned", expert_type.to_s],
373
- related_to: [k['key']]
334
+ metadata: { category: "fact", source_id: k['id'] }
374
335
  )
375
336
  end
376
337
 
@@ -410,45 +371,38 @@ class CollaborativeDecision
410
371
  end
411
372
 
412
373
  def add_participant(name, role)
413
- bot = HTM.new(
414
- robot_name: "#{name} (#{role})",
415
- robot_id: "decision-#{role.downcase}-#{SecureRandom.hex(4)}"
416
- )
374
+ bot = HTM.new(robot_name: "#{name} (#{role})")
417
375
  @participants << { name: name, role: role, bot: bot }
418
376
  bot
419
377
  end
420
378
 
421
379
  def gather_input(bot, opinion)
422
- bot.add_node(
423
- "opinion_#{SecureRandom.hex(4)}",
380
+ bot.remember(
424
381
  opinion,
425
- type: :context,
426
- importance: 8.0,
427
- tags: ["decision", @topic, "opinion"]
382
+ tags: ["decision", @topic, "opinion"],
383
+ metadata: { category: "context", priority: "high" }
428
384
  )
429
385
  end
430
386
 
431
387
  def make_decision(decision_maker)
432
388
  # Recall all opinions
433
389
  opinions = decision_maker.recall(
390
+ "decision #{@topic} opinion",
434
391
  timeframe: "last hour",
435
- topic: "decision #{@topic} opinion",
436
392
  strategy: :hybrid,
437
393
  limit: 50
438
394
  )
439
395
 
440
396
  puts "#{decision_maker.robot_name} considering:"
441
397
  opinions.each do |opinion|
442
- puts "- #{opinion['value'][0..100]}..."
398
+ puts "- #{opinion[0..100]}..."
443
399
  end
444
400
 
445
401
  # Document final decision
446
- decision_maker.add_node(
447
- "decision_#{@topic}_final",
402
+ decision_maker.remember(
448
403
  "Final decision on #{@topic} after considering team input",
449
- type: :decision,
450
- importance: 10.0,
451
- tags: ["decision", @topic, "final"]
404
+ tags: ["decision", @topic, "final"],
405
+ metadata: { category: "decision", priority: "critical" }
452
406
  )
453
407
  end
454
408
  end
@@ -475,57 +429,59 @@ decision.make_decision(lead)
475
429
 
476
430
  ### Sharing Strategies
477
431
 
478
- Control what gets shared:
432
+ Control what gets shared using tags:
479
433
 
480
434
  ```ruby
481
435
  class SmartSharing
482
- def initialize(robot_id)
483
- @htm = HTM.new(robot_name: "Smart Bot", robot_id: robot_id)
484
- @private_prefix = "private_#{robot_id}_"
436
+ def initialize(robot_name)
437
+ @htm = HTM.new(robot_name: robot_name)
438
+ @private_tag = "private:#{@htm.robot_id}"
485
439
  end
486
440
 
487
- def add_shared(key, value, **opts)
488
- # Shared with all robots
489
- @htm.add_node(key, value, **opts.merge(
490
- tags: (opts[:tags] || []) + ["shared"]
491
- ))
441
+ def add_shared(value, **opts)
442
+ # Shared with all robots - use "shared" tag
443
+ @htm.remember(value,
444
+ tags: (opts[:tags] || []) + ["shared"],
445
+ metadata: opts[:metadata] || {}
446
+ )
492
447
  end
493
448
 
494
- def add_private(key, value, **opts)
495
- # Use robot-specific key prefix
496
- private_key = "#{@private_prefix}#{key}"
497
- @htm.add_node(private_key, value, **opts.merge(
498
- tags: (opts[:tags] || []) + ["private"],
499
- importance: (opts[:importance] || 5.0)
500
- ))
449
+ def add_private(value, **opts)
450
+ # Private to this robot - use robot-specific tag
451
+ @htm.remember(value,
452
+ tags: (opts[:tags] || []) + [@private_tag],
453
+ metadata: opts[:metadata] || {}
454
+ )
501
455
  end
502
456
 
503
457
  def recall_shared(topic)
504
- # Only shared knowledge
458
+ # Query with shared tag filter
505
459
  @htm.recall(
460
+ "shared #{topic}",
506
461
  timeframe: "all time",
507
- topic: "shared #{topic}",
508
- strategy: :hybrid
509
- ).select { |m| m['tags']&.include?("shared") }
462
+ strategy: :hybrid,
463
+ query_tags: ["shared"]
464
+ )
510
465
  end
511
466
 
512
467
  def recall_private(topic)
513
- # Only my private knowledge
468
+ # Query with private tag filter
514
469
  @htm.recall(
470
+ topic,
515
471
  timeframe: "all time",
516
- topic: topic,
517
- strategy: :hybrid
518
- ).select { |m| m['key'].start_with?(@private_prefix) }
472
+ strategy: :hybrid,
473
+ query_tags: [@private_tag]
474
+ )
519
475
  end
520
476
  end
521
477
 
522
478
  # Usage
523
- bot1 = SmartSharing.new("bot-001")
524
- bot1.add_shared("shared_fact", "Everyone should know this", type: :fact)
525
- bot1.add_private("my_thought", "Private thought", type: :context)
479
+ bot1 = SmartSharing.new("Bot 001")
480
+ bot1.add_shared("Everyone should know this", metadata: { category: "fact" })
481
+ bot1.add_private("Private thought", metadata: { category: "context" })
526
482
 
527
- bot2 = SmartSharing.new("bot-002")
528
- shared = bot2.recall_shared("fact") # Can see shared_fact
483
+ bot2 = SmartSharing.new("Bot 002")
484
+ shared = bot2.recall_shared("fact") # Can see shared content
529
485
  private = bot2.recall_private("thought") # Won't see bot1's private thoughts
530
486
  ```
531
487
 
@@ -534,39 +490,30 @@ private = bot2.recall_private("thought") # Won't see bot1's private thoughts
534
490
  ### Finding Robot Activity
535
491
 
536
492
  ```ruby
537
- # Get all robots and their activity
493
+ # Get all robots and their activity using ActiveRecord
538
494
  def get_robot_activity
539
- config = HTM::Database.default_config
540
- conn = PG.connect(config)
541
-
542
- result = conn.exec(
543
- <<~SQL
544
- SELECT
545
- r.id,
546
- r.name,
547
- COUNT(n.id) as memory_count,
548
- MAX(n.created_at) as last_memory,
549
- r.last_active
550
- FROM robots r
551
- LEFT JOIN nodes n ON r.id = n.robot_id
552
- GROUP BY r.id, r.name, r.last_active
553
- ORDER BY r.last_active DESC
554
- SQL
555
- )
495
+ HTM::Models::Robot.all.map do |robot|
496
+ memory_count = robot.robot_nodes.count
497
+ last_memory = robot.robot_nodes.maximum(:last_remembered_at)
556
498
 
557
- robots = result.to_a
558
- conn.close
559
- robots
499
+ {
500
+ id: robot.id,
501
+ name: robot.name,
502
+ memory_count: memory_count,
503
+ last_memory: last_memory,
504
+ last_active_at: robot.last_active_at
505
+ }
506
+ end.sort_by { |r| r[:last_active_at] || Time.at(0) }.reverse
560
507
  end
561
508
 
562
509
  # Display activity
563
510
  robots = get_robot_activity
564
511
  puts "Robot Activity Report:"
565
512
  robots.each do |r|
566
- puts "\n#{r['name']} (#{r['id']})"
567
- puts " Memories: #{r['memory_count']}"
568
- puts " Last memory: #{r['last_memory']}"
569
- puts " Last active: #{r['last_active']}"
513
+ puts "\n#{r[:name]} (#{r[:id]})"
514
+ puts " Memories: #{r[:memory_count]}"
515
+ puts " Last memory: #{r[:last_memory]}"
516
+ puts " Last active: #{r[:last_active_at]}"
570
517
  end
571
518
  ```
572
519
 
@@ -574,37 +521,24 @@ end
574
521
 
575
522
  ```ruby
576
523
  def search_across_robots(topic, limit_per_robot: 5)
577
- config = HTM::Database.default_config
578
- conn = PG.connect(config)
579
-
580
- # Get all robots
581
- robots = conn.exec("SELECT id, name FROM robots")
582
-
583
524
  results = {}
584
525
 
585
- robots.each do |robot|
586
- # Search memories from this robot
587
- stmt = conn.prepare(
588
- "search_#{robot['id']}",
589
- <<~SQL
590
- SELECT key, value, type, importance, created_at
591
- FROM nodes
592
- WHERE robot_id = $1
593
- AND to_tsvector('english', value) @@ plainto_tsquery('english', $2)
594
- ORDER BY importance DESC
595
- LIMIT $3
596
- SQL
597
- )
526
+ HTM::Models::Robot.find_each do |robot|
527
+ # Find nodes linked to this robot via robot_nodes
528
+ node_ids = robot.robot_nodes.pluck(:node_id)
598
529
 
599
- robot_results = conn.exec_prepared(
600
- "search_#{robot['id']}",
601
- [robot['id'], topic, limit_per_robot]
602
- )
530
+ # Search within this robot's nodes using full-text search
531
+ robot_nodes = HTM::Models::Node
532
+ .where(id: node_ids)
533
+ .where("to_tsvector('english', content) @@ plainto_tsquery('english', ?)", topic)
534
+ .order(created_at: :desc)
535
+ .limit(limit_per_robot)
603
536
 
604
- results[robot['name']] = robot_results.to_a
537
+ results[robot.name] = robot_nodes.map do |n|
538
+ { id: n.id, content: n.content, created_at: n.created_at }
539
+ end
605
540
  end
606
541
 
607
- conn.close
608
542
  results
609
543
  end
610
544
 
@@ -613,7 +547,7 @@ results = search_across_robots("authentication")
613
547
  results.each do |robot_name, memories|
614
548
  puts "\n=== #{robot_name} ==="
615
549
  memories.each do |m|
616
- puts "- [#{m['type']}] #{m['value'][0..80]}..."
550
+ puts "- #{m[:content][0..80]}..."
617
551
  end
618
552
  end
619
553
  ```
@@ -624,33 +558,21 @@ end
624
558
 
625
559
  ```ruby
626
560
  class MultiRobotDashboard
627
- def initialize
628
- @config = HTM::Database.default_config
629
- end
630
-
631
561
  def summary
632
- conn = PG.connect(@config)
633
-
634
- # Total stats
635
- total_robots = conn.exec("SELECT COUNT(*) FROM robots").first['count'].to_i
636
- total_memories = conn.exec("SELECT COUNT(*) FROM nodes").first['count'].to_i
637
-
638
- # Per-robot breakdown
639
- breakdown = conn.exec(
640
- <<~SQL
641
- SELECT
642
- r.name,
643
- COUNT(n.id) as memories,
644
- AVG(n.importance) as avg_importance,
645
- MAX(n.created_at) as last_contribution
646
- FROM robots r
647
- LEFT JOIN nodes n ON r.id = n.robot_id
648
- GROUP BY r.id, r.name
649
- ORDER BY memories DESC
650
- SQL
651
- ).to_a
652
-
653
- conn.close
562
+ total_robots = HTM::Models::Robot.count
563
+ total_memories = HTM::Models::Node.count
564
+
565
+ # Per-robot breakdown using ActiveRecord
566
+ breakdown = HTM::Models::Robot.all.map do |robot|
567
+ robot_node_ids = robot.robot_nodes.pluck(:node_id)
568
+ nodes = HTM::Models::Node.where(id: robot_node_ids)
569
+
570
+ {
571
+ name: robot.name,
572
+ memories: nodes.count,
573
+ last_contribution: robot.robot_nodes.maximum(:last_remembered_at)
574
+ }
575
+ end.sort_by { |r| -r[:memories] }
654
576
 
655
577
  {
656
578
  total_robots: total_robots,
@@ -668,10 +590,9 @@ class MultiRobotDashboard
668
590
  puts "\nPer-robot breakdown:"
669
591
 
670
592
  data[:breakdown].each do |robot|
671
- puts "\n#{robot['name']}"
672
- puts " Memories: #{robot['memories']}"
673
- puts " Avg importance: #{robot['avg_importance'].to_f.round(2)}"
674
- puts " Last contribution: #{robot['last_contribution']}"
593
+ puts "\n#{robot[:name]}"
594
+ puts " Memories: #{robot[:memories]}"
595
+ puts " Last contribution: #{robot[:last_contribution]}"
675
596
  end
676
597
  end
677
598
  end
@@ -686,11 +607,11 @@ dashboard.print_summary
686
607
 
687
608
  ```ruby
688
609
  # Good: Clear, specific roles
689
- researcher = HTM.new(robot_name: "Research Specialist", robot_id: "research-001")
690
- coder = HTM.new(robot_name: "Code Generator", robot_id: "coder-001")
610
+ researcher = HTM.new(robot_name: "Research Specialist")
611
+ coder = HTM.new(robot_name: "Code Generator")
691
612
 
692
613
  # Avoid: Vague roles
693
- bot1 = HTM.new(robot_name: "Bot 1", robot_id: "bot1")
614
+ bot1 = HTM.new(robot_name: "Bot 1")
694
615
  ```
695
616
 
696
617
  ### 2. Consistent Naming
@@ -698,27 +619,23 @@ bot1 = HTM.new(robot_name: "Bot 1", robot_id: "bot1")
698
619
  ```ruby
699
620
  # Good: Consistent naming scheme
700
621
  class RobotFactory
701
- def self.create(service, purpose, instance = "001")
702
- HTM.new(
703
- robot_name: "#{service.capitalize} #{purpose.capitalize}",
704
- robot_id: "#{service}-#{purpose}-#{instance}"
705
- )
622
+ def self.create(service, purpose)
623
+ HTM.new(robot_name: "#{service.capitalize} #{purpose.capitalize}")
706
624
  end
707
625
  end
708
626
 
709
- api_assistant = RobotFactory.create("api", "assistant", "001")
710
- api_validator = RobotFactory.create("api", "validator", "001")
627
+ api_assistant = RobotFactory.create("api", "assistant")
628
+ api_validator = RobotFactory.create("api", "validator")
711
629
  ```
712
630
 
713
631
  ### 3. Attribution in Content
714
632
 
715
633
  ```ruby
716
634
  # Include attribution in the content itself
717
- bot.add_node(
718
- "finding_001",
635
+ bot.remember(
719
636
  "Research by #{bot.robot_name}: PostgreSQL outperforms MongoDB",
720
- type: :fact,
721
- importance: 8.0
637
+ tags: ["research", "database:comparison"],
638
+ metadata: { category: "fact", priority: "high" }
722
639
  )
723
640
  ```
724
641
 
@@ -727,13 +644,14 @@ bot.add_node(
727
644
  ```ruby
728
645
  # Periodically sync understanding across robots
729
646
  def sync_robots(*robots)
730
- # Find recent high-importance memories
647
+ # Find recent high-priority memories using metadata
731
648
  shared_knowledge = robots.first.recall(
649
+ "important shared",
732
650
  timeframe: "last 24 hours",
733
- topic: "important shared",
734
651
  strategy: :hybrid,
735
- limit: 50
736
- ).select { |m| m['importance'].to_f >= 8.0 }
652
+ limit: 50,
653
+ metadata: { priority: "high" }
654
+ )
737
655
 
738
656
  puts "Syncing #{shared_knowledge.length} important memories across #{robots.length} robots"
739
657
  end
@@ -743,22 +661,14 @@ end
743
661
 
744
662
  ```ruby
745
663
  def cleanup_inactive_robots(days: 30)
746
- config = HTM::Database.default_config
747
- conn = PG.connect(config)
748
-
749
664
  cutoff = Time.now - (days * 24 * 3600)
750
665
 
751
- result = conn.exec_params(
752
- "SELECT id, name FROM robots WHERE last_active < $1",
753
- [cutoff]
754
- )
666
+ inactive = HTM::Models::Robot.where("last_active_at < ?", cutoff)
755
667
 
756
668
  puts "Inactive robots (last active > #{days} days):"
757
- result.each do |robot|
758
- puts "- #{robot['name']} (#{robot['id']})"
669
+ inactive.each do |robot|
670
+ puts "- #{robot.name} (#{robot.id})"
759
671
  end
760
-
761
- conn.close
762
672
  end
763
673
 
764
674
  cleanup_inactive_robots(days: 90)
@@ -772,20 +682,9 @@ require 'htm'
772
682
  # Create a multi-robot development team
773
683
  class DevTeam
774
684
  def initialize
775
- @analyst = HTM.new(
776
- robot_name: "Requirements Analyst",
777
- robot_id: "team-analyst-001"
778
- )
779
-
780
- @developer = HTM.new(
781
- robot_name: "Senior Developer",
782
- robot_id: "team-developer-001"
783
- )
784
-
785
- @tester = HTM.new(
786
- robot_name: "QA Tester",
787
- robot_id: "team-tester-001"
788
- )
685
+ @analyst = HTM.new(robot_name: "Requirements Analyst")
686
+ @developer = HTM.new(robot_name: "Senior Developer")
687
+ @tester = HTM.new(robot_name: "QA Tester")
789
688
  end
790
689
 
791
690
  def process_feature(feature_name)
@@ -793,63 +692,68 @@ class DevTeam
793
692
 
794
693
  # 1. Analyst documents requirements
795
694
  puts "\n1. Analyst gathering requirements..."
796
- @analyst.add_node(
797
- "req_#{feature_name}",
695
+ @analyst.remember(
798
696
  "Requirements for #{feature_name}: Must support OAuth2",
799
- type: :fact,
800
- importance: 9.0,
801
- tags: ["requirements", feature_name]
697
+ tags: ["requirements", feature_name],
698
+ metadata: { category: "fact", priority: "critical" }
802
699
  )
803
700
 
804
701
  # 2. Developer recalls requirements and designs
805
702
  puts "\n2. Developer reviewing requirements..."
806
703
  requirements = @developer.recall(
807
- timeframe: "last hour",
808
- topic: "requirements #{feature_name}"
704
+ "requirements #{feature_name}",
705
+ timeframe: "last hour"
809
706
  )
810
707
 
811
708
  puts "Found #{requirements.length} requirements"
812
709
 
813
- @developer.add_node(
814
- "design_#{feature_name}",
710
+ @developer.remember(
815
711
  "Design for #{feature_name} based on requirements",
816
- type: :decision,
817
- importance: 9.0,
818
712
  tags: ["design", feature_name],
819
- related_to: ["req_#{feature_name}"]
713
+ metadata: { category: "decision", priority: "critical" }
820
714
  )
821
715
 
822
716
  # 3. Tester recalls everything and creates test plan
823
717
  puts "\n3. Tester creating test plan..."
824
718
  context = @tester.recall(
719
+ feature_name,
825
720
  timeframe: "last hour",
826
- topic: feature_name,
827
721
  strategy: :hybrid
828
722
  )
829
723
 
830
724
  puts "Tester reviewed #{context.length} items"
831
725
 
832
- @tester.add_node(
833
- "test_#{feature_name}",
726
+ @tester.remember(
834
727
  "Test plan for #{feature_name}",
835
- type: :context,
836
- importance: 8.0,
837
728
  tags: ["testing", feature_name],
838
- related_to: ["design_#{feature_name}", "req_#{feature_name}"]
729
+ metadata: { category: "context", priority: "high" }
839
730
  )
840
731
 
841
- # 4. Show collaboration
732
+ # 4. Show collaboration timeline
842
733
  puts "\n4. Collaboration summary:"
843
- timeline = @analyst.conversation_timeline(feature_name)
844
- timeline.each do |entry|
845
- puts "- #{entry[:robot]}: #{entry[:type]}"
734
+ nodes = HTM::Models::Node
735
+ .where("content ILIKE ?", "%#{feature_name}%")
736
+ .order(created_at: :asc)
737
+ .includes(:robot_nodes)
738
+
739
+ nodes.each do |node|
740
+ robot_ids = node.robot_nodes.map(&:robot_id)
741
+ robots = HTM::Models::Robot.where(id: robot_ids).pluck(:name)
742
+ puts "- #{robots.join(', ')}: #{node.content[0..50]}..."
846
743
  end
847
744
 
848
745
  # 5. Show attribution
849
746
  puts "\n5. Who contributed:"
850
- breakdown = @analyst.which_robot_said(feature_name)
851
- breakdown.each do |robot_id, count|
852
- puts "- #{robot_id}: #{count} memories"
747
+ robot_counts = Hash.new(0)
748
+ nodes.each do |node|
749
+ node.robot_nodes.each do |rn|
750
+ robot = HTM::Models::Robot.find(rn.robot_id)
751
+ robot_counts[robot.name] += 1
752
+ end
753
+ end
754
+
755
+ robot_counts.each do |robot_name, count|
756
+ puts "- #{robot_name}: #{count} memories"
853
757
  end
854
758
  end
855
759
  end