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 +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/legion/extensions/agentic/self/fingerprint/client.rb +0 -8
- data/lib/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint.rb +44 -25
- data/lib/legion/extensions/agentic/self/identity/helpers/fingerprint.rb +12 -2
- data/lib/legion/extensions/agentic/self/reflection/helpers/llm_enhancer.rb +2 -2
- data/lib/legion/extensions/agentic/self/reflection/runners/reflection.rb +18 -0
- data/lib/legion/extensions/agentic/self/version.rb +1 -1
- data/spec/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint_partner_spec.rb +85 -0
- data/spec/legion/extensions/agentic/self/identity/helpers/fingerprint_spec.rb +8 -0
- data/spec/legion/extensions/agentic/self/reflection/helpers/llm_enhancer_spec.rb +4 -0
- data/spec/legion/extensions/agentic/self/reflection/runners/reflection_spec.rb +17 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9353f26016962ab841ebc5b4e5d717d1ba7b94d5c6ccd9da3be8ed8c7fdeb938
|
|
4
|
+
data.tar.gz: 5544eb5d69d9394c6f6071acfc4ea54e0a651f700f0d41ede5ac2f9e85be33bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
54
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
70
|
-
sample_count:
|
|
71
|
-
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
|
|
78
|
-
|
|
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
|
-
|
|
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
|
data/spec/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint_partner_spec.rb
ADDED
|
@@ -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.
|
|
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
|