htm 0.0.15 → 0.0.17

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/CHANGELOG.md +67 -0
  4. data/README.md +97 -1592
  5. data/bin/htm_mcp +31 -0
  6. data/config/database.yml +7 -4
  7. data/docs/getting-started/installation.md +31 -11
  8. data/docs/guides/mcp-server.md +456 -21
  9. data/docs/multi_framework_support.md +2 -2
  10. data/examples/mcp_client.rb +2 -2
  11. data/examples/rails_app/.gitignore +2 -0
  12. data/examples/rails_app/Gemfile +22 -0
  13. data/examples/rails_app/Gemfile.lock +438 -0
  14. data/examples/rails_app/Procfile.dev +1 -0
  15. data/examples/rails_app/README.md +98 -0
  16. data/examples/rails_app/Rakefile +5 -0
  17. data/examples/rails_app/app/assets/stylesheets/application.css +83 -0
  18. data/examples/rails_app/app/assets/stylesheets/inter-font.css +6 -0
  19. data/examples/rails_app/app/controllers/application_controller.rb +19 -0
  20. data/examples/rails_app/app/controllers/dashboard_controller.rb +27 -0
  21. data/examples/rails_app/app/controllers/files_controller.rb +205 -0
  22. data/examples/rails_app/app/controllers/memories_controller.rb +102 -0
  23. data/examples/rails_app/app/controllers/robots_controller.rb +44 -0
  24. data/examples/rails_app/app/controllers/search_controller.rb +46 -0
  25. data/examples/rails_app/app/controllers/tags_controller.rb +30 -0
  26. data/examples/rails_app/app/javascript/application.js +4 -0
  27. data/examples/rails_app/app/javascript/controllers/application.js +9 -0
  28. data/examples/rails_app/app/javascript/controllers/index.js +6 -0
  29. data/examples/rails_app/app/views/dashboard/index.html.erb +123 -0
  30. data/examples/rails_app/app/views/files/index.html.erb +108 -0
  31. data/examples/rails_app/app/views/files/new.html.erb +321 -0
  32. data/examples/rails_app/app/views/files/show.html.erb +130 -0
  33. data/examples/rails_app/app/views/layouts/application.html.erb +124 -0
  34. data/examples/rails_app/app/views/memories/_memory_card.html.erb +51 -0
  35. data/examples/rails_app/app/views/memories/deleted.html.erb +62 -0
  36. data/examples/rails_app/app/views/memories/edit.html.erb +35 -0
  37. data/examples/rails_app/app/views/memories/index.html.erb +81 -0
  38. data/examples/rails_app/app/views/memories/new.html.erb +71 -0
  39. data/examples/rails_app/app/views/memories/show.html.erb +126 -0
  40. data/examples/rails_app/app/views/robots/index.html.erb +106 -0
  41. data/examples/rails_app/app/views/robots/new.html.erb +36 -0
  42. data/examples/rails_app/app/views/robots/show.html.erb +79 -0
  43. data/examples/rails_app/app/views/search/index.html.erb +184 -0
  44. data/examples/rails_app/app/views/shared/_navbar.html.erb +52 -0
  45. data/examples/rails_app/app/views/shared/_stat_card.html.erb +52 -0
  46. data/examples/rails_app/app/views/tags/index.html.erb +131 -0
  47. data/examples/rails_app/app/views/tags/show.html.erb +67 -0
  48. data/examples/rails_app/bin/dev +8 -0
  49. data/examples/rails_app/bin/rails +4 -0
  50. data/examples/rails_app/bin/rake +4 -0
  51. data/examples/rails_app/config/application.rb +33 -0
  52. data/examples/rails_app/config/boot.rb +5 -0
  53. data/examples/rails_app/config/database.yml +15 -0
  54. data/examples/rails_app/config/environment.rb +5 -0
  55. data/examples/rails_app/config/importmap.rb +7 -0
  56. data/examples/rails_app/config/routes.rb +38 -0
  57. data/examples/rails_app/config/tailwind.config.js +35 -0
  58. data/examples/rails_app/config.ru +5 -0
  59. data/examples/rails_app/log/.keep +0 -0
  60. data/examples/rails_app/tmp/local_secret.txt +1 -0
  61. data/lib/htm/active_record_config.rb +2 -5
  62. data/lib/htm/configuration.rb +35 -2
  63. data/lib/htm/database.rb +3 -6
  64. data/lib/htm/mcp/cli.rb +333 -0
  65. data/lib/htm/mcp/group_tools.rb +476 -0
  66. data/lib/htm/mcp/resources.rb +89 -0
  67. data/lib/htm/mcp/server.rb +98 -0
  68. data/lib/htm/mcp/tools.rb +488 -0
  69. data/lib/htm/models/file_source.rb +5 -3
  70. data/lib/htm/railtie.rb +0 -4
  71. data/lib/htm/tasks.rb +7 -4
  72. data/lib/htm/version.rb +1 -1
  73. data/lib/tasks/htm.rake +6 -9
  74. metadata +59 -4
  75. data/bin/htm_mcp.rb +0 -621
data/README.md CHANGED
@@ -1,1657 +1,163 @@
1
1
  <div align="center">
2
2
  <h1>HTM</h1>
3
- <!-- img src="docs/assets/images/htm.jpg" alt="HTM - Hierarchical Temporal Memory" width="600" -->
4
3
  <img src="docs/assets/images/htm_demo.gif" alt="Tree of Knowledge is Growing" width="400">
5
4
 
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
- - Uses the [ruby_llm](https://ruby_llm.com) gem for LLM access
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
- - **Robot Groups**
55
- - Coordinate multiple robots with shared working memory
56
- - Real-time synchronization via PostgreSQL LISTEN/NOTIFY
57
- - Active/passive roles for instant failover
58
- - Dynamic scaling (add/remove robots at runtime)
59
-
60
- - **LLM-Driven Tag Extraction**
61
- - Automatic hierarchical tag extraction from content
62
- - Tags in colon-delimited format (e.g., `database:postgresql:performance`)
63
- - LLM-powered asynchronous processing
64
- - Enables both structured navigation and semantic discovery
65
- - Complements vector embeddings (symbolic + sub-symbolic retrieval)
66
-
67
- - **Knowledge Graph**
68
- - Tag-based categorization
69
- - Hierarchical tag structures
70
-
71
- - **File Loading**
72
- - Load markdown files into long-term memory
73
- - Automatic paragraph-based chunking
74
- - Source file tracking with re-sync support
75
- - YAML frontmatter extraction as metadata
76
-
77
- - **Telemetry (OpenTelemetry)**
78
- - Optional metrics collection via OpenTelemetry
79
- - Zero overhead when disabled (null object pattern)
80
- - Works with 50+ backends (Jaeger, Prometheus, Datadog, etc.)
81
- - Tracks job latency, search performance, and cache effectiveness
82
-
83
- ## Installation
84
-
85
- Add this line to your application's Gemfile:
86
-
87
- ```ruby
88
- gem 'htm'
89
- ```
90
-
91
- And then execute:
92
-
93
- ```bash
94
- bundle install
95
- ```
96
-
97
- Or install it yourself as:
98
-
99
- ```bash
100
- gem install htm
101
- ```
102
-
103
- ## Setup
104
-
105
- ### 1. Database Configuration
106
-
107
- HTM uses PostgreSQL for durable long-term memory storage. Set up your database connection via environment variables:
108
-
109
- ```bash
110
- # Preferred: Full connection URL
111
- export HTM_DBURL="postgresql://user:password@host:port/dbname?sslmode=require"
112
-
113
- # Alternative: Individual parameters
114
- export HTM_DBNAME="htm_production"
115
- export HTM_DBUSER="postgres"
116
- export HTM_DBPASS="your_password"
117
- export HTM_DBHOST="localhost"
118
- export HTM_DBPORT="5432"
119
- ```
120
-
121
- See the [Environment Variables](#environment-variables) section below for complete details.
122
-
123
- ### 2. Configure LLM Providers
124
-
125
- HTM uses LLM providers for embedding generation and tag extraction. By default, it uses Ollama (local).
126
-
127
- **Start Ollama (Default Configuration)**:
128
- ```bash
129
- # Install Ollama
130
- curl https://ollama.ai/install.sh | sh
131
-
132
- # Start Ollama and pull models
133
- ollama serve
134
- ollama pull nomic-embed-text # For embeddings (768 dimensions)
135
- ollama pull llama3 # For tag extraction
136
- ```
137
-
138
- **Configure HTM** (optional - uses defaults if not configured):
139
- ```ruby
140
- require 'htm'
141
-
142
- # Use defaults (Ollama with nomic-embed-text and llama3)
143
- HTM.configure
144
-
145
- # Or customize providers
146
- HTM.configure do |config|
147
- # Embedding configuration
148
- config.embedding_provider = :ollama # or :openai
149
- config.embedding_model = 'nomic-embed-text'
150
- config.embedding_dimensions = 768
151
- config.ollama_url = 'http://localhost:11434'
152
-
153
- # Tag extraction configuration
154
- config.tag_provider = :ollama # or :openai
155
- config.tag_model = 'llama3'
156
-
157
- # Logger configuration (optional)
158
- config.logger = Logger.new($stdout)
159
- config.logger.level = Logger::INFO
160
-
161
- # Custom embedding generator (advanced)
162
- config.embedding_generator = ->(text) {
163
- # Your custom implementation
164
- # Must return Array<Float>
165
- }
166
-
167
- # Custom tag extractor (advanced)
168
- config.tag_extractor = ->(text, ontology) {
169
- # Your custom implementation
170
- # Must return Array<String>
171
- }
172
-
173
- # Token counter (optional, defaults to Tiktoken)
174
- config.token_counter = ->(text) {
175
- # Your custom implementation
176
- # Must return Integer
177
- }
178
- end
179
- ```
180
-
181
- See the [Configuration](#configuration) section below for complete details.
182
-
183
- ### 3. Initialize Database Schema
184
-
185
- **Using Rake Tasks (Recommended)**:
186
-
187
- HTM provides comprehensive rake tasks for database management. Add this to your application's `Rakefile`:
188
-
189
- ```ruby
190
- require 'htm/tasks'
191
- ```
192
-
193
- Then use the tasks:
194
-
195
- ```bash
196
- # Set up database schema and run migrations
197
- rake htm:db:setup
198
-
199
- # Verify connection
200
- rake htm:db:test
201
-
202
- # Check database status
203
- rake htm:db:info
204
- ```
205
-
206
- **Or programmatically**:
207
-
208
- ```ruby
209
- require 'htm'
210
-
211
- # Run once to set up database schema
212
- HTM::Database.setup
213
- ```
214
-
215
- **Or from command line**:
216
-
217
- ```bash
218
- ruby -r ./lib/htm -e "HTM::Database.setup"
219
- ```
220
-
221
- See [Using HTM Rake Tasks in Your Application](docs/using_rake_tasks_in_your_app.md) for complete integration guide.
222
-
223
- ### 4. Verify Setup
224
-
225
- ```bash
226
- rake htm:db:test # Using rake tasks
227
- # or
228
- ruby test_connection.rb
229
- ```
230
-
231
- See [docs/setup_local_database.md](docs/setup_local_database.md) for detailed local database setup instructions.
232
-
233
- ## Usage
234
-
235
- ### Basic Example
236
-
237
- ```ruby
238
- require 'htm'
239
- require 'ruby_llm'
240
- require 'logger'
241
-
242
- # Configure HTM with RubyLLM for embeddings and tag extraction
243
- HTM.configure do |config|
244
- # Configure logger (optional - uses STDOUT at INFO level if not provided)
245
- config.logger = Logger.new($stdout)
246
- config.logger.level = Logger::INFO
247
-
248
- # Configure embedding generation using RubyLLM with Ollama
249
- config.embedding_provider = :ollama
250
- config.embedding_model = 'nomic-embed-text'
251
- config.embedding_dimensions = 768
252
- config.ollama_url = ENV['OLLAMA_URL'] || 'http://localhost:11434'
253
-
254
- # Configure tag extraction using RubyLLM with Ollama
255
- config.tag_provider = :ollama
256
- config.tag_model = 'llama3'
257
-
258
- # Apply configuration (sets up default RubyLLM implementations)
259
- config.reset_to_defaults
260
- end
261
-
262
- # Initialize HTM for your robot
263
- htm = HTM.new(
264
- robot_name: "Code Helper",
265
- working_memory_size: 128_000 # tokens
266
- )
267
-
268
- # Remember information - embeddings and tags generated asynchronously
269
- node_id = htm.remember(
270
- "We decided to use PostgreSQL for HTM storage",
271
- source: "architect"
272
- )
273
-
274
- # Recall from the past (uses semantic search with embeddings)
275
- memories = htm.recall(
276
- "database decisions",
277
- timeframe: "last week",
278
- strategy: :hybrid # Combines vector + full-text search
279
- )
280
-
281
- # Forget (explicit deletion only)
282
- htm.forget(node_id, confirm: :confirmed)
283
- ```
284
-
285
- ### Loading Files
286
-
287
- HTM can load text-based files (currently markdown) into long-term memory with automatic chunking and source tracking.
288
-
289
- ```ruby
290
- htm = HTM.new(robot_name: "Document Loader")
291
-
292
- # Load a single markdown file
293
- result = htm.load_file("docs/guide.md")
294
- # => { file_source_id: 1, chunks_created: 5, chunks_updated: 0, chunks_deleted: 0 }
295
-
296
- # Load all markdown files in a directory
297
- results = htm.load_directory("docs/", pattern: "**/*.md")
298
-
299
- # Get nodes from a specific file
300
- nodes = htm.nodes_from_file("docs/guide.md")
301
-
302
- # Unload a file (soft deletes chunks)
303
- htm.unload_file("docs/guide.md")
304
- ```
305
-
306
- **Features:**
307
- - **Paragraph chunking**: Text split by blank lines, code blocks preserved
308
- - **Source tracking**: Files tracked with mtime for automatic re-sync
309
- - **YAML frontmatter**: Extracted and stored as metadata
310
- - **Duplicate detection**: Content hash prevents duplicate nodes
311
-
312
- **Rake tasks:**
313
- ```bash
314
- rake 'htm:files:load[docs/guide.md]' # Load a single file
315
- rake 'htm:files:load_dir[docs/]' # Load all markdown files from directory
316
- rake htm:files:list # List all loaded file sources
317
- rake htm:files:sync # Sync all files (reload changed)
318
- rake htm:files:stats # Show file loading statistics
319
- ```
320
-
321
- ### Robot Groups
322
-
323
- Robot Groups enable coordination of multiple robots with shared working memory and real-time synchronization. This is useful for high-availability setups, load balancing, and collaborative AI agents.
324
-
325
- **Key Classes:**
326
- - `HTM::RobotGroup` - Coordinates multiple robots with active/passive roles and shared working memory
327
- - `HTM::WorkingMemoryChannel` - PostgreSQL LISTEN/NOTIFY pub/sub for real-time cross-process synchronization
328
-
329
- **Basic Usage:**
330
-
331
- ```ruby
332
- require 'htm'
333
-
334
- HTM.configure do |config|
335
- config.embedding_provider = :ollama
336
- config.embedding_model = 'nomic-embed-text'
337
- end
338
-
339
- # Create a robot group with primary + standby
340
- group = HTM::RobotGroup.new(
341
- name: 'customer-support-ha',
342
- active: ['support-primary'],
343
- passive: ['support-standby'],
344
- max_tokens: 8000
345
- )
346
-
347
- # Add memories to the shared working memory
348
- group.remember("Customer #123 prefers email communication", originator: 'support-primary')
349
-
350
- # All robots in the group can recall shared memories
351
- memories = group.recall('customer', limit: 5, strategy: :fulltext)
352
-
353
- # Simulate failover when primary fails
354
- group.failover! # Promotes standby to active
355
-
356
- # Dynamic scaling - add more robots at runtime
357
- group.add_active('support-secondary')
358
- group.sync_robot('support-secondary') # Sync existing memories
359
-
360
- # Check group status
361
- status = group.status
362
- # => { name: "customer-support-ha", active: [...], passive: [...], in_sync: true, ... }
363
-
364
- # Cleanup
365
- group.shutdown
366
- ```
367
-
368
- **Cross-Process Synchronization:**
369
-
370
- For multi-process deployments, use `HTM::WorkingMemoryChannel` directly:
371
-
372
- ```ruby
373
- # In each worker process
374
- channel = HTM::WorkingMemoryChannel.new('my-group', HTM::Database.default_config)
375
-
376
- # Listen for memory changes from other processes
377
- channel.on_change do |event, node_id, origin_robot_id|
378
- case event
379
- when :added # Another robot added a memory
380
- when :evicted # Another robot evicted a memory
381
- when :cleared # Another robot cleared working memory
382
- end
383
- end
384
-
385
- channel.start_listening
386
-
387
- # Notify other processes when you add a memory
388
- channel.notify(:added, node_id: node.id, robot_id: my_robot_id)
389
- ```
390
-
391
- **Demo Applications:**
392
-
393
- See `examples/robot_groups/` for complete examples:
394
- - `same_process.rb` - Single-process demo with multiple robots
395
- - `multi_process.rb` - Multi-process demo with real-time sync
396
-
397
- ### Automatic Tag Extraction
398
-
399
- HTM automatically extracts hierarchical tags from content using LLM analysis. Tags are inferred from the content itself - you never specify them manually.
400
-
401
- **Example:**
402
- ```ruby
403
- htm.remember(
404
- "User prefers dark mode for all interfaces",
405
- source: "user"
406
- )
407
- # Tags like "preference", "ui", "dark-mode" may be auto-extracted by the LLM
408
- # The specific tags depend on the LLM's analysis of the content
409
- ```
410
-
411
- ### Async Job Processing
412
-
413
- HTM automatically generates embeddings and extracts tags asynchronously in background jobs. This avoids blocking the main request path.
414
-
415
- **Monitor job processing:**
416
- ```bash
417
- # Show statistics for nodes and async processing
418
- rake htm:jobs:stats
419
-
420
- # Output:
421
- # Total nodes: 150
422
- # Nodes with embeddings: 145 (96.7%)
423
- # Nodes without embeddings: 5 (3.3%)
424
- # Nodes with tags: 140 (93.3%)
425
- # Total tags in ontology: 42
426
- ```
427
-
428
- **Process pending jobs manually:**
429
- ```bash
430
- # Process all pending embedding jobs
431
- rake htm:jobs:process_embeddings
432
-
433
- # Process all pending tag extraction jobs
434
- rake htm:jobs:process_tags
435
-
436
- # Process both embeddings and tags
437
- rake htm:jobs:process_all
438
- ```
439
-
440
- **Reprocess all nodes (force regeneration):**
441
- ```bash
442
- # Regenerate embeddings for ALL nodes
443
- rake htm:jobs:reprocess_embeddings
444
-
445
- # WARNING: Prompts for confirmation
446
- ```
447
-
448
- **Show failed/stuck jobs:**
449
- ```bash
450
- # Show nodes that failed async processing (>1 hour old without embeddings/tags)
451
- rake htm:jobs:failed
452
- ```
453
-
454
- **Clear all for testing:**
455
- ```bash
456
- # Clear ALL embeddings and tags (development/testing only)
457
- rake htm:jobs:clear_all
458
-
459
- # WARNING: Prompts for confirmation
460
- ```
461
-
462
- See `rake -T htm:jobs` for complete list of job management tasks.
463
-
464
- ## Telemetry (OpenTelemetry)
465
-
466
- HTM includes optional OpenTelemetry-based metrics for production observability. Telemetry is **disabled by default** with zero overhead when off.
467
-
468
- ### Enabling Telemetry
469
-
470
- ```ruby
471
- HTM.configure do |config|
472
- config.telemetry_enabled = true
473
- end
474
-
475
- # Or via environment variable
476
- # HTM_TELEMETRY_ENABLED=true
477
- ```
478
-
479
- ### Configuring the Destination
480
-
481
- HTM emits metrics via standard OpenTelemetry protocols. Configure your destination using environment variables:
482
-
483
- ```bash
484
- # Export to any OTLP-compatible backend
485
- export OTEL_METRICS_EXPORTER="otlp"
486
- export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
487
- ```
488
-
489
- ### Available Metrics
490
-
491
- | Metric | Type | Attributes | Description |
492
- |--------|------|------------|-------------|
493
- | `htm.jobs` | Counter | `job`, `status` | Job execution counts (embedding, tags) |
494
- | `htm.embedding.latency` | Histogram | `provider`, `status` | Embedding generation time (ms) |
495
- | `htm.tag.latency` | Histogram | `provider`, `status` | Tag extraction time (ms) |
496
- | `htm.search.latency` | Histogram | `strategy` | Search operation time (ms) |
497
- | `htm.cache.operations` | Counter | `operation` | Cache hits/misses |
498
-
499
- ### Compatible Backends
500
-
501
- HTM works with any OTLP-compatible observability platform:
502
-
503
- **Open Source:** Jaeger, Prometheus, Grafana Tempo/Mimir, SigNoz, Uptrace
504
-
505
- **Commercial:** Datadog, New Relic, Honeycomb, Splunk, Dynatrace, AWS X-Ray, Google Cloud Trace, Azure Monitor
506
-
507
- ### Optional Dependencies
508
-
509
- Users who want telemetry should add these gems:
510
-
511
- ```ruby
512
- gem 'opentelemetry-sdk'
513
- gem 'opentelemetry-metrics-sdk'
514
- gem 'opentelemetry-exporter-otlp' # For OTLP export
515
- ```
516
-
517
- ### Design
518
-
519
- HTM uses a **null object pattern** for telemetry. When disabled or when the SDK is not installed:
520
- - All metric operations are no-ops
521
- - Zero runtime overhead
522
- - No errors or exceptions
523
-
524
- See [docs/telemetry.md](docs/telemetry.md) for detailed configuration and usage examples.
525
-
526
- ## MCP Server (Model Context Protocol)
527
-
528
- HTM includes an MCP server that exposes memory capabilities to AI assistants like Claude Desktop, Claude Code, and AIA. This enables any MCP-compatible client to store, recall, and manage memories through a standardized protocol.
529
-
530
- ### Available Tools
531
-
532
- | Tool | Description |
533
- |------|-------------|
534
- | `SetRobotTool` | Set the robot identity for the session (call first) |
535
- | `GetRobotTool` | Get current robot information |
536
- | `GetWorkingMemoryTool` | Get working memory contents for session restore |
537
- | `RememberTool` | Store information with optional tags and metadata |
538
- | `RecallTool` | Search memories using vector, fulltext, or hybrid strategies |
539
- | `ForgetTool` | Soft-delete a memory (recoverable) |
540
- | `RestoreTool` | Restore a soft-deleted memory |
541
- | `ListTagsTool` | List tags with optional prefix filtering |
542
- | `SearchTagsTool` | Fuzzy tag search with typo tolerance |
543
- | `FindByTopicTool` | Find nodes by topic with optional fuzzy matching |
544
- | `StatsTool` | Get memory usage statistics |
545
-
546
- ### Available Resources
547
-
548
- | URI | Description |
549
- |-----|-------------|
550
- | `htm://statistics` | Memory statistics as JSON |
551
- | `htm://tags/hierarchy` | Tag hierarchy as text tree |
552
- | `htm://memories/recent` | Last 20 memories |
553
-
554
- ### Client Configuration
555
-
556
- **Claude Desktop** (`~/.config/claude/claude_desktop_config.json`):
557
- ```json
558
- {
559
- "mcpServers": {
560
- "htm-memory": {
561
- "command": "htm_mcp.rb",
562
- "env": {
563
- "HTM_DBURL": "postgresql://user@localhost:5432/htm_development"
564
- }
565
- }
566
- }
567
- }
568
- ```
569
-
570
- **Claude Code** (`~/.claude/claude_code_config.json`):
571
- ```json
572
- {
573
- "mcpServers": {
574
- "htm-memory": {
575
- "command": "htm_mcp.rb",
576
- "env": {
577
- "HTM_DBURL": "postgresql://user@localhost:5432/htm_development"
578
- }
579
- }
580
- }
581
- }
582
- ```
583
-
584
- **AIA** (`~/.config/aia/config.yml`):
585
- ```yaml
586
- mcp_servers:
587
- htm-memory:
588
- command: htm_mcp.rb
589
- env:
590
- HTM_DBURL: postgresql://user@localhost:5432/htm_development
591
- ```
592
-
593
- See [docs/guides/mcp-server.md](docs/guides/mcp-server.md) for detailed configuration, usage examples, and troubleshooting.
594
-
595
- ## Configuration
596
-
597
- HTM uses dependency injection for LLM access, allowing you to configure embedding generation, tag extraction, logging, and token counting.
598
-
599
- ### Default Configuration
600
-
601
- By default, HTM uses:
602
- - **Embedding Provider**: Ollama (local) with `nomic-embed-text` model (768 dimensions)
603
- - **Tag Provider**: Ollama (local) with `llama3` model
604
- - **Logger**: Ruby's standard Logger to STDOUT at INFO level
605
- - **Token Counter**: Tiktoken with GPT-3.5-turbo encoding
606
-
607
- ```ruby
608
- require 'htm'
609
-
610
- # Use defaults
611
- HTM.configure # or omit this - configuration is lazy-loaded
612
- ```
613
-
614
- ### Custom Configuration
615
-
616
- Configure HTM before creating instances:
617
-
618
- #### Using Ollama (Default)
619
-
620
- ```ruby
621
- HTM.configure do |config|
622
- # Embedding configuration
623
- config.embedding_provider = :ollama
624
- config.embedding_model = 'nomic-embed-text' # 768 dimensions
625
- config.embedding_dimensions = 768
626
- config.ollama_url = 'http://localhost:11434'
627
-
628
- # Tag extraction configuration
629
- config.tag_provider = :ollama
630
- config.tag_model = 'llama3'
631
-
632
- # Logger configuration
633
- config.logger = Logger.new($stdout)
634
- config.logger.level = Logger::INFO
635
- end
636
- ```
637
-
638
- **Ollama Setup:**
639
- ```bash
640
- # Install Ollama
641
- curl https://ollama.ai/install.sh | sh
642
-
643
- # Pull models
644
- ollama pull nomic-embed-text # For embeddings
645
- ollama pull llama3 # For tag extraction
646
-
647
- # Verify Ollama is running
648
- curl http://localhost:11434/api/version
649
- ```
650
-
651
- #### Using OpenAI
652
-
653
- ```ruby
654
- HTM.configure do |config|
655
- # Embedding configuration (OpenAI)
656
- config.embedding_provider = :openai
657
- config.embedding_model = 'text-embedding-3-small' # 1536 dimensions
658
- config.embedding_dimensions = 1536
659
-
660
- # Tag extraction (can mix providers)
661
- config.tag_provider = :openai
662
- config.tag_model = 'gpt-4'
663
-
664
- # Logger
665
- config.logger = Rails.logger # Use Rails logger
666
- end
667
- ```
668
-
669
- **OpenAI Setup:**
670
- ```bash
671
- # Set your OpenAI API key
672
- export OPENAI_API_KEY='sk-your-api-key-here'
673
- ```
674
-
675
- **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.
676
-
677
- #### Custom Providers
678
-
679
- Provide your own LLM implementations:
680
-
681
- ```ruby
682
- HTM.configure do |config|
683
- # Custom embedding generator
684
- config.embedding_generator = ->(text) {
685
- # Call your custom LLM service
686
- response = MyEmbeddingService.generate(text)
687
-
688
- # Must return Array<Float>
689
- response[:embedding] # e.g., [0.123, -0.456, ...]
690
- }
691
-
692
- # Custom tag extractor
693
- config.tag_extractor = ->(text, existing_ontology) {
694
- # Call your custom LLM service for tag extraction
695
- # existing_ontology is Array<String> of recent tags for context
696
- response = MyTagService.extract(text, context: existing_ontology)
697
-
698
- # Must return Array<String> in hierarchical format
699
- # e.g., ["ai:llm:embeddings", "database:postgresql"]
700
- response[:tags]
701
- }
702
-
703
- # Custom token counter (optional)
704
- config.token_counter = ->(text) {
705
- # Your token counting implementation
706
- # Must return Integer
707
- MyTokenizer.count(text)
708
- }
709
- end
710
- ```
711
-
712
- #### Logger Configuration
713
-
714
- Customize logging behavior:
715
-
716
- ```ruby
717
- HTM.configure do |config|
718
- # Use custom logger
719
- config.logger = Logger.new('log/htm.log')
720
- config.logger.level = Logger::DEBUG
721
-
722
- # Or use Rails logger
723
- config.logger = Rails.logger
724
-
725
- # Or disable logging
726
- config.logger = Logger.new(IO::NULL)
727
- config.logger.level = Logger::FATAL
728
- end
729
-
730
- # Control log level via environment variable
731
- ENV['HTM_LOG_LEVEL'] = 'DEBUG' # or INFO, WARN, ERROR
732
- HTM.configure # Respects HTM_LOG_LEVEL
733
- ```
734
-
735
- #### Service Layer Architecture
736
-
737
- HTM uses a service layer to process LLM responses:
738
-
739
- - **EmbeddingService**: Calls your configured `embedding_generator`, validates responses, handles padding/truncation, and formats for storage
740
- - **TagService**: Calls your configured `tag_extractor`, parses responses (String or Array), validates format, and filters invalid tags
741
-
742
- This separation allows you to provide raw LLM access while HTM handles response processing, validation, and storage formatting.
743
-
744
- ### Recall Strategies
745
-
746
- HTM supports three retrieval strategies:
747
-
748
- ```ruby
749
- # Vector similarity search (semantic) - uses configured embedding provider
750
- memories = htm.recall(
751
- "HTM architecture",
752
- timeframe: "last week",
753
- strategy: :vector # Semantic similarity using embeddings
754
- )
755
-
756
- # Full-text search (keyword matching) - PostgreSQL full-text search with trigrams
757
- memories = htm.recall(
758
- "database performance",
759
- timeframe: "last month",
760
- strategy: :fulltext # Keyword-based matching
761
- )
762
-
763
- # Hybrid (combines both) - best of both worlds
764
- memories = htm.recall(
765
- "testing strategies",
766
- timeframe: "yesterday",
767
- strategy: :hybrid # Weighted combination of vector + full-text
768
- )
769
- ```
5
+ <p><strong>Give your AI tools a shared, persistent memory.</strong></p>
770
6
 
771
- **Strategy Details:**
772
- - `:vector` - Semantic search using pgvector cosine similarity (requires embeddings)
773
- - `:fulltext` - PostgreSQL tsvector with pg_trgm fuzzy matching
774
- - `:hybrid` - Combines both with weighted scoring (default: 70% vector, 30% full-text)
775
-
776
- ## API Reference
777
-
778
- HTM provides a minimal, focused API with only 3 core instance methods for memory operations:
779
-
780
- ### Core Memory Operations
781
-
782
- #### `remember(content, source: "", metadata: {})`
783
-
784
- Store information in memory. Embeddings and tags are automatically generated asynchronously.
785
-
786
- **Parameters:**
787
- - `content` (String, required) - The information to remember. Converted to string if nil. Returns ID of last node if empty.
788
- - `source` (String, optional) - Where the content came from (e.g., "user", "assistant", "system"). Defaults to empty string.
789
- - `metadata` (Hash, optional) - Arbitrary key-value metadata stored as JSONB. Keys must be strings or symbols. Defaults to `{}`.
790
-
791
- **Returns:** Integer - The node ID of the stored memory
792
-
793
- **Example:**
794
- ```ruby
795
- # Store with source
796
- node_id = htm.remember("PostgreSQL is excellent for vector search with pgvector", source: "architect")
797
-
798
- # Store without source (uses default empty string)
799
- node_id = htm.remember("HTM uses two-tier memory architecture")
800
-
801
- # Store with metadata
802
- node_id = htm.remember(
803
- "User prefers dark mode",
804
- metadata: { category: "preference", priority: "high", version: 2 }
805
- )
806
-
807
- # Nil/empty handling
808
- node_id = htm.remember(nil) # Returns ID of last node without creating duplicate
809
- node_id = htm.remember("") # Returns ID of last node without creating duplicate
810
- ```
811
-
812
- ---
813
-
814
- #### `recall(topic, timeframe: nil, limit: 20, strategy: :vector, with_relevance: false, query_tags: [], metadata: {})`
815
-
816
- Retrieve memories using temporal filtering and semantic/keyword search.
817
-
818
- **Parameters:**
819
- - `topic` (String, required) - Query text for semantic/keyword matching (first positional argument)
820
- - `timeframe` (Range, String, optional) - Time range to search within. Default: "last 7 days"
821
- - Range: `(Time.now - 3600)..Time.now`
822
- - String: `"last hour"`, `"last week"`, `"yesterday"`
823
- - `limit` (Integer, optional) - Maximum number of results. Default: 20
824
- - `strategy` (Symbol, optional) - Search strategy. Default: `:vector`
825
- - `:vector` - Semantic search using embeddings (cosine similarity)
826
- - `:fulltext` - Keyword search using PostgreSQL full-text + trigrams
827
- - `:hybrid` - Weighted combination (70% vector, 30% full-text)
828
- - `with_relevance` (Boolean, optional) - Include dynamic relevance scores. Default: false
829
- - `query_tags` (Array<String>, optional) - Filter results by tags. Default: []
830
- - `metadata` (Hash, optional) - Filter results by metadata using JSONB containment (`@>`). Default: `{}`
831
-
832
- **Returns:** Array<Hash> - Matching memories with fields: `id`, `content`, `source`, `created_at`, `access_count`, `metadata`, (optionally `relevance`)
833
-
834
- **Example:**
835
- ```ruby
836
- # Basic recall with time range
837
- memories = htm.recall(
838
- "database architecture",
839
- timeframe: (Time.now - 86400)..Time.now
840
- )
841
-
842
- # Using human-readable timeframe
843
- memories = htm.recall(
844
- "PostgreSQL performance",
845
- timeframe: "last week",
846
- strategy: :hybrid,
847
- limit: 10
848
- )
849
-
850
- # With relevance scoring
851
- memories = htm.recall(
852
- "HTM design decisions",
853
- timeframe: "last month",
854
- with_relevance: true,
855
- query_tags: ["architecture"]
856
- )
857
- # => [{ "id" => 123, "content" => "...", "relevance" => 0.92, ... }, ...]
858
-
859
- # Filter by metadata
860
- memories = htm.recall(
861
- "user preferences",
862
- metadata: { category: "preference" }
863
- )
864
- # => Returns only nodes with metadata containing { category: "preference" }
865
-
866
- # Combine metadata with other filters
867
- memories = htm.recall(
868
- "settings",
869
- timeframe: "last month",
870
- strategy: :hybrid,
871
- metadata: { priority: "high", version: 2 }
872
- )
873
- ```
874
-
875
- ---
876
-
877
- #### `forget(node_id, confirm: false)`
878
-
879
- Permanently delete a memory from both working memory and long-term storage.
880
-
881
- **Parameters:**
882
- - `node_id` (Integer, required) - The ID of the node to delete
883
- - `confirm` (Symbol, Boolean, required) - Must be `:confirmed` or `true` to proceed
884
-
885
- **Returns:** Boolean - `true` if deleted, `false` if not found
886
-
887
- **Safety:** This is the ONLY way to delete memories. Requires explicit confirmation to prevent accidental deletion.
888
-
889
- **Example:**
890
- ```ruby
891
- # Delete with symbol confirmation (recommended)
892
- htm.forget(node_id, confirm: :confirmed)
893
-
894
- # Delete with boolean confirmation
895
- htm.forget(node_id, confirm: true)
896
-
897
- # Will raise error - confirmation required
898
- htm.forget(node_id) # ArgumentError: Must confirm deletion
899
- htm.forget(node_id, confirm: false) # ArgumentError: Must confirm deletion
900
- ```
901
-
902
- ---
903
-
904
- ### Complete Usage Example
905
-
906
- ```ruby
907
- require 'htm'
908
-
909
- # Configure once globally (optional - uses defaults if not called)
910
- HTM.configure do |config|
911
- config.embedding_provider = :ollama
912
- config.embedding_model = 'nomic-embed-text'
913
- config.tag_provider = :ollama
914
- config.tag_model = 'llama3'
915
- end
916
-
917
- # Initialize for your robot
918
- htm = HTM.new(robot_name: "Assistant", working_memory_size: 128_000)
919
-
920
- # Store information
921
- htm.remember("PostgreSQL with pgvector for vector search", source: "architect")
922
- htm.remember("User prefers dark mode", source: "user")
923
- htm.remember("Use debug_me for debugging, not puts", source: "system")
924
-
925
- # Retrieve by time + topic
926
- recent = htm.recall(
927
- "PostgreSQL",
928
- timeframe: "last week",
929
- strategy: :hybrid,
930
- limit: 5
931
- )
932
-
933
- # Delete if needed (requires node ID from remember or recall)
934
- htm.forget(node_id, confirm: :confirmed)
935
- ```
936
-
937
- ## Use with Rails
938
-
939
- 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.
940
-
941
- ### Why HTM Works Well with Rails
942
-
943
- **1. Uses ActiveRecord**
944
- - HTM models are standard ActiveRecord classes with associations, validations, and scopes
945
- - Follows Rails naming conventions and patterns
946
- - Models are namespaced under `HTM::Models::` to avoid conflicts
947
-
948
- **2. Standard Rails Configuration**
949
- - Uses `config/database.yml` (Rails convention)
950
- - Respects `RAILS_ENV` environment variable
951
- - Supports ERB in configuration files
952
-
953
- **3. Separate Database**
954
- - HTM uses its own PostgreSQL database connection
955
- - Won't interfere with your Rails app's database
956
- - Configured with `prepared_statements: false` and `advisory_locks: false` for compatibility
957
- - Thread-safe for Rails multi-threading
958
-
959
- **4. Connection Management**
960
- - Separate connection pool from your Rails app
961
- - Configurable pool size and timeouts
962
- - Proper cleanup and disconnection support
963
-
964
- ### Integration Steps
965
-
966
- #### 1. Add to Gemfile
967
-
968
- ```ruby
969
- gem 'htm'
970
- ```
971
-
972
- Then run:
973
- ```bash
974
- bundle install
975
- ```
976
-
977
- #### 2. Configure Environment Variables
978
-
979
- Add HTM database configuration to your Rails environment. You can use `.env` files (with `dotenv-rails`) or set them directly:
7
+ <p>
8
+ <a href="https://rubygems.org/gems/htm"><img src="https://img.shields.io/gem/v/htm.svg" alt="Gem Version"></a>
9
+ <a href="https://github.com/madbomber/htm/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
10
+ </p>
11
+ </div>
980
12
 
981
- ```ruby
982
- # .env or config/application.rb
983
- HTM_DBURL=postgresql://user:pass@localhost:5432/htm_production
984
- ```
13
+ <br/>
985
14
 
986
- Or use individual variables:
987
- ```ruby
988
- HTM_DBHOST=localhost
989
- HTM_DBNAME=htm_production
990
- HTM_DBUSER=postgres
991
- HTM_DBPASS=password
992
- HTM_DBPORT=5432
993
- ```
15
+ ## The Problem
994
16
 
995
- #### 3. Create Initializer
17
+ Your AI assistants are brilliant but forgetful. Claude Desktop doesn't remember what Claude Code learned. Your custom chatbot can't recall what happened yesterday. Every tool starts fresh, every session.
996
18
 
997
- Create `config/initializers/htm.rb`:
19
+ **HTM fixes this.**
998
20
 
999
- ```ruby
1000
- # config/initializers/htm.rb
21
+ Connect all your MCP-enabled tools to the same long-term memory. What one robot learns, all robots remember. Context persists across sessions, tools, and time.
1001
22
 
1002
- # Configure HTM
1003
- HTM.configure do |config|
1004
- # Use Rails logger
1005
- config.logger = Rails.logger
23
+ ## What is HTM?
1006
24
 
1007
- # Embedding configuration (optional - uses Ollama defaults if not set)
1008
- config.embedding_provider = :ollama
1009
- config.embedding_model = 'nomic-embed-text'
25
+ - **Hierarchical**: Organizes information from simple concepts to detailed relationships
26
+ - **Temporal**: Retrieves memories across time—from moments ago to months past
27
+ - **Memory**: Encodes, stores, and recalls information with semantic understanding
1010
28
 
1011
- # Tag extraction configuration (optional - uses Ollama defaults if not set)
1012
- config.tag_provider = :ollama
1013
- config.tag_model = 'llama3'
29
+ HTM is a Ruby gem that provides durable, shared memory for LLM-powered applications.
1014
30
 
1015
- # Custom providers (optional)
1016
- # config.embedding_generator = ->(text) { YourEmbeddingService.call(text) }
1017
- # config.tag_extractor = ->(text, ontology) { YourTagService.call(text, ontology) }
1018
- end
31
+ ## Key Capabilities
1019
32
 
1020
- # Establish HTM database connection
1021
- HTM::ActiveRecordConfig.establish_connection!
33
+ ### MCP Server Connect Any AI Tool
1022
34
 
1023
- # Verify required PostgreSQL extensions are installed
1024
- HTM::ActiveRecordConfig.verify_extensions!
35
+ HTM includes a Model Context Protocol server with 23 tools for memory management. Connect Claude Desktop, Claude Code, AIA, or any MCP-compatible client:
1025
36
 
1026
- Rails.logger.info "HTM initialized with database: #{HTM::Database.default_config[:database]}"
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "htm": {
41
+ "command": "htm_mcp",
42
+ "env": { "HTM_DBURL": "postgresql://localhost/htm" }
43
+ }
44
+ }
45
+ }
1027
46
  ```
1028
47
 
1029
- #### 4. Set Up Database
1030
-
1031
- Run the HTM database setup:
48
+ The `htm_mcp` executable includes CLI commands for database management:
1032
49
 
1033
50
  ```bash
1034
- # Using HTM rake tasks
1035
- rake htm:db:setup
1036
-
1037
- # Or manually in Rails console
1038
- rails c
1039
- > HTM::Database.setup
1040
- ```
1041
-
1042
- #### 5. Use in Your Rails App
1043
-
1044
- **In Controllers:**
1045
-
1046
- ```ruby
1047
- class ChatsController < ApplicationController
1048
- before_action :initialize_memory
1049
-
1050
- def create
1051
- # Add user message to memory (embeddings + tags auto-extracted asynchronously)
1052
- @memory.remember(
1053
- params[:message],
1054
- source: "user-#{current_user.id}"
1055
- )
1056
-
1057
- # Recall relevant context for the conversation
1058
- context = @memory.create_context(strategy: :balanced)
1059
-
1060
- # Use context with your LLM to generate response
1061
- response = generate_llm_response(context, params[:message])
1062
-
1063
- # Store assistant's response
1064
- @memory.remember(
1065
- response,
1066
- source: "assistant"
1067
- )
1068
-
1069
- render json: { response: response }
1070
- end
1071
-
1072
- private
1073
-
1074
- def initialize_memory
1075
- @memory = HTM.new(
1076
- robot_name: "ChatBot-#{current_user.id}",
1077
- working_memory_size: 128_000
1078
- )
1079
- end
1080
- end
51
+ htm_mcp setup # Initialize database schema
52
+ htm_mcp verify # Check connection and migrations
53
+ htm_mcp stats # Show memory statistics
54
+ htm_mcp help # Full help with environment variables
55
+ htm_mcp # Start MCP server (default)
1081
56
  ```
1082
57
 
1083
- **In Background Jobs:**
1084
-
1085
- ```ruby
1086
- class ProcessDocumentJob < ApplicationJob
1087
- queue_as :default
1088
-
1089
- def perform(document_id)
1090
- document = Document.find(document_id)
1091
-
1092
- # Initialize HTM for this job
1093
- memory = HTM.new(robot_name: "DocumentProcessor")
1094
-
1095
- # Store document content in memory (embeddings + tags auto-extracted asynchronously)
1096
- memory.remember(
1097
- document.content,
1098
- source: "document-#{document.id}"
1099
- )
1100
-
1101
- # Recall related documents (uses vector similarity)
1102
- related = memory.recall(
1103
- document.category,
1104
- timeframe: "last month",
1105
- strategy: :vector
1106
- )
58
+ Now your AI assistant can remember and recall across sessions:
59
+ - Store decisions, preferences, and context
60
+ - Search memories with natural language
61
+ - Build knowledge that compounds over time
1107
62
 
1108
- # Process with context...
1109
- end
1110
- end
1111
- ```
63
+ ### Hive Mind — Shared Knowledge Across Robots
1112
64
 
1113
- **In Models (as a concern):**
65
+ All robots share the same global memory. What one learns, all can access:
1114
66
 
1115
67
  ```ruby
1116
- # app/models/concerns/memorable.rb
1117
- module Memorable
1118
- extend ActiveSupport::Concern
1119
-
1120
- included do
1121
- after_create :store_in_memory
1122
- after_update :update_memory
1123
- end
1124
-
1125
- def store_in_memory
1126
- memory = HTM.new(robot_name: "Rails-#{Rails.env}")
1127
-
1128
- memory.remember(
1129
- memory_content,
1130
- source: "#{self.class.name}-#{id}"
1131
- )
1132
- end
1133
-
1134
- def update_memory
1135
- # Update existing memory node
1136
- store_in_memory
1137
- end
1138
-
1139
- private
1140
-
1141
- def memory_content
1142
- # Override in model to customize what gets stored
1143
- attributes.to_json
1144
- end
1145
- end
1146
-
1147
- # Then in your model:
1148
- class Article < ApplicationRecord
1149
- include Memorable
68
+ # Claude Desktop remembers a user preference
69
+ claude_desktop.remember("User prefers dark mode and concise responses")
1150
70
 
1151
- private
1152
-
1153
- def memory_content
1154
- "Article: #{title}\n\n#{body}\n\nCategory: #{category}"
1155
- end
1156
- end
71
+ # Later, Claude Code can recall it
72
+ claude_code.recall("user preferences")
73
+ # => "User prefers dark mode and concise responses"
1157
74
  ```
1158
75
 
1159
- ### Multi-Database Configuration (Rails 6+)
76
+ Every tool builds on what came before. No more repeating yourself.
1160
77
 
1161
- If you want to use Rails' built-in multi-database support, you can configure HTM in your `config/database.yml`:
78
+ ### Long-Term Memory Never Forget
1162
79
 
1163
- ```yaml
1164
- # config/database.yml
1165
- production:
1166
- primary:
1167
- <<: *default
1168
- database: myapp_production
80
+ Two-tier architecture ensures nothing is lost:
1169
81
 
1170
- htm:
1171
- adapter: postgresql
1172
- encoding: unicode
1173
- database: <%= ENV['HTM_DBNAME'] || 'htm_production' %>
1174
- host: <%= ENV['HTM_DBHOST'] || 'localhost' %>
1175
- port: <%= ENV['HTM_DBPORT'] || 5432 %>
1176
- username: <%= ENV['HTM_DBUSER'] || 'postgres' %>
1177
- password: <%= ENV['HTM_DBPASS'] %>
1178
- pool: <%= ENV['HTM_DB_POOL_SIZE'] || 10 %>
1179
- ```
82
+ - **Working Memory**: Token-limited context for immediate use
83
+ - **Long-Term Memory**: Durable PostgreSQL storage with vector search
1180
84
 
1181
- Then update your HTM initializer:
85
+ Memories persist forever unless explicitly deleted. Working memory evicts to long-term storage—it never deletes.
1182
86
 
1183
87
  ```ruby
1184
- # config/initializers/htm.rb
1185
- HTM::ActiveRecordConfig.establish_connection!
88
+ htm.remember("PostgreSQL with pgvector handles our vector search")
1186
89
 
1187
- # Or use Rails' connection
1188
- # ActiveRecord::Base.connected_to(role: :writing, shard: :htm) do
1189
- # HTM::ActiveRecordConfig.verify_extensions!
1190
- # end
90
+ # Months later...
91
+ htm.recall("database decisions", timeframe: "last year")
92
+ # => Still there
1191
93
  ```
1192
94
 
1193
- ### Testing with Rails
1194
-
1195
- **In RSpec:**
1196
-
1197
- ```ruby
1198
- # spec/rails_helper.rb
1199
- RSpec.configure do |config|
1200
- config.before(:suite) do
1201
- HTM::ActiveRecordConfig.establish_connection!
1202
- end
1203
-
1204
- config.after(:suite) do
1205
- HTM::ActiveRecordConfig.disconnect!
1206
- end
1207
- end
1208
-
1209
- # spec/features/chat_spec.rb
1210
- RSpec.describe "Chat with memory", type: :feature do
1211
- let(:memory) { HTM.new(robot_name: "TestBot") }
1212
-
1213
- before do
1214
- memory.remember("User prefers Ruby over Python", source: "user")
1215
- end
95
+ ### Robot Groups — High Availability
1216
96
 
1217
- it "recalls user preferences" do
1218
- context = memory.create_context(strategy: :balanced)
1219
- expect(context).to include("Ruby")
1220
- end
1221
- end
1222
- ```
1223
-
1224
- **In Minitest:**
97
+ Coordinate multiple robots with shared working memory and instant failover:
1225
98
 
1226
99
  ```ruby
1227
- # test/test_helper.rb
1228
- class ActiveSupport::TestCase
1229
- setup do
1230
- HTM::ActiveRecordConfig.establish_connection! unless HTM::ActiveRecordConfig.connected?
1231
- end
1232
- end
1233
-
1234
- # test/integration/chat_test.rb
1235
- class ChatTest < ActionDispatch::IntegrationTest
1236
- test "stores chat messages in memory" do
1237
- memory = HTM.new(robot_name: "TestBot")
1238
-
1239
- post chats_path, params: { message: "Hello!" }
1240
-
1241
- assert_response :success
1242
-
1243
- # Verify message was stored
1244
- nodes = memory.recall("Hello", timeframe: "last hour")
1245
- assert_not_empty nodes
1246
- end
1247
- end
1248
- ```
1249
-
1250
- ### Production Considerations
1251
-
1252
- 1. **Connection Pooling**: Set appropriate pool size based on your Rails app's concurrency:
1253
- ```ruby
1254
- ENV['HTM_DB_POOL_SIZE'] = '20' # Match or exceed Rails pool
1255
- ```
1256
-
1257
- 2. **Separate Database Server**: For production, use a dedicated PostgreSQL instance for HTM
1258
-
1259
- 3. **Required Extensions**: Ensure `pgvector` and `pg_trgm` extensions are installed on your PostgreSQL server
1260
-
1261
- 4. **Memory Management**: HTM's working memory is per-instance. Consider using a singleton pattern or Rails cache for shared instances
1262
-
1263
- 5. **Background Processing**: Use Sidekiq or similar for embedding generation if processing large amounts of data
1264
-
1265
- ### Example Rails App Structure
1266
-
1267
- ```
1268
- app/
1269
- ├── controllers/
1270
- │ └── chats_controller.rb # Uses HTM for conversation memory
1271
- ├── jobs/
1272
- │ └── process_document_job.rb # Background HTM processing
1273
- ├── models/
1274
- │ ├── concerns/
1275
- │ │ └── memorable.rb # HTM integration concern
1276
- │ └── article.rb # Includes Memorable
1277
- └── services/
1278
- └── memory_service.rb # Centralized HTM access
1279
-
1280
- config/
1281
- ├── initializers/
1282
- │ └── htm.rb # HTM setup
1283
- └── database.yml # Optional: multi-database config
1284
-
1285
- .env
1286
- HTM_DBURL=postgresql://... # HTM database connection
1287
- OPENAI_API_KEY=sk-... # If using OpenAI embeddings
1288
- ```
1289
-
1290
- ## Environment Variables
1291
-
1292
- HTM uses environment variables for database and service configuration. These can be set in your shell profile, a `.env` file, or exported directly.
1293
-
1294
- ### Database Configuration
1295
-
1296
- #### HTM_DBURL (Recommended)
1297
-
1298
- The preferred method for database configuration is a single connection URL:
1299
-
1300
- ```bash
1301
- export HTM_DBURL="postgresql://username:password@host:port/dbname?sslmode=require"
1302
- ```
1303
-
1304
- **Format:** `postgresql://USER:PASSWORD@HOST:PORT/DATABASE?sslmode=MODE`
1305
-
1306
- **Example for Local PostgreSQL:**
1307
- ```bash
1308
- export HTM_DBURL="postgresql://postgres:postgres@localhost:5432/htm_dev?sslmode=disable"
1309
- ```
1310
-
1311
- #### Individual Database Parameters (Alternative)
1312
-
1313
- If `HTM_DBURL` is not set, HTM will use individual parameters:
1314
-
1315
- | Variable | Description | Default | Required |
1316
- |----------|-------------|---------|----------|
1317
- | `HTM_DBNAME` | Database name | None | Yes |
1318
- | `HTM_DBUSER` | Database username | None | Yes |
1319
- | `HTM_DBPASS` | Database password | None | Yes |
1320
- | `HTM_DBHOST` | Database hostname or IP | `localhost` | No |
1321
- | `HTM_DBPORT` | Database port | `5432` | No |
1322
- | `HTM_SERVICE_NAME` | Service identifier (informational only) | None | No |
100
+ group = HTM::RobotGroup.new(
101
+ name: 'support-ha',
102
+ active: ['primary'],
103
+ passive: ['standby']
104
+ )
1323
105
 
1324
- **Example:**
1325
- ```bash
1326
- export HTM_DBNAME="htm_production"
1327
- export HTM_DBUSER="postgres"
1328
- export HTM_DBPASS="secure_password_here"
1329
- export HTM_DBHOST="localhost"
1330
- export HTM_DBPORT="5432"
1331
- export HTM_SERVICE_NAME="production-db"
106
+ # Primary fails? Standby takes over instantly
107
+ group.failover!
1332
108
  ```
1333
109
 
1334
- **Configuration Priority:**
1335
- 1. `HTM_DBURL` (if set, this takes precedence)
1336
- 2. Individual parameters (`HTM_DBNAME`, `HTM_DBUSER`, etc.)
1337
- 3. Direct configuration passed to `HTM.new(db_config: {...})`
1338
-
1339
- ### LLM Provider Configuration
1340
-
1341
- HTM configuration is done programmatically via `HTM.configure` (see [Configuration](#configuration) section). However, a few environment variables are still used:
1342
-
1343
- #### OPENAI_API_KEY
110
+ Real-time synchronization via PostgreSQL LISTEN/NOTIFY. Add or remove robots dynamically.
1344
111
 
1345
- Required when using OpenAI as your embedding or tag extraction provider:
112
+ ## Quick Start
1346
113
 
1347
114
  ```bash
1348
- export OPENAI_API_KEY="sk-proj-your-api-key-here"
115
+ gem install htm
1349
116
  ```
1350
117
 
1351
- This is required when you configure:
1352
118
  ```ruby
1353
- HTM.configure do |config|
1354
- config.embedding_provider = :openai
1355
- # or
1356
- config.tag_provider = :openai
1357
- end
1358
- ```
1359
-
1360
- #### OLLAMA_URL
1361
-
1362
- Custom Ollama server URL (optional):
1363
-
1364
- ```bash
1365
- export OLLAMA_URL="http://localhost:11434" # Default
1366
- export OLLAMA_URL="http://ollama-server:11434" # Custom
1367
- ```
1368
-
1369
- HTM defaults to `http://localhost:11434` if not set.
1370
-
1371
- #### HTM_LOG_LEVEL
1372
-
1373
- Control logging verbosity:
1374
-
1375
- ```bash
1376
- export HTM_LOG_LEVEL="DEBUG" # DEBUG, INFO, WARN, ERROR, FATAL
1377
- export HTM_LOG_LEVEL="INFO" # Default
1378
- ```
1379
-
1380
- This is used by the default logger when `HTM.configure` is called without a custom logger.
1381
-
1382
- #### HTM_TELEMETRY_ENABLED
1383
-
1384
- Enable OpenTelemetry metrics collection:
1385
-
1386
- ```bash
1387
- export HTM_TELEMETRY_ENABLED="true" # Enable telemetry
1388
- export HTM_TELEMETRY_ENABLED="false" # Disable (default)
1389
- ```
1390
-
1391
- When enabled, HTM emits metrics to configured OpenTelemetry collectors. See [Telemetry](#telemetry-opentelemetry) for details.
1392
-
1393
- ### Quick Setup Examples
1394
-
1395
- #### Local Development (PostgreSQL)
1396
-
1397
- ```bash
1398
- # Simple local setup
1399
- export HTM_DBURL="postgresql://postgres:postgres@localhost:5432/htm_dev?sslmode=disable"
1400
-
1401
- # With custom user
1402
- export HTM_DBURL="postgresql://myuser:mypass@localhost:5432/htm_dev"
1403
- ```
1404
-
1405
- #### With OpenAI Embeddings
1406
-
1407
- ```bash
1408
- # Database + OpenAI
1409
- export HTM_DBURL="postgresql://user:pass@host:port/db?sslmode=require"
1410
- export OPENAI_API_KEY="sk-proj-your-api-key-here"
1411
- ```
1412
-
1413
- ### Persistent Configuration
1414
-
1415
- To make environment variables permanent, add them to your shell profile:
1416
-
1417
- **For Bash (~/.bashrc or ~/.bash_profile):**
1418
- ```bash
1419
- echo 'export HTM_DBURL="postgresql://user:pass@host:port/db?sslmode=require"' >> ~/.bashrc
1420
- source ~/.bashrc
1421
- ```
1422
-
1423
- **For Zsh (~/.zshrc):**
1424
- ```bash
1425
- echo 'export HTM_DBURL="postgresql://user:pass@host:port/db?sslmode=require"' >> ~/.zshrc
1426
- source ~/.zshrc
1427
- ```
1428
-
1429
- **Or create a dedicated file (~/.bashrc__htm):**
1430
- ```bash
1431
- # ~/.bashrc__htm
1432
- export HTM_DBURL="postgresql://user:pass@host:port/db?sslmode=require"
1433
- export OPENAI_API_KEY="sk-proj-your-api-key"
1434
- export HTM_SERVICE_NAME="my-service"
1435
-
1436
- # Then source it from your main profile
1437
- echo 'source ~/.bashrc__htm' >> ~/.bashrc
1438
- ```
1439
-
1440
- ### Verification
1441
-
1442
- Verify your configuration:
1443
-
1444
- ```bash
1445
- # Check database connection
1446
- ruby test_connection.rb
1447
-
1448
- # Or in Ruby
1449
- irb -r ./lib/htm
1450
- > HTM::Database.default_config
1451
- > # Should return a hash with your connection parameters
1452
- ```
1453
-
1454
- ## Development
1455
-
1456
- After checking out the repo, run:
1457
-
1458
- ```bash
1459
- # Install dependencies
1460
- bundle install
1461
-
1462
- # Enable direnv (loads .envrc environment variables)
1463
- direnv allow
1464
-
1465
- # Run tests
1466
- rake test
1467
-
1468
- # Run example
1469
- ruby examples/basic_usage.rb
1470
- ```
1471
-
1472
- **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:
1473
-
1474
- ```bash
1475
- export HTM_DBURL="postgresql://user:password@host:port/dbname?sslmode=require"
1476
- ```
1477
-
1478
- ### HTM Rake Tasks Reference
1479
-
1480
- HTM provides comprehensive rake tasks for database management, documentation, file loading, job processing, and tag management. Use `rake -T htm` to see all available tasks.
1481
-
1482
- #### Database Tasks (`htm:db:*`)
1483
-
1484
- | Task | Description |
1485
- |------|-------------|
1486
- | `rake htm:db:setup` | Set up HTM database schema and run migrations (sets up from scratch) |
1487
- | `rake htm:db:create` | Create database if it doesn't exist (respects RAILS_ENV) |
1488
- | `rake htm:db:migrate` | Run pending database migrations |
1489
- | `rake htm:db:status` | Show migration status |
1490
- | `rake htm:db:verify` | Verify database connection (respects RAILS_ENV) |
1491
- | `rake htm:db:info` | Show database info (size, tables, extensions) |
1492
- | `rake htm:db:stats` | Show record counts for all HTM tables |
1493
- | `rake htm:db:console` | Open PostgreSQL console (respects RAILS_ENV) |
1494
- | `rake htm:db:seed` | Seed database with sample data |
1495
- | `rake htm:db:schema:dump` | Dump current schema to db/schema.sql |
1496
- | `rake htm:db:schema:load` | Load schema from db/schema.sql |
1497
- | `rake htm:db:rebuild:embeddings` | Rebuild embeddings for all nodes |
1498
- | `rake htm:db:rebuild:propositions` | Rebuild propositions for all non-proposition nodes |
1499
- | `rake htm:db:tags:cleanup` | Soft delete orphaned tags and stale node_tags entries |
1500
- | `rake htm:db:drop` | Drop all HTM tables (WARNING: destructive!) |
1501
- | `rake htm:db:reset` | Drop and recreate database (WARNING: destructive!) |
1502
-
1503
- #### Documentation Tasks (`htm:doc:*`)
1504
-
1505
- | Task | Description |
1506
- |------|-------------|
1507
- | `rake htm:doc:all` | Generate DB docs, YARD API docs, build site, and serve |
1508
- | `rake htm:doc:yard` | Build YARD API documentation (markdown format for MkDocs) |
1509
- | `rake htm:doc:db` | Generate/update database documentation in docs/database/ |
1510
- | `rake htm:doc:build` | Build documentation site with MkDocs |
1511
- | `rake htm:doc:serve` | Serve documentation site locally with MkDocs |
1512
- | `rake htm:doc:server[port]` | Start YARD documentation server (live reload) |
1513
- | `rake htm:doc:fix_anchors` | Fix YARD anchor links for MkDocs compatibility |
1514
- | `rake htm:doc:stats` | Show documentation coverage statistics |
1515
- | `rake htm:doc:clean` | Clean generated documentation |
1516
-
1517
- #### File Loading Tasks (`htm:files:*`)
1518
-
1519
- | Task | Description |
1520
- |------|-------------|
1521
- | `rake htm:files:load[path]` | Load a markdown file into long-term memory |
1522
- | `rake htm:files:load_dir[path,pattern]` | Load all markdown files from a directory |
1523
- | `rake htm:files:list` | List all loaded file sources |
1524
- | `rake htm:files:info[path]` | Show details for a loaded file |
1525
- | `rake htm:files:sync` | Sync all loaded files (reload changed files) |
1526
- | `rake htm:files:unload[path]` | Unload a file from memory |
1527
- | `rake htm:files:stats` | Show file loading statistics |
1528
-
1529
- #### Job Processing Tasks (`htm:jobs:*`)
1530
-
1531
- | Task | Description |
1532
- |------|-------------|
1533
- | `rake htm:jobs:stats` | Show statistics for nodes and async job processing |
1534
- | `rake htm:jobs:process_embeddings` | Process pending embedding jobs for nodes without embeddings |
1535
- | `rake htm:jobs:process_tags` | Process pending tag extraction jobs for nodes without tags |
1536
- | `rake htm:jobs:process_all` | Process all pending jobs (embeddings and tags) |
1537
- | `rake htm:jobs:reprocess_embeddings` | Reprocess embeddings for all nodes (force regeneration) |
1538
- | `rake htm:jobs:failed` | Show nodes that failed async processing |
1539
- | `rake htm:jobs:clear_all` | Clear all embeddings and tags (for testing/development) |
1540
-
1541
- #### Tag Management Tasks (`htm:tags:*`)
1542
-
1543
- | Task | Description |
1544
- |------|-------------|
1545
- | `rake htm:tags:tree[prefix]` | Display tags as a hierarchical tree (text format) |
1546
- | `rake htm:tags:mermaid[prefix]` | Export tags as Mermaid flowchart to tags.md |
1547
- | `rake htm:tags:svg[prefix]` | Export tags as SVG visualization to tags.svg |
1548
- | `rake htm:tags:export[prefix]` | Export tags in all formats (tags.txt, tags.md, tags.svg) |
1549
- | `rake htm:tags:rebuild` | Rebuild all tags from node content |
1550
-
1551
- ### Common Workflows
1552
-
1553
- ```bash
1554
- # Initial setup (first time)
1555
- direnv allow
1556
- rake htm:db:setup # Creates schema and runs migrations
1557
-
1558
- # After pulling new migrations
1559
- rake htm:db:migrate # Run pending migrations
1560
-
1561
- # Check database state
1562
- rake htm:db:status # See migration status
1563
- rake htm:db:info # See database details
1564
- rake htm:db:stats # See record counts
1565
-
1566
- # Monitor async processing
1567
- rake htm:jobs:stats
1568
- rake htm:jobs:failed # Show stuck jobs
1569
-
1570
- # Process pending jobs manually
1571
- rake htm:jobs:process_all
1572
-
1573
- # Generate documentation
1574
- rake htm:doc:all # Full documentation build
1575
- rake htm:doc:serve # Local preview
1576
-
1577
- # Load files into memory
1578
- rake 'htm:files:load[docs/guide.md]'
1579
- rake 'htm:files:load_dir[docs/,**/*.md]'
1580
-
1581
- # View tag hierarchy
1582
- rake htm:tags:tree
1583
- rake 'htm:tags:svg[database]' # Export filtered tags
1584
-
1585
- # Reset database (development only!)
1586
- rake htm:db:reset # Drops and recreates everything
1587
-
1588
- # Open database console for debugging
1589
- rake htm:db:console # Opens psql
1590
- ```
1591
-
1592
- See the [Async Job Processing](#async-job-processing) section for more details on background jobs.
1593
-
1594
- ## Testing
119
+ require 'htm'
1595
120
 
1596
- HTM uses Minitest:
121
+ # Initialize
122
+ htm = HTM.new(robot_name: "MyAssistant")
1597
123
 
1598
- ```bash
1599
- # Run all tests
1600
- rake test
124
+ # Remember
125
+ htm.remember("User's project uses Rails 7 with PostgreSQL")
1601
126
 
1602
- # Run specific test file
1603
- ruby test/htm_test.rb
127
+ # Recall
128
+ memories = htm.recall("tech stack", timeframe: "last month")
1604
129
  ```
1605
130
 
1606
- ## Architecture
1607
-
1608
- See [htm_teamwork.md](htm_teamwork.md) for detailed design documentation and planning notes.
1609
-
1610
- ### Key Components
1611
-
1612
- - **HTM**: Main API class that coordinates all components and provides the primary interface
1613
- - **Configuration**: Dependency injection for LLM providers (embedding_generator, tag_extractor, token_counter, logger)
1614
- - **WorkingMemory**: In-memory, token-limited active context for immediate LLM use
1615
- - **LongTermMemory**: PostgreSQL-backed permanent storage with connection pooling
1616
- - **EmbeddingService**: Processing and validation layer for vector embeddings (calls configured `embedding_generator`)
1617
- - **TagService**: Processing and validation layer for hierarchical tags (calls configured `tag_extractor`)
1618
- - **Database**: Schema setup, migrations, and management
1619
- - **Background Jobs**: Async processing for embedding generation and tag extraction
131
+ For MCP integration, database setup, and configuration options, see the [full documentation](https://madbomber.github.io/htm).
1620
132
 
1621
- ### Database Schema
133
+ ## Features at a Glance
1622
134
 
1623
- - `robots`: Robot registry for all LLM agents using HTM
1624
- - `nodes`: Main memory storage with vector embeddings (pgvector), full-text search (tsvector), JSONB metadata
1625
- - `tags`: Hierarchical tag ontology (format: `root:level1:level2:level3`)
1626
- - `node_tags`: Join table implementing many-to-many relationship between nodes and tags
135
+ | Feature | Description |
136
+ |---------|-------------|
137
+ | **MCP Server** | 23 tools for AI assistant integration |
138
+ | **Hive Mind** | Shared memory across all robots |
139
+ | **Vector Search** | Semantic retrieval with pgvector |
140
+ | **Hybrid Search** | Combines vector + full-text matching |
141
+ | **Temporal Queries** | "last week", "yesterday", date ranges |
142
+ | **Auto-Tagging** | LLM extracts hierarchical tags automatically |
143
+ | **Robot Groups** | High-availability with failover |
144
+ | **Rails Integration** | Auto-configures via Railtie, uses ActiveJob |
145
+ | **Telemetry** | Optional OpenTelemetry metrics |
1627
146
 
1628
- **Nodes Table Key Columns:**
1629
- - `content`: The memory content
1630
- - `embedding`: Vector embedding for semantic search (up to 2000 dimensions)
1631
- - `metadata`: JSONB column for arbitrary key-value data (filterable via `@>` containment operator)
1632
- - `content_hash`: SHA-256 hash for deduplication
147
+ ## Requirements
1633
148
 
1634
- ### Service Architecture
149
+ - Ruby 3.0+
150
+ - PostgreSQL with pgvector and pg_trgm extensions
151
+ - Ollama (default) or any local/private provider for embeddings and classifications
1635
152
 
1636
- HTM uses a layered architecture for LLM integration:
153
+ ## Documentation
1637
154
 
1638
- 1. **Configuration Layer** (`HTM.configure`): Provides raw LLM access via `embedding_generator` and `tag_extractor` callables
1639
- 2. **Service Layer** (`EmbeddingService`, `TagService`): Processes and validates LLM responses, handles padding/truncation, formats for storage
1640
- 3. **Consumer Layer** (`GenerateEmbeddingJob`, `GenerateTagsJob`, rake tasks): Uses services for async or synchronous processing
1641
-
1642
- This separation allows you to provide any LLM implementation while HTM handles response processing, validation, and storage.
1643
-
1644
- ## Roadmap
1645
-
1646
- - [x] Phase 1: Foundation (basic two-tier memory)
1647
- - [x] Phase 2: RAG retrieval (semantic search)
1648
- - [x] Phase 3: Relationships & tags
1649
- - [x] Phase 4: Working memory management
1650
- - [x] Phase 5: Hive mind features
1651
- - [x] Phase 6: Operations & observability
1652
- - [x] Phase 7: Advanced features
1653
- - [x] Phase 8: Production-ready gem
155
+ **[https://madbomber.github.io/htm](https://madbomber.github.io/htm)**
1654
156
 
157
+ - [Installation & Setup](https://madbomber.github.io/htm/getting-started/)
158
+ - [MCP Server Guide](https://madbomber.github.io/htm/guides/mcp-server/)
159
+ - [Rails Integration](https://madbomber.github.io/htm/guides/rails/)
160
+ - [API Reference](https://madbomber.github.io/htm/api/)
1655
161
 
1656
162
  ## Why "Robots" Instead of "Agents"?
1657
163
 
@@ -1677,7 +183,6 @@ HTM uses "robots" rather than the fashionable "agents" for several reasons:
1677
183
 
1678
184
  Honest terminology leads to clearer thinking. These are robots: tireless workers with perfect memory, executing instructions on our behalf. That's exactly what HTM helps them do better.
1679
185
 
1680
-
1681
186
  ## Contributing
1682
187
 
1683
188
  Bug reports and pull requests are welcome on GitHub at https://github.com/madbomber/htm.