lex-agentic-affect 0.1.0

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.
Files changed (218) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-affect.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/affect/appraisal/client.rb +20 -0
  8. data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal.rb +112 -0
  9. data/lib/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine.rb +129 -0
  10. data/lib/legion/extensions/agentic/affect/appraisal/helpers/constants.rb +43 -0
  11. data/lib/legion/extensions/agentic/affect/appraisal/runners/appraisal.rb +105 -0
  12. data/lib/legion/extensions/agentic/affect/appraisal/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/affect/appraisal.rb +19 -0
  14. data/lib/legion/extensions/agentic/affect/cognitive_empathy/client.rb +19 -0
  15. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/constants.rb +37 -0
  16. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine.rb +151 -0
  17. data/lib/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective.rb +92 -0
  18. data/lib/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy.rb +93 -0
  19. data/lib/legion/extensions/agentic/affect/cognitive_empathy/version.rb +13 -0
  20. data/lib/legion/extensions/agentic/affect/cognitive_empathy.rb +20 -0
  21. data/lib/legion/extensions/agentic/affect/contagion/client.rb +28 -0
  22. data/lib/legion/extensions/agentic/affect/contagion/helpers/constants.rb +34 -0
  23. data/lib/legion/extensions/agentic/affect/contagion/helpers/contagion_engine.rb +184 -0
  24. data/lib/legion/extensions/agentic/affect/contagion/helpers/meme.rb +97 -0
  25. data/lib/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion.rb +125 -0
  26. data/lib/legion/extensions/agentic/affect/contagion/version.rb +13 -0
  27. data/lib/legion/extensions/agentic/affect/contagion.rb +19 -0
  28. data/lib/legion/extensions/agentic/affect/defusion/client.rb +28 -0
  29. data/lib/legion/extensions/agentic/affect/defusion/helpers/constants.rb +64 -0
  30. data/lib/legion/extensions/agentic/affect/defusion/helpers/defusion_engine.rb +167 -0
  31. data/lib/legion/extensions/agentic/affect/defusion/helpers/thought.rb +92 -0
  32. data/lib/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion.rb +127 -0
  33. data/lib/legion/extensions/agentic/affect/defusion/version.rb +13 -0
  34. data/lib/legion/extensions/agentic/affect/defusion.rb +19 -0
  35. data/lib/legion/extensions/agentic/affect/emotion/actors/momentum_decay.rb +45 -0
  36. data/lib/legion/extensions/agentic/affect/emotion/client.rb +36 -0
  37. data/lib/legion/extensions/agentic/affect/emotion/helpers/baseline.rb +52 -0
  38. data/lib/legion/extensions/agentic/affect/emotion/helpers/momentum.rb +52 -0
  39. data/lib/legion/extensions/agentic/affect/emotion/helpers/valence.rb +92 -0
  40. data/lib/legion/extensions/agentic/affect/emotion/runners/gut.rb +102 -0
  41. data/lib/legion/extensions/agentic/affect/emotion/runners/valence.rb +120 -0
  42. data/lib/legion/extensions/agentic/affect/emotion/version.rb +13 -0
  43. data/lib/legion/extensions/agentic/affect/emotion.rb +20 -0
  44. data/lib/legion/extensions/agentic/affect/empathy/client.rb +21 -0
  45. data/lib/legion/extensions/agentic/affect/empathy/helpers/constants.rb +54 -0
  46. data/lib/legion/extensions/agentic/affect/empathy/helpers/mental_model.rb +185 -0
  47. data/lib/legion/extensions/agentic/affect/empathy/helpers/model_store.rb +88 -0
  48. data/lib/legion/extensions/agentic/affect/empathy/runners/empathy.rb +173 -0
  49. data/lib/legion/extensions/agentic/affect/empathy/version.rb +13 -0
  50. data/lib/legion/extensions/agentic/affect/empathy.rb +20 -0
  51. data/lib/legion/extensions/agentic/affect/fatigue/client.rb +26 -0
  52. data/lib/legion/extensions/agentic/affect/fatigue/helpers/constants.rb +54 -0
  53. data/lib/legion/extensions/agentic/affect/fatigue/helpers/energy_model.rb +181 -0
  54. data/lib/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store.rb +146 -0
  55. data/lib/legion/extensions/agentic/affect/fatigue/runners/fatigue.rb +89 -0
  56. data/lib/legion/extensions/agentic/affect/fatigue/version.rb +13 -0
  57. data/lib/legion/extensions/agentic/affect/fatigue.rb +19 -0
  58. data/lib/legion/extensions/agentic/affect/flow/client.rb +25 -0
  59. data/lib/legion/extensions/agentic/affect/flow/helpers/constants.rb +84 -0
  60. data/lib/legion/extensions/agentic/affect/flow/helpers/flow_detector.rb +166 -0
  61. data/lib/legion/extensions/agentic/affect/flow/runners/flow.rb +129 -0
  62. data/lib/legion/extensions/agentic/affect/flow/version.rb +13 -0
  63. data/lib/legion/extensions/agentic/affect/flow.rb +18 -0
  64. data/lib/legion/extensions/agentic/affect/interoception/actors/decay.rb +45 -0
  65. data/lib/legion/extensions/agentic/affect/interoception/client.rb +28 -0
  66. data/lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb +152 -0
  67. data/lib/legion/extensions/agentic/affect/interoception/helpers/constants.rb +68 -0
  68. data/lib/legion/extensions/agentic/affect/interoception/helpers/somatic_marker.rb +75 -0
  69. data/lib/legion/extensions/agentic/affect/interoception/runners/interoception.rb +101 -0
  70. data/lib/legion/extensions/agentic/affect/interoception/version.rb +13 -0
  71. data/lib/legion/extensions/agentic/affect/interoception.rb +20 -0
  72. data/lib/legion/extensions/agentic/affect/mood/client.rb +21 -0
  73. data/lib/legion/extensions/agentic/affect/mood/helpers/constants.rb +78 -0
  74. data/lib/legion/extensions/agentic/affect/mood/helpers/mood_state.rb +154 -0
  75. data/lib/legion/extensions/agentic/affect/mood/runners/mood.rb +122 -0
  76. data/lib/legion/extensions/agentic/affect/mood/version.rb +13 -0
  77. data/lib/legion/extensions/agentic/affect/mood.rb +18 -0
  78. data/lib/legion/extensions/agentic/affect/motivation/client.rb +26 -0
  79. data/lib/legion/extensions/agentic/affect/motivation/helpers/constants.rb +48 -0
  80. data/lib/legion/extensions/agentic/affect/motivation/helpers/drive_state.rb +98 -0
  81. data/lib/legion/extensions/agentic/affect/motivation/helpers/motivation_store.rb +106 -0
  82. data/lib/legion/extensions/agentic/affect/motivation/runners/motivation.rb +165 -0
  83. data/lib/legion/extensions/agentic/affect/motivation/version.rb +13 -0
  84. data/lib/legion/extensions/agentic/affect/motivation.rb +19 -0
  85. data/lib/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate.rb +45 -0
  86. data/lib/legion/extensions/agentic/affect/reappraisal/client.rb +28 -0
  87. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/constants.rb +82 -0
  88. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event.rb +98 -0
  89. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer.rb +88 -0
  90. data/lib/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine.rb +153 -0
  91. data/lib/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal.rb +164 -0
  92. data/lib/legion/extensions/agentic/affect/reappraisal/version.rb +13 -0
  93. data/lib/legion/extensions/agentic/affect/reappraisal.rb +20 -0
  94. data/lib/legion/extensions/agentic/affect/regulation/client.rb +25 -0
  95. data/lib/legion/extensions/agentic/affect/regulation/helpers/constants.rb +71 -0
  96. data/lib/legion/extensions/agentic/affect/regulation/helpers/regulation_model.rb +175 -0
  97. data/lib/legion/extensions/agentic/affect/regulation/runners/emotional_regulation.rb +127 -0
  98. data/lib/legion/extensions/agentic/affect/regulation/version.rb +13 -0
  99. data/lib/legion/extensions/agentic/affect/regulation.rb +18 -0
  100. data/lib/legion/extensions/agentic/affect/resilience/client.rb +27 -0
  101. data/lib/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker.rb +130 -0
  102. data/lib/legion/extensions/agentic/affect/resilience/helpers/constants.rb +79 -0
  103. data/lib/legion/extensions/agentic/affect/resilience/helpers/resilience_model.rb +165 -0
  104. data/lib/legion/extensions/agentic/affect/resilience/runners/resilience.rb +150 -0
  105. data/lib/legion/extensions/agentic/affect/resilience/version.rb +13 -0
  106. data/lib/legion/extensions/agentic/affect/resilience.rb +19 -0
  107. data/lib/legion/extensions/agentic/affect/resonance/client.rb +24 -0
  108. data/lib/legion/extensions/agentic/affect/resonance/helpers/category.rb +75 -0
  109. data/lib/legion/extensions/agentic/affect/resonance/helpers/constants.rb +47 -0
  110. data/lib/legion/extensions/agentic/affect/resonance/helpers/resonance_engine.rb +115 -0
  111. data/lib/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance.rb +94 -0
  112. data/lib/legion/extensions/agentic/affect/resonance/version.rb +13 -0
  113. data/lib/legion/extensions/agentic/affect/resonance.rb +19 -0
  114. data/lib/legion/extensions/agentic/affect/reward/client.rb +26 -0
  115. data/lib/legion/extensions/agentic/affect/reward/helpers/constants.rb +67 -0
  116. data/lib/legion/extensions/agentic/affect/reward/helpers/reward_signal.rb +178 -0
  117. data/lib/legion/extensions/agentic/affect/reward/helpers/reward_store.rb +142 -0
  118. data/lib/legion/extensions/agentic/affect/reward/runners/reward.rb +92 -0
  119. data/lib/legion/extensions/agentic/affect/reward/version.rb +13 -0
  120. data/lib/legion/extensions/agentic/affect/reward.rb +19 -0
  121. data/lib/legion/extensions/agentic/affect/somatic_marker/actors/decay.rb +45 -0
  122. data/lib/legion/extensions/agentic/affect/somatic_marker/client.rb +29 -0
  123. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/body_state.rb +69 -0
  124. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/constants.rb +43 -0
  125. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store.rb +160 -0
  126. data/lib/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker.rb +74 -0
  127. data/lib/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker.rb +132 -0
  128. data/lib/legion/extensions/agentic/affect/somatic_marker/version.rb +13 -0
  129. data/lib/legion/extensions/agentic/affect/somatic_marker.rb +20 -0
  130. data/lib/legion/extensions/agentic/affect/version.rb +11 -0
  131. data/lib/legion/extensions/agentic/affect.rb +34 -0
  132. data/spec/legion/extensions/agentic/affect/appraisal/client_spec.rb +52 -0
  133. data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_engine_spec.rb +161 -0
  134. data/spec/legion/extensions/agentic/affect/appraisal/helpers/appraisal_spec.rb +175 -0
  135. data/spec/legion/extensions/agentic/affect/appraisal/helpers/constants_spec.rb +49 -0
  136. data/spec/legion/extensions/agentic/affect/appraisal/runners/appraisal_spec.rb +116 -0
  137. data/spec/legion/extensions/agentic/affect/cognitive_empathy/client_spec.rb +62 -0
  138. data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/empathy_engine_spec.rb +316 -0
  139. data/spec/legion/extensions/agentic/affect/cognitive_empathy/helpers/perspective_spec.rb +132 -0
  140. data/spec/legion/extensions/agentic/affect/cognitive_empathy/runners/cognitive_empathy_spec.rb +200 -0
  141. data/spec/legion/extensions/agentic/affect/contagion/client_spec.rb +63 -0
  142. data/spec/legion/extensions/agentic/affect/contagion/helpers/constants_spec.rb +86 -0
  143. data/spec/legion/extensions/agentic/affect/contagion/helpers/contagion_engine_spec.rb +241 -0
  144. data/spec/legion/extensions/agentic/affect/contagion/helpers/meme_spec.rb +160 -0
  145. data/spec/legion/extensions/agentic/affect/contagion/runners/cognitive_contagion_spec.rb +211 -0
  146. data/spec/legion/extensions/agentic/affect/defusion/client_spec.rb +80 -0
  147. data/spec/legion/extensions/agentic/affect/defusion/helpers/constants_spec.rb +84 -0
  148. data/spec/legion/extensions/agentic/affect/defusion/helpers/defusion_engine_spec.rb +250 -0
  149. data/spec/legion/extensions/agentic/affect/defusion/helpers/thought_spec.rb +178 -0
  150. data/spec/legion/extensions/agentic/affect/defusion/runners/cognitive_defusion_spec.rb +185 -0
  151. data/spec/legion/extensions/agentic/affect/emotion/actors/momentum_decay_spec.rb +46 -0
  152. data/spec/legion/extensions/agentic/affect/emotion/client_spec.rb +46 -0
  153. data/spec/legion/extensions/agentic/affect/emotion/helpers/baseline_spec.rb +48 -0
  154. data/spec/legion/extensions/agentic/affect/emotion/helpers/momentum_spec.rb +45 -0
  155. data/spec/legion/extensions/agentic/affect/emotion/helpers/valence_spec.rb +91 -0
  156. data/spec/legion/extensions/agentic/affect/emotion/runners/gut_spec.rb +73 -0
  157. data/spec/legion/extensions/agentic/affect/emotion/runners/valence_spec.rb +67 -0
  158. data/spec/legion/extensions/agentic/affect/empathy/client_spec.rb +20 -0
  159. data/spec/legion/extensions/agentic/affect/empathy/helpers/constants_spec.rb +23 -0
  160. data/spec/legion/extensions/agentic/affect/empathy/helpers/mental_model_spec.rb +150 -0
  161. data/spec/legion/extensions/agentic/affect/empathy/helpers/model_store_spec.rb +94 -0
  162. data/spec/legion/extensions/agentic/affect/empathy/runners/empathy_spec.rb +127 -0
  163. data/spec/legion/extensions/agentic/affect/fatigue/client_spec.rb +66 -0
  164. data/spec/legion/extensions/agentic/affect/fatigue/helpers/constants_spec.rb +130 -0
  165. data/spec/legion/extensions/agentic/affect/fatigue/helpers/energy_model_spec.rb +281 -0
  166. data/spec/legion/extensions/agentic/affect/fatigue/helpers/fatigue_store_spec.rb +157 -0
  167. data/spec/legion/extensions/agentic/affect/fatigue/runners/fatigue_spec.rb +127 -0
  168. data/spec/legion/extensions/agentic/affect/flow/client_spec.rb +58 -0
  169. data/spec/legion/extensions/agentic/affect/flow/helpers/constants_spec.rb +112 -0
  170. data/spec/legion/extensions/agentic/affect/flow/helpers/flow_detector_spec.rb +268 -0
  171. data/spec/legion/extensions/agentic/affect/flow/runners/flow_spec.rb +222 -0
  172. data/spec/legion/extensions/agentic/affect/interoception/client_spec.rb +52 -0
  173. data/spec/legion/extensions/agentic/affect/interoception/helpers/body_budget_spec.rb +178 -0
  174. data/spec/legion/extensions/agentic/affect/interoception/helpers/somatic_marker_spec.rb +120 -0
  175. data/spec/legion/extensions/agentic/affect/interoception/runners/interoception_spec.rb +108 -0
  176. data/spec/legion/extensions/agentic/affect/mood/client_spec.rb +20 -0
  177. data/spec/legion/extensions/agentic/affect/mood/helpers/constants_spec.rb +29 -0
  178. data/spec/legion/extensions/agentic/affect/mood/helpers/mood_state_spec.rb +94 -0
  179. data/spec/legion/extensions/agentic/affect/mood/runners/mood_spec.rb +71 -0
  180. data/spec/legion/extensions/agentic/affect/motivation/client_spec.rb +35 -0
  181. data/spec/legion/extensions/agentic/affect/motivation/helpers/constants_spec.rb +111 -0
  182. data/spec/legion/extensions/agentic/affect/motivation/helpers/drive_state_spec.rb +183 -0
  183. data/spec/legion/extensions/agentic/affect/motivation/helpers/motivation_store_spec.rb +185 -0
  184. data/spec/legion/extensions/agentic/affect/motivation/runners/motivation_spec.rb +248 -0
  185. data/spec/legion/extensions/agentic/affect/reappraisal/actors/auto_regulate_spec.rb +46 -0
  186. data/spec/legion/extensions/agentic/affect/reappraisal/client_spec.rb +64 -0
  187. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/constants_spec.rb +102 -0
  188. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/emotional_event_spec.rb +177 -0
  189. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/llm_enhancer_spec.rb +161 -0
  190. data/spec/legion/extensions/agentic/affect/reappraisal/helpers/reappraisal_engine_spec.rb +211 -0
  191. data/spec/legion/extensions/agentic/affect/reappraisal/runners/cognitive_reappraisal_spec.rb +312 -0
  192. data/spec/legion/extensions/agentic/affect/regulation/client_spec.rb +61 -0
  193. data/spec/legion/extensions/agentic/affect/regulation/helpers/constants_spec.rb +108 -0
  194. data/spec/legion/extensions/agentic/affect/regulation/helpers/regulation_model_spec.rb +200 -0
  195. data/spec/legion/extensions/agentic/affect/regulation/runners/emotional_regulation_spec.rb +190 -0
  196. data/spec/legion/extensions/agentic/affect/resilience/client_spec.rb +36 -0
  197. data/spec/legion/extensions/agentic/affect/resilience/helpers/adversity_tracker_spec.rb +164 -0
  198. data/spec/legion/extensions/agentic/affect/resilience/helpers/constants_spec.rb +78 -0
  199. data/spec/legion/extensions/agentic/affect/resilience/helpers/resilience_model_spec.rb +133 -0
  200. data/spec/legion/extensions/agentic/affect/resilience/runners/resilience_spec.rb +150 -0
  201. data/spec/legion/extensions/agentic/affect/resonance/client_spec.rb +66 -0
  202. data/spec/legion/extensions/agentic/affect/resonance/cognitive_resonance_spec.rb +27 -0
  203. data/spec/legion/extensions/agentic/affect/resonance/helpers/category_spec.rb +146 -0
  204. data/spec/legion/extensions/agentic/affect/resonance/helpers/constants_spec.rb +104 -0
  205. data/spec/legion/extensions/agentic/affect/resonance/helpers/resonance_engine_spec.rb +189 -0
  206. data/spec/legion/extensions/agentic/affect/resonance/runners/cognitive_resonance_spec.rb +197 -0
  207. data/spec/legion/extensions/agentic/affect/reward/client_spec.rb +42 -0
  208. data/spec/legion/extensions/agentic/affect/reward/helpers/constants_spec.rb +91 -0
  209. data/spec/legion/extensions/agentic/affect/reward/helpers/reward_signal_spec.rb +296 -0
  210. data/spec/legion/extensions/agentic/affect/reward/helpers/reward_store_spec.rb +167 -0
  211. data/spec/legion/extensions/agentic/affect/reward/runners/reward_spec.rb +149 -0
  212. data/spec/legion/extensions/agentic/affect/somatic_marker/client_spec.rb +83 -0
  213. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/body_state_spec.rb +155 -0
  214. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/marker_store_spec.rb +233 -0
  215. data/spec/legion/extensions/agentic/affect/somatic_marker/helpers/somatic_marker_spec.rb +172 -0
  216. data/spec/legion/extensions/agentic/affect/somatic_marker/runners/somatic_marker_spec.rb +181 -0
  217. data/spec/spec_helper.rb +46 -0
  218. metadata +302 -0
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Empathy::Helpers::MentalModel do
4
+ subject(:model) { described_class.new(agent_id: 'agent-42') }
5
+
6
+ describe '#initialize' do
7
+ it 'sets agent_id' do
8
+ expect(model.agent_id).to eq('agent-42')
9
+ end
10
+
11
+ it 'starts with unknown emotional state' do
12
+ expect(model.emotional_state).to eq(:unknown)
13
+ end
14
+
15
+ it 'starts with unknown cooperation stance' do
16
+ expect(model.cooperation_stance).to eq(:unknown)
17
+ end
18
+
19
+ it 'starts with 0.5 confidence' do
20
+ expect(model.confidence_level).to eq(0.5)
21
+ end
22
+ end
23
+
24
+ describe '#update_from_observation' do
25
+ it 'updates believed goal' do
26
+ model.update_from_observation(goal: 'code_review')
27
+ expect(model.believed_goal).to eq('code_review')
28
+ end
29
+
30
+ it 'updates emotional state' do
31
+ model.update_from_observation(emotion: :focused)
32
+ expect(model.emotional_state).to eq(:focused)
33
+ end
34
+
35
+ it 'updates cooperation stance' do
36
+ model.update_from_observation(cooperation: :cooperative)
37
+ expect(model.cooperation_stance).to eq(:cooperative)
38
+ end
39
+
40
+ it 'records interaction history' do
41
+ model.update_from_observation(summary: 'sent a message')
42
+ expect(model.interaction_history.size).to eq(1)
43
+ end
44
+
45
+ it 'rejects unknown emotions as :unknown' do
46
+ model.update_from_observation(emotion: :nonexistent)
47
+ expect(model.emotional_state).to eq(:unknown)
48
+ end
49
+
50
+ it 'updates confidence via EMA' do
51
+ model.update_from_observation(evidence_strength: 0.9)
52
+ expect(model.confidence_level).to be > 0.5
53
+ end
54
+ end
55
+
56
+ describe '#predict_reaction' do
57
+ before do
58
+ model.update_from_observation(cooperation: :cooperative, emotion: :calm)
59
+ end
60
+
61
+ it 'returns a prediction hash' do
62
+ prediction = model.predict_reaction(emotional_impact: :positive)
63
+ expect(prediction).to have_key(:prediction_id)
64
+ expect(prediction).to have_key(:likely_response)
65
+ expect(prediction).to have_key(:confidence)
66
+ end
67
+
68
+ it 'predicts cooperative agents will likely agree' do
69
+ prediction = model.predict_reaction(cooperative_option: :accept)
70
+ expect(prediction[:likely_response]).to eq(:accept)
71
+ end
72
+
73
+ it 'stores predictions' do
74
+ model.predict_reaction({})
75
+ expect(model.predictions.size).to eq(1)
76
+ end
77
+ end
78
+
79
+ describe '#record_prediction_outcome' do
80
+ it 'records accurate prediction' do
81
+ prediction = model.predict_reaction({})
82
+ result = model.record_prediction_outcome(
83
+ prediction_id: prediction[:prediction_id],
84
+ actual_response: :agreed,
85
+ accurate: true
86
+ )
87
+ expect(result).to be true
88
+ end
89
+
90
+ it 'returns nil for unknown prediction' do
91
+ result = model.record_prediction_outcome(
92
+ prediction_id: 'nonexistent',
93
+ actual_response: :agreed,
94
+ accurate: true
95
+ )
96
+ expect(result).to be_nil
97
+ end
98
+ end
99
+
100
+ describe '#prediction_accuracy' do
101
+ it 'returns nil with no outcomes' do
102
+ expect(model.prediction_accuracy).to be_nil
103
+ end
104
+
105
+ it 'computes accuracy from outcomes' do
106
+ 3.times do
107
+ pred = model.predict_reaction({})
108
+ model.record_prediction_outcome(prediction_id: pred[:prediction_id],
109
+ actual_response: :ok, accurate: true)
110
+ end
111
+ pred = model.predict_reaction({})
112
+ model.record_prediction_outcome(prediction_id: pred[:prediction_id],
113
+ actual_response: :nope, accurate: false)
114
+
115
+ expect(model.prediction_accuracy).to eq(0.75)
116
+ end
117
+ end
118
+
119
+ describe '#stale?' do
120
+ it 'returns false when fresh' do
121
+ expect(model.stale?).to be false
122
+ end
123
+
124
+ it 'returns true when old' do
125
+ model.instance_variable_set(:@updated_at, Time.now.utc - 400)
126
+ expect(model.stale?).to be true
127
+ end
128
+ end
129
+
130
+ describe '#decay' do
131
+ it 'reduces confidence' do
132
+ original = model.confidence_level
133
+ model.decay
134
+ expect(model.confidence_level).to be < original
135
+ end
136
+
137
+ it 'floors confidence at 0.1' do
138
+ 50.times { model.decay }
139
+ expect(model.confidence_level).to be >= 0.1
140
+ end
141
+ end
142
+
143
+ describe '#to_h' do
144
+ it 'returns a complete state hash' do
145
+ h = model.to_h
146
+ expect(h).to include(:agent_id, :believed_goal, :emotional_state,
147
+ :cooperation_stance, :confidence_level, :stale)
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Empathy::Helpers::ModelStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ describe '#get_or_create' do
7
+ it 'creates a new model for unknown agent' do
8
+ model = store.get_or_create('agent-1')
9
+ expect(model.agent_id).to eq('agent-1')
10
+ end
11
+
12
+ it 'returns existing model' do
13
+ m1 = store.get_or_create('agent-1')
14
+ m2 = store.get_or_create('agent-1')
15
+ expect(m1).to equal(m2)
16
+ end
17
+ end
18
+
19
+ describe '#update' do
20
+ it 'updates model with observation' do
21
+ model = store.update('agent-1', emotion: :focused, cooperation: :cooperative)
22
+ expect(model.emotional_state).to eq(:focused)
23
+ end
24
+
25
+ it 'increments store size' do
26
+ store.update('agent-1', {})
27
+ store.update('agent-2', {})
28
+ expect(store.size).to eq(2)
29
+ end
30
+ end
31
+
32
+ describe '#predict' do
33
+ it 'returns nil for unknown agent' do
34
+ expect(store.predict('nobody', {})).to be_nil
35
+ end
36
+
37
+ it 'returns prediction for known agent' do
38
+ store.update('agent-1', cooperation: :cooperative)
39
+ prediction = store.predict('agent-1', {})
40
+ expect(prediction).to have_key(:likely_response)
41
+ end
42
+ end
43
+
44
+ describe '#decay_all' do
45
+ it 'decays all models' do
46
+ store.update('agent-1', {})
47
+ store.update('agent-2', {})
48
+ count = store.decay_all
49
+ expect(count).to eq(2)
50
+ end
51
+ end
52
+
53
+ describe '#remove_stale' do
54
+ it 'removes stale models with no interactions' do
55
+ store.get_or_create('agent-old')
56
+ store.models['agent-old'].instance_variable_set(:@updated_at, Time.now.utc - 400)
57
+ removed = store.remove_stale
58
+ expect(removed).to eq(1)
59
+ expect(store.size).to eq(0)
60
+ end
61
+
62
+ it 'keeps stale models that have interactions' do
63
+ store.update('agent-old', summary: 'did something')
64
+ store.models['agent-old'].instance_variable_set(:@updated_at, Time.now.utc - 400)
65
+ removed = store.remove_stale
66
+ expect(removed).to eq(0)
67
+ end
68
+ end
69
+
70
+ describe '#by_cooperation' do
71
+ it 'filters by cooperation stance' do
72
+ store.update('a', cooperation: :cooperative)
73
+ store.update('b', cooperation: :competitive)
74
+ store.update('c', cooperation: :cooperative)
75
+ expect(store.by_cooperation(:cooperative).size).to eq(2)
76
+ end
77
+ end
78
+
79
+ describe '#by_emotion' do
80
+ it 'filters by emotional state' do
81
+ store.update('a', emotion: :stressed)
82
+ store.update('b', emotion: :calm)
83
+ expect(store.by_emotion(:stressed).size).to eq(1)
84
+ end
85
+ end
86
+
87
+ describe '#clear' do
88
+ it 'removes all models' do
89
+ store.update('a', {})
90
+ store.clear
91
+ expect(store.size).to eq(0)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Empathy::Runners::Empathy do
4
+ let(:client) { Legion::Extensions::Agentic::Affect::Empathy::Client.new }
5
+
6
+ describe '#observe_agent' do
7
+ it 'creates a mental model from observation' do
8
+ result = client.observe_agent(agent_id: 'agent-b', observation: {
9
+ goal: 'testing', emotion: :focused, cooperation: :cooperative, evidence_strength: 0.8
10
+ })
11
+ expect(result[:emotional_state]).to eq(:focused)
12
+ expect(result[:cooperation_stance]).to eq(:cooperative)
13
+ expect(result[:believed_goal]).to eq('testing')
14
+ end
15
+
16
+ it 'updates existing models' do
17
+ client.observe_agent(agent_id: 'agent-b', observation: { emotion: :calm })
18
+ result = client.observe_agent(agent_id: 'agent-b', observation: { emotion: :stressed })
19
+ expect(result[:emotional_state]).to eq(:stressed)
20
+ end
21
+ end
22
+
23
+ describe '#predict_reaction' do
24
+ before do
25
+ client.observe_agent(agent_id: 'agent-b', observation: {
26
+ cooperation: :cooperative, emotion: :calm
27
+ })
28
+ end
29
+
30
+ it 'returns prediction for known agent' do
31
+ result = client.predict_reaction(agent_id: 'agent-b', scenario: {
32
+ emotional_impact: :positive, impact_on_agent: :beneficial
33
+ })
34
+ expect(result).to have_key(:likely_response)
35
+ expect(result).to have_key(:confidence)
36
+ end
37
+
38
+ it 'returns error for unknown agent' do
39
+ result = client.predict_reaction(agent_id: 'nobody', scenario: {})
40
+ expect(result[:error]).to eq(:no_model)
41
+ end
42
+ end
43
+
44
+ describe '#record_outcome' do
45
+ it 'records prediction outcome' do
46
+ client.observe_agent(agent_id: 'agent-b', observation: { cooperation: :cooperative })
47
+ prediction = client.predict_reaction(agent_id: 'agent-b', scenario: {})
48
+ result = client.record_outcome(
49
+ agent_id: 'agent-b',
50
+ prediction_id: prediction[:prediction_id],
51
+ actual_response: :agreed,
52
+ accurate: true
53
+ )
54
+ expect(result[:accurate]).to be true
55
+ expect(result[:current_accuracy]).to eq(1.0)
56
+ end
57
+
58
+ it 'returns error for unknown agent' do
59
+ result = client.record_outcome(agent_id: 'nobody', prediction_id: 'x',
60
+ actual_response: :ok, accurate: true)
61
+ expect(result[:error]).to eq(:no_model)
62
+ end
63
+
64
+ it 'returns error for unknown prediction' do
65
+ client.observe_agent(agent_id: 'agent-b', observation: {})
66
+ result = client.record_outcome(agent_id: 'agent-b', prediction_id: 'nonexistent',
67
+ actual_response: :ok, accurate: true)
68
+ expect(result[:error]).to eq(:prediction_not_found)
69
+ end
70
+ end
71
+
72
+ describe '#perspective_take' do
73
+ it 'generates narrative for known agent' do
74
+ client.observe_agent(agent_id: 'agent-b', observation: {
75
+ goal: 'code_review', emotion: :focused, cooperation: :cooperative
76
+ })
77
+ result = client.perspective_take(agent_id: 'agent-b')
78
+ expect(result[:narrative]).to include('agent-b')
79
+ expect(result[:narrative]).to include('code_review')
80
+ end
81
+
82
+ it 'returns error for unknown agent' do
83
+ result = client.perspective_take(agent_id: 'nobody')
84
+ expect(result[:error]).to eq(:no_model)
85
+ end
86
+ end
87
+
88
+ describe '#social_landscape' do
89
+ before do
90
+ client.observe_agent(agent_id: 'a', observation: { cooperation: :cooperative, emotion: :calm })
91
+ client.observe_agent(agent_id: 'b', observation: { cooperation: :cooperative, emotion: :focused })
92
+ client.observe_agent(agent_id: 'c', observation: { cooperation: :competitive, emotion: :stressed })
93
+ end
94
+
95
+ it 'returns social climate assessment' do
96
+ result = client.social_landscape
97
+ expect(result[:tracked_agents]).to eq(3)
98
+ expect(result[:cooperative_count]).to eq(2)
99
+ expect(result[:competitive_count]).to eq(1)
100
+ expect(result).to have_key(:overall_climate)
101
+ end
102
+
103
+ it 'assesses harmonious climate when mostly cooperative' do
104
+ client.observe_agent(agent_id: 'd', observation: { cooperation: :cooperative })
105
+ result = client.social_landscape
106
+ expect(result[:overall_climate]).to eq(:harmonious)
107
+ end
108
+ end
109
+
110
+ describe '#decay_models' do
111
+ it 'decays all models' do
112
+ client.observe_agent(agent_id: 'a', observation: {})
113
+ result = client.decay_models
114
+ expect(result[:decayed]).to eq(1)
115
+ end
116
+ end
117
+
118
+ describe '#empathy_stats' do
119
+ it 'returns summary statistics' do
120
+ client.observe_agent(agent_id: 'a', observation: { cooperation: :cooperative })
121
+ client.predict_reaction(agent_id: 'a', scenario: {})
122
+ result = client.empathy_stats
123
+ expect(result[:tracked_agents]).to eq(1)
124
+ expect(result[:total_predictions]).to eq(1)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/fatigue/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Affect::Fatigue::Client do
6
+ subject(:client) { described_class.new }
7
+
8
+ describe '#initialize' do
9
+ it 'creates a default fatigue_store' do
10
+ expect(client.fatigue_store).to be_a(Legion::Extensions::Agentic::Affect::Fatigue::Helpers::FatigueStore)
11
+ end
12
+
13
+ it 'accepts an injected fatigue_store' do
14
+ custom_store = Legion::Extensions::Agentic::Affect::Fatigue::Helpers::FatigueStore.new
15
+ c = described_class.new(fatigue_store: custom_store)
16
+ expect(c.fatigue_store).to be(custom_store)
17
+ end
18
+ end
19
+
20
+ describe 'runner methods' do
21
+ it 'responds to update_fatigue' do
22
+ expect(client).to respond_to(:update_fatigue)
23
+ end
24
+
25
+ it 'responds to energy_status' do
26
+ expect(client).to respond_to(:energy_status)
27
+ end
28
+
29
+ it 'responds to enter_rest' do
30
+ expect(client).to respond_to(:enter_rest)
31
+ end
32
+
33
+ it 'responds to exit_rest' do
34
+ expect(client).to respond_to(:exit_rest)
35
+ end
36
+
37
+ it 'responds to energy_forecast' do
38
+ expect(client).to respond_to(:energy_forecast)
39
+ end
40
+
41
+ it 'responds to fatigue_stats' do
42
+ expect(client).to respond_to(:fatigue_stats)
43
+ end
44
+ end
45
+
46
+ describe 'state persistence' do
47
+ it 'maintains state across multiple update calls' do
48
+ 5.times { client.update_fatigue(tick_results: { cognitive_load: 0.8, emotional_arousal: 0.8 }) }
49
+ stats = client.fatigue_stats
50
+ expect(stats[:session][:active_ticks]).to eq(5)
51
+ end
52
+
53
+ it 'reflects state after entering rest' do
54
+ client.enter_rest(mode: :sleep)
55
+ status = client.energy_status
56
+ expect(status[:recovery_mode]).to eq(:sleep)
57
+ end
58
+
59
+ it 'clears recovery mode after exit_rest' do
60
+ client.enter_rest(mode: :sleep)
61
+ client.exit_rest
62
+ status = client.energy_status
63
+ expect(status[:recovery_mode]).to be_nil
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Affect::Fatigue::Helpers::Constants do
4
+ describe 'energy limits' do
5
+ it 'defines MAX_ENERGY as 1.0' do
6
+ expect(described_class::MAX_ENERGY).to eq(1.0)
7
+ end
8
+
9
+ it 'defines MIN_ENERGY as 0.0' do
10
+ expect(described_class::MIN_ENERGY).to eq(0.0)
11
+ end
12
+
13
+ it 'MAX_ENERGY is greater than MIN_ENERGY' do
14
+ expect(described_class::MAX_ENERGY).to be > described_class::MIN_ENERGY
15
+ end
16
+ end
17
+
18
+ describe 'drain and recovery rates' do
19
+ it 'defines ACTIVE_DRAIN_RATE as a positive float' do
20
+ expect(described_class::ACTIVE_DRAIN_RATE).to be > 0.0
21
+ end
22
+
23
+ it 'defines RESTING_RECOVERY_RATE as a positive float' do
24
+ expect(described_class::RESTING_RECOVERY_RATE).to be > 0.0
25
+ end
26
+
27
+ it 'defines COGNITIVE_DRAIN_MULTIPLIER greater than 1.0' do
28
+ expect(described_class::COGNITIVE_DRAIN_MULTIPLIER).to be > 1.0
29
+ end
30
+
31
+ it 'defines EMOTIONAL_DRAIN_MULTIPLIER greater than 1.0' do
32
+ expect(described_class::EMOTIONAL_DRAIN_MULTIPLIER).to be > 1.0
33
+ end
34
+ end
35
+
36
+ describe 'FATIGUE_LEVELS' do
37
+ subject(:levels) { described_class::FATIGUE_LEVELS }
38
+
39
+ it 'contains exactly 5 levels' do
40
+ expect(levels.size).to eq(5)
41
+ end
42
+
43
+ it 'includes fresh, alert, tired, exhausted, depleted' do
44
+ expect(levels).to include(:fresh, :alert, :tired, :exhausted, :depleted)
45
+ end
46
+
47
+ it 'has thresholds in descending order' do
48
+ values = levels.values
49
+ expect(values).to eq(values.sort.reverse)
50
+ end
51
+
52
+ it 'fresh threshold is the highest' do
53
+ expect(levels[:fresh]).to be > levels[:alert]
54
+ end
55
+
56
+ it 'depleted threshold is 0.0' do
57
+ expect(levels[:depleted]).to eq(0.0)
58
+ end
59
+ end
60
+
61
+ describe 'PERFORMANCE_DEGRADATION' do
62
+ subject(:degradation) { described_class::PERFORMANCE_DEGRADATION }
63
+
64
+ it 'contains exactly 5 levels matching FATIGUE_LEVELS' do
65
+ expect(degradation.keys).to match_array(described_class::FATIGUE_LEVELS.keys)
66
+ end
67
+
68
+ it 'fresh performance is 1.0' do
69
+ expect(degradation[:fresh]).to eq(1.0)
70
+ end
71
+
72
+ it 'depleted performance is lowest' do
73
+ expect(degradation[:depleted]).to be < degradation[:exhausted]
74
+ end
75
+
76
+ it 'all values are between 0 and 1' do
77
+ degradation.each_value do |v|
78
+ expect(v).to be_between(0.0, 1.0)
79
+ end
80
+ end
81
+ end
82
+
83
+ describe 'RECOVERY_MODES' do
84
+ it 'includes expected recovery modes' do
85
+ expect(described_class::RECOVERY_MODES).to include(:active_rest, :light_duty, :full_rest, :sleep)
86
+ end
87
+
88
+ it 'has exactly 4 modes' do
89
+ expect(described_class::RECOVERY_MODES.size).to eq(4)
90
+ end
91
+ end
92
+
93
+ describe 'RECOVERY_RATES' do
94
+ subject(:rates) { described_class::RECOVERY_RATES }
95
+
96
+ it 'has a rate for each recovery mode' do
97
+ described_class::RECOVERY_MODES.each do |mode|
98
+ expect(rates).to include(mode)
99
+ end
100
+ end
101
+
102
+ it 'sleep has the highest recovery rate' do
103
+ expect(rates[:sleep]).to be > rates[:full_rest]
104
+ expect(rates[:full_rest]).to be > rates[:light_duty]
105
+ expect(rates[:light_duty]).to be > rates[:active_rest]
106
+ end
107
+ end
108
+
109
+ describe 'thresholds' do
110
+ it 'REST_THRESHOLD is between 0 and 1' do
111
+ expect(described_class::REST_THRESHOLD).to be_between(0.0, 1.0)
112
+ end
113
+
114
+ it 'CRITICAL_THRESHOLD is below REST_THRESHOLD' do
115
+ expect(described_class::CRITICAL_THRESHOLD).to be < described_class::REST_THRESHOLD
116
+ end
117
+
118
+ it 'SECOND_WIND_CHANCE is a small probability' do
119
+ expect(described_class::SECOND_WIND_CHANCE).to be_between(0.0, 0.1)
120
+ end
121
+
122
+ it 'BURNOUT_THRESHOLD is a positive integer' do
123
+ expect(described_class::BURNOUT_THRESHOLD).to be > 0
124
+ end
125
+
126
+ it 'MAX_HISTORY is a positive integer' do
127
+ expect(described_class::MAX_HISTORY).to be > 0
128
+ end
129
+ end
130
+ end