Class: HTM::WorkingMemory

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(max_tokens:) ⇒ WorkingMemory

Initialize working memory

Parameters:

  • max_tokens (Integer)

    Maximum tokens allowed in 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_tokensObject (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

Parameters:

  • key (String)

    Node identifier

  • value (String)

    Node content

  • token_count (Integer)

    Number of tokens in this node

  • access_count (Integer) (defaults to: 0)

    Access count from long-term memory (default: 0)

  • last_accessed (Time, nil) (defaults to: nil)

    Last access time from long-term memory

  • from_recall (Boolean) (defaults to: false)

    Whether this node was recalled from long-term 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

Parameters:

  • strategy (Symbol)

    Assembly strategy (:recent, :frequent, :balanced)

    • :recent - Most recently accessed (LRU)

    • :frequent - Most frequently accessed (LFU)

    • :balanced - Combines frequency × recency

  • max_tokens (Integer, nil) (defaults to: nil)

    Optional token limit

Returns:

  • (String)

    Assembled context



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

Parameters:

  • needed_tokens (Integer)

    Number of tokens needed

Returns:

  • (Array<Hash>)

    Evicted nodes



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

Parameters:

  • token_count (Integer)

    Number of tokens needed

Returns:

  • (Boolean)

    true if space available



59
60
61
# File 'lib/htm/working_memory.rb', line 59

def has_space?(token_count)
  current_tokens + token_count <= @max_tokens
end

#node_countInteger

Get node count

Returns:

  • (Integer)

    Number of nodes in working memory



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

Parameters:

  • key (String)

    Node identifier



49
50
51
52
# File 'lib/htm/working_memory.rb', line 49

def remove(key)
  @nodes.delete(key)
  @access_order.delete(key)
end

#token_countInteger

Get current token count

Returns:

  • (Integer)

    Total tokens in working memory



151
152
153
# File 'lib/htm/working_memory.rb', line 151

def token_count
  @nodes.values.sum { |n| n[:token_count] }
end

#utilization_percentageFloat

Get utilization percentage

Returns:

  • (Float)

    Percentage of working memory used



159
160
161
# File 'lib/htm/working_memory.rb', line 159

def utilization_percentage
  (token_count.to_f / @max_tokens * 100).round(2)
end