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
|
@@ -4,744 +4,425 @@ This guide covers everything you need to know about storing information in HTM e
|
|
|
4
4
|
|
|
5
5
|
## Basic Usage
|
|
6
6
|
|
|
7
|
-
The primary method for adding memories is `
|
|
7
|
+
The primary method for adding memories is `remember`:
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
node_id = htm.
|
|
11
|
-
key, # Unique identifier
|
|
12
|
-
value, # Content (string)
|
|
13
|
-
type: :fact, # Memory type
|
|
14
|
-
category: nil, # Optional category
|
|
15
|
-
importance: 1.0, # Importance score (0.0-10.0)
|
|
16
|
-
related_to: [], # Array of related node keys
|
|
17
|
-
tags: [] # Array of tags
|
|
18
|
-
)
|
|
10
|
+
node_id = htm.remember(content, tags: [], metadata: {})
|
|
19
11
|
```
|
|
20
12
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Memory Types Deep Dive
|
|
24
|
-
|
|
25
|
-
HTM supports six memory types, each optimized for specific use cases.
|
|
26
|
-
|
|
27
|
-
### :fact - Immutable Facts
|
|
28
|
-
|
|
29
|
-
Facts are unchanging truths about the world, users, or systems.
|
|
30
|
-
|
|
31
|
-
```ruby
|
|
32
|
-
# User information
|
|
33
|
-
htm.add_node(
|
|
34
|
-
"user_name",
|
|
35
|
-
"The user's name is Alice Thompson",
|
|
36
|
-
type: :fact,
|
|
37
|
-
importance: 9.0,
|
|
38
|
-
tags: ["user", "identity"]
|
|
39
|
-
)
|
|
13
|
+
**Parameters:**
|
|
40
14
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
importance: 6.0,
|
|
47
|
-
tags: ["system", "config"]
|
|
48
|
-
)
|
|
15
|
+
| Parameter | Type | Default | Description |
|
|
16
|
+
|-----------|------|---------|-------------|
|
|
17
|
+
| `content` | String | *required* | The information to remember |
|
|
18
|
+
| `tags` | Array\<String\> | `[]` | Manual tags to assign (in addition to auto-extracted tags) |
|
|
19
|
+
| `metadata` | Hash | `{}` | Arbitrary key-value metadata stored as JSONB |
|
|
49
20
|
|
|
50
|
-
|
|
51
|
-
htm.add_node(
|
|
52
|
-
"fact_photosynthesis",
|
|
53
|
-
"Photosynthesis converts light energy into chemical energy in plants",
|
|
54
|
-
type: :fact,
|
|
55
|
-
importance: 7.0,
|
|
56
|
-
tags: ["biology", "science"]
|
|
57
|
-
)
|
|
58
|
-
```
|
|
21
|
+
The method returns the database ID of the created node.
|
|
59
22
|
|
|
60
|
-
|
|
61
|
-
- User profile information (name, email, preferences)
|
|
62
|
-
- System configuration that rarely changes
|
|
63
|
-
- Scientific facts or domain knowledge
|
|
64
|
-
- Historical events
|
|
65
|
-
- API endpoints and credentials
|
|
23
|
+
## How Remember Works
|
|
66
24
|
|
|
67
|
-
|
|
25
|
+
When you call `remember()`:
|
|
68
26
|
|
|
69
|
-
|
|
27
|
+
1. **Content hashing**: A SHA-256 hash of the content is computed
|
|
28
|
+
2. **Deduplication check**: If a node with the same hash exists, reuse it
|
|
29
|
+
3. **Node creation/linking**: Create new node OR link robot to existing node
|
|
30
|
+
4. **Working memory**: Add node to working memory (evict if needed)
|
|
31
|
+
5. **Background jobs**: Enqueue embedding and tag generation (async)
|
|
70
32
|
|
|
71
33
|
```ruby
|
|
72
|
-
#
|
|
73
|
-
htm.
|
|
74
|
-
|
|
75
|
-
"User is asking about database performance optimization",
|
|
76
|
-
type: :context,
|
|
77
|
-
importance: 6.0,
|
|
78
|
-
tags: ["conversation", "current"]
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
# Conversation mood
|
|
82
|
-
htm.add_node(
|
|
83
|
-
"context_mood",
|
|
84
|
-
"User seems frustrated with slow query times",
|
|
85
|
-
type: :context,
|
|
86
|
-
importance: 7.0,
|
|
87
|
-
tags: ["conversation", "sentiment"]
|
|
88
|
-
)
|
|
34
|
+
# First robot remembers something
|
|
35
|
+
node_id = htm.remember("PostgreSQL supports vector similarity search")
|
|
36
|
+
# => 123 (new node created)
|
|
89
37
|
|
|
90
|
-
#
|
|
91
|
-
htm.
|
|
92
|
-
|
|
93
|
-
"Helping user optimize their PostgreSQL queries",
|
|
94
|
-
type: :context,
|
|
95
|
-
importance: 8.0,
|
|
96
|
-
tags: ["task", "active"]
|
|
97
|
-
)
|
|
38
|
+
# Same content remembered again (by same or different robot)
|
|
39
|
+
node_id = htm.remember("PostgreSQL supports vector similarity search")
|
|
40
|
+
# => 123 (same node_id returned, just updates remember_count)
|
|
98
41
|
```
|
|
99
42
|
|
|
100
|
-
|
|
101
|
-
- Current conversation topics
|
|
102
|
-
- Session state
|
|
103
|
-
- Temporary workflow status
|
|
104
|
-
- User's current goals or questions
|
|
105
|
-
- Conversation sentiment or mood
|
|
43
|
+
## Content Types
|
|
106
44
|
|
|
107
|
-
|
|
108
|
-
Context memories are typically lower importance (4-6) since they become outdated quickly. They'll naturally get evicted from working memory as new context arrives.
|
|
45
|
+
HTM doesn't enforce content types - just store meaningful text that stands alone:
|
|
109
46
|
|
|
110
|
-
###
|
|
111
|
-
|
|
112
|
-
Store code examples, patterns, and technical solutions.
|
|
47
|
+
### Facts
|
|
113
48
|
|
|
114
49
|
```ruby
|
|
115
|
-
#
|
|
116
|
-
htm.
|
|
117
|
-
"code_date_parser",
|
|
118
|
-
<<~CODE,
|
|
119
|
-
def parse_date(date_string)
|
|
120
|
-
Date.parse(date_string)
|
|
121
|
-
rescue ArgumentError
|
|
122
|
-
nil
|
|
123
|
-
end
|
|
124
|
-
CODE
|
|
125
|
-
type: :code,
|
|
126
|
-
importance: 6.0,
|
|
127
|
-
tags: ["ruby", "date", "parsing"]
|
|
128
|
-
)
|
|
50
|
+
# User information
|
|
51
|
+
htm.remember("The user's name is Alice Thompson")
|
|
129
52
|
|
|
130
|
-
#
|
|
131
|
-
htm.
|
|
132
|
-
"code_user_query",
|
|
133
|
-
<<~SQL,
|
|
134
|
-
SELECT u.id, u.name, COUNT(o.id) as order_count
|
|
135
|
-
FROM users u
|
|
136
|
-
LEFT JOIN orders o ON u.id = o.user_id
|
|
137
|
-
GROUP BY u.id, u.name
|
|
138
|
-
HAVING COUNT(o.id) > 10
|
|
139
|
-
SQL
|
|
140
|
-
type: :code,
|
|
141
|
-
category: "sql",
|
|
142
|
-
importance: 7.0,
|
|
143
|
-
tags: ["sql", "aggregation", "joins"]
|
|
144
|
-
)
|
|
53
|
+
# System configuration
|
|
54
|
+
htm.remember("System timezone is UTC")
|
|
145
55
|
|
|
146
|
-
#
|
|
147
|
-
htm.
|
|
148
|
-
"code_redis_config",
|
|
149
|
-
<<~YAML,
|
|
150
|
-
redis:
|
|
151
|
-
host: localhost
|
|
152
|
-
port: 6379
|
|
153
|
-
pool_size: 5
|
|
154
|
-
timeout: 2
|
|
155
|
-
YAML
|
|
156
|
-
type: :code,
|
|
157
|
-
category: "config",
|
|
158
|
-
importance: 5.0,
|
|
159
|
-
tags: ["redis", "configuration", "yaml"]
|
|
160
|
-
)
|
|
56
|
+
# Domain knowledge
|
|
57
|
+
htm.remember("Photosynthesis converts light energy into chemical energy in plants")
|
|
161
58
|
```
|
|
162
59
|
|
|
163
|
-
|
|
164
|
-
- Reusable code snippets
|
|
165
|
-
- Configuration examples
|
|
166
|
-
- SQL queries and patterns
|
|
167
|
-
- API request/response examples
|
|
168
|
-
- Algorithm implementations
|
|
169
|
-
- Regular expressions
|
|
170
|
-
|
|
171
|
-
### :preference - User Preferences
|
|
172
|
-
|
|
173
|
-
Store user preferences and settings.
|
|
60
|
+
### Preferences
|
|
174
61
|
|
|
175
62
|
```ruby
|
|
176
63
|
# Communication style
|
|
177
|
-
htm.
|
|
178
|
-
"pref_communication",
|
|
179
|
-
"User prefers concise answers with bullet points",
|
|
180
|
-
type: :preference,
|
|
181
|
-
importance: 8.0,
|
|
182
|
-
tags: ["user", "communication", "style"]
|
|
183
|
-
)
|
|
64
|
+
htm.remember("User prefers concise answers with bullet points")
|
|
184
65
|
|
|
185
66
|
# Technical preferences
|
|
186
|
-
htm.
|
|
187
|
-
"pref_language",
|
|
188
|
-
"User prefers Ruby over Python for scripting tasks",
|
|
189
|
-
type: :preference,
|
|
190
|
-
importance: 7.0,
|
|
191
|
-
tags: ["user", "programming", "language"]
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
# UI preferences
|
|
195
|
-
htm.add_node(
|
|
196
|
-
"pref_theme",
|
|
197
|
-
"User uses dark theme in their IDE",
|
|
198
|
-
type: :preference,
|
|
199
|
-
importance: 4.0,
|
|
200
|
-
tags: ["user", "ui", "theme"]
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
# Work preferences
|
|
204
|
-
htm.add_node(
|
|
205
|
-
"pref_working_hours",
|
|
206
|
-
"User typically codes in the morning, prefers design work in afternoon",
|
|
207
|
-
type: :preference,
|
|
208
|
-
importance: 5.0,
|
|
209
|
-
tags: ["user", "schedule", "productivity"]
|
|
210
|
-
)
|
|
67
|
+
htm.remember("User prefers Ruby over Python for scripting tasks")
|
|
211
68
|
```
|
|
212
69
|
|
|
213
|
-
|
|
214
|
-
- Communication style preferences
|
|
215
|
-
- Technical tool preferences
|
|
216
|
-
- UI/UX preferences
|
|
217
|
-
- Work habits and patterns
|
|
218
|
-
- Learning style preferences
|
|
219
|
-
|
|
220
|
-
### :decision - Architectural Decisions
|
|
221
|
-
|
|
222
|
-
Track important decisions with rationale.
|
|
70
|
+
### Decisions
|
|
223
71
|
|
|
224
72
|
```ruby
|
|
225
73
|
# Technology choice
|
|
226
|
-
htm.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
- Redis (rejected: limited persistence)
|
|
240
|
-
DECISION
|
|
241
|
-
type: :decision,
|
|
242
|
-
category: "architecture",
|
|
243
|
-
importance: 9.5,
|
|
244
|
-
tags: ["architecture", "database", "timescaledb"]
|
|
245
|
-
)
|
|
74
|
+
htm.remember(<<~DECISION)
|
|
75
|
+
Decision: Use PostgreSQL with pgvector for HTM storage
|
|
76
|
+
|
|
77
|
+
Rationale:
|
|
78
|
+
- Excellent vector search via pgvector
|
|
79
|
+
- Strong consistency guarantees
|
|
80
|
+
- Mature ecosystem
|
|
81
|
+
|
|
82
|
+
Alternatives considered:
|
|
83
|
+
- MongoDB (rejected: eventual consistency issues)
|
|
84
|
+
- Redis (rejected: limited persistence)
|
|
85
|
+
DECISION
|
|
86
|
+
```
|
|
246
87
|
|
|
247
|
-
|
|
248
|
-
htm.add_node(
|
|
249
|
-
"decision_memory_architecture",
|
|
250
|
-
<<~DECISION,
|
|
251
|
-
Decision: Implement two-tier memory (working + long-term)
|
|
252
|
-
|
|
253
|
-
Rationale:
|
|
254
|
-
- Working memory provides fast access
|
|
255
|
-
- Long-term memory ensures durability
|
|
256
|
-
- Mirrors human memory architecture
|
|
257
|
-
- Allows token-limited LLM context
|
|
258
|
-
|
|
259
|
-
Trade-offs:
|
|
260
|
-
- Added complexity in synchronization
|
|
261
|
-
- Eviction strategy needs tuning
|
|
262
|
-
DECISION
|
|
263
|
-
type: :decision,
|
|
264
|
-
category: "architecture",
|
|
265
|
-
importance: 10.0,
|
|
266
|
-
tags: ["architecture", "memory", "design-pattern"]
|
|
267
|
-
)
|
|
88
|
+
### Code Snippets
|
|
268
89
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
90
|
+
```ruby
|
|
91
|
+
# Function example
|
|
92
|
+
htm.remember(<<~CODE)
|
|
93
|
+
def parse_date(date_string)
|
|
94
|
+
Date.parse(date_string)
|
|
95
|
+
rescue ArgumentError
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
CODE
|
|
99
|
+
|
|
100
|
+
# SQL query pattern
|
|
101
|
+
htm.remember(<<~SQL)
|
|
102
|
+
SELECT u.id, u.name, COUNT(o.id) as order_count
|
|
103
|
+
FROM users u
|
|
104
|
+
LEFT JOIN orders o ON u.id = o.user_id
|
|
105
|
+
GROUP BY u.id, u.name
|
|
106
|
+
HAVING COUNT(o.id) > 10
|
|
107
|
+
SQL
|
|
278
108
|
```
|
|
279
109
|
|
|
280
|
-
|
|
281
|
-
- Technology selections
|
|
282
|
-
- Architecture patterns
|
|
283
|
-
- API design choices
|
|
284
|
-
- Process decisions
|
|
285
|
-
- Trade-off analysis results
|
|
110
|
+
## Using Tags
|
|
286
111
|
|
|
287
|
-
|
|
288
|
-
Include: what was decided, why, alternatives considered, and trade-offs. This context helps future decision-making.
|
|
112
|
+
Tags provide hierarchical organization for your memories. HTM automatically extracts tags from content, but you can also specify manual tags.
|
|
289
113
|
|
|
290
|
-
###
|
|
114
|
+
### Hierarchical Tag Convention
|
|
291
115
|
|
|
292
|
-
|
|
116
|
+
Use colons to create hierarchical namespaces:
|
|
293
117
|
|
|
294
118
|
```ruby
|
|
295
|
-
#
|
|
296
|
-
htm.
|
|
297
|
-
"
|
|
298
|
-
"
|
|
299
|
-
type: :question,
|
|
300
|
-
importance: 7.0,
|
|
301
|
-
tags: ["performance", "caching", "open"]
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
# Design question
|
|
305
|
-
htm.add_node(
|
|
306
|
-
"question_auth",
|
|
307
|
-
"How should we handle authentication for multi-robot scenarios?",
|
|
308
|
-
type: :question,
|
|
309
|
-
importance: 8.0,
|
|
310
|
-
tags: ["security", "architecture", "open"]
|
|
119
|
+
# Manual tags with hierarchy
|
|
120
|
+
htm.remember(
|
|
121
|
+
"PostgreSQL 17 adds MERGE statement improvements",
|
|
122
|
+
tags: ["database:postgresql", "database:sql", "version:17"]
|
|
311
123
|
)
|
|
312
124
|
|
|
313
|
-
#
|
|
314
|
-
|
|
315
|
-
"question_embeddings",
|
|
316
|
-
"Would fine-tuning embeddings on our domain improve recall accuracy?",
|
|
317
|
-
type: :question,
|
|
318
|
-
importance: 6.0,
|
|
319
|
-
tags: ["embeddings", "research", "open"]
|
|
320
|
-
)
|
|
125
|
+
# Tags are used in hybrid search for relevance boosting
|
|
126
|
+
# A recall for "postgresql" will boost nodes with matching tags
|
|
321
127
|
```
|
|
322
128
|
|
|
323
|
-
|
|
324
|
-
- Open technical questions
|
|
325
|
-
- Design uncertainties
|
|
326
|
-
- Research topics to investigate
|
|
327
|
-
- Feature requests to evaluate
|
|
328
|
-
- Performance questions
|
|
329
|
-
|
|
330
|
-
!!! tip "Closing Questions"
|
|
331
|
-
When a question is answered, add a related decision node and mark the question as resolved by updating its tags.
|
|
332
|
-
|
|
333
|
-
## Importance Scoring Guidelines
|
|
129
|
+
### Tag Naming Conventions
|
|
334
130
|
|
|
335
|
-
|
|
131
|
+
```ruby
|
|
132
|
+
# Good: Consistent, lowercase, hierarchical
|
|
133
|
+
tags: ["database:postgresql", "architecture:api", "security:authentication"]
|
|
336
134
|
|
|
337
|
-
|
|
135
|
+
# Avoid: Inconsistent casing, flat tags, vague terms
|
|
136
|
+
tags: ["PostgreSQL", "stuff", "misc"]
|
|
137
|
+
```
|
|
338
138
|
|
|
339
|
-
###
|
|
139
|
+
### Common Tag Patterns
|
|
340
140
|
|
|
341
141
|
```ruby
|
|
342
|
-
#
|
|
343
|
-
|
|
344
|
-
htm.add_node("decision_architecture", "Core architecture decision", importance: 9.5)
|
|
345
|
-
|
|
346
|
-
# High (7.0-8.9): Very important, high retention
|
|
347
|
-
htm.add_node("user_identity", "User's name and email", importance: 8.0)
|
|
348
|
-
htm.add_node("major_decision", "Chose Rails for web framework", importance: 7.5)
|
|
349
|
-
|
|
350
|
-
# Medium (4.0-6.9): Moderately important
|
|
351
|
-
htm.add_node("code_snippet", "Useful utility function", importance: 6.0)
|
|
352
|
-
htm.add_node("context_current", "Current conversation topic", importance: 5.0)
|
|
353
|
-
htm.add_node("preference_minor", "Prefers tabs over spaces", importance: 4.0)
|
|
354
|
-
|
|
355
|
-
# Low (1.0-3.9): Nice to have, can evict
|
|
356
|
-
htm.add_node("temp_note", "Check logs later", importance: 3.0)
|
|
357
|
-
htm.add_node("minor_context", "Mentioned weather briefly", importance: 2.0)
|
|
358
|
-
htm.add_node("throwaway", "Temporary calculation result", importance: 1.0)
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### Importance by Type
|
|
142
|
+
# Domain tags
|
|
143
|
+
tags: ["database:postgresql", "api:rest", "auth:jwt"]
|
|
362
144
|
|
|
363
|
-
|
|
145
|
+
# Layer tags
|
|
146
|
+
tags: ["layer:frontend", "layer:backend", "layer:infrastructure"]
|
|
364
147
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
| `:fact` | 7.0-10.0 | User identity, system facts |
|
|
368
|
-
| `:decision` | 7.0-10.0 | Architecture, major choices |
|
|
369
|
-
| `:preference` | 4.0-8.0 | User preferences |
|
|
370
|
-
| `:code` | 4.0-7.0 | Code snippets, examples |
|
|
371
|
-
| `:context` | 3.0-6.0 | Conversation state |
|
|
372
|
-
| `:question` | 5.0-8.0 | Open questions |
|
|
148
|
+
# Technology tags
|
|
149
|
+
tags: ["tech:ruby", "tech:javascript", "tech:docker"]
|
|
373
150
|
|
|
374
|
-
|
|
375
|
-
|
|
151
|
+
# Project tags
|
|
152
|
+
tags: ["project:alpha", "project:beta"]
|
|
153
|
+
```
|
|
376
154
|
|
|
377
|
-
|
|
155
|
+
### Automatic Tag Extraction
|
|
378
156
|
|
|
379
|
-
|
|
157
|
+
When a node is created, a background job (GenerateTagsJob) automatically extracts hierarchical tags from the content using an LLM. This happens asynchronously.
|
|
380
158
|
|
|
381
159
|
```ruby
|
|
382
|
-
#
|
|
383
|
-
htm.
|
|
384
|
-
|
|
385
|
-
"Use PostgreSQL for data storage",
|
|
386
|
-
type: :decision,
|
|
387
|
-
importance: 9.0
|
|
388
|
-
)
|
|
389
|
-
|
|
390
|
-
# Add related implementation code
|
|
391
|
-
htm.add_node(
|
|
392
|
-
"code_db_connection",
|
|
393
|
-
"PG.connect(ENV['DATABASE_URL'])",
|
|
394
|
-
type: :code,
|
|
395
|
-
importance: 6.0,
|
|
396
|
-
related_to: ["decision_database"]
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
# Add related configuration
|
|
400
|
-
htm.add_node(
|
|
401
|
-
"fact_db_config",
|
|
402
|
-
"Database uses connection pool of size 5",
|
|
403
|
-
type: :fact,
|
|
404
|
-
importance: 7.0,
|
|
405
|
-
related_to: ["decision_database", "code_db_connection"]
|
|
406
|
-
)
|
|
160
|
+
# Just provide content, tags are auto-extracted
|
|
161
|
+
htm.remember("We're using Redis for session caching with a 24-hour TTL")
|
|
162
|
+
# Background job might extract: ["database:redis", "caching:session", "performance"]
|
|
407
163
|
```
|
|
408
164
|
|
|
409
|
-
|
|
410
|
-
- Link implementation code to decisions
|
|
411
|
-
- Connect questions to related facts
|
|
412
|
-
- Link preferences to user facts
|
|
413
|
-
- Connect related decisions (e.g., database choice → ORM choice)
|
|
165
|
+
## Using Metadata
|
|
414
166
|
|
|
415
|
-
|
|
167
|
+
Metadata provides flexible key-value storage for arbitrary data that doesn't fit into tags. Unlike tags (which are for hierarchical categorization), metadata is for structured data like version numbers, priorities, source systems, or any custom attributes.
|
|
416
168
|
|
|
417
|
-
|
|
169
|
+
### Basic Metadata Usage
|
|
418
170
|
|
|
419
171
|
```ruby
|
|
420
|
-
#
|
|
421
|
-
htm.
|
|
422
|
-
"
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
"
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
172
|
+
# Store with metadata
|
|
173
|
+
htm.remember(
|
|
174
|
+
"User prefers dark mode",
|
|
175
|
+
metadata: { category: "preference", priority: "high" }
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Multiple metadata fields
|
|
179
|
+
htm.remember(
|
|
180
|
+
"API endpoint changed from /v1 to /v2",
|
|
181
|
+
metadata: {
|
|
182
|
+
category: "migration",
|
|
183
|
+
version: 2,
|
|
184
|
+
breaking_change: true,
|
|
185
|
+
affected_services: ["web", "mobile"]
|
|
186
|
+
}
|
|
434
187
|
)
|
|
435
188
|
```
|
|
436
189
|
|
|
437
|
-
###
|
|
190
|
+
### Metadata vs Tags
|
|
438
191
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
192
|
+
| Feature | Tags | Metadata |
|
|
193
|
+
|---------|------|----------|
|
|
194
|
+
| Structure | Hierarchical (colon-separated) | Flat key-value pairs |
|
|
195
|
+
| Type | String only | Any JSON type (string, number, boolean, array, object) |
|
|
196
|
+
| Search | Prefix matching (`LIKE 'ai:%'`) | JSONB containment (`@>`) |
|
|
197
|
+
| Purpose | Categorization & navigation | Arbitrary attributes & filtering |
|
|
198
|
+
| Auto-extraction | Yes (via LLM) | No (always explicit) |
|
|
442
199
|
|
|
443
|
-
|
|
444
|
-
tags: ["User", "auth", "stuff", "misc"]
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### Common Tag Patterns
|
|
200
|
+
### Common Metadata Patterns
|
|
448
201
|
|
|
449
202
|
```ruby
|
|
450
|
-
#
|
|
451
|
-
|
|
203
|
+
# Version tracking
|
|
204
|
+
htm.remember("API uses OAuth 2.0", metadata: { version: 3, deprecated: false })
|
|
452
205
|
|
|
453
|
-
#
|
|
454
|
-
|
|
206
|
+
# Source tracking
|
|
207
|
+
htm.remember("Error rate is 0.1%", metadata: { source: "monitoring", dashboard: "errors" })
|
|
455
208
|
|
|
456
|
-
#
|
|
457
|
-
|
|
209
|
+
# Priority/importance
|
|
210
|
+
htm.remember("Deploy to prod on Fridays is forbidden", metadata: { priority: "critical" })
|
|
458
211
|
|
|
459
|
-
#
|
|
460
|
-
|
|
212
|
+
# Environment-specific
|
|
213
|
+
htm.remember("Database connection limit is 100", metadata: { environment: "production" })
|
|
461
214
|
|
|
462
|
-
#
|
|
463
|
-
|
|
215
|
+
# Combining with tags
|
|
216
|
+
htm.remember(
|
|
217
|
+
"Use connection pooling for better performance",
|
|
218
|
+
tags: ["database:postgresql", "performance"],
|
|
219
|
+
metadata: { priority: "high", reviewed: true, author: "dba-team" }
|
|
220
|
+
)
|
|
464
221
|
```
|
|
465
222
|
|
|
466
|
-
|
|
223
|
+
### Querying by Metadata
|
|
467
224
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
Create time-series logs:
|
|
225
|
+
Use the `metadata` parameter in `recall()` to filter by metadata:
|
|
471
226
|
|
|
472
227
|
```ruby
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
htm.add_node(
|
|
477
|
-
"event_#{event_type}_#{timestamp}",
|
|
478
|
-
"#{event_type.upcase}: #{description}",
|
|
479
|
-
type: :context,
|
|
480
|
-
importance: 5.0,
|
|
481
|
-
tags: ["event", event_type, "log"]
|
|
482
|
-
)
|
|
483
|
-
end
|
|
228
|
+
# Find all high-priority items
|
|
229
|
+
htm.recall("settings", metadata: { priority: "high" })
|
|
484
230
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
```
|
|
231
|
+
# Find production-specific configurations
|
|
232
|
+
htm.recall("database", metadata: { environment: "production" })
|
|
488
233
|
|
|
489
|
-
|
|
234
|
+
# Combine with other filters
|
|
235
|
+
htm.recall(
|
|
236
|
+
"API changes",
|
|
237
|
+
timeframe: "last month",
|
|
238
|
+
metadata: { breaking_change: true },
|
|
239
|
+
strategy: :hybrid
|
|
240
|
+
)
|
|
241
|
+
```
|
|
490
242
|
|
|
491
|
-
|
|
243
|
+
Metadata filtering uses PostgreSQL's JSONB containment operator (`@>`), which means the node's metadata must contain all the key-value pairs you specify.
|
|
492
244
|
|
|
493
|
-
|
|
494
|
-
def update_fact(base_key, new_value, version)
|
|
495
|
-
# Add versioned node
|
|
496
|
-
htm.add_node(
|
|
497
|
-
"#{base_key}_v#{version}",
|
|
498
|
-
new_value,
|
|
499
|
-
type: :fact,
|
|
500
|
-
importance: 8.0,
|
|
501
|
-
tags: ["versioned", "v#{version}"],
|
|
502
|
-
related_to: version > 1 ? ["#{base_key}_v#{version-1}"] : []
|
|
503
|
-
)
|
|
504
|
-
end
|
|
245
|
+
## Content Deduplication
|
|
505
246
|
|
|
506
|
-
|
|
507
|
-
update_fact("user_email", "alice@newdomain.com", 2)
|
|
508
|
-
```
|
|
247
|
+
HTM automatically deduplicates content across all robots using SHA-256 hashing.
|
|
509
248
|
|
|
510
|
-
###
|
|
511
|
-
|
|
512
|
-
Store structured information:
|
|
249
|
+
### How It Works
|
|
513
250
|
|
|
514
251
|
```ruby
|
|
515
|
-
#
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
"user_profile_001",
|
|
525
|
-
user_profile,
|
|
526
|
-
type: :fact,
|
|
527
|
-
importance: 9.0,
|
|
528
|
-
tags: ["user", "profile", "complete"]
|
|
529
|
-
)
|
|
252
|
+
# Robot 1 remembers something
|
|
253
|
+
robot1 = HTM.new(robot_name: "assistant_1")
|
|
254
|
+
node_id = robot1.remember("Ruby 3.3 supports YJIT by default")
|
|
255
|
+
# => 123 (new node)
|
|
256
|
+
|
|
257
|
+
# Robot 2 remembers the same thing
|
|
258
|
+
robot2 = HTM.new(robot_name: "assistant_2")
|
|
259
|
+
node_id = robot2.remember("Ruby 3.3 supports YJIT by default")
|
|
260
|
+
# => 123 (same node_id! Content matched by hash)
|
|
530
261
|
```
|
|
531
262
|
|
|
532
|
-
###
|
|
263
|
+
### Robot-Node Association
|
|
533
264
|
|
|
534
|
-
|
|
265
|
+
Each robot-node relationship is tracked in `robot_nodes`:
|
|
535
266
|
|
|
536
267
|
```ruby
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
htm.add_node(
|
|
544
|
-
key,
|
|
545
|
-
value,
|
|
546
|
-
type: type,
|
|
547
|
-
importance: importance,
|
|
548
|
-
tags: [current_project, type.to_s]
|
|
549
|
-
)
|
|
550
|
-
end
|
|
268
|
+
# Check how many times a robot has "remembered" content
|
|
269
|
+
rn = HTM::Models::RobotNode.find_by(robot_id: htm.robot_id, node_id: node_id)
|
|
270
|
+
rn.remember_count # => 3 (remembered 3 times)
|
|
271
|
+
rn.first_remembered_at # => When first encountered
|
|
272
|
+
rn.last_remembered_at # => When last tried to remember
|
|
551
273
|
```
|
|
552
274
|
|
|
553
275
|
## Best Practices
|
|
554
276
|
|
|
555
|
-
### 1.
|
|
556
|
-
|
|
557
|
-
```ruby
|
|
558
|
-
# Good: Descriptive and namespaced
|
|
559
|
-
"user_profile_alice_001"
|
|
560
|
-
"decision_database_selection"
|
|
561
|
-
"code_authentication_jwt"
|
|
562
|
-
|
|
563
|
-
# Bad: Vague or collision-prone
|
|
564
|
-
"profile"
|
|
565
|
-
"dec1"
|
|
566
|
-
"code"
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
### 2. Be Consistent with Categories
|
|
277
|
+
### 1. Make Content Self-Contained
|
|
570
278
|
|
|
571
279
|
```ruby
|
|
572
|
-
#
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
security: "security",
|
|
576
|
-
performance: "performance",
|
|
577
|
-
ui: "user-interface"
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
htm.add_node(
|
|
581
|
-
key, value,
|
|
582
|
-
category: CATEGORIES[:architecture]
|
|
280
|
+
# Good: Self-contained, understandable without context
|
|
281
|
+
htm.remember(
|
|
282
|
+
"Decided to use Redis for session storage because it provides fast access and automatic expiration"
|
|
583
283
|
)
|
|
284
|
+
|
|
285
|
+
# Bad: Requires external context
|
|
286
|
+
htm.remember("Use Redis") # Why? For what?
|
|
584
287
|
```
|
|
585
288
|
|
|
586
|
-
###
|
|
289
|
+
### 2. Include Rich Context
|
|
587
290
|
|
|
588
291
|
```ruby
|
|
589
|
-
# Good:
|
|
590
|
-
htm.
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
292
|
+
# Good: Includes rationale and alternatives
|
|
293
|
+
htm.remember(<<~DECISION)
|
|
294
|
+
Decision: Use OAuth 2.0 for authentication
|
|
295
|
+
|
|
296
|
+
Rationale:
|
|
297
|
+
- Industry standard
|
|
298
|
+
- Better security than basic auth
|
|
299
|
+
- Supports SSO
|
|
300
|
+
|
|
301
|
+
Alternatives considered:
|
|
302
|
+
- Basic auth (rejected: security concerns)
|
|
303
|
+
- Custom tokens (rejected: maintenance burden)
|
|
304
|
+
DECISION
|
|
602
305
|
```
|
|
603
306
|
|
|
604
|
-
###
|
|
307
|
+
### 3. Use Hierarchical Tags
|
|
605
308
|
|
|
606
309
|
```ruby
|
|
607
310
|
# Good: Rich tags for multiple retrieval paths
|
|
608
|
-
htm.
|
|
609
|
-
"
|
|
610
|
-
"
|
|
611
|
-
tags: ["api", "authentication", "security", "jwt", "middleware", "ruby"]
|
|
311
|
+
htm.remember(
|
|
312
|
+
"JWT tokens are stateless authentication tokens",
|
|
313
|
+
tags: ["auth:jwt", "security:tokens", "architecture:stateless"]
|
|
612
314
|
)
|
|
613
315
|
|
|
614
|
-
# Suboptimal:
|
|
615
|
-
htm.
|
|
616
|
-
"code_api_auth",
|
|
617
|
-
"...",
|
|
618
|
-
tags: ["code"]
|
|
619
|
-
)
|
|
316
|
+
# Suboptimal: Flat or minimal tags
|
|
317
|
+
htm.remember("JWT info", tags: ["jwt"])
|
|
620
318
|
```
|
|
621
319
|
|
|
622
|
-
###
|
|
320
|
+
### 4. Keep Content Focused
|
|
623
321
|
|
|
624
322
|
```ruby
|
|
625
|
-
#
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
htm.add_node(
|
|
629
|
-
"question_api",
|
|
630
|
-
"How to handle file uploads in GraphQL?",
|
|
631
|
-
type: :question,
|
|
632
|
-
related_to: ["decision_api"]
|
|
633
|
-
)
|
|
323
|
+
# Good: One concept per memory
|
|
324
|
+
htm.remember("PostgreSQL's EXPLAIN ANALYZE shows actual execution times")
|
|
325
|
+
htm.remember("PostgreSQL's EXPLAIN shows the query plan without executing")
|
|
634
326
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
"GraphQL upload implementation",
|
|
638
|
-
type: :code,
|
|
639
|
-
related_to: ["decision_api", "question_api"]
|
|
640
|
-
)
|
|
327
|
+
# Suboptimal: Multiple unrelated concepts
|
|
328
|
+
htm.remember("PostgreSQL has EXPLAIN and also supports JSON and has good performance")
|
|
641
329
|
```
|
|
642
330
|
|
|
643
|
-
##
|
|
644
|
-
|
|
645
|
-
### Pitfall 1: Duplicate Keys
|
|
331
|
+
## Async Processing
|
|
646
332
|
|
|
647
|
-
|
|
648
|
-
# This will fail - keys must be unique
|
|
649
|
-
htm.add_node("user_001", "Alice")
|
|
650
|
-
htm.add_node("user_001", "Bob") # Error: key already exists
|
|
651
|
-
```
|
|
333
|
+
Embedding generation and tag extraction happen asynchronously:
|
|
652
334
|
|
|
653
|
-
|
|
335
|
+
### Workflow
|
|
654
336
|
|
|
655
337
|
```ruby
|
|
656
|
-
|
|
338
|
+
# 1. Node created immediately (~15ms)
|
|
339
|
+
node_id = htm.remember("Important fact about databases")
|
|
340
|
+
# Returns immediately with node_id
|
|
657
341
|
|
|
658
|
-
|
|
659
|
-
|
|
342
|
+
# 2. Background jobs enqueue (async)
|
|
343
|
+
# - GenerateEmbeddingJob runs (~100ms)
|
|
344
|
+
# - GenerateTagsJob runs (~1 second)
|
|
345
|
+
|
|
346
|
+
# 3. Node is eventually enriched
|
|
347
|
+
# - embedding field populated (enables vector search)
|
|
348
|
+
# - tags associated (enables tag navigation and boosting)
|
|
660
349
|
```
|
|
661
350
|
|
|
662
|
-
###
|
|
351
|
+
### Immediate vs Eventual Capabilities
|
|
663
352
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
353
|
+
| Capability | Available | Notes |
|
|
354
|
+
|------------|-----------|-------|
|
|
355
|
+
| Full-text search | Immediately | Works on content |
|
|
356
|
+
| Basic retrieval | Immediately | By node ID |
|
|
357
|
+
| Vector search | After ~100ms | Needs embedding |
|
|
358
|
+
| Tag-enhanced search | After ~1s | Needs tags |
|
|
359
|
+
| Hybrid search | After ~1s | Needs embedding + tags |
|
|
668
360
|
|
|
669
|
-
|
|
361
|
+
## Working Memory Integration
|
|
670
362
|
|
|
671
|
-
|
|
363
|
+
When you `remember()`, the node is automatically added to working memory:
|
|
672
364
|
|
|
673
365
|
```ruby
|
|
674
|
-
#
|
|
675
|
-
htm.
|
|
676
|
-
|
|
677
|
-
#
|
|
678
|
-
htm.
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
type: :decision
|
|
682
|
-
)
|
|
366
|
+
# Remember adds to both LTM and WM
|
|
367
|
+
htm.remember("Important fact")
|
|
368
|
+
|
|
369
|
+
# Check working memory
|
|
370
|
+
stats = htm.working_memory.stats
|
|
371
|
+
puts "Nodes in WM: #{stats[:node_count]}"
|
|
372
|
+
puts "Token usage: #{stats[:utilization]}%"
|
|
683
373
|
```
|
|
684
374
|
|
|
685
|
-
###
|
|
375
|
+
### Eviction
|
|
376
|
+
|
|
377
|
+
If working memory is full, older/less important nodes are evicted to make room:
|
|
686
378
|
|
|
687
379
|
```ruby
|
|
688
|
-
#
|
|
689
|
-
htm.
|
|
690
|
-
|
|
691
|
-
#
|
|
692
|
-
|
|
693
|
-
"code_001",
|
|
694
|
-
"def foo...",
|
|
695
|
-
type: :code,
|
|
696
|
-
tags: ["ruby", "functions", "utilities"]
|
|
697
|
-
)
|
|
380
|
+
# Working memory has a token budget
|
|
381
|
+
htm = HTM.new(working_memory_size: 128_000) # 128K tokens
|
|
382
|
+
|
|
383
|
+
# As you remember more, older items may be evicted from WM
|
|
384
|
+
# They remain in LTM and can be recalled later
|
|
698
385
|
```
|
|
699
386
|
|
|
700
387
|
## Performance Considerations
|
|
701
388
|
|
|
702
389
|
### Batch Operations
|
|
703
390
|
|
|
704
|
-
|
|
391
|
+
Each `remember()` call is a database operation. For bulk inserts:
|
|
705
392
|
|
|
706
393
|
```ruby
|
|
707
|
-
#
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
394
|
+
# Multiple memories
|
|
395
|
+
facts = [
|
|
396
|
+
"PostgreSQL supports JSONB",
|
|
397
|
+
"PostgreSQL has excellent indexing",
|
|
398
|
+
"PostgreSQL handles concurrent writes well"
|
|
712
399
|
]
|
|
713
400
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
htm.add_node(m[:key], m[:value], type: m[:type], importance: m[:importance])
|
|
401
|
+
facts.each do |fact|
|
|
402
|
+
htm.remember(fact)
|
|
717
403
|
end
|
|
718
404
|
```
|
|
719
405
|
|
|
720
|
-
|
|
721
|
-
Each `add_node` call generates embeddings via Ollama. For large batches, this can take time. Consider adding in the background or showing progress.
|
|
722
|
-
|
|
723
|
-
### Embedding Generation
|
|
406
|
+
### Content Length
|
|
724
407
|
|
|
725
|
-
|
|
408
|
+
Longer content takes more time to process:
|
|
726
409
|
|
|
727
410
|
```ruby
|
|
728
|
-
# Short text: Fast (~
|
|
729
|
-
htm.
|
|
411
|
+
# Short text: Fast (~15ms save, ~100ms embedding)
|
|
412
|
+
htm.remember("User name is Alice")
|
|
730
413
|
|
|
731
|
-
# Long text: Slower (~500ms)
|
|
732
|
-
htm.
|
|
414
|
+
# Long text: Slower (~15ms save, ~500ms embedding)
|
|
415
|
+
htm.remember("..." * 1000) # 1000 chars
|
|
733
416
|
```
|
|
734
417
|
|
|
735
|
-
|
|
736
|
-
For very long content (>1000 tokens), consider splitting into multiple nodes or summarizing.
|
|
418
|
+
For very long content (>1000 tokens), consider splitting into multiple memories.
|
|
737
419
|
|
|
738
420
|
## Next Steps
|
|
739
421
|
|
|
740
422
|
Now that you know how to add memories effectively, learn about:
|
|
741
423
|
|
|
742
|
-
- [**Recalling Memories**](recalling-memories.md) - Search and retrieve memories
|
|
743
424
|
- [**Search Strategies**](search-strategies.md) - Optimize retrieval with different strategies
|
|
744
|
-
- [**
|
|
425
|
+
- [**Recalling Memories**](recalling-memories.md) - Search and retrieve memories
|
|
745
426
|
|
|
746
427
|
## Complete Example
|
|
747
428
|
|
|
@@ -750,75 +431,46 @@ require 'htm'
|
|
|
750
431
|
|
|
751
432
|
htm = HTM.new(robot_name: "Memory Demo")
|
|
752
433
|
|
|
753
|
-
# Add a fact
|
|
754
|
-
htm.
|
|
755
|
-
"
|
|
756
|
-
"Alice Thompson is a senior software engineer specializing in distributed systems",
|
|
757
|
-
type: :fact,
|
|
758
|
-
category: "user",
|
|
759
|
-
importance: 9.0,
|
|
760
|
-
tags: ["user", "profile", "engineering"]
|
|
434
|
+
# Add a fact
|
|
435
|
+
htm.remember(
|
|
436
|
+
"Alice Thompson is a senior software engineer specializing in distributed systems"
|
|
761
437
|
)
|
|
762
438
|
|
|
763
|
-
# Add a
|
|
764
|
-
htm.
|
|
765
|
-
"user_pref_tools",
|
|
439
|
+
# Add a preference with metadata
|
|
440
|
+
htm.remember(
|
|
766
441
|
"Alice prefers Vim for editing and tmux for terminal management",
|
|
767
|
-
|
|
768
|
-
importance: 7.0,
|
|
769
|
-
tags: ["user", "tools", "preferences"],
|
|
770
|
-
related_to: ["user_profile"]
|
|
442
|
+
metadata: { category: "preference", source: "user-interview" }
|
|
771
443
|
)
|
|
772
444
|
|
|
773
|
-
# Add a decision with context
|
|
774
|
-
htm.
|
|
775
|
-
|
|
776
|
-
<<~DECISION,
|
|
777
|
-
Decision: Use RabbitMQ for async job processing
|
|
778
|
-
|
|
779
|
-
Rationale:
|
|
780
|
-
- Need reliable message delivery
|
|
781
|
-
- Support for multiple consumer patterns
|
|
782
|
-
- Excellent Ruby client library
|
|
783
|
-
|
|
784
|
-
Alternatives:
|
|
785
|
-
- Redis (simpler but less reliable)
|
|
786
|
-
- Kafka (overkill for our scale)
|
|
787
|
-
DECISION
|
|
788
|
-
type: :decision,
|
|
789
|
-
category: "architecture",
|
|
790
|
-
importance: 8.5,
|
|
791
|
-
tags: ["architecture", "messaging", "rabbitmq", "async"]
|
|
792
|
-
)
|
|
445
|
+
# Add a decision with context, tags, and metadata
|
|
446
|
+
htm.remember(<<~DECISION, tags: ["architecture", "messaging"], metadata: { priority: "high", approved: true, version: 1 })
|
|
447
|
+
Decision: Use RabbitMQ for async job processing
|
|
793
448
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
require 'bunny'
|
|
799
|
-
|
|
800
|
-
connection = Bunny.new(ENV['RABBITMQ_URL'])
|
|
801
|
-
connection.start
|
|
802
|
-
|
|
803
|
-
channel = connection.create_channel
|
|
804
|
-
queue = channel.queue('jobs', durable: true)
|
|
805
|
-
RUBY
|
|
806
|
-
type: :code,
|
|
807
|
-
importance: 6.0,
|
|
808
|
-
tags: ["ruby", "rabbitmq", "setup", "code"],
|
|
809
|
-
related_to: ["decision_messaging"]
|
|
810
|
-
)
|
|
449
|
+
Rationale:
|
|
450
|
+
- Need reliable message delivery
|
|
451
|
+
- Support for multiple consumer patterns
|
|
452
|
+
- Excellent Ruby client library
|
|
811
453
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
454
|
+
Alternatives:
|
|
455
|
+
- Redis (simpler but less reliable)
|
|
456
|
+
- Kafka (overkill for our scale)
|
|
457
|
+
DECISION
|
|
458
|
+
|
|
459
|
+
# Add implementation code with metadata
|
|
460
|
+
htm.remember(<<~RUBY, tags: ["code:ruby", "messaging:rabbitmq"], metadata: { language: "ruby", tested: true })
|
|
461
|
+
require 'bunny'
|
|
462
|
+
|
|
463
|
+
connection = Bunny.new(ENV['RABBITMQ_URL'])
|
|
464
|
+
connection.start
|
|
465
|
+
|
|
466
|
+
channel = connection.create_channel
|
|
467
|
+
queue = channel.queue('jobs', durable: true)
|
|
468
|
+
RUBY
|
|
469
|
+
|
|
470
|
+
puts "Added memories with relationships and rich metadata"
|
|
471
|
+
puts "Stats: #{HTM::Models::Node.count} total nodes"
|
|
821
472
|
|
|
822
|
-
|
|
823
|
-
|
|
473
|
+
# Query by metadata
|
|
474
|
+
high_priority = htm.recall("decisions", metadata: { priority: "high" })
|
|
475
|
+
puts "High priority decisions: #{high_priority.count}"
|
|
824
476
|
```
|