lex-agentic-memory 0.1.32 → 0.1.33

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: 07b491ea4b919f623905cf3fe0c651c6a462e46bfe096e06e4221e9eb9e6b801
4
- data.tar.gz: b92fc5808525806e2dca50c86ea4382583adcbe5b6b9e84d0dd8e1c4add0f14b
3
+ metadata.gz: 01630a8c5fd981fb85c69337d299f71dc215b79b9ce3dca7a9b0aef57545fde3
4
+ data.tar.gz: 6eb6bd8f8e924aacb9a805561f9f39c0ac18b5d1f559faa7f58a1af3a6d3ad8a
5
5
  SHA512:
6
- metadata.gz: ed8eb6cff096080b1435aab249896d4c473d1aab7b11ebec1f687081aa9494bc8671c029d3d7e2e80b4144b705b477e6d58fc796c412d2a6bf75a852fa02f27e
7
- data.tar.gz: 834d02319dbf0c63722d8bc8e59e5f63d6c18c42c740a71a47ac5f1c6df0c1ad9b983e73b246f751bd24a784aa4e43c5d5f508ed516dbd7f94321fea220f2cf3
6
+ metadata.gz: 69c12558b2cb6d7a883a9f0bd345ad0934273847b1a44f2dcaed02682a347d9a88dfddbbf6b2ab48d91a418c83f137eb585723dc3aecf19c0a2fb2e1454e7bd3
7
+ data.tar.gz: 8200e4e37b770d4afa344153cd6dc95b9bd047dd55d34079b1bfc6c87c7f261e0049ee2c6f77e4f2b04bf7455745f88eb06160ad006eb81dda7ee0d2053093f9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.33] - 2026-05-07
4
+ ### Fixed
5
+ - `PostgresStore#parse_json_or_raw` no longer attempts JSON parse on plain-text content — checks for `{`/`[` prefix before parsing, eliminating ERROR log spam during bulk reads with mixed content types
6
+ - `PostgresStore#parse_json_array` now guards against nil/empty strings before parsing
7
+ - `Store#parse_db_json` now short-circuits on nil/empty columns (domain_tags, associations, child_ids, emotional_valence) before attempting parse, fixing ERROR spam on traces with sparse optional fields
8
+
3
9
  ## [0.1.32] - 2026-05-07
4
10
  ### Fixed
5
11
  - Trace association retrieval now snapshots associated traces under the store mutex before filtering.
@@ -394,15 +394,18 @@ module Legion
394
394
  def parse_json_or_raw(raw)
395
395
  return raw unless raw.is_a?(String)
396
396
 
397
- parsed = Legion::JSON.load(raw)
398
- parsed.is_a?(Hash) ? parsed : raw
397
+ stripped = raw.strip
398
+ return raw unless stripped.start_with?('{', '[')
399
+
400
+ parsed = Legion::JSON.load(stripped)
401
+ parsed.is_a?(Hash) || parsed.is_a?(Array) ? parsed : raw
399
402
  rescue StandardError => e
400
403
  log.error "[trace_persistence] parse_json_or_raw: #{e.message}"
401
404
  raw
402
405
  end
403
406
 
404
407
  def parse_json_array(raw)
405
- return [] unless raw.is_a?(String)
408
+ return [] if raw.nil? || !raw.is_a?(String) || raw.strip.empty?
406
409
 
407
410
  result = Legion::JSON.load(raw)
408
411
  result.is_a?(Array) ? result : []
@@ -358,6 +358,8 @@ module Legion
358
358
  end
359
359
 
360
360
  def parse_db_json(raw, field, symbolize: false, &default)
361
+ return default&.call if raw.nil? || raw.to_s.strip.empty?
362
+
361
363
  parsed = Legion::JSON.load(raw.to_s)
362
364
  symbolize ? symbolize_keys(parsed) : parsed
363
365
  rescue StandardError => e
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Memory
7
- VERSION = '0.1.32'
7
+ VERSION = '0.1.33'
8
8
  end
9
9
  end
10
10
  end
@@ -556,4 +556,61 @@ RSpec.describe Legion::Extensions::Agentic::Memory::Trace::Helpers::PostgresStor
556
556
  expect(store.flush).to be_nil
557
557
  end
558
558
  end
559
+
560
+ # --- plain-text content round-trip (log spam regression) ---
561
+
562
+ describe 'plain-text content deserialization' do
563
+ it 'returns plain-text content as-is without logging errors' do
564
+ trace = trace_helper.new_trace(type: :episodic, content_payload: 'It appears the service is down.')
565
+ store.store(trace)
566
+
567
+ expect(store).not_to receive(:log)
568
+ result = store.retrieve(trace[:trace_id])
569
+ expect(result[:content]).to eq('It appears the service is down.')
570
+ end
571
+
572
+ it 'parses JSON object content into a hash' do
573
+ trace = trace_helper.new_trace(type: :semantic, content_payload: { fact: 'ruby' })
574
+ store.store(trace)
575
+
576
+ result = store.retrieve(trace[:trace_id])
577
+ expect(result[:content]).to be_a(Hash)
578
+ end
579
+
580
+ it 'does not log errors when domain_tags column is nil' do
581
+ trace = trace_helper.new_trace(type: :episodic, content_payload: 'hello')
582
+ store.store(trace)
583
+
584
+ db[:memory_traces].where(trace_id: trace[:trace_id]).update(domain_tags: nil)
585
+
586
+ expect(store).not_to receive(:log)
587
+ result = store.retrieve(trace[:trace_id])
588
+ expect(result[:domain_tags]).to eq([])
589
+ end
590
+
591
+ it 'does not log errors when associations column is nil' do
592
+ trace = trace_helper.new_trace(type: :episodic, content_payload: 'hello')
593
+ store.store(trace)
594
+
595
+ db[:memory_traces].where(trace_id: trace[:trace_id]).update(associations: nil)
596
+
597
+ expect(store).not_to receive(:log)
598
+ result = store.retrieve(trace[:trace_id])
599
+ expect(result[:associated_traces]).to eq([])
600
+ end
601
+
602
+ it 'generates no ERROR log lines during a bulk read with mixed content types' do
603
+ plain_trace = trace_helper.new_trace(type: :episodic, content_payload: 'Hello, I am plain text')
604
+ json_trace = trace_helper.new_trace(type: :semantic, content_payload: { fact: 'structured' })
605
+ store.store(plain_trace)
606
+ store.store(json_trace)
607
+
608
+ log_double = double('log', debug: nil, info: nil, warn: nil)
609
+ allow(store).to receive(:log).and_return(log_double)
610
+ expect(log_double).not_to receive(:error)
611
+
612
+ results = store.all_traces
613
+ expect(results.size).to eq(2)
614
+ end
615
+ end
559
616
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'sequel'
4
+
3
5
  RSpec.describe Legion::Extensions::Agentic::Memory::Trace::Helpers::Store do
4
6
  let(:store) { described_class.new }
5
7
  let(:trace_helper) { Legion::Extensions::Agentic::Memory::Trace::Helpers::Trace }
@@ -235,6 +237,78 @@ RSpec.describe Legion::Extensions::Agentic::Memory::Trace::Helpers::Store do
235
237
  end
236
238
  end
237
239
 
240
+ describe 'parse_db_json nil/empty guard (log spam regression)' do
241
+ let(:local_db) do
242
+ d = Sequel.sqlite
243
+ d.create_table(:memory_traces) do
244
+ primary_key :id
245
+ String :trace_id, size: 36, null: false, unique: true
246
+ String :trace_type, null: false, default: 'episodic'
247
+ String :content, text: true, null: false, default: ''
248
+ Float :strength, default: 1.0
249
+ Float :peak_strength, default: 1.0
250
+ Float :base_decay_rate, default: 0.02
251
+ Float :emotional_valence
252
+ Float :emotional_intensity, default: 0.0
253
+ String :domain_tags, text: true
254
+ String :origin
255
+ String :storage_tier, default: 'hot'
256
+ DateTime :created_at
257
+ DateTime :last_reinforced
258
+ DateTime :last_decayed
259
+ Integer :reinforcement_count, default: 0
260
+ Float :confidence, default: 0.5
261
+ String :partition_id
262
+ String :associated_traces, text: true
263
+ String :parent_id
264
+ String :child_ids, text: true
265
+ TrueClass :unresolved, default: false
266
+ TrueClass :consolidation_candidate, default: false
267
+ end
268
+ d.create_table(:memory_associations) do
269
+ primary_key :id
270
+ String :trace_id_a, size: 36, null: false
271
+ String :trace_id_b, size: 36, null: false
272
+ Integer :coactivation_count, default: 1, null: false
273
+ String :partition_id
274
+ unique %i[trace_id_a trace_id_b]
275
+ end
276
+ d
277
+ end
278
+
279
+ let(:data_local_stub) do
280
+ conn = local_db
281
+ Module.new do
282
+ define_singleton_method(:connected?) { true }
283
+ define_singleton_method(:connection) { conn }
284
+ define_singleton_method(:table_exists?) { |name| conn.table_exists?(name) }
285
+ end
286
+ end
287
+
288
+ before { stub_const('Legion::Data::Local', data_local_stub) }
289
+
290
+ it 'does not log errors when nil optional columns are deserialized on load' do
291
+ local_db[:memory_traces].insert(
292
+ trace_id: SecureRandom.uuid,
293
+ trace_type: 'episodic',
294
+ content: 'plain text content',
295
+ partition_id: 'default',
296
+ strength: 1.0,
297
+ domain_tags: nil,
298
+ associated_traces: nil,
299
+ child_ids: nil,
300
+ emotional_valence: nil
301
+ )
302
+
303
+ fresh = described_class.new
304
+ expect(fresh.count).to eq(1)
305
+ trace = fresh.traces.values.first
306
+ expect(trace[:domain_tags]).to eq([])
307
+ expect(trace[:associated_traces]).to eq([])
308
+ expect(trace[:child_trace_ids]).to eq([])
309
+ end
310
+ end
311
+
238
312
  describe '#restore_traces' do
239
313
  it 'replaces existing traces and clears stale associations' do
240
314
  store.store(semantic_trace)
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.32
4
+ version: 0.1.33
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity