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,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Fatigue
8
+ module Helpers
9
+ class EnergyModel
10
+ include Constants
11
+
12
+ attr_reader :energy, :fatigue_level, :ticks_active, :ticks_resting,
13
+ :consecutive_low_ticks, :burnout, :recovery_mode,
14
+ :peak_energy, :history
15
+
16
+ def initialize
17
+ @energy = Constants::MAX_ENERGY
18
+ @fatigue_level = :fresh
19
+ @ticks_active = 0
20
+ @ticks_resting = 0
21
+ @consecutive_low_ticks = 0
22
+ @burnout = false
23
+ @recovery_mode = nil
24
+ @peak_energy = Constants::MAX_ENERGY
25
+ @history = []
26
+ end
27
+
28
+ def tick(cognitive_load: 0.5, emotional_arousal: 0.5, is_resting: false)
29
+ if is_resting
30
+ recover
31
+ @ticks_resting += 1
32
+ @consecutive_low_ticks = 0
33
+ else
34
+ drain(cognitive_load, emotional_arousal)
35
+ @ticks_active += 1
36
+ check_second_wind
37
+ end
38
+
39
+ @energy = @energy.clamp(Constants::MIN_ENERGY, Constants::MAX_ENERGY)
40
+ @peak_energy = @energy if @energy > @peak_energy
41
+ classify_fatigue
42
+ track_low_ticks
43
+ check_burnout
44
+ record_snapshot
45
+
46
+ to_h
47
+ end
48
+
49
+ def performance_factor
50
+ Constants::PERFORMANCE_DEGRADATION[@fatigue_level]
51
+ end
52
+
53
+ def needs_rest?
54
+ @energy < Constants::REST_THRESHOLD
55
+ end
56
+
57
+ def critically_fatigued?
58
+ @energy < Constants::CRITICAL_THRESHOLD
59
+ end
60
+
61
+ def burnout?
62
+ @burnout
63
+ end
64
+
65
+ def enter_recovery(mode)
66
+ return unless Constants::RECOVERY_MODES.include?(mode)
67
+
68
+ @recovery_mode = mode
69
+ end
70
+
71
+ def exit_recovery
72
+ @recovery_mode = nil
73
+ end
74
+
75
+ def time_to_rest_threshold
76
+ return 0 if needs_rest?
77
+
78
+ current_drain = effective_drain(0.5, 0.5)
79
+ return Float::INFINITY if current_drain <= 0.0
80
+
81
+ ((@energy - Constants::REST_THRESHOLD) / current_drain).ceil
82
+ end
83
+
84
+ def time_to_full_recovery
85
+ rate = @recovery_mode ? Constants::RECOVERY_RATES[@recovery_mode] : Constants::RESTING_RECOVERY_RATE
86
+ return 0 if @energy >= Constants::MAX_ENERGY
87
+ return Float::INFINITY if rate <= 0.0
88
+
89
+ ((Constants::MAX_ENERGY - @energy) / rate).ceil
90
+ end
91
+
92
+ def trend
93
+ return :stable if @history.size < 5
94
+
95
+ recent = @history.last(5).map { |s| s[:energy] }
96
+ delta = recent.last - recent.first
97
+ if delta > 0.01
98
+ :recovering
99
+ elsif delta < -0.01
100
+ :draining
101
+ else
102
+ :stable
103
+ end
104
+ end
105
+
106
+ def to_h
107
+ {
108
+ energy: @energy.round(4),
109
+ fatigue_level: @fatigue_level,
110
+ performance_factor: performance_factor,
111
+ needs_rest: needs_rest?,
112
+ critically_fatigued: critically_fatigued?,
113
+ burnout: @burnout,
114
+ recovery_mode: @recovery_mode,
115
+ peak_energy: @peak_energy.round(4),
116
+ ticks_active: @ticks_active,
117
+ ticks_resting: @ticks_resting,
118
+ consecutive_low_ticks: @consecutive_low_ticks,
119
+ trend: trend,
120
+ history_size: @history.size
121
+ }
122
+ end
123
+
124
+ private
125
+
126
+ def effective_drain(cognitive_load, emotional_arousal)
127
+ cognitive_factor = 1.0 + ((cognitive_load - 0.5) * Constants::COGNITIVE_DRAIN_MULTIPLIER)
128
+ emotional_factor = 1.0 + ([0.0, emotional_arousal - 0.5].max * Constants::EMOTIONAL_DRAIN_MULTIPLIER)
129
+ Constants::ACTIVE_DRAIN_RATE * cognitive_factor * emotional_factor
130
+ end
131
+
132
+ def drain(cognitive_load, emotional_arousal)
133
+ @energy -= effective_drain(cognitive_load, emotional_arousal)
134
+ end
135
+
136
+ def recover
137
+ rate = @recovery_mode ? Constants::RECOVERY_RATES[@recovery_mode] : Constants::RESTING_RECOVERY_RATE
138
+ @energy += rate
139
+ end
140
+
141
+ def classify_fatigue
142
+ @fatigue_level = Constants::FATIGUE_LEVELS.each_key.find do |level|
143
+ @energy >= Constants::FATIGUE_LEVELS[level]
144
+ end || :depleted
145
+ end
146
+
147
+ def track_low_ticks
148
+ if needs_rest?
149
+ @consecutive_low_ticks += 1
150
+ else
151
+ @consecutive_low_ticks = 0
152
+ end
153
+ end
154
+
155
+ def check_second_wind
156
+ return unless @fatigue_level == :tired
157
+ return unless rand < Constants::SECOND_WIND_CHANCE
158
+
159
+ @energy = [@energy + 0.1, Constants::MAX_ENERGY].min
160
+ end
161
+
162
+ def check_burnout
163
+ @burnout = true if @consecutive_low_ticks > Constants::BURNOUT_THRESHOLD
164
+ end
165
+
166
+ def record_snapshot
167
+ @history << {
168
+ energy: @energy.round(4),
169
+ fatigue_level: @fatigue_level,
170
+ performance: performance_factor,
171
+ at: Time.now.utc
172
+ }
173
+ @history.shift if @history.size > Constants::MAX_HISTORY
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Fatigue
8
+ module Helpers
9
+ class FatigueStore
10
+ include Constants
11
+
12
+ attr_reader :model, :session_start, :peak_performance_streak,
13
+ :total_rest_ticks, :total_active_ticks
14
+
15
+ def initialize(model: nil)
16
+ @model = model || EnergyModel.new
17
+ @session_start = Time.now.utc
18
+ @peak_performance_streak = 0
19
+ @total_rest_ticks = 0
20
+ @total_active_ticks = 0
21
+ @current_streak = 0
22
+ end
23
+
24
+ def update(tick_results: {})
25
+ cognitive_load = tick_results[:cognitive_load] || 0.5
26
+ emotional_arousal = tick_results[:emotional_arousal] || 0.5
27
+ is_resting = tick_results[:mode] == :resting || !@model.recovery_mode.nil?
28
+
29
+ result = @model.tick(
30
+ cognitive_load: cognitive_load,
31
+ emotional_arousal: emotional_arousal,
32
+ is_resting: is_resting
33
+ )
34
+
35
+ if is_resting
36
+ @total_rest_ticks += 1
37
+ else
38
+ @total_active_ticks += 1
39
+ end
40
+
41
+ update_streak(result[:performance_factor])
42
+ result
43
+ end
44
+
45
+ def recommend_action
46
+ energy = @model.energy
47
+
48
+ if @model.burnout?
49
+ :emergency_shutdown
50
+ elsif energy < Constants::CRITICAL_THRESHOLD
51
+ :enter_rest
52
+ elsif @model.needs_rest?
53
+ :take_break
54
+ elsif @model.fatigue_level == :tired
55
+ :reduce_load
56
+ else
57
+ :continue
58
+ end
59
+ end
60
+
61
+ def session_stats
62
+ total_ticks = @total_active_ticks + @total_rest_ticks
63
+ duration = Time.now.utc - @session_start
64
+
65
+ {
66
+ duration_seconds: duration.round(2),
67
+ total_ticks: total_ticks,
68
+ active_ticks: @total_active_ticks,
69
+ rest_ticks: @total_rest_ticks,
70
+ active_ratio: total_ticks.positive? ? (@total_active_ticks.to_f / total_ticks).round(4) : 0.0,
71
+ current_energy: @model.energy.round(4),
72
+ fatigue_level: @model.fatigue_level,
73
+ burnout: @model.burnout?,
74
+ peak_performance_streak: @peak_performance_streak
75
+ }
76
+ end
77
+
78
+ def energy_forecast(ticks:)
79
+ current = @model.energy
80
+ drain_rate = Constants::ACTIVE_DRAIN_RATE
81
+ projected = []
82
+
83
+ ticks.times do |i|
84
+ projected_energy = (current - (drain_rate * (i + 1))).clamp(Constants::MIN_ENERGY, Constants::MAX_ENERGY)
85
+ level = classify_level(projected_energy)
86
+ projected << {
87
+ tick: i + 1,
88
+ energy: projected_energy.round(4),
89
+ fatigue_level: level
90
+ }
91
+ end
92
+
93
+ {
94
+ current_energy: current.round(4),
95
+ forecast: projected,
96
+ ticks_to_rest: @model.time_to_rest_threshold
97
+ }
98
+ end
99
+
100
+ def optimal_rest_schedule
101
+ ticks_until_rest = @model.time_to_rest_threshold
102
+ ticks_to_recover = @model.time_to_full_recovery
103
+
104
+ {
105
+ recommend_rest_in: ticks_until_rest,
106
+ full_recovery_ticks: ticks_to_recover,
107
+ current_energy: @model.energy.round(4),
108
+ recommended_mode: suggest_recovery_mode,
109
+ trend: @model.trend
110
+ }
111
+ end
112
+
113
+ private
114
+
115
+ def classify_level(energy)
116
+ Constants::FATIGUE_LEVELS.each_key.find do |level|
117
+ energy >= Constants::FATIGUE_LEVELS[level]
118
+ end || :depleted
119
+ end
120
+
121
+ def update_streak(performance_factor)
122
+ if performance_factor >= Constants::PERFORMANCE_DEGRADATION[:fresh]
123
+ @current_streak += 1
124
+ @peak_performance_streak = @current_streak if @current_streak > @peak_performance_streak
125
+ else
126
+ @current_streak = 0
127
+ end
128
+ end
129
+
130
+ def suggest_recovery_mode
131
+ energy = @model.energy
132
+ if energy < Constants::CRITICAL_THRESHOLD
133
+ :sleep
134
+ elsif energy < Constants::REST_THRESHOLD
135
+ :full_rest
136
+ else
137
+ :active_rest
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Fatigue
8
+ module Runners
9
+ module Fatigue
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ def update_fatigue(tick_results: {}, **)
14
+ result = fatigue_store.update(tick_results: tick_results)
15
+ recommendation = fatigue_store.recommend_action
16
+
17
+ Legion::Logging.debug "[fatigue] tick: energy=#{result[:energy]} level=#{result[:fatigue_level]} " \
18
+ "perf=#{result[:performance_factor]} rec=#{recommendation}"
19
+
20
+ {
21
+ energy: result[:energy],
22
+ fatigue_level: result[:fatigue_level],
23
+ performance_factor: result[:performance_factor],
24
+ recommendation: recommendation,
25
+ needs_rest: result[:needs_rest],
26
+ burnout: result[:burnout]
27
+ }
28
+ end
29
+
30
+ def energy_status(**)
31
+ model = fatigue_store.model
32
+ Legion::Logging.debug "[fatigue] status: energy=#{model.energy.round(3)} level=#{model.fatigue_level}"
33
+
34
+ {
35
+ energy: model.energy.round(4),
36
+ fatigue_level: model.fatigue_level,
37
+ performance_factor: model.performance_factor,
38
+ needs_rest: model.needs_rest?,
39
+ critically_fatigued: model.critically_fatigued?,
40
+ burnout: model.burnout?,
41
+ recovery_mode: model.recovery_mode,
42
+ trend: model.trend
43
+ }
44
+ end
45
+
46
+ def enter_rest(mode: :full_rest, **)
47
+ return { success: false, error: "unknown recovery mode: #{mode}" } unless Helpers::Constants::RECOVERY_MODES.include?(mode)
48
+
49
+ fatigue_store.model.enter_recovery(mode)
50
+ Legion::Logging.info "[fatigue] entered recovery mode=#{mode}"
51
+ { success: true, mode: mode, energy: fatigue_store.model.energy.round(4) }
52
+ end
53
+
54
+ def exit_rest(**)
55
+ fatigue_store.model.exit_recovery
56
+ Legion::Logging.info '[fatigue] exited recovery mode'
57
+ { success: true, energy: fatigue_store.model.energy.round(4), fatigue_level: fatigue_store.model.fatigue_level }
58
+ end
59
+
60
+ def energy_forecast(ticks: 50, **)
61
+ Legion::Logging.debug "[fatigue] forecasting #{ticks} ticks"
62
+ fatigue_store.energy_forecast(ticks: ticks)
63
+ end
64
+
65
+ def fatigue_stats(**)
66
+ stats = fatigue_store.session_stats
67
+ model = fatigue_store.model
68
+ Legion::Logging.debug "[fatigue] stats: ticks=#{stats[:total_ticks]} burnout=#{stats[:burnout]}"
69
+
70
+ {
71
+ session: stats,
72
+ history: model.history.last(10),
73
+ trend: model.trend,
74
+ schedule: fatigue_store.optimal_rest_schedule
75
+ }
76
+ end
77
+
78
+ private
79
+
80
+ def fatigue_store
81
+ @fatigue_store ||= Helpers::FatigueStore.new
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ 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 Fatigue
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/fatigue/version'
4
+ require 'legion/extensions/agentic/affect/fatigue/helpers/constants'
5
+ require 'legion/extensions/agentic/affect/fatigue/helpers/energy_model'
6
+ require 'legion/extensions/agentic/affect/fatigue/helpers/fatigue_store'
7
+ require 'legion/extensions/agentic/affect/fatigue/runners/fatigue'
8
+ require 'legion/extensions/agentic/affect/fatigue/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module Agentic
13
+ module Affect
14
+ module Fatigue
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/flow/helpers/constants'
4
+ require 'legion/extensions/agentic/affect/flow/helpers/flow_detector'
5
+ require 'legion/extensions/agentic/affect/flow/runners/flow'
6
+
7
+ module Legion
8
+ module Extensions
9
+ module Agentic
10
+ module Affect
11
+ module Flow
12
+ class Client
13
+ include Runners::Flow
14
+
15
+ attr_reader :flow_detector
16
+
17
+ def initialize(flow_detector: nil, **)
18
+ @flow_detector = flow_detector || Helpers::FlowDetector.new
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Flow
8
+ module Helpers
9
+ module Constants
10
+ # Flow states (Csikszentmihalyi's model)
11
+ FLOW_STATES = %i[
12
+ flow
13
+ arousal
14
+ control
15
+ relaxation
16
+ boredom
17
+ apathy
18
+ worry
19
+ anxiety
20
+ ].freeze
21
+
22
+ # Input dimensions for flow detection
23
+ DIMENSIONS = %i[challenge skill].freeze
24
+
25
+ # Flow zone boundaries (challenge-skill space)
26
+ # Flow occurs when challenge ~= skill and both are moderate-to-high
27
+ FLOW_ZONE = {
28
+ challenge_min: 0.4,
29
+ challenge_max: 0.8,
30
+ skill_min: 0.4,
31
+ skill_max: 0.8,
32
+ balance_tolerance: 0.15
33
+ }.freeze
34
+
35
+ # EMA alpha for challenge/skill tracking
36
+ FLOW_ALPHA = 0.15
37
+
38
+ # Minimum ticks in flow before "deep flow" bonus
39
+ DEEP_FLOW_THRESHOLD = 20
40
+
41
+ # Flow score bonuses
42
+ DEEP_FLOW_BONUS = 0.1
43
+ CURIOSITY_BONUS = 0.05
44
+ LOW_ERROR_BONUS = 0.05
45
+
46
+ # Flow effects on other subsystems
47
+ FLOW_EFFECTS = {
48
+ fatigue_reduction: 0.5, # fatigue drains 50% slower in flow
49
+ time_dilation: 0.7, # subjective time flies (rate < 1.0)
50
+ performance_boost: 1.15, # 15% performance improvement
51
+ attention_broadening: 0.8, # attention threshold lowered (more open)
52
+ creativity_boost: 1.2 # curiosity/imagination amplified
53
+ }.freeze
54
+
55
+ # Anti-flow indicators (break flow state)
56
+ FLOW_BREAKERS = %i[
57
+ high_anxiety
58
+ trust_violation
59
+ critical_error
60
+ burnout
61
+ conflict_escalation
62
+ ].freeze
63
+
64
+ # History cap
65
+ MAX_FLOW_HISTORY = 100
66
+
67
+ # State classification regions in challenge-skill space
68
+ STATE_REGIONS = {
69
+ flow: { challenge: (0.4..0.8), skill: (0.4..0.8), balanced: true },
70
+ arousal: { challenge: (0.6..1.0), skill: (0.3..0.6), balanced: false },
71
+ control: { challenge: (0.2..0.5), skill: (0.5..0.8), balanced: false },
72
+ relaxation: { challenge: (0.1..0.4), skill: (0.4..0.7), balanced: false },
73
+ boredom: { challenge: (0.0..0.3), skill: (0.5..1.0), balanced: false },
74
+ apathy: { challenge: (0.0..0.3), skill: (0.0..0.3), balanced: false },
75
+ worry: { challenge: (0.5..0.8), skill: (0.1..0.4), balanced: false },
76
+ anxiety: { challenge: (0.7..1.0), skill: (0.0..0.4), balanced: false }
77
+ }.freeze
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end