htm 0.0.1

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 (155) hide show
  1. checksums.yaml +7 -0
  2. data/.architecture/decisions/adrs/001-use-postgresql-timescaledb-storage.md +227 -0
  3. data/.architecture/decisions/adrs/002-two-tier-memory-architecture.md +322 -0
  4. data/.architecture/decisions/adrs/003-ollama-default-embedding-provider.md +339 -0
  5. data/.architecture/decisions/adrs/004-multi-robot-shared-memory-hive-mind.md +374 -0
  6. data/.architecture/decisions/adrs/005-rag-based-retrieval-with-hybrid-search.md +443 -0
  7. data/.architecture/decisions/adrs/006-context-assembly-strategies.md +444 -0
  8. data/.architecture/decisions/adrs/007-working-memory-eviction-strategy.md +461 -0
  9. data/.architecture/decisions/adrs/008-robot-identification-system.md +550 -0
  10. data/.architecture/decisions/adrs/009-never-forget-explicit-deletion-only.md +570 -0
  11. data/.architecture/decisions/adrs/010-redis-working-memory-rejected.md +323 -0
  12. data/.architecture/decisions/adrs/011-database-side-embedding-generation-with-pgai.md +585 -0
  13. data/.architecture/decisions/adrs/012-llm-driven-ontology-topic-extraction.md +583 -0
  14. data/.architecture/decisions/adrs/013-activerecord-orm-and-many-to-many-tagging.md +299 -0
  15. data/.architecture/decisions/adrs/014-client-side-embedding-generation-workflow.md +569 -0
  16. data/.architecture/decisions/adrs/015-hierarchical-tag-ontology-and-llm-extraction.md +701 -0
  17. data/.architecture/decisions/adrs/016-async-embedding-and-tag-generation.md +694 -0
  18. data/.architecture/members.yml +144 -0
  19. data/.architecture/reviews/2025-10-29-llm-configuration-and-async-processing-review.md +1137 -0
  20. data/.architecture/reviews/initial-system-analysis.md +330 -0
  21. data/.envrc +32 -0
  22. data/.irbrc +145 -0
  23. data/CHANGELOG.md +150 -0
  24. data/COMMITS.md +196 -0
  25. data/LICENSE +21 -0
  26. data/README.md +1347 -0
  27. data/Rakefile +51 -0
  28. data/SETUP.md +268 -0
  29. data/config/database.yml +67 -0
  30. data/db/migrate/20250101000001_enable_extensions.rb +14 -0
  31. data/db/migrate/20250101000002_create_robots.rb +14 -0
  32. data/db/migrate/20250101000003_create_nodes.rb +42 -0
  33. data/db/migrate/20250101000005_create_tags.rb +38 -0
  34. data/db/migrate/20250101000007_add_node_vector_indexes.rb +30 -0
  35. data/db/schema.sql +473 -0
  36. data/db/seed_data/README.md +100 -0
  37. data/db/seed_data/presidents.md +136 -0
  38. data/db/seed_data/states.md +151 -0
  39. data/db/seeds.rb +208 -0
  40. data/dbdoc/README.md +173 -0
  41. data/dbdoc/public.node_stats.md +48 -0
  42. data/dbdoc/public.node_stats.svg +41 -0
  43. data/dbdoc/public.node_tags.md +40 -0
  44. data/dbdoc/public.node_tags.svg +112 -0
  45. data/dbdoc/public.nodes.md +54 -0
  46. data/dbdoc/public.nodes.svg +118 -0
  47. data/dbdoc/public.nodes_tags.md +39 -0
  48. data/dbdoc/public.nodes_tags.svg +112 -0
  49. data/dbdoc/public.ontology_structure.md +48 -0
  50. data/dbdoc/public.ontology_structure.svg +38 -0
  51. data/dbdoc/public.operations_log.md +42 -0
  52. data/dbdoc/public.operations_log.svg +130 -0
  53. data/dbdoc/public.relationships.md +39 -0
  54. data/dbdoc/public.relationships.svg +41 -0
  55. data/dbdoc/public.robot_activity.md +46 -0
  56. data/dbdoc/public.robot_activity.svg +35 -0
  57. data/dbdoc/public.robots.md +35 -0
  58. data/dbdoc/public.robots.svg +90 -0
  59. data/dbdoc/public.schema_migrations.md +29 -0
  60. data/dbdoc/public.schema_migrations.svg +26 -0
  61. data/dbdoc/public.tags.md +35 -0
  62. data/dbdoc/public.tags.svg +60 -0
  63. data/dbdoc/public.topic_relationships.md +45 -0
  64. data/dbdoc/public.topic_relationships.svg +32 -0
  65. data/dbdoc/schema.json +1437 -0
  66. data/dbdoc/schema.svg +154 -0
  67. data/docs/api/database.md +806 -0
  68. data/docs/api/embedding-service.md +532 -0
  69. data/docs/api/htm.md +797 -0
  70. data/docs/api/index.md +259 -0
  71. data/docs/api/long-term-memory.md +1096 -0
  72. data/docs/api/working-memory.md +665 -0
  73. data/docs/architecture/adrs/001-postgresql-timescaledb.md +314 -0
  74. data/docs/architecture/adrs/002-two-tier-memory.md +411 -0
  75. data/docs/architecture/adrs/003-ollama-embeddings.md +421 -0
  76. data/docs/architecture/adrs/004-hive-mind.md +437 -0
  77. data/docs/architecture/adrs/005-rag-retrieval.md +531 -0
  78. data/docs/architecture/adrs/006-context-assembly.md +496 -0
  79. data/docs/architecture/adrs/007-eviction-strategy.md +645 -0
  80. data/docs/architecture/adrs/008-robot-identification.md +625 -0
  81. data/docs/architecture/adrs/009-never-forget.md +648 -0
  82. data/docs/architecture/adrs/010-redis-working-memory-rejected.md +323 -0
  83. data/docs/architecture/adrs/011-pgai-integration.md +494 -0
  84. data/docs/architecture/adrs/index.md +215 -0
  85. data/docs/architecture/hive-mind.md +736 -0
  86. data/docs/architecture/index.md +351 -0
  87. data/docs/architecture/overview.md +538 -0
  88. data/docs/architecture/two-tier-memory.md +873 -0
  89. data/docs/assets/css/custom.css +83 -0
  90. data/docs/assets/images/htm-core-components.svg +63 -0
  91. data/docs/assets/images/htm-database-schema.svg +93 -0
  92. data/docs/assets/images/htm-hive-mind-architecture.svg +125 -0
  93. data/docs/assets/images/htm-importance-scoring-framework.svg +83 -0
  94. data/docs/assets/images/htm-layered-architecture.svg +71 -0
  95. data/docs/assets/images/htm-long-term-memory-architecture.svg +115 -0
  96. data/docs/assets/images/htm-working-memory-architecture.svg +120 -0
  97. data/docs/assets/images/htm.jpg +0 -0
  98. data/docs/assets/images/htm_demo.gif +0 -0
  99. data/docs/assets/js/mathjax.js +18 -0
  100. data/docs/assets/videos/htm_video.mp4 +0 -0
  101. data/docs/database_rake_tasks.md +322 -0
  102. data/docs/development/contributing.md +787 -0
  103. data/docs/development/index.md +336 -0
  104. data/docs/development/schema.md +596 -0
  105. data/docs/development/setup.md +719 -0
  106. data/docs/development/testing.md +819 -0
  107. data/docs/guides/adding-memories.md +824 -0
  108. data/docs/guides/context-assembly.md +1009 -0
  109. data/docs/guides/getting-started.md +577 -0
  110. data/docs/guides/index.md +118 -0
  111. data/docs/guides/long-term-memory.md +941 -0
  112. data/docs/guides/multi-robot.md +866 -0
  113. data/docs/guides/recalling-memories.md +927 -0
  114. data/docs/guides/search-strategies.md +953 -0
  115. data/docs/guides/working-memory.md +717 -0
  116. data/docs/index.md +214 -0
  117. data/docs/installation.md +477 -0
  118. data/docs/multi_framework_support.md +519 -0
  119. data/docs/quick-start.md +655 -0
  120. data/docs/setup_local_database.md +302 -0
  121. data/docs/using_rake_tasks_in_your_app.md +383 -0
  122. data/examples/basic_usage.rb +93 -0
  123. data/examples/cli_app/README.md +317 -0
  124. data/examples/cli_app/htm_cli.rb +270 -0
  125. data/examples/custom_llm_configuration.rb +183 -0
  126. data/examples/example_app/Rakefile +71 -0
  127. data/examples/example_app/app.rb +206 -0
  128. data/examples/sinatra_app/Gemfile +21 -0
  129. data/examples/sinatra_app/app.rb +335 -0
  130. data/lib/htm/active_record_config.rb +113 -0
  131. data/lib/htm/configuration.rb +342 -0
  132. data/lib/htm/database.rb +594 -0
  133. data/lib/htm/embedding_service.rb +115 -0
  134. data/lib/htm/errors.rb +34 -0
  135. data/lib/htm/job_adapter.rb +154 -0
  136. data/lib/htm/jobs/generate_embedding_job.rb +65 -0
  137. data/lib/htm/jobs/generate_tags_job.rb +82 -0
  138. data/lib/htm/long_term_memory.rb +965 -0
  139. data/lib/htm/models/node.rb +109 -0
  140. data/lib/htm/models/node_tag.rb +33 -0
  141. data/lib/htm/models/robot.rb +52 -0
  142. data/lib/htm/models/tag.rb +76 -0
  143. data/lib/htm/railtie.rb +76 -0
  144. data/lib/htm/sinatra.rb +157 -0
  145. data/lib/htm/tag_service.rb +135 -0
  146. data/lib/htm/tasks.rb +38 -0
  147. data/lib/htm/version.rb +5 -0
  148. data/lib/htm/working_memory.rb +182 -0
  149. data/lib/htm.rb +400 -0
  150. data/lib/tasks/db.rake +19 -0
  151. data/lib/tasks/htm.rake +147 -0
  152. data/lib/tasks/jobs.rake +312 -0
  153. data/mkdocs.yml +190 -0
  154. data/scripts/install_local_database.sh +309 -0
  155. metadata +341 -0
data/README.md ADDED
@@ -0,0 +1,1347 @@
1
+ <div align="center">
2
+ <h1>HTM</h1>
3
+ <!-- img src="docs/assets/images/htm.jpg" alt="HTM - Hierarchical Temporal Memory" width="600" -->
4
+ <img src="docs/assets/images/htm_demo.gif" alt="Tree of Knowledge is Growing" width="400">
5
+
6
+ <p>A hierarchical and temporal system for encoding, storing, and retrieving information—operating across varying levels of abstraction (from simple to detailed concepts and their relationships) and across time (from the present to the past).<br/>
7
+ </p>
8
+ </div>
9
+
10
+ <br/><br/>
11
+
12
+ > [!CAUTION]
13
+ > This library is under active development and experimentation. APIs and features may change without notice. Not recommended for production use yet... and the documentation may lie to you!
14
+ > <br /><br/>
15
+ > Apologies to Jeff Hawkins for using his term in such a macro-superficial way.
16
+
17
+
18
+ ## What does it mean?
19
+
20
+ - **Hierarchical**: operates across multiple levels of abstraction, from simple concepts to detailed relationships
21
+ - **Temporal**: functions across time, from the present moment to historical data
22
+ - **Memory function**: encodes, stores, and retrieves information
23
+
24
+ **HTM**: a hierarchical and temporal memory system that organizes and recalls information at multiple levels of detail over extended timeframes.
25
+
26
+ ## Features
27
+
28
+ - **Client-Side Embeddings**
29
+ - Automatic embedding generation before database insertion
30
+ - Supports Ollama (local, default) and OpenAI
31
+ - Configurable embedding providers and models
32
+
33
+ - **Two-Tier Memory Architecture**
34
+ - Working Memory: Token-limited active context for immediate LLM use
35
+ - Long-term Memory: Durable PostgreSQL storage
36
+
37
+ - **Never Forgets (Unless Told)**
38
+ - All memories persist in long-term storage
39
+ - Only explicit `forget()` commands delete data
40
+ - Working memory evicts to long-term, never deletes
41
+
42
+ - **RAG-Based Retrieval**
43
+ - Vector similarity search (pgvector)
44
+ - Full-text search (PostgreSQL)
45
+ - Hybrid search (combines both)
46
+ - Temporal filtering ("last week", date ranges)
47
+ - Variable embedding dimensions (384 to 3072)
48
+
49
+ - **Hive Mind**
50
+ - All robots share global memory
51
+ - Cross-robot context awareness
52
+ - Track which robot said what
53
+
54
+ - **LLM-Driven Tag Extraction**
55
+ - Automatic hierarchical tag extraction from content
56
+ - Tags in colon-delimited format (e.g., `database:postgresql:performance`)
57
+ - LLM-powered asynchronous processing
58
+ - Enables both structured navigation and semantic discovery
59
+ - Complements vector embeddings (symbolic + sub-symbolic retrieval)
60
+
61
+ - **Knowledge Graph**
62
+ - Tag-based categorization
63
+ - Hierarchical tag structures
64
+
65
+ ## Installation
66
+
67
+ Add this line to your application's Gemfile:
68
+
69
+ ```ruby
70
+ gem 'htm'
71
+ ```
72
+
73
+ And then execute:
74
+
75
+ ```bash
76
+ bundle install
77
+ ```
78
+
79
+ Or install it yourself as:
80
+
81
+ ```bash
82
+ gem install htm
83
+ ```
84
+
85
+ ## Setup
86
+
87
+ ### 1. Database Configuration
88
+
89
+ HTM uses PostgreSQL for durable long-term memory storage. Set up your database connection via environment variables:
90
+
91
+ ```bash
92
+ # Preferred: Full connection URL
93
+ export HTM_DBURL="postgresql://user:password@host:port/dbname?sslmode=require"
94
+
95
+ # Alternative: Individual parameters
96
+ export HTM_DBNAME="htm_production"
97
+ export HTM_DBUSER="postgres"
98
+ export HTM_DBPASS="your_password"
99
+ export HTM_DBHOST="localhost"
100
+ export HTM_DBPORT="5432"
101
+ ```
102
+
103
+ See the [Environment Variables](#environment-variables) section below for complete details.
104
+
105
+ ### 2. Configure LLM Providers
106
+
107
+ HTM uses LLM providers for embedding generation and tag extraction. By default, it uses Ollama (local).
108
+
109
+ **Start Ollama (Default Configuration)**:
110
+ ```bash
111
+ # Install Ollama
112
+ curl https://ollama.ai/install.sh | sh
113
+
114
+ # Start Ollama and pull models
115
+ ollama serve
116
+ ollama pull nomic-embed-text # For embeddings (768 dimensions)
117
+ ollama pull llama3 # For tag extraction
118
+ ```
119
+
120
+ **Configure HTM** (optional - uses defaults if not configured):
121
+ ```ruby
122
+ require 'htm'
123
+
124
+ # Use defaults (Ollama with nomic-embed-text and llama3)
125
+ HTM.configure
126
+
127
+ # Or customize providers
128
+ HTM.configure do |config|
129
+ # Embedding configuration
130
+ config.embedding_provider = :ollama # or :openai
131
+ config.embedding_model = 'nomic-embed-text'
132
+ config.embedding_dimensions = 768
133
+ config.ollama_url = 'http://localhost:11434'
134
+
135
+ # Tag extraction configuration
136
+ config.tag_provider = :ollama # or :openai
137
+ config.tag_model = 'llama3'
138
+
139
+ # Logger configuration (optional)
140
+ config.logger = Logger.new($stdout)
141
+ config.logger.level = Logger::INFO
142
+
143
+ # Custom embedding generator (advanced)
144
+ config.embedding_generator = ->(text) {
145
+ # Your custom implementation
146
+ # Must return Array<Float>
147
+ }
148
+
149
+ # Custom tag extractor (advanced)
150
+ config.tag_extractor = ->(text, ontology) {
151
+ # Your custom implementation
152
+ # Must return Array<String>
153
+ }
154
+
155
+ # Token counter (optional, defaults to Tiktoken)
156
+ config.token_counter = ->(text) {
157
+ # Your custom implementation
158
+ # Must return Integer
159
+ }
160
+ end
161
+ ```
162
+
163
+ See the [Configuration](#configuration) section below for complete details.
164
+
165
+ ### 3. Initialize Database Schema
166
+
167
+ **Using Rake Tasks (Recommended)**:
168
+
169
+ HTM provides comprehensive rake tasks for database management. Add this to your application's `Rakefile`:
170
+
171
+ ```ruby
172
+ require 'htm/tasks'
173
+ ```
174
+
175
+ Then use the tasks:
176
+
177
+ ```bash
178
+ # Set up database schema and run migrations
179
+ rake htm:db:setup
180
+
181
+ # Verify connection
182
+ rake htm:db:test
183
+
184
+ # Check database status
185
+ rake htm:db:info
186
+ ```
187
+
188
+ **Or programmatically**:
189
+
190
+ ```ruby
191
+ require 'htm'
192
+
193
+ # Run once to set up database schema
194
+ HTM::Database.setup
195
+ ```
196
+
197
+ **Or from command line**:
198
+
199
+ ```bash
200
+ ruby -r ./lib/htm -e "HTM::Database.setup"
201
+ ```
202
+
203
+ See [Using HTM Rake Tasks in Your Application](docs/using_rake_tasks_in_your_app.md) for complete integration guide.
204
+
205
+ ### 4. Verify Setup
206
+
207
+ ```bash
208
+ rake htm:db:test # Using rake tasks
209
+ # or
210
+ ruby test_connection.rb
211
+ ```
212
+
213
+ See [docs/setup_local_database.md](docs/setup_local_database.md) for detailed local database setup instructions.
214
+
215
+ ## Usage
216
+
217
+ ### Basic Example
218
+
219
+ ```ruby
220
+ require 'htm'
221
+ require 'ruby_llm'
222
+ require 'logger'
223
+
224
+ # Configure HTM with RubyLLM for embeddings and tag extraction
225
+ HTM.configure do |config|
226
+ # Configure logger (optional - uses STDOUT at INFO level if not provided)
227
+ config.logger = Logger.new($stdout)
228
+ config.logger.level = Logger::INFO
229
+
230
+ # Configure embedding generation using RubyLLM with Ollama
231
+ config.embedding_provider = :ollama
232
+ config.embedding_model = 'nomic-embed-text'
233
+ config.embedding_dimensions = 768
234
+ config.ollama_url = ENV['OLLAMA_URL'] || 'http://localhost:11434'
235
+
236
+ # Configure tag extraction using RubyLLM with Ollama
237
+ config.tag_provider = :ollama
238
+ config.tag_model = 'llama3'
239
+
240
+ # Apply configuration (sets up default RubyLLM implementations)
241
+ config.reset_to_defaults
242
+ end
243
+
244
+ # Initialize HTM for your robot
245
+ htm = HTM.new(
246
+ robot_name: "Code Helper",
247
+ working_memory_size: 128_000 # tokens
248
+ )
249
+
250
+ # Remember information - embeddings and tags generated asynchronously
251
+ node_id = htm.remember(
252
+ "We decided to use PostgreSQL for HTM storage",
253
+ source: "architect"
254
+ )
255
+
256
+ # Recall from the past (uses semantic search with embeddings)
257
+ memories = htm.recall(
258
+ "database decisions",
259
+ timeframe: "last week",
260
+ strategy: :hybrid # Combines vector + full-text search
261
+ )
262
+
263
+ # Forget (explicit deletion only)
264
+ htm.forget(node_id, confirm: :confirmed)
265
+ ```
266
+
267
+ ### Automatic Tag Extraction
268
+
269
+ HTM automatically extracts hierarchical tags from content using LLM analysis. Tags are inferred from the content itself - you never specify them manually.
270
+
271
+ **Example:**
272
+ ```ruby
273
+ htm.remember(
274
+ "User prefers dark mode for all interfaces",
275
+ source: "user"
276
+ )
277
+ # Tags like "preference", "ui", "dark-mode" may be auto-extracted by the LLM
278
+ # The specific tags depend on the LLM's analysis of the content
279
+ ```
280
+
281
+ ### Async Job Processing
282
+
283
+ HTM automatically generates embeddings and extracts tags asynchronously in background jobs. This avoids blocking the main request path.
284
+
285
+ **Monitor job processing:**
286
+ ```bash
287
+ # Show statistics for nodes and async processing
288
+ rake htm:jobs:stats
289
+
290
+ # Output:
291
+ # Total nodes: 150
292
+ # Nodes with embeddings: 145 (96.7%)
293
+ # Nodes without embeddings: 5 (3.3%)
294
+ # Nodes with tags: 140 (93.3%)
295
+ # Total tags in ontology: 42
296
+ ```
297
+
298
+ **Process pending jobs manually:**
299
+ ```bash
300
+ # Process all pending embedding jobs
301
+ rake htm:jobs:process_embeddings
302
+
303
+ # Process all pending tag extraction jobs
304
+ rake htm:jobs:process_tags
305
+
306
+ # Process both embeddings and tags
307
+ rake htm:jobs:process_all
308
+ ```
309
+
310
+ **Reprocess all nodes (force regeneration):**
311
+ ```bash
312
+ # Regenerate embeddings for ALL nodes
313
+ rake htm:jobs:reprocess_embeddings
314
+
315
+ # WARNING: Prompts for confirmation
316
+ ```
317
+
318
+ **Show failed/stuck jobs:**
319
+ ```bash
320
+ # Show nodes that failed async processing (>1 hour old without embeddings/tags)
321
+ rake htm:jobs:failed
322
+ ```
323
+
324
+ **Clear all for testing:**
325
+ ```bash
326
+ # Clear ALL embeddings and tags (development/testing only)
327
+ rake htm:jobs:clear_all
328
+
329
+ # WARNING: Prompts for confirmation
330
+ ```
331
+
332
+ See `rake -T htm:jobs` for complete list of job management tasks.
333
+
334
+ ## Configuration
335
+
336
+ HTM uses dependency injection for LLM access, allowing you to configure embedding generation, tag extraction, logging, and token counting.
337
+
338
+ ### Default Configuration
339
+
340
+ By default, HTM uses:
341
+ - **Embedding Provider**: Ollama (local) with `nomic-embed-text` model (768 dimensions)
342
+ - **Tag Provider**: Ollama (local) with `llama3` model
343
+ - **Logger**: Ruby's standard Logger to STDOUT at INFO level
344
+ - **Token Counter**: Tiktoken with GPT-3.5-turbo encoding
345
+
346
+ ```ruby
347
+ require 'htm'
348
+
349
+ # Use defaults
350
+ HTM.configure # or omit this - configuration is lazy-loaded
351
+ ```
352
+
353
+ ### Custom Configuration
354
+
355
+ Configure HTM before creating instances:
356
+
357
+ #### Using Ollama (Default)
358
+
359
+ ```ruby
360
+ HTM.configure do |config|
361
+ # Embedding configuration
362
+ config.embedding_provider = :ollama
363
+ config.embedding_model = 'nomic-embed-text' # 768 dimensions
364
+ config.embedding_dimensions = 768
365
+ config.ollama_url = 'http://localhost:11434'
366
+
367
+ # Tag extraction configuration
368
+ config.tag_provider = :ollama
369
+ config.tag_model = 'llama3'
370
+
371
+ # Logger configuration
372
+ config.logger = Logger.new($stdout)
373
+ config.logger.level = Logger::INFO
374
+ end
375
+ ```
376
+
377
+ **Ollama Setup:**
378
+ ```bash
379
+ # Install Ollama
380
+ curl https://ollama.ai/install.sh | sh
381
+
382
+ # Pull models
383
+ ollama pull nomic-embed-text # For embeddings
384
+ ollama pull llama3 # For tag extraction
385
+
386
+ # Verify Ollama is running
387
+ curl http://localhost:11434/api/version
388
+ ```
389
+
390
+ #### Using OpenAI
391
+
392
+ ```ruby
393
+ HTM.configure do |config|
394
+ # Embedding configuration (OpenAI)
395
+ config.embedding_provider = :openai
396
+ config.embedding_model = 'text-embedding-3-small' # 1536 dimensions
397
+ config.embedding_dimensions = 1536
398
+
399
+ # Tag extraction (can mix providers)
400
+ config.tag_provider = :openai
401
+ config.tag_model = 'gpt-4'
402
+
403
+ # Logger
404
+ config.logger = Rails.logger # Use Rails logger
405
+ end
406
+ ```
407
+
408
+ **OpenAI Setup:**
409
+ ```bash
410
+ # Set your OpenAI API key
411
+ export OPENAI_API_KEY='sk-your-api-key-here'
412
+ ```
413
+
414
+ **Important:** The database uses pgvector with HNSW indexing, which has a maximum dimension limit of 2000. Embeddings exceeding this limit will be automatically truncated with a warning.
415
+
416
+ #### Custom Providers
417
+
418
+ Provide your own LLM implementations:
419
+
420
+ ```ruby
421
+ HTM.configure do |config|
422
+ # Custom embedding generator
423
+ config.embedding_generator = ->(text) {
424
+ # Call your custom LLM service
425
+ response = MyEmbeddingService.generate(text)
426
+
427
+ # Must return Array<Float>
428
+ response[:embedding] # e.g., [0.123, -0.456, ...]
429
+ }
430
+
431
+ # Custom tag extractor
432
+ config.tag_extractor = ->(text, existing_ontology) {
433
+ # Call your custom LLM service for tag extraction
434
+ # existing_ontology is Array<String> of recent tags for context
435
+ response = MyTagService.extract(text, context: existing_ontology)
436
+
437
+ # Must return Array<String> in hierarchical format
438
+ # e.g., ["ai:llm:embeddings", "database:postgresql"]
439
+ response[:tags]
440
+ }
441
+
442
+ # Custom token counter (optional)
443
+ config.token_counter = ->(text) {
444
+ # Your token counting implementation
445
+ # Must return Integer
446
+ MyTokenizer.count(text)
447
+ }
448
+ end
449
+ ```
450
+
451
+ #### Logger Configuration
452
+
453
+ Customize logging behavior:
454
+
455
+ ```ruby
456
+ HTM.configure do |config|
457
+ # Use custom logger
458
+ config.logger = Logger.new('log/htm.log')
459
+ config.logger.level = Logger::DEBUG
460
+
461
+ # Or use Rails logger
462
+ config.logger = Rails.logger
463
+
464
+ # Or disable logging
465
+ config.logger = Logger.new(IO::NULL)
466
+ config.logger.level = Logger::FATAL
467
+ end
468
+
469
+ # Control log level via environment variable
470
+ ENV['HTM_LOG_LEVEL'] = 'DEBUG' # or INFO, WARN, ERROR
471
+ HTM.configure # Respects HTM_LOG_LEVEL
472
+ ```
473
+
474
+ #### Service Layer Architecture
475
+
476
+ HTM uses a service layer to process LLM responses:
477
+
478
+ - **EmbeddingService**: Calls your configured `embedding_generator`, validates responses, handles padding/truncation, and formats for storage
479
+ - **TagService**: Calls your configured `tag_extractor`, parses responses (String or Array), validates format, and filters invalid tags
480
+
481
+ This separation allows you to provide raw LLM access while HTM handles response processing, validation, and storage formatting.
482
+
483
+ ### Recall Strategies
484
+
485
+ HTM supports three retrieval strategies:
486
+
487
+ ```ruby
488
+ # Vector similarity search (semantic) - uses configured embedding provider
489
+ memories = htm.recall(
490
+ "HTM architecture",
491
+ timeframe: "last week",
492
+ strategy: :vector # Semantic similarity using embeddings
493
+ )
494
+
495
+ # Full-text search (keyword matching) - PostgreSQL full-text search with trigrams
496
+ memories = htm.recall(
497
+ "database performance",
498
+ timeframe: "last month",
499
+ strategy: :fulltext # Keyword-based matching
500
+ )
501
+
502
+ # Hybrid (combines both) - best of both worlds
503
+ memories = htm.recall(
504
+ "testing strategies",
505
+ timeframe: "yesterday",
506
+ strategy: :hybrid # Weighted combination of vector + full-text
507
+ )
508
+ ```
509
+
510
+ **Strategy Details:**
511
+ - `:vector` - Semantic search using pgvector cosine similarity (requires embeddings)
512
+ - `:fulltext` - PostgreSQL tsvector with pg_trgm fuzzy matching
513
+ - `:hybrid` - Combines both with weighted scoring (default: 70% vector, 30% full-text)
514
+
515
+ ## API Reference
516
+
517
+ HTM provides a minimal, focused API with only 3 core instance methods for memory operations:
518
+
519
+ ### Core Memory Operations
520
+
521
+ #### `remember(content, source: "")`
522
+
523
+ Store information in memory. Embeddings and tags are automatically generated asynchronously.
524
+
525
+ **Parameters:**
526
+ - `content` (String, required) - The information to remember. Converted to string if nil. Returns ID of last node if empty.
527
+ - `source` (String, optional) - Where the content came from (e.g., "user", "assistant", "system"). Defaults to empty string.
528
+
529
+ **Returns:** Integer - The node ID of the stored memory
530
+
531
+ **Example:**
532
+ ```ruby
533
+ # Store with source
534
+ node_id = htm.remember("PostgreSQL is excellent for vector search with pgvector", source: "architect")
535
+
536
+ # Store without source (uses default empty string)
537
+ node_id = htm.remember("HTM uses two-tier memory architecture")
538
+
539
+ # Nil/empty handling
540
+ node_id = htm.remember(nil) # Returns ID of last node without creating duplicate
541
+ node_id = htm.remember("") # Returns ID of last node without creating duplicate
542
+ ```
543
+
544
+ ---
545
+
546
+ #### `recall(topic, timeframe: nil, limit: 20, strategy: :vector, with_relevance: false, query_tags: [])`
547
+
548
+ Retrieve memories using temporal filtering and semantic/keyword search.
549
+
550
+ **Parameters:**
551
+ - `topic` (String, required) - Query text for semantic/keyword matching (first positional argument)
552
+ - `timeframe` (Range, String, optional) - Time range to search within. Default: "last 7 days"
553
+ - Range: `(Time.now - 3600)..Time.now`
554
+ - String: `"last hour"`, `"last week"`, `"yesterday"`
555
+ - `limit` (Integer, optional) - Maximum number of results. Default: 20
556
+ - `strategy` (Symbol, optional) - Search strategy. Default: `:vector`
557
+ - `:vector` - Semantic search using embeddings (cosine similarity)
558
+ - `:fulltext` - Keyword search using PostgreSQL full-text + trigrams
559
+ - `:hybrid` - Weighted combination (70% vector, 30% full-text)
560
+ - `with_relevance` (Boolean, optional) - Include dynamic relevance scores. Default: false
561
+ - `query_tags` (Array<String>, optional) - Filter results by tags. Default: []
562
+
563
+ **Returns:** Array<Hash> - Matching memories with fields: `id`, `content`, `source`, `created_at`, `access_count`, (optionally `relevance`)
564
+
565
+ **Example:**
566
+ ```ruby
567
+ # Basic recall with time range
568
+ memories = htm.recall(
569
+ "database architecture",
570
+ timeframe: (Time.now - 86400)..Time.now
571
+ )
572
+
573
+ # Using human-readable timeframe
574
+ memories = htm.recall(
575
+ "PostgreSQL performance",
576
+ timeframe: "last week",
577
+ strategy: :hybrid,
578
+ limit: 10
579
+ )
580
+
581
+ # With relevance scoring
582
+ memories = htm.recall(
583
+ "HTM design decisions",
584
+ timeframe: "last month",
585
+ with_relevance: true,
586
+ query_tags: ["architecture"]
587
+ )
588
+ # => [{ "id" => 123, "content" => "...", "relevance" => 0.92, ... }, ...]
589
+ ```
590
+
591
+ ---
592
+
593
+ #### `forget(node_id, confirm: false)`
594
+
595
+ Permanently delete a memory from both working memory and long-term storage.
596
+
597
+ **Parameters:**
598
+ - `node_id` (Integer, required) - The ID of the node to delete
599
+ - `confirm` (Symbol, Boolean, required) - Must be `:confirmed` or `true` to proceed
600
+
601
+ **Returns:** Boolean - `true` if deleted, `false` if not found
602
+
603
+ **Safety:** This is the ONLY way to delete memories. Requires explicit confirmation to prevent accidental deletion.
604
+
605
+ **Example:**
606
+ ```ruby
607
+ # Delete with symbol confirmation (recommended)
608
+ htm.forget(node_id, confirm: :confirmed)
609
+
610
+ # Delete with boolean confirmation
611
+ htm.forget(node_id, confirm: true)
612
+
613
+ # Will raise error - confirmation required
614
+ htm.forget(node_id) # ArgumentError: Must confirm deletion
615
+ htm.forget(node_id, confirm: false) # ArgumentError: Must confirm deletion
616
+ ```
617
+
618
+ ---
619
+
620
+ ### Complete Usage Example
621
+
622
+ ```ruby
623
+ require 'htm'
624
+
625
+ # Configure once globally (optional - uses defaults if not called)
626
+ HTM.configure do |config|
627
+ config.embedding_provider = :ollama
628
+ config.embedding_model = 'nomic-embed-text'
629
+ config.tag_provider = :ollama
630
+ config.tag_model = 'llama3'
631
+ end
632
+
633
+ # Initialize for your robot
634
+ htm = HTM.new(robot_name: "Assistant", working_memory_size: 128_000)
635
+
636
+ # Store information
637
+ htm.remember("PostgreSQL with pgvector for vector search", source: "architect")
638
+ htm.remember("User prefers dark mode", source: "user")
639
+ htm.remember("Use debug_me for debugging, not puts", source: "system")
640
+
641
+ # Retrieve by time + topic
642
+ recent = htm.recall(
643
+ "PostgreSQL",
644
+ timeframe: "last week",
645
+ strategy: :hybrid,
646
+ limit: 5
647
+ )
648
+
649
+ # Delete if needed (requires node ID from remember or recall)
650
+ htm.forget(node_id, confirm: :confirmed)
651
+ ```
652
+
653
+ ## Use with Rails
654
+
655
+ HTM is designed to integrate seamlessly with Ruby on Rails applications. It uses ActiveRecord models and follows Rails conventions, making it easy to add AI memory capabilities to existing Rails apps.
656
+
657
+ ### Why HTM Works Well with Rails
658
+
659
+ **1. Uses ActiveRecord**
660
+ - HTM models are standard ActiveRecord classes with associations, validations, and scopes
661
+ - Follows Rails naming conventions and patterns
662
+ - Models are namespaced under `HTM::Models::` to avoid conflicts
663
+
664
+ **2. Standard Rails Configuration**
665
+ - Uses `config/database.yml` (Rails convention)
666
+ - Respects `RAILS_ENV` environment variable
667
+ - Supports ERB in configuration files
668
+
669
+ **3. Separate Database**
670
+ - HTM uses its own PostgreSQL database connection
671
+ - Won't interfere with your Rails app's database
672
+ - Configured with `prepared_statements: false` and `advisory_locks: false` for compatibility
673
+ - Thread-safe for Rails multi-threading
674
+
675
+ **4. Connection Management**
676
+ - Separate connection pool from your Rails app
677
+ - Configurable pool size and timeouts
678
+ - Proper cleanup and disconnection support
679
+
680
+ ### Integration Steps
681
+
682
+ #### 1. Add to Gemfile
683
+
684
+ ```ruby
685
+ gem 'htm'
686
+ ```
687
+
688
+ Then run:
689
+ ```bash
690
+ bundle install
691
+ ```
692
+
693
+ #### 2. Configure Environment Variables
694
+
695
+ Add HTM database configuration to your Rails environment. You can use `.env` files (with `dotenv-rails`) or set them directly:
696
+
697
+ ```ruby
698
+ # .env or config/application.rb
699
+ HTM_DBURL=postgresql://user:pass@localhost:5432/htm_production
700
+ ```
701
+
702
+ Or use individual variables:
703
+ ```ruby
704
+ HTM_DBHOST=localhost
705
+ HTM_DBNAME=htm_production
706
+ HTM_DBUSER=postgres
707
+ HTM_DBPASS=password
708
+ HTM_DBPORT=5432
709
+ ```
710
+
711
+ #### 3. Create Initializer
712
+
713
+ Create `config/initializers/htm.rb`:
714
+
715
+ ```ruby
716
+ # config/initializers/htm.rb
717
+
718
+ # Configure HTM
719
+ HTM.configure do |config|
720
+ # Use Rails logger
721
+ config.logger = Rails.logger
722
+
723
+ # Embedding configuration (optional - uses Ollama defaults if not set)
724
+ config.embedding_provider = :ollama
725
+ config.embedding_model = 'nomic-embed-text'
726
+
727
+ # Tag extraction configuration (optional - uses Ollama defaults if not set)
728
+ config.tag_provider = :ollama
729
+ config.tag_model = 'llama3'
730
+
731
+ # Custom providers (optional)
732
+ # config.embedding_generator = ->(text) { YourEmbeddingService.call(text) }
733
+ # config.tag_extractor = ->(text, ontology) { YourTagService.call(text, ontology) }
734
+ end
735
+
736
+ # Establish HTM database connection
737
+ HTM::ActiveRecordConfig.establish_connection!
738
+
739
+ # Verify required PostgreSQL extensions are installed
740
+ HTM::ActiveRecordConfig.verify_extensions!
741
+
742
+ Rails.logger.info "HTM initialized with database: #{HTM::Database.default_config[:database]}"
743
+ ```
744
+
745
+ #### 4. Set Up Database
746
+
747
+ Run the HTM database setup:
748
+
749
+ ```bash
750
+ # Using HTM rake tasks
751
+ rake htm:db:setup
752
+
753
+ # Or manually in Rails console
754
+ rails c
755
+ > HTM::Database.setup
756
+ ```
757
+
758
+ #### 5. Use in Your Rails App
759
+
760
+ **In Controllers:**
761
+
762
+ ```ruby
763
+ class ChatsController < ApplicationController
764
+ before_action :initialize_memory
765
+
766
+ def create
767
+ # Add user message to memory (embeddings + tags auto-extracted asynchronously)
768
+ @memory.remember(
769
+ params[:message],
770
+ source: "user-#{current_user.id}"
771
+ )
772
+
773
+ # Recall relevant context for the conversation
774
+ context = @memory.create_context(strategy: :balanced)
775
+
776
+ # Use context with your LLM to generate response
777
+ response = generate_llm_response(context, params[:message])
778
+
779
+ # Store assistant's response
780
+ @memory.remember(
781
+ response,
782
+ source: "assistant"
783
+ )
784
+
785
+ render json: { response: response }
786
+ end
787
+
788
+ private
789
+
790
+ def initialize_memory
791
+ @memory = HTM.new(
792
+ robot_name: "ChatBot-#{current_user.id}",
793
+ working_memory_size: 128_000
794
+ )
795
+ end
796
+ end
797
+ ```
798
+
799
+ **In Background Jobs:**
800
+
801
+ ```ruby
802
+ class ProcessDocumentJob < ApplicationJob
803
+ queue_as :default
804
+
805
+ def perform(document_id)
806
+ document = Document.find(document_id)
807
+
808
+ # Initialize HTM for this job
809
+ memory = HTM.new(robot_name: "DocumentProcessor")
810
+
811
+ # Store document content in memory (embeddings + tags auto-extracted asynchronously)
812
+ memory.remember(
813
+ document.content,
814
+ source: "document-#{document.id}"
815
+ )
816
+
817
+ # Recall related documents (uses vector similarity)
818
+ related = memory.recall(
819
+ document.category,
820
+ timeframe: "last month",
821
+ strategy: :vector
822
+ )
823
+
824
+ # Process with context...
825
+ end
826
+ end
827
+ ```
828
+
829
+ **In Models (as a concern):**
830
+
831
+ ```ruby
832
+ # app/models/concerns/memorable.rb
833
+ module Memorable
834
+ extend ActiveSupport::Concern
835
+
836
+ included do
837
+ after_create :store_in_memory
838
+ after_update :update_memory
839
+ end
840
+
841
+ def store_in_memory
842
+ memory = HTM.new(robot_name: "Rails-#{Rails.env}")
843
+
844
+ memory.remember(
845
+ memory_content,
846
+ source: "#{self.class.name}-#{id}"
847
+ )
848
+ end
849
+
850
+ def update_memory
851
+ # Update existing memory node
852
+ store_in_memory
853
+ end
854
+
855
+ private
856
+
857
+ def memory_content
858
+ # Override in model to customize what gets stored
859
+ attributes.to_json
860
+ end
861
+ end
862
+
863
+ # Then in your model:
864
+ class Article < ApplicationRecord
865
+ include Memorable
866
+
867
+ private
868
+
869
+ def memory_content
870
+ "Article: #{title}\n\n#{body}\n\nCategory: #{category}"
871
+ end
872
+ end
873
+ ```
874
+
875
+ ### Multi-Database Configuration (Rails 6+)
876
+
877
+ If you want to use Rails' built-in multi-database support, you can configure HTM in your `config/database.yml`:
878
+
879
+ ```yaml
880
+ # config/database.yml
881
+ production:
882
+ primary:
883
+ <<: *default
884
+ database: myapp_production
885
+
886
+ htm:
887
+ adapter: postgresql
888
+ encoding: unicode
889
+ database: <%= ENV['HTM_DBNAME'] || 'htm_production' %>
890
+ host: <%= ENV['HTM_DBHOST'] || 'localhost' %>
891
+ port: <%= ENV['HTM_DBPORT'] || 5432 %>
892
+ username: <%= ENV['HTM_DBUSER'] || 'postgres' %>
893
+ password: <%= ENV['HTM_DBPASS'] %>
894
+ pool: <%= ENV['HTM_DB_POOL_SIZE'] || 10 %>
895
+ ```
896
+
897
+ Then update your HTM initializer:
898
+
899
+ ```ruby
900
+ # config/initializers/htm.rb
901
+ HTM::ActiveRecordConfig.establish_connection!
902
+
903
+ # Or use Rails' connection
904
+ # ActiveRecord::Base.connected_to(role: :writing, shard: :htm) do
905
+ # HTM::ActiveRecordConfig.verify_extensions!
906
+ # end
907
+ ```
908
+
909
+ ### Testing with Rails
910
+
911
+ **In RSpec:**
912
+
913
+ ```ruby
914
+ # spec/rails_helper.rb
915
+ RSpec.configure do |config|
916
+ config.before(:suite) do
917
+ HTM::ActiveRecordConfig.establish_connection!
918
+ end
919
+
920
+ config.after(:suite) do
921
+ HTM::ActiveRecordConfig.disconnect!
922
+ end
923
+ end
924
+
925
+ # spec/features/chat_spec.rb
926
+ RSpec.describe "Chat with memory", type: :feature do
927
+ let(:memory) { HTM.new(robot_name: "TestBot") }
928
+
929
+ before do
930
+ memory.remember("User prefers Ruby over Python", source: "user")
931
+ end
932
+
933
+ it "recalls user preferences" do
934
+ context = memory.create_context(strategy: :balanced)
935
+ expect(context).to include("Ruby")
936
+ end
937
+ end
938
+ ```
939
+
940
+ **In Minitest:**
941
+
942
+ ```ruby
943
+ # test/test_helper.rb
944
+ class ActiveSupport::TestCase
945
+ setup do
946
+ HTM::ActiveRecordConfig.establish_connection! unless HTM::ActiveRecordConfig.connected?
947
+ end
948
+ end
949
+
950
+ # test/integration/chat_test.rb
951
+ class ChatTest < ActionDispatch::IntegrationTest
952
+ test "stores chat messages in memory" do
953
+ memory = HTM.new(robot_name: "TestBot")
954
+
955
+ post chats_path, params: { message: "Hello!" }
956
+
957
+ assert_response :success
958
+
959
+ # Verify message was stored
960
+ nodes = memory.recall("Hello", timeframe: "last hour")
961
+ assert_not_empty nodes
962
+ end
963
+ end
964
+ ```
965
+
966
+ ### Production Considerations
967
+
968
+ 1. **Connection Pooling**: Set appropriate pool size based on your Rails app's concurrency:
969
+ ```ruby
970
+ ENV['HTM_DB_POOL_SIZE'] = '20' # Match or exceed Rails pool
971
+ ```
972
+
973
+ 2. **Separate Database Server**: For production, use a dedicated PostgreSQL instance for HTM
974
+
975
+ 3. **Required Extensions**: Ensure `pgvector` and `pg_trgm` extensions are installed on your PostgreSQL server
976
+
977
+ 4. **Memory Management**: HTM's working memory is per-instance. Consider using a singleton pattern or Rails cache for shared instances
978
+
979
+ 5. **Background Processing**: Use Sidekiq or similar for embedding generation if processing large amounts of data
980
+
981
+ ### Example Rails App Structure
982
+
983
+ ```
984
+ app/
985
+ ├── controllers/
986
+ │ └── chats_controller.rb # Uses HTM for conversation memory
987
+ ├── jobs/
988
+ │ └── process_document_job.rb # Background HTM processing
989
+ ├── models/
990
+ │ ├── concerns/
991
+ │ │ └── memorable.rb # HTM integration concern
992
+ │ └── article.rb # Includes Memorable
993
+ └── services/
994
+ └── memory_service.rb # Centralized HTM access
995
+
996
+ config/
997
+ ├── initializers/
998
+ │ └── htm.rb # HTM setup
999
+ └── database.yml # Optional: multi-database config
1000
+
1001
+ .env
1002
+ HTM_DBURL=postgresql://... # HTM database connection
1003
+ OPENAI_API_KEY=sk-... # If using OpenAI embeddings
1004
+ ```
1005
+
1006
+ ## Environment Variables
1007
+
1008
+ HTM uses environment variables for database and service configuration. These can be set in your shell profile, a `.env` file, or exported directly.
1009
+
1010
+ ### Database Configuration
1011
+
1012
+ #### HTM_DBURL (Recommended)
1013
+
1014
+ The preferred method for database configuration is a single connection URL:
1015
+
1016
+ ```bash
1017
+ export HTM_DBURL="postgresql://username:password@host:port/dbname?sslmode=require"
1018
+ ```
1019
+
1020
+ **Format:** `postgresql://USER:PASSWORD@HOST:PORT/DATABASE?sslmode=MODE`
1021
+
1022
+ **Example for Local PostgreSQL:**
1023
+ ```bash
1024
+ export HTM_DBURL="postgresql://postgres:postgres@localhost:5432/htm_dev?sslmode=disable"
1025
+ ```
1026
+
1027
+ #### Individual Database Parameters (Alternative)
1028
+
1029
+ If `HTM_DBURL` is not set, HTM will use individual parameters:
1030
+
1031
+ | Variable | Description | Default | Required |
1032
+ |----------|-------------|---------|----------|
1033
+ | `HTM_DBNAME` | Database name | None | Yes |
1034
+ | `HTM_DBUSER` | Database username | None | Yes |
1035
+ | `HTM_DBPASS` | Database password | None | Yes |
1036
+ | `HTM_DBHOST` | Database hostname or IP | `localhost` | No |
1037
+ | `HTM_DBPORT` | Database port | `5432` | No |
1038
+ | `HTM_SERVICE_NAME` | Service identifier (informational only) | None | No |
1039
+
1040
+ **Example:**
1041
+ ```bash
1042
+ export HTM_DBNAME="htm_production"
1043
+ export HTM_DBUSER="postgres"
1044
+ export HTM_DBPASS="secure_password_here"
1045
+ export HTM_DBHOST="localhost"
1046
+ export HTM_DBPORT="5432"
1047
+ export HTM_SERVICE_NAME="production-db"
1048
+ ```
1049
+
1050
+ **Configuration Priority:**
1051
+ 1. `HTM_DBURL` (if set, this takes precedence)
1052
+ 2. Individual parameters (`HTM_DBNAME`, `HTM_DBUSER`, etc.)
1053
+ 3. Direct configuration passed to `HTM.new(db_config: {...})`
1054
+
1055
+ ### LLM Provider Configuration
1056
+
1057
+ HTM configuration is done programmatically via `HTM.configure` (see [Configuration](#configuration) section). However, a few environment variables are still used:
1058
+
1059
+ #### OPENAI_API_KEY
1060
+
1061
+ Required when using OpenAI as your embedding or tag extraction provider:
1062
+
1063
+ ```bash
1064
+ export OPENAI_API_KEY="sk-proj-your-api-key-here"
1065
+ ```
1066
+
1067
+ This is required when you configure:
1068
+ ```ruby
1069
+ HTM.configure do |config|
1070
+ config.embedding_provider = :openai
1071
+ # or
1072
+ config.tag_provider = :openai
1073
+ end
1074
+ ```
1075
+
1076
+ #### OLLAMA_URL
1077
+
1078
+ Custom Ollama server URL (optional):
1079
+
1080
+ ```bash
1081
+ export OLLAMA_URL="http://localhost:11434" # Default
1082
+ export OLLAMA_URL="http://ollama-server:11434" # Custom
1083
+ ```
1084
+
1085
+ HTM defaults to `http://localhost:11434` if not set.
1086
+
1087
+ #### HTM_LOG_LEVEL
1088
+
1089
+ Control logging verbosity:
1090
+
1091
+ ```bash
1092
+ export HTM_LOG_LEVEL="DEBUG" # DEBUG, INFO, WARN, ERROR, FATAL
1093
+ export HTM_LOG_LEVEL="INFO" # Default
1094
+ ```
1095
+
1096
+ This is used by the default logger when `HTM.configure` is called without a custom logger.
1097
+
1098
+ ### Quick Setup Examples
1099
+
1100
+ #### Local Development (PostgreSQL)
1101
+
1102
+ ```bash
1103
+ # Simple local setup
1104
+ export HTM_DBURL="postgresql://postgres:postgres@localhost:5432/htm_dev?sslmode=disable"
1105
+
1106
+ # With custom user
1107
+ export HTM_DBURL="postgresql://myuser:mypass@localhost:5432/htm_dev"
1108
+ ```
1109
+
1110
+ #### With OpenAI Embeddings
1111
+
1112
+ ```bash
1113
+ # Database + OpenAI
1114
+ export HTM_DBURL="postgresql://user:pass@host:port/db?sslmode=require"
1115
+ export OPENAI_API_KEY="sk-proj-your-api-key-here"
1116
+ ```
1117
+
1118
+ ### Persistent Configuration
1119
+
1120
+ To make environment variables permanent, add them to your shell profile:
1121
+
1122
+ **For Bash (~/.bashrc or ~/.bash_profile):**
1123
+ ```bash
1124
+ echo 'export HTM_DBURL="postgresql://user:pass@host:port/db?sslmode=require"' >> ~/.bashrc
1125
+ source ~/.bashrc
1126
+ ```
1127
+
1128
+ **For Zsh (~/.zshrc):**
1129
+ ```bash
1130
+ echo 'export HTM_DBURL="postgresql://user:pass@host:port/db?sslmode=require"' >> ~/.zshrc
1131
+ source ~/.zshrc
1132
+ ```
1133
+
1134
+ **Or create a dedicated file (~/.bashrc__htm):**
1135
+ ```bash
1136
+ # ~/.bashrc__htm
1137
+ export HTM_DBURL="postgresql://user:pass@host:port/db?sslmode=require"
1138
+ export OPENAI_API_KEY="sk-proj-your-api-key"
1139
+ export HTM_SERVICE_NAME="my-service"
1140
+
1141
+ # Then source it from your main profile
1142
+ echo 'source ~/.bashrc__htm' >> ~/.bashrc
1143
+ ```
1144
+
1145
+ ### Verification
1146
+
1147
+ Verify your configuration:
1148
+
1149
+ ```bash
1150
+ # Check database connection
1151
+ ruby test_connection.rb
1152
+
1153
+ # Or in Ruby
1154
+ irb -r ./lib/htm
1155
+ > HTM::Database.default_config
1156
+ > # Should return a hash with your connection parameters
1157
+ ```
1158
+
1159
+ ## Development
1160
+
1161
+ After checking out the repo, run:
1162
+
1163
+ ```bash
1164
+ # Install dependencies
1165
+ bundle install
1166
+
1167
+ # Enable direnv (loads .envrc environment variables)
1168
+ direnv allow
1169
+
1170
+ # Run tests
1171
+ rake test
1172
+
1173
+ # Run example
1174
+ ruby examples/basic_usage.rb
1175
+ ```
1176
+
1177
+ ### Database Management
1178
+
1179
+ HTM provides comprehensive rake tasks under the `htm:db` namespace for managing the database:
1180
+
1181
+ ```bash
1182
+ # List all database tasks
1183
+ rake -T htm:db
1184
+
1185
+ # Set up database (create schema + run migrations)
1186
+ rake htm:db:setup
1187
+
1188
+ # Run pending migrations only
1189
+ rake htm:db:migrate
1190
+
1191
+ # Show migration status (which migrations are applied)
1192
+ rake htm:db:status
1193
+
1194
+ # Show database info (size, tables, extensions, row counts)
1195
+ rake htm:db:info
1196
+
1197
+ # Test database connection
1198
+ rake htm:db:test
1199
+
1200
+ # Open PostgreSQL console (interactive psql session)
1201
+ rake htm:db:console
1202
+
1203
+ # Seed database with sample data
1204
+ rake htm:db:seed
1205
+
1206
+ # Drop all HTM tables (WARNING: destructive!)
1207
+ rake htm:db:drop
1208
+
1209
+ # Drop and recreate database (WARNING: destructive!)
1210
+ rake htm:db:reset
1211
+ ```
1212
+
1213
+ **Important**: Make sure `direnv allow` has been run once in the project directory to load database environment variables from `.envrc`. Alternatively, you can manually export the environment variables:
1214
+
1215
+ ```bash
1216
+ # Manually export HTM_DBURL
1217
+ export HTM_DBURL="postgresql://user:password@host:port/dbname?sslmode=require"
1218
+ ```
1219
+
1220
+ **Common Workflows**:
1221
+
1222
+ ```bash
1223
+ # Initial setup (first time)
1224
+ direnv allow
1225
+ rake htm:db:setup # Creates schema and runs migrations
1226
+
1227
+ # After pulling new migrations
1228
+ rake htm:db:migrate # Run pending migrations
1229
+
1230
+ # Check database state
1231
+ rake htm:db:status # See migration status
1232
+ rake htm:db:info # See database details
1233
+
1234
+ # Reset database (development only!)
1235
+ rake htm:db:reset # Drops and recreates everything
1236
+
1237
+ # Open database console for debugging
1238
+ rake htm:db:console # Opens psql
1239
+ ```
1240
+
1241
+ ### Async Job Management
1242
+
1243
+ HTM provides rake tasks under the `htm:jobs` namespace for managing async embedding and tag extraction jobs:
1244
+
1245
+ ```bash
1246
+ # List all job management tasks
1247
+ rake -T htm:jobs
1248
+
1249
+ # Show statistics for async processing
1250
+ rake htm:jobs:stats
1251
+
1252
+ # Process pending jobs
1253
+ rake htm:jobs:process_embeddings # Process nodes without embeddings
1254
+ rake htm:jobs:process_tags # Process nodes without tags
1255
+ rake htm:jobs:process_all # Process both
1256
+
1257
+ # Reprocess all nodes (force regeneration)
1258
+ rake htm:jobs:reprocess_embeddings # WARNING: Prompts for confirmation
1259
+
1260
+ # Debugging and maintenance
1261
+ rake htm:jobs:failed # Show stuck jobs (>1 hour old)
1262
+ rake htm:jobs:clear_all # Clear all embeddings/tags (testing only)
1263
+ ```
1264
+
1265
+ **Common Workflows**:
1266
+
1267
+ ```bash
1268
+ # Monitor async processing
1269
+ rake htm:jobs:stats
1270
+
1271
+ # Manually process pending jobs (if async job runner is not running)
1272
+ rake htm:jobs:process_all
1273
+
1274
+ # Debug stuck jobs
1275
+ rake htm:jobs:failed
1276
+ rake htm:jobs:process_embeddings # Retry failed embeddings
1277
+
1278
+ # Development/testing: force regeneration
1279
+ rake htm:jobs:reprocess_embeddings # Regenerate all embeddings
1280
+ rake htm:jobs:clear_all # Clear everything and start fresh
1281
+ ```
1282
+
1283
+ See the [Async Job Processing](#async-job-processing) section for more details.
1284
+
1285
+ ## Testing
1286
+
1287
+ HTM uses Minitest:
1288
+
1289
+ ```bash
1290
+ # Run all tests
1291
+ rake test
1292
+
1293
+ # Run specific test file
1294
+ ruby test/htm_test.rb
1295
+ ```
1296
+
1297
+ ## Architecture
1298
+
1299
+ See [htm_teamwork.md](htm_teamwork.md) for detailed design documentation and planning notes.
1300
+
1301
+ ### Key Components
1302
+
1303
+ - **HTM**: Main API class that coordinates all components and provides the primary interface
1304
+ - **Configuration**: Dependency injection for LLM providers (embedding_generator, tag_extractor, token_counter, logger)
1305
+ - **WorkingMemory**: In-memory, token-limited active context for immediate LLM use
1306
+ - **LongTermMemory**: PostgreSQL-backed permanent storage with connection pooling
1307
+ - **EmbeddingService**: Processing and validation layer for vector embeddings (calls configured `embedding_generator`)
1308
+ - **TagService**: Processing and validation layer for hierarchical tags (calls configured `tag_extractor`)
1309
+ - **Database**: Schema setup, migrations, and management
1310
+ - **Background Jobs**: Async processing for embedding generation and tag extraction
1311
+
1312
+ ### Database Schema
1313
+
1314
+ - `robots`: Robot registry for all LLM agents using HTM
1315
+ - `nodes`: Main memory storage with vector embeddings (pgvector), full-text search (tsvector), metadata
1316
+ - `tags`: Hierarchical tag ontology (format: `root:level1:level2:level3`)
1317
+ - `node_tags`: Join table implementing many-to-many relationship between nodes and tags
1318
+
1319
+ ### Service Architecture
1320
+
1321
+ HTM uses a layered architecture for LLM integration:
1322
+
1323
+ 1. **Configuration Layer** (`HTM.configure`): Provides raw LLM access via `embedding_generator` and `tag_extractor` callables
1324
+ 2. **Service Layer** (`EmbeddingService`, `TagService`): Processes and validates LLM responses, handles padding/truncation, formats for storage
1325
+ 3. **Consumer Layer** (`GenerateEmbeddingJob`, `GenerateTagsJob`, rake tasks): Uses services for async or synchronous processing
1326
+
1327
+ This separation allows you to provide any LLM implementation while HTM handles response processing, validation, and storage.
1328
+
1329
+ ## Roadmap
1330
+
1331
+ - [x] Phase 1: Foundation (basic two-tier memory)
1332
+ - [ ] Phase 2: RAG retrieval (semantic search)
1333
+ - [ ] Phase 3: Relationships & tags
1334
+ - [ ] Phase 4: Working memory management
1335
+ - [ ] Phase 5: Hive mind features
1336
+ - [ ] Phase 6: Operations & observability
1337
+ - [ ] Phase 7: Advanced features
1338
+ - [ ] Phase 8: Production-ready gem
1339
+
1340
+
1341
+ ## Contributing
1342
+
1343
+ Bug reports and pull requests are welcome on GitHub at https://github.com/madbomber/htm.
1344
+
1345
+ ## License
1346
+
1347
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).