lex-agentic-affect 0.1.8 → 0.1.9

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: 304db292215662c9a10acb7c604d279ff263a747ffa410d4306020a03f2d1d69
4
- data.tar.gz: dc50e3bcecccdd41ce18855d375576cbd52747634a8d93bd161e00ffb9c3622e
3
+ metadata.gz: a5768d527e95204ef16c40ce69b64e6fea209640636223b405804e4dfccc6cf3
4
+ data.tar.gz: 13e3b630d0c1434e716d85cf149949ac2a9848005bb97bba80733ffa61ab74f0
5
5
  SHA512:
6
- metadata.gz: f4f56f2da0be9254ca01595c8d891e8b1f8a846ccc16fa8df485c818fd2ba37fb5e888e5391085c5539fdf10f342c601963ff6a5d37e22749d986e2f16a89401
7
- data.tar.gz: f8d3e0b229ba3b01749c1a5e5773ffe64ef3f5ac3cb67bdfea1046a2fd7ce984554d4d1af6c4dee729d25b67c14b00e5912678e7245b03c00d6b5212371a255b
6
+ metadata.gz: 0e967fd97104da2eea612ac0373635c249925263855cf1966fbb7c4d93291dc9f8896f7305554bda4700c888296fb96f48c2c41015356df9f0f489746faf0d3a
7
+ data.tar.gz: 749e3642b5b7418f519fe660be924ba97db8ccc213bdf185373bf6ee90408f1aebec2832accc6c38a7b5fb9a1d651f8cc8ceb322448b5d8ac80a41bcae7f93b0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.9] - 2026-03-31
4
+
5
+ ### Fixed
6
+ - Reappraisal: `ReappraisalEngine.mechanical_appraisal` replaces the meaningless `"auto-reappraised via #{strategy}"` placeholder with strategy- and valence-bracket-specific text drawn from `MECHANICAL_REAPPRAISALS` (7 strategies × 3 valence brackets: `highly_negative`, `negative`, `neutral`)
7
+ - Reappraisal: `auto_reappraise` (engine) and `auto_reappraise_event` (runner) now use `mechanical_appraisal` as the LLM-unavailable fallback
8
+ - Flow spec: replaced non-asserting `consecutive_flow_ticks >= 0` assertion with meaningful checks — after 50 balanced ticks the state must be `:flow`, `deep_flow` must be `true`, and `consecutive_flow_ticks` must exceed `DEEP_FLOW_THRESHOLD` (20)
9
+
3
10
  ## [0.1.8] - 2026-03-31
4
11
 
5
12
  ### Added
@@ -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
@@ -9,6 +9,44 @@ module Legion
9
9
  class ReappraisalEngine
10
10
  include Constants
11
11
 
12
+ MECHANICAL_REAPPRAISALS = {
13
+ reinterpretation: {
14
+ negative: 'This situation may have aspects that are not immediately apparent. Consider alternative explanations.',
15
+ highly_negative: 'Strong negative reactions often signal something important. Examine what underlying need is unmet.',
16
+ neutral: 'A balanced perspective reveals both challenges and opportunities in this situation.'
17
+ },
18
+ distancing: {
19
+ negative: 'Viewed from a broader timeline, this event occupies a small portion of overall experience.',
20
+ highly_negative: 'In the larger context of ongoing goals and relationships, this moment will pass.',
21
+ neutral: 'Stepping back reveals this is one event among many.'
22
+ },
23
+ benefit_finding: {
24
+ negative: 'Difficult experiences often contain lessons that become apparent with reflection.',
25
+ highly_negative: 'Even significant setbacks can reveal strengths and areas for growth.',
26
+ neutral: 'There may be unexpected value in examining this experience closely.'
27
+ },
28
+ acceptance: {
29
+ negative: 'Acknowledging this experience as it is, without resistance, creates space for response.',
30
+ highly_negative: 'Some experiences cannot be changed, only accepted and integrated.',
31
+ neutral: 'This experience is acknowledged and integrated without judgment.'
32
+ },
33
+ normalizing: {
34
+ negative: 'Many others have faced similar situations. This reaction is a common and understandable response.',
35
+ highly_negative: 'Intense responses to difficult events are a natural part of experience, not a sign of weakness.',
36
+ neutral: 'This situation falls within the normal range of experience.'
37
+ },
38
+ perspective_taking: {
39
+ negative: 'Considering this from another vantage point reveals aspects that were not initially visible.',
40
+ highly_negative: 'Seeing through a different lens can transform how a significant event is understood.',
41
+ neutral: 'Multiple perspectives offer a fuller picture of what is happening.'
42
+ },
43
+ temporal_distancing: {
44
+ negative: 'Looking back from the future, this moment is likely to appear smaller and more manageable.',
45
+ highly_negative: 'Even the most difficult moments recede with time. This too will become part of a larger story.',
46
+ neutral: 'In the fullness of time, the significance of this event will become clearer.'
47
+ }
48
+ }.freeze
49
+
12
50
  attr_reader :events, :reappraisal_log
13
51
 
14
52
  def initialize
@@ -65,7 +103,7 @@ module Legion
65
103
  return { success: false, reason: :event_not_found } unless event
66
104
 
67
105
  strategy = select_strategy(event)
68
- new_appraisal = "auto-reappraised via #{strategy}"
106
+ new_appraisal = self.class.mechanical_appraisal(strategy, event.current_valence)
69
107
  reappraise(event_id: event_id, strategy: strategy, new_appraisal: new_appraisal)
70
108
  end
71
109
 
@@ -131,6 +169,25 @@ module Legion
131
169
  }
132
170
  end
133
171
 
172
+ def self.valence_bracket(valence)
173
+ if valence < -0.6
174
+ :highly_negative
175
+ elsif valence < 0.0
176
+ :negative
177
+ else
178
+ :neutral
179
+ end
180
+ end
181
+
182
+ def self.mechanical_appraisal(strategy, valence)
183
+ bracket = valence_bracket(valence)
184
+ strategy = strategy.to_sym
185
+ brackets = MECHANICAL_REAPPRAISALS[strategy]
186
+ return "Reappraised via #{strategy}" unless brackets
187
+
188
+ brackets[bracket] || brackets.values.first || "Reappraised via #{strategy}"
189
+ end
190
+
134
191
  private
135
192
 
136
193
  def select_strategy(event)
@@ -52,7 +52,8 @@ module Legion
52
52
  return { success: false, reason: :event_not_found } unless event
53
53
 
54
54
  strategy = select_strategy_for(event)
55
- appraisal = llm_appraisal_for(event, strategy) || "auto-reappraised via #{strategy}"
55
+ appraisal = llm_appraisal_for(event, strategy) ||
56
+ Helpers::ReappraisalEngine.mechanical_appraisal(strategy, event.current_valence)
56
57
  result = eng.reappraise(event_id: event_id, strategy: strategy, new_appraisal: appraisal)
57
58
 
58
59
  if result[:success]
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Affect
7
- VERSION = '0.1.8'
7
+ VERSION = '0.1.9'
8
8
  end
9
9
  end
10
10
  end
@@ -215,8 +215,12 @@ RSpec.describe Legion::Extensions::Agentic::Affect::Flow::Runners::Flow do
215
215
  it 'reaches deep flow after threshold' do
216
216
  50.times { client.update_flow(tick_results: balanced_tick) }
217
217
  status = client.flow_status
218
- # May or may not reach deep flow depending on exact EMA convergence
219
- expect(status[:consecutive_flow_ticks]).to be >= 0
218
+ # After 50 balanced ticks the agent is in :flow (confirmed by the prior example).
219
+ # DEEP_FLOW_THRESHOLD is 20 consecutive flow ticks, so consecutive_flow_ticks must
220
+ # exceed that threshold, and deep_flow? must be true.
221
+ expect(status[:in_flow]).to be true
222
+ expect(status[:consecutive_flow_ticks]).to be > Legion::Extensions::Agentic::Affect::Flow::Helpers::Constants::DEEP_FLOW_THRESHOLD
223
+ expect(status[:deep_flow]).to be true
220
224
  end
221
225
  end
222
226
  end
@@ -208,4 +208,107 @@ RSpec.describe Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::Reappr
208
208
  :overall_regulation_ability, :strategy_effectiveness)
209
209
  end
210
210
  end
211
+
212
+ describe '.mechanical_appraisal' do
213
+ subject(:appraisal) { described_class.mechanical_appraisal(strategy, valence) }
214
+
215
+ context 'with a known strategy and highly negative valence' do
216
+ let(:strategy) { :reinterpretation }
217
+ let(:valence) { -0.8 }
218
+
219
+ it 'returns meaningful text (not a template stub)' do
220
+ expect(appraisal).not_to match(/auto-reappraised via/)
221
+ expect(appraisal).not_to match(/Reappraised via/)
222
+ expect(appraisal.length).to be > 20
223
+ end
224
+
225
+ it 'returns the highly_negative bracket text' do
226
+ expect(appraisal).to eq(
227
+ described_class::MECHANICAL_REAPPRAISALS[:reinterpretation][:highly_negative]
228
+ )
229
+ end
230
+ end
231
+
232
+ context 'with a known strategy and mildly negative valence' do
233
+ let(:strategy) { :distancing }
234
+ let(:valence) { -0.3 }
235
+
236
+ it 'returns the negative bracket text' do
237
+ expect(appraisal).to eq(
238
+ described_class::MECHANICAL_REAPPRAISALS[:distancing][:negative]
239
+ )
240
+ end
241
+
242
+ it 'does not return a template stub' do
243
+ expect(appraisal).not_to match(/auto-reappraised via/)
244
+ end
245
+ end
246
+
247
+ context 'with a known strategy and neutral/positive valence' do
248
+ let(:strategy) { :benefit_finding }
249
+ let(:valence) { 0.1 }
250
+
251
+ it 'returns the neutral bracket text' do
252
+ expect(appraisal).to eq(
253
+ described_class::MECHANICAL_REAPPRAISALS[:benefit_finding][:neutral]
254
+ )
255
+ end
256
+ end
257
+
258
+ context 'with normalizing strategy' do
259
+ let(:strategy) { :normalizing }
260
+ let(:valence) { -0.4 }
261
+
262
+ it 'returns meaningful normalizing text' do
263
+ expect(appraisal).to include('common')
264
+ end
265
+ end
266
+
267
+ context 'with perspective_taking strategy' do
268
+ let(:strategy) { :perspective_taking }
269
+ let(:valence) { -0.7 }
270
+
271
+ it 'returns meaningful perspective text' do
272
+ expect(appraisal).not_to match(/auto-reappraised via/)
273
+ expect(appraisal.length).to be > 20
274
+ end
275
+ end
276
+
277
+ context 'with temporal_distancing strategy' do
278
+ let(:strategy) { :temporal_distancing }
279
+ let(:valence) { -0.7 }
280
+
281
+ it 'returns meaningful temporal text' do
282
+ expect(appraisal).not_to match(/auto-reappraised via/)
283
+ expect(appraisal).to include('time')
284
+ end
285
+ end
286
+
287
+ context 'with an unknown strategy' do
288
+ let(:strategy) { :unknown_strategy }
289
+ let(:valence) { -0.5 }
290
+
291
+ it 'falls back to "Reappraised via <strategy>"' do
292
+ expect(appraisal).to eq('Reappraised via unknown_strategy')
293
+ end
294
+ end
295
+ end
296
+
297
+ describe '#auto_reappraise mechanical path' do
298
+ it 'produces meaningful appraisal text when called' do
299
+ event_id = engine.register_event(content: 'failure', valence: -0.7, intensity: 0.4, appraisal: 'terrible').id
300
+ engine.auto_reappraise(event_id: event_id)
301
+ appraisal = engine.events[event_id].appraisal
302
+ expect(appraisal).not_to match(/auto-reappraised via/)
303
+ expect(appraisal.length).to be > 20
304
+ end
305
+
306
+ it 'produces highly_negative bracket text for very negative events' do
307
+ event_id = engine.register_event(content: 'crisis', valence: -0.9, intensity: 0.9, appraisal: 'worst').id
308
+ engine.auto_reappraise(event_id: event_id)
309
+ # intense + negative → :distancing, valence -0.9 → :highly_negative
310
+ expected = described_class.mechanical_appraisal(:distancing, -0.9)
311
+ expect(engine.events[event_id].appraisal).to eq(expected)
312
+ end
313
+ end
211
314
  end
@@ -137,10 +137,16 @@ RSpec.describe Legion::Extensions::Agentic::Affect::Reappraisal::Runners::Cognit
137
137
  allow(enhancer).to receive(:available?).and_return(false)
138
138
  end
139
139
 
140
- it 'falls back to mechanical appraisal stub' do
140
+ it 'falls back to a meaningful mechanical appraisal (not a template stub)' do
141
141
  result = client.auto_reappraise_event(event_id: registered[:event_id], engine: engine)
142
+ appraisal = engine.events[registered[:event_id]].appraisal
142
143
  expect(result[:success]).to be true
143
- expect(engine.events[registered[:event_id]].appraisal).to match(/auto-reappraised via/)
144
+ # Valence -0.7 (highly_negative) with non-intense event → :reinterpretation strategy
145
+ expect(appraisal).to eq(
146
+ Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::ReappraisalEngine
147
+ .mechanical_appraisal(:reinterpretation, -0.7)
148
+ )
149
+ expect(appraisal).not_to match(/auto-reappraised via/)
144
150
  end
145
151
  end
146
152
 
@@ -150,10 +156,15 @@ RSpec.describe Legion::Extensions::Agentic::Affect::Reappraisal::Runners::Cognit
150
156
  allow(enhancer).to receive(:generate_reappraisal).and_return(nil)
151
157
  end
152
158
 
153
- it 'falls back to mechanical appraisal stub' do
159
+ it 'falls back to a meaningful mechanical appraisal (not a template stub)' do
154
160
  result = client.auto_reappraise_event(event_id: registered[:event_id], engine: engine)
161
+ appraisal = engine.events[registered[:event_id]].appraisal
155
162
  expect(result[:success]).to be true
156
- expect(engine.events[registered[:event_id]].appraisal).to match(/auto-reappraised via/)
163
+ expect(appraisal).to eq(
164
+ Legion::Extensions::Agentic::Affect::Reappraisal::Helpers::ReappraisalEngine
165
+ .mechanical_appraisal(:reinterpretation, -0.7)
166
+ )
167
+ expect(appraisal).not_to match(/auto-reappraised via/)
157
168
  end
158
169
  end
159
170
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-affect
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
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 affect domain: emotions, valence, mood regulation'
153
153
  email:
154
154
  - matthewdiverson@gmail.com