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,166 @@
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
+ class FlowDetector
10
+ attr_reader :challenge, :skill, :flow_state, :flow_score,
11
+ :consecutive_flow_ticks, :total_flow_ticks, :history
12
+
13
+ def initialize
14
+ @challenge = 0.5
15
+ @skill = 0.5
16
+ @flow_state = :relaxation
17
+ @flow_score = 0.0
18
+ @consecutive_flow_ticks = 0
19
+ @total_flow_ticks = 0
20
+ @history = []
21
+ end
22
+
23
+ def update(challenge_input:, skill_input:, modifiers: {})
24
+ @challenge = ema(@challenge, challenge_input.clamp(0.0, 1.0), Constants::FLOW_ALPHA)
25
+ @skill = ema(@skill, skill_input.clamp(0.0, 1.0), Constants::FLOW_ALPHA)
26
+
27
+ @flow_state = classify_state
28
+ @flow_score = compute_flow_score(modifiers)
29
+
30
+ if @flow_state == :flow
31
+ @consecutive_flow_ticks += 1
32
+ @total_flow_ticks += 1
33
+ else
34
+ @consecutive_flow_ticks = 0
35
+ end
36
+
37
+ record_snapshot
38
+ end
39
+
40
+ def in_flow?
41
+ @flow_state == :flow
42
+ end
43
+
44
+ def deep_flow?
45
+ in_flow? && @consecutive_flow_ticks >= Constants::DEEP_FLOW_THRESHOLD
46
+ end
47
+
48
+ def flow_effects
49
+ if in_flow?
50
+ effects = Constants::FLOW_EFFECTS.dup
51
+ if deep_flow?
52
+ effects[:performance_boost] += 0.05
53
+ effects[:creativity_boost] += 0.1
54
+ end
55
+ effects
56
+ else
57
+ { fatigue_reduction: 1.0, time_dilation: 1.0, performance_boost: 1.0,
58
+ attention_broadening: 1.0, creativity_boost: 1.0 }
59
+ end
60
+ end
61
+
62
+ def challenge_skill_balance
63
+ (@challenge - @skill).abs
64
+ end
65
+
66
+ def flow_trend
67
+ return :insufficient_data if @history.size < 5
68
+
69
+ recent = @history.last(10)
70
+ scores = recent.map { |h| h[:flow_score] }
71
+ first_half = scores[0...(scores.size / 2)]
72
+ second_half = scores[(scores.size / 2)..]
73
+ diff = (second_half.sum / second_half.size.to_f) - (first_half.sum / first_half.size.to_f)
74
+
75
+ if diff > 0.05
76
+ :entering_flow
77
+ elsif diff < -0.05
78
+ :leaving_flow
79
+ else
80
+ :stable
81
+ end
82
+ end
83
+
84
+ def flow_percentage
85
+ return 0.0 if @history.empty?
86
+
87
+ flow_count = @history.count { |h| h[:state] == :flow }
88
+ (flow_count.to_f / @history.size * 100).round(1)
89
+ end
90
+
91
+ def to_h
92
+ {
93
+ state: @flow_state,
94
+ score: @flow_score.round(3),
95
+ challenge: @challenge.round(3),
96
+ skill: @skill.round(3),
97
+ balance: challenge_skill_balance.round(3),
98
+ in_flow: in_flow?,
99
+ deep_flow: deep_flow?,
100
+ consecutive_flow_ticks: @consecutive_flow_ticks,
101
+ total_flow_ticks: @total_flow_ticks,
102
+ flow_percentage: flow_percentage,
103
+ trend: flow_trend,
104
+ effects: flow_effects
105
+ }
106
+ end
107
+
108
+ private
109
+
110
+ def classify_state
111
+ best_state = :apathy
112
+ best_score = -1.0
113
+
114
+ Constants::STATE_REGIONS.each do |state, region|
115
+ next unless region[:challenge].cover?(@challenge) && region[:skill].cover?(@skill)
116
+
117
+ score = region[:balanced] ? balance_bonus : 0.5
118
+ if score > best_score
119
+ best_score = score
120
+ best_state = state
121
+ end
122
+ end
123
+
124
+ best_state
125
+ end
126
+
127
+ def balance_bonus
128
+ balance = challenge_skill_balance
129
+ balance <= Constants::FLOW_ZONE[:balance_tolerance] ? 1.0 : 0.7
130
+ end
131
+
132
+ def compute_flow_score(modifiers)
133
+ base = if in_flow?
134
+ 0.7 + ((1.0 - challenge_skill_balance) * 0.3)
135
+ else
136
+ [0.0, 0.5 - (challenge_skill_balance * 0.5)].max
137
+ end
138
+
139
+ base += Constants::DEEP_FLOW_BONUS if deep_flow?
140
+ base += Constants::CURIOSITY_BONUS if modifiers[:curiosity_active]
141
+ base += Constants::LOW_ERROR_BONUS if modifiers[:low_errors]
142
+
143
+ base.clamp(0.0, 1.0)
144
+ end
145
+
146
+ def ema(current, observed, alpha)
147
+ (current * (1.0 - alpha)) + (observed * alpha)
148
+ end
149
+
150
+ def record_snapshot
151
+ @history << {
152
+ state: @flow_state,
153
+ flow_score: @flow_score,
154
+ challenge: @challenge,
155
+ skill: @skill,
156
+ at: Time.now.utc
157
+ }
158
+ @history.shift while @history.size > Constants::MAX_FLOW_HISTORY
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Flow
8
+ module Runners
9
+ module Flow
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ def update_flow(tick_results: {}, **)
14
+ challenge_input = extract_challenge(tick_results)
15
+ skill_input = extract_skill(tick_results)
16
+ modifiers = extract_modifiers(tick_results)
17
+
18
+ flow_detector.update(challenge_input: challenge_input, skill_input: skill_input, modifiers: modifiers)
19
+
20
+ breakers = detect_flow_breakers(tick_results)
21
+ if breakers.any? && flow_detector.in_flow?
22
+ flow_detector.instance_variable_set(:@flow_state, :disrupted)
23
+ flow_detector.instance_variable_set(:@consecutive_flow_ticks, 0)
24
+ end
25
+
26
+ Legion::Logging.debug "[flow] state=#{flow_detector.flow_state} score=#{flow_detector.flow_score.round(3)} " \
27
+ "deep=#{flow_detector.deep_flow?} breakers=#{breakers}"
28
+
29
+ {
30
+ state: flow_detector.flow_state,
31
+ score: flow_detector.flow_score.round(3),
32
+ in_flow: flow_detector.in_flow?,
33
+ deep_flow: flow_detector.deep_flow?,
34
+ effects: flow_detector.flow_effects,
35
+ breakers: breakers,
36
+ challenge: flow_detector.challenge.round(3),
37
+ skill: flow_detector.skill.round(3)
38
+ }
39
+ end
40
+
41
+ def flow_status(**)
42
+ Legion::Logging.debug "[flow] status: state=#{flow_detector.flow_state} score=#{flow_detector.flow_score.round(3)}"
43
+ flow_detector.to_h
44
+ end
45
+
46
+ def flow_effects(**)
47
+ effects = flow_detector.flow_effects
48
+ Legion::Logging.debug "[flow] effects: #{effects}"
49
+ { effects: effects, in_flow: flow_detector.in_flow?, deep_flow: flow_detector.deep_flow? }
50
+ end
51
+
52
+ def flow_history(limit: 20, **)
53
+ recent = flow_detector.history.last(limit)
54
+ Legion::Logging.debug "[flow] history: #{recent.size} entries"
55
+ { history: recent, total: flow_detector.history.size }
56
+ end
57
+
58
+ def flow_stats(**)
59
+ Legion::Logging.debug '[flow] stats'
60
+ {
61
+ state: flow_detector.flow_state,
62
+ score: flow_detector.flow_score.round(3),
63
+ consecutive_flow_ticks: flow_detector.consecutive_flow_ticks,
64
+ total_flow_ticks: flow_detector.total_flow_ticks,
65
+ flow_percentage: flow_detector.flow_percentage,
66
+ trend: flow_detector.flow_trend,
67
+ balance: flow_detector.challenge_skill_balance.round(3)
68
+ }
69
+ end
70
+
71
+ private
72
+
73
+ def flow_detector
74
+ @flow_detector ||= Helpers::FlowDetector.new
75
+ end
76
+
77
+ def extract_challenge(tick_results)
78
+ prediction_accuracy = tick_results.dig(:prediction_engine, :rolling_accuracy) || 0.5
79
+ error_rate = tick_results.dig(:prediction_engine, :error_rate) || 0.5
80
+ task_complexity = tick_results.dig(:action_selection, :complexity) || 0.5
81
+
82
+ ((1.0 - prediction_accuracy) * 0.4) + (error_rate * 0.3) + (task_complexity * 0.3)
83
+ end
84
+
85
+ def extract_skill(tick_results)
86
+ prediction_accuracy = tick_results.dig(:prediction_engine, :rolling_accuracy) || 0.5
87
+ memory_strength = tick_results.dig(:memory_retrieval, :avg_strength) || 0.5
88
+ habit_automation = tick_results.dig(:habit, :automation_level) || 0.5
89
+
90
+ (prediction_accuracy * 0.4) + (memory_strength * 0.3) + (habit_automation * 0.3)
91
+ end
92
+
93
+ def extract_modifiers(tick_results)
94
+ {
95
+ curiosity_active: (tick_results.dig(:curiosity, :intensity) || 0.0) > 0.5,
96
+ low_errors: (tick_results.dig(:prediction_engine, :error_rate) || 1.0) < 0.2
97
+ }
98
+ end
99
+
100
+ def detect_flow_breakers(tick_results)
101
+ breakers = []
102
+ check_anxiety_breaker(tick_results, breakers)
103
+ check_simple_breakers(tick_results, breakers)
104
+ check_conflict_breaker(tick_results, breakers)
105
+ breakers
106
+ end
107
+
108
+ def check_anxiety_breaker(tick_results, breakers)
109
+ anxiety = tick_results.dig(:emotional_evaluation, :anxiety) || 0.0
110
+ breakers << :high_anxiety if anxiety > 0.8
111
+ end
112
+
113
+ def check_simple_breakers(tick_results, breakers)
114
+ breakers << :trust_violation if tick_results.dig(:trust, :violation)
115
+ breakers << :critical_error if tick_results.dig(:error, :critical)
116
+ breakers << :burnout if tick_results.dig(:fatigue, :burnout)
117
+ end
118
+
119
+ def check_conflict_breaker(tick_results, breakers)
120
+ conflict = tick_results.dig(:conflict, :severity)
121
+ breakers << :conflict_escalation if conflict && conflict >= 4
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ 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 Flow
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/flow/version'
4
+ require 'legion/extensions/agentic/affect/flow/helpers/constants'
5
+ require 'legion/extensions/agentic/affect/flow/helpers/flow_detector'
6
+ require 'legion/extensions/agentic/affect/flow/runners/flow'
7
+ require 'legion/extensions/agentic/affect/flow/client'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Agentic
12
+ module Affect
13
+ module Flow
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/actors/every'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Agentic
8
+ module Affect
9
+ module Interoception
10
+ module Actor
11
+ class Decay < Legion::Extensions::Actors::Every
12
+ def runner_class
13
+ Legion::Extensions::Agentic::Affect::Interoception::Runners::Interoception
14
+ end
15
+
16
+ def runner_function
17
+ 'update_interoception'
18
+ end
19
+
20
+ def time
21
+ 60
22
+ end
23
+
24
+ def run_now?
25
+ false
26
+ end
27
+
28
+ def use_runner?
29
+ false
30
+ end
31
+
32
+ def check_subtask?
33
+ false
34
+ end
35
+
36
+ def generate_task?
37
+ false
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/interoception/helpers/constants'
4
+ require 'legion/extensions/agentic/affect/interoception/helpers/somatic_marker'
5
+ require 'legion/extensions/agentic/affect/interoception/helpers/body_budget'
6
+ require 'legion/extensions/agentic/affect/interoception/runners/interoception'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Agentic
11
+ module Affect
12
+ module Interoception
13
+ class Client
14
+ include Runners::Interoception
15
+
16
+ def initialize(body_budget: nil, **)
17
+ @body_budget = body_budget || Helpers::BodyBudget.new
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :body_budget
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Interoception
8
+ module Helpers
9
+ class BodyBudget
10
+ include Constants
11
+
12
+ attr_reader :vitals, :baselines, :markers, :vital_history
13
+
14
+ def initialize
15
+ @vitals = {}
16
+ @baselines = {}
17
+ @markers = []
18
+ @vital_history = {}
19
+ end
20
+
21
+ # --- Vital Signal Tracking ---
22
+
23
+ def report_vital(channel:, value:)
24
+ channel = channel.to_sym
25
+ normalized = value.clamp(0.0, 1.0)
26
+ @baselines[channel] ||= DEFAULT_BASELINE
27
+ @vitals[channel] = if @vitals.key?(channel)
28
+ ema(@vitals[channel], normalized, VITAL_ALPHA)
29
+ else
30
+ normalized
31
+ end
32
+ record_vital_history(channel, @vitals[channel])
33
+ @baselines[channel] = ema(@baselines[channel], @vitals[channel], VITAL_ALPHA * 0.5)
34
+ @vitals[channel]
35
+ end
36
+
37
+ def vital_for(channel)
38
+ @vitals.fetch(channel.to_sym, DEFAULT_BASELINE)
39
+ end
40
+
41
+ def deviation_for(channel)
42
+ channel = channel.to_sym
43
+ current = @vitals.fetch(channel, DEFAULT_BASELINE)
44
+ baseline = @baselines.fetch(channel, DEFAULT_BASELINE)
45
+ current - baseline
46
+ end
47
+
48
+ def vital_label(channel)
49
+ health = vital_health(channel)
50
+ VITAL_LABELS.each { |range, lbl| return lbl if range.cover?(health) }
51
+ :nominal
52
+ end
53
+
54
+ def vital_health(channel)
55
+ val = vital_for(channel)
56
+ inverted_channels = %i[cpu_load memory_pressure queue_depth error_rate disk_usage gc_pressure]
57
+ inverted_channels.include?(channel.to_sym) ? 1.0 - val : val
58
+ end
59
+
60
+ def deviating_channels
61
+ @vitals.select { |ch, _| deviation_for(ch).abs >= DEVIATION_THRESHOLD }
62
+ .map { |ch, _| { channel: ch, deviation: deviation_for(ch).round(4), label: vital_label(ch) } }
63
+ end
64
+
65
+ # --- Somatic Markers ---
66
+
67
+ def create_marker(action:, domain:, valence:, strength: 1.0)
68
+ marker = SomaticMarker.new(action: action, domain: domain, valence: valence, strength: strength)
69
+ @markers << marker
70
+ prune_markers if @markers.size > MAX_MARKERS
71
+ marker
72
+ end
73
+
74
+ def markers_for(action:, domain: nil)
75
+ results = @markers.select { |m| m.action == action }
76
+ results = results.select { |m| m.domain == domain } if domain
77
+ results
78
+ end
79
+
80
+ def bias_for_action(action:, domain: nil)
81
+ relevant = markers_for(action: action, domain: domain)
82
+ return 0.0 if relevant.empty?
83
+
84
+ relevant.sum { |m| m.bias_for(action) } / relevant.size
85
+ end
86
+
87
+ def reinforce_markers(action:, domain: nil, amount: 0.1)
88
+ markers_for(action: action, domain: domain).each { |m| m.reinforce(amount: amount) }
89
+ end
90
+
91
+ def decay_markers
92
+ @markers.each(&:decay)
93
+ @markers.reject!(&:faded?)
94
+ end
95
+
96
+ # --- Body Budget Overview ---
97
+
98
+ def overall_health
99
+ return DEFAULT_BASELINE if @vitals.empty?
100
+
101
+ healths = @vitals.keys.map { |ch| vital_health(ch) }
102
+ healths.sum / healths.size
103
+ end
104
+
105
+ def body_budget_label
106
+ health = overall_health
107
+ BODY_BUDGET_LABELS.each { |range, lbl| return lbl if range.cover?(health) }
108
+ :comfortable
109
+ end
110
+
111
+ def channel_count
112
+ @vitals.size
113
+ end
114
+
115
+ def marker_count
116
+ @markers.size
117
+ end
118
+
119
+ def to_h
120
+ {
121
+ overall_health: overall_health.round(4),
122
+ body_budget_label: body_budget_label,
123
+ channels: channel_count,
124
+ markers: marker_count,
125
+ vitals: @vitals.transform_values { |v| v.round(4) },
126
+ deviations: deviating_channels
127
+ }
128
+ end
129
+
130
+ private
131
+
132
+ def ema(old_val, new_val, alpha)
133
+ old_val + (alpha * (new_val - old_val))
134
+ end
135
+
136
+ def record_vital_history(channel, value)
137
+ @vital_history[channel] ||= []
138
+ @vital_history[channel] << { value: value, at: Time.now.utc }
139
+ @vital_history[channel].shift while @vital_history[channel].size > MAX_VITAL_HISTORY
140
+ end
141
+
142
+ def prune_markers
143
+ @markers.sort_by!(&:strength)
144
+ @markers.shift while @markers.size > MAX_MARKERS
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Interoception
8
+ module Helpers
9
+ module Constants
10
+ # Vital signal channels the agent monitors
11
+ VITAL_CHANNELS = %i[
12
+ cpu_load memory_pressure queue_depth
13
+ response_latency error_rate connection_health
14
+ disk_usage thread_count gc_pressure
15
+ ].freeze
16
+
17
+ # Somatic marker valence thresholds
18
+ MARKER_POSITIVE_THRESHOLD = 0.3
19
+ MARKER_NEGATIVE_THRESHOLD = -0.3
20
+
21
+ # How strongly markers bias decisions (0..1)
22
+ MARKER_INFLUENCE = 0.4
23
+
24
+ # EMA alpha for vital signal smoothing
25
+ VITAL_ALPHA = 0.15
26
+
27
+ # Default baseline for vitals (normalized 0..1)
28
+ DEFAULT_BASELINE = 0.5
29
+
30
+ # Deviation from baseline that triggers a somatic marker
31
+ DEVIATION_THRESHOLD = 0.2
32
+
33
+ # Maximum stored somatic markers
34
+ MAX_MARKERS = 200
35
+
36
+ # Maximum stored vital snapshots per channel
37
+ MAX_VITAL_HISTORY = 100
38
+
39
+ # Marker decay per tick
40
+ MARKER_DECAY = 0.02
41
+
42
+ # Marker floor (below this, marker is pruned)
43
+ MARKER_FLOOR = 0.05
44
+
45
+ # Body budget labels based on overall vital health
46
+ BODY_BUDGET_LABELS = {
47
+ (0.8..) => :thriving,
48
+ (0.6...0.8) => :comfortable,
49
+ (0.4...0.6) => :strained,
50
+ (0.2...0.4) => :distressed,
51
+ (..0.2) => :critical
52
+ }.freeze
53
+
54
+ # Vital health labels
55
+ VITAL_LABELS = {
56
+ (0.8..) => :healthy,
57
+ (0.6...0.8) => :nominal,
58
+ (0.4...0.6) => :elevated,
59
+ (0.2...0.4) => :warning,
60
+ (..0.2) => :critical
61
+ }.freeze
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Interoception
8
+ module Helpers
9
+ class SomaticMarker
10
+ attr_reader :id, :action, :domain, :valence, :created_at
11
+ attr_accessor :strength
12
+
13
+ def initialize(action:, domain:, valence:, strength: 1.0)
14
+ @id = SecureRandom.uuid
15
+ @action = action
16
+ @domain = domain
17
+ @valence = valence.clamp(-1.0, 1.0)
18
+ @strength = strength.clamp(0.0, 1.0)
19
+ @created_at = Time.now.utc
20
+ end
21
+
22
+ def bias_for(candidate_action)
23
+ return 0.0 unless candidate_action == @action
24
+
25
+ @valence * @strength * Constants::MARKER_INFLUENCE
26
+ end
27
+
28
+ def reinforce(amount: 0.1)
29
+ @strength = [@strength + amount, 1.0].min
30
+ end
31
+
32
+ def decay
33
+ @strength = [@strength - Constants::MARKER_DECAY, Constants::MARKER_FLOOR].max
34
+ end
35
+
36
+ def faded?
37
+ @strength <= Constants::MARKER_FLOOR
38
+ end
39
+
40
+ def positive?
41
+ @valence >= Constants::MARKER_POSITIVE_THRESHOLD
42
+ end
43
+
44
+ def negative?
45
+ @valence <= Constants::MARKER_NEGATIVE_THRESHOLD
46
+ end
47
+
48
+ def label
49
+ if positive?
50
+ :approach
51
+ elsif negative?
52
+ :avoid
53
+ else
54
+ :neutral
55
+ end
56
+ end
57
+
58
+ def to_h
59
+ {
60
+ id: @id,
61
+ action: @action,
62
+ domain: @domain,
63
+ valence: @valence,
64
+ strength: @strength,
65
+ label: label,
66
+ created_at: @created_at
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end