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,645 @@
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
+ ---
10
+
11
+ ## Quick Summary
12
+
13
+ HTM implements a **hybrid eviction strategy** that prioritizes memories by both importance and recency, evicting low-importance older memories first when working memory reaches token capacity. Evicted memories are preserved in long-term storage, never deleted.
14
+
15
+ **Why**: Token limits are hard constraints, but eviction policy determines which context stays active. Hybrid approach balances preserving critical knowledge with maintaining temporal relevance.
16
+
17
+ **Impact**: Predictable eviction behavior with never-forget guarantees, at the cost of requiring meaningful importance scoring from users.
18
+
19
+ ---
20
+
21
+ ## Context
22
+
23
+ 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.
24
+
25
+ ### Eviction Challenges
26
+
27
+ - **Token limits**: Hard constraint, cannot exceed working memory capacity
28
+ - **Importance preservation**: Critical memories should remain in working memory
29
+ - **Recency**: Recent context often more relevant than old context
30
+ - **Fairness**: Don't always evict the same types of memories
31
+ - **Performance**: Eviction should be fast (< 10ms)
32
+ - **Never forget**: Evicted memories must be preserved in long-term memory
33
+
34
+ ### Alternative Eviction Policies
35
+
36
+ 1. **FIFO (First-In-First-Out)**: Evict oldest memories
37
+ 2. **LRU (Least Recently Used)**: Evict least recently accessed
38
+ 3. **LFU (Least Frequently Used)**: Evict least frequently accessed
39
+ 4. **Random**: Evict random memories
40
+ 5. **Importance-only**: Evict lowest importance first
41
+ 6. **Hybrid**: Combine importance and recency with scoring function
42
+
43
+ ---
44
+
45
+ ## Decision
46
+
47
+ We will implement a **hybrid eviction strategy** that prioritizes memories by both importance and recency, evicting low-importance older memories first.
48
+
49
+ <svg viewBox="0 0 950 750" xmlns="http://www.w3.org/2000/svg" style="background: transparent;">
50
+ <defs>
51
+ <style>
52
+ .flow-box { fill: rgba(33, 150, 243, 0.2); stroke: #2196F3; stroke-width: 2; }
53
+ .tier-box { fill: rgba(76, 175, 80, 0.2); stroke: #4CAF50; stroke-width: 2; }
54
+ .tier1-box { fill: rgba(244, 67, 54, 0.3); stroke: #F44336; stroke-width: 2; }
55
+ .tier2-box { fill: rgba(255, 152, 0, 0.3); stroke: #FF9800; stroke-width: 2; }
56
+ .tier3-box { fill: rgba(255, 235, 59, 0.3); stroke: #FFEB3B; stroke-width: 2; }
57
+ .tier4-box { fill: rgba(76, 175, 80, 0.3); stroke: #4CAF50; stroke-width: 2; }
58
+ .text-header { fill: #E0E0E0; font-size: 18px; font-weight: bold; }
59
+ .text-label { fill: #E0E0E0; font-size: 14px; font-weight: bold; }
60
+ .text-small { fill: #B0B0B0; font-size: 11px; }
61
+ .text-tiny { fill: #A0A0A0; font-size: 10px; }
62
+ .arrow { stroke: #4A9EFF; stroke-width: 2; fill: none; marker-end: url(#arrowhead); }
63
+ </style>
64
+ <marker id="arrowhead" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
65
+ <polygon points="0 0, 10 3, 0 6" fill="#4A9EFF" />
66
+ </marker>
67
+ </defs>
68
+
69
+ <!-- Title -->
70
+ <text x="475" y="30" text-anchor="middle" class="text-header">Hybrid Eviction Algorithm</text>
71
+
72
+ <!-- Algorithm Flow -->
73
+ <text x="50" y="65" class="text-label">Eviction Process</text>
74
+
75
+ <!-- Step 1 -->
76
+ <rect x="50" y="80" width="380" height="60" class="flow-box" rx="5"/>
77
+ <text x="60" y="100" class="text-label">1. Calculate Eviction Score</text>
78
+ <text x="60" y="120" class="text-small">score = [importance, -recency]</text>
79
+
80
+ <!-- Step 2 -->
81
+ <rect x="50" y="155" width="380" height="60" class="flow-box" rx="5"/>
82
+ <text x="60" y="175" class="text-label">2. Sort Memories by Score</text>
83
+ <text x="60" y="195" class="text-small">Primary: importance (asc), Secondary: age (desc)</text>
84
+
85
+ <!-- Step 3 -->
86
+ <rect x="50" y="230" width="380" height="60" class="flow-box" rx="5"/>
87
+ <text x="60" y="250" class="text-label">3. Greedy Eviction</text>
88
+ <text x="60" y="270" class="text-small">Evict from front until needed_tokens freed</text>
89
+
90
+ <!-- Step 4 -->
91
+ <rect x="50" y="305" width="380" height="60" class="flow-box" rx="5"/>
92
+ <text x="60" y="325" class="text-label">4. Mark as Evicted</text>
93
+ <text x="60" y="345" class="text-small">Set in_working_memory = false in database</text>
94
+
95
+ <!-- Arrows -->
96
+ <path d="M 240 140 L 240 155" class="arrow"/>
97
+ <path d="M 240 215 L 240 230" class="arrow"/>
98
+ <path d="M 240 290 L 240 305" class="arrow"/>
99
+
100
+ <!-- Eviction Tiers -->
101
+ <text x="520" y="65" class="text-label">Eviction Priority Tiers</text>
102
+ <text x="520" y="80" class="text-small">(Lower tier = evicted first)</text>
103
+
104
+ <!-- Tier 1 -->
105
+ <rect x="520" y="100" width="380" height="110" class="tier1-box" rx="5"/>
106
+ <text x="530" y="120" class="text-label" fill="#F44336">Tier 1: Low Importance + Old</text>
107
+ <text x="530" y="140" class="text-small">Evicted First</text>
108
+ <text x="530" y="165" class="text-tiny">importance: 1.0, age: 5 days → evicted 1st</text>
109
+ <text x="530" y="180" class="text-tiny">importance: 2.0, age: 3 days → evicted 2nd</text>
110
+ <text x="530" y="195" class="text-tiny">Examples: Temporary notes, scratch data</text>
111
+
112
+ <!-- Tier 2 -->
113
+ <rect x="520" y="225" width="380" height="110" class="tier2-box" rx="5"/>
114
+ <text x="530" y="245" class="text-label" fill="#FF9800">Tier 2: Low Importance + Recent</text>
115
+ <text x="530" y="265" class="text-small">Evicted Second</text>
116
+ <text x="530" y="290" class="text-tiny">importance: 1.0, age: 1 hour → evicted 3rd</text>
117
+ <text x="530" y="305" class="text-tiny">importance: 2.0, age: 30 min → evicted 4th</text>
118
+ <text x="530" y="320" class="text-tiny">Examples: Recent but low-value context</text>
119
+
120
+ <!-- Tier 3 -->
121
+ <rect x="520" y="350" width="380" height="110" class="tier3-box" rx="5"/>
122
+ <text x="530" y="370" class="text-label" fill="#FDD835">Tier 3: High Importance + Old</text>
123
+ <text x="530" y="390" class="text-small">Evicted Third</text>
124
+ <text x="530" y="415" class="text-tiny">importance: 9.0, age: 30 days → evicted 5th</text>
125
+ <text x="530" y="430" class="text-tiny">importance: 10.0, age: 90 days → evicted 6th</text>
126
+ <text x="530" y="445" class="text-tiny">Examples: Old but critical decisions</text>
127
+
128
+ <!-- Tier 4 -->
129
+ <rect x="520" y="475" width="380" height="110" class="tier4-box" rx="5"/>
130
+ <text x="530" y="495" class="text-label" fill="#4CAF50">Tier 4: High Importance + Recent</text>
131
+ <text x="530" y="515" class="text-small">Kept (Evicted Last)</text>
132
+ <text x="530" y="540" class="text-tiny">importance: 9.0, age: 1 hour → kept longest</text>
133
+ <text x="530" y="555" class="text-tiny">importance: 10.0, age: 5 min → never evicted (if possible)</text>
134
+ <text x="530" y="570" class="text-tiny">Examples: Critical + actively used data</text>
135
+
136
+ <!-- Key Features -->
137
+ <text x="50" y="410" class="text-label">Guarantees</text>
138
+
139
+ <rect x="50" y="425" width="170" height="140" fill="rgba(33, 150, 243, 0.15)" stroke="#2196F3" stroke-width="1.5" rx="3"/>
140
+ <text x="60" y="450" class="text-small">✓ Never-forget principle</text>
141
+ <text x="60" y="470" class="text-small">✓ Preserved in long-term</text>
142
+ <text x="60" y="490" class="text-small">✓ Can be recalled</text>
143
+ <text x="60" y="510" class="text-small">✓ No data loss</text>
144
+ <text x="60" y="530" class="text-small">✓ Deterministic order</text>
145
+ <text x="60" y="550" class="text-small">✓ O(n log n) complexity</text>
146
+
147
+ <rect x="240" y="425" width="190" height="140" fill="rgba(33, 150, 243, 0.15)" stroke="#2196F3" stroke-width="1.5" rx="3"/>
148
+ <text x="250" y="450" class="text-small">Performance:</text>
149
+ <text x="250" y="470" class="text-tiny">• Sort: O(n log n)</text>
150
+ <text x="250" y="485" class="text-tiny">• Eviction: O(k) greedy</text>
151
+ <text x="250" y="500" class="text-tiny">• Typical: &lt;10ms</text>
152
+ <text x="250" y="515" class="text-tiny">• Memory: O(n) temp</text>
153
+ <text x="250" y="530" class="text-tiny">• Stops when enough</text>
154
+ <text x="250" y="545" class="text-tiny"> space freed</text>
155
+
156
+ <!-- Example -->
157
+ <text x="50" y="600" class="text-label">Example: Need 5,000 tokens</text>
158
+
159
+ <rect x="50" y="615" width="850" height="110" fill="rgba(33, 150, 243, 0.1)" stroke="#2196F3" stroke-width="1" rx="3"/>
160
+
161
+ <text x="60" y="635" class="text-tiny">Working memory contains:</text>
162
+ <text x="70" y="655" class="text-tiny" fill="#F44336">• "random_note" (imp: 1.0, 2000 tok, 1 hr ago) → EVICTED (Tier 2)</text>
163
+ <text x="70" y="670" class="text-tiny" fill="#F44336">• "debug_log" (imp: 2.0, 1500 tok, 2 days ago) → EVICTED (Tier 1)</text>
164
+ <text x="70" y="685" class="text-tiny" fill="#F44336">• "temp_calc" (imp: 1.5, 1600 tok, 5 days ago) → EVICTED (Tier 1)</text>
165
+ <text x="70" y="700" class="text-tiny" fill="#4CAF50">• "user_pref" (imp: 8.0, 100 tok, 5 days ago) → KEPT (Tier 3)</text>
166
+ <text x="70" y="715" class="text-tiny" fill="#4CAF50">• "architecture_decision" (imp: 10.0, 3000 tok, 3 days ago) → KEPT (Tier 3)</text>
167
+ </svg>
168
+
169
+ ### Eviction Algorithm
170
+
171
+ ```ruby
172
+ def evict_to_make_space(needed_tokens)
173
+ # Sort by [importance, -recency]
174
+ # Lower importance evicted first
175
+ # Within same importance, older memories evicted first
176
+ candidates = @nodes.sort_by do |key, node|
177
+ recency = Time.now - node[:added_at]
178
+ [node[:importance], -recency]
179
+ end
180
+
181
+ # Evict from front until enough space
182
+ evicted = []
183
+ tokens_freed = 0
184
+
185
+ candidates.each do |key, node|
186
+ break if tokens_freed >= needed_tokens
187
+
188
+ evicted << { key: key, value: node[:value] }
189
+ tokens_freed += node[:token_count]
190
+ @nodes.delete(key)
191
+ @access_order.delete(key)
192
+ end
193
+
194
+ evicted
195
+ end
196
+ ```
197
+
198
+ ### Eviction Scoring
199
+
200
+ **Primary sort**: Importance (ascending)
201
+
202
+ - Importance 1.0 evicted before importance 5.0
203
+ - Importance 5.0 evicted before importance 10.0
204
+
205
+ **Secondary sort**: Recency (descending age)
206
+
207
+ - Within same importance, older memories evicted first
208
+ - Added 5 days ago evicted before added 1 hour ago
209
+
210
+ ### Never-Forget Guarantee
211
+
212
+ !!! tip "Critical Design Principle"
213
+ Evicted memories are NOT deleted. They:
214
+
215
+ 1. Remain in long-term memory (PostgreSQL) permanently
216
+ 2. Can be recalled via `recall()` when needed
217
+ 3. Marked as `in_working_memory = FALSE` in database
218
+ 4. Automatically reloaded if recalled again
219
+
220
+ ---
221
+
222
+ ## Rationale
223
+
224
+ ### Why Hybrid (Importance + Recency)?
225
+
226
+ **Importance alone is insufficient**:
227
+
228
+ - All memories might have same importance
229
+ - Old important memories eventually become stale
230
+ - Doesn't account for temporal relevance
231
+
232
+ **Recency alone is insufficient**:
233
+
234
+ - Critical information gets evicted just because it's old
235
+ - Architectural decisions disappear after time passes
236
+ - Loses important long-term context
237
+
238
+ **Hybrid balances both**:
239
+
240
+ - Low-importance recent memories evicted before high-importance old ones
241
+ - Within same importance, temporal relevance matters
242
+ - Preserves critical knowledge while making space for new context
243
+
244
+ ### Why This Sort Order?
245
+
246
+ ```ruby
247
+ [node[:importance], -recency]
248
+ ```
249
+
250
+ This creates tiers:
251
+
252
+ 1. **Tier 1 (evict first)**: Low importance, old
253
+ 2. **Tier 2**: Low importance, recent
254
+ 3. **Tier 3**: High importance, old
255
+ 4. **Tier 4 (evict last)**: High importance, recent
256
+
257
+ Example eviction order:
258
+
259
+ ```
260
+ importance: 1.0, age: 5 days → evicted first
261
+ importance: 1.0, age: 1 hour → evicted second
262
+ importance: 5.0, age: 5 days → evicted third
263
+ importance: 5.0, age: 1 hour → evicted fourth
264
+ importance: 10.0, age: 5 days → evicted fifth
265
+ importance: 10.0, age: 1 hour → kept (evict last)
266
+ ```
267
+
268
+ ### Eviction Guarantees
269
+
270
+ **Safety**:
271
+
272
+ - Evicted memories preserved in long-term storage
273
+ - No data loss, only working memory removal
274
+ - Can be recalled when needed
275
+
276
+ **Performance**:
277
+
278
+ - Greedy eviction: stops as soon as enough space freed
279
+ - O(n log n) sorting (one-time cost)
280
+ - O(k) eviction where k = nodes evicted
281
+ - Typical eviction: < 10ms for 100-node working memory
282
+
283
+ **Correctness**:
284
+
285
+ - Always frees enough tokens (or evicts all nodes trying)
286
+ - Respects importance ordering
287
+ - Deterministic (same state → same evictions)
288
+
289
+ ---
290
+
291
+ ## Consequences
292
+
293
+ ### Positive
294
+
295
+ - Preserves important context: High-importance memories stay longer
296
+ - Temporal relevance: Recent context preferred over old
297
+ - Never forgets: Evicted memories remain in long-term storage
298
+ - Predictable: Clear eviction order based on importance + recency
299
+ - Fast: O(n log n) sort, greedy eviction
300
+ - Greedy: Only evicts what's necessary, no over-eviction
301
+ - Safe: No data loss, recallable from long-term memory
302
+
303
+ ### Negative
304
+
305
+ - No access frequency: Doesn't track how often memory is used
306
+ - No semantic clustering: May split related memories
307
+ - Importance subjectivity: Relies on user-assigned importance
308
+ - Batch eviction cost: O(n log n) sort on every eviction
309
+ - No look-ahead: Doesn't predict which memories will be needed soon
310
+
311
+ ### Neutral
312
+
313
+ - Importance matters: Users must assign meaningful importance scores
314
+ - Eviction visibility: No notification when memories evicted
315
+ - Recall overhead: Need to recall evicted memories if needed again
316
+
317
+ ---
318
+
319
+ ## Use Cases
320
+
321
+ ### Use Case 1: Adding Large Memory to Full Working Memory
322
+
323
+ ```ruby
324
+ # Working memory: 127,500 / 128,000 tokens (99% full)
325
+ # Attempt to add 5,000 token memory
326
+
327
+ htm.add_node("new_large_memory", large_text, importance: 7.0)
328
+
329
+ # Eviction needed: 5,000 - 500 = 4,500 tokens
330
+
331
+ # Working memory contains:
332
+ # - "user_pref" (importance: 8.0, 100 tokens, 5 days old)
333
+ # - "random_note" (importance: 1.0, 2,000 tokens, 1 hour ago)
334
+ # - "architecture_decision" (importance: 10.0, 3,000 tokens, 3 days ago)
335
+ # - "debug_log" (importance: 2.0, 1,500 tokens, 2 days ago)
336
+
337
+ # Eviction order:
338
+ # 1. "random_note" (importance: 1.0) → 2,000 tokens freed
339
+ # 2. "debug_log" (importance: 2.0) → 3,500 tokens freed
340
+ # 3. Stop (4,500 > needed)
341
+
342
+ # Result: user_pref and architecture_decision preserved
343
+ ```
344
+
345
+ ### Use Case 2: Importance Ties
346
+
347
+ ```ruby
348
+ # All memories have importance: 5.0
349
+
350
+ # Working memory:
351
+ # - "note_1" (5 days old, 1,000 tokens)
352
+ # - "note_2" (3 days old, 1,000 tokens)
353
+ # - "note_3" (1 hour ago, 1,000 tokens)
354
+
355
+ # Need to evict 2,000 tokens
356
+
357
+ # Eviction order:
358
+ # 1. "note_1" (oldest) → 1,000 tokens
359
+ # 2. "note_2" (second oldest) → 2,000 tokens
360
+ # 3. Stop
361
+
362
+ # Result: Most recent note preserved
363
+ ```
364
+
365
+ ### Use Case 3: Recall Evicted Memory
366
+
367
+ ```ruby
368
+ # Memory evicted from working memory
369
+ htm.add_node("temp_note", "Some temporary information", importance: 1.0)
370
+ # ... later evicted to make space ...
371
+
372
+ # Later: recall the evicted memory
373
+ memories = htm.recall(timeframe: "last week", topic: "temporary information")
374
+
375
+ # Result: Memory retrieved from long-term storage
376
+ # And automatically added back to working memory
377
+ ```
378
+
379
+ ### Use Case 4: High-Importance Old Memory
380
+
381
+ ```ruby
382
+ # Critical decision made months ago
383
+ htm.add_node("critical_decision",
384
+ "We must never use MongoDB for time-series data",
385
+ importance: 10.0)
386
+
387
+ # ... 90 days later, working memory full ...
388
+
389
+ # Many recent low-importance memories added
390
+ # Critical decision still in working memory due to importance: 10.0
391
+
392
+ # Eviction: Low-importance recent memories evicted first
393
+ # Result: Critical decision preserved despite being 90 days old
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Performance Characteristics
399
+
400
+ ### Time Complexity
401
+
402
+ - **Sorting**: O(n log n) where n = nodes in working memory
403
+ - **Eviction**: O(k) where k = nodes evicted
404
+ - **Total**: O(n log n + k) ≈ O(n log n)
405
+
406
+ ### Space Complexity
407
+
408
+ - **Sorted candidates**: O(n) temporary array
409
+ - **Evicted list**: O(k) returned result
410
+ - **Total**: O(n) additional memory
411
+
412
+ ### Typical Performance
413
+
414
+ - **Working memory size**: 50-200 nodes
415
+ - **Eviction frequency**: Low (most additions fit without eviction)
416
+ - **Sort time**: < 5ms for 200 nodes
417
+ - **Eviction time**: < 1ms (greedy, stops early)
418
+ - **Total**: < 10ms per eviction event
419
+
420
+ ### Optimization Opportunities
421
+
422
+ - **Cache sorted order**: Invalidate on add/remove
423
+ - **Incremental sort**: Use heap for O(k log n) eviction
424
+ - **Lazy eviction**: Evict only when space check fails
425
+
426
+ ---
427
+
428
+ ## Design Decisions
429
+
430
+ ### Decision: Hybrid (Importance + Recency) vs Pure Policies
431
+
432
+ **Rationale**: Balances competing priorities better than any single factor
433
+
434
+ **Alternative**: Pure LRU (least recently used)
435
+
436
+ **Rejected**: Loses important long-term context
437
+
438
+ **Alternative**: Pure importance-based
439
+
440
+ **Rejected**: All old memories evicted regardless of importance
441
+
442
+ ### Decision: Primary Sort by Importance
443
+
444
+ **Rationale**: Importance is the stronger signal for retention
445
+
446
+ **Alternative**: Primary sort by recency
447
+
448
+ **Rejected**: Would evict critical old memories before trivial recent ones
449
+
450
+ ### Decision: Greedy Eviction (Stop When Enough Space)
451
+
452
+ **Rationale**: Minimize evictions, preserve as much context as possible
453
+
454
+ **Alternative**: Evict to some threshold (e.g., 80% full)
455
+
456
+ **Rejected**: Unnecessary evictions, reduces available context
457
+
458
+ ### Decision: No Access Frequency Tracking
459
+
460
+ **Rationale**: Simplicity, access patterns not stable in LLM workflows
461
+
462
+ **Alternative**: LFU (Least Frequently Used)
463
+
464
+ **Deferred**: Can add access_count tracking if real-world usage shows value
465
+
466
+ ### Decision: Evicted Memories Preserved in Long-Term
467
+
468
+ **Rationale**: Never-forget philosophy, safety, recallability
469
+
470
+ **Alternative**: Truly delete evicted memories
471
+
472
+ **Rejected**: Violates never-forget principle, data loss
473
+
474
+ ---
475
+
476
+ ## Risks and Mitigations
477
+
478
+ ### Risk: Batch Eviction Cost
479
+
480
+ !!! info "Risk"
481
+ O(n log n) sort on every eviction is expensive
482
+
483
+ **Likelihood**: Low (evictions infrequent, n is small)
484
+
485
+ **Impact**: Low (< 10ms for typical working memory)
486
+
487
+ **Mitigation**:
488
+
489
+ - Working memory stays small (128K tokens ≈ 50-200 nodes)
490
+ - Sort only when eviction needed
491
+ - Consider heap-based eviction if profiling shows bottleneck
492
+
493
+ ### Risk: Importance Scoring Inconsistency
494
+
495
+ !!! warning "Risk"
496
+ Users assign arbitrary importance, breaks eviction quality
497
+
498
+ **Likelihood**: Medium (subjective scoring)
499
+
500
+ **Impact**: Medium (suboptimal evictions)
501
+
502
+ **Mitigation**:
503
+
504
+ - Document importance scoring guidelines
505
+ - Provide examples of importance ranges
506
+ - Default importance: 1.0 for most memories
507
+
508
+ ### Risk: No Access Frequency Signal
509
+
510
+ !!! info "Risk"
511
+ Frequently accessed memories evicted if old and low importance
512
+
513
+ **Likelihood**: Low (frequently accessed → likely important or recent)
514
+
515
+ **Impact**: Low (can recall from long-term)
516
+
517
+ **Mitigation**:
518
+
519
+ - Monitor real-world eviction patterns
520
+ - Add access_count tracking if needed
521
+
522
+ ### Risk: Related Memories Split
523
+
524
+ !!! info "Risk"
525
+ Semantically related memories evicted separately
526
+
527
+ **Likelihood**: Medium (no clustering)
528
+
529
+ **Impact**: Low (can recall together with topic search)
530
+
531
+ **Mitigation**:
532
+
533
+ - Use relationships to co-recall related memories
534
+ - Consider cluster-aware eviction in future
535
+
536
+ ---
537
+
538
+ ## Future Enhancements
539
+
540
+ ### Access Frequency Tracking
541
+
542
+ ```ruby
543
+ def add(key, value, ...)
544
+ @nodes[key] = {
545
+ ...,
546
+ access_count: 0
547
+ }
548
+ end
549
+
550
+ def evict_to_make_space(needed_tokens)
551
+ candidates = @nodes.sort_by do |key, node|
552
+ recency = Time.now - node[:added_at]
553
+ access_freq = node[:access_count]
554
+
555
+ # Lower score evicted first
556
+ score = node[:importance] * (1 + Math.log(1 + access_freq))
557
+ [score, -recency]
558
+ end
559
+ end
560
+ ```
561
+
562
+ ### Cluster-Aware Eviction
563
+
564
+ ```ruby
565
+ # Keep related memories together
566
+ def evict_to_make_space(needed_tokens)
567
+ # Identify memory clusters
568
+ clusters = identify_clusters(@nodes)
569
+
570
+ # Evict entire clusters to preserve coherence
571
+ clusters.sort_by { |c| cluster_score(c) }.each do |cluster|
572
+ # Evict full cluster
573
+ end
574
+ end
575
+ ```
576
+
577
+ ### Lazy Eviction
578
+
579
+ ```ruby
580
+ # Don't evict until actually assembling context
581
+ def assemble_context(strategy:, max_tokens:)
582
+ # Only NOW evict if needed
583
+ evict_to_fit(max_tokens) if token_count > max_tokens
584
+ end
585
+ ```
586
+
587
+ ### Predicted Need Scoring
588
+
589
+ ```ruby
590
+ # Use LLM or heuristics to predict which memories will be needed
591
+ def evict_to_make_space(needed_tokens)
592
+ candidates = @nodes.sort_by do |key, node|
593
+ predicted_need = predict_future_access(node) # ML model or heuristics
594
+ [node[:importance] * predicted_need, -recency]
595
+ end
596
+ end
597
+ ```
598
+
599
+ ### Configurable Eviction Policy
600
+
601
+ ```ruby
602
+ htm = HTM.new(
603
+ eviction_policy: :lru # or :importance, :hybrid, :custom
604
+ )
605
+ ```
606
+
607
+ ---
608
+
609
+ ## Alternatives Comparison
610
+
611
+ | Policy | Pros | Cons | Decision |
612
+ |--------|------|------|----------|
613
+ | **Hybrid (Importance + Recency)** | **Balanced retention** | **Requires importance** | **ACCEPTED** |
614
+ | Pure LRU | Standard caching, temporal | Loses important context | Rejected |
615
+ | Pure Importance | Preserves critical info | No temporal relevance | Rejected |
616
+ | LFU | Captures access patterns | New memories always evicted | Rejected |
617
+ | Random | Simple, no sorting | Unpredictable, risky | Rejected |
618
+ | Learned Policy | Optimal for patterns | Complex, non-deterministic | Deferred |
619
+
620
+ ---
621
+
622
+ ## References
623
+
624
+ - [Cache Replacement Policies](https://en.wikipedia.org/wiki/Cache_replacement_policies)
625
+ - [LRU Cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU))
626
+ - [Working Memory Management](https://en.wikipedia.org/wiki/Working_memory)
627
+ - [Multi-Level Memory Hierarchies](https://en.wikipedia.org/wiki/Memory_hierarchy)
628
+ - [ADR-002: Two-Tier Memory](002-two-tier-memory.md)
629
+ - [ADR-006: Context Assembly](006-context-assembly.md)
630
+ - [ADR-009: Never-Forget Philosophy](009-never-forget.md)
631
+ - [Working Memory Guide](../../guides/working-memory.md)
632
+
633
+ ---
634
+
635
+ ## Review Notes
636
+
637
+ **Systems Architect**: Hybrid eviction is the right choice. Consider heap-based eviction for better asymptotic complexity.
638
+
639
+ **Performance Specialist**: O(n log n) is acceptable for n < 200. Monitor real-world eviction frequency.
640
+
641
+ **Domain Expert**: Never-forget guarantee is essential. Eviction is just working memory management.
642
+
643
+ **AI Engineer**: Importance + recency works well for LLM context. Consider learned eviction in future.
644
+
645
+ **Ruby Expert**: Clean implementation. Consider extracting eviction policy to strategy pattern for extensibility.