htm 0.0.1 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.aigcm_msg +1 -0
- data/.architecture/reviews/comprehensive-codebase-review.md +577 -0
- data/.claude/settings.local.json +92 -0
- data/.envrc +1 -0
- data/.irbrc +283 -80
- data/.tbls.yml +31 -0
- data/CHANGELOG.md +314 -16
- data/CLAUDE.md +603 -0
- data/README.md +76 -5
- data/Rakefile +5 -0
- data/SETUP.md +132 -101
- data/db/migrate/{20250101000001_enable_extensions.rb → 00001_enable_extensions.rb} +0 -1
- data/db/migrate/00002_create_robots.rb +11 -0
- data/db/migrate/00003_create_file_sources.rb +20 -0
- data/db/migrate/00004_create_nodes.rb +65 -0
- data/db/migrate/00005_create_tags.rb +13 -0
- data/db/migrate/00006_create_node_tags.rb +18 -0
- data/db/migrate/00007_create_robot_nodes.rb +26 -0
- data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +12 -0
- data/db/schema.sql +390 -36
- data/docs/api/database.md +19 -232
- data/docs/api/embedding-service.md +1 -7
- data/docs/api/htm.md +305 -364
- data/docs/api/index.md +1 -7
- data/docs/api/long-term-memory.md +342 -590
- data/docs/api/yard/HTM/ActiveRecordConfig.md +23 -0
- data/docs/api/yard/HTM/AuthorizationError.md +11 -0
- data/docs/api/yard/HTM/CircuitBreaker.md +92 -0
- data/docs/api/yard/HTM/CircuitBreakerOpenError.md +34 -0
- data/docs/api/yard/HTM/Configuration.md +175 -0
- data/docs/api/yard/HTM/Database.md +99 -0
- data/docs/api/yard/HTM/DatabaseError.md +14 -0
- data/docs/api/yard/HTM/EmbeddingError.md +18 -0
- data/docs/api/yard/HTM/EmbeddingService.md +58 -0
- data/docs/api/yard/HTM/Error.md +11 -0
- data/docs/api/yard/HTM/JobAdapter.md +39 -0
- data/docs/api/yard/HTM/LongTermMemory.md +342 -0
- data/docs/api/yard/HTM/NotFoundError.md +17 -0
- data/docs/api/yard/HTM/Observability.md +107 -0
- data/docs/api/yard/HTM/QueryTimeoutError.md +19 -0
- data/docs/api/yard/HTM/Railtie.md +27 -0
- data/docs/api/yard/HTM/ResourceExhaustedError.md +13 -0
- data/docs/api/yard/HTM/TagError.md +18 -0
- data/docs/api/yard/HTM/TagService.md +67 -0
- data/docs/api/yard/HTM/Timeframe/Result.md +24 -0
- data/docs/api/yard/HTM/Timeframe.md +40 -0
- data/docs/api/yard/HTM/TimeframeExtractor/Result.md +24 -0
- data/docs/api/yard/HTM/TimeframeExtractor.md +45 -0
- data/docs/api/yard/HTM/ValidationError.md +20 -0
- data/docs/api/yard/HTM/WorkingMemory.md +131 -0
- data/docs/api/yard/HTM.md +80 -0
- data/docs/api/yard/index.csv +179 -0
- data/docs/api/yard-reference.md +51 -0
- data/docs/architecture/adrs/001-postgresql-timescaledb.md +1 -1
- data/docs/architecture/adrs/003-ollama-embeddings.md +1 -1
- data/docs/architecture/adrs/010-redis-working-memory-rejected.md +2 -27
- data/docs/architecture/adrs/index.md +2 -13
- data/docs/architecture/hive-mind.md +165 -166
- data/docs/architecture/index.md +2 -2
- data/docs/architecture/overview.md +5 -171
- data/docs/architecture/two-tier-memory.md +1 -35
- data/docs/assets/images/adr-010-current-architecture.svg +37 -0
- data/docs/assets/images/adr-010-proposed-architecture.svg +48 -0
- data/docs/assets/images/adr-dependency-tree.svg +93 -0
- data/docs/assets/images/class-hierarchy.svg +55 -0
- data/docs/assets/images/exception-hierarchy.svg +45 -0
- data/docs/assets/images/htm-architecture-overview.svg +83 -0
- data/docs/assets/images/htm-complete-memory-flow.svg +160 -0
- data/docs/assets/images/htm-context-assembly-flow.svg +148 -0
- data/docs/assets/images/htm-eviction-process.svg +141 -0
- data/docs/assets/images/htm-memory-addition-flow.svg +138 -0
- data/docs/assets/images/htm-memory-recall-flow.svg +152 -0
- data/docs/assets/images/htm-node-states.svg +123 -0
- data/docs/assets/images/project-structure.svg +78 -0
- data/docs/assets/images/test-directory-structure.svg +38 -0
- data/{dbdoc → docs/database}/README.md +127 -125
- data/docs/database/public.file_sources.md +42 -0
- data/docs/database/public.file_sources.svg +211 -0
- data/{dbdoc → docs/database}/public.node_tags.md +7 -8
- data/docs/database/public.node_tags.svg +239 -0
- data/{dbdoc → docs/database}/public.nodes.md +22 -17
- data/docs/database/public.nodes.svg +271 -0
- data/docs/database/public.robot_nodes.md +46 -0
- data/docs/database/public.robot_nodes.svg +243 -0
- data/{dbdoc → docs/database}/public.robots.md +2 -3
- data/docs/database/public.robots.svg +161 -0
- data/docs/database/public.tags.svg +139 -0
- data/{dbdoc → docs/database}/schema.json +941 -630
- data/docs/database/schema.svg +282 -0
- data/docs/development/index.md +1 -29
- data/docs/development/schema.md +134 -309
- data/docs/development/testing.md +1 -9
- data/docs/getting-started/index.md +47 -0
- data/docs/{installation.md → getting-started/installation.md} +2 -2
- data/docs/{quick-start.md → getting-started/quick-start.md} +5 -5
- data/docs/guides/adding-memories.md +295 -643
- data/docs/guides/recalling-memories.md +36 -1
- data/docs/guides/search-strategies.md +85 -51
- data/docs/images/htm-er-diagram.svg +156 -0
- data/docs/index.md +16 -31
- data/docs/multi_framework_support.md +4 -4
- data/examples/README.md +280 -0
- data/examples/basic_usage.rb +18 -16
- data/examples/cli_app/htm_cli.rb +146 -8
- data/examples/cli_app/temp.log +93 -0
- data/examples/custom_llm_configuration.rb +1 -2
- data/examples/example_app/app.rb +11 -14
- data/examples/file_loader_usage.rb +177 -0
- data/examples/robot_groups/lib/robot_group.rb +419 -0
- data/examples/robot_groups/lib/working_memory_channel.rb +140 -0
- data/examples/robot_groups/multi_process.rb +286 -0
- data/examples/robot_groups/robot_worker.rb +136 -0
- data/examples/robot_groups/same_process.rb +229 -0
- data/examples/sinatra_app/Gemfile +1 -0
- data/examples/sinatra_app/Gemfile.lock +166 -0
- data/examples/sinatra_app/app.rb +219 -24
- data/examples/timeframe_demo.rb +276 -0
- data/lib/htm/active_record_config.rb +10 -3
- data/lib/htm/circuit_breaker.rb +202 -0
- data/lib/htm/configuration.rb +313 -80
- data/lib/htm/database.rb +67 -36
- data/lib/htm/embedding_service.rb +39 -2
- data/lib/htm/errors.rb +131 -11
- data/lib/htm/{sinatra.rb → integrations/sinatra.rb} +87 -12
- data/lib/htm/job_adapter.rb +10 -3
- data/lib/htm/jobs/generate_embedding_job.rb +5 -4
- data/lib/htm/jobs/generate_tags_job.rb +4 -0
- data/lib/htm/loaders/markdown_loader.rb +263 -0
- data/lib/htm/loaders/paragraph_chunker.rb +112 -0
- data/lib/htm/long_term_memory.rb +601 -321
- data/lib/htm/models/file_source.rb +99 -0
- data/lib/htm/models/node.rb +116 -12
- data/lib/htm/models/robot.rb +53 -4
- data/lib/htm/models/robot_node.rb +51 -0
- data/lib/htm/models/tag.rb +302 -0
- data/lib/htm/observability.rb +395 -0
- data/lib/htm/tag_service.rb +60 -3
- data/lib/htm/tasks.rb +29 -0
- data/lib/htm/timeframe.rb +194 -0
- data/lib/htm/timeframe_extractor.rb +307 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm/working_memory.rb +165 -70
- data/lib/htm.rb +352 -133
- data/lib/tasks/doc.rake +300 -0
- data/lib/tasks/files.rake +299 -0
- data/lib/tasks/htm.rake +188 -2
- data/lib/tasks/jobs.rake +10 -12
- data/lib/tasks/tags.rake +194 -0
- data/mkdocs.yml +91 -9
- data/notes/ARCHITECTURE_REVIEW.md +1167 -0
- data/notes/IMPLEMENTATION_SUMMARY.md +606 -0
- data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +451 -0
- data/notes/next_steps.md +100 -0
- data/notes/plan.md +627 -0
- data/notes/tag_ontology_enhancement_ideas.md +222 -0
- data/notes/timescaledb_removal_summary.md +200 -0
- metadata +177 -37
- data/db/migrate/20250101000002_create_robots.rb +0 -14
- data/db/migrate/20250101000003_create_nodes.rb +0 -42
- data/db/migrate/20250101000005_create_tags.rb +0 -38
- data/db/migrate/20250101000007_add_node_vector_indexes.rb +0 -30
- data/dbdoc/public.node_tags.svg +0 -112
- data/dbdoc/public.nodes.svg +0 -118
- data/dbdoc/public.robots.svg +0 -90
- data/dbdoc/public.tags.svg +0 -60
- data/dbdoc/schema.svg +0 -154
- data/{dbdoc → docs/database}/public.node_stats.md +0 -0
- data/{dbdoc → docs/database}/public.node_stats.svg +0 -0
- data/{dbdoc → docs/database}/public.nodes_tags.md +0 -0
- data/{dbdoc → docs/database}/public.nodes_tags.svg +0 -0
- data/{dbdoc → docs/database}/public.ontology_structure.md +0 -0
- data/{dbdoc → docs/database}/public.ontology_structure.svg +0 -0
- data/{dbdoc → docs/database}/public.operations_log.md +0 -0
- data/{dbdoc → docs/database}/public.operations_log.svg +0 -0
- data/{dbdoc → docs/database}/public.relationships.md +0 -0
- data/{dbdoc → docs/database}/public.relationships.svg +0 -0
- data/{dbdoc → docs/database}/public.robot_activity.md +0 -0
- data/{dbdoc → docs/database}/public.robot_activity.svg +0 -0
- data/{dbdoc → docs/database}/public.schema_migrations.md +0 -0
- data/{dbdoc → docs/database}/public.schema_migrations.svg +0 -0
- data/{dbdoc → docs/database}/public.tags.md +3 -3
- /data/{dbdoc → docs/database}/public.topic_relationships.md +0 -0
- /data/{dbdoc → docs/database}/public.topic_relationships.svg +0 -0
data/docs/api/htm.md
CHANGED
|
@@ -7,15 +7,15 @@ The main interface for HTM's intelligent memory management system.
|
|
|
7
7
|
`HTM` is the primary class that orchestrates the two-tier memory system:
|
|
8
8
|
|
|
9
9
|
- **Working Memory**: Token-limited active context for immediate LLM use
|
|
10
|
-
- **Long-term Memory**: Durable PostgreSQL storage
|
|
10
|
+
- **Long-term Memory**: Durable PostgreSQL storage with vector embeddings
|
|
11
11
|
|
|
12
12
|
Key features:
|
|
13
13
|
|
|
14
14
|
- Never forgets unless explicitly told (`forget`)
|
|
15
15
|
- RAG-based retrieval (temporal + semantic search)
|
|
16
|
-
- Multi-robot "hive mind" - all robots share global memory
|
|
17
|
-
-
|
|
18
|
-
-
|
|
16
|
+
- Multi-robot "hive mind" - all robots share global memory via content deduplication
|
|
17
|
+
- Hierarchical tagging for knowledge organization
|
|
18
|
+
- Tag-enhanced hybrid search for improved relevance
|
|
19
19
|
|
|
20
20
|
## Class Definition
|
|
21
21
|
|
|
@@ -34,11 +34,12 @@ Create a new HTM instance.
|
|
|
34
34
|
```ruby
|
|
35
35
|
HTM.new(
|
|
36
36
|
working_memory_size: 128_000,
|
|
37
|
-
robot_id: nil,
|
|
38
37
|
robot_name: nil,
|
|
39
38
|
db_config: nil,
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
db_pool_size: 5,
|
|
40
|
+
db_query_timeout: 30_000,
|
|
41
|
+
db_cache_size: 1000,
|
|
42
|
+
db_cache_ttl: 300
|
|
42
43
|
)
|
|
43
44
|
```
|
|
44
45
|
|
|
@@ -47,11 +48,12 @@ HTM.new(
|
|
|
47
48
|
| Parameter | Type | Default | Description |
|
|
48
49
|
|-----------|------|---------|-------------|
|
|
49
50
|
| `working_memory_size` | Integer | `128_000` | Maximum tokens for working memory |
|
|
50
|
-
| `
|
|
51
|
-
| `robot_name` | String, nil | `"robot_#{id[0..7]}"` | Human-readable name |
|
|
51
|
+
| `robot_name` | String, nil | `"robot_#{uuid[0..7]}"` | Human-readable name |
|
|
52
52
|
| `db_config` | Hash, nil | From `ENV['HTM_DBURL']` | Database configuration |
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
53
|
+
| `db_pool_size` | Integer | `5` | Database connection pool size |
|
|
54
|
+
| `db_query_timeout` | Integer | `30_000` | Query timeout in milliseconds |
|
|
55
|
+
| `db_cache_size` | Integer | `1000` | Query cache size (0 to disable) |
|
|
56
|
+
| `db_cache_ttl` | Integer | `300` | Cache TTL in seconds |
|
|
55
57
|
|
|
56
58
|
#### Returns
|
|
57
59
|
|
|
@@ -69,14 +71,7 @@ htm = HTM.new(
|
|
|
69
71
|
working_memory_size: 256_000
|
|
70
72
|
)
|
|
71
73
|
|
|
72
|
-
#
|
|
73
|
-
htm = HTM.new(
|
|
74
|
-
robot_name: "Research Bot",
|
|
75
|
-
embedding_service: :openai,
|
|
76
|
-
embedding_model: 'text-embedding-3-small'
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
# Custom database
|
|
74
|
+
# Custom database configuration
|
|
80
75
|
htm = HTM.new(
|
|
81
76
|
db_config: {
|
|
82
77
|
host: 'localhost',
|
|
@@ -86,6 +81,12 @@ htm = HTM.new(
|
|
|
86
81
|
password: 'secret'
|
|
87
82
|
}
|
|
88
83
|
)
|
|
84
|
+
|
|
85
|
+
# With caching disabled
|
|
86
|
+
htm = HTM.new(
|
|
87
|
+
robot_name: "No Cache Bot",
|
|
88
|
+
db_cache_size: 0
|
|
89
|
+
)
|
|
89
90
|
```
|
|
90
91
|
|
|
91
92
|
---
|
|
@@ -94,13 +95,13 @@ htm = HTM.new(
|
|
|
94
95
|
|
|
95
96
|
### `robot_id` {: #robot_id }
|
|
96
97
|
|
|
97
|
-
Unique identifier for this robot instance.
|
|
98
|
+
Unique integer identifier for this robot instance.
|
|
98
99
|
|
|
99
|
-
- **Type**:
|
|
100
|
+
- **Type**: Integer
|
|
100
101
|
- **Read-only**: Yes
|
|
101
102
|
|
|
102
103
|
```ruby
|
|
103
|
-
htm.robot_id # =>
|
|
104
|
+
htm.robot_id # => 42
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
### `robot_name` {: #robot_name }
|
|
@@ -140,106 +141,102 @@ htm.long_term_memory.stats # => {...}
|
|
|
140
141
|
|
|
141
142
|
## Public Methods
|
|
142
143
|
|
|
143
|
-
### `
|
|
144
|
+
### `remember(content, tags:, metadata:)` {: #remember }
|
|
144
145
|
|
|
145
|
-
|
|
146
|
+
Remember new information by storing it in long-term memory.
|
|
146
147
|
|
|
147
148
|
```ruby
|
|
148
|
-
|
|
149
|
-
type: nil,
|
|
150
|
-
category: nil,
|
|
151
|
-
importance: 1.0,
|
|
152
|
-
related_to: [],
|
|
153
|
-
tags: []
|
|
154
|
-
)
|
|
149
|
+
remember(content, tags: [], metadata: {})
|
|
155
150
|
```
|
|
156
151
|
|
|
157
152
|
#### Parameters
|
|
158
153
|
|
|
159
154
|
| Parameter | Type | Default | Description |
|
|
160
155
|
|-----------|------|---------|-------------|
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `category` | String, nil | `nil` | Optional category for organization |
|
|
165
|
-
| `importance` | Float | `1.0` | Importance score (0.0-10.0) |
|
|
166
|
-
| `related_to` | Array\<String\> | `[]` | Keys of related nodes |
|
|
167
|
-
| `tags` | Array\<String\> | `[]` | Tags for categorization |
|
|
156
|
+
| `content` | String | *required* | The information to remember |
|
|
157
|
+
| `tags` | Array\<String\> | `[]` | Manual tags to assign (in addition to auto-extracted tags) |
|
|
158
|
+
| `metadata` | Hash | `{}` | Arbitrary key-value metadata stored as JSONB. Keys must be strings or symbols. |
|
|
168
159
|
|
|
169
160
|
#### Returns
|
|
170
161
|
|
|
171
|
-
- `Integer` - Database ID of the
|
|
162
|
+
- `Integer` - Database ID of the memory node
|
|
172
163
|
|
|
173
164
|
#### Side Effects
|
|
174
165
|
|
|
175
|
-
- Stores node in PostgreSQL with
|
|
166
|
+
- Stores node in PostgreSQL with content deduplication (via SHA-256 hash)
|
|
167
|
+
- Creates/updates `robot_nodes` association for this robot
|
|
176
168
|
- Adds node to working memory (evicts if needed)
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
- Logs operation to `operations_log` table
|
|
169
|
+
- Enqueues background job for embedding generation (new nodes only)
|
|
170
|
+
- Enqueues background job for tag extraction (new nodes only)
|
|
180
171
|
- Updates robot activity timestamp
|
|
181
172
|
|
|
173
|
+
#### Content Deduplication
|
|
174
|
+
|
|
175
|
+
When `remember()` is called:
|
|
176
|
+
|
|
177
|
+
1. A SHA-256 hash of the content is computed
|
|
178
|
+
2. If a node with the same hash exists, the existing node is reused
|
|
179
|
+
3. A new `robot_nodes` association is created (or `remember_count` is incremented)
|
|
180
|
+
4. This ensures identical memories are stored once but can be "remembered" by multiple robots
|
|
181
|
+
|
|
182
182
|
#### Examples
|
|
183
183
|
|
|
184
184
|
```ruby
|
|
185
|
-
#
|
|
186
|
-
htm.
|
|
187
|
-
|
|
188
|
-
#
|
|
189
|
-
htm.
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
type: :decision,
|
|
193
|
-
importance: 9.0,
|
|
194
|
-
tags: ["architecture", "api", "gateway"],
|
|
195
|
-
related_to: ["microservices_architecture"]
|
|
185
|
+
# Basic usage
|
|
186
|
+
node_id = htm.remember("PostgreSQL supports vector similarity search via pgvector")
|
|
187
|
+
|
|
188
|
+
# With manual tags
|
|
189
|
+
node_id = htm.remember(
|
|
190
|
+
"Time-series data works great with hypertables",
|
|
191
|
+
tags: ["database:timescaledb", "performance"]
|
|
196
192
|
)
|
|
197
193
|
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
end
|
|
203
|
-
RUBY
|
|
204
|
-
|
|
205
|
-
htm.add_node(
|
|
206
|
-
"total_calculation_v1",
|
|
207
|
-
code,
|
|
208
|
-
type: :code,
|
|
209
|
-
category: "helpers",
|
|
210
|
-
importance: 5.0,
|
|
211
|
-
tags: ["ruby", "calculation"]
|
|
194
|
+
# With metadata
|
|
195
|
+
node_id = htm.remember(
|
|
196
|
+
"User prefers dark mode for all interfaces",
|
|
197
|
+
metadata: { category: "preference", priority: "high", source_app: "settings" }
|
|
212
198
|
)
|
|
213
199
|
|
|
214
|
-
#
|
|
215
|
-
htm.
|
|
216
|
-
"
|
|
217
|
-
"
|
|
218
|
-
|
|
219
|
-
category: "user_settings",
|
|
220
|
-
importance: 6.0
|
|
200
|
+
# With both tags and metadata
|
|
201
|
+
node_id = htm.remember(
|
|
202
|
+
"API rate limit is 1000 requests per minute",
|
|
203
|
+
tags: ["api:rate-limiting", "infrastructure"],
|
|
204
|
+
metadata: { environment: "production", version: 2 }
|
|
221
205
|
)
|
|
206
|
+
|
|
207
|
+
# Multiple robots remembering the same content
|
|
208
|
+
robot1 = HTM.new(robot_name: "assistant_1")
|
|
209
|
+
robot2 = HTM.new(robot_name: "assistant_2")
|
|
210
|
+
|
|
211
|
+
# Both robots remember the same fact - stored once, linked to both
|
|
212
|
+
robot1.remember("Ruby 3.3 was released in December 2023")
|
|
213
|
+
robot2.remember("Ruby 3.3 was released in December 2023")
|
|
214
|
+
# Same node_id returned, remember_count incremented for robot2
|
|
222
215
|
```
|
|
223
216
|
|
|
224
217
|
#### Notes
|
|
225
218
|
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
- Token count is calculated automatically
|
|
229
|
-
-
|
|
219
|
+
- Embeddings and hierarchical tags are generated asynchronously via background jobs
|
|
220
|
+
- Empty content returns the ID of the most recent node without creating a duplicate
|
|
221
|
+
- Token count is calculated automatically using the configured token counter
|
|
222
|
+
- Metadata is stored in a JSONB column with a GIN index for efficient queries
|
|
230
223
|
|
|
231
224
|
---
|
|
232
225
|
|
|
233
|
-
### `recall(
|
|
226
|
+
### `recall(topic, **options)` {: #recall }
|
|
234
227
|
|
|
235
|
-
Recall memories from
|
|
228
|
+
Recall memories from long-term storage using RAG-based retrieval.
|
|
236
229
|
|
|
237
230
|
```ruby
|
|
238
231
|
recall(
|
|
239
|
-
|
|
240
|
-
|
|
232
|
+
topic,
|
|
233
|
+
timeframe: "last 7 days",
|
|
241
234
|
limit: 20,
|
|
242
|
-
strategy: :vector
|
|
235
|
+
strategy: :vector,
|
|
236
|
+
with_relevance: false,
|
|
237
|
+
query_tags: [],
|
|
238
|
+
metadata: {},
|
|
239
|
+
raw: false
|
|
243
240
|
)
|
|
244
241
|
```
|
|
245
242
|
|
|
@@ -247,10 +244,14 @@ recall(
|
|
|
247
244
|
|
|
248
245
|
| Parameter | Type | Default | Description |
|
|
249
246
|
|-----------|------|---------|-------------|
|
|
250
|
-
| `timeframe` | String, Range | *required* | Time range (e.g., `"last week"`, `7.days.ago..Time.now`) |
|
|
251
247
|
| `topic` | String | *required* | Topic to search for |
|
|
248
|
+
| `timeframe` | String, Range | `"last 7 days"` | Time range |
|
|
252
249
|
| `limit` | Integer | `20` | Maximum number of nodes to retrieve |
|
|
253
250
|
| `strategy` | Symbol | `:vector` | Search strategy (`:vector`, `:fulltext`, `:hybrid`) |
|
|
251
|
+
| `with_relevance` | Boolean | `false` | Include dynamic relevance scores |
|
|
252
|
+
| `query_tags` | Array\<String\> | `[]` | Tags to boost relevance |
|
|
253
|
+
| `metadata` | Hash | `{}` | Filter results by metadata (uses JSONB `@>` containment) |
|
|
254
|
+
| `raw` | Boolean | `false` | Return full node hashes instead of content strings |
|
|
254
255
|
|
|
255
256
|
#### Timeframe Formats
|
|
256
257
|
|
|
@@ -274,27 +275,36 @@ Range format:
|
|
|
274
275
|
|----------|-------------|----------|
|
|
275
276
|
| `:vector` | Semantic similarity using embeddings | Find conceptually related content |
|
|
276
277
|
| `:fulltext` | PostgreSQL full-text search | Find exact terms and phrases |
|
|
277
|
-
| `:hybrid` |
|
|
278
|
+
| `:hybrid` | Vector + fulltext + tag matching | Best accuracy with tag boosting |
|
|
279
|
+
|
|
280
|
+
#### Tag-Enhanced Hybrid Search
|
|
281
|
+
|
|
282
|
+
When using `:hybrid` strategy, the search automatically:
|
|
283
|
+
|
|
284
|
+
1. Finds tags matching query terms (words 3+ chars)
|
|
285
|
+
2. Includes nodes with matching tags in the candidate pool
|
|
286
|
+
3. Calculates combined score: `(similarity × 0.7) + (tag_boost × 0.3)`
|
|
287
|
+
4. Returns results sorted by combined score
|
|
278
288
|
|
|
279
289
|
#### Returns
|
|
280
290
|
|
|
281
|
-
- `Array<
|
|
291
|
+
- `Array<String>` - Content strings (when `raw: false`, default)
|
|
292
|
+
- `Array<Hash>` - Full node hashes (when `raw: true`)
|
|
282
293
|
|
|
283
|
-
|
|
294
|
+
When `raw: true`, each hash contains:
|
|
284
295
|
|
|
285
296
|
```ruby
|
|
286
297
|
{
|
|
287
298
|
"id" => 123, # Database ID
|
|
288
|
-
"
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"category" => "architecture", # Category
|
|
292
|
-
"importance" => 8.0, # Importance score
|
|
299
|
+
"content" => "...", # Node content
|
|
300
|
+
"content_hash" => "abc123...", # SHA-256 hash
|
|
301
|
+
"access_count" => 5, # Times accessed
|
|
293
302
|
"created_at" => "2025-01-15...", # Creation timestamp
|
|
294
|
-
"robot_id" => "abc123...", # Robot that created it
|
|
295
303
|
"token_count" => 125, # Token count
|
|
296
|
-
"
|
|
297
|
-
|
|
304
|
+
"metadata" => { "category" => "preference", "priority" => "high" }, # JSONB metadata
|
|
305
|
+
"similarity" => 0.87, # Similarity score (hybrid/vector)
|
|
306
|
+
"tag_boost" => 0.3, # Tag boost score (hybrid only)
|
|
307
|
+
"combined_score" => 0.79 # Combined score (hybrid only)
|
|
298
308
|
}
|
|
299
309
|
```
|
|
300
310
|
|
|
@@ -302,118 +312,96 @@ Each hash contains:
|
|
|
302
312
|
|
|
303
313
|
- Adds recalled nodes to working memory
|
|
304
314
|
- Evicts existing nodes if working memory is full
|
|
305
|
-
- Logs operation to `operations_log` table
|
|
306
315
|
- Updates robot activity timestamp
|
|
307
316
|
|
|
308
317
|
#### Examples
|
|
309
318
|
|
|
310
319
|
```ruby
|
|
320
|
+
# Basic usage (returns content strings)
|
|
321
|
+
memories = htm.recall("PostgreSQL")
|
|
322
|
+
# => ["PostgreSQL supports vector search...", "PostgreSQL with pgvector..."]
|
|
323
|
+
|
|
324
|
+
# Get full node hashes
|
|
325
|
+
nodes = htm.recall("PostgreSQL", raw: true)
|
|
326
|
+
# => [{"id" => 1, "content" => "...", "similarity" => 0.92, ...}, ...]
|
|
327
|
+
|
|
311
328
|
# Vector semantic search
|
|
312
329
|
memories = htm.recall(
|
|
330
|
+
"database performance optimization",
|
|
313
331
|
timeframe: "last week",
|
|
314
|
-
|
|
332
|
+
strategy: :vector
|
|
315
333
|
)
|
|
316
334
|
|
|
317
335
|
# Fulltext search for exact phrases
|
|
318
336
|
memories = htm.recall(
|
|
337
|
+
"PostgreSQL connection pooling",
|
|
319
338
|
timeframe: "last 30 days",
|
|
320
|
-
topic: "PostgreSQL connection pooling",
|
|
321
339
|
strategy: :fulltext,
|
|
322
340
|
limit: 10
|
|
323
341
|
)
|
|
324
342
|
|
|
325
|
-
# Hybrid search
|
|
343
|
+
# Hybrid search with tag boosting (recommended)
|
|
326
344
|
memories = htm.recall(
|
|
345
|
+
"API rate limiting implementation",
|
|
327
346
|
timeframe: "this month",
|
|
328
|
-
topic: "API rate limiting implementation",
|
|
329
347
|
strategy: :hybrid,
|
|
330
|
-
limit: 15
|
|
348
|
+
limit: 15,
|
|
349
|
+
raw: true
|
|
331
350
|
)
|
|
332
351
|
|
|
352
|
+
# Check matching tags for a query
|
|
353
|
+
matching_tags = htm.long_term_memory.find_query_matching_tags("PostgreSQL")
|
|
354
|
+
# => ["database:postgresql", "database:postgresql:extensions"]
|
|
355
|
+
|
|
333
356
|
# Custom time range
|
|
334
357
|
start_time = Time.new(2025, 1, 1)
|
|
335
358
|
end_time = Time.now
|
|
336
359
|
|
|
337
360
|
memories = htm.recall(
|
|
361
|
+
"security vulnerabilities",
|
|
338
362
|
timeframe: start_time..end_time,
|
|
339
|
-
topic: "security vulnerabilities",
|
|
340
363
|
limit: 50
|
|
341
364
|
)
|
|
342
365
|
|
|
343
|
-
#
|
|
344
|
-
memories
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
366
|
+
# Filter by metadata
|
|
367
|
+
memories = htm.recall(
|
|
368
|
+
"user preferences",
|
|
369
|
+
metadata: { category: "preference" }
|
|
370
|
+
)
|
|
371
|
+
# => Returns only nodes with metadata containing { category: "preference" }
|
|
372
|
+
|
|
373
|
+
# Combine metadata with other filters
|
|
374
|
+
memories = htm.recall(
|
|
375
|
+
"API configuration",
|
|
376
|
+
timeframe: "last month",
|
|
377
|
+
strategy: :hybrid,
|
|
378
|
+
metadata: { environment: "production", version: 2 },
|
|
379
|
+
raw: true
|
|
380
|
+
)
|
|
381
|
+
# => Returns production configs with version 2, sorted by relevance
|
|
349
382
|
```
|
|
350
383
|
|
|
351
384
|
#### Performance Notes
|
|
352
385
|
|
|
353
386
|
- Vector search: Best for semantic understanding, requires embedding generation
|
|
354
387
|
- Fulltext search: Fastest for exact matches, no embedding overhead
|
|
355
|
-
- Hybrid search:
|
|
388
|
+
- Hybrid search: Most accurate, combines vector + fulltext + tags with weighted scoring
|
|
356
389
|
|
|
357
390
|
---
|
|
358
391
|
|
|
359
|
-
### `
|
|
360
|
-
|
|
361
|
-
Retrieve a specific memory node by its key.
|
|
362
|
-
|
|
363
|
-
```ruby
|
|
364
|
-
retrieve(key)
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
#### Parameters
|
|
368
|
-
|
|
369
|
-
| Parameter | Type | Description |
|
|
370
|
-
|-----------|------|-------------|
|
|
371
|
-
| `key` | String | Key of the node to retrieve |
|
|
372
|
-
|
|
373
|
-
#### Returns
|
|
374
|
-
|
|
375
|
-
- `Hash` - Node data if found
|
|
376
|
-
- `nil` - If node doesn't exist
|
|
377
|
-
|
|
378
|
-
#### Side Effects
|
|
379
|
-
|
|
380
|
-
- Updates `last_accessed` timestamp for the node
|
|
381
|
-
- Logs operation to `operations_log` table
|
|
382
|
-
|
|
383
|
-
#### Examples
|
|
384
|
-
|
|
385
|
-
```ruby
|
|
386
|
-
# Retrieve a node
|
|
387
|
-
node = htm.retrieve("api_decision_001")
|
|
388
|
-
|
|
389
|
-
if node
|
|
390
|
-
puts node['value']
|
|
391
|
-
puts "Created: #{node['created_at']}"
|
|
392
|
-
puts "Importance: #{node['importance']}"
|
|
393
|
-
else
|
|
394
|
-
puts "Node not found"
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
# Use retrieved data
|
|
398
|
-
config = htm.retrieve("database_config")
|
|
399
|
-
db_url = JSON.parse(config['value'])['url'] if config
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
### `forget(key, confirm:)` {: #forget }
|
|
392
|
+
### `forget(node_id, confirm:)` {: #forget }
|
|
405
393
|
|
|
406
394
|
Explicitly delete a memory node. Requires confirmation to prevent accidental deletion.
|
|
407
395
|
|
|
408
396
|
```ruby
|
|
409
|
-
forget(
|
|
397
|
+
forget(node_id, confirm: :confirmed)
|
|
410
398
|
```
|
|
411
399
|
|
|
412
400
|
#### Parameters
|
|
413
401
|
|
|
414
402
|
| Parameter | Type | Description |
|
|
415
403
|
|-----------|------|-------------|
|
|
416
|
-
| `
|
|
404
|
+
| `node_id` | Integer | ID of the node to delete |
|
|
417
405
|
| `confirm` | Symbol | Must be `:confirmed` to proceed |
|
|
418
406
|
|
|
419
407
|
#### Returns
|
|
@@ -423,27 +411,28 @@ forget(key, confirm: :confirmed)
|
|
|
423
411
|
#### Raises
|
|
424
412
|
|
|
425
413
|
- `ArgumentError` - If `confirm` is not `:confirmed`
|
|
414
|
+
- `ArgumentError` - If `node_id` is nil
|
|
415
|
+
- `HTM::NotFoundError` - If node doesn't exist
|
|
426
416
|
|
|
427
417
|
#### Side Effects
|
|
428
418
|
|
|
429
419
|
- Deletes node from PostgreSQL
|
|
430
420
|
- Removes node from working memory
|
|
431
|
-
- Logs operation before deletion
|
|
432
421
|
- Updates robot activity timestamp
|
|
433
422
|
|
|
434
423
|
#### Examples
|
|
435
424
|
|
|
436
425
|
```ruby
|
|
437
426
|
# Correct usage
|
|
438
|
-
htm.forget(
|
|
427
|
+
htm.forget(123, confirm: :confirmed)
|
|
439
428
|
|
|
440
429
|
# This will raise ArgumentError
|
|
441
|
-
htm.forget(
|
|
430
|
+
htm.forget(123) # Missing confirm parameter
|
|
442
431
|
|
|
443
432
|
# Safe deletion with verification
|
|
444
|
-
if htm.
|
|
445
|
-
htm.forget(
|
|
446
|
-
puts "Deleted
|
|
433
|
+
if htm.long_term_memory.exists?(node_id)
|
|
434
|
+
htm.forget(node_id, confirm: :confirmed)
|
|
435
|
+
puts "Deleted node #{node_id}"
|
|
447
436
|
end
|
|
448
437
|
```
|
|
449
438
|
|
|
@@ -451,248 +440,166 @@ end
|
|
|
451
440
|
|
|
452
441
|
- This is the **only** way to delete data from HTM
|
|
453
442
|
- Deletion is permanent and cannot be undone
|
|
454
|
-
- Related
|
|
443
|
+
- Related robot_nodes, node_tags are also deleted (CASCADE)
|
|
444
|
+
- Other robots' associations to this node are also removed
|
|
455
445
|
|
|
456
446
|
---
|
|
457
447
|
|
|
458
|
-
### `
|
|
448
|
+
### `load_file(path, force: false)` {: #load_file }
|
|
459
449
|
|
|
460
|
-
|
|
450
|
+
Load a markdown file into long-term memory with automatic chunking and source tracking.
|
|
461
451
|
|
|
462
452
|
```ruby
|
|
463
|
-
|
|
453
|
+
load_file(path, force: false)
|
|
464
454
|
```
|
|
465
455
|
|
|
466
456
|
#### Parameters
|
|
467
457
|
|
|
468
458
|
| Parameter | Type | Default | Description |
|
|
469
459
|
|-----------|------|---------|-------------|
|
|
470
|
-
| `
|
|
471
|
-
| `
|
|
460
|
+
| `path` | String | *required* | Path to the markdown file to load |
|
|
461
|
+
| `force` | Boolean | `false` | Force re-sync even if file hasn't changed |
|
|
472
462
|
|
|
473
|
-
####
|
|
463
|
+
#### Returns
|
|
474
464
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
465
|
+
- `Hash` with keys:
|
|
466
|
+
- `file_source_id` - ID of the FileSource record
|
|
467
|
+
- `chunks_created` - Number of new nodes created
|
|
468
|
+
- `chunks_updated` - Number of existing nodes updated
|
|
469
|
+
- `chunks_deleted` - Number of nodes soft-deleted
|
|
480
470
|
|
|
481
|
-
####
|
|
471
|
+
#### Side Effects
|
|
482
472
|
|
|
483
|
-
-
|
|
473
|
+
- Creates or updates a FileSource record for tracking
|
|
474
|
+
- Parses YAML frontmatter and stores as metadata
|
|
475
|
+
- Chunks content by paragraph, preserving code blocks
|
|
476
|
+
- Creates nodes for each chunk with `source_id` linking to file
|
|
477
|
+
- Triggers async embedding and tag extraction for new nodes
|
|
484
478
|
|
|
485
479
|
#### Examples
|
|
486
480
|
|
|
487
481
|
```ruby
|
|
488
|
-
#
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
# Recent context with token limit
|
|
492
|
-
context = htm.create_context(
|
|
493
|
-
strategy: :recent,
|
|
494
|
-
max_tokens: 50_000
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
# Important context only
|
|
498
|
-
context = htm.create_context(strategy: :important)
|
|
482
|
+
# Load a file
|
|
483
|
+
result = htm.load_file("docs/guide.md")
|
|
484
|
+
# => { file_source_id: 1, chunks_created: 5, chunks_updated: 0, chunks_deleted: 0 }
|
|
499
485
|
|
|
500
|
-
#
|
|
501
|
-
|
|
502
|
-
You are a helpful assistant.
|
|
486
|
+
# Force reload even if unchanged
|
|
487
|
+
result = htm.load_file("docs/guide.md", force: true)
|
|
503
488
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
489
|
+
# File with frontmatter
|
|
490
|
+
# ---
|
|
491
|
+
# title: User Guide
|
|
492
|
+
# tags: [documentation, tutorial]
|
|
493
|
+
# ---
|
|
494
|
+
# Content here...
|
|
495
|
+
result = htm.load_file("docs/guide.md")
|
|
496
|
+
# Frontmatter stored in FileSource.frontmatter
|
|
509
497
|
```
|
|
510
498
|
|
|
511
|
-
#### Notes
|
|
512
|
-
|
|
513
|
-
- Nodes are concatenated with double newlines
|
|
514
|
-
- Token limits are respected (stops adding when limit reached)
|
|
515
|
-
- Empty string if working memory is empty
|
|
516
|
-
|
|
517
499
|
---
|
|
518
500
|
|
|
519
|
-
### `
|
|
501
|
+
### `load_directory(path, pattern: '**/*.md', force: false)` {: #load_directory }
|
|
520
502
|
|
|
521
|
-
|
|
503
|
+
Load all matching files in a directory into long-term memory.
|
|
522
504
|
|
|
523
505
|
```ruby
|
|
524
|
-
|
|
506
|
+
load_directory(path, pattern: '**/*.md', force: false)
|
|
525
507
|
```
|
|
526
508
|
|
|
527
|
-
####
|
|
509
|
+
#### Parameters
|
|
528
510
|
|
|
529
|
-
|
|
511
|
+
| Parameter | Type | Default | Description |
|
|
512
|
+
|-----------|------|---------|-------------|
|
|
513
|
+
| `path` | String | *required* | Directory path to scan |
|
|
514
|
+
| `pattern` | String | `'**/*.md'` | Glob pattern for matching files |
|
|
515
|
+
| `force` | Boolean | `false` | Force re-sync all files |
|
|
530
516
|
|
|
531
|
-
|
|
517
|
+
#### Returns
|
|
532
518
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
total_nodes: 1234,
|
|
540
|
-
nodes_by_robot: {
|
|
541
|
-
"robot-1" => 500,
|
|
542
|
-
"robot-2" => 734
|
|
543
|
-
},
|
|
544
|
-
nodes_by_type: [
|
|
545
|
-
{"type" => "fact", "count" => 400},
|
|
546
|
-
{"type" => "decision", "count" => 200},
|
|
547
|
-
...
|
|
548
|
-
],
|
|
549
|
-
total_relationships: 567,
|
|
550
|
-
total_tags: 890,
|
|
551
|
-
oldest_memory: "2025-01-01 12:00:00",
|
|
552
|
-
newest_memory: "2025-01-15 14:30:00",
|
|
553
|
-
active_robots: 3,
|
|
554
|
-
robot_activity: [...],
|
|
555
|
-
database_size: 12345678,
|
|
556
|
-
|
|
557
|
-
# Working memory stats
|
|
558
|
-
working_memory: {
|
|
559
|
-
current_tokens: 45234,
|
|
560
|
-
max_tokens: 128000,
|
|
561
|
-
utilization: 35.34,
|
|
562
|
-
node_count: 23
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
```
|
|
519
|
+
- `Array<Hash>` - Results for each file loaded, each containing:
|
|
520
|
+
- `file_path` - Path of the loaded file
|
|
521
|
+
- `file_source_id` - ID of the FileSource record
|
|
522
|
+
- `chunks_created` - Number of new nodes created
|
|
523
|
+
- `chunks_updated` - Number of existing nodes updated
|
|
524
|
+
- `chunks_deleted` - Number of nodes soft-deleted
|
|
566
525
|
|
|
567
526
|
#### Examples
|
|
568
527
|
|
|
569
528
|
```ruby
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
puts "Total memories: #{stats[:total_nodes]}"
|
|
573
|
-
puts "Working memory: #{stats[:working_memory][:utilization]}% full"
|
|
574
|
-
puts "Active robots: #{stats[:active_robots]}"
|
|
529
|
+
# Load all markdown files
|
|
530
|
+
results = htm.load_directory("docs/")
|
|
575
531
|
|
|
576
|
-
#
|
|
577
|
-
|
|
578
|
-
puts "Warning: Working memory is #{stats[:working_memory][:utilization]}% full"
|
|
579
|
-
end
|
|
532
|
+
# Load with custom pattern
|
|
533
|
+
results = htm.load_directory("content/", pattern: "**/*.md")
|
|
580
534
|
|
|
581
|
-
#
|
|
582
|
-
|
|
583
|
-
puts "#{robot_id}: #{count} nodes"
|
|
584
|
-
end
|
|
535
|
+
# Force reload all
|
|
536
|
+
results = htm.load_directory("docs/", force: true)
|
|
585
537
|
```
|
|
586
538
|
|
|
587
539
|
---
|
|
588
540
|
|
|
589
|
-
### `
|
|
541
|
+
### `nodes_from_file(file_path)` {: #nodes_from_file }
|
|
590
542
|
|
|
591
|
-
|
|
543
|
+
Get all nodes loaded from a specific file.
|
|
592
544
|
|
|
593
545
|
```ruby
|
|
594
|
-
|
|
546
|
+
nodes_from_file(file_path)
|
|
595
547
|
```
|
|
596
548
|
|
|
597
549
|
#### Parameters
|
|
598
550
|
|
|
599
|
-
| Parameter | Type |
|
|
600
|
-
|
|
601
|
-
| `
|
|
602
|
-
| `limit` | Integer | `100` | Maximum results to consider |
|
|
551
|
+
| Parameter | Type | Description |
|
|
552
|
+
|-----------|------|-------------|
|
|
553
|
+
| `file_path` | String | Path of the source file |
|
|
603
554
|
|
|
604
555
|
#### Returns
|
|
605
556
|
|
|
606
|
-
- `
|
|
607
|
-
|
|
608
|
-
```ruby
|
|
609
|
-
{
|
|
610
|
-
"robot-abc123" => 15,
|
|
611
|
-
"robot-def456" => 8,
|
|
612
|
-
"robot-ghi789" => 3
|
|
613
|
-
}
|
|
614
|
-
```
|
|
557
|
+
- `Array<HTM::Models::Node>` - Nodes from the file, ordered by chunk position
|
|
615
558
|
|
|
616
559
|
#### Examples
|
|
617
560
|
|
|
618
561
|
```ruby
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
# Top contributor
|
|
624
|
-
top_robot, count = robots.max_by { |robot, count| count }
|
|
625
|
-
puts "#{top_robot} mentioned it #{count} times"
|
|
626
|
-
|
|
627
|
-
# Check if specific robot discussed it
|
|
628
|
-
if robots.key?("robot-123")
|
|
629
|
-
puts "Robot-123 discussed deployment #{robots['robot-123']} times"
|
|
562
|
+
nodes = htm.nodes_from_file("docs/guide.md")
|
|
563
|
+
nodes.each do |node|
|
|
564
|
+
puts "Chunk #{node.chunk_position}: #{node.content[0..50]}..."
|
|
630
565
|
end
|
|
631
566
|
```
|
|
632
567
|
|
|
633
568
|
---
|
|
634
569
|
|
|
635
|
-
### `
|
|
570
|
+
### `unload_file(file_path)` {: #unload_file }
|
|
636
571
|
|
|
637
|
-
|
|
572
|
+
Remove a file from memory by soft-deleting all its chunks and the file source.
|
|
638
573
|
|
|
639
574
|
```ruby
|
|
640
|
-
|
|
575
|
+
unload_file(file_path)
|
|
641
576
|
```
|
|
642
577
|
|
|
643
578
|
#### Parameters
|
|
644
579
|
|
|
645
|
-
| Parameter | Type |
|
|
646
|
-
|
|
647
|
-
| `
|
|
648
|
-
| `limit` | Integer | `50` | Maximum results |
|
|
580
|
+
| Parameter | Type | Description |
|
|
581
|
+
|-----------|------|-------------|
|
|
582
|
+
| `file_path` | String | Path of the source file to unload |
|
|
649
583
|
|
|
650
584
|
#### Returns
|
|
651
585
|
|
|
652
|
-
- `
|
|
586
|
+
- `true` if file was found and unloaded
|
|
587
|
+
- `false` if file was not found
|
|
653
588
|
|
|
654
|
-
|
|
589
|
+
#### Side Effects
|
|
655
590
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
{
|
|
659
|
-
timestamp: "2025-01-15 10:30:00",
|
|
660
|
-
robot: "robot-abc123",
|
|
661
|
-
content: "We should consider PostgreSQL...",
|
|
662
|
-
type: "decision"
|
|
663
|
-
},
|
|
664
|
-
{
|
|
665
|
-
timestamp: "2025-01-15 11:45:00",
|
|
666
|
-
robot: "robot-def456",
|
|
667
|
-
content: "Agreed, PostgreSQL has better...",
|
|
668
|
-
type: "fact"
|
|
669
|
-
},
|
|
670
|
-
...
|
|
671
|
-
]
|
|
672
|
-
```
|
|
591
|
+
- Soft-deletes all nodes from the file (sets `deleted_at`)
|
|
592
|
+
- Destroys the FileSource record
|
|
673
593
|
|
|
674
594
|
#### Examples
|
|
675
595
|
|
|
676
596
|
```ruby
|
|
677
|
-
#
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
# Display timeline
|
|
681
|
-
timeline.each do |entry|
|
|
682
|
-
puts "[#{entry[:timestamp]}] #{entry[:robot]}:"
|
|
683
|
-
puts " #{entry[:content]}"
|
|
684
|
-
puts " (#{entry[:type]})"
|
|
685
|
-
puts
|
|
686
|
-
end
|
|
597
|
+
# Unload a file
|
|
598
|
+
htm.unload_file("docs/guide.md")
|
|
687
599
|
|
|
688
|
-
#
|
|
689
|
-
|
|
690
|
-
puts "
|
|
691
|
-
|
|
692
|
-
# Group by robot
|
|
693
|
-
by_robot = timeline.group_by { |e| e[:robot] }
|
|
694
|
-
by_robot.each do |robot, entries|
|
|
695
|
-
puts "#{robot}: #{entries.size} contributions"
|
|
600
|
+
# Check if file is loaded
|
|
601
|
+
if htm.nodes_from_file("docs/guide.md").empty?
|
|
602
|
+
puts "File not loaded"
|
|
696
603
|
end
|
|
697
604
|
```
|
|
698
605
|
|
|
@@ -704,12 +611,24 @@ end
|
|
|
704
611
|
|
|
705
612
|
```ruby
|
|
706
613
|
# Invalid confirm parameter
|
|
707
|
-
htm.forget(
|
|
614
|
+
htm.forget(123)
|
|
708
615
|
# => ArgumentError: Must pass confirm: :confirmed to delete
|
|
709
616
|
|
|
617
|
+
# Nil node_id
|
|
618
|
+
htm.forget(nil, confirm: :confirmed)
|
|
619
|
+
# => ArgumentError: node_id cannot be nil
|
|
620
|
+
|
|
710
621
|
# Invalid timeframe
|
|
711
|
-
htm.recall(
|
|
712
|
-
# =>
|
|
622
|
+
htm.recall("test", timeframe: 123)
|
|
623
|
+
# => ValidationError: Timeframe must be a Range or String
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### HTM::NotFoundError
|
|
627
|
+
|
|
628
|
+
```ruby
|
|
629
|
+
# Node doesn't exist
|
|
630
|
+
htm.forget(999999, confirm: :confirmed)
|
|
631
|
+
# => HTM::NotFoundError: Node not found: 999999
|
|
713
632
|
```
|
|
714
633
|
|
|
715
634
|
### PG::Error
|
|
@@ -718,73 +637,95 @@ htm.recall(timeframe: nil, topic: "test")
|
|
|
718
637
|
# Database connection issues
|
|
719
638
|
htm = HTM.new(db_config: { host: 'invalid' })
|
|
720
639
|
# => PG::ConnectionBad: could not translate host name...
|
|
721
|
-
|
|
722
|
-
# Duplicate key
|
|
723
|
-
htm.add_node("existing_key", "value")
|
|
724
|
-
# => PG::UniqueViolation: duplicate key value...
|
|
725
640
|
```
|
|
726
641
|
|
|
727
642
|
## Best Practices
|
|
728
643
|
|
|
729
|
-
###
|
|
644
|
+
### Content Organization
|
|
730
645
|
|
|
731
646
|
```ruby
|
|
732
|
-
# Use
|
|
733
|
-
htm.
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
htm.add_node(key, value, importance: 2.0) # Low priority
|
|
740
|
-
|
|
741
|
-
# Build knowledge graphs
|
|
742
|
-
htm.add_node(
|
|
743
|
-
"api_v2_implementation",
|
|
744
|
-
"...",
|
|
745
|
-
related_to: ["api_v1_design", "authentication_decision"]
|
|
647
|
+
# Use meaningful content that stands alone
|
|
648
|
+
htm.remember("PostgreSQL was chosen for its reliability and pgvector support")
|
|
649
|
+
|
|
650
|
+
# Add hierarchical tags for organization
|
|
651
|
+
htm.remember(
|
|
652
|
+
"Rate limiting implemented using Redis sliding window algorithm",
|
|
653
|
+
tags: ["architecture:api:rate-limiting", "database:redis"]
|
|
746
654
|
)
|
|
655
|
+
|
|
656
|
+
# Let the system extract tags automatically for most content
|
|
657
|
+
htm.remember("The authentication system uses JWT tokens with 1-hour expiry")
|
|
658
|
+
# Auto-extracted tags might include: security:authentication, technology:jwt
|
|
747
659
|
```
|
|
748
660
|
|
|
749
661
|
### Search Strategies
|
|
750
662
|
|
|
751
663
|
```ruby
|
|
664
|
+
# Use hybrid for best results (recommended)
|
|
665
|
+
memories = htm.recall(
|
|
666
|
+
"security vulnerability",
|
|
667
|
+
strategy: :hybrid # Combines vector + fulltext + tags
|
|
668
|
+
)
|
|
669
|
+
|
|
752
670
|
# Use vector for semantic understanding
|
|
753
671
|
memories = htm.recall(
|
|
754
|
-
|
|
755
|
-
topic: "performance issues",
|
|
672
|
+
"performance issues",
|
|
756
673
|
strategy: :vector # Finds "slow queries", "optimization", etc.
|
|
757
674
|
)
|
|
758
675
|
|
|
759
676
|
# Use fulltext for exact terms
|
|
760
677
|
memories = htm.recall(
|
|
761
|
-
|
|
762
|
-
topic: "PostgreSQL EXPLAIN ANALYZE",
|
|
678
|
+
"PostgreSQL EXPLAIN ANALYZE",
|
|
763
679
|
strategy: :fulltext # Exact match
|
|
764
680
|
)
|
|
681
|
+
```
|
|
765
682
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
683
|
+
### Leveraging Tag-Enhanced Search
|
|
684
|
+
|
|
685
|
+
```ruby
|
|
686
|
+
# Check what tags exist for a topic
|
|
687
|
+
tags = htm.long_term_memory.find_query_matching_tags("database")
|
|
688
|
+
# => ["database:postgresql", "database:redis", "database:timescaledb"]
|
|
689
|
+
|
|
690
|
+
# Hybrid search automatically boosts nodes with matching tags
|
|
691
|
+
memories = htm.recall("database optimization", strategy: :hybrid, raw: true)
|
|
692
|
+
memories.each do |m|
|
|
693
|
+
puts "Score: #{m['combined_score']} (sim: #{m['similarity']}, tag: #{m['tag_boost']})"
|
|
694
|
+
end
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Multi-Robot Memory Sharing
|
|
698
|
+
|
|
699
|
+
```ruby
|
|
700
|
+
# Content is deduplicated across robots
|
|
701
|
+
assistant = HTM.new(robot_name: "assistant")
|
|
702
|
+
researcher = HTM.new(robot_name: "researcher")
|
|
703
|
+
|
|
704
|
+
# Both robots remember the same fact
|
|
705
|
+
assistant.remember("Ruby 3.3 supports YJIT by default")
|
|
706
|
+
researcher.remember("Ruby 3.3 supports YJIT by default")
|
|
707
|
+
# Node stored once, linked to both robots
|
|
708
|
+
|
|
709
|
+
# Any robot can recall shared memories
|
|
710
|
+
memories = assistant.recall("Ruby YJIT")
|
|
711
|
+
# Returns the shared memory
|
|
772
712
|
```
|
|
773
713
|
|
|
774
714
|
### Resource Management
|
|
775
715
|
|
|
776
716
|
```ruby
|
|
777
717
|
# Check working memory before large operations
|
|
778
|
-
stats = htm.
|
|
779
|
-
if stats[:
|
|
780
|
-
#
|
|
718
|
+
stats = htm.working_memory.stats
|
|
719
|
+
if stats[:utilization] > 90
|
|
720
|
+
# Consider clearing working memory or using smaller limits
|
|
781
721
|
end
|
|
782
722
|
|
|
783
723
|
# Use appropriate limits
|
|
784
|
-
htm.recall(
|
|
724
|
+
htm.recall("common_topic", limit: 10) # Not 1000
|
|
785
725
|
|
|
786
|
-
# Monitor
|
|
787
|
-
|
|
726
|
+
# Monitor node counts
|
|
727
|
+
node_count = HTM::Models::Node.count
|
|
728
|
+
if node_count > 1_000_000
|
|
788
729
|
# Consider archival strategy
|
|
789
730
|
end
|
|
790
731
|
```
|