lex-agentic-inference 0.1.9 → 0.1.10

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: aab6451e969d15dec0c0611253cda68bf6395f73c94a107fb40bd6ac7906bfab
4
- data.tar.gz: 35f5f2e847b918d8f39418ea2db34143cb7541051b39a904194c15cb5b7ea8ef
3
+ metadata.gz: d8d433f84cb7914fe6bbed1b49f6c4e2376d0fc2616981415d8bdabcec2549a0
4
+ data.tar.gz: 7bfd8bbd8210dfbd3a249735d3f4985fa5af6a33b93f9ee5d45e204fd36ab9ee
5
5
  SHA512:
6
- metadata.gz: db120c178d16008756bb83b5c84548b27de13d60e628dcf0fbb96b3f4166ddab1e56a751241ae84876d68a6222eec2f4e863b9b51e27d09319aa1b645de4ed29
7
- data.tar.gz: 41221a494ac82877d7795c4e4a609d6f52efaecb68248ade74359da358b43ac5b881cfbf6ec0d4c82e4c66ece948faf3fd36e0f0c3ff3c01505b9f7093e8a3c2
6
+ metadata.gz: 441a6d38e9fa3bc1911ae8c80a312336ae1462a810b262d309fb4fa07a5c5e0c55c76e1de748300928ba1c62e90dbb0fbc048037b83e0c03e1a387500d246180
7
+ data.tar.gz: 573dd89cbee59b4ea980ce2acea0afd0cc1d306e244be959f38081e88cd51bf0514e478a4ab5820f1b5b2bc155d965a147857a9d9c014f0c2292492d3fd68cf7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.10] - 2026-07-16
4
+ ### Added
5
+ - Per-identity prediction store and rolling accuracy: `PredictionStore` is now keyed by `identity:`, isolating each partner's predictions and outcomes
6
+ - `PredictionStore#erase_partner!(identity:)` — removes all predictions and outcomes for the given identity, raising `ArgumentError` on mismatch
7
+ - `Runners::Prediction#erase_partner!(identity:)` — delegates to the per-identity store, enabling selective data erasure
8
+ - All runner methods (`predict`, `resolve_prediction`, `pending_predictions`, `prediction_accuracy`, `expire_stale_predictions`, `get_prediction`) accept `identity:` kwarg to route to the correct store
9
+
3
10
  ## [0.1.9] - 2026-05-07
4
11
  ### Fixed
5
12
  - Abductive reasoning decay now handles restored hypotheses with no `last_evaluated_at` timestamp instead of crashing the update cycle.
@@ -13,12 +13,14 @@ module Legion
13
13
  include Runners::Prediction
14
14
 
15
15
  def initialize(**)
16
- @prediction_store = Helpers::PredictionStore.new
16
+ @prediction_stores = {}
17
17
  end
18
18
 
19
19
  private
20
20
 
21
- attr_reader :prediction_store
21
+ def prediction_store(identity: nil)
22
+ @prediction_stores[identity] ||= Helpers::PredictionStore.new(identity: identity)
23
+ end
22
24
  end
23
25
  end
24
26
  end
@@ -9,9 +9,10 @@ module Legion
9
9
  module Prediction
10
10
  module Helpers
11
11
  class PredictionStore
12
- attr_reader :predictions, :outcomes
12
+ attr_reader :predictions, :outcomes, :identity
13
13
 
14
- def initialize
14
+ def initialize(identity: nil)
15
+ @identity = identity
15
16
  @predictions = {}
16
17
  @outcomes = []
17
18
  end
@@ -78,6 +79,16 @@ module Legion
78
79
  }
79
80
  end
80
81
  end
82
+
83
+ def erase_partner!(identity:)
84
+ raise ArgumentError, "identity mismatch: expected #{@identity.inspect}, got #{identity.inspect}" if @identity != identity
85
+
86
+ predictions_erased = @predictions.size
87
+ outcomes_erased = @outcomes.size
88
+ @predictions.clear
89
+ @outcomes.clear
90
+ { erased: true, identity: identity, predictions_erased: predictions_erased, outcomes_erased: outcomes_erased }
91
+ end
81
92
  end
82
93
  end
83
94
  end
@@ -12,9 +12,10 @@ module Legion
12
12
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
13
13
  Legion::Extensions::Helpers.const_defined?(:Lex, false)
14
14
 
15
- def predict(mode:, context: {}, confidence: nil, description: nil, **)
15
+ def predict(mode:, context: {}, confidence: nil, description: nil, identity: nil, **)
16
16
  return { error: :invalid_mode, valid_modes: Helpers::Modes::REASONING_MODES } unless Helpers::Modes.valid_mode?(mode)
17
17
 
18
+ store = prediction_store(identity: identity)
18
19
  prediction = {
19
20
  prediction_id: SecureRandom.uuid,
20
21
  mode: mode,
@@ -26,10 +27,10 @@ module Legion
26
27
  horizon: Helpers::Modes::PREDICTION_HORIZON
27
28
  }
28
29
 
29
- prediction_store.store(prediction)
30
+ store.store(prediction)
30
31
 
31
32
  actionable = prediction[:confidence] >= Helpers::Modes::PREDICTION_CONFIDENCE_MIN
32
- rolling_accuracy = prediction_store.accuracy
33
+ rolling_accuracy = store.accuracy
33
34
  log.debug "[prediction] new: mode=#{mode} confidence=#{prediction[:confidence].round(2)} " \
34
35
  "actionable=#{actionable} accuracy=#{rolling_accuracy.round(2)} id=#{prediction[:prediction_id][0..7]}"
35
36
 
@@ -40,12 +41,13 @@ module Legion
40
41
  actionable: actionable,
41
42
  rolling_accuracy: rolling_accuracy,
42
43
  error_rate: (1.0 - rolling_accuracy).round(4),
43
- resolved: prediction_store.recently_resolved
44
+ resolved: store.recently_resolved
44
45
  }
45
46
  end
46
47
 
47
- def resolve_prediction(prediction_id:, outcome:, actual: nil, **)
48
- pred = prediction_store.resolve(prediction_id, outcome: outcome, actual: actual)
48
+ def resolve_prediction(prediction_id:, outcome:, actual: nil, identity: nil, **)
49
+ store = prediction_store(identity: identity)
50
+ pred = store.resolve(prediction_id, outcome: outcome, actual: actual)
49
51
  if pred
50
52
  log.info "[prediction] resolved #{prediction_id[0..7]} outcome=#{outcome}"
51
53
  record_outcome_trace(pred, outcome)
@@ -56,47 +58,54 @@ module Legion
56
58
  end
57
59
  end
58
60
 
59
- def pending_predictions(**)
60
- preds = prediction_store.pending
61
+ def pending_predictions(identity: nil, **)
62
+ store = prediction_store(identity: identity)
63
+ preds = store.pending
61
64
  log.debug "[prediction] pending count=#{preds.size}"
62
65
  { predictions: preds, count: preds.size }
63
66
  end
64
67
 
65
- def prediction_accuracy(window: 100, **)
66
- acc = prediction_store.accuracy(window: window)
67
- total = prediction_store.outcomes.size
68
+ def prediction_accuracy(window: 100, identity: nil, **)
69
+ store = prediction_store(identity: identity)
70
+ acc = store.accuracy(window: window)
71
+ total = store.outcomes.size
68
72
  log.debug "[prediction] accuracy=#{acc.round(2)} total_outcomes=#{total}"
69
73
  { accuracy: acc, total_outcomes: total }
70
74
  end
71
75
 
72
- def expire_stale_predictions(**)
76
+ def expire_stale_predictions(identity: nil, **)
77
+ store = prediction_store(identity: identity)
73
78
  expired_count = 0
74
79
 
75
- prediction_store.pending.each do |pred|
80
+ store.pending.each do |pred|
76
81
  age = Time.now.utc - pred[:created_at]
77
82
  next unless age > pred[:horizon]
78
83
 
79
- prediction_store.resolve(pred[:prediction_id], outcome: :expired, actual: nil)
84
+ store.resolve(pred[:prediction_id], outcome: :expired, actual: nil)
80
85
  expired_count += 1
81
86
  end
82
87
 
83
- remaining = prediction_store.pending.size
88
+ remaining = store.pending.size
84
89
  log.debug "[prediction] expire sweep: expired=#{expired_count} remaining=#{remaining}"
85
90
 
86
91
  { expired_count: expired_count, remaining_pending: remaining }
87
92
  end
88
93
 
89
- def get_prediction(prediction_id:, **)
90
- pred = prediction_store.get(prediction_id)
94
+ def get_prediction(prediction_id:, identity: nil, **)
95
+ store = prediction_store(identity: identity)
96
+ pred = store.get(prediction_id)
91
97
  pred ? { found: true, prediction: pred } : { found: false }
92
98
  end
93
99
 
94
- private
95
-
96
- def prediction_store
97
- @prediction_store ||= Helpers::PredictionStore.new
100
+ def erase_partner!(identity:, **)
101
+ store = prediction_store(identity: identity)
102
+ result = store.erase_partner!(identity: identity)
103
+ log.info "[prediction] erased partner identity=#{identity}"
104
+ result
98
105
  end
99
106
 
107
+ private
108
+
100
109
  def estimate_confidence(mode, context)
101
110
  base = case mode
102
111
  when :fault_localization then 0.7
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Inference
7
- VERSION = '0.1.9'
7
+ VERSION = '0.1.10'
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Inference::Prediction::Helpers::PredictionStore do
6
+ let(:store_x) { described_class.new(identity: 'partner-X') }
7
+ let(:store_y) { described_class.new(identity: 'partner-Y') }
8
+
9
+ let(:basic_prediction) do
10
+ {
11
+ mode: :fault_localization,
12
+ confidence: 0.75,
13
+ description: 'disk latency spike incoming',
14
+ status: :pending
15
+ }
16
+ end
17
+
18
+ describe 'identity isolation — reads for X never see Y data' do
19
+ it 'stores predictions under the correct identity' do
20
+ id_x = store_x.store(basic_prediction.dup)
21
+ expect(store_x.get(id_x)).not_to be_nil
22
+ expect(store_y.get(id_x)).to be_nil
23
+ end
24
+
25
+ it 'X accuracy is independent of Y outcomes' do
26
+ 3.times do
27
+ id = store_x.store(basic_prediction.dup)
28
+ store_x.resolve(id, outcome: :correct)
29
+ end
30
+ 3.times do
31
+ id = store_y.store(basic_prediction.dup)
32
+ store_y.resolve(id, outcome: :incorrect)
33
+ end
34
+
35
+ expect(store_x.accuracy).to eq(1.0)
36
+ expect(store_y.accuracy).to eq(0.0)
37
+ end
38
+
39
+ it 'X pending predictions do not include Y predictions' do
40
+ store_x.store(basic_prediction.dup)
41
+ id_y = store_y.store(basic_prediction.dup)
42
+ store_y.resolve(id_y, outcome: :correct)
43
+
44
+ expect(store_x.pending.size).to eq(1)
45
+ expect(store_y.pending.size).to eq(0)
46
+ end
47
+
48
+ it 'X count is independent of Y count' do
49
+ 3.times { store_x.store(basic_prediction.dup) }
50
+ 5.times { store_y.store(basic_prediction.dup) }
51
+
52
+ expect(store_x.count).to eq(3)
53
+ expect(store_y.count).to eq(5)
54
+ end
55
+ end
56
+
57
+ describe '#identity' do
58
+ it 'exposes the identity it was initialized with' do
59
+ expect(store_x.identity).to eq('partner-X')
60
+ end
61
+ end
62
+
63
+ describe '#erase_partner!' do
64
+ it 'removes all predictions for the identity' do
65
+ 3.times { store_x.store(basic_prediction.dup) }
66
+ store_x.erase_partner!(identity: 'partner-X')
67
+ expect(store_x.count).to eq(0)
68
+ end
69
+
70
+ it 'removes all outcomes for the identity' do
71
+ id = store_x.store(basic_prediction.dup)
72
+ store_x.resolve(id, outcome: :correct)
73
+ store_x.erase_partner!(identity: 'partner-X')
74
+ expect(store_x.outcomes).to be_empty
75
+ end
76
+
77
+ it 'does not touch a different identity store' do
78
+ id_x = store_x.store(basic_prediction.dup)
79
+ store_x.resolve(id_x, outcome: :correct)
80
+
81
+ 3.times { store_y.store(basic_prediction.dup) }
82
+ store_x.erase_partner!(identity: 'partner-X')
83
+
84
+ expect(store_y.count).to eq(3)
85
+ end
86
+
87
+ it 'returns a confirmation hash' do
88
+ result = store_x.erase_partner!(identity: 'partner-X')
89
+ expect(result).to include(erased: true, identity: 'partner-X')
90
+ end
91
+
92
+ it 'raises ArgumentError when identity does not match own identity' do
93
+ expect { store_x.erase_partner!(identity: 'partner-Y') }.to raise_error(ArgumentError)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/inference/prediction/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Inference::Prediction::Runners::Prediction do
6
+ let(:client) { Legion::Extensions::Agentic::Inference::Prediction::Client.new }
7
+
8
+ describe 'per-identity isolation' do
9
+ it 'predictions for identity X are not visible to identity Y' do
10
+ result_x = client.predict(mode: :fault_localization, identity: 'alice')
11
+ result_y = client.get_prediction(prediction_id: result_x[:prediction_id], identity: 'bob')
12
+ expect(result_y[:found]).to be false
13
+ end
14
+
15
+ it 'accuracy for X is independent of Y outcomes' do
16
+ 3.times do
17
+ pred = client.predict(mode: :fault_localization, identity: 'alice')
18
+ client.resolve_prediction(prediction_id: pred[:prediction_id], outcome: :correct, identity: 'alice')
19
+ end
20
+ 3.times do
21
+ pred = client.predict(mode: :fault_localization, identity: 'bob')
22
+ client.resolve_prediction(prediction_id: pred[:prediction_id], outcome: :incorrect, identity: 'bob')
23
+ end
24
+
25
+ acc_alice = client.prediction_accuracy(identity: 'alice')
26
+ acc_bob = client.prediction_accuracy(identity: 'bob')
27
+
28
+ expect(acc_alice[:accuracy]).to eq(1.0)
29
+ expect(acc_bob[:accuracy]).to eq(0.0)
30
+ end
31
+
32
+ it 'pending count for X does not include Y predictions' do
33
+ client.predict(mode: :fault_localization, identity: 'alice')
34
+ pred_bob = client.predict(mode: :fault_localization, identity: 'bob')
35
+ client.resolve_prediction(prediction_id: pred_bob[:prediction_id], outcome: :correct, identity: 'bob')
36
+
37
+ alice_pending = client.pending_predictions(identity: 'alice')
38
+ bob_pending = client.pending_predictions(identity: 'bob')
39
+
40
+ expect(alice_pending[:count]).to eq(1)
41
+ expect(bob_pending[:count]).to eq(0)
42
+ end
43
+ end
44
+
45
+ describe '#erase_partner!' do
46
+ it 'removes all predictions and outcomes for the identity' do
47
+ 3.times do
48
+ pred = client.predict(mode: :fault_localization, identity: 'alice')
49
+ client.resolve_prediction(prediction_id: pred[:prediction_id], outcome: :correct, identity: 'alice')
50
+ end
51
+
52
+ result = client.erase_partner!(identity: 'alice')
53
+ expect(result[:erased]).to be true
54
+ expect(result[:identity]).to eq('alice')
55
+
56
+ expect(client.pending_predictions(identity: 'alice')[:count]).to eq(0)
57
+ expect(client.prediction_accuracy(identity: 'alice')[:total_outcomes]).to eq(0)
58
+ end
59
+
60
+ it 'does not affect a different identity after erase' do
61
+ 3.times { client.predict(mode: :fault_localization, identity: 'bob') }
62
+ client.erase_partner!(identity: 'alice')
63
+
64
+ expect(client.pending_predictions(identity: 'bob')[:count]).to eq(3)
65
+ end
66
+ end
67
+
68
+ describe '#client responds to erase_partner!' do
69
+ it 'responds to erase_partner!' do
70
+ expect(client).to respond_to(:erase_partner!)
71
+ end
72
+ end
73
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-inference
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -483,7 +483,9 @@ files:
483
483
  - spec/legion/extensions/agentic/inference/prediction/actors/expire_predictions_spec.rb
484
484
  - spec/legion/extensions/agentic/inference/prediction/client_spec.rb
485
485
  - spec/legion/extensions/agentic/inference/prediction/helpers/modes_spec.rb
486
+ - spec/legion/extensions/agentic/inference/prediction/helpers/prediction_store_identity_spec.rb
486
487
  - spec/legion/extensions/agentic/inference/prediction/helpers/prediction_store_spec.rb
488
+ - spec/legion/extensions/agentic/inference/prediction/runners/prediction_identity_spec.rb
487
489
  - spec/legion/extensions/agentic/inference/prediction/runners/prediction_spec.rb
488
490
  - spec/legion/extensions/agentic/inference/predictive_coding/client_spec.rb
489
491
  - spec/legion/extensions/agentic/inference/predictive_coding/helpers/generative_model_spec.rb