Class: HTM::WorkingMemory
- Inherits:
-
Object
- Object
- HTM::WorkingMemory
- Defined in:
- lib/htm/working_memory.rb
Overview
Working Memory - Token-limited active context for immediate LLM use
WorkingMemory manages the active conversation context within token limits. When full, it evicts less important or older nodes back to long-term storage.
Instance Attribute Summary collapse
-
#max_tokens ⇒ Object
readonly
Returns the value of attribute max_tokens.
Instance Method Summary collapse
-
#add(key, value, token_count:, access_count: 0, last_accessed: nil, from_recall: false) ⇒ void
Add a node to working memory.
-
#assemble_context(strategy:, max_tokens: nil) ⇒ String
Assemble context string for LLM.
-
#evict_to_make_space(needed_tokens) ⇒ Array<Hash>
Evict nodes to make space.
-
#has_space?(token_count) ⇒ Boolean
Check if there’s space for a node.
-
#initialize(max_tokens:) ⇒ WorkingMemory
constructor
Initialize working memory.
-
#node_count ⇒ Integer
Get node count.
-
#remove(key) ⇒ void
Remove a node from working memory.
-
#token_count ⇒ Integer
Get current token count.
-
#utilization_percentage ⇒ Float
Get utilization percentage.
Constructor Details
#initialize(max_tokens:) ⇒ WorkingMemory
Initialize working memory
16 17 18 19 20 |
# File 'lib/htm/working_memory.rb', line 16 def initialize(max_tokens:) @max_tokens = max_tokens @nodes = {} @access_order = [] end |
Instance Attribute Details
#max_tokens ⇒ Object (readonly)
Returns the value of attribute max_tokens.
10 11 12 |
# File 'lib/htm/working_memory.rb', line 10 def max_tokens @max_tokens end |
Instance Method Details
#add(key, value, token_count:, access_count: 0, last_accessed: nil, from_recall: false) ⇒ void
This method returns an undefined value.
Add a node to working memory
32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/htm/working_memory.rb', line 32 def add(key, value, token_count:, access_count: 0, last_accessed: nil, from_recall: false) @nodes[key] = { value: value, token_count: token_count, access_count: access_count, last_accessed: last_accessed || Time.now, added_at: Time.now, from_recall: from_recall } update_access(key) end |
#assemble_context(strategy:, max_tokens: nil) ⇒ String
Assemble context string for LLM
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/htm/working_memory.rb', line 110 def assemble_context(strategy:, max_tokens: nil) max = max_tokens || @max_tokens nodes = case strategy when :recent # Most recently accessed (LRU) @access_order.reverse.map { |k| @nodes[k] } when :frequent # Most frequently accessed (LFU) @nodes.sort_by { |k, v| -(v[:access_count] || 0) }.map(&:last) when :balanced # Combined frequency × recency @nodes.sort_by { |k, v| access_frequency = v[:access_count] || 0 time_since_accessed = Time.now - (v[:last_accessed] || v[:added_at]) recency_factor = 1.0 / (1 + time_since_accessed / 3600.0) # Higher score = more relevant -(Math.log(1 + access_frequency) * recency_factor) }.map(&:last) else raise ArgumentError, "Unknown strategy: #{strategy}. Use :recent, :frequent, or :balanced" end # Build context up to token limit context_parts = [] current_tokens = 0 nodes.each do |node| break if current_tokens + node[:token_count] > max context_parts << node[:value] current_tokens += node[:token_count] end context_parts.join("\n\n") end |
#evict_to_make_space(needed_tokens) ⇒ Array<Hash>
Evict nodes to make space
Uses LFU + LRU strategy: Least Frequently Used + Least Recently Used Nodes with low access count and old timestamps are evicted first
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/htm/working_memory.rb', line 71 def evict_to_make_space(needed_tokens) evicted = [] tokens_freed = 0 # Sort by access frequency + recency (lower score = more evictable) candidates = @nodes.sort_by do |key, node| access_frequency = node[:access_count] || 0 time_since_accessed = Time.now - (node[:last_accessed] || node[:added_at]) # Combined score: lower is more evictable # Frequently accessed = higher score (keep) # Recently accessed = higher score (keep) access_score = Math.log(1 + access_frequency) recency_score = 1.0 / (1 + time_since_accessed / 3600.0) -(access_score + recency_score) # Negative for ascending sort end candidates.each do |key, node| break if tokens_freed >= needed_tokens evicted << { key: key, value: node[:value] } tokens_freed += node[:token_count] @nodes.delete(key) @access_order.delete(key) end evicted end |
#has_space?(token_count) ⇒ Boolean
Check if there’s space for a node
59 60 61 |
# File 'lib/htm/working_memory.rb', line 59 def has_space?(token_count) current_tokens + token_count <= @max_tokens end |
#node_count ⇒ Integer
Get node count
167 168 169 |
# File 'lib/htm/working_memory.rb', line 167 def node_count @nodes.size end |
#remove(key) ⇒ void
This method returns an undefined value.
Remove a node from working memory
49 50 51 52 |
# File 'lib/htm/working_memory.rb', line 49 def remove(key) @nodes.delete(key) @access_order.delete(key) end |
#token_count ⇒ Integer
Get current token count
151 152 153 |
# File 'lib/htm/working_memory.rb', line 151 def token_count @nodes.values.sum { |n| n[:token_count] } end |
#utilization_percentage ⇒ Float
Get utilization percentage
159 160 161 |
# File 'lib/htm/working_memory.rb', line 159 def utilization_percentage (token_count.to_f / @max_tokens * 100).round(2) end |