lex-agentic-attention 0.1.7 → 0.1.8

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: 103c62f8f413c7ee40d8b3e7708d2db29a3c1ad17a69a015d68b6e361c60d575
4
- data.tar.gz: 864c880d5bbae74a42649bb7fc80c9889c61b47541ce578e26cbed88799b2158
3
+ metadata.gz: 9e6357375a251617aae1b7b3c415374cf2ccaf930f0cec48e0b0adc2db4310e5
4
+ data.tar.gz: fb2bf32e027a564c17d0914a3496a65d3d8c844205cd6c09326ea24783f6dd20
5
5
  SHA512:
6
- metadata.gz: 1f98d1299efda24bed59ee8a45df612d9a548e1ea8e6132bf64bd204b1fae7081c3594b6f448150a9aa9e2956d8781ba1ca0a262441fc4283052bfee3a50ae54
7
- data.tar.gz: b47bb341c84ac9a3c9996b1d8ffd7aba6244379a390235e2f917aef259ff0e670b6b9fbd8c5e6b14f0896c2ff5f1fbed327b3e23fe5df91851c79d4ee7eefd31
6
+ metadata.gz: 93b6915bcd364e1c83b1ab28e032c1d557d8c297a5efe148658fe9a804d0512b87ac5a1a227ca3b2fb01c2bfa2adfbcc014ad93e701e4ee60cd030ff8268f35b
7
+ data.tar.gz: 7ba33757d9c2ae8f2a16a777dbc7c6738d56aa3793906e3fbba7643428f916a43ee9b96cac8cca9f632283b8c0dc298218a6366c2ffea745564e8cf65e265071
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.8] - 2026-10-29
4
+ ### Fixed
5
+ - Preserve principal, channel, and frame identity when deduplicating attention signals.
6
+ - Return retained spotlight and peripheral signals in priority order, including batches below spotlight capacity.
7
+
3
8
  ## [0.1.7] - 2026-04-22
4
9
  ### Fixed
5
10
  - Renamed Economy `attention_status` to `attention_economy_status` to resolve method collision with Focus
data/README.md CHANGED
@@ -5,7 +5,7 @@ Domain consolidation gem for attention, perception, and signal processing. Bundl
5
5
  ## Overview
6
6
 
7
7
  **Gem**: `lex-agentic-attention`
8
- **Version**: 0.1.7
8
+ **Version**: 0.1.8
9
9
  **Namespace**: `Legion::Extensions::Agentic::Attention`
10
10
 
11
11
  ## Sub-Modules
@@ -8,7 +8,7 @@ module Legion
8
8
  module Attention
9
9
  module Arousal
10
10
  module Actor
11
- class Update < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
11
+ class Update < Legion::Extensions::Actors::Every
12
12
  def runner_class
13
13
  Legion::Extensions::Agentic::Attention::Arousal::Runners::Arousal
14
14
  end
@@ -8,7 +8,7 @@ module Legion
8
8
  module Attention
9
9
  module Blink
10
10
  module Actor
11
- class Decay < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
11
+ class Decay < Legion::Extensions::Actors::Every
12
12
  def runner_class
13
13
  Legion::Extensions::Agentic::Attention::Blink::Runners::AttentionalBlink
14
14
  end
@@ -8,7 +8,7 @@ module Legion
8
8
  module Attention
9
9
  module FeatureBinding
10
10
  module Actor
11
- class Decay < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
11
+ class Decay < Legion::Extensions::Actors::Every
12
12
  def runner_class
13
13
  Legion::Extensions::Agentic::Attention::FeatureBinding::Runners::FeatureBinding
14
14
  end
@@ -67,7 +67,8 @@ module Legion
67
67
  def signal_identity(signal)
68
68
  return signal.object_id unless signal.is_a?(Hash)
69
69
 
70
- [signal[:source_type], signal[:domain], signal[:value]].hash
70
+ [signal[:source_type], signal[:domain], signal[:principal_id],
71
+ signal[:channel_id], signal[:frame_id], signal[:value]]
71
72
  end
72
73
  end
73
74
  end
@@ -103,12 +103,14 @@ module Legion
103
103
  def enforce_capacity(categorized)
104
104
  capacity = Helpers::Constants::ATTENTIONAL_CAPACITY
105
105
  spotlight = categorized[:spotlight].sort_by { |s| -(s[:attention_score] || 0) }
106
+ categorized[:spotlight] = spotlight
106
107
 
107
108
  if spotlight.size > capacity
108
109
  overflow = spotlight[capacity..]
109
110
  categorized[:spotlight] = spotlight.first(capacity)
110
111
  categorized[:peripheral].concat(overflow)
111
112
  end
113
+ categorized[:peripheral].sort_by! { |s| -(s[:attention_score] || 0) }
112
114
 
113
115
  categorized
114
116
  end
@@ -14,11 +14,11 @@ module Legion
14
14
  MAX_LENSES = 8
15
15
 
16
16
  CLARITY_LABELS = [
17
- [0.0..0.2, :opaque],
18
- [0.2..0.4, :foggy],
19
- [0.4..0.6, :hazy],
20
- [0.6..0.8, :clear],
21
- [0.8..1.0, :crystal]
17
+ [0.0..0.2, :opaque],
18
+ [0.2..0.4, :foggy],
19
+ [0.4..0.6, :hazy],
20
+ [0.6..0.8, :clear],
21
+ [0.8..1.0, :crystal]
22
22
  ].freeze
23
23
 
24
24
  MAGNIFICATION_LABELS = [
@@ -48,13 +48,12 @@ module Legion
48
48
  raise ArgumentError, "stack not found: #{stack_id}" unless stack
49
49
 
50
50
  result = stack.view_through(content)
51
- # rubocop:disable Legion/HelperMigration/DirectLogging
51
+ # rubocop:disable-next Legion/HelperMigration/DirectLogging
52
52
  Legion::Logging.debug("[cognitive_lens] view_through stack=#{stack_id} " \
53
53
  "perceived=#{result[:perceived].round(4)} " \
54
54
  "magnification=#{result[:combined_magnification].round(2)} " \
55
55
  "distortion=#{result[:combined_distortion].round(2)} " \
56
56
  "clarity=#{result[:stack_clarity].round(2)}")
57
- # rubocop:enable Legion/HelperMigration/DirectLogging
58
57
  result
59
58
  end
60
59
 
@@ -8,7 +8,7 @@ module Legion
8
8
  module Attention
9
9
  module Regulation
10
10
  module Actor
11
- class Update < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
11
+ class Update < Legion::Extensions::Actors::Every
12
12
  def runner_class
13
13
  Legion::Extensions::Agentic::Attention::Regulation::Runners::AttentionRegulation
14
14
  end
@@ -8,7 +8,7 @@ module Legion
8
8
  module Attention
9
9
  module Salience
10
10
  module Actor
11
- class Compute < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
11
+ class Compute < Legion::Extensions::Actors::Every
12
12
  def runner_class
13
13
  Legion::Extensions::Agentic::Attention::Salience::Runners::Salience
14
14
  end
@@ -8,7 +8,7 @@ module Legion
8
8
  module Attention
9
9
  module Schema
10
10
  module Actor
11
- class Decay < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
11
+ class Decay < Legion::Extensions::Actors::Every
12
12
  def runner_class
13
13
  Legion::Extensions::Agentic::Attention::Schema::Runners::AttentionSchema
14
14
  end
@@ -8,7 +8,7 @@ module Legion
8
8
  module Attention
9
9
  module SignalDetection
10
10
  module Actor
11
- class Update < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
11
+ class Update < Legion::Extensions::Actors::Every
12
12
  def runner_class
13
13
  Legion::Extensions::Agentic::Attention::SignalDetection::Runners::SignalDetection
14
14
  end
@@ -32,10 +32,9 @@ module Legion
32
32
  @mappings[mapping.id] = mapping
33
33
  prune_mappings! if @mappings.size > MAX_MAPPINGS
34
34
 
35
- # rubocop:disable Legion/HelperMigration/DirectLogging
35
+ # rubocop:disable-next Legion/HelperMigration/DirectLogging
36
36
  Legion::Logging.debug("[cognitive_synesthesia] mapping registered id=#{mapping.id[0..7]} " \
37
37
  "#{source_modality}->#{target_modality} strength=#{strength.round(2)}")
38
- # rubocop:enable Legion/HelperMigration/DirectLogging
39
38
 
40
39
  { success: true, mapping_id: mapping.id, source_modality: source_modality,
41
40
  target_modality: target_modality }
@@ -137,10 +136,9 @@ module Legion
137
136
  @events << event
138
137
  @events.shift while @events.size > MAX_EVENTS
139
138
 
140
- # rubocop:disable Legion/HelperMigration/DirectLogging
139
+ # rubocop:disable-next Legion/HelperMigration/DirectLogging
141
140
  Legion::Logging.debug("[cognitive_synesthesia] event fired id=#{event.id[0..7]} " \
142
141
  "mapping=#{mapping.id[0..7]} intensity=#{intensity.round(2)}")
143
- # rubocop:enable Legion/HelperMigration/DirectLogging
144
142
  event
145
143
  end
146
144
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Attention
7
- VERSION = '0.1.7'
7
+ VERSION = '0.1.8'
8
8
  end
9
9
  end
10
10
  end
@@ -26,35 +26,48 @@ RSpec.describe Legion::Extensions::Agentic::Attention::Focus::Runners::Attention
26
26
  expect(result[:filtered].size).to be >= 1
27
27
  end
28
28
 
29
- it 'drops low-salience signals' do
30
- signals = [{ salience: 0.01, domain: :heartbeat, novelty: 0.0 }]
29
+ it 'deduplicates identical signals' do
30
+ signals = [
31
+ { salience: 0.8, domain: :terraform, source_type: :event, value: 'deploy' },
32
+ { salience: 0.8, domain: :terraform, source_type: :event, value: 'deploy' }
33
+ ]
31
34
  result = client.filter_signals(signals: signals)
32
- expect(result[:dropped]).to be >= 0
33
- expect(result[:filtered].size).to be <= 1
35
+ expect(result[:spotlight] + result[:peripheral] + result[:background]).to be <= 1
34
36
  end
35
37
 
36
- it 'boosts signals matching active wonders' do
37
- signals = [{ salience: 0.3, domain: :terraform, novelty: 0.3 }]
38
- wonders = [{ domain: :terraform, salience: 0.9 }]
38
+ it 'preserves identical requests from different principals and frames' do
39
+ request = { salience: 0.9, source_type: :human_direct, value: 'continue', channel_id: :cli }
40
+ signals = [
41
+ request.merge(principal_id: 'alice', frame_id: 'first'),
42
+ request.merge(principal_id: 'bob', frame_id: 'second'),
43
+ request.merge(principal_id: 'alice', frame_id: 'third')
44
+ ]
39
45
 
40
- without = client.filter_signals(signals: signals)
41
- # New client to avoid habituation carryover
42
- fresh = Legion::Extensions::Agentic::Attention::Focus::Client.new
43
- with_wonders = fresh.filter_signals(signals: signals, active_wonders: wonders)
46
+ result = client.filter_signals(signals: signals + [signals.first.dup])
44
47
 
45
- # With wonder boost, the signal should score higher
46
- with_total = with_wonders[:spotlight] + with_wonders[:peripheral]
47
- without_total = without[:spotlight] + without[:peripheral]
48
- expect(with_total).to be >= without_total
48
+ expect(result[:filtered].map { |signal| signal[:frame_id] }).to contain_exactly('first', 'second', 'third')
49
49
  end
50
50
 
51
- it 'deduplicates identical signals' do
51
+ it 'keeps requests from separate channels distinct when no frame identity is available' do
52
+ request = { salience: 0.9, source_type: :human_direct, value: 'continue' }
53
+ result = client.filter_signals(signals: [request.merge(channel_id: :cli), request.merge(channel_id: :slack)])
54
+
55
+ expect(result[:filtered].map { |signal| signal[:channel_id] }).to contain_exactly(:cli, :slack)
56
+ end
57
+
58
+ it 'prioritizes signals within both retained tiers below the capacity limit' do
52
59
  signals = [
53
- { salience: 0.8, domain: :terraform, source_type: :event, value: 'deploy' },
54
- { salience: 0.8, domain: :terraform, source_type: :event, value: 'deploy' }
60
+ { value: 'spotlight-low', salience: 0.8, novelty: 0.8, domain: :deploy },
61
+ { value: 'spotlight-high', salience: 1.0, novelty: 1.0, domain: :incident },
62
+ { value: 'peripheral-low', salience: 0.7, novelty: 0.5, domain: :status },
63
+ { value: 'peripheral-high', salience: 0.9, novelty: 0.5, domain: :review }
55
64
  ]
56
- result = client.filter_signals(signals: signals)
57
- expect(result[:spotlight] + result[:peripheral] + result[:background]).to be <= 1
65
+ wonders = [{ domain: :deploy, salience: 1.0 }, { domain: :incident, salience: 1.0 }]
66
+
67
+ result = client.filter_signals(signals: signals, active_wonders: wonders)
68
+
69
+ expect(result[:filtered].map { |signal| signal[:value] })
70
+ .to eq(%w[spotlight-high spotlight-low peripheral-high peripheral-low])
58
71
  end
59
72
 
60
73
  it 'enforces attentional capacity' do
@@ -62,22 +75,6 @@ RSpec.describe Legion::Extensions::Agentic::Attention::Focus::Runners::Attention
62
75
  result = client.filter_signals(signals: signals)
63
76
  expect(result[:spotlight]).to be <= Legion::Extensions::Agentic::Attention::Focus::Helpers::Constants::ATTENTIONAL_CAPACITY
64
77
  end
65
-
66
- it 'habituates repeated domain signals' do
67
- first_client = Legion::Extensions::Agentic::Attention::Focus::Client.new
68
- signals = [{ salience: 0.6, domain: :terraform, novelty: 0.3 }]
69
-
70
- first_result = first_client.filter_signals(signals: signals)
71
- # Record same domain multiple times to build habituation
72
- 10.times { first_client.filter_signals(signals: signals) }
73
- later_result = first_client.filter_signals(signals: signals)
74
-
75
- first_scores = first_result[:filtered].map { |s| s[:attention_score] || 0 }
76
- later_scores = later_result[:filtered].map { |s| s[:attention_score] || 0 }
77
-
78
- # Later scores should be lower due to habituation (if signal still passes)
79
- expect(later_scores.max).to be <= first_scores.max if first_scores.any? && later_scores.any?
80
- end
81
78
  end
82
79
 
83
80
  describe '#attention_status' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-attention
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity