lex-agentic-executive 0.1.6 → 0.1.7

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: 162085450c8aaeacfd84ced17da35c78b086d34d1ee9e4a0305a46784ac8d3ea
4
+ data.tar.gz: 8a8f9a4f5ffca47bbdcecd40fdc3b4616e3fc24bf0777458e0547a12317ad7df
5
5
  SHA512:
6
- metadata.gz: ca2eae6c6c98e44e3252b7e24781ae041de08bc42b1a2e567c7300accdcaac3ec3718b8411487dd991cc36135bc52506c2e9485ae808ddabda76066f40b3467b
7
- data.tar.gz: '0084babdf8779e23ad27e51c802101b103250aec6879e9da20d98f6874da850553df3ae72afdaefa53ddf2136ed807628a9dd1c44b3f625d2645e814aab4f7c1'
6
+ metadata.gz: f06abb9d450194b60de1e128952885bdb712ada8d8dd3cffa2a1744590c86ee91130b2c6ddb950cd7d04dfeaf5af943d781cd1061fcceddfdb9f5ef399fbc3d7
7
+ data.tar.gz: fb613003c03d2f7a571731433f2f1b08bd45645ea6ac22d70637ed2f03567487c34c0e0d3b1f3bfc7d8c465785567195d2446d08ae22c53ca82ce023a6dd3f4c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.7] - 2026-03-31
4
+
5
+ ### Added
6
+ - Proactive outreach evaluation in Volition `form_intentions`
7
+ - 4 trigger types: insight, check_in, milestone, curiosity
8
+ - Attachment style gating (avoidant suppresses proactive)
9
+ - Priority-based trigger selection
10
+
3
11
  ## [0.1.6] - 2026-03-26
4
12
 
5
13
  ### 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.7'
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
@@ -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
@@ -133,6 +133,107 @@ RSpec.describe Legion::Extensions::Agentic::Executive::Volition::Runners::Voliti
133
133
  end
134
134
  end
135
135
 
136
+ describe '#form_intentions with proactive evaluation' do
137
+ let(:bond_state) do
138
+ {
139
+ partner_bond: {
140
+ stage: :established,
141
+ strength: 0.72,
142
+ style: :secure,
143
+ health: 0.85,
144
+ milestones_today: [],
145
+ narrative: nil
146
+ }
147
+ }
148
+ end
149
+
150
+ context 'when bond_state has partner bond' do
151
+ it 'includes proactive_outreach in result' do
152
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state)
153
+ expect(result).to have_key(:proactive_outreach)
154
+ end
155
+
156
+ it 'returns nil proactive when no triggers fire' do
157
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state)
158
+ expect(result[:proactive_outreach]).to be_nil
159
+ end
160
+ end
161
+
162
+ context 'when dream reflection has an insight' do
163
+ let(:tick_results) do
164
+ { dream_reflection: { insight: 'Discovered an interesting pattern in partner interaction frequency that suggests weekly review cycles.' } }
165
+ end
166
+
167
+ it 'generates insight trigger' do
168
+ result = client.form_intentions(tick_results: tick_results, bond_state: bond_state)
169
+ outreach = result[:proactive_outreach]
170
+ expect(outreach).not_to be_nil
171
+ expect(outreach[:trigger][:reason]).to eq(:insight)
172
+ end
173
+ end
174
+
175
+ context 'when partner absence exceeds pattern' do
176
+ let(:bond_state_absent) do
177
+ bond_state.merge(partner_bond: bond_state[:partner_bond].merge(absence_exceeds_pattern: true))
178
+ end
179
+
180
+ it 'generates check_in trigger' do
181
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state_absent)
182
+ outreach = result[:proactive_outreach]
183
+ expect(outreach).not_to be_nil
184
+ expect(outreach[:trigger][:reason]).to eq(:check_in)
185
+ end
186
+ end
187
+
188
+ context 'when milestones exist today' do
189
+ let(:bond_state_milestone) do
190
+ ms = { description: 'Bond stage reached established', type: :stage_transition }
191
+ bond_state.merge(partner_bond: bond_state[:partner_bond].merge(milestones_today: [ms]))
192
+ end
193
+
194
+ it 'generates milestone trigger' do
195
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state_milestone)
196
+ outreach = result[:proactive_outreach]
197
+ expect(outreach).not_to be_nil
198
+ expect(outreach[:trigger][:reason]).to eq(:milestone)
199
+ end
200
+ end
201
+
202
+ context 'when agenda has partner items' do
203
+ let(:tick_results) do
204
+ { agenda_formation: { agenda: [{ domain: :partner, question: 'How is the project going?' }] } }
205
+ end
206
+
207
+ it 'generates curiosity trigger' do
208
+ result = client.form_intentions(tick_results: tick_results, bond_state: bond_state)
209
+ outreach = result[:proactive_outreach]
210
+ expect(outreach).not_to be_nil
211
+ expect(outreach[:trigger][:reason]).to eq(:curiosity)
212
+ end
213
+ end
214
+
215
+ context 'when bond_state is empty' do
216
+ it 'returns nil proactive' do
217
+ result = client.form_intentions(tick_results: {}, bond_state: {})
218
+ expect(result[:proactive_outreach]).to be_nil
219
+ end
220
+ end
221
+
222
+ context 'when attachment style is avoidant' do
223
+ let(:bond_state_avoidant) do
224
+ bond_state.merge(partner_bond: bond_state[:partner_bond].merge(
225
+ style: :avoidant,
226
+ absence_exceeds_pattern: true
227
+ ))
228
+ end
229
+
230
+ it 'suppresses proactive outreach' do
231
+ result = client.form_intentions(tick_results: {}, bond_state: bond_state_avoidant)
232
+ expect(result[:proactive_outreach]).to be_nil
233
+ end
234
+ end
235
+ end
236
+
136
237
  describe '#form_absorption_intention' do
137
238
  it 'returns success false when no domains provided' do
138
239
  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.7
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