htm 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. checksums.yaml +7 -0
  2. data/.architecture/decisions/adrs/001-use-postgresql-timescaledb-storage.md +227 -0
  3. data/.architecture/decisions/adrs/002-two-tier-memory-architecture.md +322 -0
  4. data/.architecture/decisions/adrs/003-ollama-default-embedding-provider.md +339 -0
  5. data/.architecture/decisions/adrs/004-multi-robot-shared-memory-hive-mind.md +374 -0
  6. data/.architecture/decisions/adrs/005-rag-based-retrieval-with-hybrid-search.md +443 -0
  7. data/.architecture/decisions/adrs/006-context-assembly-strategies.md +444 -0
  8. data/.architecture/decisions/adrs/007-working-memory-eviction-strategy.md +461 -0
  9. data/.architecture/decisions/adrs/008-robot-identification-system.md +550 -0
  10. data/.architecture/decisions/adrs/009-never-forget-explicit-deletion-only.md +570 -0
  11. data/.architecture/decisions/adrs/010-redis-working-memory-rejected.md +323 -0
  12. data/.architecture/decisions/adrs/011-database-side-embedding-generation-with-pgai.md +585 -0
  13. data/.architecture/decisions/adrs/012-llm-driven-ontology-topic-extraction.md +583 -0
  14. data/.architecture/decisions/adrs/013-activerecord-orm-and-many-to-many-tagging.md +299 -0
  15. data/.architecture/decisions/adrs/014-client-side-embedding-generation-workflow.md +569 -0
  16. data/.architecture/decisions/adrs/015-hierarchical-tag-ontology-and-llm-extraction.md +701 -0
  17. data/.architecture/decisions/adrs/016-async-embedding-and-tag-generation.md +694 -0
  18. data/.architecture/members.yml +144 -0
  19. data/.architecture/reviews/2025-10-29-llm-configuration-and-async-processing-review.md +1137 -0
  20. data/.architecture/reviews/initial-system-analysis.md +330 -0
  21. data/.envrc +32 -0
  22. data/.irbrc +145 -0
  23. data/CHANGELOG.md +150 -0
  24. data/COMMITS.md +196 -0
  25. data/LICENSE +21 -0
  26. data/README.md +1347 -0
  27. data/Rakefile +51 -0
  28. data/SETUP.md +268 -0
  29. data/config/database.yml +67 -0
  30. data/db/migrate/20250101000001_enable_extensions.rb +14 -0
  31. data/db/migrate/20250101000002_create_robots.rb +14 -0
  32. data/db/migrate/20250101000003_create_nodes.rb +42 -0
  33. data/db/migrate/20250101000005_create_tags.rb +38 -0
  34. data/db/migrate/20250101000007_add_node_vector_indexes.rb +30 -0
  35. data/db/schema.sql +473 -0
  36. data/db/seed_data/README.md +100 -0
  37. data/db/seed_data/presidents.md +136 -0
  38. data/db/seed_data/states.md +151 -0
  39. data/db/seeds.rb +208 -0
  40. data/dbdoc/README.md +173 -0
  41. data/dbdoc/public.node_stats.md +48 -0
  42. data/dbdoc/public.node_stats.svg +41 -0
  43. data/dbdoc/public.node_tags.md +40 -0
  44. data/dbdoc/public.node_tags.svg +112 -0
  45. data/dbdoc/public.nodes.md +54 -0
  46. data/dbdoc/public.nodes.svg +118 -0
  47. data/dbdoc/public.nodes_tags.md +39 -0
  48. data/dbdoc/public.nodes_tags.svg +112 -0
  49. data/dbdoc/public.ontology_structure.md +48 -0
  50. data/dbdoc/public.ontology_structure.svg +38 -0
  51. data/dbdoc/public.operations_log.md +42 -0
  52. data/dbdoc/public.operations_log.svg +130 -0
  53. data/dbdoc/public.relationships.md +39 -0
  54. data/dbdoc/public.relationships.svg +41 -0
  55. data/dbdoc/public.robot_activity.md +46 -0
  56. data/dbdoc/public.robot_activity.svg +35 -0
  57. data/dbdoc/public.robots.md +35 -0
  58. data/dbdoc/public.robots.svg +90 -0
  59. data/dbdoc/public.schema_migrations.md +29 -0
  60. data/dbdoc/public.schema_migrations.svg +26 -0
  61. data/dbdoc/public.tags.md +35 -0
  62. data/dbdoc/public.tags.svg +60 -0
  63. data/dbdoc/public.topic_relationships.md +45 -0
  64. data/dbdoc/public.topic_relationships.svg +32 -0
  65. data/dbdoc/schema.json +1437 -0
  66. data/dbdoc/schema.svg +154 -0
  67. data/docs/api/database.md +806 -0
  68. data/docs/api/embedding-service.md +532 -0
  69. data/docs/api/htm.md +797 -0
  70. data/docs/api/index.md +259 -0
  71. data/docs/api/long-term-memory.md +1096 -0
  72. data/docs/api/working-memory.md +665 -0
  73. data/docs/architecture/adrs/001-postgresql-timescaledb.md +314 -0
  74. data/docs/architecture/adrs/002-two-tier-memory.md +411 -0
  75. data/docs/architecture/adrs/003-ollama-embeddings.md +421 -0
  76. data/docs/architecture/adrs/004-hive-mind.md +437 -0
  77. data/docs/architecture/adrs/005-rag-retrieval.md +531 -0
  78. data/docs/architecture/adrs/006-context-assembly.md +496 -0
  79. data/docs/architecture/adrs/007-eviction-strategy.md +645 -0
  80. data/docs/architecture/adrs/008-robot-identification.md +625 -0
  81. data/docs/architecture/adrs/009-never-forget.md +648 -0
  82. data/docs/architecture/adrs/010-redis-working-memory-rejected.md +323 -0
  83. data/docs/architecture/adrs/011-pgai-integration.md +494 -0
  84. data/docs/architecture/adrs/index.md +215 -0
  85. data/docs/architecture/hive-mind.md +736 -0
  86. data/docs/architecture/index.md +351 -0
  87. data/docs/architecture/overview.md +538 -0
  88. data/docs/architecture/two-tier-memory.md +873 -0
  89. data/docs/assets/css/custom.css +83 -0
  90. data/docs/assets/images/htm-core-components.svg +63 -0
  91. data/docs/assets/images/htm-database-schema.svg +93 -0
  92. data/docs/assets/images/htm-hive-mind-architecture.svg +125 -0
  93. data/docs/assets/images/htm-importance-scoring-framework.svg +83 -0
  94. data/docs/assets/images/htm-layered-architecture.svg +71 -0
  95. data/docs/assets/images/htm-long-term-memory-architecture.svg +115 -0
  96. data/docs/assets/images/htm-working-memory-architecture.svg +120 -0
  97. data/docs/assets/images/htm.jpg +0 -0
  98. data/docs/assets/images/htm_demo.gif +0 -0
  99. data/docs/assets/js/mathjax.js +18 -0
  100. data/docs/assets/videos/htm_video.mp4 +0 -0
  101. data/docs/database_rake_tasks.md +322 -0
  102. data/docs/development/contributing.md +787 -0
  103. data/docs/development/index.md +336 -0
  104. data/docs/development/schema.md +596 -0
  105. data/docs/development/setup.md +719 -0
  106. data/docs/development/testing.md +819 -0
  107. data/docs/guides/adding-memories.md +824 -0
  108. data/docs/guides/context-assembly.md +1009 -0
  109. data/docs/guides/getting-started.md +577 -0
  110. data/docs/guides/index.md +118 -0
  111. data/docs/guides/long-term-memory.md +941 -0
  112. data/docs/guides/multi-robot.md +866 -0
  113. data/docs/guides/recalling-memories.md +927 -0
  114. data/docs/guides/search-strategies.md +953 -0
  115. data/docs/guides/working-memory.md +717 -0
  116. data/docs/index.md +214 -0
  117. data/docs/installation.md +477 -0
  118. data/docs/multi_framework_support.md +519 -0
  119. data/docs/quick-start.md +655 -0
  120. data/docs/setup_local_database.md +302 -0
  121. data/docs/using_rake_tasks_in_your_app.md +383 -0
  122. data/examples/basic_usage.rb +93 -0
  123. data/examples/cli_app/README.md +317 -0
  124. data/examples/cli_app/htm_cli.rb +270 -0
  125. data/examples/custom_llm_configuration.rb +183 -0
  126. data/examples/example_app/Rakefile +71 -0
  127. data/examples/example_app/app.rb +206 -0
  128. data/examples/sinatra_app/Gemfile +21 -0
  129. data/examples/sinatra_app/app.rb +335 -0
  130. data/lib/htm/active_record_config.rb +113 -0
  131. data/lib/htm/configuration.rb +342 -0
  132. data/lib/htm/database.rb +594 -0
  133. data/lib/htm/embedding_service.rb +115 -0
  134. data/lib/htm/errors.rb +34 -0
  135. data/lib/htm/job_adapter.rb +154 -0
  136. data/lib/htm/jobs/generate_embedding_job.rb +65 -0
  137. data/lib/htm/jobs/generate_tags_job.rb +82 -0
  138. data/lib/htm/long_term_memory.rb +965 -0
  139. data/lib/htm/models/node.rb +109 -0
  140. data/lib/htm/models/node_tag.rb +33 -0
  141. data/lib/htm/models/robot.rb +52 -0
  142. data/lib/htm/models/tag.rb +76 -0
  143. data/lib/htm/railtie.rb +76 -0
  144. data/lib/htm/sinatra.rb +157 -0
  145. data/lib/htm/tag_service.rb +135 -0
  146. data/lib/htm/tasks.rb +38 -0
  147. data/lib/htm/version.rb +5 -0
  148. data/lib/htm/working_memory.rb +182 -0
  149. data/lib/htm.rb +400 -0
  150. data/lib/tasks/db.rake +19 -0
  151. data/lib/tasks/htm.rake +147 -0
  152. data/lib/tasks/jobs.rake +312 -0
  153. data/mkdocs.yml +190 -0
  154. data/scripts/install_local_database.sh +309 -0
  155. metadata +341 -0
@@ -0,0 +1,461 @@
1
+ # ADR-007: Working Memory Eviction Strategy
2
+
3
+ **Status**: Accepted
4
+
5
+ **Date**: 2025-10-25
6
+
7
+ **Decision Makers**: Dewayne VanHoozer, Claude (Anthropic)
8
+
9
+ ## Context
10
+
11
+ Working memory is token-limited (default 128,000 tokens). When adding new memories that would exceed the token limit, HTM must decide which existing memories to evict.
12
+
13
+ Eviction challenges:
14
+
15
+ - **Token limits**: Hard constraint, cannot exceed working memory capacity
16
+ - **Importance preservation**: Critical memories should remain in working memory
17
+ - **Recency**: Recent context often more relevant than old context
18
+ - **Fairness**: Don't always evict the same types of memories
19
+ - **Performance**: Eviction should be fast (< 10ms)
20
+ - **Never forget**: Evicted memories must be preserved in long-term memory
21
+
22
+ Alternative eviction policies:
23
+
24
+ 1. **FIFO (First-In-First-Out)**: Evict oldest memories
25
+ 2. **LRU (Least Recently Used)**: Evict least recently accessed
26
+ 3. **LFU (Least Frequently Used)**: Evict least frequently accessed
27
+ 4. **Random**: Evict random memories
28
+ 5. **Importance-only**: Evict lowest importance first
29
+ 6. **Hybrid**: Combine importance and recency with scoring function
30
+
31
+ ## Decision
32
+
33
+ We will implement a **hybrid eviction strategy** that prioritizes memories by both importance and recency, evicting low-importance older memories first.
34
+
35
+ ### Eviction Algorithm
36
+
37
+ ```ruby
38
+ def evict_to_make_space(needed_tokens)
39
+ # Sort by [importance, -recency]
40
+ # Lower importance evicted first
41
+ # Within same importance, older memories evicted first
42
+ candidates = @nodes.sort_by do |key, node|
43
+ recency = Time.now - node[:added_at]
44
+ [node[:importance], -recency]
45
+ end
46
+
47
+ # Evict from front until enough space
48
+ evicted = []
49
+ tokens_freed = 0
50
+
51
+ candidates.each do |key, node|
52
+ break if tokens_freed >= needed_tokens
53
+
54
+ evicted << { key: key, value: node[:value] }
55
+ tokens_freed += node[:token_count]
56
+ @nodes.delete(key)
57
+ @access_order.delete(key)
58
+ end
59
+
60
+ evicted
61
+ end
62
+ ```
63
+
64
+ ### Eviction Scoring
65
+
66
+ **Primary sort**: Importance (ascending)
67
+
68
+ - Importance 1.0 evicted before importance 5.0
69
+ - Importance 5.0 evicted before importance 10.0
70
+
71
+ **Secondary sort**: Recency (descending age)
72
+
73
+ - Within same importance, older memories evicted first
74
+ - Added 5 days ago evicted before added 1 hour ago
75
+
76
+ ### Never-Forget Guarantee
77
+
78
+ **Critical**: Evicted memories are NOT deleted. They:
79
+
80
+ 1. Remain in long-term memory (PostgreSQL) permanently
81
+ 2. Can be recalled via `recall()` when needed
82
+ 3. Marked as `in_working_memory = FALSE` in database
83
+ 4. Automatically reloaded if recalled again
84
+
85
+ ## Rationale
86
+
87
+ ### Why Hybrid (Importance + Recency)?
88
+
89
+ **Importance alone is insufficient**:
90
+
91
+ - All memories might have same importance
92
+ - Old important memories eventually become stale
93
+ - Doesn't account for temporal relevance
94
+
95
+ **Recency alone is insufficient**:
96
+
97
+ - Critical information gets evicted just because it's old
98
+ - Architectural decisions disappear after time passes
99
+ - Loses important long-term context
100
+
101
+ **Hybrid balances both**:
102
+
103
+ - Low-importance recent memories evicted before high-importance old ones
104
+ - Within same importance, temporal relevance matters
105
+ - Preserves critical knowledge while making space for new context
106
+
107
+ ### Why This Sort Order?
108
+
109
+ ```ruby
110
+ [node[:importance], -recency]
111
+ ```
112
+
113
+ This creates tiers:
114
+
115
+ 1. **Tier 1 (evict first)**: Low importance, old
116
+ 2. **Tier 2**: Low importance, recent
117
+ 3. **Tier 3**: High importance, old
118
+ 4. **Tier 4 (evict last)**: High importance, recent
119
+
120
+ Example eviction order:
121
+ ```
122
+ importance: 1.0, age: 5 days → evicted first
123
+ importance: 1.0, age: 1 hour → evicted second
124
+ importance: 5.0, age: 5 days → evicted third
125
+ importance: 5.0, age: 1 hour → evicted fourth
126
+ importance: 10.0, age: 5 days → evicted fifth
127
+ importance: 10.0, age: 1 hour → kept (evict last)
128
+ ```
129
+
130
+ ### Eviction Guarantees
131
+
132
+ **Safety**:
133
+
134
+ - ✅ Evicted memories preserved in long-term storage
135
+ - ✅ No data loss, only working memory removal
136
+ - ✅ Can be recalled when needed
137
+
138
+ **Performance**:
139
+
140
+ - ✅ Greedy eviction: stops as soon as enough space freed
141
+ - ✅ O(n log n) sorting (one-time cost)
142
+ - ✅ O(k) eviction where k = nodes evicted
143
+ - ✅ Typical eviction: < 10ms for 100-node working memory
144
+
145
+ **Correctness**:
146
+
147
+ - ✅ Always frees enough tokens (or evicts all nodes trying)
148
+ - ✅ Respects importance ordering
149
+ - ✅ Deterministic (same state → same evictions)
150
+
151
+ ## Consequences
152
+
153
+ ### Positive
154
+
155
+ ✅ **Preserves important context**: High-importance memories stay longer
156
+ ✅ **Temporal relevance**: Recent context preferred over old
157
+ ✅ **Never forgets**: Evicted memories remain in long-term storage
158
+ ✅ **Predictable**: Clear eviction order based on importance + recency
159
+ ✅ **Fast**: O(n log n) sort, greedy eviction
160
+ ✅ **Greedy**: Only evicts what's necessary, no over-eviction
161
+ ✅ **Safe**: No data loss, recallable from long-term memory
162
+
163
+ ### Negative
164
+
165
+ ❌ **No access frequency**: Doesn't track how often memory is used
166
+ ❌ **No semantic clustering**: May split related memories
167
+ ❌ **Importance subjectivity**: Relies on user-assigned importance
168
+ ❌ **Batch eviction cost**: O(n log n) sort on every eviction
169
+ ❌ **No look-ahead**: Doesn't predict which memories will be needed soon
170
+
171
+ ### Neutral
172
+
173
+ ➡️ **Importance matters**: Users must assign meaningful importance scores
174
+ ➡️ **Eviction visibility**: No notification when memories evicted
175
+ ➡️ **Recall overhead**: Need to recall evicted memories if needed again
176
+
177
+ ## Design Decisions
178
+
179
+ ### Decision: Hybrid (Importance + Recency) vs Pure Policies
180
+ **Rationale**: Balances competing priorities better than any single factor
181
+
182
+ **Alternative**: Pure LRU (least recently used)
183
+ **Rejected**: Loses important long-term context
184
+
185
+ **Alternative**: Pure importance-based
186
+ **Rejected**: All old memories evicted regardless of importance
187
+
188
+ ### Decision: Primary Sort by Importance
189
+ **Rationale**: Importance is the stronger signal for retention
190
+
191
+ **Alternative**: Primary sort by recency
192
+ **Rejected**: Would evict critical old memories before trivial recent ones
193
+
194
+ ### Decision: Greedy Eviction (Stop When Enough Space)
195
+ **Rationale**: Minimize evictions, preserve as much context as possible
196
+
197
+ **Alternative**: Evict to some threshold (e.g., 80% full)
198
+ **Rejected**: Unnecessary evictions, reduces available context
199
+
200
+ ### Decision: No Access Frequency Tracking
201
+ **Rationale**: Simplicity, access patterns not stable in LLM workflows
202
+
203
+ **Alternative**: LFU (Least Frequently Used)
204
+ **Deferred**: Can add access_count tracking if real-world usage shows value
205
+
206
+ ### Decision: Evicted Memories Preserved in Long-Term
207
+ **Rationale**: Never-forget philosophy, safety, recallability
208
+
209
+ **Alternative**: Truly delete evicted memories
210
+ **Rejected**: Violates never-forget principle, data loss
211
+
212
+ ## Use Cases
213
+
214
+ ### Use Case 1: Adding Large Memory to Full Working Memory
215
+ ```ruby
216
+ # Working memory: 127,500 / 128,000 tokens (99% full)
217
+ # Attempt to add 5,000 token memory
218
+
219
+ htm.add_node("new_large_memory", large_text, importance: 7.0)
220
+
221
+ # Eviction needed: 5,000 - 500 = 4,500 tokens
222
+
223
+ # Working memory contains:
224
+ # - "user_pref" (importance: 8.0, 100 tokens, 5 days old)
225
+ # - "random_note" (importance: 1.0, 2,000 tokens, 1 hour ago)
226
+ # - "architecture_decision" (importance: 10.0, 3,000 tokens, 3 days ago)
227
+ # - "debug_log" (importance: 2.0, 1,500 tokens, 2 days ago)
228
+
229
+ # Eviction order:
230
+ # 1. "random_note" (importance: 1.0) → 2,000 tokens freed
231
+ # 2. "debug_log" (importance: 2.0) → 3,500 tokens freed
232
+ # 3. Stop (4,500 > needed)
233
+
234
+ # Result: user_pref and architecture_decision preserved
235
+ ```
236
+
237
+ ### Use Case 2: Importance Ties
238
+ ```ruby
239
+ # All memories have importance: 5.0
240
+
241
+ # Working memory:
242
+ # - "note_1" (5 days old, 1,000 tokens)
243
+ # - "note_2" (3 days old, 1,000 tokens)
244
+ # - "note_3" (1 hour ago, 1,000 tokens)
245
+
246
+ # Need to evict 2,000 tokens
247
+
248
+ # Eviction order:
249
+ # 1. "note_1" (oldest) → 1,000 tokens
250
+ # 2. "note_2" (second oldest) → 2,000 tokens
251
+ # 3. Stop
252
+
253
+ # Result: Most recent note preserved
254
+ ```
255
+
256
+ ### Use Case 3: Recall Evicted Memory
257
+ ```ruby
258
+ # Memory evicted from working memory
259
+ htm.add_node("temp_note", "Some temporary information", importance: 1.0)
260
+ # ... later evicted to make space ...
261
+
262
+ # Later: recall the evicted memory
263
+ memories = htm.recall(timeframe: "last week", topic: "temporary information")
264
+
265
+ # Result: Memory retrieved from long-term storage
266
+ # And automatically added back to working memory
267
+ ```
268
+
269
+ ### Use Case 4: High-Importance Old Memory
270
+ ```ruby
271
+ # Critical decision made months ago
272
+ htm.add_node("critical_decision",
273
+ "We must never use MongoDB for time-series data",
274
+ importance: 10.0)
275
+
276
+ # ... 90 days later, working memory full ...
277
+
278
+ # Many recent low-importance memories added
279
+ # Critical decision still in working memory due to importance: 10.0
280
+
281
+ # Eviction: Low-importance recent memories evicted first
282
+ # Result: Critical decision preserved despite being 90 days old
283
+ ```
284
+
285
+ ## Performance Characteristics
286
+
287
+ ### Time Complexity
288
+
289
+ - **Sorting**: O(n log n) where n = nodes in working memory
290
+ - **Eviction**: O(k) where k = nodes evicted
291
+ - **Total**: O(n log n + k) ≈ O(n log n)
292
+
293
+ ### Space Complexity
294
+
295
+ - **Sorted candidates**: O(n) temporary array
296
+ - **Evicted list**: O(k) returned result
297
+ - **Total**: O(n) additional memory
298
+
299
+ ### Typical Performance
300
+
301
+ - **Working memory size**: 50-200 nodes
302
+ - **Eviction frequency**: Low (most additions fit without eviction)
303
+ - **Sort time**: < 5ms for 200 nodes
304
+ - **Eviction time**: < 1ms (greedy, stops early)
305
+ - **Total**: < 10ms per eviction event
306
+
307
+ ### Optimization Opportunities
308
+
309
+ - **Cache sorted order**: Invalidate on add/remove
310
+ - **Incremental sort**: Use heap for O(k log n) eviction
311
+ - **Lazy eviction**: Evict only when space check fails
312
+
313
+ ## Risks and Mitigations
314
+
315
+ ### Risk: Batch Eviction Cost
316
+
317
+ - **Risk**: O(n log n) sort on every eviction is expensive
318
+ - **Likelihood**: Low (evictions infrequent, n is small)
319
+ - **Impact**: Low (< 10ms for typical working memory)
320
+ - **Mitigation**:
321
+ - Working memory stays small (128K tokens ≈ 50-200 nodes)
322
+ - Sort only when eviction needed
323
+ - Consider heap-based eviction if profiling shows bottleneck
324
+
325
+ ### Risk: Importance Scoring Inconsistency
326
+
327
+ - **Risk**: Users assign arbitrary importance, breaks eviction quality
328
+ - **Likelihood**: Medium (subjective scoring)
329
+ - **Impact**: Medium (suboptimal evictions)
330
+ - **Mitigation**:
331
+ - Document importance scoring guidelines
332
+ - Provide examples of importance ranges
333
+ - Default importance: 1.0 for most memories
334
+
335
+ ### Risk: No Access Frequency Signal
336
+
337
+ - **Risk**: Frequently accessed memories evicted if old and low importance
338
+ - **Likelihood**: Low (frequently accessed → likely important or recent)
339
+ - **Impact**: Low (can recall from long-term)
340
+ - **Mitigation**:
341
+ - Monitor real-world eviction patterns
342
+ - Add access_count tracking if needed
343
+
344
+ ### Risk: Related Memories Split
345
+
346
+ - **Risk**: Semantically related memories evicted separately
347
+ - **Likelihood**: Medium (no clustering)
348
+ - **Impact**: Low (can recall together with topic search)
349
+ - **Mitigation**:
350
+ - Use relationships to co-recall related memories
351
+ - Consider cluster-aware eviction in future
352
+
353
+ ## Future Enhancements
354
+
355
+ ### Access Frequency Tracking
356
+ ```ruby
357
+ def add(key, value, ...)
358
+ @nodes[key] = {
359
+ ...,
360
+ access_count: 0
361
+ }
362
+ end
363
+
364
+ def evict_to_make_space(needed_tokens)
365
+ candidates = @nodes.sort_by do |key, node|
366
+ recency = Time.now - node[:added_at]
367
+ access_freq = node[:access_count]
368
+
369
+ # Lower score evicted first
370
+ score = node[:importance] * (1 + Math.log(1 + access_freq))
371
+ [score, -recency]
372
+ end
373
+ end
374
+ ```
375
+
376
+ ### Cluster-Aware Eviction
377
+ ```ruby
378
+ # Keep related memories together
379
+ def evict_to_make_space(needed_tokens)
380
+ # Identify memory clusters
381
+ clusters = identify_clusters(@nodes)
382
+
383
+ # Evict entire clusters to preserve coherence
384
+ clusters.sort_by { |c| cluster_score(c) }.each do |cluster|
385
+ # Evict full cluster
386
+ end
387
+ end
388
+ ```
389
+
390
+ ### Lazy Eviction
391
+ ```ruby
392
+ # Don't evict until actually assembling context
393
+ def assemble_context(strategy:, max_tokens:)
394
+ # Only NOW evict if needed
395
+ evict_to_fit(max_tokens) if token_count > max_tokens
396
+ end
397
+ ```
398
+
399
+ ### Predicted Need Scoring
400
+ ```ruby
401
+ # Use LLM or heuristics to predict which memories will be needed
402
+ def evict_to_make_space(needed_tokens)
403
+ candidates = @nodes.sort_by do |key, node|
404
+ predicted_need = predict_future_access(node) # ML model or heuristics
405
+ [node[:importance] * predicted_need, -recency]
406
+ end
407
+ end
408
+ ```
409
+
410
+ ### Configurable Eviction Policy
411
+ ```ruby
412
+ htm = HTM.new(
413
+ eviction_policy: :lru # or :importance, :hybrid, :custom
414
+ )
415
+ ```
416
+
417
+ ## Alternatives Considered
418
+
419
+ ### Pure LRU (Least Recently Used)
420
+ **Pros**: Standard caching algorithm, temporal locality
421
+ **Cons**: Loses important long-term context
422
+ **Decision**: ❌ Rejected - importance matters for LLM memory
423
+
424
+ ### Pure Importance-Based
425
+ **Pros**: Preserves most important information
426
+ **Cons**: Old memories never evicted, no temporal relevance
427
+ **Decision**: ❌ Rejected - recency matters in conversations
428
+
429
+ ### LFU (Least Frequently Used)
430
+ **Pros**: Captures access patterns
431
+ **Cons**: New memories always evicted (frequency = 1), complex
432
+ **Decision**: ❌ Rejected - access patterns unstable in LLM workflows
433
+
434
+ ### Random Eviction
435
+ **Pros**: Simple, no sorting overhead
436
+ **Cons**: Unpredictable, may evict important memories
437
+ **Decision**: ❌ Rejected - too risky, no guarantees
438
+
439
+ ### Learned Eviction Policy
440
+ **Pros**: Optimal for specific usage patterns
441
+ **Cons**: Complex, requires training, non-deterministic
442
+ **Decision**: 🔄 Deferred - consider for v2 after usage data
443
+
444
+ ## References
445
+
446
+ - [Cache Replacement Policies](https://en.wikipedia.org/wiki/Cache_replacement_policies)
447
+ - [LRU Cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU))
448
+ - [Working Memory Management](https://en.wikipedia.org/wiki/Working_memory)
449
+ - [Multi-Level Memory Hierarchies](https://en.wikipedia.org/wiki/Memory_hierarchy)
450
+
451
+ ## Review Notes
452
+
453
+ **Systems Architect**: ✅ Hybrid eviction is the right choice. Consider heap-based eviction for better asymptotic complexity.
454
+
455
+ **Performance Specialist**: ✅ O(n log n) is acceptable for n < 200. Monitor real-world eviction frequency.
456
+
457
+ **Domain Expert**: ✅ Never-forget guarantee is essential. Eviction is just working memory management.
458
+
459
+ **AI Engineer**: ✅ Importance + recency works well for LLM context. Consider learned eviction in future.
460
+
461
+ **Ruby Expert**: ✅ Clean implementation. Consider extracting eviction policy to strategy pattern for extensibility.