lex-agentic-memory 0.1.39 → 0.1.40

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: 89cf392450f3ff2b43d9ff1430c9cb4e8b108d0221b46aa4c2e3e064514c6200
4
- data.tar.gz: 0343f1b89ae601f477939c5890fca5bcb2e8e4961f411049612731edf16ae43e
3
+ metadata.gz: d111b8e5f52156c7060509c6d50d9c4ffe3f7d02108e7f1fcb6a5ba03f1982d8
4
+ data.tar.gz: 52db96d9807a927c29a097c7a0a47b50d63c43069e657bc7fdad57119a09152a
5
5
  SHA512:
6
- metadata.gz: ca0b83053d099fd5e445427105023210371b4e4048961bfa4d3446b8ca1412902c8c6dbbcfaa0881d787d55ba5ec70fc5321023374c3a9c35b6718d99e9af8b8
7
- data.tar.gz: 5c6e4acf09c5cafae4694253381e963316fe898e9c738bb9ae0143e9788e242e01b5dd442f3187ebb8f997b1a3ef095fdb1c1fd9a77c0ac2072c0e255247b860
6
+ metadata.gz: a538816df808c0bb5da844ababdbb8dca9f02516803c4ec8791b7228f4c70c96269be9232fe69be56e107ac6a08b4d82e57bb3a7931074742420dccae8f22c56
7
+ data.tar.gz: a8c41f78835e8e044ce289c0298727b1e4381598366882715836a2bafd591c8bf88223a388ca4f26cd1be2e9fac2a96a34b6a64f3a3b03c3d2501357928d5211
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.40] - 2026-06-17
4
+ ### Fixed
5
+ - **Critical performance fix:** Trace store `snapshot_dirty_state` no longer serializes ALL traces to JSON on every flush — replaced boolean `@traces_dirty` flag with per-trace `@dirty_trace_ids` Set so only modified traces are serialized (O(changed) instead of O(total))
6
+ - `load_from_local` no longer re-serializes every trace on boot — stores raw DB rows directly in `@persisted_trace_rows`
7
+ - `persist_dirty_traces` now handles explicit deletion tracking via `@deleted_trace_ids` Set
8
+ - Eliminates 97%+ single-core CPU usage caused by `decay_cycle` triggering full-store JSON serialization every 60 seconds
9
+
3
10
  ## [0.1.39] - 2026-06-01
4
11
  ### Fixed
5
12
  - `ErrorTracer` now guards against infinite recursion — if downstream trace storage triggers error/fatal logging, the `tracing?` thread-local flag prevents re-entry
@@ -22,7 +22,8 @@ module Legion
22
22
  @traces = {}
23
23
  @associations = Hash.new { |h, k| h[k] = Hash.new(0) }
24
24
  @partition_id = partition_id || resolve_partition_id
25
- @traces_dirty = false
25
+ @dirty_trace_ids = Set.new
26
+ @deleted_trace_ids = Set.new
26
27
  @associations_dirty = false
27
28
  @persisted_trace_rows = {}
28
29
  load_from_local
@@ -32,7 +33,7 @@ module Legion
32
33
  persisted_trace = Helpers::Trace.normalize_trace_affect(trace)
33
34
  persisted_trace[:partition_id] ||= @partition_id
34
35
  @mutex.synchronize do
35
- @traces_dirty = true if @traces[persisted_trace[:trace_id]] != persisted_trace
36
+ @dirty_trace_ids << persisted_trace[:trace_id]
36
37
  @traces[persisted_trace[:trace_id]] = persisted_trace
37
38
  end
38
39
  persisted_trace[:trace_id]
@@ -50,7 +51,10 @@ module Legion
50
51
  removed_links = @associations.delete(trace_id)
51
52
  @associations.each_value { |links| links.delete(trace_id) }
52
53
 
53
- @traces_dirty = true if removed_trace
54
+ if removed_trace
55
+ @dirty_trace_ids.delete(trace_id)
56
+ @deleted_trace_ids << trace_id
57
+ end
54
58
  @associations_dirty = true if removed_trace || removed_links
55
59
  end
56
60
  end
@@ -92,8 +96,10 @@ module Legion
92
96
  @associations_dirty = true
93
97
 
94
98
  threshold = Helpers::Trace::COACTIVATION_THRESHOLD
95
- @traces_dirty = true if @associations[trace_id_a][trace_id_b] >= threshold &&
96
- link_traces(trace_id_a, trace_id_b)
99
+ if @associations[trace_id_a][trace_id_b] >= threshold && link_traces(trace_id_a, trace_id_b)
100
+ @dirty_trace_ids << trace_id_a
101
+ @dirty_trace_ids << trace_id_b
102
+ end
97
103
  end
98
104
  end
99
105
 
@@ -128,7 +134,8 @@ module Legion
128
134
  @mutex.synchronize do
129
135
  @traces = snapshot
130
136
  @associations = Hash.new { |h, k| h[k] = Hash.new(0) }
131
- @traces_dirty = true
137
+ @dirty_trace_ids = Set.new(@traces.keys)
138
+ @deleted_trace_ids = Set.new
132
139
  @associations_dirty = true
133
140
  end
134
141
  flush
@@ -183,27 +190,41 @@ module Legion
183
190
  end
184
191
 
185
192
  def snapshot_dirty_state
186
- traces_snapshot, associations_snapshot, trace_rows_snapshot, trace_changes, associations_dirty = @mutex.synchronize do
187
- ts = @traces.transform_values(&:dup)
193
+ @mutex.synchronize do
194
+ dirty_ids = @dirty_trace_ids.to_a
195
+ deleted_ids = @deleted_trace_ids.to_a
196
+ associations_dirty = @associations_dirty
197
+
198
+ return nil if dirty_ids.empty? && deleted_ids.empty? && !associations_dirty
199
+
200
+ dirty_rows = dirty_ids.each_with_object({}) do |trace_id, h|
201
+ trace = @traces[trace_id]
202
+ h[trace_id] = serialize_trace_for_db(trace) if trace
203
+ end
204
+
205
+ trace_rows_snapshot = @persisted_trace_rows.merge(dirty_rows)
206
+ deleted_ids.each { |id| trace_rows_snapshot.delete(id) }
207
+
208
+ traces_snapshot = @traces.transform_values(&:dup)
188
209
  as = @associations.each_with_object({}) { |(tid, targets), memo| memo[tid] = targets.dup }
189
- trs = ts.transform_values { |trace| serialize_trace_for_db(trace) }
190
- changed_trace_ids = trs.each_key.reject { |trace_id| trs[trace_id] == @persisted_trace_rows[trace_id] }
191
- trace_changes = { dirty: @traces_dirty || changed_trace_ids.any?, changed_ids: changed_trace_ids }
192
- [ts, as, trs, trace_changes, @associations_dirty]
193
- end
194
- return nil unless trace_changes[:dirty] || associations_dirty
210
+ trace_changes = { dirty: true, changed_ids: dirty_ids, deleted_ids: deleted_ids }
195
211
 
196
- [traces_snapshot, associations_snapshot, trace_rows_snapshot, trace_changes, associations_dirty]
212
+ [traces_snapshot, as, trace_rows_snapshot, trace_changes, associations_dirty]
213
+ end
197
214
  end
198
215
 
199
216
  def persist_dirty_traces(db, trace_rows_snapshot, trace_changes, stale_ids)
200
- return unless trace_changes[:dirty] || !stale_ids.empty?
217
+ changed_ids = trace_changes[:changed_ids] || []
218
+ deleted_ids = trace_changes[:deleted_ids] || []
219
+ all_removals = (stale_ids + deleted_ids).uniq
220
+ return if changed_ids.empty? && all_removals.empty?
201
221
 
202
222
  ds = db[:memory_traces]
203
- trace_changes[:changed_ids].each do |trace_id|
204
- ds.insert_conflict(:replace).insert(trace_rows_snapshot.fetch(trace_id))
223
+ changed_ids.each do |trace_id|
224
+ row = trace_rows_snapshot[trace_id]
225
+ ds.insert_conflict(:replace).insert(row) if row
205
226
  end
206
- db[:memory_traces].where(trace_id: stale_ids).delete unless stale_ids.empty?
227
+ db[:memory_traces].where(trace_id: all_removals).delete unless all_removals.empty?
207
228
  end
208
229
 
209
230
  def persist_dirty_associations(db, associations_snapshot, scoped_trace_ids, memory_trace_ids, stale_ids, dirty)
@@ -232,7 +253,8 @@ module Legion
232
253
 
233
254
  def clear_dirty_flags(trace_rows_snapshot)
234
255
  @mutex.synchronize do
235
- @traces_dirty = false
256
+ @dirty_trace_ids.clear
257
+ @deleted_trace_ids.clear
236
258
  @associations_dirty = false
237
259
  @persisted_trace_rows = trace_rows_snapshot
238
260
  end
@@ -246,12 +268,13 @@ module Legion
246
268
 
247
269
  db[:memory_traces].where(partition_id: @partition_id).each do |row|
248
270
  @traces[row[:trace_id]] = deserialize_trace_from_db(row)
271
+ @persisted_trace_rows[row[:trace_id]] = row.dup
249
272
  end
250
273
 
251
274
  load_local_associations(db)
252
275
 
253
- @persisted_trace_rows = @traces.transform_values { |trace| serialize_trace_for_db(trace) }
254
- @traces_dirty = false
276
+ @dirty_trace_ids = Set.new
277
+ @deleted_trace_ids = Set.new
255
278
  @associations_dirty = false
256
279
  end
257
280
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Memory
7
- VERSION = '0.1.39'
7
+ VERSION = '0.1.40'
8
8
  end
9
9
  end
10
10
  end
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.39
4
+ version: 0.1.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity