htm 0.0.31 → 0.0.32

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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/.irbrc +2 -3
  3. data/.rubocop.yml +184 -0
  4. data/CHANGELOG.md +46 -0
  5. data/README.md +2 -0
  6. data/Rakefile +93 -12
  7. data/db/migrate/00008_create_node_relationships.rb +54 -0
  8. data/db/migrate/00009_fix_node_relationships_column_types.rb +17 -0
  9. data/db/schema.sql +124 -1
  10. data/docs/api/database.md +35 -57
  11. data/docs/api/embedding-service.md +1 -1
  12. data/docs/api/index.md +26 -15
  13. data/docs/api/working-memory.md +8 -8
  14. data/docs/architecture/index.md +5 -7
  15. data/docs/architecture/overview.md +5 -8
  16. data/docs/assets/images/htm-architecture-overview.svg +1 -1
  17. data/docs/assets/images/htm-context-assembly-flow.svg +2 -2
  18. data/docs/assets/images/htm-layered-architecture.svg +3 -3
  19. data/docs/assets/images/two-tier-memory-architecture.svg +1 -1
  20. data/docs/database/README.md +1 -0
  21. data/docs/database_rake_tasks.md +20 -28
  22. data/docs/development/contributing.md +5 -5
  23. data/docs/development/index.md +4 -7
  24. data/docs/development/schema.md +71 -1
  25. data/docs/development/setup.md +40 -82
  26. data/docs/development/testing.md +1 -1
  27. data/docs/examples/file-loading.md +4 -4
  28. data/docs/examples/mcp-client.md +1 -1
  29. data/docs/getting-started/quick-start.md +4 -4
  30. data/docs/guides/adding-memories.md +14 -1
  31. data/docs/guides/configuration.md +5 -5
  32. data/docs/guides/context-assembly.md +4 -4
  33. data/docs/guides/file-loading.md +12 -12
  34. data/docs/guides/getting-started.md +2 -2
  35. data/docs/guides/long-term-memory.md +7 -27
  36. data/docs/guides/propositions.md +20 -19
  37. data/docs/guides/recalling-memories.md +5 -5
  38. data/docs/guides/tags.md +18 -13
  39. data/docs/multi_framework_support.md +1 -1
  40. data/docs/robots/hive-mind.md +1 -1
  41. data/docs/robots/multi-robot.md +2 -2
  42. data/docs/robots/robot-groups.md +1 -1
  43. data/docs/robots/two-tier-memory.md +72 -94
  44. data/docs/setup_local_database.md +8 -54
  45. data/docs/using_rake_tasks_in_your_app.md +6 -6
  46. data/examples/01_basic_usage.rb +1 -0
  47. data/examples/03_custom_llm_configuration.rb +1 -0
  48. data/examples/04_file_loader_usage.rb +1 -0
  49. data/examples/05_timeframe_demo.rb +1 -0
  50. data/examples/06_example_app/app.rb +1 -0
  51. data/examples/07_cli_app/htm_cli.rb +1 -0
  52. data/examples/09_mcp_client.rb +1 -0
  53. data/examples/10_telemetry/demo.rb +1 -0
  54. data/examples/11_robot_groups/multi_process.rb +1 -0
  55. data/examples/11_robot_groups/same_process.rb +1 -0
  56. data/examples/12_rails_app/.envrc +12 -0
  57. data/examples/12_rails_app/Gemfile +8 -3
  58. data/examples/12_rails_app/Gemfile.lock +94 -89
  59. data/examples/12_rails_app/README.md +70 -19
  60. data/examples/12_rails_app/app/controllers/application_controller.rb +6 -0
  61. data/examples/12_rails_app/app/controllers/chats_controller.rb +305 -0
  62. data/examples/12_rails_app/app/controllers/dashboard_controller.rb +3 -0
  63. data/examples/12_rails_app/app/controllers/files_controller.rb +17 -2
  64. data/examples/12_rails_app/app/controllers/home_controller.rb +8 -0
  65. data/examples/12_rails_app/app/controllers/memories_controller.rb +9 -4
  66. data/examples/12_rails_app/app/controllers/messages_controller.rb +214 -0
  67. data/examples/12_rails_app/app/controllers/robots_controller.rb +11 -1
  68. data/examples/12_rails_app/app/controllers/tags_controller.rb +14 -1
  69. data/examples/12_rails_app/app/javascript/application.js +1 -1
  70. data/examples/12_rails_app/app/models/application_record.rb +5 -0
  71. data/examples/12_rails_app/app/models/chat.rb +36 -0
  72. data/examples/12_rails_app/app/models/message.rb +5 -0
  73. data/examples/12_rails_app/app/models/model.rb +5 -0
  74. data/examples/12_rails_app/app/models/tool_call.rb +5 -0
  75. data/examples/12_rails_app/app/views/chats/index.html.erb +61 -0
  76. data/examples/12_rails_app/app/views/chats/show.html.erb +213 -0
  77. data/examples/12_rails_app/app/views/dashboard/index.html.erb +3 -0
  78. data/examples/12_rails_app/app/views/files/index.html.erb +10 -5
  79. data/examples/12_rails_app/app/views/files/new.html.erb +4 -2
  80. data/examples/12_rails_app/app/views/files/show.html.erb +19 -3
  81. data/examples/12_rails_app/app/views/home/index.html.erb +45 -0
  82. data/examples/12_rails_app/app/views/layouts/application.html.erb +20 -18
  83. data/examples/12_rails_app/app/views/memories/_memory_card.html.erb +1 -1
  84. data/examples/12_rails_app/app/views/memories/deleted.html.erb +3 -1
  85. data/examples/12_rails_app/app/views/memories/edit.html.erb +2 -0
  86. data/examples/12_rails_app/app/views/memories/index.html.erb +2 -0
  87. data/examples/12_rails_app/app/views/memories/new.html.erb +2 -0
  88. data/examples/12_rails_app/app/views/memories/show.html.erb +4 -2
  89. data/examples/12_rails_app/app/views/messages/_message.html.erb +20 -0
  90. data/examples/12_rails_app/app/views/robots/index.html.erb +2 -0
  91. data/examples/12_rails_app/app/views/robots/new.html.erb +2 -0
  92. data/examples/12_rails_app/app/views/robots/show.html.erb +2 -0
  93. data/examples/12_rails_app/app/views/search/index.html.erb +59 -8
  94. data/examples/12_rails_app/app/views/shared/_navbar.html.erb +75 -29
  95. data/examples/12_rails_app/app/views/tags/index.html.erb +2 -0
  96. data/examples/12_rails_app/app/views/tags/show.html.erb +3 -1
  97. data/examples/12_rails_app/config/application.rb +1 -1
  98. data/examples/12_rails_app/config/database.yml +9 -5
  99. data/examples/12_rails_app/config/importmap.rb +1 -1
  100. data/examples/12_rails_app/config/initializers/htm.rb +9 -2
  101. data/examples/12_rails_app/config/initializers/ruby_llm.rb +33 -0
  102. data/examples/12_rails_app/config/routes.rb +39 -23
  103. data/examples/12_rails_app/db/migrate/20250124000001_create_ruby_llm_tables.rb +34 -0
  104. data/examples/12_rails_app/db/migrate/20250124000002_create_models_table.rb +28 -0
  105. data/examples/12_rails_app/db/schema.rb +67 -0
  106. data/examples/examples_helper.rb +25 -0
  107. data/lib/htm/circuit_breaker.rb +5 -6
  108. data/lib/htm/config/builder.rb +12 -12
  109. data/lib/htm/config/database.rb +21 -27
  110. data/lib/htm/config/validator.rb +12 -18
  111. data/lib/htm/config.rb +76 -65
  112. data/lib/htm/database.rb +193 -199
  113. data/lib/htm/embedding_service.rb +4 -9
  114. data/lib/htm/integrations/sinatra.rb +7 -7
  115. data/lib/htm/job_adapter.rb +14 -21
  116. data/lib/htm/jobs/generate_embedding_job.rb +28 -44
  117. data/lib/htm/jobs/generate_propositions_job.rb +29 -55
  118. data/lib/htm/jobs/generate_relationships_job.rb +137 -0
  119. data/lib/htm/jobs/generate_tags_job.rb +45 -67
  120. data/lib/htm/loaders/markdown_loader.rb +65 -112
  121. data/lib/htm/long_term_memory/fulltext_search.rb +1 -1
  122. data/lib/htm/long_term_memory/hybrid_search.rb +300 -128
  123. data/lib/htm/long_term_memory/node_operations.rb +2 -2
  124. data/lib/htm/long_term_memory/relevance_scorer.rb +100 -68
  125. data/lib/htm/long_term_memory/tag_operations.rb +87 -120
  126. data/lib/htm/long_term_memory/vector_search.rb +1 -1
  127. data/lib/htm/long_term_memory.rb +2 -1
  128. data/lib/htm/mcp/cli.rb +59 -58
  129. data/lib/htm/mcp/server.rb +5 -6
  130. data/lib/htm/mcp/tools.rb +30 -36
  131. data/lib/htm/migration.rb +10 -10
  132. data/lib/htm/models/node.rb +2 -3
  133. data/lib/htm/models/node_relationship.rb +72 -0
  134. data/lib/htm/models/node_tag.rb +2 -2
  135. data/lib/htm/models/robot_node.rb +2 -2
  136. data/lib/htm/models/tag.rb +41 -28
  137. data/lib/htm/observability.rb +45 -51
  138. data/lib/htm/proposition_service.rb +3 -7
  139. data/lib/htm/query_cache.rb +13 -15
  140. data/lib/htm/railtie.rb +1 -2
  141. data/lib/htm/robot_group.rb +9 -9
  142. data/lib/htm/sequel_config.rb +1 -0
  143. data/lib/htm/sql_builder.rb +1 -1
  144. data/lib/htm/tag_service.rb +2 -6
  145. data/lib/htm/timeframe.rb +4 -5
  146. data/lib/htm/timeframe_extractor.rb +42 -83
  147. data/lib/htm/version.rb +1 -1
  148. data/lib/htm/workflows/remember_workflow.rb +112 -115
  149. data/lib/htm/working_memory.rb +21 -26
  150. data/lib/htm.rb +103 -116
  151. data/lib/tasks/db.rake +0 -2
  152. data/lib/tasks/doc.rake +14 -13
  153. data/lib/tasks/files.rake +5 -12
  154. data/lib/tasks/htm.rake +70 -71
  155. data/lib/tasks/jobs.rake +41 -47
  156. data/lib/tasks/tags.rake +3 -8
  157. metadata +25 -100
@@ -52,10 +52,10 @@ Context assembly transforms working memory into LLM-ready context:
52
52
  <text x="350" y="120" class="text-label">:recent</text>
53
53
  <text x="550" y="120" text-anchor="end" class="text-small">Sort by access time</text>
54
54
 
55
- <!-- Strategy 2: Important -->
55
+ <!-- Strategy 2: Frequent -->
56
56
  <rect x="340" y="145" width="220" height="35" fill="rgba(76, 175, 80, 0.3)" rx="3"/>
57
- <text x="350" y="165" class="text-label">:important</text>
58
- <text x="550" y="165" text-anchor="end" class="text-small">Sort by importance</text>
57
+ <text x="350" y="165" class="text-label">:frequent</text>
58
+ <text x="550" y="165" text-anchor="end" class="text-small">Sort by access count</text>
59
59
 
60
60
  <!-- Strategy 3: Balanced -->
61
61
  <rect x="340" y="190" width="220" height="35" fill="rgba(76, 175, 80, 0.3)" rx="3"/>
@@ -70,7 +70,7 @@ Context assembly transforms working memory into LLM-ready context:
70
70
  <text x="750" y="105" text-anchor="middle" class="text-small">(Ordered)</text>
71
71
 
72
72
  <text x="660" y="135" class="text-label">Node 3</text>
73
- <text x="840" y="135" text-anchor="end" class="text-small">(important)</text>
73
+ <text x="840" y="135" text-anchor="end" class="text-small">(frequent)</text>
74
74
  <text x="660" y="155" class="text-label">Node 1</text>
75
75
  <text x="840" y="155" text-anchor="end" class="text-small">(recent)</text>
76
76
  <text x="660" y="175" class="text-label">Node 5</text>
@@ -125,7 +125,7 @@ Content starts here...
125
125
  Access frontmatter via the FileSource model:
126
126
 
127
127
  ```ruby
128
- source = HTM::Models::FileSource.find_by(file_path: "docs/guide.md")
128
+ source = HTM::Models::FileSource.first(file_path: "docs/guide.md")
129
129
  source.title # => "PostgreSQL Guide"
130
130
  source.author # => "HTM Team"
131
131
  source.frontmatter_tags # => ["database", "postgresql"]
@@ -146,13 +146,13 @@ HTM uses the [Baran gem](https://github.com/baran) with `MarkdownSplitter` for i
146
146
  ```ruby
147
147
  # Global configuration
148
148
  HTM.configure do |config|
149
- config.chunk_size = 1024 # Characters per chunk (default: 1024)
150
- config.chunk_overlap = 64 # Overlap between chunks (default: 64)
149
+ config.chunking.chunk_size = 1024 # Characters per chunk (default: 1024)
150
+ config.chunking.chunk_overlap = 64 # Overlap between chunks (default: 64)
151
151
  end
152
152
 
153
153
  # Or via environment variables
154
- # HTM_CHUNK_SIZE=512
155
- # HTM_CHUNK_OVERLAP=50
154
+ # HTM_CHUNKING__CHUNK_SIZE=512
155
+ # HTM_CHUNKING__CHUNK_OVERLAP=50
156
156
  ```
157
157
 
158
158
  ### Per-Loader Configuration
@@ -198,7 +198,7 @@ htm.load_file("docs/guide.md", force: true)
198
198
  The `HTM::Models::FileSource` tracks loaded files:
199
199
 
200
200
  ```ruby
201
- source = HTM::Models::FileSource.find_by(file_path: "docs/guide.md")
201
+ source = HTM::Models::FileSource.first(file_path: "docs/guide.md")
202
202
 
203
203
  source.file_path # Full path to file
204
204
  source.mtime # Last modification time
@@ -269,17 +269,17 @@ priority: high
269
269
 
270
270
  ```ruby
271
271
  # Smaller chunks for dense technical content
272
- HTM.configure { |c| c.chunk_size = 512 }
272
+ HTM.configure { |c| c.chunking.chunk_size = 512 }
273
273
 
274
274
  # Larger chunks for narrative content
275
- HTM.configure { |c| c.chunk_size = 2048 }
275
+ HTM.configure { |c| c.chunking.chunk_size = 2048 }
276
276
  ```
277
277
 
278
278
  ### Regular Sync for Updated Content
279
279
 
280
280
  ```ruby
281
- # Sync all loaded files periodically
282
- htm.sync_files # Re-checks all FileSource records
281
+ # Sync all loaded files periodically (via rake task)
282
+ # rake htm:files:sync # Re-checks all FileSource records
283
283
  ```
284
284
 
285
285
  ## Example: Building a Knowledge Base
@@ -292,8 +292,8 @@ htm = HTM.new(robot_name: "Knowledge Base")
292
292
 
293
293
  # Configure chunking for technical docs
294
294
  HTM.configure do |config|
295
- config.chunk_size = 768
296
- config.chunk_overlap = 100
295
+ config.chunking.chunk_size = 768
296
+ config.chunking.chunk_overlap = 100
297
297
  end
298
298
 
299
299
  # Load documentation
@@ -7,7 +7,7 @@ Welcome to HTM! This guide will help you build your first intelligent memory sys
7
7
  Before starting, ensure you have:
8
8
 
9
9
  1. **Ruby 3.0+** installed
10
- 2. **PostgreSQL with TimescaleDB** (or access to a TimescaleDB cloud instance)
10
+ 2. **PostgreSQL 16+** with pgvector and pg_trgm extensions
11
11
  3. **LLM Provider** configured - Ollama (default for local development), OpenAI, Anthropic, Gemini, or others via RubyLLM
12
12
  4. Basic understanding of Ruby and LLMs
13
13
 
@@ -72,7 +72,7 @@ gem install htm
72
72
 
73
73
  ## Database Setup
74
74
 
75
- HTM requires a TimescaleDB database. Set your database connection:
75
+ HTM requires a PostgreSQL database with pgvector and pg_trgm. Set your database connection:
76
76
 
77
77
  ```bash
78
78
  # Add to your .bashrc or .zshrc
@@ -9,7 +9,7 @@ Long-term memory provides:
9
9
  - **Permanent storage** for all memories
10
10
  - **Vector embeddings** via pgvector
11
11
  - **Full-text search** via PostgreSQL's ts_vector
12
- - **Time-series optimization** via TimescaleDB hypertables
12
+ - **Temporal filtering** via created_at index and Chronic gem for natural language timeframes
13
13
  - **Relationship graphs** for knowledge connections
14
14
  - **Audit logging** for all operations
15
15
 
@@ -383,33 +383,13 @@ conn.close
383
383
  puts "Reindexing completed"
384
384
  ```
385
385
 
386
- ### Compression (TimescaleDB)
386
+ ### Cleanup Old Soft-Deleted Nodes
387
387
 
388
- TimescaleDB can compress old data:
388
+ Periodically purge soft-deleted nodes to reclaim storage:
389
389
 
390
390
  ```ruby
391
- # Enable compression on operations_log hypertable
392
- conn = PG.connect(HTM::Database.default_config)
393
-
394
- conn.exec(
395
- <<~SQL
396
- ALTER TABLE operations_log SET (
397
- timescaledb.compress,
398
- timescaledb.compress_segmentby = 'robot_id'
399
- )
400
- SQL
401
- )
402
-
403
- # Add compression policy (compress data older than 7 days)
404
- conn.exec(
405
- <<~SQL
406
- SELECT add_compression_policy('operations_log', INTERVAL '7 days')
407
- SQL
408
- )
409
-
410
- conn.close
411
-
412
- puts "Compression policy enabled"
391
+ # Purge nodes soft-deleted more than 90 days ago
392
+ htm.purge_deleted(older_than: Time.now - (90 * 24 * 60 * 60), confirm: :confirmed)
413
393
  ```
414
394
 
415
395
  ### Cleanup Old Logs
@@ -882,9 +862,9 @@ puts "Total nodes: #{HTM::Models::Node.count}"
882
862
  puts "Total tags: #{HTM::Models::Tag.count}"
883
863
  puts "Active robots: #{HTM::Models::Robot.count}"
884
864
 
885
- # Query by tag using ActiveRecord
865
+ # Query by tag
886
866
  puts "\n=== Query by Tag ==="
887
- test_tag = HTM::Models::Tag.find_by(name: "test")
867
+ test_tag = HTM::Models::Tag.first(name: "test")
888
868
  if test_tag
889
869
  tagged_nodes = test_tag.nodes
890
870
  puts "Found #{tagged_nodes.count} nodes with tag 'test'"
@@ -40,13 +40,13 @@ A proposition is an atomic factual statement that:
40
40
  ```ruby
41
41
  # Via configuration block
42
42
  HTM.configure do |config|
43
- config.extract_propositions = true
44
- config.proposition_provider = :ollama # or :openai, :anthropic, etc.
45
- config.proposition_model = 'gemma3:latest'
43
+ config.proposition.enabled = true
44
+ config.proposition.provider = :ollama # or :openai, :anthropic, etc.
45
+ config.proposition.model = 'gemma3:latest'
46
46
  end
47
47
 
48
48
  # Or via environment variable
49
- # HTM_EXTRACT_PROPOSITIONS=true
49
+ # HTM_PROPOSITION__ENABLED=true
50
50
  ```
51
51
 
52
52
  ### Provider Options
@@ -62,11 +62,11 @@ Proposition extraction uses LLM chat completion. Configure your preferred provid
62
62
 
63
63
  ```ruby
64
64
  HTM.configure do |config|
65
- config.extract_propositions = true
65
+ config.proposition.enabled = true
66
66
 
67
67
  # Use OpenAI for higher quality extraction
68
- config.proposition_provider = :openai
69
- config.proposition_model = 'gpt-4o-mini'
68
+ config.proposition.provider = :openai
69
+ config.proposition.model = 'gpt-4o-mini'
70
70
  end
71
71
  ```
72
72
 
@@ -150,13 +150,14 @@ propositions.each { |p| htm.remember(p) }
150
150
 
151
151
  ```ruby
152
152
  # Find all proposition nodes
153
- propositions = HTM::Models::Node.where("metadata->>'is_proposition' = ?", 'true')
153
+ propositions = HTM::Models::Node.where(
154
+ Sequel.lit("metadata->>'is_proposition' = ?", 'true')
155
+ )
154
156
 
155
157
  # Find propositions from a specific source
156
158
  source_node_id = 123
157
159
  related = HTM::Models::Node.where(
158
- "metadata->>'source_node_id' = ?",
159
- source_node_id.to_s
160
+ Sequel.lit("metadata->>'source_node_id' = ?", source_node_id.to_s)
160
161
  )
161
162
 
162
163
  # Include propositions in recall (default behavior)
@@ -193,8 +194,8 @@ With async job backend, extraction happens in background:
193
194
 
194
195
  ```ruby
195
196
  HTM.configure do |config|
196
- config.extract_propositions = true
197
- config.job.backend = :thread # or :sidekiq
197
+ config.proposition.enabled = true
198
+ config.job.backend = :fiber # or :sidekiq
198
199
  end
199
200
 
200
201
  # Returns immediately, propositions created async
@@ -229,12 +230,12 @@ Use a capable model for better extraction:
229
230
 
230
231
  ```ruby
231
232
  # Higher quality (slower, costs more)
232
- config.proposition_provider = :openai
233
- config.proposition_model = 'gpt-4o'
233
+ config.proposition.provider = :openai
234
+ config.proposition.model = 'gpt-4o'
234
235
 
235
236
  # Balanced (faster, local)
236
- config.proposition_provider = :ollama
237
- config.proposition_model = 'gemma3:latest'
237
+ config.proposition.provider = :ollama
238
+ config.proposition.model = 'gemma3:latest'
238
239
  ```
239
240
 
240
241
  ### Selective Extraction
@@ -243,10 +244,10 @@ Enable/disable per operation if needed:
243
244
 
244
245
  ```ruby
245
246
  # Temporarily disable for specific content
246
- original_setting = HTM.configuration.extract_propositions
247
- HTM.configuration.extract_propositions = false
247
+ original_setting = HTM.config.proposition.enabled
248
+ HTM.config.proposition.enabled = false
248
249
  htm.remember("Simple fact that doesn't need decomposition.")
249
- HTM.configuration.extract_propositions = original_setting
250
+ HTM.config.proposition.enabled = original_setting
250
251
  ```
251
252
 
252
253
  ## Rake Tasks
@@ -625,11 +625,11 @@ For known node IDs, access the node directly via the model:
625
625
 
626
626
  ```ruby
627
627
  # Look up by node ID
628
- node = HTM::Models::Node.find_by(id: node_id)
628
+ node = HTM::Models::Node.first(id: node_id)
629
629
 
630
630
  if node
631
631
  puts node.content
632
- puts "Tags: #{node.tags.pluck(:name).join(', ')}"
632
+ puts "Tags: #{node.tag_names.join(', ')}"
633
633
  puts "Created: #{node.created_at}"
634
634
  else
635
635
  puts "Memory not found"
@@ -792,10 +792,10 @@ Times vary based on database size and query complexity.
792
792
 
793
793
  ```ruby
794
794
  # Slow: Wide timeframe + high limit
795
- htm.recall(timeframe: "last 5 years", topic: "...", limit: 1000)
795
+ htm.recall("...", timeframe: "last 5 years", limit: 1000)
796
796
 
797
797
  # Fast: Narrow timeframe + reasonable limit
798
- htm.recall(timeframe: "last week", topic: "...", limit: 20)
798
+ htm.recall("...", timeframe: "last week", limit: 20)
799
799
  ```
800
800
 
801
801
  ### Caching Results
@@ -860,7 +860,7 @@ good_results = results.select do |m|
860
860
  end
861
861
 
862
862
  # Or boost limit and take top results
863
- htm.recall(timeframe: "...", topic: "...", limit: 100)
863
+ htm.recall("...", timeframe: "...", limit: 100, raw: true)
864
864
  .sort_by { |m| -m['similarity'].to_f }
865
865
  .first(10)
866
866
  ```
data/docs/guides/tags.md CHANGED
@@ -121,11 +121,19 @@ htm.long_term_memory.add_tag(node_id: node.id, tag: "new:tag")
121
121
  tags = htm.long_term_memory.node_topics(node.id)
122
122
  # => ["database:postgresql", "search:vector"]
123
123
 
124
- # Find nodes by tag
125
- nodes = HTM::Models::Node.joins(:tags).where(tags: { name: "database:postgresql" })
124
+ # Find nodes by tag (via Sequel)
125
+ nodes = HTM::Models::Node
126
+ .join(:nodes_tags, node_id: :id)
127
+ .join(:tags, id: :tag_id)
128
+ .where(Sequel[:tags][:name] => "database:postgresql")
129
+ .all
126
130
 
127
131
  # Find by tag prefix
128
- nodes = HTM::Models::Node.joins(:tags).where("tags.name LIKE ?", "database:%")
132
+ nodes = HTM::Models::Node
133
+ .join(:nodes_tags, node_id: :id)
134
+ .join(:tags, id: :tag_id)
135
+ .where(Sequel.like(Sequel[:tags][:name], "database:%"))
136
+ .all
129
137
  ```
130
138
 
131
139
  ### Tag Relationships
@@ -218,15 +226,15 @@ rake htm:tags:rebuild
218
226
 
219
227
  ```ruby
220
228
  # Filter by specific tag
221
- results = htm.recall("query", tags: ["database:postgresql"])
229
+ results = htm.recall("query", query_tags: ["database:postgresql"])
222
230
 
223
231
  # Filter by multiple tags (AND)
224
- results = htm.recall("query", tags: ["database:postgresql", "search:vector"])
232
+ results = htm.recall("query", query_tags: ["database:postgresql", "search:vector"])
225
233
 
226
234
  # Combine with other filters
227
235
  results = htm.recall(
228
236
  "performance optimization",
229
- tags: ["database:postgresql"],
237
+ query_tags: ["database:postgresql"],
230
238
  timeframe: "last week",
231
239
  strategy: :hybrid,
232
240
  limit: 10
@@ -236,14 +244,11 @@ results = htm.recall(
236
244
  ### Direct Queries
237
245
 
238
246
  ```ruby
239
- # Find all nodes with a tag
240
- HTM::Models::Node.with_tag("database:postgresql")
247
+ # Find all nodes with a specific tag (via LongTermMemory)
248
+ results = htm.long_term_memory.search_by_tags(tags: ["database:postgresql"])
241
249
 
242
- # Find nodes with any of several tags
243
- HTM::Models::Node.with_any_tags(["database:postgresql", "database:mysql"])
244
-
245
- # Find nodes with all specified tags
246
- HTM::Models::Node.with_all_tags(["database:postgresql", "search:vector"])
250
+ # Find nodes with multiple tags
251
+ results = htm.long_term_memory.search_by_tags(tags: ["database:postgresql", "search:vector"])
247
252
  ```
248
253
 
249
254
  ## Database Schema
@@ -75,7 +75,7 @@ end
75
75
  class MemoriesController < ApplicationController
76
76
  def create
77
77
  htm = HTM.new(robot_name: "user_#{current_user.id}")
78
- node_id = htm.remember(params[:content], source: 'user')
78
+ node_id = htm.remember(params[:content], metadata: { source: 'user' })
79
79
 
80
80
  render json: { status: 'ok', node_id: node_id }
81
81
  end
@@ -188,7 +188,7 @@ def remember(content, tags: [])
188
188
  content_hash = Digest::SHA256.hexdigest(content)
189
189
 
190
190
  # 2. Check if node with same hash exists
191
- existing_node = HTM::Models::Node.find_by(content_hash: content_hash)
191
+ existing_node = HTM::Models::Node.first(content_hash: content_hash)
192
192
 
193
193
  if existing_node
194
194
  # 3a. Link robot to existing node (or update remember_count)
@@ -187,7 +187,7 @@ timeline = memory_timeline("architecture decisions", limit: 50)
187
187
  puts "Architecture discussion timeline:"
188
188
  timeline.each do |node|
189
189
  robot_ids = node.robot_nodes.map(&:robot_id)
190
- robots = HTM::Models::Robot.where(id: robot_ids).pluck(:name)
190
+ robots = HTM::Models::Robot.where(id: robot_ids).select_map(:name)
191
191
 
192
192
  puts "#{node.created_at} - #{robots.join(', ')}"
193
193
  puts " #{node.content[0..100]}..."
@@ -738,7 +738,7 @@ class DevTeam
738
738
 
739
739
  nodes.each do |node|
740
740
  robot_ids = node.robot_nodes.map(&:robot_id)
741
- robots = HTM::Models::Robot.where(id: robot_ids).pluck(:name)
741
+ robots = HTM::Models::Robot.where(id: robot_ids).select_map(:name)
742
742
  puts "- #{robots.join(', ')}: #{node.content[0..50]}..."
743
743
  end
744
744
 
@@ -404,7 +404,7 @@ channel.on_change do |event, node_id, origin_robot_id|
404
404
 
405
405
  case event
406
406
  when :added
407
- node = HTM::Models::Node.find_by(id: node_id)
407
+ node = HTM::Models::Node.first(id: node_id)
408
408
  if node
409
409
  htm.working_memory.add_from_sync(
410
410
  id: node.id,