lex-agentic-executive 0.1.6 → 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: 63e7291af930919f6bba5e3491216cd3f08952dd0ee433bee20b644b7114006b
4
- data.tar.gz: 64074c431b24fa82c65ad0c097b0eb1e88263ac17c3397647c72a93784937ee0
3
+ metadata.gz: f8b462cfe7996e5d0e29c0be80280aa7e2d41cb839a2f9abe9add558d6fc0d0f
4
+ data.tar.gz: a0c5d73d86245d74b6705193916306da3b6db2af5d5a31898377138c1c16ca3d
5
5
  SHA512:
6
- metadata.gz: ca2eae6c6c98e44e3252b7e24781ae041de08bc42b1a2e567c7300accdcaac3ec3718b8411487dd991cc36135bc52506c2e9485ae808ddabda76066f40b3467b
7
- data.tar.gz: '0084babdf8779e23ad27e51c802101b103250aec6879e9da20d98f6874da850553df3ae72afdaefa53ddf2136ed807628a9dd1c44b3f625d2645e814aab4f7c1'
6
+ metadata.gz: 60b7246e2235da77e1e63ba488566c346870605d40291a2e52bd15b93740722eeda4b73a5c70d3b5f266327a0cc0d25d601e50a749f44a00ccea48581d115568
7
+ data.tar.gz: f5ee84e895c7e651d286754cfadf7656ae18da2ae4e76a31405b71239d168b018c541fc6bc93ae32df82559f76ddc310504bb7a9033e3a28e657cb9f8dbfff47
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.8] - 2026-04-03
4
+
5
+ ### Changed
6
+ - Fix drive synthesizer default values to avoid fabricating urgency, epistemic, and social drive without evidence
7
+ - Return 0.0 (not 0.5) for arousal and trust when no signal is present
8
+ - Short-circuit epistemic and social drive to 0.0 when prediction and mesh/trust state are empty
9
+ - Lower calm gut signal from 0.1 to 0.05 and unknown signals from 0.3 to 0.0
10
+
11
+ ## [0.1.7] - 2026-03-31
12
+
13
+ ### Added
14
+ - Proactive outreach evaluation in Volition `form_intentions`
15
+ - 4 trigger types: insight, check_in, milestone, curiosity
16
+ - Attachment style gating (avoidant suppresses proactive)
17
+ - Priority-based trigger selection
18
+
3
19
  ## [0.1.6] - 2026-03-26
4
20
 
5
21
  ### Changed
data/Gemfile CHANGED
@@ -3,3 +3,5 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
+
7
+ gem 'rubocop-legion'
@@ -33,6 +33,6 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency 'legion-transport', '>= 1.3.9'
34
34
 
35
35
  spec.add_development_dependency 'rspec', '~> 3.13'
36
- spec.add_development_dependency 'rubocop', '~> 1.60'
37
- spec.add_development_dependency 'rubocop-rspec', '~> 2.26'
36
+ spec.add_development_dependency 'rubocop'
37
+ spec.add_development_dependency 'rubocop-rspec'
38
38
  end
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Executive
7
- VERSION = '0.1.6'
7
+ VERSION = '0.1.8'
8
8
  end
9
9
  end
10
10
  end
@@ -42,6 +42,15 @@ module Legion
42
42
 
43
43
  # Intention states
44
44
  STATES = %i[active suspended completed expired].freeze
45
+
46
+ # Proactive outreach triggers
47
+ PROACTIVE_TRIGGERS = %i[insight check_in milestone curiosity].freeze
48
+
49
+ # Priority ordering for trigger selection (lower = higher priority)
50
+ PRIORITY_ORDER = { critical: 0, urgent: 1, normal: 2, low: 3, ambient: 4 }.freeze
51
+
52
+ # Intent type for proactive outreach
53
+ PROACTIVE_INTENT_TYPE = :proactive_outreach
45
54
  end
46
55
  end
47
56
  end
@@ -74,7 +74,7 @@ module Legion
74
74
  gut = tick_results[:gut_instinct] || cognitive_state[:gut] || {}
75
75
  emotion = tick_results[:emotional_evaluation] || {}
76
76
 
77
- arousal = emotion[:arousal] || cognitive_state.dig(:emotion, :arousal) || 0.5
77
+ arousal = emotion[:arousal] || cognitive_state.dig(:emotion, :arousal) || 0.0
78
78
  gut_signal = extract_gut_strength(gut)
79
79
 
80
80
  ((arousal * 0.5) + (gut_signal * 0.5)).clamp(0.0, 1.0)
@@ -84,7 +84,9 @@ module Legion
84
84
  pred = tick_results[:prediction_engine] || {}
85
85
  pred_state = cognitive_state[:prediction] || {}
86
86
 
87
- confidence = pred[:confidence] || pred_state[:confidence] || 0.5
87
+ return 0.0 if pred.empty? && pred_state.empty?
88
+
89
+ confidence = pred[:confidence] || pred_state[:confidence] || 1.0
88
90
  pending = pred_state[:pending_count] || 0
89
91
 
90
92
  confidence_gap = 1.0 - confidence
@@ -96,8 +98,10 @@ module Legion
96
98
  mesh = cognitive_state[:mesh] || {}
97
99
  trust = cognitive_state[:trust] || {}
98
100
 
101
+ return 0.0 if mesh.empty? && trust.empty?
102
+
99
103
  peer_count = mesh[:peer_count] || 0
100
- trust_level = trust[:avg_composite] || 0.5
104
+ trust_level = trust[:avg_composite] || 0.0
101
105
 
102
106
  peer_factor = [peer_count / 5.0, 1.0].min
103
107
  ((peer_factor * 0.4) + (trust_level * 0.6)).clamp(0.0, 1.0)
@@ -105,15 +109,15 @@ module Legion
105
109
 
106
110
  def extract_gut_strength(gut)
107
111
  signal = gut[:signal]
108
- return 0.3 unless signal
112
+ return 0.0 unless signal
109
113
 
110
114
  case signal
111
115
  when :alarm then 1.0
112
116
  when :heightened then 0.7
113
117
  when :explore then 0.5
114
118
  when :attend then 0.4
115
- when :calm then 0.1
116
- else 0.3
119
+ when :calm then 0.05
120
+ else 0.0
117
121
  end
118
122
  end
119
123
 
@@ -10,7 +10,7 @@ module Legion
10
10
  include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
11
  Legion::Extensions::Helpers.const_defined?(:Lex)
12
12
 
13
- def form_intentions(tick_results: {}, cognitive_state: {}, **)
13
+ def form_intentions(tick_results: {}, cognitive_state: {}, bond_state: {}, **)
14
14
  drives = Helpers::DriveSynthesizer.synthesize(
15
15
  tick_results: tick_results,
16
16
  cognitive_state: cognitive_state
@@ -26,17 +26,19 @@ module Legion
26
26
  expired = intention_stack.decay_all
27
27
  dominant = Helpers::DriveSynthesizer.dominant_drive(drives)
28
28
  current = intention_stack.top
29
+ proactive = evaluate_proactive_outreach(tick_results, bond_state)
29
30
 
30
- Legion::Logging.debug "[volition] drives=#{format_drives(drives)} pushed=#{pushed} expired=#{expired} " \
31
- "active=#{intention_stack.active_count} top=#{current&.dig(:goal)}"
31
+ log.debug "[volition] drives=#{format_drives(drives)} pushed=#{pushed} expired=#{expired} " \
32
+ "active=#{intention_stack.active_count} top=#{current&.dig(:goal)}"
32
33
 
33
34
  {
34
- drives: drives,
35
- dominant_drive: dominant,
36
- new_intentions: pushed,
37
- expired: expired,
38
- active_intentions: intention_stack.active_count,
39
- current_intention: format_intention(current)
35
+ drives: drives,
36
+ dominant_drive: dominant,
37
+ new_intentions: pushed,
38
+ expired: expired,
39
+ active_intentions: intention_stack.active_count,
40
+ current_intention: format_intention(current),
41
+ proactive_outreach: proactive
40
42
  }
41
43
  end
42
44
 
@@ -55,19 +57,19 @@ module Legion
55
57
 
56
58
  def complete_intention(intention_id:, **)
57
59
  result = intention_stack.complete(intention_id)
58
- Legion::Logging.info "[volition] complete intention=#{intention_id} result=#{result}"
60
+ log.info "[volition] complete intention=#{intention_id} result=#{result}"
59
61
  { status: result, intention_id: intention_id }
60
62
  end
61
63
 
62
64
  def suspend_intention(intention_id:, **)
63
65
  result = intention_stack.suspend(intention_id)
64
- Legion::Logging.info "[volition] suspend intention=#{intention_id} result=#{result}"
66
+ log.info "[volition] suspend intention=#{intention_id} result=#{result}"
65
67
  { status: result, intention_id: intention_id }
66
68
  end
67
69
 
68
70
  def resume_intention(intention_id:, **)
69
71
  result = intention_stack.resume(intention_id)
70
- Legion::Logging.info "[volition] resume intention=#{intention_id} result=#{result}"
72
+ log.info "[volition] resume intention=#{intention_id} result=#{result}"
71
73
  { status: result, intention_id: intention_id }
72
74
  end
73
75
 
@@ -95,8 +97,8 @@ module Legion
95
97
  )
96
98
 
97
99
  result = intention_stack.push(intention)
98
- Legion::Logging.info "[volition] absorption intention formed: domains=#{domains.join(',')} " \
99
- "neighbors=#{neighbors.size} salience=#{salience.round(2)} result=#{result}"
100
+ log.info "[volition] absorption intention formed: domains=#{domains.join(',')} " \
101
+ "neighbors=#{neighbors.size} salience=#{salience.round(2)} result=#{result}"
100
102
 
101
103
  {
102
104
  success: %i[pushed duplicate].include?(result),
@@ -152,6 +154,40 @@ module Legion
152
154
  def format_drives(drives)
153
155
  drives.map { |k, v| "#{k}=#{v.round(2)}" }.join(' ')
154
156
  end
157
+
158
+ def evaluate_proactive_outreach(tick_results, bond_state)
159
+ return nil unless bond_state.is_a?(Hash) && bond_state[:partner_bond]
160
+
161
+ partner = bond_state[:partner_bond]
162
+ return nil if partner[:style] == :avoidant
163
+
164
+ triggers = collect_proactive_triggers(tick_results, partner)
165
+ return nil if triggers.empty?
166
+
167
+ best = triggers.min_by { |t| Helpers::Constants::PRIORITY_ORDER.fetch(t[:priority], 99) }
168
+ { type: Helpers::Constants::PROACTIVE_INTENT_TYPE, trigger: best, all_triggers: triggers }
169
+ end
170
+
171
+ def collect_proactive_triggers(tick_results, partner)
172
+ triggers = []
173
+
174
+ insight = tick_results.dig(:dream_reflection, :insight)
175
+ triggers << { reason: :insight, content: insight, priority: :low } if insight.is_a?(String) && insight.length > 50
176
+
177
+ triggers << { reason: :check_in, content: nil, priority: :normal } if partner[:absence_exceeds_pattern]
178
+
179
+ (partner[:milestones_today] || []).each do |ms|
180
+ desc = ms.is_a?(Hash) ? (ms[:description] || ms['description']) : ms.to_s
181
+ triggers << { reason: :milestone, content: desc, priority: :low }
182
+ end
183
+
184
+ agenda = tick_results.dig(:agenda_formation, :agenda) || []
185
+ agenda.select { |a| a[:domain] == :partner }.each do |item|
186
+ triggers << { reason: :curiosity, content: item[:question], priority: :low }
187
+ end
188
+
189
+ triggers
190
+ end
155
191
  end
156
192
  end
157
193
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Executive::Volition::Helpers::Constants do
6
+ describe 'proactive constants' do
7
+ it 'defines PROACTIVE_TRIGGERS' do
8
+ expect(described_class::PROACTIVE_TRIGGERS).to include(:insight, :check_in, :milestone, :curiosity)
9
+ end
10
+
11
+ it 'defines PRIORITY_ORDER' do
12
+ expect(described_class::PRIORITY_ORDER).to be_a(Hash)
13
+ expect(described_class::PRIORITY_ORDER[:normal]).to be < described_class::PRIORITY_ORDER[:low]
14
+ end
15
+
16
+ it 'defines PROACTIVE_INTENT_TYPE' do
17
+ expect(described_class::PROACTIVE_INTENT_TYPE).to eq(:proactive_outreach)
18
+ end
19
+ end
20
+ end
@@ -20,6 +20,14 @@ RSpec.describe Legion::Extensions::Agentic::Executive::Volition::Helpers::DriveS
20
20
  )
21
21
  drives.each_value { |v| expect(v).to be_between(0.0, 1.0) }
22
22
  end
23
+
24
+ it 'does not fabricate urgency, epistemic, or social drive without evidence' do
25
+ drives = synth.synthesize(tick_results: {}, cognitive_state: {})
26
+
27
+ expect(drives[:urgency]).to eq(0.0)
28
+ expect(drives[:epistemic]).to eq(0.0)
29
+ expect(drives[:social]).to eq(0.0)
30
+ end
23
31
  end
24
32
 
25
33
  describe '.compute_curiosity_drive' do
@@ -91,9 +99,9 @@ RSpec.describe Legion::Extensions::Agentic::Executive::Volition::Helpers::DriveS
91
99
  it 'maps gut signal symbols to numeric strength' do
92
100
  expect(synth.extract_gut_strength({ signal: :alarm })).to eq(1.0)
93
101
  expect(synth.extract_gut_strength({ signal: :heightened })).to eq(0.7)
94
- expect(synth.extract_gut_strength({ signal: :calm })).to eq(0.1)
95
- expect(synth.extract_gut_strength({ signal: :neutral })).to eq(0.3)
96
- expect(synth.extract_gut_strength({})).to eq(0.3)
102
+ expect(synth.extract_gut_strength({ signal: :calm })).to eq(0.05)
103
+ expect(synth.extract_gut_strength({ signal: :neutral })).to eq(0.0)
104
+ expect(synth.extract_gut_strength({})).to eq(0.0)
97
105
  end
98
106
  end
99
107
  end
@@ -27,7 +27,8 @@ RSpec.describe Legion::Extensions::Agentic::Executive::Volition::Runners::Voliti
27
27
  it 'handles empty inputs' do
28
28
  result = client.form_intentions(tick_results: {}, cognitive_state: {})
29
29
  expect(result[:drives]).to be_a(Hash)
30
- expect(result[:active_intentions]).to be >= 0
30
+ expect(result[:active_intentions]).to eq(0)
31
+ expect(result[:current_intention]).to be_nil
31
32
  end
32
33
 
33
34
  it 'decays existing intentions over multiple ticks' do
@@ -133,6 +134,107 @@ RSpec.describe Legion::Extensions::Agentic::Executive::Volition::Runners::Voliti
133
134
  end
134
135
  end
135
136
 
137
+ describe '#form_intentions with proactive evaluation' do
138
+ let(:bond_state) do
139
+ {
140
+ partner_bond: {
141
+ stage: :established,
142
+ strength: 0.72,
143
+ style: :secure,
144
+ health: 0.85,
145
+ milestones_today: [],
146
+ narrative: nil
147
+ }
148
+ }
149
+ end
150
+
151
+ context 'when bond_state has partner bond' do
152
+ it 'includes proactive_outreach in result' do
153
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state)
154
+ expect(result).to have_key(:proactive_outreach)
155
+ end
156
+
157
+ it 'returns nil proactive when no triggers fire' do
158
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state)
159
+ expect(result[:proactive_outreach]).to be_nil
160
+ end
161
+ end
162
+
163
+ context 'when dream reflection has an insight' do
164
+ let(:tick_results) do
165
+ { dream_reflection: { insight: 'Discovered an interesting pattern in partner interaction frequency that suggests weekly review cycles.' } }
166
+ end
167
+
168
+ it 'generates insight trigger' do
169
+ result = client.form_intentions(tick_results: tick_results, bond_state: bond_state)
170
+ outreach = result[:proactive_outreach]
171
+ expect(outreach).not_to be_nil
172
+ expect(outreach[:trigger][:reason]).to eq(:insight)
173
+ end
174
+ end
175
+
176
+ context 'when partner absence exceeds pattern' do
177
+ let(:bond_state_absent) do
178
+ bond_state.merge(partner_bond: bond_state[:partner_bond].merge(absence_exceeds_pattern: true))
179
+ end
180
+
181
+ it 'generates check_in trigger' do
182
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state_absent)
183
+ outreach = result[:proactive_outreach]
184
+ expect(outreach).not_to be_nil
185
+ expect(outreach[:trigger][:reason]).to eq(:check_in)
186
+ end
187
+ end
188
+
189
+ context 'when milestones exist today' do
190
+ let(:bond_state_milestone) do
191
+ ms = { description: 'Bond stage reached established', type: :stage_transition }
192
+ bond_state.merge(partner_bond: bond_state[:partner_bond].merge(milestones_today: [ms]))
193
+ end
194
+
195
+ it 'generates milestone trigger' do
196
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state_milestone)
197
+ outreach = result[:proactive_outreach]
198
+ expect(outreach).not_to be_nil
199
+ expect(outreach[:trigger][:reason]).to eq(:milestone)
200
+ end
201
+ end
202
+
203
+ context 'when agenda has partner items' do
204
+ let(:tick_results) do
205
+ { agenda_formation: { agenda: [{ domain: :partner, question: 'How is the project going?' }] } }
206
+ end
207
+
208
+ it 'generates curiosity trigger' do
209
+ result = client.form_intentions(tick_results: tick_results, bond_state: bond_state)
210
+ outreach = result[:proactive_outreach]
211
+ expect(outreach).not_to be_nil
212
+ expect(outreach[:trigger][:reason]).to eq(:curiosity)
213
+ end
214
+ end
215
+
216
+ context 'when bond_state is empty' do
217
+ it 'returns nil proactive' do
218
+ result = client.form_intentions(tick_results: {}, bond_state: {})
219
+ expect(result[:proactive_outreach]).to be_nil
220
+ end
221
+ end
222
+
223
+ context 'when attachment style is avoidant' do
224
+ let(:bond_state_avoidant) do
225
+ bond_state.merge(partner_bond: bond_state[:partner_bond].merge(
226
+ style: :avoidant,
227
+ absence_exceeds_pattern: true
228
+ ))
229
+ end
230
+
231
+ it 'suppresses proactive outreach' do
232
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state_avoidant)
233
+ expect(result[:proactive_outreach]).to be_nil
234
+ end
235
+ end
236
+ end
237
+
136
238
  describe '#form_absorption_intention' do
137
239
  it 'returns success false when no domains provided' do
138
240
  result = client.form_absorption_intention(domains_at_risk: [])
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-executive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -125,30 +125,30 @@ dependencies:
125
125
  name: rubocop
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
- - - "~>"
128
+ - - ">="
129
129
  - !ruby/object:Gem::Version
130
- version: '1.60'
130
+ version: '0'
131
131
  type: :development
132
132
  prerelease: false
133
133
  version_requirements: !ruby/object:Gem::Requirement
134
134
  requirements:
135
- - - "~>"
135
+ - - ">="
136
136
  - !ruby/object:Gem::Version
137
- version: '1.60'
137
+ version: '0'
138
138
  - !ruby/object:Gem::Dependency
139
139
  name: rubocop-rspec
140
140
  requirement: !ruby/object:Gem::Requirement
141
141
  requirements:
142
- - - "~>"
142
+ - - ">="
143
143
  - !ruby/object:Gem::Version
144
- version: '2.26'
144
+ version: '0'
145
145
  type: :development
146
146
  prerelease: false
147
147
  version_requirements: !ruby/object:Gem::Requirement
148
148
  requirements:
149
- - - "~>"
149
+ - - ">="
150
150
  - !ruby/object:Gem::Version
151
- version: '2.26'
151
+ version: '0'
152
152
  description: 'LEX agentic executive domain: planning, working memory, cognitive control'
153
153
  email:
154
154
  - matthewdiverson@gmail.com
@@ -440,6 +440,7 @@ files:
440
440
  - spec/legion/extensions/agentic/executive/triage/helpers/triage_engine_spec.rb
441
441
  - spec/legion/extensions/agentic/executive/triage/runners_spec.rb
442
442
  - spec/legion/extensions/agentic/executive/volition/client_spec.rb
443
+ - spec/legion/extensions/agentic/executive/volition/helpers/constants_spec.rb
443
444
  - spec/legion/extensions/agentic/executive/volition/helpers/drive_synthesizer_spec.rb
444
445
  - spec/legion/extensions/agentic/executive/volition/helpers/intention_spec.rb
445
446
  - spec/legion/extensions/agentic/executive/volition/helpers/intention_stack_spec.rb