lex-agentic-memory 0.1.38 → 0.1.39
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 +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/legion/extensions/agentic/memory/archaeology/helpers/archaeology_engine.rb +5 -4
- data/lib/legion/extensions/agentic/memory/trace/helpers/error_tracer.rb +55 -25
- data/lib/legion/extensions/agentic/memory/trace/helpers/hot_tier.rb +85 -18
- data/lib/legion/extensions/agentic/memory/trace/helpers/postgres_store.rb +33 -6
- data/lib/legion/extensions/agentic/memory/trace/runners/consolidation.rb +9 -3
- data/lib/legion/extensions/agentic/memory/version.rb +1 -1
- data/spec/legion/extensions/agentic/memory/trace/helpers/postgres_store_spec.rb +1 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89cf392450f3ff2b43d9ff1430c9cb4e8b108d0221b46aa4c2e3e064514c6200
|
|
4
|
+
data.tar.gz: 0343f1b89ae601f477939c5890fca5bcb2e8e4961f411049612731edf16ae43e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca0b83053d099fd5e445427105023210371b4e4048961bfa4d3446b8ca1412902c8c6dbbcfaa0881d787d55ba5ec70fc5321023374c3a9c35b6718d99e9af8b8
|
|
7
|
+
data.tar.gz: 5c6e4acf09c5cafae4694253381e963316fe898e9c738bb9ae0143e9788e242e01b5dd442f3187ebb8f997b1a3ef095fdb1c1fd9a77c0ac2072c0e255247b860
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.39] - 2026-06-01
|
|
4
|
+
### Fixed
|
|
5
|
+
- `ErrorTracer` now guards against infinite recursion — if downstream trace storage triggers error/fatal logging, the `tracing?` thread-local flag prevents re-entry
|
|
6
|
+
- `ErrorTracer` background worker thread now terminates cleanly — `setup` registers an `at_exit` hook, and a new `shutdown` method pushes the `:stop` sentinel to the queue (previously the thread ran forever with no shutdown path)
|
|
7
|
+
- `HotTier.serialize_trace` now preserves all trace fields (was only serializing 9 out of 25+) — adds `base_decay_rate`, `emotional_valence`, `emotional_intensity`, `origin`, `source_agent_id`, `domain_tags`, `associated_traces`, `child_trace_ids`, `reinforcement_count`, `unresolved`, `consolidation_candidate`, `last_decayed`, `created_at`, `encryption_key_id`, `parent_trace_id`
|
|
8
|
+
- `HotTier.deserialize_trace` reconstructs all fields with proper type coercion (arrays via JSON, booleans, integers, symbols, timestamps)
|
|
9
|
+
- `PostgresStore#deserialize_trace` now reads `child_trace_ids` from the DB column instead of hardcoding `[]` — children are no longer silently lost on retrieval
|
|
10
|
+
- `PostgresStore#serialize_trace` now writes `child_trace_ids` to the DB row
|
|
11
|
+
- `PostgresStore#map_update_fields` now maps `child_trace_ids` to the `:child_trace_ids` column (was `nil`, silently dropping partial updates)
|
|
12
|
+
- `PostgresStore#retrieve_by_domain` uses PostgreSQL JSONB `@>` containment operator for exact array-member matching instead of `LIKE` — eliminates full table scans and substring false positives (e.g., `%auth%` matching `oauth`)
|
|
13
|
+
- `Consolidation#erase_by_type` now uses `batch_delete_by_type` on PostgresStore, avoiding loading all matching traces into Ruby memory before deleting one-by-one (O(100k) heap allocation → O(1) SQL delete)
|
|
14
|
+
|
|
3
15
|
## [0.1.38] - 2026-05-27
|
|
4
16
|
### Fixed
|
|
5
17
|
- `PostgresStore#parse_json_or_raw` now requires matching start/end delimiters (`{}`/`[]`) before attempting JSON parse — eliminates 100k+/day error log spam from bracket-prefixed text content (e.g. `[trace_persistence] ...`) that triggered a self-amplifying feedback loop via RabbitMQ logging
|
|
@@ -161,12 +161,13 @@ module Legion
|
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
def site_summary(site)
|
|
164
|
+
artifact_types = site.artifacts_found.each_with_object(Hash.new(0)) do |a, h|
|
|
165
|
+
h[a.artifact_type] += 1
|
|
166
|
+
end
|
|
167
|
+
|
|
164
168
|
site.survey.merge(
|
|
165
169
|
depth_progress: depth_progress(site),
|
|
166
|
-
artifact_types:
|
|
167
|
-
.each_with_object(Hash.new(0)) do |a, h|
|
|
168
|
-
h[a.artifact_type] += 1
|
|
169
|
-
end
|
|
170
|
+
artifact_types: artifact_types
|
|
170
171
|
)
|
|
171
172
|
end
|
|
172
173
|
|
|
@@ -25,6 +25,7 @@ module Legion
|
|
|
25
25
|
@worker.name = 'legion-error-tracer'
|
|
26
26
|
wrap_logging_methods
|
|
27
27
|
@active = true
|
|
28
|
+
register_shutdown_hook
|
|
28
29
|
Legion::Logging.info '[memory] ErrorTracer active — errors/fatals will become episodic traces'
|
|
29
30
|
end
|
|
30
31
|
|
|
@@ -32,8 +33,25 @@ module Legion
|
|
|
32
33
|
@active == true
|
|
33
34
|
end
|
|
34
35
|
|
|
36
|
+
# Gracefully shut down the background worker and unregister the at_exit hook.
|
|
37
|
+
def shutdown
|
|
38
|
+
return unless @active
|
|
39
|
+
|
|
40
|
+
@write_queue&.push(:stop)
|
|
41
|
+
@worker&.join(5) # wait up to 5 seconds for clean exit
|
|
42
|
+
@active = false
|
|
43
|
+
Legion::Logging.info '[memory] ErrorTracer shut down'
|
|
44
|
+
rescue StandardError
|
|
45
|
+
@active = false
|
|
46
|
+
end
|
|
47
|
+
|
|
35
48
|
private
|
|
36
49
|
|
|
50
|
+
def register_shutdown_hook
|
|
51
|
+
@shutdown_hook = proc { ErrorTracer.shutdown }
|
|
52
|
+
at_exit(&@shutdown_hook)
|
|
53
|
+
end
|
|
54
|
+
|
|
37
55
|
def drain_queue
|
|
38
56
|
loop do
|
|
39
57
|
payload = @write_queue.pop
|
|
@@ -54,43 +72,55 @@ module Legion
|
|
|
54
72
|
Legion::Logging.define_singleton_method(:error) do |message = nil, &block|
|
|
55
73
|
message = block.call if message.nil? && block
|
|
56
74
|
original_error.call(message)
|
|
57
|
-
ErrorTracer.send(:record_trace, message, :error) if message.is_a?(String)
|
|
75
|
+
ErrorTracer.send(:record_trace, message, :error) if message.is_a?(String) && !ErrorTracer.send(:tracing?)
|
|
58
76
|
end
|
|
59
77
|
|
|
60
78
|
Legion::Logging.define_singleton_method(:fatal) do |message = nil, &block|
|
|
61
79
|
message = block.call if message.nil? && block
|
|
62
80
|
original_fatal.call(message)
|
|
63
|
-
ErrorTracer.send(:record_trace, message, :fatal) if message.is_a?(String)
|
|
81
|
+
ErrorTracer.send(:record_trace, message, :fatal) if message.is_a?(String) && !ErrorTracer.send(:tracing?)
|
|
64
82
|
end
|
|
65
83
|
end
|
|
66
84
|
|
|
85
|
+
def tracing?
|
|
86
|
+
Thread.current[:error_tracer_active]
|
|
87
|
+
end
|
|
88
|
+
|
|
67
89
|
def record_trace(message, level)
|
|
68
90
|
return unless message.is_a?(String) && !message.empty?
|
|
69
91
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
# Guard against infinite recursion if downstream logging triggers error/fatal
|
|
93
|
+
return if tracing?
|
|
94
|
+
|
|
95
|
+
Thread.current[:error_tracer_active] = true
|
|
96
|
+
begin
|
|
97
|
+
now = Time.now.utc
|
|
98
|
+
key = "#{level}:#{message[0..100]}"
|
|
99
|
+
|
|
100
|
+
@recent_mutex.synchronize do
|
|
101
|
+
return if @recent[key] && (now - @recent[key]) < DEBOUNCE_WINDOW
|
|
102
|
+
|
|
103
|
+
@recent[key] = now
|
|
104
|
+
@recent.delete_if { |_, t| (now - t) > DEBOUNCE_WINDOW } if @recent.size > 500
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
component = message.match(/\A\[([^\]]+)\]/)&.captures&.first || 'unknown'
|
|
108
|
+
valence = level == :fatal ? FATAL_VALENCE : ERROR_VALENCE
|
|
109
|
+
intensity = level == :fatal ? FATAL_INTENSITY : ERROR_INTENSITY
|
|
110
|
+
|
|
111
|
+
@write_queue.push(
|
|
112
|
+
type: :episodic,
|
|
113
|
+
content_payload: message,
|
|
114
|
+
domain_tags: ['error', component.downcase],
|
|
115
|
+
origin: :direct_experience,
|
|
116
|
+
emotional_valence: valence,
|
|
117
|
+
emotional_intensity: intensity,
|
|
118
|
+
unresolved: true,
|
|
119
|
+
confidence: 0.9
|
|
120
|
+
)
|
|
121
|
+
ensure
|
|
122
|
+
Thread.current[:error_tracer_active] = false
|
|
78
123
|
end
|
|
79
|
-
|
|
80
|
-
component = message.match(/\A\[([^\]]+)\]/)&.captures&.first || 'unknown'
|
|
81
|
-
valence = level == :fatal ? FATAL_VALENCE : ERROR_VALENCE
|
|
82
|
-
intensity = level == :fatal ? FATAL_INTENSITY : ERROR_INTENSITY
|
|
83
|
-
|
|
84
|
-
@write_queue.push(
|
|
85
|
-
type: :episodic,
|
|
86
|
-
content_payload: message,
|
|
87
|
-
domain_tags: ['error', component.downcase],
|
|
88
|
-
origin: :direct_experience,
|
|
89
|
-
emotional_valence: valence,
|
|
90
|
-
emotional_intensity: intensity,
|
|
91
|
-
unresolved: true,
|
|
92
|
-
confidence: 0.9
|
|
93
|
-
)
|
|
94
124
|
rescue StandardError
|
|
95
125
|
nil
|
|
96
126
|
end
|
|
@@ -81,34 +81,101 @@ module Legion
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# Serialize a trace hash to a string-only flat hash suitable for Redis HSET.
|
|
84
|
+
# All fields are preserved as strings; arrays/hashes are JSON-encoded.
|
|
84
85
|
def serialize_trace(trace)
|
|
86
|
+
payload = trace[:content_payload] || trace[:content]
|
|
85
87
|
{
|
|
86
|
-
'trace_id'
|
|
87
|
-
'trace_type'
|
|
88
|
-
'content_payload'
|
|
89
|
-
'strength'
|
|
90
|
-
'peak_strength'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
94
|
-
'
|
|
88
|
+
'trace_id' => trace[:trace_id].to_s,
|
|
89
|
+
'trace_type' => trace[:trace_type].to_s,
|
|
90
|
+
'content_payload' => payload.is_a?(Hash) || payload.is_a?(Array) ? Legion::JSON.dump(payload) : payload.to_s,
|
|
91
|
+
'strength' => trace[:strength].to_s,
|
|
92
|
+
'peak_strength' => trace[:peak_strength].to_s,
|
|
93
|
+
'base_decay_rate' => trace[:base_decay_rate].to_s,
|
|
94
|
+
'confidence' => trace[:confidence].to_s,
|
|
95
|
+
'emotional_valence' => trace[:emotional_valence].to_s,
|
|
96
|
+
'emotional_intensity' => trace[:emotional_intensity].to_s,
|
|
97
|
+
'storage_tier' => 'hot',
|
|
98
|
+
'partition_id' => trace[:partition_id].to_s,
|
|
99
|
+
'origin' => trace[:origin].to_s,
|
|
100
|
+
'source_agent_id' => trace[:source_agent_id].to_s,
|
|
101
|
+
'encryption_key_id' => trace[:encryption_key_id].to_s,
|
|
102
|
+
'parent_trace_id' => trace[:parent_trace_id].to_s,
|
|
103
|
+
'domain_tags' => trace[:domain_tags].is_a?(Array) ? Legion::JSON.dump(trace[:domain_tags]) : '[]',
|
|
104
|
+
'associated_traces' => trace[:associated_traces].is_a?(Array) ? Legion::JSON.dump(trace[:associated_traces]) : '[]',
|
|
105
|
+
'child_trace_ids' => trace[:child_trace_ids].is_a?(Array) ? Legion::JSON.dump(trace[:child_trace_ids]) : '[]',
|
|
106
|
+
'reinforcement_count' => trace[:reinforcement_count].to_s,
|
|
107
|
+
'unresolved' => trace[:unresolved].to_s,
|
|
108
|
+
'consolidation_candidate' => trace[:consolidation_candidate].to_s,
|
|
109
|
+
'last_reinforced' => (trace[:last_reinforced] || Time.now).to_s,
|
|
110
|
+
'last_decayed' => trace[:last_decayed].to_s,
|
|
111
|
+
'created_at' => trace[:created_at].to_s
|
|
95
112
|
}
|
|
96
113
|
end
|
|
97
114
|
|
|
98
115
|
# Deserialize a Redis string-hash back to a typed trace hash.
|
|
99
116
|
def deserialize_trace(data)
|
|
100
117
|
{
|
|
101
|
-
trace_id:
|
|
102
|
-
trace_type:
|
|
103
|
-
content_payload:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
trace_id: data['trace_id'],
|
|
119
|
+
trace_type: data['trace_type']&.to_sym,
|
|
120
|
+
content_payload: parse_json_or_string(data['content_payload']),
|
|
121
|
+
content: parse_json_or_string(data['content_payload']),
|
|
122
|
+
strength: data['strength']&.to_f,
|
|
123
|
+
peak_strength: data['peak_strength']&.to_f,
|
|
124
|
+
base_decay_rate: data['base_decay_rate']&.to_f,
|
|
125
|
+
confidence: data['confidence']&.to_f,
|
|
126
|
+
emotional_valence: data['emotional_valence'].to_f,
|
|
127
|
+
emotional_intensity: data['emotional_intensity'].to_f,
|
|
128
|
+
storage_tier: :hot,
|
|
129
|
+
partition_id: presence(data['partition_id']),
|
|
130
|
+
origin: presence(data['origin'])&.to_sym,
|
|
131
|
+
source_agent_id: presence(data['source_agent_id']),
|
|
132
|
+
encryption_key_id: presence(data['encryption_key_id']),
|
|
133
|
+
parent_trace_id: presence(data['parent_trace_id']),
|
|
134
|
+
domain_tags: parse_json_array(data['domain_tags']),
|
|
135
|
+
associated_traces: parse_json_array(data['associated_traces']),
|
|
136
|
+
child_trace_ids: parse_json_array(data['child_trace_ids']),
|
|
137
|
+
reinforcement_count: data['reinforcement_count'].to_i,
|
|
138
|
+
unresolved: data['unresolved'] == 'true',
|
|
139
|
+
consolidation_candidate: data['consolidation_candidate'] == 'true',
|
|
140
|
+
last_reinforced: data['last_reinforced'],
|
|
141
|
+
last_decayed: presence(data['last_decayed']),
|
|
142
|
+
created_at: presence(data['created_at'])
|
|
110
143
|
}
|
|
111
144
|
end
|
|
145
|
+
|
|
146
|
+
# Parse a JSON array string safely; returns [] on failure.
|
|
147
|
+
def parse_json_array(raw)
|
|
148
|
+
return [] if raw.nil? || !raw.is_a?(String) || raw.strip.empty?
|
|
149
|
+
|
|
150
|
+
parsed = Legion::JSON.load(raw)
|
|
151
|
+
parsed.is_a?(Array) ? parsed : []
|
|
152
|
+
rescue StandardError => e
|
|
153
|
+
log.debug "[trace_persistence] parse_json_array: #{e.message}"
|
|
154
|
+
[]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Attempt to parse JSON, fall back to raw string.
|
|
158
|
+
def parse_json_or_string(raw)
|
|
159
|
+
return raw unless raw.is_a?(String)
|
|
160
|
+
|
|
161
|
+
stripped = raw.strip
|
|
162
|
+
return raw unless (stripped.start_with?('{') && stripped.end_with?('}')) ||
|
|
163
|
+
(stripped.start_with?('[') && stripped.end_with?(']'))
|
|
164
|
+
|
|
165
|
+
parsed = Legion::JSON.load(stripped)
|
|
166
|
+
parsed.is_a?(Hash) || parsed.is_a?(Array) ? parsed : raw
|
|
167
|
+
rescue StandardError => e
|
|
168
|
+
log.debug "[trace_persistence] parse_json_or_string: #{e.message}"
|
|
169
|
+
raw
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Return value only if it is a non-empty string.
|
|
173
|
+
def presence(value)
|
|
174
|
+
return nil unless value.is_a?(String)
|
|
175
|
+
|
|
176
|
+
stripped = value.strip
|
|
177
|
+
stripped.empty? ? nil : stripped
|
|
178
|
+
end
|
|
112
179
|
end
|
|
113
180
|
end
|
|
114
181
|
end
|
|
@@ -80,12 +80,22 @@ module Legion
|
|
|
80
80
|
[]
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
# Retrieve traces whose domain_tags
|
|
83
|
+
# Retrieve traces whose domain_tags JSON array contains the given tag.
|
|
84
|
+
# Uses PostgreSQL JSON containment operator (@>) when available for exact array-member
|
|
85
|
+
# matching; falls back to LIKE for other adapters.
|
|
84
86
|
def retrieve_by_domain(tag, min_strength: 0.0, limit: 50)
|
|
85
87
|
return [] unless db_ready?
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
ds = traces_ds
|
|
90
|
+
if db.adapter_scheme == :postgres
|
|
91
|
+
# JSONB @> operator matches exact array elements, not substrings
|
|
92
|
+
json_array = ::JSON.dump([tag])
|
|
93
|
+
ds = ds.where(Sequel.lit("domain_tags::jsonb @> '#{json_array}'::jsonb"))
|
|
94
|
+
else
|
|
95
|
+
# Fallback: substring match (imprecise but broadly compatible)
|
|
96
|
+
ds = ds.where(Sequel.like(:domain_tags, "%#{tag}%"))
|
|
97
|
+
end
|
|
98
|
+
rows = ds
|
|
89
99
|
.where { strength >= min_strength }
|
|
90
100
|
.order(Sequel.desc(:strength))
|
|
91
101
|
.limit(limit)
|
|
@@ -330,6 +340,7 @@ module Legion
|
|
|
330
340
|
parent_trace_id: sanitize_pg_string(trace[:parent_trace_id]),
|
|
331
341
|
encryption_key_id: sanitize_pg_string(trace[:encryption_key_id]),
|
|
332
342
|
partition_id: sanitize_pg_string(trace[:partition_id]),
|
|
343
|
+
child_trace_ids: sanitize_pg_string((trace[:child_trace_ids] || []).is_a?(Array) ? Legion::JSON.dump(trace[:child_trace_ids]) : '[]'),
|
|
333
344
|
created_at: trace[:created_at] || Time.now.utc,
|
|
334
345
|
accessed_at: Time.now.utc
|
|
335
346
|
}
|
|
@@ -360,7 +371,7 @@ module Legion
|
|
|
360
371
|
encryption_key_id: row[:encryption_key_id],
|
|
361
372
|
associated_traces: parse_json_array(row[:associations]),
|
|
362
373
|
parent_trace_id: row[:parent_trace_id],
|
|
363
|
-
child_trace_ids: [],
|
|
374
|
+
child_trace_ids: parse_json_array(row[:child_trace_ids]),
|
|
364
375
|
unresolved: row[:unresolved] || false,
|
|
365
376
|
consolidation_candidate: row[:consolidation_candidate] || false
|
|
366
377
|
}
|
|
@@ -372,7 +383,7 @@ module Legion
|
|
|
372
383
|
content_payload: :content,
|
|
373
384
|
associated_traces: :associations,
|
|
374
385
|
parent_trace_id: :parent_trace_id,
|
|
375
|
-
child_trace_ids:
|
|
386
|
+
child_trace_ids: :child_trace_ids
|
|
376
387
|
}
|
|
377
388
|
|
|
378
389
|
fields.each_with_object({}) do |(k, v), row|
|
|
@@ -382,7 +393,7 @@ module Legion
|
|
|
382
393
|
row[col] = case col
|
|
383
394
|
when :content
|
|
384
395
|
sanitize_pg_string(v.is_a?(Hash) ? Legion::JSON.dump(v) : v.to_s)
|
|
385
|
-
when :associations
|
|
396
|
+
when :associations, :child_trace_ids
|
|
386
397
|
sanitize_pg_string(v.is_a?(Array) ? Legion::JSON.dump(v) : '[]')
|
|
387
398
|
when :domain_tags
|
|
388
399
|
sanitize_pg_string(v.is_a?(Array) ? Legion::JSON.dump(v) : nil)
|
|
@@ -394,6 +405,22 @@ module Legion
|
|
|
394
405
|
end
|
|
395
406
|
end
|
|
396
407
|
|
|
408
|
+
# Delete all traces of a given type in a single SQL statement,
|
|
409
|
+
# avoiding loading rows into Ruby memory.
|
|
410
|
+
def batch_delete_by_type(trace_type)
|
|
411
|
+
return 0 unless db_ready?
|
|
412
|
+
|
|
413
|
+
ids = traces_ds
|
|
414
|
+
.where(trace_type: trace_type.to_s)
|
|
415
|
+
.select_map(:trace_id)
|
|
416
|
+
|
|
417
|
+
ids.each { |tid| delete(tid) }
|
|
418
|
+
ids.size
|
|
419
|
+
rescue StandardError => e
|
|
420
|
+
log_warn("batch_delete_by_type failed: #{e.message}")
|
|
421
|
+
0
|
|
422
|
+
end
|
|
423
|
+
|
|
397
424
|
def parse_json_or_raw(raw)
|
|
398
425
|
return raw unless raw.is_a?(String)
|
|
399
426
|
|
|
@@ -117,9 +117,15 @@ module Legion
|
|
|
117
117
|
def erase_by_type(type:, store: nil, **)
|
|
118
118
|
store ||= default_store
|
|
119
119
|
type = type.to_sym
|
|
120
|
-
|
|
121
|
-
count =
|
|
122
|
-
|
|
120
|
+
|
|
121
|
+
count = if store.respond_to?(:batch_delete_by_type)
|
|
122
|
+
store.batch_delete_by_type(type)
|
|
123
|
+
else
|
|
124
|
+
traces = store.retrieve_by_type(type, min_strength: 0.0, limit: 100_000)
|
|
125
|
+
traces.each { |t| store.delete(t[:trace_id]) }
|
|
126
|
+
traces.size
|
|
127
|
+
end
|
|
128
|
+
|
|
123
129
|
persist_store(store) if count.positive?
|
|
124
130
|
log.info("[memory] erased #{count} traces of type=#{type}")
|
|
125
131
|
{ erased: count, type: type }
|
|
@@ -36,6 +36,7 @@ RSpec.describe Legion::Extensions::Agentic::Memory::Trace::Helpers::PostgresStor
|
|
|
36
36
|
String :parent_trace_id, size: 36
|
|
37
37
|
String :encryption_key_id
|
|
38
38
|
String :partition_id
|
|
39
|
+
String :child_trace_ids, text: true
|
|
39
40
|
DateTime :created_at
|
|
40
41
|
DateTime :accessed_at
|
|
41
42
|
end
|