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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53c28592aa4721c2cc7901452451b9836cad8d3ded37f83c2a8dc04aac3ba1f8
4
- data.tar.gz: 29a423e61504f68cf11fd8c234f29b0d4ff654e33ea5651fd2d7739656cfb9ab
3
+ metadata.gz: 78fabc193208b0e2059a6f95600bd6c358abe763ee62d26da104e624d8e665ab
4
+ data.tar.gz: a606062b0f999c578120934bc4cd6486d5b4f6f80456f2b119557cf7afbd7ea0
5
5
  SHA512:
6
- metadata.gz: 38e5b0225a70847a4fb4a8d2f323f1bf1174004f16643deca0fca3567970f16fb5ee722e5e974051b42aff17c7dbb4ccdf7d1c403651c9d4228f3dec4433f7ca
7
- data.tar.gz: 3bc0d255fb156a9ce05ac926ecd6c5ca15c57a0729c57de9b679ebb684748143e004bbb209433056b628b4de78f7fa0437393d428d810069b69cd3f0fa787ff0
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
@@ -5,7 +5,7 @@ Domain consolidation gem for self-model, identity, metacognition, and self-aware
5
5
  ## Overview
6
6
 
7
7
  **Gem**: `lex-agentic-self`
8
- **Version**: 0.1.13
8
+ **Version**: 0.1.16
9
9
  **Namespace**: `Legion::Extensions::Agentic::Self`
10
10
 
11
11
  ## Sub-Modules
@@ -23,7 +23,7 @@ module Legion
23
23
  @load = 0.0
24
24
  @activation_count = 0
25
25
  @last_activated_at = nil
26
- @created_at = Time.now.utc
26
+ @created_at = Time.now.utc
27
27
  end
28
28
 
29
29
  def activate!
@@ -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
@@ -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
- model = if snapshot_store.stale?
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 = snapshot_store.latest || Helpers::SelfModel.build
63
- snapshot_store.store(model) if snapshot_store.stale?
64
+ model = introspect
64
65
 
65
66
  {
66
67
  identity: model[:identity],
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Self
7
- VERSION = '0.1.14'
7
+ VERSION = '0.1.16'
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
@@ -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) { db }
43
- define_singleton_method(:connected?) { true }
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(*); end
13
- def warn(*); end
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(worker_id:) # rubocop:disable Lint/UnusedMethodArgument
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.14
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