lex-agentic-self 0.1.13 → 0.1.15

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: 86842222337e93a3e159eff411592f845792f5fde64fb32691feed47fff7ef7b
4
- data.tar.gz: 440179ab3ad0a8dde91657cd0e2955a2dae16a2e50fdcdd99b7aff163fcda232
3
+ metadata.gz: 9353f26016962ab841ebc5b4e5d717d1ba7b94d5c6ccd9da3be8ed8c7fdeb938
4
+ data.tar.gz: 5544eb5d69d9394c6f6071acfc4ea54e0a651f700f0d41ede5ac2f9e85be33bb
5
5
  SHA512:
6
- metadata.gz: b180e99b145fc1f272cb29fafa7d09d28fa9b26aa3f5d56b11e6ab3263a31062234625f921ddd2eed47e891063aae2607298cdca21967b28f6072add6efb5184
7
- data.tar.gz: 84e76ec6b1d8a781ce47f91c63218fefcc6a641068ec76bc0ec1e32aeb30f8da1869737b11c74a015a859b814f1bb69f3af3d2557483fd37970fdf3df24fd858
6
+ metadata.gz: 125c49e963e0ed3832a81a277a4f71fe815fa7fbfbc8b264775205ea6b06085313fdfc716383b4364afcff1bdb9b2d5367904eeac1a20a93254dbbb1232b004d
7
+ data.tar.gz: c5c64a7caf5080f6b0a335985ece8aefeaacdb2fccd4096408e1902ba0ab73e1c3cc0beb4658f9d3fbd72a5ccb795aa3597df0928b768f06856163edac366486
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.15] - 2026-07-16
4
+ ### Added
5
+ - Per-partner identity fingerprint models: `CognitiveFingerprint` runner now accepts `partner_identity:` on all methods, routing each partner to an isolated `FingerprintEngine` instance via an in-memory registry.
6
+ - `erase_partner!(identity:)` removes the fingerprint model for a given partner identity; returns `{erased: true/false, identity:}`.
7
+ - `tracked_partners` returns the list of partner identities currently held in the registry.
8
+
9
+ ## [0.1.14] - 2026-05-07
10
+ ### Fixed
11
+ - Identity fingerprint save now warns and returns false if local persistence disconnects after previously being available.
12
+ - Reflection health now updates trust drift and mode-pattern category scores from tick results.
13
+ - Reflection LLM enhancer rejects arbitrary extra keyword arguments instead of forwarding them.
14
+
3
15
  ## [0.1.13] - 2026-04-22
4
16
  ### Added
5
17
  - Module-level snapshot methods (`personality_snapshot`, `reflection_snapshot`, `restore_personality`, `restore_reflections`) enabling memory↔self bridge for snapshot save/restore
@@ -12,14 +12,6 @@ module Legion
12
12
  module Fingerprint
13
13
  class Client
14
14
  include Runners::CognitiveFingerprint
15
-
16
- def initialize(**)
17
- @fingerprint_engine = Helpers::FingerprintEngine.new
18
- end
19
-
20
- private
21
-
22
- attr_reader :fingerprint_engine
23
15
  end
24
16
  end
25
17
  end
@@ -10,26 +10,26 @@ module Legion
10
10
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
11
11
  Legion::Extensions::Helpers.const_defined?(:Lex, false)
12
12
 
13
- def record_observation(category:, value:, **)
13
+ def record_observation(category:, value:, partner_identity: nil, **)
14
14
  category = category.to_sym
15
- result = fingerprint_engine.record_observation(category: category, value: value.to_f)
15
+ result = engine_for(partner_identity).record_observation(category: category, value: value.to_f)
16
16
  log.debug "[cognitive_fingerprint] record category=#{category} " \
17
17
  "baseline=#{result[:baseline]&.round(4)} samples=#{result[:samples]}"
18
18
  result
19
19
  end
20
20
 
21
- def verify_identity(observations:, **)
21
+ def verify_identity(observations:, partner_identity: nil, **)
22
22
  parsed = Array(observations).map do |obs|
23
23
  { category: obs[:category].to_sym, value: obs[:value].to_f }
24
24
  end
25
- result = fingerprint_engine.verify_identity(observations: parsed)
25
+ result = engine_for(partner_identity).verify_identity(observations: parsed)
26
26
  log.info "[cognitive_fingerprint] verify score=#{result[:match_score]&.round(4)} " \
27
27
  "verdict=#{result[:verdict]}"
28
28
  result
29
29
  end
30
30
 
31
- def anomaly_check(category:, value:, **)
32
- result = fingerprint_engine.anomaly_check(category: category.to_sym, value: value.to_f)
31
+ def anomaly_check(category:, value:, partner_identity: nil, **)
32
+ result = engine_for(partner_identity).anomaly_check(category: category.to_sym, value: value.to_f)
33
33
  if result[:anomaly]
34
34
  log.warn "[cognitive_fingerprint] anomaly category=#{category} " \
35
35
  "deviation=#{result[:deviation]&.round(4)}"
@@ -37,45 +37,64 @@ module Legion
37
37
  result
38
38
  end
39
39
 
40
- def trait_profile(**)
41
- { profile: fingerprint_engine.trait_profile }
40
+ def trait_profile(partner_identity: nil, **)
41
+ { profile: engine_for(partner_identity).trait_profile }
42
42
  end
43
43
 
44
- def strongest_traits(top_n: 3, **)
45
- { traits: fingerprint_engine.strongest_traits(top_n.to_i) }
44
+ def strongest_traits(top_n: 3, partner_identity: nil, **)
45
+ { traits: engine_for(partner_identity).strongest_traits(top_n.to_i) }
46
46
  end
47
47
 
48
- def weakest_traits(top_n: 3, **)
49
- { traits: fingerprint_engine.weakest_traits(top_n.to_i) }
48
+ def weakest_traits(top_n: 3, partner_identity: nil, **)
49
+ { traits: engine_for(partner_identity).weakest_traits(top_n.to_i) }
50
50
  end
51
51
 
52
- def identity_confidence(**)
53
- confidence = fingerprint_engine.identity_confidence
54
- label = fingerprint_engine.identity_label
52
+ def identity_confidence(partner_identity: nil, **)
53
+ engine = engine_for(partner_identity)
54
+ confidence = engine.identity_confidence
55
+ label = engine.identity_label
55
56
  log.debug "[cognitive_fingerprint] confidence=#{confidence.round(4)} label=#{label}"
56
57
  { confidence: confidence, label: label }
57
58
  end
58
59
 
59
- def fingerprint_hash(**)
60
- { fingerprint_hash: fingerprint_engine.fingerprint_hash }
60
+ def fingerprint_hash(partner_identity: nil, **)
61
+ { fingerprint_hash: engine_for(partner_identity).fingerprint_hash }
61
62
  end
62
63
 
63
- def fingerprint_report(**)
64
- fingerprint_engine.fingerprint_report
64
+ def fingerprint_report(partner_identity: nil, **)
65
+ engine_for(partner_identity).fingerprint_report
65
66
  end
66
67
 
67
- def fingerprint_status(**)
68
+ def fingerprint_status(partner_identity: nil, **)
69
+ engine = engine_for(partner_identity)
68
70
  {
69
- trait_count: fingerprint_engine.trait_count,
70
- sample_count: fingerprint_engine.sample_count,
71
- label: fingerprint_engine.identity_label
71
+ trait_count: engine.trait_count,
72
+ sample_count: engine.sample_count,
73
+ label: engine.identity_label
72
74
  }
73
75
  end
74
76
 
77
+ def erase_partner!(identity:, **)
78
+ registry = fingerprint_registry
79
+ return { erased: false, identity: identity } unless registry.key?(identity)
80
+
81
+ registry.delete(identity)
82
+ log.info "[cognitive_fingerprint] erased partner identity=#{identity}"
83
+ { erased: true, identity: identity }
84
+ end
85
+
86
+ def tracked_partners
87
+ fingerprint_registry.keys.compact
88
+ end
89
+
75
90
  private
76
91
 
77
- def fingerprint_engine
78
- @fingerprint_engine ||= Helpers::FingerprintEngine.new
92
+ def engine_for(partner_identity)
93
+ fingerprint_registry[partner_identity] ||= Helpers::FingerprintEngine.new
94
+ end
95
+
96
+ def fingerprint_registry
97
+ @fingerprint_registry ||= {}
79
98
  end
80
99
  end
81
100
  end
@@ -18,6 +18,7 @@ module Legion
18
18
  @model = Dimensions.new_identity_model
19
19
  @observation_count = 0
20
20
  @entropy_history = []
21
+ @local_previously_available = false
21
22
  load_from_local
22
23
  end
23
24
 
@@ -87,7 +88,14 @@ module Legion
87
88
  end
88
89
 
89
90
  def save_to_local
90
- return unless local_available?
91
+ unless local_available?
92
+ if @local_previously_available
93
+ Legion::Logging.warn 'lex-identity: local persistence unavailable after being available'
94
+ return false
95
+ end
96
+
97
+ return nil
98
+ end
91
99
 
92
100
  db = local_data_connection
93
101
 
@@ -161,7 +169,9 @@ module Legion
161
169
  private
162
170
 
163
171
  def local_available?
164
- defined?(Legion::Data::Local) && respond_to?(:local_data_connected?) && local_data_connected?
172
+ available = defined?(Legion::Data::Local) && respond_to?(:local_data_connected?) && local_data_connected?
173
+ @local_previously_available = true if available
174
+ available
165
175
  end
166
176
  end
167
177
  end
@@ -42,12 +42,12 @@ module Legion
42
42
  false
43
43
  end
44
44
 
45
- def enhance(prompt, phase: 'reflection', **)
45
+ def enhance(prompt, phase: 'reflection')
46
46
  return nil unless available?
47
47
 
48
48
  if pipeline_available?
49
49
  response = Legion::LLM::Pipeline::GaiaCaller.chat(
50
- message: prompt, phase: phase, **
50
+ message: prompt, phase: phase
51
51
  )
52
52
  response&.message&.dig(:content)
53
53
  else
@@ -149,6 +149,8 @@ module Legion
149
149
  update_emotion_score(tick_results)
150
150
  update_memory_score(tick_results)
151
151
  update_load_score(tick_results)
152
+ update_trust_score(tick_results)
153
+ update_mode_score(tick_results)
152
154
  end
153
155
 
154
156
  def update_prediction_score(tick_results)
@@ -190,6 +192,22 @@ module Legion
190
192
  utilization = elapsed / budget
191
193
  reflection_store.update_category_score(:cognitive_load, [1.0 - utilization, 0.0].max)
192
194
  end
195
+
196
+ def update_trust_score(tick_results)
197
+ social = tick_results[:social_cognition]
198
+ return unless social.is_a?(Hash) && social[:trust_drift].is_a?(Numeric)
199
+
200
+ reflection_store.update_category_score(:trust_drift, 1.0 - social[:trust_drift].abs)
201
+ end
202
+
203
+ def update_mode_score(tick_results)
204
+ state = tick_results[:tick_state]
205
+ return unless state.is_a?(Hash) && state[:mode_transitions].is_a?(Numeric)
206
+
207
+ transitions = state[:mode_transitions].to_f
208
+ threshold = Helpers::Constants::MODE_OSCILLATION_THRESHOLD.to_f
209
+ reflection_store.update_category_score(:mode_patterns, [1.0 - (transitions / threshold), 0.0].max)
210
+ end
193
211
  end
194
212
  end
195
213
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Self
7
- VERSION = '0.1.13'
7
+ VERSION = '0.1.15'
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/self/fingerprint/client'
4
+
5
+ RSpec.describe 'CognitiveFingerprint per-partner identity scoping' do
6
+ let(:client) { Legion::Extensions::Agentic::Self::Fingerprint::Client.new }
7
+
8
+ def seed(partner, category, value, count = 10)
9
+ count.times { client.record_observation(category: category, value: value, partner_identity: partner) }
10
+ end
11
+
12
+ describe 'isolation between partners' do
13
+ it 'observations for partner X are not visible to partner Y' do
14
+ seed('alice', :accuracy, 0.9)
15
+ result = client.fingerprint_status(partner_identity: 'bob')
16
+ expect(result[:trait_count]).to eq(0)
17
+ end
18
+
19
+ it 'partner X traits are visible when queried for X' do
20
+ seed('alice', :accuracy, 0.9)
21
+ result = client.fingerprint_status(partner_identity: 'alice')
22
+ expect(result[:trait_count]).to eq(1)
23
+ end
24
+
25
+ it 'two partners maintain independent baselines' do
26
+ seed('alice', :accuracy, 0.9)
27
+ seed('bob', :accuracy, 0.1)
28
+ alice_profile = client.trait_profile(partner_identity: 'alice')[:profile]
29
+ bob_profile = client.trait_profile(partner_identity: 'bob')[:profile]
30
+ expect(alice_profile[:accuracy]).to be > 0.5
31
+ expect(bob_profile[:accuracy]).to be < 0.5
32
+ end
33
+
34
+ it 'verify_identity for X never sees Y data' do
35
+ seed('alice', :accuracy, 0.9, 20)
36
+ result = client.verify_identity(observations: [{ category: :accuracy, value: 0.9 }],
37
+ partner_identity: 'bob')
38
+ expect(result[:verdict]).to eq(:insufficient_data)
39
+ end
40
+
41
+ it 'anomaly_check for X never uses Y baseline' do
42
+ seed('alice', :accuracy, 0.9, 10)
43
+ result = client.anomaly_check(category: :accuracy, value: 0.9, partner_identity: 'bob')
44
+ expect(result[:reason]).to eq(:no_baseline)
45
+ end
46
+ end
47
+
48
+ describe '#erase_partner!' do
49
+ before { seed('alice', :accuracy, 0.9) }
50
+
51
+ it 'removes all data for the named partner' do
52
+ client.erase_partner!(identity: 'alice')
53
+ result = client.fingerprint_status(partner_identity: 'alice')
54
+ expect(result[:trait_count]).to eq(0)
55
+ end
56
+
57
+ it 'leaves other partners untouched' do
58
+ seed('bob', :accuracy, 0.5)
59
+ client.erase_partner!(identity: 'alice')
60
+ result = client.fingerprint_status(partner_identity: 'bob')
61
+ expect(result[:trait_count]).to eq(1)
62
+ end
63
+
64
+ it 'returns a success hash' do
65
+ result = client.erase_partner!(identity: 'alice')
66
+ expect(result[:erased]).to eq(true)
67
+ expect(result[:identity]).to eq('alice')
68
+ end
69
+
70
+ it 'is a no-op for unknown partner and returns erased: false' do
71
+ result = client.erase_partner!(identity: 'nobody')
72
+ expect(result[:erased]).to eq(false)
73
+ end
74
+ end
75
+
76
+ describe 'registry introspection' do
77
+ it 'returns list of tracked partner identities' do
78
+ seed('alice', :accuracy, 0.9)
79
+ seed('bob', :creativity, 0.5)
80
+ partners = client.tracked_partners
81
+ expect(partners).to include('alice')
82
+ expect(partners).to include('bob')
83
+ end
84
+ end
85
+ end
@@ -91,5 +91,13 @@ RSpec.describe Legion::Extensions::Agentic::Self::Identity::Helpers::Fingerprint
91
91
  expect(fp.save_to_local).to be_nil
92
92
  end
93
93
  end
94
+
95
+ it 'warns when local persistence was previously available but is no longer connected' do
96
+ fp.instance_variable_set(:@local_previously_available, true)
97
+ allow(fp).to receive(:local_data_connected?).and_return(false)
98
+
99
+ expect(Legion::Logging).to receive(:warn).with(/local persistence unavailable/)
100
+ expect(fp.save_to_local).to be false
101
+ end
94
102
  end
95
103
  end
@@ -75,6 +75,10 @@ RSpec.describe Legion::Extensions::Agentic::Self::Reflection::Helpers::LlmEnhanc
75
75
  allow(enhancer_mod).to receive(:available?).and_return(false)
76
76
  expect(enhancer_mod.enhance('hello')).to be_nil
77
77
  end
78
+
79
+ it 'rejects arbitrary extra kwargs instead of forwarding them to LLM' do
80
+ expect { enhancer_mod.enhance('hello', unexpected: true) }.to raise_error(ArgumentError)
81
+ end
78
82
  end
79
83
 
80
84
  describe '.available?' do
@@ -46,6 +46,23 @@ RSpec.describe Legion::Extensions::Agentic::Self::Reflection::Runners::Reflectio
46
46
  expect(result[:health]).to be < 1.0
47
47
  expect(result[:category_scores][:prediction_calibration]).to eq(0.2)
48
48
  end
49
+
50
+ it 'updates trust_drift from social cognition results' do
51
+ client.reflect(tick_results: { social_cognition: { trust_drift: 0.4 } })
52
+
53
+ result = client.cognitive_health
54
+
55
+ expect(result[:category_scores][:trust_drift]).to eq(0.6)
56
+ expect(result[:health]).to be < 1.0
57
+ end
58
+
59
+ it 'updates mode_patterns from tick mode transition counts' do
60
+ client.reflect(tick_results: { tick_state: { mode_transitions: 5 } })
61
+
62
+ result = client.cognitive_health
63
+
64
+ expect(result[:category_scores][:mode_patterns]).to eq(0.0)
65
+ end
49
66
  end
50
67
 
51
68
  describe '#recent_reflections' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-self
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -367,6 +367,7 @@ files:
367
367
  - spec/legion/extensions/agentic/self/fingerprint/helpers/cognitive_trait_spec.rb
368
368
  - spec/legion/extensions/agentic/self/fingerprint/helpers/constants_spec.rb
369
369
  - spec/legion/extensions/agentic/self/fingerprint/helpers/fingerprint_engine_spec.rb
370
+ - spec/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint_partner_spec.rb
370
371
  - spec/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint_spec.rb
371
372
  - spec/legion/extensions/agentic/self/identity/actors/orphan_check_spec.rb
372
373
  - spec/legion/extensions/agentic/self/identity/client_spec.rb