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,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Regulation
8
+ module Helpers
9
+ class RegulationModel
10
+ include Constants
11
+
12
+ attr_reader :skill, :consecutive_suppressions, :regulation_history
13
+
14
+ def initialize
15
+ @skill = STRATEGIES.to_h { |s| [s, DEFAULT_SKILL] }
16
+ @consecutive_suppressions = 0
17
+ @regulation_history = []
18
+ end
19
+
20
+ # Apply a regulation strategy to an emotion.
21
+ #
22
+ # Returns a hash with :regulated_magnitude, :cost, :success
23
+ def regulate(emotion_magnitude:, emotion_valence:, strategy:)
24
+ unless STRATEGIES.include?(strategy)
25
+ return { regulated_magnitude: emotion_magnitude, cost: 0.0, success: false,
26
+ reason: :unknown_strategy }
27
+ end
28
+
29
+ base_effectiveness = STRATEGY_EFFECTIVENESS[strategy]
30
+ base_cost = STRATEGY_COST[strategy]
31
+ proficiency = @skill[strategy]
32
+
33
+ # Proficiency scales effectiveness (skill of 1.0 adds 20% to base)
34
+ effective_reduction = base_effectiveness * (1.0 + (proficiency * 0.2))
35
+ effective_reduction = [effective_reduction, 1.0].min
36
+
37
+ # Suppression penalty: repeated suppression degrades its own effectiveness
38
+ effective_reduction = apply_suppression_penalty(strategy, effective_reduction)
39
+
40
+ regulated = (emotion_magnitude * (1.0 - effective_reduction)).clamp(0.0, 1.0)
41
+ actual_cost = base_cost * (1.0 - (proficiency * 0.3))
42
+
43
+ success = regulated < emotion_magnitude
44
+ update_skill(strategy, success: success)
45
+ track_suppression(strategy)
46
+ record_event({
47
+ strategy: strategy,
48
+ emotion_magnitude: emotion_magnitude,
49
+ regulated_magnitude: regulated,
50
+ cost: actual_cost,
51
+ emotion_valence: emotion_valence,
52
+ success: success
53
+ })
54
+
55
+ { regulated_magnitude: regulated, cost: actual_cost, success: success,
56
+ strategy: strategy, proficiency: proficiency }
57
+ end
58
+
59
+ # Recommend the best strategy given current skills and context.
60
+ def recommend_strategy(emotion_magnitude:, emotion_valence:, context: :general)
61
+ scores = STRATEGIES.to_h do |strategy|
62
+ [strategy, score_strategy(strategy, emotion_magnitude, emotion_valence, context)]
63
+ end
64
+
65
+ best = scores.max_by { |_, v| v }[0]
66
+ { recommended: best, scores: scores, context: context }
67
+ end
68
+
69
+ # Decay all skills toward DEFAULT_SKILL by SKILL_DECAY each tick.
70
+ def decay
71
+ @skill.each_key do |strategy|
72
+ current = @skill[strategy]
73
+ @skill[strategy] = if current > DEFAULT_SKILL
74
+ [current - SKILL_DECAY, DEFAULT_SKILL].max
75
+ else
76
+ [current + (SKILL_DECAY * 0.5), DEFAULT_SKILL].min
77
+ end
78
+ end
79
+ end
80
+
81
+ # Get proficiency for a specific strategy.
82
+ def skill_for(strategy)
83
+ @skill.fetch(strategy, DEFAULT_SKILL)
84
+ end
85
+
86
+ # Weighted average of all strategy skills.
87
+ # Effectiveness-weighted so higher-value strategies contribute more.
88
+ def overall_regulation_ability
89
+ total_weight = STRATEGIES.sum { |s| STRATEGY_EFFECTIVENESS[s] }
90
+ weighted_sum = STRATEGIES.sum { |s| @skill[s] * STRATEGY_EFFECTIVENESS[s] }
91
+ weighted_sum / total_weight
92
+ end
93
+
94
+ # Human-readable label for overall regulation ability.
95
+ def regulation_label
96
+ ability = overall_regulation_ability
97
+ REGULATION_LABELS.each do |range, label|
98
+ return label if range.cover?(ability)
99
+ end
100
+ :reactive
101
+ end
102
+
103
+ def to_h
104
+ {
105
+ skill: @skill.dup,
106
+ consecutive_suppressions: @consecutive_suppressions,
107
+ overall_ability: overall_regulation_ability,
108
+ regulation_label: regulation_label,
109
+ history_size: @regulation_history.size
110
+ }
111
+ end
112
+
113
+ private
114
+
115
+ def score_strategy(strategy, emotion_magnitude, _emotion_valence, context)
116
+ effectiveness = STRATEGY_EFFECTIVENESS[strategy]
117
+ cost = STRATEGY_COST[strategy]
118
+ proficiency = @skill[strategy]
119
+
120
+ base_score = (effectiveness * 0.5) + (proficiency * 0.3) - (cost * 0.2)
121
+
122
+ # Reappraisal bonus — healthiest long-term strategy
123
+ base_score += REAPPRAISAL_BONUS if strategy == :cognitive_reappraisal
124
+
125
+ # In high-magnitude situations penalise situation_selection (too late to avoid)
126
+ base_score -= 0.15 if strategy == :situation_selection && emotion_magnitude > 0.7
127
+
128
+ # Suppression penalty in long-running contexts
129
+ if strategy == :response_suppression
130
+ base_score -= 0.1 if context == :sustained
131
+ base_score -= ([@consecutive_suppressions, SUPPRESSION_PENALTY_THRESHOLD].min * 0.03)
132
+ end
133
+
134
+ base_score
135
+ end
136
+
137
+ def apply_suppression_penalty(strategy, effectiveness)
138
+ return effectiveness unless strategy == :response_suppression
139
+ return effectiveness if @consecutive_suppressions < SUPPRESSION_PENALTY_THRESHOLD
140
+
141
+ excess = @consecutive_suppressions - SUPPRESSION_PENALTY_THRESHOLD
142
+ penalty = [excess * 0.05, 0.2].min
143
+ [effectiveness - penalty, 0.05].max
144
+ end
145
+
146
+ def update_skill(strategy, success:)
147
+ current = @skill[strategy]
148
+ if success
149
+ bonus = strategy == :cognitive_reappraisal ? SKILL_GAIN * 1.2 : SKILL_GAIN
150
+ @skill[strategy] = [current + bonus, 1.0].min
151
+ else
152
+ @skill[strategy] = [current - (SKILL_GAIN * 0.5), 0.0].max
153
+ end
154
+ end
155
+
156
+ def track_suppression(strategy)
157
+ if strategy == :response_suppression
158
+ @consecutive_suppressions += 1
159
+ else
160
+ @consecutive_suppressions = 0
161
+ end
162
+ end
163
+
164
+ def record_event(data)
165
+ event = data.merge(timestamp: Time.now.utc)
166
+ @regulation_history << event
167
+ @regulation_history.shift while @regulation_history.size > MAX_REGULATION_HISTORY
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Regulation
8
+ module Runners
9
+ module EmotionalRegulation
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ # Apply emotion regulation. Auto-selects strategy when none is provided.
14
+ def regulate_emotion(emotion_magnitude:, emotion_valence: :neutral, strategy: nil, **)
15
+ chosen = strategy || regulation_model.recommend_strategy(
16
+ emotion_magnitude: emotion_magnitude,
17
+ emotion_valence: emotion_valence
18
+ )[:recommended]
19
+
20
+ result = regulation_model.regulate(
21
+ emotion_magnitude: emotion_magnitude,
22
+ emotion_valence: emotion_valence,
23
+ strategy: chosen
24
+ )
25
+
26
+ Legion::Logging.debug "[emotional_regulation] regulate: strategy=#{chosen} " \
27
+ "magnitude=#{emotion_magnitude.round(2)} -> " \
28
+ "#{result[:regulated_magnitude].round(2)} " \
29
+ "cost=#{result[:cost].round(3)} success=#{result[:success]}"
30
+
31
+ { success: true }.merge(result)
32
+ end
33
+
34
+ # Return a strategy recommendation without applying it.
35
+ def recommend_strategy(emotion_magnitude:, emotion_valence: :neutral, context: :general, **)
36
+ result = regulation_model.recommend_strategy(
37
+ emotion_magnitude: emotion_magnitude,
38
+ emotion_valence: emotion_valence,
39
+ context: context
40
+ )
41
+
42
+ Legion::Logging.debug "[emotional_regulation] recommend: magnitude=#{emotion_magnitude.round(2)} " \
43
+ "context=#{context} recommended=#{result[:recommended]}"
44
+
45
+ { success: true }.merge(result)
46
+ end
47
+
48
+ # Per-tick skill decay — call from scheduler or tick actor.
49
+ def update_emotional_regulation(**)
50
+ regulation_model.decay
51
+ ability = regulation_model.overall_regulation_ability
52
+ label = regulation_model.regulation_label
53
+
54
+ Legion::Logging.debug "[emotional_regulation] decay tick: ability=#{ability.round(3)} label=#{label}"
55
+
56
+ { success: true, overall_ability: ability, regulation_label: label }
57
+ end
58
+
59
+ # Return the full skill profile across all strategies.
60
+ def regulation_profile(**)
61
+ profile = Helpers::Constants::STRATEGIES.to_h do |strategy|
62
+ [strategy, regulation_model.skill_for(strategy)]
63
+ end
64
+
65
+ Legion::Logging.debug "[emotional_regulation] profile query: overall=#{regulation_model.overall_regulation_ability.round(3)}"
66
+
67
+ {
68
+ success: true,
69
+ skills: profile,
70
+ overall: regulation_model.overall_regulation_ability,
71
+ label: regulation_model.regulation_label,
72
+ suppressions: regulation_model.consecutive_suppressions
73
+ }
74
+ end
75
+
76
+ # Return recent regulation events.
77
+ def regulation_history(count: 20, **)
78
+ events = regulation_model.regulation_history.last(count)
79
+ Legion::Logging.debug "[emotional_regulation] history: requested=#{count} returned=#{events.size}"
80
+ { success: true, events: events, count: events.size }
81
+ end
82
+
83
+ # Return aggregate statistics about regulation performance.
84
+ def emotional_regulation_stats(**)
85
+ history = regulation_model.regulation_history
86
+ total = history.size
87
+
88
+ if total.zero?
89
+ return { success: true, total_events: 0, success_rate: 0.0,
90
+ average_cost: 0.0, strategy_breakdown: {},
91
+ overall_ability: regulation_model.overall_regulation_ability,
92
+ regulation_label: regulation_model.regulation_label }
93
+ end
94
+
95
+ successes = history.count { |e| e[:success] }
96
+ total_cost = history.sum { |e| e[:cost] }
97
+
98
+ strategy_breakdown = Helpers::Constants::STRATEGIES.to_h do |strategy|
99
+ events = history.select { |e| e[:strategy] == strategy }
100
+ [strategy, { count: events.size, successes: events.count { |e| e[:success] } }]
101
+ end
102
+
103
+ Legion::Logging.debug "[emotional_regulation] stats: total=#{total} success_rate=#{(successes.to_f / total).round(2)}"
104
+
105
+ {
106
+ success: true,
107
+ total_events: total,
108
+ success_rate: successes.to_f / total,
109
+ average_cost: total_cost / total,
110
+ strategy_breakdown: strategy_breakdown,
111
+ overall_ability: regulation_model.overall_regulation_ability,
112
+ regulation_label: regulation_model.regulation_label
113
+ }
114
+ end
115
+
116
+ private
117
+
118
+ def regulation_model
119
+ @regulation_model ||= Helpers::RegulationModel.new
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Regulation
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/regulation/version'
4
+ require 'legion/extensions/agentic/affect/regulation/helpers/constants'
5
+ require 'legion/extensions/agentic/affect/regulation/helpers/regulation_model'
6
+ require 'legion/extensions/agentic/affect/regulation/runners/emotional_regulation'
7
+ require 'legion/extensions/agentic/affect/regulation/client'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Agentic
12
+ module Affect
13
+ module Regulation
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/resilience/helpers/constants'
4
+ require 'legion/extensions/agentic/affect/resilience/helpers/adversity_tracker'
5
+ require 'legion/extensions/agentic/affect/resilience/helpers/resilience_model'
6
+ require 'legion/extensions/agentic/affect/resilience/runners/resilience'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Agentic
11
+ module Affect
12
+ module Resilience
13
+ class Client
14
+ include Runners::Resilience
15
+
16
+ attr_reader :adversity_tracker, :resilience_model
17
+
18
+ def initialize(adversity_tracker: nil, resilience_model: nil, **)
19
+ @adversity_tracker = adversity_tracker || Helpers::AdversityTracker.new
20
+ @resilience_model = resilience_model || Helpers::ResilienceModel.new
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Resilience
8
+ module Helpers
9
+ class AdversityTracker
10
+ attr_reader :active_adversities, :resolved_adversities, :consecutive_recoveries
11
+
12
+ def initialize
13
+ @active_adversities = []
14
+ @resolved_adversities = []
15
+ @consecutive_recoveries = 0
16
+ @adversity_counter = 0
17
+ end
18
+
19
+ def register(type:, severity:, context: {})
20
+ return nil unless Constants::ADVERSITY_TYPES.include?(type)
21
+ return nil unless Constants::SEVERITY_LEVELS.key?(severity)
22
+
23
+ @adversity_counter += 1
24
+ severity_config = Constants::SEVERITY_LEVELS[severity]
25
+
26
+ adversity = {
27
+ id: @adversity_counter,
28
+ type: type,
29
+ severity: severity,
30
+ impact: severity_config[:impact],
31
+ expected_ticks: severity_config[:recovery_ticks],
32
+ phase: :absorbing,
33
+ health_at_onset: 1.0,
34
+ current_health: 1.0 - severity_config[:impact],
35
+ ticks_elapsed: 0,
36
+ context: context,
37
+ registered_at: Time.now.utc
38
+ }
39
+
40
+ @active_adversities << adversity
41
+ trim_active
42
+ adversity
43
+ end
44
+
45
+ def tick_recovery
46
+ @active_adversities.each do |adv|
47
+ adv[:ticks_elapsed] += 1
48
+ advance_phase(adv)
49
+ recover_health(adv)
50
+ end
51
+
52
+ newly_resolved = @active_adversities.select { |a| a[:current_health] >= Constants::RECOVERY_THRESHOLD }
53
+ newly_resolved.each { |a| resolve(a) }
54
+
55
+ {
56
+ active_count: @active_adversities.size,
57
+ resolved_count: newly_resolved.size,
58
+ worst_health: worst_health
59
+ }
60
+ end
61
+
62
+ def worst_health
63
+ return 1.0 if @active_adversities.empty?
64
+
65
+ @active_adversities.map { |a| a[:current_health] }.min
66
+ end
67
+
68
+ def active_by_type
69
+ @active_adversities.group_by { |a| a[:type] }.transform_values(&:size)
70
+ end
71
+
72
+ def recovery_rate
73
+ total = @resolved_adversities.size
74
+ return 0.0 if total.zero?
75
+
76
+ on_time = @resolved_adversities.count { |a| a[:ticks_elapsed] <= a[:expected_ticks] }
77
+ on_time.to_f / total
78
+ end
79
+
80
+ def average_recovery_speed
81
+ return 0.0 if @resolved_adversities.empty?
82
+
83
+ ratios = @resolved_adversities.map { |a| a[:ticks_elapsed].to_f / [a[:expected_ticks], 1].max }
84
+ ratios.sum / ratios.size.to_f
85
+ end
86
+
87
+ def total_adversities
88
+ @active_adversities.size + @resolved_adversities.size
89
+ end
90
+
91
+ private
92
+
93
+ def advance_phase(adv)
94
+ progress = adv[:current_health]
95
+ adv[:phase] = if progress < 0.3
96
+ :absorbing
97
+ elsif progress < 0.6
98
+ :adapting
99
+ elsif progress < Constants::RECOVERY_THRESHOLD
100
+ :recovering
101
+ else
102
+ :thriving
103
+ end
104
+ end
105
+
106
+ def recover_health(adv)
107
+ recovery_rate = 1.0 / [adv[:expected_ticks], 1].max
108
+ adv[:current_health] = [adv[:current_health] + recovery_rate, 1.0].min
109
+ end
110
+
111
+ def resolve(adv)
112
+ adv[:phase] = :thriving
113
+ adv[:resolved_at] = Time.now.utc
114
+ @active_adversities.delete(adv)
115
+ @resolved_adversities << adv
116
+ @resolved_adversities.shift while @resolved_adversities.size > Constants::MAX_RESILIENCE_HISTORY
117
+
118
+ @consecutive_recoveries += 1
119
+ end
120
+
121
+ def trim_active
122
+ @active_adversities.shift while @active_adversities.size > Constants::MAX_ACTIVE_ADVERSITIES
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Resilience
8
+ module Helpers
9
+ module Constants
10
+ # Adversity types the resilience system tracks
11
+ ADVERSITY_TYPES = %i[
12
+ prediction_failure
13
+ trust_violation
14
+ conflict_escalation
15
+ resource_depletion
16
+ communication_failure
17
+ goal_failure
18
+ emotional_shock
19
+ system_error
20
+ ].freeze
21
+
22
+ # Recovery phases (Masten's resilience model)
23
+ RECOVERY_PHASES = %i[
24
+ absorbing
25
+ adapting
26
+ recovering
27
+ thriving
28
+ ].freeze
29
+
30
+ # Resilience dimensions
31
+ DIMENSIONS = {
32
+ elasticity: { description: 'Speed of recovery to baseline', weight: 0.30 },
33
+ robustness: { description: 'Resistance to initial disruption', weight: 0.25 },
34
+ adaptability: { description: 'Capacity to adjust strategy', weight: 0.25 },
35
+ growth: { description: 'Ability to improve from adversity', weight: 0.20 }
36
+ }.freeze
37
+
38
+ # EMA alpha for resilience dimension tracking
39
+ RESILIENCE_ALPHA = 0.08
40
+
41
+ # Growth bonus per successful recovery
42
+ GROWTH_INCREMENT = 0.02
43
+
44
+ # Maximum growth bonus (anti-fragile ceiling)
45
+ MAX_GROWTH_BONUS = 0.3
46
+
47
+ # Adversity severity levels
48
+ SEVERITY_LEVELS = {
49
+ minor: { impact: 0.1, recovery_ticks: 5 },
50
+ moderate: { impact: 0.3, recovery_ticks: 15 },
51
+ major: { impact: 0.5, recovery_ticks: 30 },
52
+ severe: { impact: 0.8, recovery_ticks: 60 },
53
+ critical: { impact: 1.0, recovery_ticks: 100 }
54
+ }.freeze
55
+
56
+ # Threshold for considering recovery complete
57
+ RECOVERY_THRESHOLD = 0.9
58
+
59
+ # Maximum active adversity events tracked
60
+ MAX_ACTIVE_ADVERSITIES = 20
61
+
62
+ # History cap
63
+ MAX_RESILIENCE_HISTORY = 200
64
+
65
+ # Fragility threshold — below this, the system is fragile
66
+ FRAGILITY_THRESHOLD = 0.3
67
+
68
+ # Anti-fragility threshold — above this, the system grows from stress
69
+ ANTIFRAGILITY_THRESHOLD = 0.7
70
+
71
+ # Consecutive recoveries needed to boost growth dimension
72
+ GROWTH_TRIGGER = 3
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end