lex-agentic-memory 0.1.4 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53e0072011f48d0b5fe21fb99b61fd6df8ffaf292a554073d6b65741ad57e4a3
4
- data.tar.gz: b88e61da456389bbbb358c0a29816e85b98b0038fa11d12f59935094393dd476
3
+ metadata.gz: ab87360ac0b2afc81f1868d934c29c851acdbb9db7b3864bc41437a20d959850
4
+ data.tar.gz: 922800b83b2c11ae1d525d23c9da664fbe6098bab81d4c0f32b45d96cc80d111
5
5
  SHA512:
6
- metadata.gz: 4236a2d5b8048945cf6889076555a8dab15c39e7b51b2632ade419f7432da7c85efca70ff404cf7a64757a1d67bfcae289f6424750f447f7a1b5e753c304e93c
7
- data.tar.gz: b3a1a7c781ba2b41057249d7e383f81ac1cb41bd5031aaa1657631cd560889dd44f0345d6687b2714d5e103ffa83f789ec88dfb6e89b4d9ff0d1e596fa89a437
6
+ metadata.gz: 2b2d45540a99c67dae5a5b27b49f75b27c1baa3beb56db00f1b7f4aef671577172b6a9a3a71077e535c7d9bcff0dc8d0690eb3f8a1af818c9bea5b0ac7d04d2c
7
+ data.tar.gz: 212f0679f2e1248209ad7a4a41a92694348080f1d71d24bbd6baa2eb7fff4389d591bad3f931fd8c1d7fc48298fb361f64cb9c5d08053785f59c3ec82d5be2e1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.6] - 2026-03-23
4
+
5
+ ### Fixed
6
+ - Fix trace migration path registration (`'memory/local_migrations'` -> `'trace/local_migrations'`)
7
+ - Refactor CacheStore from single-blob storage to per-trace individual cache keys with index, preventing OOM on flush
8
+
9
+ ### Changed
10
+ - CacheStore now uses `legion:memory:trace:<uuid>` keys instead of serializing all traces into one key
11
+ - Dirty set tracking replaces dirty flag for more efficient selective flushing
12
+ - Batch flush in groups of 500 traces
13
+
14
+ ## [0.1.5] - 2026-03-22
15
+
16
+ ### Changed
17
+ - Add legion-logging, legion-settings, legion-json, legion-cache, legion-crypt, legion-data, legion-transport as runtime dependencies
18
+ - Update spec_helper with real sub-gem helper stubs replacing manual Legion::Logging and Helpers::Lex stubs
19
+
3
20
  ## [0.1.3] - 2026-03-22
4
21
 
5
22
  ### Fixed
@@ -24,6 +24,13 @@ Gem::Specification.new do |spec|
24
24
  end
25
25
  spec.require_paths = ['lib']
26
26
 
27
+ spec.add_dependency 'legion-cache', '>= 1.3.11'
28
+ spec.add_dependency 'legion-crypt', '>= 1.4.9'
29
+ spec.add_dependency 'legion-data', '>= 1.4.17'
30
+ spec.add_dependency 'legion-json', '>= 1.2.1'
31
+ spec.add_dependency 'legion-logging', '>= 1.3.2'
32
+ spec.add_dependency 'legion-settings', '>= 1.3.14'
33
+ spec.add_dependency 'legion-transport', '>= 1.3.9'
27
34
  spec.add_dependency 'msgpack', '~> 1.7'
28
35
 
29
36
  spec.add_development_dependency 'rspec', '~> 3.13'
@@ -7,27 +7,32 @@ module Legion
7
7
  module Trace
8
8
  module Helpers
9
9
  # Cache-backed store using Legion::Cache (Memcached/Redis).
10
- # Keeps a local copy in memory, syncs to/from cache on load/flush.
11
- # Call `flush` after a batch of writes, or it auto-flushes when dirty.
12
- # Call `reload` to pull latest state from cache (e.g. after another process wrote).
10
+ # Each trace is stored as an individual cache key for scalability.
11
+ # An index key tracks all known trace IDs.
12
+ # Keeps a local in-memory copy for fast reads; syncs to cache on flush.
13
13
  class CacheStore
14
- TRACES_KEY = 'legion:memory:traces'
15
- ASSOC_KEY = 'legion:memory:associations'
16
- TTL = 86_400 # 24 hours
14
+ TRACE_PREFIX = 'legion:memory:trace:'
15
+ INDEX_KEY = 'legion:memory:trace_index'
16
+ ASSOC_KEY = 'legion:memory:associations'
17
+ TTL = 86_400 # 24 hours
18
+ FLUSH_BATCH = 500 # traces per flush batch
17
19
 
18
20
  attr_reader :traces, :associations
19
21
 
20
22
  def initialize
21
- Legion::Logging.info '[memory] CacheStore initialized (memcached-backed)'
22
- @traces = Legion::Cache.get(TRACES_KEY) || {}
23
- @associations = Legion::Cache.get(ASSOC_KEY) || {}
24
- @dirty = false
23
+ Legion::Logging.info '[memory] CacheStore initialized (memcached-backed, per-key)'
24
+ @traces = {}
25
+ @associations = {}
26
+ @dirty_ids = Set.new
27
+ @deleted_ids = Set.new
28
+ @assoc_dirty = false
29
+ load_index
25
30
  Legion::Logging.info "[memory] CacheStore loaded #{@traces.size} traces from cache"
26
31
  end
27
32
 
28
33
  def store(trace)
29
34
  @traces[trace[:trace_id]] = trace
30
- @dirty = true
35
+ @dirty_ids << trace[:trace_id]
31
36
  trace[:trace_id]
32
37
  end
33
38
 
@@ -39,7 +44,9 @@ module Legion
39
44
  @traces.delete(trace_id)
40
45
  @associations.delete(trace_id)
41
46
  @associations.each_value { |links| links.delete(trace_id) }
42
- @dirty = true
47
+ @dirty_ids.delete(trace_id)
48
+ @deleted_ids << trace_id
49
+ @assoc_dirty = true
43
50
  end
44
51
 
45
52
  def retrieve_by_type(type, min_strength: 0.0, limit: 100)
@@ -77,7 +84,7 @@ module Legion
77
84
 
78
85
  threshold = Helpers::Trace::COACTIVATION_THRESHOLD
79
86
  link_traces(trace_id_a, trace_id_b) if @associations[trace_id_a][trace_id_b] >= threshold
80
- @dirty = true
87
+ @assoc_dirty = true
81
88
  end
82
89
 
83
90
  def all_traces(min_strength: 0.0)
@@ -121,26 +128,81 @@ module Legion
121
128
  results
122
129
  end
123
130
 
124
- # Write local state to cache
131
+ # Write dirty traces to cache as individual keys
125
132
  def flush
126
- return unless @dirty
127
-
128
- Legion::Cache.set(TRACES_KEY, @traces, TTL)
129
- Legion::Cache.set(ASSOC_KEY, strip_default_procs(@associations), TTL)
130
- @dirty = false
131
- Legion::Logging.debug "[memory] CacheStore flushed #{@traces.size} traces to cache"
133
+ flush_deleted
134
+ flush_traces
135
+ flush_associations
136
+ flush_index
137
+ Legion::Logging.debug "[memory] CacheStore flushed #{@dirty_ids.size} dirty traces (#{@traces.size} total)"
138
+ @dirty_ids.clear
139
+ @deleted_ids.clear
132
140
  end
133
141
 
134
- # Pull latest state from cache (after another process wrote)
142
+ # Pull latest state from cache
135
143
  def reload
136
- @traces = Legion::Cache.get(TRACES_KEY) || {}
137
- @associations = Legion::Cache.get(ASSOC_KEY) || {}
138
- @dirty = false
144
+ @traces.clear
145
+ @associations = {}
146
+ @dirty_ids.clear
147
+ @deleted_ids.clear
148
+ @assoc_dirty = false
149
+ load_index
139
150
  Legion::Logging.debug "[memory] CacheStore reloaded #{@traces.size} traces from cache"
140
151
  end
141
152
 
142
153
  private
143
154
 
155
+ def trace_key(trace_id)
156
+ "#{TRACE_PREFIX}#{trace_id}"
157
+ end
158
+
159
+ def load_index
160
+ index = Legion::Cache.get(INDEX_KEY)
161
+ return unless index.is_a?(Array)
162
+
163
+ loaded = 0
164
+ index.each do |id|
165
+ trace = Legion::Cache.get(trace_key(id))
166
+ if trace
167
+ @traces[id] = trace
168
+ loaded += 1
169
+ end
170
+ end
171
+ Legion::Logging.debug "[memory] CacheStore loaded #{loaded}/#{index.size} traces from index" if defined?(Legion::Logging)
172
+ rescue StandardError => e
173
+ Legion::Logging.warn "[memory] CacheStore load_index failed: #{e.message}" if defined?(Legion::Logging)
174
+ end
175
+
176
+ def flush_traces
177
+ return if @dirty_ids.empty?
178
+
179
+ @dirty_ids.each_slice(FLUSH_BATCH) do |batch|
180
+ batch.each do |id|
181
+ trace = @traces[id]
182
+ Legion::Cache.set(trace_key(id), trace, TTL) if trace
183
+ end
184
+ end
185
+ end
186
+
187
+ def flush_deleted
188
+ @deleted_ids.each do |id|
189
+ Legion::Cache.delete(trace_key(id))
190
+ end
191
+ end
192
+
193
+ def flush_associations
194
+ return unless @assoc_dirty
195
+
196
+ Legion::Cache.set(ASSOC_KEY, strip_default_procs(@associations), TTL)
197
+ @assoc_dirty = false
198
+ end
199
+
200
+ def flush_index
201
+ return if @dirty_ids.empty? && @deleted_ids.empty?
202
+
203
+ Legion::Cache.set(INDEX_KEY, @traces.keys, TTL)
204
+ end
205
+
144
206
  def strip_default_procs(hash)
145
207
  hash.each_with_object({}) do |(k, v), plain|
146
208
  plain[k] = v.is_a?(Hash) ? {}.merge(v) : v
@@ -47,7 +47,7 @@ module Legion
47
47
  if defined?(Legion::Data::Local)
48
48
  Legion::Data::Local.register_migrations(
49
49
  name: :memory,
50
- path: File.join(__dir__, 'memory', 'local_migrations')
50
+ path: File.join(__dir__, 'trace', 'local_migrations')
51
51
  )
52
52
  end
53
53
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Memory
7
- VERSION = '0.1.4'
7
+ VERSION = '0.1.6'
8
8
  end
9
9
  end
10
10
  end
data/spec/spec_helper.rb CHANGED
@@ -1,41 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
+ require 'legion/logging'
5
+ require 'legion/settings'
6
+ require 'legion/cache/helper'
7
+ require 'legion/crypt/helper'
8
+ require 'legion/data/helper'
9
+ require 'legion/json/helper'
10
+ require 'legion/transport/helper'
4
11
 
5
12
  module Legion
6
- module Logging
7
- def self.debug(_msg); end
8
- def self.info(_msg); end
9
- def self.warn(_msg); end
10
- def self.error(_msg); end
11
- def self.fatal(_msg); end
12
- end
13
-
14
13
  module Extensions
15
- module Core
16
- def self.extended(_base); end
17
- end
18
-
19
14
  module Helpers
20
15
  module Lex
21
- def self.included(_base); end
16
+ include Legion::Logging::Helper
17
+ include Legion::Settings::Helper
18
+ include Legion::Cache::Helper
19
+ include Legion::Crypt::Helper
20
+ include Legion::Data::Helper
21
+ include Legion::JSON::Helper
22
+ include Legion::Transport::Helper
22
23
  end
23
24
  end
24
- end
25
- end
26
25
 
27
- # rubocop:disable Lint/EmptyClass, Style/OneClassPerFile
28
- module Legion
29
- module Extensions
30
26
  module Actors
31
- class Every; end
32
- class Once; end
27
+ class Every
28
+ include Helpers::Lex
29
+ end
30
+
31
+ class Once
32
+ include Helpers::Lex
33
+ end
33
34
  end
34
35
  end
35
36
  end
37
+
36
38
  $LOADED_FEATURES << 'legion/extensions/actors/every'
37
39
  $LOADED_FEATURES << 'legion/extensions/actors/once'
38
- # rubocop:enable Lint/EmptyClass, Style/OneClassPerFile
39
40
 
40
41
  require 'legion/extensions/agentic/memory'
41
42
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-memory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -9,6 +9,104 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: legion-cache
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 1.3.11
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 1.3.11
26
+ - !ruby/object:Gem::Dependency
27
+ name: legion-crypt
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.4.9
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.4.9
40
+ - !ruby/object:Gem::Dependency
41
+ name: legion-data
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.4.17
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.4.17
54
+ - !ruby/object:Gem::Dependency
55
+ name: legion-json
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.2.1
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 1.2.1
68
+ - !ruby/object:Gem::Dependency
69
+ name: legion-logging
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.3.2
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 1.3.2
82
+ - !ruby/object:Gem::Dependency
83
+ name: legion-settings
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 1.3.14
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 1.3.14
96
+ - !ruby/object:Gem::Dependency
97
+ name: legion-transport
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 1.3.9
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 1.3.9
12
110
  - !ruby/object:Gem::Dependency
13
111
  name: msgpack
14
112
  requirement: !ruby/object:Gem::Requirement