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,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module CognitiveEmpathy
8
+ module Helpers
9
+ class EmpathyEngine
10
+ include Constants
11
+
12
+ attr_reader :perspectives, :contagion_level, :history
13
+
14
+ def initialize
15
+ @perspectives = {}
16
+ @contagion_level = 0.0
17
+ @counter = 0
18
+ @history = []
19
+ end
20
+
21
+ def take_perspective(agent_id:, perspective_type:, predicted_state:, confidence:)
22
+ return nil if @perspectives.size >= MAX_PERSPECTIVES
23
+ return nil unless PERSPECTIVE_TYPES.include?(perspective_type)
24
+
25
+ @counter += 1
26
+ id = :"perspective_#{@counter}"
27
+ perspective = Perspective.new(
28
+ id: id,
29
+ agent_id: agent_id,
30
+ perspective_type: perspective_type,
31
+ predicted_state: predicted_state,
32
+ confidence: confidence
33
+ )
34
+ @perspectives[id] = perspective
35
+ record_event(:perspective_taken, id: id, agent_id: agent_id)
36
+ perspective
37
+ end
38
+
39
+ def record_outcome(perspective_id:, actual_state:)
40
+ perspective = @perspectives[perspective_id]
41
+ return nil unless perspective
42
+
43
+ perspective.record_actual(actual_state: actual_state)
44
+ record_event(:outcome_recorded, id: perspective_id, accuracy: perspective.accuracy)
45
+ perspective
46
+ end
47
+
48
+ def empathic_accuracy(agent_id:)
49
+ agent_perspectives = perspectives_for(agent_id: agent_id)
50
+ resolved = agent_perspectives.select(&:resolved?)
51
+ return DEFAULT_ACCURACY if resolved.empty?
52
+
53
+ resolved.sum(&:accuracy) / resolved.size
54
+ end
55
+
56
+ def overall_accuracy
57
+ resolved = @perspectives.values.select(&:resolved?)
58
+ return DEFAULT_ACCURACY if resolved.empty?
59
+
60
+ resolved.sum(&:accuracy) / resolved.size
61
+ end
62
+
63
+ def emotional_contagion(emotion_valence:, intensity:)
64
+ intensity_f = intensity.to_f.clamp(0.0, 1.0)
65
+ absorption = CONTAGION_RATE * intensity_f
66
+ @contagion_level = (@contagion_level + absorption).clamp(0.0, 1.0)
67
+ record_event(:contagion, valence: emotion_valence, intensity: intensity_f,
68
+ level: @contagion_level)
69
+ @contagion_level
70
+ end
71
+
72
+ def contagion_decay
73
+ @contagion_level = [@contagion_level - CONTAGION_DECAY, 0.0].max
74
+ end
75
+
76
+ def empathic_state
77
+ return :immersed if @contagion_level >= 0.75
78
+ return :resonating if @contagion_level >= 0.45
79
+ return :observing if @contagion_level >= 0.15
80
+
81
+ :detached
82
+ end
83
+
84
+ def perspectives_for(agent_id:)
85
+ @perspectives.values.select { |p| p.agent_id == agent_id }
86
+ end
87
+
88
+ def most_accurate_agent
89
+ agent_accuracies = build_agent_accuracies
90
+ return nil if agent_accuracies.empty?
91
+
92
+ agent_accuracies.max_by { |_, acc| acc }&.first
93
+ end
94
+
95
+ def least_accurate_agent
96
+ agent_accuracies = build_agent_accuracies
97
+ return nil if agent_accuracies.empty?
98
+
99
+ agent_accuracies.min_by { |_, acc| acc }&.first
100
+ end
101
+
102
+ def tick
103
+ contagion_decay
104
+ prune_old_perspectives
105
+ self
106
+ end
107
+
108
+ def to_h
109
+ {
110
+ perspective_count: @perspectives.size,
111
+ resolved_count: @perspectives.values.count(&:resolved?),
112
+ overall_accuracy: overall_accuracy.round(4),
113
+ contagion_level: @contagion_level.round(4),
114
+ empathic_state: empathic_state,
115
+ history_size: @history.size
116
+ }
117
+ end
118
+
119
+ private
120
+
121
+ def build_agent_accuracies
122
+ agent_ids = @perspectives.values.map(&:agent_id).uniq
123
+ accuracies = {}
124
+ agent_ids.each do |aid|
125
+ resolved = perspectives_for(agent_id: aid).select(&:resolved?)
126
+ next if resolved.empty?
127
+
128
+ accuracies[aid] = resolved.sum(&:accuracy) / resolved.size
129
+ end
130
+ accuracies
131
+ end
132
+
133
+ def prune_old_perspectives
134
+ resolved = @perspectives.select { |_, p| p.resolved? }
135
+ return unless resolved.size > MAX_PERSPECTIVES / 2
136
+
137
+ oldest_keys = resolved.keys.first(resolved.size - (MAX_PERSPECTIVES / 4))
138
+ oldest_keys.each { |k| @perspectives.delete(k) }
139
+ end
140
+
141
+ def record_event(type, **details)
142
+ @history << { type: type, at: Time.now.utc }.merge(details)
143
+ @history.shift while @history.size > MAX_HISTORY
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module CognitiveEmpathy
8
+ module Helpers
9
+ class Perspective
10
+ include Constants
11
+
12
+ attr_reader :id, :agent_id, :perspective_type, :predicted_state, :actual_state,
13
+ :confidence, :accuracy
14
+
15
+ def initialize(id:, agent_id:, perspective_type: :cognitive, predicted_state: {}, confidence: 0.5)
16
+ @id = id
17
+ @agent_id = agent_id
18
+ @perspective_type = perspective_type
19
+ @predicted_state = predicted_state
20
+ @confidence = confidence.to_f.clamp(0.0, 1.0)
21
+ @actual_state = nil
22
+ @accuracy = DEFAULT_ACCURACY
23
+ @created_at = Time.now.utc
24
+ @resolved_at = nil
25
+ end
26
+
27
+ def record_actual(actual_state:)
28
+ @actual_state = actual_state
29
+ @resolved_at = Time.now.utc
30
+ error = compute_error(predicted_state, actual_state)
31
+ observed_accuracy = (1.0 - error).clamp(ACCURACY_FLOOR, ACCURACY_CEILING)
32
+ @accuracy = ((1.0 - ACCURACY_ALPHA) * @accuracy) + (ACCURACY_ALPHA * observed_accuracy)
33
+ @accuracy = @accuracy.clamp(ACCURACY_FLOOR, ACCURACY_CEILING)
34
+ self
35
+ end
36
+
37
+ def accurate?
38
+ @accuracy > 0.6
39
+ end
40
+
41
+ def resolved?
42
+ !@actual_state.nil?
43
+ end
44
+
45
+ def to_h
46
+ {
47
+ id: @id,
48
+ agent_id: @agent_id,
49
+ perspective_type: @perspective_type,
50
+ predicted_state: @predicted_state,
51
+ actual_state: @actual_state,
52
+ confidence: @confidence.round(4),
53
+ accuracy: @accuracy.round(4),
54
+ accurate: accurate?,
55
+ resolved: resolved?,
56
+ created_at: @created_at,
57
+ resolved_at: @resolved_at
58
+ }
59
+ end
60
+
61
+ private
62
+
63
+ def compute_error(predicted, actual)
64
+ return 0.0 if predicted.empty? && actual.empty?
65
+ return 1.0 if predicted.empty? || actual.empty?
66
+
67
+ keys = (predicted.keys | actual.keys)
68
+ return 1.0 if keys.empty?
69
+
70
+ total_error = keys.sum do |k|
71
+ p_val = numeric_value(predicted[k])
72
+ a_val = numeric_value(actual[k])
73
+ (p_val - a_val).abs
74
+ end
75
+
76
+ (total_error / keys.size).clamp(0.0, 1.0)
77
+ end
78
+
79
+ def numeric_value(val)
80
+ return val.to_f if val.is_a?(Numeric)
81
+ return 1.0 if val == true
82
+ return 0.0 if val == false || val.nil?
83
+
84
+ 0.5
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module CognitiveEmpathy
8
+ module Runners
9
+ module CognitiveEmpathy
10
+ include Helpers::Constants
11
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
12
+
13
+ def take_empathic_perspective(agent_id:, perspective_type:, predicted_state:, confidence: 0.5, **)
14
+ perspective = engine.take_perspective(
15
+ agent_id: agent_id,
16
+ perspective_type: perspective_type,
17
+ predicted_state: predicted_state,
18
+ confidence: confidence
19
+ )
20
+ return { success: false, reason: :limit_or_invalid_type } unless perspective
21
+
22
+ { success: true, perspective_id: perspective.id, agent_id: agent_id,
23
+ perspective_type: perspective_type }
24
+ end
25
+
26
+ def record_empathic_outcome(perspective_id:, actual_state:, **)
27
+ perspective = engine.record_outcome(perspective_id: perspective_id, actual_state: actual_state)
28
+ return { success: false, reason: :not_found } unless perspective
29
+
30
+ { success: true, perspective_id: perspective_id,
31
+ accuracy: perspective.accuracy.round(4), accurate: perspective.accurate? }
32
+ end
33
+
34
+ def empathic_accuracy_for(agent_id:, **)
35
+ accuracy = engine.empathic_accuracy(agent_id: agent_id)
36
+ label = accuracy_label(accuracy)
37
+ { success: true, agent_id: agent_id, accuracy: accuracy.round(4), label: label }
38
+ end
39
+
40
+ def overall_empathic_accuracy(**)
41
+ accuracy = engine.overall_accuracy
42
+ label = accuracy_label(accuracy)
43
+ { success: true, accuracy: accuracy.round(4), label: label }
44
+ end
45
+
46
+ def apply_emotional_contagion(emotion_valence:, intensity:, **)
47
+ level = engine.emotional_contagion(emotion_valence: emotion_valence, intensity: intensity)
48
+ { success: true, contagion_level: level.round(4), empathic_state: engine.empathic_state }
49
+ end
50
+
51
+ def current_empathic_state(**)
52
+ { success: true, empathic_state: engine.empathic_state,
53
+ contagion_level: engine.contagion_level.round(4) }
54
+ end
55
+
56
+ def perspectives_for_agent(agent_id:, **)
57
+ list = engine.perspectives_for(agent_id: agent_id).map(&:to_h)
58
+ { success: true, agent_id: agent_id, perspectives: list, count: list.size }
59
+ end
60
+
61
+ def empathic_blind_spots(**)
62
+ least = engine.least_accurate_agent
63
+ most = engine.most_accurate_agent
64
+ { success: true, least_accurate_agent: least, most_accurate_agent: most,
65
+ overall_accuracy: engine.overall_accuracy.round(4) }
66
+ end
67
+
68
+ def update_cognitive_empathy(**)
69
+ engine.tick
70
+ { success: true }.merge(engine.to_h)
71
+ end
72
+
73
+ def cognitive_empathy_stats(**)
74
+ { success: true }.merge(engine.to_h)
75
+ end
76
+
77
+ private
78
+
79
+ def engine
80
+ @engine ||= Helpers::EmpathyEngine.new
81
+ end
82
+
83
+ def accuracy_label(accuracy)
84
+ ACCURACY_LABELS.each { |range, lbl| return lbl if range.cover?(accuracy) }
85
+ :blind
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ 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 CognitiveEmpathy
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cognitive_empathy/version'
4
+ require_relative 'cognitive_empathy/helpers/constants'
5
+ require_relative 'cognitive_empathy/helpers/empathy_engine'
6
+ require_relative 'cognitive_empathy/helpers/perspective'
7
+ require_relative 'cognitive_empathy/runners/cognitive_empathy'
8
+ require_relative 'cognitive_empathy/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module Agentic
13
+ module Affect
14
+ module CognitiveEmpathy
15
+ # Sub-module for cognitive empathy: affective and cognitive empathy modeling
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/affect/contagion/helpers/constants'
4
+ require 'legion/extensions/agentic/affect/contagion/helpers/meme'
5
+ require 'legion/extensions/agentic/affect/contagion/helpers/contagion_engine'
6
+ require 'legion/extensions/agentic/affect/contagion/runners/cognitive_contagion'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Agentic
11
+ module Affect
12
+ module Contagion
13
+ class Client
14
+ include Runners::CognitiveContagion
15
+
16
+ def initialize(**)
17
+ @engine = Helpers::ContagionEngine.new
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :engine
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Contagion
8
+ module Helpers
9
+ module Constants
10
+ MAX_AGENTS = 200
11
+ MAX_MEMES = 500
12
+ DEFAULT_VIRULENCE = 0.3
13
+ DEFAULT_RESISTANCE = 0.5
14
+ TRANSMISSION_RATE = 0.15
15
+ RECOVERY_RATE = 0.05
16
+ IMMUNITY_BOOST = 0.1
17
+
18
+ VIRULENCE_LABELS = {
19
+ (0.8..) => :pandemic,
20
+ (0.6...0.8) => :epidemic,
21
+ (0.4...0.6) => :endemic,
22
+ (0.2...0.4) => :sporadic,
23
+ (..0.2) => :contained
24
+ }.freeze
25
+
26
+ STATUS_LABELS = %i[susceptible exposed infected recovered immune].freeze
27
+ CONTAGION_TYPES = %i[emotional belief behavioral cognitive].freeze
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Affect
7
+ module Contagion
8
+ module Helpers
9
+ class ContagionEngine
10
+ attr_reader :memes, :agent_resistance
11
+
12
+ def initialize
13
+ @memes = {}
14
+ @agent_resistance = {}
15
+ end
16
+
17
+ def create_meme(label:, contagion_type: :cognitive, virulence: Constants::DEFAULT_VIRULENCE)
18
+ return { error: :too_many_memes } if @memes.size >= Constants::MAX_MEMES
19
+
20
+ meme = Meme.new(label: label, contagion_type: contagion_type, virulence: virulence)
21
+ @memes[meme.id] = meme
22
+ meme
23
+ end
24
+
25
+ def register_agent(agent_id:, resistance: Constants::DEFAULT_RESISTANCE)
26
+ return { error: :too_many_agents } if @agent_resistance.size >= Constants::MAX_AGENTS
27
+
28
+ @agent_resistance[agent_id] = resistance.clamp(0.0, 1.0).round(10)
29
+ { agent_id: agent_id, resistance: @agent_resistance[agent_id] }
30
+ end
31
+
32
+ def attempt_transmission(meme_id:, source_agent_id:, target_agent_id:)
33
+ meme = @memes.fetch(meme_id, nil)
34
+ return { transmitted: false, reason: :meme_not_found } unless meme
35
+ return { transmitted: false, reason: :source_not_carrying } unless meme.carrying?(agent_id: source_agent_id)
36
+
37
+ resistance = @agent_resistance.fetch(target_agent_id, Constants::DEFAULT_RESISTANCE)
38
+ probability = (meme.virulence * Constants::TRANSMISSION_RATE * (1.0 - resistance)).round(10)
39
+
40
+ if rand <= probability
41
+ result = meme.infect!(agent_id: target_agent_id)
42
+ { transmitted: result == :infected, reason: result, meme_id: meme_id,
43
+ source: source_agent_id, target: target_agent_id, probability: probability }
44
+ else
45
+ { transmitted: false, reason: :blocked_by_resistance, meme_id: meme_id,
46
+ source: source_agent_id, target: target_agent_id, probability: probability }
47
+ end
48
+ end
49
+
50
+ def recover_agent(meme_id:, agent_id:)
51
+ meme = @memes.fetch(meme_id, nil)
52
+ return { recovered: false, reason: :meme_not_found } unless meme
53
+
54
+ result = meme.recover!(agent_id: agent_id)
55
+ boost_resistance(agent_id)
56
+ { recovered: result == :recovered, agent_id: agent_id, meme_id: meme_id, result: result }
57
+ end
58
+
59
+ def immunize_agent(meme_id:, agent_id:)
60
+ meme = @memes.fetch(meme_id, nil)
61
+ return { immunized: false, reason: :meme_not_found } unless meme
62
+
63
+ result = meme.immunize!(agent_id: agent_id)
64
+ { immunized: true, agent_id: agent_id, meme_id: meme_id, result: result }
65
+ end
66
+
67
+ def spread_step(meme_id:)
68
+ meme = @memes.fetch(meme_id, nil)
69
+ return { transmissions: 0, reason: :meme_not_found } unless meme
70
+
71
+ susceptible = susceptible_agents(meme_id: meme_id)
72
+ carriers = meme.carriers.to_a
73
+ transmissions = 0
74
+
75
+ carriers.each do |carrier_id|
76
+ susceptible.each do |target_id|
77
+ result = attempt_transmission(meme_id: meme_id, source_agent_id: carrier_id,
78
+ target_agent_id: target_id)
79
+ transmissions += 1 if result[:transmitted]
80
+ end
81
+ end
82
+
83
+ recoveries = apply_natural_recovery(meme_id: meme_id)
84
+ { meme_id: meme_id, transmissions: transmissions, recoveries: recoveries,
85
+ carrier_count: meme.carrier_count }
86
+ end
87
+
88
+ def epidemic_report(meme_id:)
89
+ meme = @memes.fetch(meme_id, nil)
90
+ return { error: :meme_not_found } unless meme
91
+
92
+ total = @agent_resistance.size
93
+ infected = meme.carriers.size
94
+ recovered = meme.recovered.size
95
+ immune = meme.immune.size
96
+ susceptible_count = [total - infected - recovered - immune, 0].max
97
+
98
+ {
99
+ meme_id: meme_id,
100
+ label: meme.label,
101
+ contagion_type: meme.contagion_type,
102
+ virulence: meme.virulence,
103
+ virulence_label: meme.virulence_label,
104
+ susceptible: susceptible_count,
105
+ infected: infected,
106
+ recovered: recovered,
107
+ immune: immune,
108
+ total_agents: total,
109
+ total_transmissions: meme.total_transmissions,
110
+ transmission_rate: meme.transmission_rate
111
+ }
112
+ end
113
+
114
+ def most_viral(limit: 5)
115
+ @memes.values
116
+ .sort_by { |m| -m.virulence }
117
+ .first(limit)
118
+ .map(&:to_h)
119
+ end
120
+
121
+ def agent_status(agent_id:, meme_id:)
122
+ meme = @memes.fetch(meme_id, nil)
123
+ return { status: :unknown, reason: :meme_not_found } unless meme
124
+
125
+ status = if meme.immune.include?(agent_id)
126
+ :immune
127
+ elsif meme.carrying?(agent_id: agent_id)
128
+ :infected
129
+ elsif meme.recovered.include?(agent_id)
130
+ :recovered
131
+ else
132
+ :susceptible
133
+ end
134
+
135
+ { agent_id: agent_id, meme_id: meme_id, status: status,
136
+ resistance: @agent_resistance.fetch(agent_id, Constants::DEFAULT_RESISTANCE) }
137
+ end
138
+
139
+ def susceptible_agents(meme_id:)
140
+ meme = @memes.fetch(meme_id, nil)
141
+ return [] unless meme
142
+
143
+ @agent_resistance.keys.reject do |id|
144
+ meme.carrying?(agent_id: id) || meme.immune.include?(id)
145
+ end
146
+ end
147
+
148
+ def to_h
149
+ {
150
+ meme_count: @memes.size,
151
+ agent_count: @agent_resistance.size,
152
+ memes: @memes.values.map(&:to_h),
153
+ agent_resistance: @agent_resistance
154
+ }
155
+ end
156
+
157
+ private
158
+
159
+ def boost_resistance(agent_id)
160
+ current = @agent_resistance.fetch(agent_id, Constants::DEFAULT_RESISTANCE)
161
+ @agent_resistance[agent_id] = (current + Constants::IMMUNITY_BOOST).clamp(0.0, 1.0).round(10)
162
+ end
163
+
164
+ def apply_natural_recovery(meme_id:)
165
+ meme = @memes.fetch(meme_id, nil)
166
+ return 0 unless meme
167
+
168
+ recoveries = 0
169
+ meme.carriers.to_a.each do |carrier_id|
170
+ next unless rand <= Constants::RECOVERY_RATE
171
+
172
+ meme.recover!(agent_id: carrier_id)
173
+ boost_resistance(carrier_id)
174
+ recoveries += 1
175
+ end
176
+ recoveries
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end