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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/README.md +269 -79
- data/db/migrate/00003_create_file_sources.rb +5 -0
- data/db/migrate/00004_create_nodes.rb +17 -0
- data/db/migrate/00005_create_tags.rb +7 -0
- data/db/migrate/00006_create_node_tags.rb +2 -0
- data/db/migrate/00007_create_robot_nodes.rb +7 -0
- data/db/schema.sql +41 -29
- data/docs/api/yard/HTM/Configuration.md +54 -0
- data/docs/api/yard/HTM/Database.md +13 -10
- data/docs/api/yard/HTM/EmbeddingService.md +5 -1
- data/docs/api/yard/HTM/LongTermMemory.md +18 -277
- data/docs/api/yard/HTM/PropositionError.md +18 -0
- data/docs/api/yard/HTM/PropositionService.md +66 -0
- data/docs/api/yard/HTM/QueryCache.md +88 -0
- data/docs/api/yard/HTM/RobotGroup.md +481 -0
- data/docs/api/yard/HTM/SqlBuilder.md +108 -0
- data/docs/api/yard/HTM/TagService.md +4 -0
- data/docs/api/yard/HTM/Telemetry/NullInstrument.md +13 -0
- data/docs/api/yard/HTM/Telemetry/NullMeter.md +15 -0
- data/docs/api/yard/HTM/Telemetry.md +109 -0
- data/docs/api/yard/HTM/WorkingMemoryChannel.md +176 -0
- data/docs/api/yard/HTM.md +11 -23
- data/docs/api/yard/index.csv +102 -25
- data/docs/api/yard-reference.md +8 -0
- data/docs/assets/images/multi-provider-failover.svg +51 -0
- data/docs/assets/images/robot-group-architecture.svg +65 -0
- data/docs/database/README.md +3 -3
- data/docs/database/public.file_sources.svg +29 -21
- data/docs/database/public.node_tags.md +2 -0
- data/docs/database/public.node_tags.svg +53 -41
- data/docs/database/public.nodes.md +2 -0
- data/docs/database/public.nodes.svg +52 -40
- data/docs/database/public.robot_nodes.md +2 -0
- data/docs/database/public.robot_nodes.svg +30 -22
- data/docs/database/public.robots.svg +16 -12
- data/docs/database/public.tags.md +3 -0
- data/docs/database/public.tags.svg +41 -33
- data/docs/database/schema.json +66 -0
- data/docs/database/schema.svg +60 -48
- data/docs/development/index.md +13 -0
- data/docs/development/rake-tasks.md +1068 -0
- data/docs/getting-started/quick-start.md +144 -155
- data/docs/guides/adding-memories.md +2 -3
- data/docs/guides/context-assembly.md +185 -184
- data/docs/guides/getting-started.md +154 -148
- data/docs/guides/index.md +7 -0
- data/docs/guides/long-term-memory.md +60 -92
- data/docs/guides/mcp-server.md +617 -0
- data/docs/guides/multi-robot.md +249 -345
- data/docs/guides/recalling-memories.md +153 -163
- data/docs/guides/robot-groups.md +604 -0
- data/docs/guides/search-strategies.md +61 -58
- data/docs/guides/working-memory.md +103 -136
- data/docs/index.md +30 -26
- data/examples/robot_groups/robot_worker.rb +1 -2
- data/examples/robot_groups/same_process.rb +1 -4
- data/lib/htm/robot_group.rb +721 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm/working_memory_channel.rb +250 -0
- data/lib/htm.rb +2 -0
- data/mkdocs.yml +2 -0
- metadata +18 -9
- data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +0 -12
- data/db/migrate/00010_add_soft_delete_to_associations.rb +0 -29
- data/db/migrate/00011_add_performance_indexes.rb +0 -21
- data/db/migrate/00012_add_tags_trigram_index.rb +0 -18
- data/db/migrate/00013_enable_lz4_compression.rb +0 -43
- data/examples/robot_groups/lib/robot_group.rb +0 -419
- data/examples/robot_groups/lib/working_memory_channel.rb +0 -140
|
@@ -4,20 +4,20 @@ This guide covers HTM's powerful RAG-based retrieval system for finding relevant
|
|
|
4
4
|
|
|
5
5
|
## Basic Recall
|
|
6
6
|
|
|
7
|
-
The `recall` method searches long-term memory using
|
|
7
|
+
The `recall` method searches long-term memory using topic and optional filters:
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
10
|
memories = htm.recall(
|
|
11
|
+
"database design", # Topic (first positional argument)
|
|
11
12
|
timeframe: "last week", # Time range to search
|
|
12
|
-
topic: "database design", # What to search for
|
|
13
13
|
limit: 20, # Max results (default: 20)
|
|
14
|
-
strategy: :vector # Search strategy (default: :
|
|
14
|
+
strategy: :vector, # Search strategy (default: :fulltext)
|
|
15
|
+
raw: true # Return full node hashes
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
memories.each do |memory|
|
|
18
|
-
puts memory['
|
|
19
|
+
puts memory['content']
|
|
19
20
|
puts "Similarity: #{memory['similarity']}"
|
|
20
|
-
puts "Importance: #{memory['importance']}"
|
|
21
21
|
puts "Created: #{memory['created_at']}"
|
|
22
22
|
puts
|
|
23
23
|
end
|
|
@@ -148,23 +148,23 @@ HTM supports both natural language timeframes and explicit ranges.
|
|
|
148
148
|
|
|
149
149
|
```ruby
|
|
150
150
|
# Last 24 hours (default if unparseable)
|
|
151
|
-
htm.recall(
|
|
151
|
+
htm.recall("...", timeframe: "today")
|
|
152
152
|
|
|
153
153
|
# Yesterday
|
|
154
|
-
htm.recall(
|
|
154
|
+
htm.recall("...", timeframe: "yesterday")
|
|
155
155
|
|
|
156
156
|
# Last week
|
|
157
|
-
htm.recall(timeframe: "last week"
|
|
157
|
+
htm.recall("...", timeframe: "last week")
|
|
158
158
|
|
|
159
159
|
# Last N days
|
|
160
|
-
htm.recall(timeframe: "last 7 days"
|
|
161
|
-
htm.recall(timeframe: "last 30 days"
|
|
160
|
+
htm.recall("...", timeframe: "last 7 days")
|
|
161
|
+
htm.recall("...", timeframe: "last 30 days")
|
|
162
162
|
|
|
163
163
|
# This month
|
|
164
|
-
htm.recall(timeframe: "this month"
|
|
164
|
+
htm.recall("...", timeframe: "this month")
|
|
165
165
|
|
|
166
166
|
# Last month
|
|
167
|
-
htm.recall(timeframe: "last month"
|
|
167
|
+
htm.recall("...", timeframe: "last month")
|
|
168
168
|
```
|
|
169
169
|
|
|
170
170
|
### Explicit Time Ranges
|
|
@@ -176,27 +176,24 @@ For precise control, use Ruby time ranges:
|
|
|
176
176
|
start_date = Time.new(2024, 1, 1)
|
|
177
177
|
end_date = Time.new(2024, 12, 31)
|
|
178
178
|
htm.recall(
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
"annual report",
|
|
180
|
+
timeframe: start_date..end_date
|
|
181
181
|
)
|
|
182
182
|
|
|
183
183
|
# Last 24 hours precisely
|
|
184
184
|
htm.recall(
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
"errors",
|
|
186
|
+
timeframe: (Time.now - 24*3600)..Time.now
|
|
187
187
|
)
|
|
188
188
|
|
|
189
|
-
#
|
|
190
|
-
htm.recall(
|
|
191
|
-
timeframe: Time.at(0)..Time.now,
|
|
192
|
-
topic: "architecture decisions"
|
|
193
|
-
)
|
|
189
|
+
# No time filter (all time)
|
|
190
|
+
htm.recall("architecture decisions")
|
|
194
191
|
|
|
195
192
|
# Relative to current time
|
|
196
193
|
three_days_ago = Time.now - (3 * 24 * 3600)
|
|
197
194
|
htm.recall(
|
|
198
|
-
|
|
199
|
-
|
|
195
|
+
"bug fixes",
|
|
196
|
+
timeframe: three_days_ago..Time.now
|
|
200
197
|
)
|
|
201
198
|
```
|
|
202
199
|
|
|
@@ -215,8 +212,8 @@ Vector search uses embeddings to find semantically similar memories.
|
|
|
215
212
|
|
|
216
213
|
```ruby
|
|
217
214
|
memories = htm.recall(
|
|
215
|
+
"improving application performance",
|
|
218
216
|
timeframe: "last month",
|
|
219
|
-
topic: "improving application performance",
|
|
220
217
|
strategy: :vector,
|
|
221
218
|
limit: 10
|
|
222
219
|
)
|
|
@@ -240,8 +237,8 @@ memories = htm.recall(
|
|
|
240
237
|
```ruby
|
|
241
238
|
# Will find memories about databases, even without the word "PostgreSQL"
|
|
242
239
|
memories = htm.recall(
|
|
240
|
+
"data persistence strategies",
|
|
243
241
|
timeframe: "last year",
|
|
244
|
-
topic: "data persistence strategies",
|
|
245
242
|
strategy: :vector
|
|
246
243
|
)
|
|
247
244
|
|
|
@@ -257,8 +254,8 @@ Full-text search uses PostgreSQL's text search for exact keyword matching.
|
|
|
257
254
|
|
|
258
255
|
```ruby
|
|
259
256
|
memories = htm.recall(
|
|
257
|
+
"PostgreSQL indexing",
|
|
260
258
|
timeframe: "last week",
|
|
261
|
-
topic: "PostgreSQL indexing",
|
|
262
259
|
strategy: :fulltext,
|
|
263
260
|
limit: 10
|
|
264
261
|
)
|
|
@@ -282,8 +279,7 @@ memories = htm.recall(
|
|
|
282
279
|
```ruby
|
|
283
280
|
# Will only find memories containing "JWT"
|
|
284
281
|
memories = htm.recall(
|
|
285
|
-
|
|
286
|
-
topic: "JWT authentication",
|
|
282
|
+
"JWT authentication",
|
|
287
283
|
strategy: :fulltext
|
|
288
284
|
)
|
|
289
285
|
|
|
@@ -300,8 +296,8 @@ Hybrid search combines full-text and vector search for optimal results.
|
|
|
300
296
|
|
|
301
297
|
```ruby
|
|
302
298
|
memories = htm.recall(
|
|
299
|
+
"database performance issues",
|
|
303
300
|
timeframe: "last month",
|
|
304
|
-
topic: "database performance issues",
|
|
305
301
|
strategy: :hybrid,
|
|
306
302
|
limit: 10
|
|
307
303
|
)
|
|
@@ -325,8 +321,8 @@ memories = htm.recall(
|
|
|
325
321
|
```ruby
|
|
326
322
|
# Combines keyword matching with semantic understanding
|
|
327
323
|
memories = htm.recall(
|
|
324
|
+
"scaling our PostgreSQL database",
|
|
328
325
|
timeframe: "last quarter",
|
|
329
|
-
topic: "scaling our PostgreSQL database",
|
|
330
326
|
strategy: :hybrid
|
|
331
327
|
)
|
|
332
328
|
|
|
@@ -351,43 +347,43 @@ memories = htm.recall(
|
|
|
351
347
|
|
|
352
348
|
```ruby
|
|
353
349
|
# Vague: Returns too many irrelevant results
|
|
354
|
-
htm.recall(timeframe: "last year"
|
|
350
|
+
htm.recall("data", timeframe: "last year")
|
|
355
351
|
|
|
356
352
|
# Specific: Returns targeted results
|
|
357
|
-
htm.recall(
|
|
353
|
+
htm.recall("PostgreSQL query optimization", timeframe: "last year")
|
|
358
354
|
```
|
|
359
355
|
|
|
360
356
|
### 2. Use Appropriate Timeframes
|
|
361
357
|
|
|
362
358
|
```ruby
|
|
363
359
|
# Too wide: Includes outdated information
|
|
364
|
-
htm.recall(
|
|
360
|
+
htm.recall("current project status", timeframe: "last 5 years")
|
|
365
361
|
|
|
366
362
|
# Right size: Recent context
|
|
367
|
-
htm.recall(
|
|
363
|
+
htm.recall("current project status", timeframe: "last week")
|
|
368
364
|
```
|
|
369
365
|
|
|
370
366
|
### 3. Adjust Limit Based on Need
|
|
371
367
|
|
|
372
368
|
```ruby
|
|
373
369
|
# Few results: Quick overview
|
|
374
|
-
htm.recall(timeframe: "last month",
|
|
370
|
+
htm.recall("errors", timeframe: "last month", limit: 5)
|
|
375
371
|
|
|
376
372
|
# Many results: Comprehensive search
|
|
377
|
-
htm.recall(
|
|
373
|
+
htm.recall("architecture decisions", timeframe: "last year", limit: 50)
|
|
378
374
|
```
|
|
379
375
|
|
|
380
376
|
### 4. Try Different Strategies
|
|
381
377
|
|
|
382
378
|
```ruby
|
|
383
379
|
# Start with hybrid (best all-around)
|
|
384
|
-
results = htm.recall(
|
|
380
|
+
results = htm.recall("authentication", strategy: :hybrid)
|
|
385
381
|
|
|
386
382
|
# If too many results, try full-text (more precise)
|
|
387
|
-
results = htm.recall(
|
|
383
|
+
results = htm.recall("JWT authentication", strategy: :fulltext)
|
|
388
384
|
|
|
389
385
|
# If no results, try vector (more flexible)
|
|
390
|
-
results = htm.recall(
|
|
386
|
+
results = htm.recall("user validation methods", strategy: :vector)
|
|
391
387
|
```
|
|
392
388
|
|
|
393
389
|
## Filtering by Metadata
|
|
@@ -397,21 +393,21 @@ HTM supports metadata filtering directly in the `recall()` method. This is more
|
|
|
397
393
|
```ruby
|
|
398
394
|
# Filter by single metadata field
|
|
399
395
|
memories = htm.recall(
|
|
400
|
-
|
|
396
|
+
"user settings",
|
|
401
397
|
metadata: { category: "preference" }
|
|
402
398
|
)
|
|
403
399
|
# => Returns only nodes with metadata containing { category: "preference" }
|
|
404
400
|
|
|
405
401
|
# Filter by multiple metadata fields
|
|
406
402
|
memories = htm.recall(
|
|
407
|
-
|
|
403
|
+
"API configuration",
|
|
408
404
|
metadata: { environment: "production", version: 2 }
|
|
409
405
|
)
|
|
410
406
|
# => Returns nodes with BOTH environment: "production" AND version: 2
|
|
411
407
|
|
|
412
408
|
# Combine with other filters
|
|
413
409
|
memories = htm.recall(
|
|
414
|
-
|
|
410
|
+
"database changes",
|
|
415
411
|
timeframe: "last month",
|
|
416
412
|
strategy: :hybrid,
|
|
417
413
|
metadata: { breaking_change: true },
|
|
@@ -429,19 +425,17 @@ Metadata filtering uses PostgreSQL's JSONB containment operator (`@>`), which me
|
|
|
429
425
|
While `recall` handles timeframes, topics, and metadata, you can filter results further:
|
|
430
426
|
|
|
431
427
|
```ruby
|
|
432
|
-
# Recall memories
|
|
428
|
+
# Recall memories with full data
|
|
433
429
|
memories = htm.recall(
|
|
430
|
+
"database",
|
|
434
431
|
timeframe: "last month",
|
|
435
|
-
topic: "database",
|
|
436
432
|
strategy: :hybrid,
|
|
437
|
-
limit: 50
|
|
433
|
+
limit: 50,
|
|
434
|
+
raw: true # Get full node hashes
|
|
438
435
|
)
|
|
439
436
|
|
|
440
|
-
# Filter by
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
# Filter by importance
|
|
444
|
-
critical = memories.select { |m| m['importance'].to_f >= 8.0 }
|
|
437
|
+
# Filter by metadata
|
|
438
|
+
high_priority = memories.select { |m| m['metadata']&.dig('priority') == 'high' }
|
|
445
439
|
|
|
446
440
|
# Filter by robot
|
|
447
441
|
my_memories = memories.select { |m| m['robot_id'] == htm.robot_id }
|
|
@@ -459,26 +453,28 @@ end
|
|
|
459
453
|
Search for multiple related topics:
|
|
460
454
|
|
|
461
455
|
```ruby
|
|
462
|
-
def search_multiple_topics(timeframe, topics, strategy: :hybrid, limit: 10)
|
|
456
|
+
def search_multiple_topics(htm, timeframe, topics, strategy: :hybrid, limit: 10)
|
|
463
457
|
results = []
|
|
464
458
|
|
|
465
459
|
topics.each do |topic|
|
|
466
460
|
results.concat(
|
|
467
461
|
htm.recall(
|
|
462
|
+
topic,
|
|
468
463
|
timeframe: timeframe,
|
|
469
|
-
topic: topic,
|
|
470
464
|
strategy: strategy,
|
|
471
|
-
limit: limit
|
|
465
|
+
limit: limit,
|
|
466
|
+
raw: true
|
|
472
467
|
)
|
|
473
468
|
)
|
|
474
469
|
end
|
|
475
470
|
|
|
476
|
-
# Remove duplicates by
|
|
477
|
-
results.uniq { |m| m['
|
|
471
|
+
# Remove duplicates by id
|
|
472
|
+
results.uniq { |m| m['id'] }
|
|
478
473
|
end
|
|
479
474
|
|
|
480
475
|
# Usage
|
|
481
476
|
memories = search_multiple_topics(
|
|
477
|
+
htm,
|
|
482
478
|
"last month",
|
|
483
479
|
["database optimization", "query performance", "indexing strategies"]
|
|
484
480
|
)
|
|
@@ -491,22 +487,23 @@ Start broad, then narrow:
|
|
|
491
487
|
```ruby
|
|
492
488
|
# First pass: Broad search
|
|
493
489
|
broad_results = htm.recall(
|
|
490
|
+
"architecture",
|
|
494
491
|
timeframe: "last year",
|
|
495
|
-
topic: "architecture",
|
|
496
492
|
strategy: :vector,
|
|
497
|
-
limit: 100
|
|
493
|
+
limit: 100,
|
|
494
|
+
raw: true
|
|
498
495
|
)
|
|
499
496
|
|
|
500
497
|
# Analyze results, refine query
|
|
501
498
|
relevant_terms = broad_results
|
|
502
499
|
.select { |m| m['similarity'].to_f > 0.7 }
|
|
503
|
-
.
|
|
500
|
+
.map { |m| m['content'].split.first(3).join(' ') }
|
|
504
501
|
.uniq
|
|
505
502
|
|
|
506
503
|
# Second pass: Refined search
|
|
507
504
|
refined_results = htm.recall(
|
|
505
|
+
"architecture #{relevant_terms.first}",
|
|
508
506
|
timeframe: "last year",
|
|
509
|
-
topic: "architecture #{relevant_terms.join(' ')}",
|
|
510
507
|
strategy: :hybrid,
|
|
511
508
|
limit: 20
|
|
512
509
|
)
|
|
@@ -517,12 +514,13 @@ refined_results = htm.recall(
|
|
|
517
514
|
Only keep high-quality matches:
|
|
518
515
|
|
|
519
516
|
```ruby
|
|
520
|
-
def recall_with_threshold(
|
|
517
|
+
def recall_with_threshold(htm, topic, timeframe: nil, threshold: 0.7, strategy: :vector)
|
|
521
518
|
results = htm.recall(
|
|
519
|
+
topic,
|
|
522
520
|
timeframe: timeframe,
|
|
523
|
-
topic: topic,
|
|
524
521
|
strategy: strategy,
|
|
525
|
-
limit: 50 # Get more candidates
|
|
522
|
+
limit: 50, # Get more candidates
|
|
523
|
+
raw: true
|
|
526
524
|
)
|
|
527
525
|
|
|
528
526
|
# Filter by similarity threshold
|
|
@@ -537,8 +535,9 @@ end
|
|
|
537
535
|
|
|
538
536
|
# Usage
|
|
539
537
|
high_quality = recall_with_threshold(
|
|
538
|
+
htm,
|
|
539
|
+
"performance optimization",
|
|
540
540
|
timeframe: "last month",
|
|
541
|
-
topic: "performance optimization",
|
|
542
541
|
threshold: 0.8
|
|
543
542
|
)
|
|
544
543
|
```
|
|
@@ -548,12 +547,13 @@ high_quality = recall_with_threshold(
|
|
|
548
547
|
Weight results by recency:
|
|
549
548
|
|
|
550
549
|
```ruby
|
|
551
|
-
def recall_time_weighted(
|
|
550
|
+
def recall_time_weighted(htm, topic, timeframe: nil, recency_weight: 0.3)
|
|
552
551
|
memories = htm.recall(
|
|
552
|
+
topic,
|
|
553
553
|
timeframe: timeframe,
|
|
554
|
-
topic: topic,
|
|
555
554
|
strategy: :hybrid,
|
|
556
|
-
limit: 50
|
|
555
|
+
limit: 50,
|
|
556
|
+
raw: true
|
|
557
557
|
)
|
|
558
558
|
|
|
559
559
|
# Calculate time-weighted score
|
|
@@ -593,14 +593,14 @@ class ContextualRecall
|
|
|
593
593
|
@current_context << { key: key, value: value }
|
|
594
594
|
end
|
|
595
595
|
|
|
596
|
-
def recall(timeframe
|
|
596
|
+
def recall(topic, timeframe: nil, strategy: :hybrid)
|
|
597
597
|
# Enhance topic with current context
|
|
598
598
|
context_terms = @current_context.map { |c| c[:value] }.join(" ")
|
|
599
599
|
enhanced_topic = "#{topic} #{context_terms}"
|
|
600
600
|
|
|
601
601
|
@htm.recall(
|
|
602
|
+
enhanced_topic,
|
|
602
603
|
timeframe: timeframe,
|
|
603
|
-
topic: enhanced_topic,
|
|
604
604
|
strategy: strategy,
|
|
605
605
|
limit: 20
|
|
606
606
|
)
|
|
@@ -614,71 +614,73 @@ recall.add_context("focus", "checkout flow")
|
|
|
614
614
|
|
|
615
615
|
# Search includes context automatically
|
|
616
616
|
results = recall.recall(
|
|
617
|
-
|
|
618
|
-
|
|
617
|
+
"payment processing",
|
|
618
|
+
timeframe: "last month"
|
|
619
619
|
)
|
|
620
620
|
```
|
|
621
621
|
|
|
622
|
-
##
|
|
622
|
+
## Looking Up Specific Memories
|
|
623
623
|
|
|
624
|
-
For known
|
|
624
|
+
For known node IDs, access the node directly via the model:
|
|
625
625
|
|
|
626
626
|
```ruby
|
|
627
|
-
#
|
|
628
|
-
|
|
627
|
+
# Look up by node ID
|
|
628
|
+
node = HTM::Models::Node.find_by(id: node_id)
|
|
629
629
|
|
|
630
|
-
if
|
|
631
|
-
puts
|
|
632
|
-
puts "
|
|
633
|
-
puts "Created: #{
|
|
630
|
+
if node
|
|
631
|
+
puts node.content
|
|
632
|
+
puts "Tags: #{node.tags.pluck(:name).join(', ')}"
|
|
633
|
+
puts "Created: #{node.created_at}"
|
|
634
634
|
else
|
|
635
635
|
puts "Memory not found"
|
|
636
636
|
end
|
|
637
637
|
```
|
|
638
638
|
|
|
639
639
|
!!! note
|
|
640
|
-
|
|
640
|
+
Direct model access is faster than `recall` because it doesn't require embedding generation or similarity calculation.
|
|
641
641
|
|
|
642
642
|
## Working with Search Results
|
|
643
643
|
|
|
644
644
|
### Result Structure
|
|
645
645
|
|
|
646
|
-
|
|
646
|
+
When using `raw: true`, each memory returned by `recall` has these fields:
|
|
647
647
|
|
|
648
648
|
```ruby
|
|
649
649
|
memory = {
|
|
650
650
|
'id' => 123, # Database ID
|
|
651
|
-
'
|
|
652
|
-
'value' => "Decision text...", # Content
|
|
653
|
-
'type' => "decision", # Memory type
|
|
654
|
-
'category' => "architecture", # Category (if set)
|
|
655
|
-
'importance' => 9.0, # Importance score
|
|
651
|
+
'content' => "Decision text...", # The memory content
|
|
656
652
|
'created_at' => "2024-01-15 10:30:00", # Timestamp
|
|
657
|
-
'robot_id' => "uuid...", # Which robot added it
|
|
658
653
|
'token_count' => 150, # Token count
|
|
659
|
-
'metadata' => { 'priority' => 'high'
|
|
654
|
+
'metadata' => { 'priority' => 'high' }, # JSONB metadata
|
|
660
655
|
'similarity' => 0.85 # Similarity score (vector/hybrid)
|
|
661
656
|
# or 'rank' for fulltext
|
|
662
657
|
}
|
|
663
658
|
```
|
|
664
659
|
|
|
660
|
+
When using `raw: false` (default), `recall` returns just the content strings:
|
|
661
|
+
|
|
662
|
+
```ruby
|
|
663
|
+
memories = htm.recall("database")
|
|
664
|
+
# => ["PostgreSQL is great...", "Use connection pooling...", ...]
|
|
665
|
+
```
|
|
666
|
+
|
|
665
667
|
### Processing Results
|
|
666
668
|
|
|
667
669
|
```ruby
|
|
668
|
-
memories = htm.recall(timeframe: "last month",
|
|
670
|
+
memories = htm.recall("errors", timeframe: "last month", raw: true)
|
|
669
671
|
|
|
670
|
-
# Sort by
|
|
671
|
-
|
|
672
|
+
# Sort by similarity
|
|
673
|
+
by_similarity = memories.sort_by { |m| -m['similarity'].to_f }
|
|
672
674
|
|
|
673
|
-
# Group by
|
|
674
|
-
|
|
675
|
+
# Group by metadata category
|
|
676
|
+
by_category = memories.group_by { |m| m['metadata']&.dig('category') }
|
|
675
677
|
|
|
676
678
|
# Extract just the content
|
|
677
|
-
content = memories.map { |m| m['
|
|
679
|
+
content = memories.map { |m| m['content'] }
|
|
678
680
|
|
|
679
681
|
# Create summary
|
|
680
682
|
summary = memories.map do |m|
|
|
681
|
-
"
|
|
683
|
+
"#{m['content'][0..100]}... (sim: #{m['similarity']})"
|
|
682
684
|
end.join("\n\n")
|
|
683
685
|
```
|
|
684
686
|
|
|
@@ -691,15 +693,16 @@ Find recent errors and their solutions:
|
|
|
691
693
|
```ruby
|
|
692
694
|
# Find recent errors
|
|
693
695
|
errors = htm.recall(
|
|
696
|
+
"error exception failure",
|
|
694
697
|
timeframe: "last 7 days",
|
|
695
|
-
topic: "error exception failure",
|
|
696
698
|
strategy: :fulltext,
|
|
697
|
-
limit: 20
|
|
699
|
+
limit: 20,
|
|
700
|
+
raw: true
|
|
698
701
|
)
|
|
699
702
|
|
|
700
|
-
# Group by error
|
|
703
|
+
# Group by error pattern
|
|
701
704
|
error_types = errors
|
|
702
|
-
.map { |e| e['
|
|
705
|
+
.map { |e| e['content'][/Error: (.+?)$/, 1] }
|
|
703
706
|
.compact
|
|
704
707
|
.tally
|
|
705
708
|
|
|
@@ -714,20 +717,21 @@ end
|
|
|
714
717
|
Track decision evolution:
|
|
715
718
|
|
|
716
719
|
```ruby
|
|
717
|
-
# Get all decisions about a topic
|
|
720
|
+
# Get all decisions about a topic (filter by metadata)
|
|
718
721
|
decisions = htm.recall(
|
|
719
|
-
|
|
720
|
-
topic: "authentication",
|
|
722
|
+
"authentication",
|
|
721
723
|
strategy: :hybrid,
|
|
722
|
-
limit: 50
|
|
723
|
-
|
|
724
|
+
limit: 50,
|
|
725
|
+
metadata: { category: "decision" },
|
|
726
|
+
raw: true
|
|
727
|
+
)
|
|
724
728
|
|
|
725
729
|
# Sort chronologically
|
|
726
730
|
timeline = decisions.sort_by { |d| d['created_at'] }
|
|
727
731
|
|
|
728
732
|
puts "Decision timeline:"
|
|
729
733
|
timeline.each do |decision|
|
|
730
|
-
puts "#{decision['created_at']}: #{decision['
|
|
734
|
+
puts "#{decision['created_at']}: #{decision['content'][0..100]}..."
|
|
731
735
|
end
|
|
732
736
|
```
|
|
733
737
|
|
|
@@ -736,34 +740,24 @@ end
|
|
|
736
740
|
Gather all knowledge about a topic:
|
|
737
741
|
|
|
738
742
|
```ruby
|
|
739
|
-
def gather_knowledge(topic)
|
|
740
|
-
# Gather
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
decisions = htm.recall(
|
|
748
|
-
timeframe: "all time",
|
|
749
|
-
topic: topic,
|
|
750
|
-
strategy: :hybrid
|
|
751
|
-
).select { |m| m['type'] == 'decision' }
|
|
752
|
-
|
|
753
|
-
code = htm.recall(
|
|
754
|
-
timeframe: "all time",
|
|
755
|
-
topic: topic,
|
|
756
|
-
strategy: :hybrid
|
|
757
|
-
).select { |m| m['type'] == 'code' }
|
|
743
|
+
def gather_knowledge(htm, topic)
|
|
744
|
+
# Gather all memories about a topic
|
|
745
|
+
all_memories = htm.recall(
|
|
746
|
+
topic,
|
|
747
|
+
strategy: :hybrid,
|
|
748
|
+
limit: 100,
|
|
749
|
+
raw: true
|
|
750
|
+
)
|
|
758
751
|
|
|
752
|
+
# Group by metadata category
|
|
759
753
|
{
|
|
760
|
-
facts:
|
|
761
|
-
decisions:
|
|
762
|
-
code_examples: code
|
|
754
|
+
facts: all_memories.select { |m| m['metadata']&.dig('category') == 'fact' },
|
|
755
|
+
decisions: all_memories.select { |m| m['metadata']&.dig('category') == 'decision' },
|
|
756
|
+
code_examples: all_memories.select { |m| m['metadata']&.dig('category') == 'code' }
|
|
763
757
|
}
|
|
764
758
|
end
|
|
765
759
|
|
|
766
|
-
knowledge = gather_knowledge("PostgreSQL")
|
|
760
|
+
knowledge = gather_knowledge(htm, "PostgreSQL")
|
|
767
761
|
```
|
|
768
762
|
|
|
769
763
|
### Use Case 4: Conversation Context
|
|
@@ -771,16 +765,16 @@ knowledge = gather_knowledge("PostgreSQL")
|
|
|
771
765
|
Recall recent conversation:
|
|
772
766
|
|
|
773
767
|
```ruby
|
|
774
|
-
def get_conversation_context(session_id, turns: 5)
|
|
775
|
-
# Get recent conversation turns
|
|
768
|
+
def get_conversation_context(htm, session_id, turns: 5)
|
|
769
|
+
# Get recent conversation turns by tag
|
|
776
770
|
htm.recall(
|
|
771
|
+
"session:#{session_id}",
|
|
777
772
|
timeframe: "last 24 hours",
|
|
778
|
-
topic: "session_#{session_id}",
|
|
779
773
|
strategy: :fulltext,
|
|
780
|
-
limit: turns * 2 # user + assistant messages
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
774
|
+
limit: turns * 2, # user + assistant messages
|
|
775
|
+
raw: true
|
|
776
|
+
).sort_by { |m| m['created_at'] }
|
|
777
|
+
.last(turns * 2)
|
|
784
778
|
end
|
|
785
779
|
```
|
|
786
780
|
|
|
@@ -835,23 +829,23 @@ end
|
|
|
835
829
|
### No Results
|
|
836
830
|
|
|
837
831
|
```ruby
|
|
838
|
-
results = htm.recall(timeframe: "last week"
|
|
832
|
+
results = htm.recall("xyz", timeframe: "last week")
|
|
839
833
|
|
|
840
834
|
if results.empty?
|
|
841
835
|
# Try wider timeframe
|
|
842
|
-
results = htm.recall(timeframe: "last month"
|
|
836
|
+
results = htm.recall("xyz", timeframe: "last month")
|
|
843
837
|
|
|
844
838
|
# Try different strategy
|
|
845
839
|
results = htm.recall(
|
|
840
|
+
"xyz",
|
|
846
841
|
timeframe: "last month",
|
|
847
|
-
topic: "xyz",
|
|
848
842
|
strategy: :vector # More flexible
|
|
849
843
|
)
|
|
850
844
|
|
|
851
845
|
# Try related terms
|
|
852
846
|
results = htm.recall(
|
|
847
|
+
"xyz related similar",
|
|
853
848
|
timeframe: "last month",
|
|
854
|
-
topic: "xyz related similar",
|
|
855
849
|
strategy: :vector
|
|
856
850
|
)
|
|
857
851
|
end
|
|
@@ -877,11 +871,11 @@ If vector search fails:
|
|
|
877
871
|
|
|
878
872
|
```ruby
|
|
879
873
|
begin
|
|
880
|
-
results = htm.recall(
|
|
874
|
+
results = htm.recall("...", strategy: :vector)
|
|
881
875
|
rescue => e
|
|
882
876
|
warn "Vector search failed: #{e.message}"
|
|
883
877
|
warn "Falling back to full-text search"
|
|
884
|
-
results = htm.recall(
|
|
878
|
+
results = htm.recall("...", strategy: :fulltext)
|
|
885
879
|
end
|
|
886
880
|
```
|
|
887
881
|
|
|
@@ -899,33 +893,29 @@ require 'htm'
|
|
|
899
893
|
htm = HTM.new(robot_name: "Search Demo")
|
|
900
894
|
|
|
901
895
|
# Add test memories
|
|
902
|
-
htm.
|
|
903
|
-
"decision_db",
|
|
896
|
+
htm.remember(
|
|
904
897
|
"Chose PostgreSQL for its reliability and ACID compliance",
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
tags: ["database", "postgresql", "architecture"]
|
|
898
|
+
tags: ["database:postgresql", "architecture:decisions"],
|
|
899
|
+
metadata: { category: "decision" }
|
|
908
900
|
)
|
|
909
901
|
|
|
910
|
-
htm.
|
|
911
|
-
"code_connection",
|
|
902
|
+
htm.remember(
|
|
912
903
|
"conn = PG.connect(dbname: 'mydb')",
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
tags: ["postgresql", "ruby", "connection"]
|
|
904
|
+
tags: ["database:postgresql", "ruby:patterns"],
|
|
905
|
+
metadata: { category: "code" }
|
|
916
906
|
)
|
|
917
907
|
|
|
918
908
|
# Vector search: Semantic understanding
|
|
919
909
|
puts "=== Vector Search ==="
|
|
920
910
|
vector_results = htm.recall(
|
|
921
|
-
|
|
922
|
-
topic: "data persistence strategies",
|
|
911
|
+
"data persistence strategies",
|
|
923
912
|
strategy: :vector,
|
|
924
|
-
limit: 10
|
|
913
|
+
limit: 10,
|
|
914
|
+
raw: true
|
|
925
915
|
)
|
|
926
916
|
|
|
927
917
|
vector_results.each do |m|
|
|
928
|
-
puts "#{m['
|
|
918
|
+
puts "#{m['content'][0..80]}..."
|
|
929
919
|
puts " Similarity: #{m['similarity']}"
|
|
930
920
|
puts
|
|
931
921
|
end
|
|
@@ -933,14 +923,14 @@ end
|
|
|
933
923
|
# Full-text search: Exact keywords
|
|
934
924
|
puts "\n=== Full-text Search ==="
|
|
935
925
|
fulltext_results = htm.recall(
|
|
936
|
-
|
|
937
|
-
topic: "PostgreSQL",
|
|
926
|
+
"PostgreSQL",
|
|
938
927
|
strategy: :fulltext,
|
|
939
|
-
limit: 10
|
|
928
|
+
limit: 10,
|
|
929
|
+
raw: true
|
|
940
930
|
)
|
|
941
931
|
|
|
942
932
|
fulltext_results.each do |m|
|
|
943
|
-
puts "#{m['
|
|
933
|
+
puts "#{m['content'][0..80]}..."
|
|
944
934
|
puts " Rank: #{m['rank']}"
|
|
945
935
|
puts
|
|
946
936
|
end
|
|
@@ -948,15 +938,15 @@ end
|
|
|
948
938
|
# Hybrid search: Best of both
|
|
949
939
|
puts "\n=== Hybrid Search ==="
|
|
950
940
|
hybrid_results = htm.recall(
|
|
951
|
-
|
|
952
|
-
topic: "database connection setup",
|
|
941
|
+
"database connection setup",
|
|
953
942
|
strategy: :hybrid,
|
|
954
|
-
limit: 10
|
|
943
|
+
limit: 10,
|
|
944
|
+
raw: true
|
|
955
945
|
)
|
|
956
946
|
|
|
957
947
|
hybrid_results.each do |m|
|
|
958
|
-
puts "
|
|
959
|
-
puts "
|
|
948
|
+
puts "#{m['content'][0..80]}..."
|
|
949
|
+
puts " Similarity: #{m['similarity']}"
|
|
960
950
|
puts
|
|
961
951
|
end
|
|
962
952
|
```
|