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 +4 -4
- data/CHANGELOG.md +7 -0
- data/lex-agentic-affect.gemspec +2 -2
- data/lib/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine.rb +58 -1
- data/lib/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal.rb +2 -1
- data/lib/legion/extensions/agentic/affect/version.rb +1 -1
- data/spec/legion/extensions/agentic/affect/flow/runners/flow_spec.rb +6 -2
- data/spec/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine_spec.rb +103 -0
- data/spec/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal_spec.rb +15 -4
- metadata +9 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5768d527e95204ef16c40ce69b64e6fea209640636223b405804e4dfccc6cf3
|
|
4
|
+
data.tar.gz: 13e3b630d0c1434e716d85cf149949ac2a9848005bb97bba80733ffa61ab74f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lex-agentic-affect.gemspec
CHANGED
|
@@ -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'
|
|
37
|
-
spec.add_development_dependency 'rubocop-rspec'
|
|
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 =
|
|
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) ||
|
|
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]
|
|
@@ -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
|
-
#
|
|
219
|
-
|
|
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
|
data/spec/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal_spec.rb
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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.
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
151
|
+
version: '0'
|
|
152
152
|
description: 'LEX agentic affect domain: emotions, valence, mood regulation'
|
|
153
153
|
email:
|
|
154
154
|
- matthewdiverson@gmail.com
|