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,165 @@
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 ResilienceModel
10
+ attr_reader :dimensions, :growth_bonus, :history
11
+
12
+ def initialize
13
+ @dimensions = Constants::DIMENSIONS.keys.to_h { |d| [d, 0.5] }
14
+ @growth_bonus = 0.0
15
+ @history = []
16
+ end
17
+
18
+ def update_from_tracker(tracker)
19
+ update_elasticity(tracker)
20
+ update_robustness(tracker)
21
+ update_adaptability(tracker)
22
+ update_growth(tracker)
23
+
24
+ record_snapshot
25
+ end
26
+
27
+ def composite_score
28
+ total = 0.0
29
+ Constants::DIMENSIONS.each do |dim, config|
30
+ total += (@dimensions[dim] + (dim == :growth ? @growth_bonus : 0.0)) * config[:weight]
31
+ end
32
+ total.clamp(0.0, 1.0)
33
+ end
34
+
35
+ def classification
36
+ score = composite_score
37
+ if score >= Constants::ANTIFRAGILITY_THRESHOLD
38
+ :antifragile
39
+ elsif score >= 0.5
40
+ :resilient
41
+ elsif score >= Constants::FRAGILITY_THRESHOLD
42
+ :fragile
43
+ else
44
+ :brittle
45
+ end
46
+ end
47
+
48
+ def dimension_detail(name)
49
+ return nil unless @dimensions.key?(name)
50
+
51
+ {
52
+ name: name,
53
+ value: @dimensions[name].round(4),
54
+ config: Constants::DIMENSIONS[name],
55
+ trend: dimension_trend(name),
56
+ healthy: @dimensions[name] >= 0.5
57
+ }
58
+ end
59
+
60
+ def trend
61
+ return :insufficient_data if @history.size < 5
62
+
63
+ recent = @history.last(10)
64
+ scores = recent.map { |h| h[:composite] }
65
+ first_half = scores[0...(scores.size / 2)]
66
+ second_half = scores[(scores.size / 2)..]
67
+ diff = mean(second_half) - mean(first_half)
68
+
69
+ if diff > 0.03
70
+ :strengthening
71
+ elsif diff < -0.03
72
+ :weakening
73
+ else
74
+ :stable
75
+ end
76
+ end
77
+
78
+ def to_h
79
+ {
80
+ dimensions: @dimensions.transform_values { |v| v.round(4) },
81
+ growth_bonus: @growth_bonus.round(4),
82
+ composite: composite_score.round(4),
83
+ class: classification,
84
+ trend: trend
85
+ }
86
+ end
87
+
88
+ private
89
+
90
+ def update_elasticity(tracker)
91
+ speed = tracker.average_recovery_speed
92
+ signal = if speed.zero?
93
+ 0.5
94
+ elsif speed <= 1.0
95
+ 0.7 + ((1.0 - speed) * 0.3)
96
+ else
97
+ [0.3, 0.7 - ((speed - 1.0) * 0.2)].max
98
+ end
99
+ @dimensions[:elasticity] = ema(@dimensions[:elasticity], signal, Constants::RESILIENCE_ALPHA)
100
+ end
101
+
102
+ def update_robustness(tracker)
103
+ worst = tracker.worst_health
104
+ @dimensions[:robustness] = ema(@dimensions[:robustness], worst, Constants::RESILIENCE_ALPHA)
105
+ end
106
+
107
+ def update_adaptability(tracker)
108
+ rate = tracker.recovery_rate
109
+ signal = tracker.total_adversities.zero? ? 0.5 : rate
110
+ @dimensions[:adaptability] = ema(@dimensions[:adaptability], signal, Constants::RESILIENCE_ALPHA)
111
+ end
112
+
113
+ def update_growth(tracker)
114
+ if tracker.consecutive_recoveries >= Constants::GROWTH_TRIGGER
115
+ @growth_bonus = [@growth_bonus + Constants::GROWTH_INCREMENT, Constants::MAX_GROWTH_BONUS].min
116
+ end
117
+
118
+ growth_signal = tracker.total_adversities.zero? ? 0.5 : 0.5 + @growth_bonus
119
+ @dimensions[:growth] = ema(@dimensions[:growth], growth_signal, Constants::RESILIENCE_ALPHA)
120
+ end
121
+
122
+ def dimension_trend(name)
123
+ return :insufficient_data if @history.size < 5
124
+
125
+ recent = @history.last(10)
126
+ values = recent.map { |h| h[:dimensions][name] }
127
+ first_half = values[0...(values.size / 2)]
128
+ second_half = values[(values.size / 2)..]
129
+ diff = mean(second_half) - mean(first_half)
130
+
131
+ if diff > 0.02
132
+ :improving
133
+ elsif diff < -0.02
134
+ :declining
135
+ else
136
+ :stable
137
+ end
138
+ end
139
+
140
+ def ema(current, observed, alpha)
141
+ (current * (1.0 - alpha)) + (observed * alpha)
142
+ end
143
+
144
+ def mean(values)
145
+ return 0.0 if values.empty?
146
+
147
+ values.sum / values.size.to_f
148
+ end
149
+
150
+ def record_snapshot
151
+ @history << {
152
+ dimensions: @dimensions.dup,
153
+ composite: composite_score,
154
+ class: classification,
155
+ at: Time.now.utc
156
+ }
157
+ @history.shift while @history.size > Constants::MAX_RESILIENCE_HISTORY
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Resilience
8
+ module Runners
9
+ module Resilience
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ def update_resilience(tick_results: {}, **)
14
+ detect_adversities(tick_results)
15
+ recovery = adversity_tracker.tick_recovery
16
+ resilience_model.update_from_tracker(adversity_tracker)
17
+
18
+ Legion::Logging.debug "[resilience] active=#{recovery[:active_count]} " \
19
+ "resolved=#{recovery[:resolved_count]} " \
20
+ "composite=#{resilience_model.composite_score.round(3)}"
21
+
22
+ {
23
+ active_adversities: recovery[:active_count],
24
+ resolved_this_tick: recovery[:resolved_count],
25
+ worst_health: recovery[:worst_health],
26
+ composite_score: resilience_model.composite_score.round(4),
27
+ classification: resilience_model.classification,
28
+ growth_bonus: resilience_model.growth_bonus.round(4)
29
+ }
30
+ end
31
+
32
+ def register_adversity(type:, severity:, context: {}, **)
33
+ adversity = adversity_tracker.register(type: type, severity: severity, context: context)
34
+ return { success: false, error: 'invalid type or severity' } unless adversity
35
+
36
+ Legion::Logging.info "[resilience] adversity registered: type=#{type} severity=#{severity}"
37
+ { success: true, adversity: adversity }
38
+ end
39
+
40
+ def resilience_status(**)
41
+ model_state = resilience_model.to_h
42
+ Legion::Logging.debug "[resilience] status: #{model_state[:class]} score=#{model_state[:composite]}"
43
+
44
+ model_state.merge(
45
+ active_adversities: adversity_tracker.active_adversities.size,
46
+ total_adversities: adversity_tracker.total_adversities,
47
+ consecutive_recoveries: adversity_tracker.consecutive_recoveries,
48
+ recovery_rate: adversity_tracker.recovery_rate.round(4)
49
+ )
50
+ end
51
+
52
+ def adversity_report(**)
53
+ Legion::Logging.debug '[resilience] adversity report'
54
+
55
+ {
56
+ active: adversity_tracker.active_adversities,
57
+ by_type: adversity_tracker.active_by_type,
58
+ total: adversity_tracker.total_adversities,
59
+ worst: adversity_tracker.worst_health.round(4),
60
+ avg_speed: adversity_tracker.average_recovery_speed.round(4)
61
+ }
62
+ end
63
+
64
+ def dimension_detail(dimension:, **)
65
+ detail = resilience_model.dimension_detail(dimension.to_sym)
66
+ return { error: "unknown dimension: #{dimension}" } unless detail
67
+
68
+ Legion::Logging.debug "[resilience] dimension #{dimension}: #{detail[:value]}"
69
+ detail
70
+ end
71
+
72
+ def resilience_stats(**)
73
+ Legion::Logging.debug '[resilience] stats'
74
+
75
+ {
76
+ composite: resilience_model.composite_score.round(4),
77
+ classification: resilience_model.classification,
78
+ dimensions: resilience_model.dimensions.transform_values { |v| v.round(4) },
79
+ growth_bonus: resilience_model.growth_bonus.round(4),
80
+ trend: resilience_model.trend,
81
+ total_adversities: adversity_tracker.total_adversities,
82
+ active_adversities: adversity_tracker.active_adversities.size,
83
+ recovery_rate: adversity_tracker.recovery_rate.round(4),
84
+ consecutive_recoveries: adversity_tracker.consecutive_recoveries,
85
+ history_size: resilience_model.history.size
86
+ }
87
+ end
88
+
89
+ private
90
+
91
+ def adversity_tracker
92
+ @adversity_tracker ||= Helpers::AdversityTracker.new
93
+ end
94
+
95
+ def resilience_model
96
+ @resilience_model ||= Helpers::ResilienceModel.new
97
+ end
98
+
99
+ def detect_adversities(tick_results)
100
+ detect_prediction_adversity(tick_results)
101
+ detect_trust_adversity(tick_results)
102
+ detect_conflict_adversity(tick_results)
103
+ detect_energy_adversity(tick_results)
104
+ detect_emotional_adversity(tick_results)
105
+ end
106
+
107
+ def detect_prediction_adversity(tick_results)
108
+ error_rate = tick_results.dig(:prediction_engine, :error_rate)
109
+ return unless error_rate && error_rate > 0.7
110
+
111
+ severity = error_rate > 0.9 ? :major : :moderate
112
+ adversity_tracker.register(type: :prediction_failure, severity: severity)
113
+ end
114
+
115
+ def detect_trust_adversity(tick_results)
116
+ violation = tick_results.dig(:trust, :violation)
117
+ return unless violation
118
+
119
+ adversity_tracker.register(type: :trust_violation, severity: :major)
120
+ end
121
+
122
+ def detect_conflict_adversity(tick_results)
123
+ conflict_severity = tick_results.dig(:conflict, :severity)
124
+ return unless conflict_severity && conflict_severity >= 3
125
+
126
+ severity = conflict_severity >= 4 ? :severe : :moderate
127
+ adversity_tracker.register(type: :conflict_escalation, severity: severity)
128
+ end
129
+
130
+ def detect_energy_adversity(tick_results)
131
+ energy = tick_results.dig(:fatigue, :energy)
132
+ return unless energy && energy < 0.2
133
+
134
+ severity = energy < 0.1 ? :major : :moderate
135
+ adversity_tracker.register(type: :resource_depletion, severity: severity)
136
+ end
137
+
138
+ def detect_emotional_adversity(tick_results)
139
+ arousal = tick_results.dig(:emotional_evaluation, :arousal)
140
+ return unless arousal && arousal > 0.9
141
+
142
+ adversity_tracker.register(type: :emotional_shock, severity: :moderate)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ 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 Resilience
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/resilience/version'
4
+ require 'legion/extensions/agentic/affect/resilience/helpers/constants'
5
+ require 'legion/extensions/agentic/affect/resilience/helpers/adversity_tracker'
6
+ require 'legion/extensions/agentic/affect/resilience/helpers/resilience_model'
7
+ require 'legion/extensions/agentic/affect/resilience/runners/resilience'
8
+ require 'legion/extensions/agentic/affect/resilience/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module Agentic
13
+ module Affect
14
+ module Resilience
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/resonance/helpers/constants'
4
+ require 'legion/extensions/agentic/affect/resonance/helpers/category'
5
+ require 'legion/extensions/agentic/affect/resonance/helpers/resonance_engine'
6
+ require 'legion/extensions/agentic/affect/resonance/runners/cognitive_resonance'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Agentic
11
+ module Affect
12
+ module Resonance
13
+ class Client
14
+ include Runners::CognitiveResonance
15
+
16
+ def initialize(**)
17
+ @default_engine = Helpers::ResonanceEngine.new
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Agentic
8
+ module Affect
9
+ module Resonance
10
+ module Helpers
11
+ class Category
12
+ attr_reader :id, :prototype, :match_count, :last_matched_at
13
+
14
+ def initialize(prototype:)
15
+ @id = SecureRandom.uuid
16
+ @prototype = prototype.map { |v| v.to_f.clamp(0.0, 1.0) }
17
+ @match_count = 0
18
+ @last_matched_at = nil
19
+ end
20
+
21
+ def match_quality(input:)
22
+ return 0.0 if @prototype.empty? || input.empty?
23
+
24
+ len = [@prototype.size, input.size].min
25
+ proto_slice = @prototype.first(len)
26
+ input_slice = input.first(len).map { |v| v.to_f.clamp(0.0, 1.0) }
27
+ cosine_similarity(proto_slice, input_slice)
28
+ end
29
+
30
+ def update_prototype!(input:, learning_rate: Constants::DEFAULT_LEARNING_RATE)
31
+ rate = learning_rate.clamp(0.0, 1.0)
32
+ input_normalized = input.map { |v| v.to_f.clamp(0.0, 1.0) }
33
+
34
+ len = [@prototype.size, input_normalized.size].max
35
+ updated = Array.new(len) do |i|
36
+ proto_val = @prototype[i] || 0.0
37
+ input_val = input_normalized[i] || 0.0
38
+ (proto_val + (rate * (input_val - proto_val))).round(10).clamp(0.0, 1.0)
39
+ end
40
+
41
+ @prototype = updated
42
+ @match_count += 1
43
+ @last_matched_at = Time.now.utc
44
+ self
45
+ end
46
+
47
+ private
48
+
49
+ def cosine_similarity(proto_slice, input_slice)
50
+ dot = proto_slice.zip(input_slice).sum { |a, b| (a * b).round(10) }
51
+ mag_proto = Math.sqrt(proto_slice.sum { |v| (v**2).round(10) })
52
+ mag_input = Math.sqrt(input_slice.sum { |v| (v**2).round(10) })
53
+ return 0.0 if mag_proto.zero? || mag_input.zero?
54
+
55
+ (dot / (mag_proto * mag_input)).clamp(0.0, 1.0).round(10)
56
+ end
57
+
58
+ public
59
+
60
+ def to_h
61
+ {
62
+ id: @id,
63
+ prototype: @prototype,
64
+ match_count: @match_count,
65
+ last_matched_at: @last_matched_at,
66
+ dimensions: @prototype.size
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Resonance
8
+ module Helpers
9
+ module Constants
10
+ DEFAULT_VIGILANCE = 0.7
11
+ MAX_CATEGORIES = 200
12
+ RESONANCE_THRESHOLD = 0.6
13
+ DEFAULT_LEARNING_RATE = 0.2
14
+
15
+ MATCH_LABELS = [
16
+ { range: (0.9..1.0), label: :perfect },
17
+ { range: (0.75..0.9), label: :strong },
18
+ { range: (0.6..0.75), label: :moderate },
19
+ { range: (0.4..0.6), label: :weak },
20
+ { range: (0.0..0.4), label: :mismatch }
21
+ ].freeze
22
+
23
+ VIGILANCE_LABELS = [
24
+ { range: (0.85..1.0), label: :fine },
25
+ { range: (0.65..0.85), label: :medium },
26
+ { range: (0.4..0.65), label: :coarse },
27
+ { range: (0.0..0.4), label: :very_coarse }
28
+ ].freeze
29
+
30
+ module_function
31
+
32
+ def match_label(quality)
33
+ entry = MATCH_LABELS.find { |e| e[:range].cover?(quality) }
34
+ entry ? entry[:label] : :mismatch
35
+ end
36
+
37
+ def vigilance_label(vigilance)
38
+ entry = VIGILANCE_LABELS.find { |e| e[:range].cover?(vigilance) }
39
+ entry ? entry[:label] : :very_coarse
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Resonance
8
+ module Helpers
9
+ class ResonanceEngine
10
+ attr_reader :vigilance
11
+
12
+ def initialize(vigilance: Constants::DEFAULT_VIGILANCE)
13
+ @vigilance = vigilance.clamp(0.0, 1.0)
14
+ @categories = {}
15
+ end
16
+
17
+ def present_input(input:)
18
+ normalized = input.map { |v| v.to_f.clamp(0.0, 1.0) }
19
+ match = best_match(normalized)
20
+
21
+ if match && match[:quality] >= @vigilance
22
+ resonate_with(match, normalized)
23
+ else
24
+ create_category(normalized, prior_match: match)
25
+ end
26
+ end
27
+
28
+ def best_match(input)
29
+ return nil if @categories.empty?
30
+
31
+ normalized = input.map { |v| v.to_f.clamp(0.0, 1.0) }
32
+ best = nil
33
+
34
+ @categories.each_value do |category|
35
+ quality = category.match_quality(input: normalized)
36
+ next unless best.nil? || quality > best[:quality]
37
+
38
+ best = { id: category.id, quality: quality }
39
+ end
40
+
41
+ best
42
+ end
43
+
44
+ def adjust_vigilance(amount:)
45
+ @vigilance = (@vigilance + amount).clamp(0.0, 1.0)
46
+ Legion::Logging.debug "[cognitive_resonance] vigilance adjusted to #{@vigilance.round(3)}"
47
+ @vigilance
48
+ end
49
+
50
+ def category_count
51
+ @categories.size
52
+ end
53
+
54
+ def resonance_report
55
+ vigilance_lbl = Constants.vigilance_label(@vigilance)
56
+ {
57
+ category_count: @categories.size,
58
+ vigilance: @vigilance,
59
+ vigilance_label: vigilance_lbl,
60
+ categories: @categories.values.map(&:to_h)
61
+ }
62
+ end
63
+
64
+ def to_h
65
+ {
66
+ vigilance: @vigilance,
67
+ category_count: @categories.size
68
+ }
69
+ end
70
+
71
+ private
72
+
73
+ def resonate_with(match, normalized)
74
+ category = @categories[match[:id]]
75
+ category.update_prototype!(input: normalized)
76
+ Legion::Logging.debug "[cognitive_resonance] resonance with category #{match[:id][0..7]} " \
77
+ "quality=#{match[:quality].round(3)} vigilance=#{@vigilance.round(3)}"
78
+ {
79
+ outcome: :resonance,
80
+ category_id: match[:id],
81
+ quality: match[:quality],
82
+ label: Constants.match_label(match[:quality]),
83
+ created: false
84
+ }
85
+ end
86
+
87
+ def create_category(normalized, prior_match: nil)
88
+ prune_if_full!
89
+ category = Category.new(prototype: normalized)
90
+ @categories[category.id] = category
91
+ Legion::Logging.debug "[cognitive_resonance] new category #{category.id[0..7]} " \
92
+ "total=#{@categories.size} vigilance=#{@vigilance.round(3)}"
93
+ {
94
+ outcome: :new_category,
95
+ category_id: category.id,
96
+ quality: prior_match ? prior_match[:quality] : 0.0,
97
+ label: :new,
98
+ created: true
99
+ }
100
+ end
101
+
102
+ def prune_if_full!
103
+ return unless @categories.size >= Constants::MAX_CATEGORIES
104
+
105
+ oldest_id = @categories.min_by { |_, cat| cat.last_matched_at || Time.at(0) }.first
106
+ @categories.delete(oldest_id)
107
+ Legion::Logging.debug "[cognitive_resonance] pruned oldest category #{oldest_id[0..7]}"
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end