lex-agentic-self 0.1.14 → 0.1.16
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 +11 -0
- data/README.md +1 -1
- data/lib/legion/extensions/agentic/self/architecture/helpers/subsystem.rb +1 -1
- 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/metacognition/runners/metacognition.rb +4 -3
- 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/local_persistence_spec.rb +2 -2
- data/spec/legion/extensions/agentic/self/identity/runners/entra_spec.rb +3 -3
- data/spec/legion/extensions/agentic/self/metacognition/runners/metacognition_spec.rb +31 -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: 78fabc193208b0e2059a6f95600bd6c358abe763ee62d26da104e624d8e665ab
|
|
4
|
+
data.tar.gz: a606062b0f999c578120934bc4cd6486d5b4f6f80456f2b119557cf7afbd7ea0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ada4ab413f48e62b67e0ed9913db7d88b4756af102d7bc121b6b9d26905158c2d1d3dcfacfe12e914d560583e2832076ac8c178d0977344bc0b9a245a8894ae
|
|
7
|
+
data.tar.gz: 9695b4662b313bbce2dc7d73ca9f1b6dd12e96a18525cf1cd4dc87dada063a5fbd87e8c93fc07202f35bfe7c178b2f4501b6a98309e626ce1abf25b4183da187
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.16] - 2026-10-29
|
|
4
|
+
### Fixed
|
|
5
|
+
- Refresh metacognitive snapshots immediately when new tick or subsystem observations arrive instead of hiding them behind the cache TTL.
|
|
6
|
+
- Refresh expired architecture overviews through normal introspection so newly loaded extensions appear in architecture change history.
|
|
7
|
+
|
|
8
|
+
## [0.1.15] - 2026-07-16
|
|
9
|
+
### Added
|
|
10
|
+
- 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.
|
|
11
|
+
- `erase_partner!(identity:)` removes the fingerprint model for a given partner identity; returns `{erased: true/false, identity:}`.
|
|
12
|
+
- `tracked_partners` returns the list of partner identities currently held in the registry.
|
|
13
|
+
|
|
3
14
|
## [0.1.14] - 2026-05-07
|
|
4
15
|
### Fixed
|
|
5
16
|
- Identity fingerprint save now warns and returns false if local persistence disconnects after previously being available.
|
data/README.md
CHANGED
|
@@ -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
|
|
@@ -11,7 +11,9 @@ module Legion
|
|
|
11
11
|
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def introspect(tick_results: {}, subsystem_states: {}, **)
|
|
14
|
-
|
|
14
|
+
observed = (tick_results.is_a?(Hash) && !tick_results.empty?) ||
|
|
15
|
+
(subsystem_states.is_a?(Hash) && !subsystem_states.empty?)
|
|
16
|
+
model = if snapshot_store.stale? || observed
|
|
15
17
|
fresh = Helpers::SelfModel.build(
|
|
16
18
|
subsystem_states: subsystem_states,
|
|
17
19
|
tick_results: tick_results
|
|
@@ -59,8 +61,7 @@ module Legion
|
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def architecture_overview(**)
|
|
62
|
-
model =
|
|
63
|
-
snapshot_store.store(model) if snapshot_store.stale?
|
|
64
|
+
model = introspect
|
|
64
65
|
|
|
65
66
|
{
|
|
66
67
|
identity: model[:identity],
|
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
|
|
@@ -39,8 +39,8 @@ RSpec.describe Legion::Extensions::Agentic::Self::Identity::Helpers::Fingerprint
|
|
|
39
39
|
|
|
40
40
|
def stub_local(db)
|
|
41
41
|
local_mod = Module.new do
|
|
42
|
-
define_singleton_method(:connection)
|
|
43
|
-
define_singleton_method(:connected?)
|
|
42
|
+
define_singleton_method(:connection) { db }
|
|
43
|
+
define_singleton_method(:connected?) { true }
|
|
44
44
|
end
|
|
45
45
|
stub_const('Legion::Data', Module.new)
|
|
46
46
|
stub_const('Legion::Data::Local', local_mod)
|
|
@@ -9,8 +9,8 @@ unless defined?(Legion::Logging)
|
|
|
9
9
|
module_function
|
|
10
10
|
|
|
11
11
|
def debug(*); end
|
|
12
|
-
def info(*);
|
|
13
|
-
def warn(*);
|
|
12
|
+
def info(*); end
|
|
13
|
+
def warn(*); end
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -245,7 +245,7 @@ RSpec.describe Legion::Extensions::Agentic::Self::Identity::Runners::Entra do
|
|
|
245
245
|
describe '#refresh_access_token' do
|
|
246
246
|
let(:vault_secrets_mod) do
|
|
247
247
|
Module.new do
|
|
248
|
-
def self.read_client_secret(
|
|
248
|
+
def self.read_client_secret(**)
|
|
249
249
|
{ client_id: 'app-id', client_secret: 'secret-val' }
|
|
250
250
|
end
|
|
251
251
|
end
|
|
@@ -41,6 +41,24 @@ RSpec.describe Legion::Extensions::Agentic::Self::Metacognition::Runners::Metaco
|
|
|
41
41
|
expect(model.dig(:cognitive, :health)).to eq(0.95)
|
|
42
42
|
expect(store.size).to eq(2)
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
it 'uses newly observed health and subsystem state before the cache expires' do
|
|
46
|
+
client.introspect(tick_results: tick_results, subsystem_states: { memory: { status: :healthy } })
|
|
47
|
+
changed = tick_results.merge(post_tick_reflection: { cognitive_health: 0.35 })
|
|
48
|
+
|
|
49
|
+
model = client.introspect(tick_results: changed, subsystem_states: { memory: { status: :degraded } })
|
|
50
|
+
|
|
51
|
+
expect(model.dig(:cognitive, :health)).to eq(0.35)
|
|
52
|
+
expect(client.explain_subsystem(subsystem: :memory)[:state]).to eq(status: :degraded)
|
|
53
|
+
expect(client.health_trend[:trend].map { |entry| entry[:health] }).to eq([0.88, 0.35])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'accepts new subsystem observations without requiring a new tick' do
|
|
57
|
+
client.introspect(subsystem_states: { memory: { status: :healthy } })
|
|
58
|
+
client.introspect(subsystem_states: { memory: { status: :degraded } })
|
|
59
|
+
|
|
60
|
+
expect(client.explain_subsystem(subsystem: :memory)[:state]).to eq(status: :degraded)
|
|
61
|
+
end
|
|
44
62
|
end
|
|
45
63
|
|
|
46
64
|
describe '#self_narrative' do
|
|
@@ -74,6 +92,19 @@ RSpec.describe Legion::Extensions::Agentic::Self::Metacognition::Runners::Metaco
|
|
|
74
92
|
expect(result[:identity][:framework]).to eq('LegionIO')
|
|
75
93
|
expect(result).to have_key(:capabilities)
|
|
76
94
|
end
|
|
95
|
+
|
|
96
|
+
it 'rediscovers loaded extensions after the cached architecture expires' do
|
|
97
|
+
hide_const('Legion::Extensions::Curiosity') if Legion::Extensions.const_defined?(:Curiosity, false)
|
|
98
|
+
old = client.introspect
|
|
99
|
+
old[:assembled_at] = Time.now.utc - 60
|
|
100
|
+
stub_const('Legion::Extensions::Curiosity', Module.new)
|
|
101
|
+
|
|
102
|
+
overview = client.architecture_overview
|
|
103
|
+
|
|
104
|
+
expect(overview[:architecture][:loaded]).to include(:Curiosity)
|
|
105
|
+
expect(overview[:assembled_at]).to be > old[:assembled_at]
|
|
106
|
+
expect(client.architecture_changes[:changes].last[:added]).to include(:Curiosity)
|
|
107
|
+
end
|
|
77
108
|
end
|
|
78
109
|
|
|
79
110
|
describe '#health_trend' 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.16
|
|
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
|