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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d111b8e5f52156c7060509c6d50d9c4ffe3f7d02108e7f1fcb6a5ba03f1982d8
|
|
4
|
+
data.tar.gz: 52db96d9807a927c29a097c7a0a47b50d63c43069e657bc7fdad57119a09152a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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:
|
|
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
|
-
@
|
|
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
|
-
@
|
|
254
|
-
@
|
|
276
|
+
@dirty_trace_ids = Set.new
|
|
277
|
+
@deleted_trace_ids = Set.new
|
|
255
278
|
@associations_dirty = false
|
|
256
279
|
end
|
|
257
280
|
|