lex-agentic-learning 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 (192) 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-learning.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/learning/anchoring/client.rb +26 -0
  8. data/lib/legion/extensions/agentic/learning/anchoring/helpers/anchor.rb +65 -0
  9. data/lib/legion/extensions/agentic/learning/anchoring/helpers/anchor_store.rb +132 -0
  10. data/lib/legion/extensions/agentic/learning/anchoring/helpers/constants.rb +31 -0
  11. data/lib/legion/extensions/agentic/learning/anchoring/runners/anchoring.rb +100 -0
  12. data/lib/legion/extensions/agentic/learning/anchoring/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/learning/anchoring.rb +19 -0
  14. data/lib/legion/extensions/agentic/learning/catalyst/client.rb +15 -0
  15. data/lib/legion/extensions/agentic/learning/catalyst/helpers/catalyst.rb +87 -0
  16. data/lib/legion/extensions/agentic/learning/catalyst/helpers/catalyst_engine.rb +153 -0
  17. data/lib/legion/extensions/agentic/learning/catalyst/helpers/constants.rb +55 -0
  18. data/lib/legion/extensions/agentic/learning/catalyst/helpers/reaction.rb +87 -0
  19. data/lib/legion/extensions/agentic/learning/catalyst/runners/cognitive_catalyst.rb +103 -0
  20. data/lib/legion/extensions/agentic/learning/catalyst/version.rb +13 -0
  21. data/lib/legion/extensions/agentic/learning/catalyst.rb +22 -0
  22. data/lib/legion/extensions/agentic/learning/chrysalis/client.rb +22 -0
  23. data/lib/legion/extensions/agentic/learning/chrysalis/helpers/chrysalis.rb +137 -0
  24. data/lib/legion/extensions/agentic/learning/chrysalis/helpers/cocoon.rb +89 -0
  25. data/lib/legion/extensions/agentic/learning/chrysalis/helpers/constants.rb +49 -0
  26. data/lib/legion/extensions/agentic/learning/chrysalis/helpers/metamorphosis_engine.rb +157 -0
  27. data/lib/legion/extensions/agentic/learning/chrysalis/runners/cognitive_chrysalis.rb +129 -0
  28. data/lib/legion/extensions/agentic/learning/chrysalis/version.rb +13 -0
  29. data/lib/legion/extensions/agentic/learning/chrysalis.rb +21 -0
  30. data/lib/legion/extensions/agentic/learning/curiosity/client.rb +28 -0
  31. data/lib/legion/extensions/agentic/learning/curiosity/helpers/constants.rb +30 -0
  32. data/lib/legion/extensions/agentic/learning/curiosity/helpers/gap_detector.rb +167 -0
  33. data/lib/legion/extensions/agentic/learning/curiosity/helpers/wonder.rb +73 -0
  34. data/lib/legion/extensions/agentic/learning/curiosity/helpers/wonder_store.rb +149 -0
  35. data/lib/legion/extensions/agentic/learning/curiosity/runners/curiosity.rb +163 -0
  36. data/lib/legion/extensions/agentic/learning/curiosity/version.rb +13 -0
  37. data/lib/legion/extensions/agentic/learning/curiosity.rb +21 -0
  38. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/client.rb +28 -0
  39. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/helpers/constants.rb +31 -0
  40. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/helpers/curiosity_engine.rb +122 -0
  41. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/helpers/knowledge_gap.rb +70 -0
  42. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity.rb +106 -0
  43. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/version.rb +13 -0
  44. data/lib/legion/extensions/agentic/learning/epistemic_curiosity.rb +19 -0
  45. data/lib/legion/extensions/agentic/learning/fermentation/client.rb +19 -0
  46. data/lib/legion/extensions/agentic/learning/fermentation/helpers/batch.rb +75 -0
  47. data/lib/legion/extensions/agentic/learning/fermentation/helpers/constants.rb +78 -0
  48. data/lib/legion/extensions/agentic/learning/fermentation/helpers/fermentation_engine.rb +147 -0
  49. data/lib/legion/extensions/agentic/learning/fermentation/helpers/substrate.rb +108 -0
  50. data/lib/legion/extensions/agentic/learning/fermentation/runners/cognitive_fermentation.rb +60 -0
  51. data/lib/legion/extensions/agentic/learning/fermentation/version.rb +13 -0
  52. data/lib/legion/extensions/agentic/learning/fermentation.rb +22 -0
  53. data/lib/legion/extensions/agentic/learning/habit/client.rb +26 -0
  54. data/lib/legion/extensions/agentic/learning/habit/helpers/action_sequence.rb +120 -0
  55. data/lib/legion/extensions/agentic/learning/habit/helpers/constants.rb +44 -0
  56. data/lib/legion/extensions/agentic/learning/habit/helpers/habit_store.rb +148 -0
  57. data/lib/legion/extensions/agentic/learning/habit/runners/habit.rb +86 -0
  58. data/lib/legion/extensions/agentic/learning/habit/version.rb +13 -0
  59. data/lib/legion/extensions/agentic/learning/habit.rb +19 -0
  60. data/lib/legion/extensions/agentic/learning/hebbian/actors/decay.rb +45 -0
  61. data/lib/legion/extensions/agentic/learning/hebbian/client.rb +29 -0
  62. data/lib/legion/extensions/agentic/learning/hebbian/helpers/assembly.rb +82 -0
  63. data/lib/legion/extensions/agentic/learning/hebbian/helpers/assembly_network.rb +190 -0
  64. data/lib/legion/extensions/agentic/learning/hebbian/helpers/constants.rb +50 -0
  65. data/lib/legion/extensions/agentic/learning/hebbian/helpers/unit.rb +94 -0
  66. data/lib/legion/extensions/agentic/learning/hebbian/runners/hebbian_assembly.rb +94 -0
  67. data/lib/legion/extensions/agentic/learning/hebbian/version.rb +13 -0
  68. data/lib/legion/extensions/agentic/learning/hebbian.rb +20 -0
  69. data/lib/legion/extensions/agentic/learning/learning_rate/client.rb +25 -0
  70. data/lib/legion/extensions/agentic/learning/learning_rate/helpers/constants.rb +35 -0
  71. data/lib/legion/extensions/agentic/learning/learning_rate/helpers/rate_model.rb +133 -0
  72. data/lib/legion/extensions/agentic/learning/learning_rate/runners/learning_rate.rb +85 -0
  73. data/lib/legion/extensions/agentic/learning/learning_rate/version.rb +13 -0
  74. data/lib/legion/extensions/agentic/learning/learning_rate.rb +18 -0
  75. data/lib/legion/extensions/agentic/learning/meta_learning/client.rb +27 -0
  76. data/lib/legion/extensions/agentic/learning/meta_learning/helpers/constants.rb +46 -0
  77. data/lib/legion/extensions/agentic/learning/meta_learning/helpers/learning_domain.rb +85 -0
  78. data/lib/legion/extensions/agentic/learning/meta_learning/helpers/meta_learning_engine.rb +202 -0
  79. data/lib/legion/extensions/agentic/learning/meta_learning/helpers/strategy.rb +62 -0
  80. data/lib/legion/extensions/agentic/learning/meta_learning/runners/meta_learning.rb +118 -0
  81. data/lib/legion/extensions/agentic/learning/meta_learning/version.rb +13 -0
  82. data/lib/legion/extensions/agentic/learning/meta_learning.rb +20 -0
  83. data/lib/legion/extensions/agentic/learning/plasticity/client.rb +15 -0
  84. data/lib/legion/extensions/agentic/learning/plasticity/helpers/constants.rb +45 -0
  85. data/lib/legion/extensions/agentic/learning/plasticity/helpers/neural_pathway.rb +85 -0
  86. data/lib/legion/extensions/agentic/learning/plasticity/helpers/plasticity_engine.rb +130 -0
  87. data/lib/legion/extensions/agentic/learning/plasticity/runners/cognitive_plasticity.rb +85 -0
  88. data/lib/legion/extensions/agentic/learning/plasticity/version.rb +13 -0
  89. data/lib/legion/extensions/agentic/learning/plasticity.rb +19 -0
  90. data/lib/legion/extensions/agentic/learning/preference_learning/actors/decay.rb +45 -0
  91. data/lib/legion/extensions/agentic/learning/preference_learning/client.rb +28 -0
  92. data/lib/legion/extensions/agentic/learning/preference_learning/helpers/constants.rb +35 -0
  93. data/lib/legion/extensions/agentic/learning/preference_learning/helpers/option.rb +78 -0
  94. data/lib/legion/extensions/agentic/learning/preference_learning/helpers/preference_engine.rb +121 -0
  95. data/lib/legion/extensions/agentic/learning/preference_learning/runners/preference_learning.rb +84 -0
  96. data/lib/legion/extensions/agentic/learning/preference_learning/version.rb +13 -0
  97. data/lib/legion/extensions/agentic/learning/preference_learning.rb +19 -0
  98. data/lib/legion/extensions/agentic/learning/procedural/client.rb +19 -0
  99. data/lib/legion/extensions/agentic/learning/procedural/helpers/constants.rb +46 -0
  100. data/lib/legion/extensions/agentic/learning/procedural/helpers/learning_engine.rb +160 -0
  101. data/lib/legion/extensions/agentic/learning/procedural/helpers/production.rb +66 -0
  102. data/lib/legion/extensions/agentic/learning/procedural/helpers/skill.rb +101 -0
  103. data/lib/legion/extensions/agentic/learning/procedural/runners/procedural_learning.rb +96 -0
  104. data/lib/legion/extensions/agentic/learning/procedural/version.rb +13 -0
  105. data/lib/legion/extensions/agentic/learning/procedural.rb +20 -0
  106. data/lib/legion/extensions/agentic/learning/scaffolding/client.rb +26 -0
  107. data/lib/legion/extensions/agentic/learning/scaffolding/helpers/constants.rb +42 -0
  108. data/lib/legion/extensions/agentic/learning/scaffolding/helpers/scaffold.rb +136 -0
  109. data/lib/legion/extensions/agentic/learning/scaffolding/helpers/scaffolding_engine.rb +112 -0
  110. data/lib/legion/extensions/agentic/learning/scaffolding/runners/cognitive_scaffolding.rb +107 -0
  111. data/lib/legion/extensions/agentic/learning/scaffolding/version.rb +13 -0
  112. data/lib/legion/extensions/agentic/learning/scaffolding.rb +19 -0
  113. data/lib/legion/extensions/agentic/learning/version.rb +11 -0
  114. data/lib/legion/extensions/agentic/learning.rb +31 -0
  115. data/spec/legion/extensions/agentic/learning/anchoring/client_spec.rb +32 -0
  116. data/spec/legion/extensions/agentic/learning/anchoring/helpers/anchor_spec.rb +130 -0
  117. data/spec/legion/extensions/agentic/learning/anchoring/helpers/anchor_store_spec.rb +201 -0
  118. data/spec/legion/extensions/agentic/learning/anchoring/helpers/constants_spec.rb +63 -0
  119. data/spec/legion/extensions/agentic/learning/anchoring/runners/anchoring_spec.rb +199 -0
  120. data/spec/legion/extensions/agentic/learning/catalyst/client_spec.rb +58 -0
  121. data/spec/legion/extensions/agentic/learning/catalyst/cognitive_catalyst_spec.rb +49 -0
  122. data/spec/legion/extensions/agentic/learning/catalyst/helpers/catalyst_engine_spec.rb +263 -0
  123. data/spec/legion/extensions/agentic/learning/catalyst/helpers/catalyst_spec.rb +214 -0
  124. data/spec/legion/extensions/agentic/learning/catalyst/helpers/reaction_spec.rb +223 -0
  125. data/spec/legion/extensions/agentic/learning/catalyst/runners/cognitive_catalyst_spec.rb +217 -0
  126. data/spec/legion/extensions/agentic/learning/chrysalis/client_spec.rb +83 -0
  127. data/spec/legion/extensions/agentic/learning/chrysalis/cognitive_chrysalis_spec.rb +15 -0
  128. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/chrysalis_engine_spec.rb +57 -0
  129. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/chrysalis_spec.rb +305 -0
  130. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/cocoon_spec.rb +206 -0
  131. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/constants_spec.rb +109 -0
  132. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/metamorphic_cycle_spec.rb +76 -0
  133. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/metamorphosis_engine_spec.rb +247 -0
  134. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/transformation_phase_spec.rb +98 -0
  135. data/spec/legion/extensions/agentic/learning/chrysalis/runners/cognitive_chrysalis_spec.rb +180 -0
  136. data/spec/legion/extensions/agentic/learning/chrysalis/runners/reporting_spec.rb +81 -0
  137. data/spec/legion/extensions/agentic/learning/chrysalis/runners/transformation_spec.rb +74 -0
  138. data/spec/legion/extensions/agentic/learning/curiosity/client_spec.rb +27 -0
  139. data/spec/legion/extensions/agentic/learning/curiosity/helpers/gap_detector_spec.rb +118 -0
  140. data/spec/legion/extensions/agentic/learning/curiosity/helpers/wonder_spec.rb +130 -0
  141. data/spec/legion/extensions/agentic/learning/curiosity/helpers/wonder_store_spec.rb +136 -0
  142. data/spec/legion/extensions/agentic/learning/curiosity/runners/curiosity_spec.rb +159 -0
  143. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/client_spec.rb +47 -0
  144. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/helpers/constants_spec.rb +45 -0
  145. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/helpers/curiosity_engine_spec.rb +229 -0
  146. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/helpers/knowledge_gap_spec.rb +188 -0
  147. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity_spec.rb +175 -0
  148. data/spec/legion/extensions/agentic/learning/fermentation/client_spec.rb +36 -0
  149. data/spec/legion/extensions/agentic/learning/fermentation/helpers/batch_spec.rb +72 -0
  150. data/spec/legion/extensions/agentic/learning/fermentation/helpers/fermentation_engine_spec.rb +138 -0
  151. data/spec/legion/extensions/agentic/learning/fermentation/helpers/substrate_spec.rb +146 -0
  152. data/spec/legion/extensions/agentic/learning/habit/client_spec.rb +50 -0
  153. data/spec/legion/extensions/agentic/learning/habit/helpers/action_sequence_spec.rb +276 -0
  154. data/spec/legion/extensions/agentic/learning/habit/helpers/constants_spec.rb +115 -0
  155. data/spec/legion/extensions/agentic/learning/habit/helpers/habit_store_spec.rb +274 -0
  156. data/spec/legion/extensions/agentic/learning/habit/runners/habit_spec.rb +228 -0
  157. data/spec/legion/extensions/agentic/learning/hebbian/client_spec.rb +38 -0
  158. data/spec/legion/extensions/agentic/learning/hebbian/helpers/assembly_network_spec.rb +142 -0
  159. data/spec/legion/extensions/agentic/learning/hebbian/helpers/assembly_spec.rb +89 -0
  160. data/spec/legion/extensions/agentic/learning/hebbian/helpers/unit_spec.rb +119 -0
  161. data/spec/legion/extensions/agentic/learning/hebbian/runners/hebbian_assembly_spec.rb +109 -0
  162. data/spec/legion/extensions/agentic/learning/learning_rate/client_spec.rb +51 -0
  163. data/spec/legion/extensions/agentic/learning/learning_rate/helpers/constants_spec.rb +29 -0
  164. data/spec/legion/extensions/agentic/learning/learning_rate/helpers/rate_model_spec.rb +151 -0
  165. data/spec/legion/extensions/agentic/learning/learning_rate/runners/learning_rate_spec.rb +92 -0
  166. data/spec/legion/extensions/agentic/learning/meta_learning/client_spec.rb +27 -0
  167. data/spec/legion/extensions/agentic/learning/meta_learning/helpers/constants_spec.rb +43 -0
  168. data/spec/legion/extensions/agentic/learning/meta_learning/helpers/learning_domain_spec.rb +146 -0
  169. data/spec/legion/extensions/agentic/learning/meta_learning/helpers/meta_learning_engine_spec.rb +309 -0
  170. data/spec/legion/extensions/agentic/learning/meta_learning/helpers/strategy_spec.rb +82 -0
  171. data/spec/legion/extensions/agentic/learning/meta_learning/runners/meta_learning_spec.rb +185 -0
  172. data/spec/legion/extensions/agentic/learning/plasticity/helpers/constants_spec.rb +54 -0
  173. data/spec/legion/extensions/agentic/learning/plasticity/helpers/neural_pathway_spec.rb +136 -0
  174. data/spec/legion/extensions/agentic/learning/plasticity/helpers/plasticity_engine_spec.rb +157 -0
  175. data/spec/legion/extensions/agentic/learning/plasticity/runners/cognitive_plasticity_spec.rb +83 -0
  176. data/spec/legion/extensions/agentic/learning/preference_learning/client_spec.rb +17 -0
  177. data/spec/legion/extensions/agentic/learning/preference_learning/helpers/constants_spec.rb +67 -0
  178. data/spec/legion/extensions/agentic/learning/preference_learning/helpers/option_spec.rb +104 -0
  179. data/spec/legion/extensions/agentic/learning/preference_learning/helpers/preference_engine_spec.rb +151 -0
  180. data/spec/legion/extensions/agentic/learning/preference_learning/runners/preference_learning_spec.rb +86 -0
  181. data/spec/legion/extensions/agentic/learning/procedural/client_spec.rb +22 -0
  182. data/spec/legion/extensions/agentic/learning/procedural/helpers/learning_engine_spec.rb +135 -0
  183. data/spec/legion/extensions/agentic/learning/procedural/helpers/production_spec.rb +66 -0
  184. data/spec/legion/extensions/agentic/learning/procedural/helpers/skill_spec.rb +102 -0
  185. data/spec/legion/extensions/agentic/learning/procedural/runners/procedural_learning_spec.rb +94 -0
  186. data/spec/legion/extensions/agentic/learning/scaffolding/client_spec.rb +20 -0
  187. data/spec/legion/extensions/agentic/learning/scaffolding/helpers/constants_spec.rb +36 -0
  188. data/spec/legion/extensions/agentic/learning/scaffolding/helpers/scaffold_spec.rb +187 -0
  189. data/spec/legion/extensions/agentic/learning/scaffolding/helpers/scaffolding_engine_spec.rb +159 -0
  190. data/spec/legion/extensions/agentic/learning/scaffolding/runners/cognitive_scaffolding_spec.rb +163 -0
  191. data/spec/spec_helper.rb +46 -0
  192. metadata +277 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Curiosity::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'initializes with a default wonder store' do
7
+ expect(client.wonder_store).to be_a(Legion::Extensions::Agentic::Learning::Curiosity::Helpers::WonderStore)
8
+ end
9
+
10
+ it 'accepts an injected store' do
11
+ custom_store = Legion::Extensions::Agentic::Learning::Curiosity::Helpers::WonderStore.new
12
+ client = described_class.new(store: custom_store)
13
+ expect(client.wonder_store).to be(custom_store)
14
+ end
15
+
16
+ it 'includes the Curiosity runner' do
17
+ expect(client).to respond_to(:detect_gaps)
18
+ expect(client).to respond_to(:generate_wonder)
19
+ expect(client).to respond_to(:explore_wonder)
20
+ expect(client).to respond_to(:resolve_wonder)
21
+ expect(client).to respond_to(:curiosity_intensity)
22
+ expect(client).to respond_to(:top_wonders)
23
+ expect(client).to respond_to(:form_agenda)
24
+ expect(client).to respond_to(:wonder_stats)
25
+ expect(client).to respond_to(:decay_wonders)
26
+ end
27
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Curiosity::Helpers::GapDetector do
4
+ describe '.detect' do
5
+ context 'with empty prior results' do
6
+ it 'returns empty array' do
7
+ expect(described_class.detect({})).to eq([])
8
+ end
9
+ end
10
+
11
+ context 'with memory gaps' do
12
+ it 'detects unknown domain when few traces returned' do
13
+ prior = {
14
+ memory_retrieval: { traces: [], domain: :kubernetes }
15
+ }
16
+ gaps = described_class.detect(prior)
17
+ expect(gaps.size).to be >= 1
18
+ unknown = gaps.find { |g| g[:gap_type] == :unknown }
19
+ expect(unknown).not_to be_nil
20
+ expect(unknown[:domain]).to eq(:kubernetes)
21
+ end
22
+
23
+ it 'detects incomplete knowledge with low-strength traces' do
24
+ prior = {
25
+ memory_retrieval: {
26
+ traces: [
27
+ { trace_id: 'a', strength: 0.1 },
28
+ { trace_id: 'b', strength: 0.2 },
29
+ { trace_id: 'c', strength: 0.9 }
30
+ ],
31
+ domain: :consul
32
+ }
33
+ }
34
+ gaps = described_class.detect(prior)
35
+ incomplete = gaps.find { |g| g[:gap_type] == :incomplete }
36
+ expect(incomplete).not_to be_nil
37
+ expect(incomplete[:source_trace_ids]).to contain_exactly('a', 'b')
38
+ end
39
+ end
40
+
41
+ context 'with prediction gaps' do
42
+ it 'detects low-confidence predictions' do
43
+ prior = {
44
+ prediction_engine: { confidence: 0.3, domain: :vault }
45
+ }
46
+ gaps = described_class.detect(prior)
47
+ uncertain = gaps.find { |g| g[:gap_type] == :uncertain }
48
+ expect(uncertain).not_to be_nil
49
+ expect(uncertain[:domain]).to eq(:vault)
50
+ end
51
+
52
+ it 'detects failed predictions' do
53
+ prior = {
54
+ prediction_engine: { status: :failed, domain: :terraform }
55
+ }
56
+ gaps = described_class.detect(prior)
57
+ uncertain = gaps.find { |g| g[:gap_type] == :uncertain }
58
+ expect(uncertain).not_to be_nil
59
+ expect(uncertain[:salience]).to eq(0.8)
60
+ end
61
+ end
62
+
63
+ context 'with emotional gaps' do
64
+ it 'detects novel unfamiliar stimuli' do
65
+ prior = {
66
+ emotional_evaluation: {
67
+ valence: { novelty: 0.9, familiarity: 0.1 },
68
+ domain: :new_domain
69
+ }
70
+ }
71
+ gaps = described_class.detect(prior)
72
+ unknown = gaps.find { |g| g[:gap_type] == :unknown }
73
+ expect(unknown).not_to be_nil
74
+ expect(unknown[:salience]).to eq(0.9)
75
+ end
76
+
77
+ it 'does not trigger for familiar novel stimuli' do
78
+ prior = {
79
+ emotional_evaluation: {
80
+ valence: { novelty: 0.9, familiarity: 0.8 }
81
+ }
82
+ }
83
+ gaps = described_class.detect(prior)
84
+ expect(gaps.select { |g| g[:gap_type] == :unknown }).to be_empty
85
+ end
86
+ end
87
+
88
+ context 'with contradiction gaps' do
89
+ it 'detects active conflicts' do
90
+ prior = {
91
+ contradiction_resolution: {
92
+ active_conflicts: [
93
+ { domain: :vault, trace_ids: %w[x y] }
94
+ ]
95
+ }
96
+ }
97
+ gaps = described_class.detect(prior)
98
+ contradictory = gaps.find { |g| g[:gap_type] == :contradictory }
99
+ expect(contradictory).not_to be_nil
100
+ expect(contradictory[:domain]).to eq(:vault)
101
+ end
102
+ end
103
+
104
+ context 'with information gain threshold' do
105
+ it 'filters out low-gain gaps' do
106
+ prior = {
107
+ memory_retrieval: { traces: [{ trace_id: 'a', strength: 0.25 }], domain: :general }
108
+ }
109
+ gaps = described_class.detect(prior)
110
+ # The incomplete gap has info gain 0.5 which is above threshold
111
+ # But there may be low-gain gaps filtered out
112
+ gaps.each do |g|
113
+ expect(g[:information_gain]).to be >= Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Constants::INFORMATION_GAIN_THRESHOLD
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder do
4
+ describe '.new_wonder' do
5
+ it 'creates a wonder with required fields' do
6
+ wonder = described_class.new_wonder(question: 'Why is the sky blue?')
7
+ expect(wonder[:wonder_id]).to be_a(String)
8
+ expect(wonder[:question]).to eq('Why is the sky blue?')
9
+ expect(wonder[:domain]).to eq(:general)
10
+ expect(wonder[:gap_type]).to eq(:unknown)
11
+ expect(wonder[:salience]).to eq(0.5)
12
+ expect(wonder[:information_gain]).to eq(0.5)
13
+ expect(wonder[:attempts]).to eq(0)
14
+ expect(wonder[:resolved]).to be false
15
+ expect(wonder[:created_at]).to be_a(Time)
16
+ end
17
+
18
+ it 'accepts custom fields' do
19
+ wonder = described_class.new_wonder(
20
+ question: 'Why do predictions fail here?',
21
+ domain: :terraform,
22
+ gap_type: :uncertain,
23
+ salience: 0.9,
24
+ information_gain: 0.8,
25
+ source_trace_ids: ['abc-123']
26
+ )
27
+ expect(wonder[:domain]).to eq(:terraform)
28
+ expect(wonder[:gap_type]).to eq(:uncertain)
29
+ expect(wonder[:salience]).to eq(0.9)
30
+ expect(wonder[:source_trace_ids]).to eq(['abc-123'])
31
+ end
32
+
33
+ it 'clamps salience to [0.0, 1.0]' do
34
+ wonder = described_class.new_wonder(question: 'test', salience: 1.5)
35
+ expect(wonder[:salience]).to eq(1.0)
36
+ end
37
+
38
+ it 'raises on invalid gap_type' do
39
+ expect { described_class.new_wonder(question: 'test', gap_type: :invalid) }
40
+ .to raise_error(ArgumentError, /invalid gap_type/)
41
+ end
42
+
43
+ it 'converts domain to symbol' do
44
+ wonder = described_class.new_wonder(question: 'test', domain: 'vault')
45
+ expect(wonder[:domain]).to eq(:vault)
46
+ end
47
+ end
48
+
49
+ describe '.score' do
50
+ it 'combines salience and information gain' do
51
+ wonder = described_class.new_wonder(question: 'test', salience: 1.0, information_gain: 1.0)
52
+ expect(described_class.score(wonder)).to eq(1.0)
53
+ end
54
+
55
+ it 'returns 0.0 for resolved wonders' do
56
+ wonder = described_class.new_wonder(question: 'test').merge(resolved: true)
57
+ expect(described_class.score(wonder)).to eq(0.0)
58
+ end
59
+
60
+ it 'applies attempt penalty' do
61
+ wonder = described_class.new_wonder(question: 'test', salience: 1.0, information_gain: 1.0)
62
+ score_fresh = described_class.score(wonder)
63
+ score_tried = described_class.score(wonder.merge(attempts: 3))
64
+ expect(score_tried).to be < score_fresh
65
+ end
66
+
67
+ it 'caps attempt penalty at 0.5' do
68
+ wonder = described_class.new_wonder(question: 'test', salience: 1.0, information_gain: 1.0)
69
+ score = described_class.score(wonder.merge(attempts: 100))
70
+ expect(score).to eq(0.5)
71
+ end
72
+ end
73
+
74
+ describe '.stale?' do
75
+ it 'returns false for recent wonders' do
76
+ wonder = described_class.new_wonder(question: 'test')
77
+ expect(described_class.stale?(wonder)).to be false
78
+ end
79
+
80
+ it 'returns true for old wonders' do
81
+ old_time = Time.now.utc - (Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Constants::WONDER_STALE_THRESHOLD + 1)
82
+ wonder = described_class.new_wonder(question: 'test').merge(created_at: old_time)
83
+ expect(described_class.stale?(wonder)).to be true
84
+ end
85
+ end
86
+
87
+ describe '.explorable?' do
88
+ it 'returns true for fresh wonders' do
89
+ wonder = described_class.new_wonder(question: 'test')
90
+ expect(described_class.explorable?(wonder)).to be true
91
+ end
92
+
93
+ it 'returns false for resolved wonders' do
94
+ wonder = described_class.new_wonder(question: 'test').merge(resolved: true)
95
+ expect(described_class.explorable?(wonder)).to be false
96
+ end
97
+
98
+ it 'returns false when max attempts reached' do
99
+ wonder = described_class.new_wonder(question: 'test')
100
+ .merge(attempts: Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Constants::MAX_EXPLORATION_ATTEMPTS)
101
+ expect(described_class.explorable?(wonder)).to be false
102
+ end
103
+
104
+ it 'respects exploration cooldown' do
105
+ wonder = described_class.new_wonder(question: 'test')
106
+ .merge(attempts: 1, last_explored_at: Time.now.utc)
107
+ expect(described_class.explorable?(wonder)).to be false
108
+ end
109
+ end
110
+
111
+ describe '.decay_salience' do
112
+ it 'reduces salience over time' do
113
+ wonder = described_class.new_wonder(question: 'test', salience: 1.0)
114
+ decayed = described_class.decay_salience(wonder, hours_elapsed: 5.0)
115
+ expect(decayed[:salience]).to be < 1.0
116
+ end
117
+
118
+ it 'does not go below 0.0' do
119
+ wonder = described_class.new_wonder(question: 'test', salience: 0.01)
120
+ decayed = described_class.decay_salience(wonder, hours_elapsed: 100.0)
121
+ expect(decayed[:salience]).to eq(0.0)
122
+ end
123
+
124
+ it 'skips resolved wonders' do
125
+ wonder = described_class.new_wonder(question: 'test', salience: 1.0).merge(resolved: true)
126
+ decayed = described_class.decay_salience(wonder, hours_elapsed: 100.0)
127
+ expect(decayed[:salience]).to eq(1.0)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Curiosity::Helpers::WonderStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ let(:wonder) do
7
+ Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder.new_wonder(
8
+ question: 'Why does this fail?',
9
+ domain: :terraform,
10
+ gap_type: :uncertain,
11
+ salience: 0.8
12
+ )
13
+ end
14
+
15
+ describe '#store' do
16
+ it 'stores and retrieves a wonder' do
17
+ store.store(wonder)
18
+ expect(store.get(wonder[:wonder_id])).to eq(wonder)
19
+ end
20
+
21
+ it 'increments total_generated' do
22
+ store.store(wonder)
23
+ expect(store.total_generated).to eq(1)
24
+ end
25
+
26
+ it 'prunes lowest-scored wonder when full' do
27
+ low_wonder = Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder.new_wonder(
28
+ question: 'low priority', salience: 0.01, information_gain: 0.01
29
+ )
30
+ store.store(low_wonder)
31
+
32
+ Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Constants::MAX_WONDERS.times do |i|
33
+ w = Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder.new_wonder(
34
+ question: "wonder #{i}", salience: 0.9
35
+ )
36
+ store.store(w)
37
+ end
38
+
39
+ expect(store.get(low_wonder[:wonder_id])).to be_nil
40
+ end
41
+ end
42
+
43
+ describe '#active_wonders and #resolved_wonders' do
44
+ it 'separates active from resolved' do
45
+ store.store(wonder)
46
+ expect(store.active_wonders.size).to eq(1)
47
+ expect(store.resolved_wonders.size).to eq(0)
48
+
49
+ store.mark_resolved(wonder[:wonder_id], resolution: 'found the answer')
50
+ expect(store.active_wonders.size).to eq(0)
51
+ expect(store.resolved_wonders.size).to eq(1)
52
+ end
53
+ end
54
+
55
+ describe '#top_balanced' do
56
+ it 'returns wonders sorted by score with domain balancing' do
57
+ 3.times do |i|
58
+ w = Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder.new_wonder(
59
+ question: "terraform #{i}", domain: :terraform, salience: 0.8
60
+ )
61
+ store.store(w)
62
+ end
63
+
64
+ other = Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder.new_wonder(
65
+ question: 'vault question', domain: :vault, salience: 0.7
66
+ )
67
+ store.store(other)
68
+
69
+ top = store.top_balanced(limit: 2)
70
+ domains = top.map { |w| w[:domain] }
71
+ expect(domains).to include(:vault)
72
+ end
73
+ end
74
+
75
+ describe '#mark_resolved' do
76
+ it 'marks wonder as resolved with resolution text' do
77
+ store.store(wonder)
78
+ resolved = store.mark_resolved(wonder[:wonder_id], resolution: 'TTL was 30 minutes', actual_gain: 0.8)
79
+ expect(resolved[:resolved]).to be true
80
+ expect(resolved[:resolution]).to eq('TTL was 30 minutes')
81
+ expect(store.resolved_count).to eq(1)
82
+ end
83
+
84
+ it 'returns nil for unknown wonder' do
85
+ expect(store.mark_resolved('nonexistent', resolution: 'nope')).to be_nil
86
+ end
87
+ end
88
+
89
+ describe '#decay_all' do
90
+ it 'reduces salience and prunes exhausted wonders' do
91
+ low = Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder.new_wonder(
92
+ question: 'fading', salience: 0.01
93
+ )
94
+ store.store(low)
95
+ pruned = store.decay_all(hours_elapsed: 10.0)
96
+ expect(pruned).to eq(1)
97
+ expect(store.get(low[:wonder_id])).to be_nil
98
+ end
99
+
100
+ it 'preserves resolved wonders' do
101
+ store.store(wonder)
102
+ store.mark_resolved(wonder[:wonder_id], resolution: 'done')
103
+ store.decay_all(hours_elapsed: 1000.0)
104
+ expect(store.get(wonder[:wonder_id])).not_to be_nil
105
+ end
106
+ end
107
+
108
+ describe '#domain_stats' do
109
+ it 'reports per-domain counts' do
110
+ store.store(wonder)
111
+ w2 = Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder.new_wonder(
112
+ question: 'another', domain: :terraform
113
+ )
114
+ store.store(w2)
115
+ store.mark_resolved(w2[:wonder_id], resolution: 'resolved')
116
+
117
+ stats = store.domain_stats
118
+ expect(stats[:terraform][:active]).to eq(1)
119
+ expect(stats[:terraform][:resolved]).to eq(1)
120
+ end
121
+ end
122
+
123
+ describe '#resolution_rate' do
124
+ it 'returns 0.0 when no wonders generated' do
125
+ expect(store.resolution_rate).to eq(0.0)
126
+ end
127
+
128
+ it 'computes fraction of resolved wonders' do
129
+ store.store(wonder)
130
+ w2 = Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Wonder.new_wonder(question: 'other')
131
+ store.store(w2)
132
+ store.mark_resolved(wonder[:wonder_id], resolution: 'done')
133
+ expect(store.resolution_rate).to eq(0.5)
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Curiosity::Runners::Curiosity do
4
+ let(:client) { Legion::Extensions::Agentic::Learning::Curiosity::Client.new }
5
+
6
+ describe '#detect_gaps' do
7
+ it 'returns gap detection results' do
8
+ result = client.detect_gaps(prior_results: {})
9
+ expect(result[:gaps_detected]).to eq(0)
10
+ expect(result[:wonders_created]).to eq(0)
11
+ expect(result[:curiosity_intensity]).to eq(0.0)
12
+ end
13
+
14
+ it 'creates wonders from detected gaps' do
15
+ prior = {
16
+ memory_retrieval: { traces: [], domain: :unknown_domain },
17
+ prediction_engine: { confidence: 0.2, domain: :uncertain_domain }
18
+ }
19
+ result = client.detect_gaps(prior_results: prior)
20
+ expect(result[:gaps_detected]).to be >= 2
21
+ expect(result[:wonders_created]).to be >= 1
22
+ expect(result[:curiosity_intensity]).to be > 0.0
23
+ end
24
+
25
+ it 'does not create duplicate wonders' do
26
+ prior = {
27
+ memory_retrieval: { traces: [], domain: :repeated }
28
+ }
29
+ client.detect_gaps(prior_results: prior)
30
+ result = client.detect_gaps(prior_results: prior)
31
+ expect(result[:wonders_created]).to eq(0)
32
+ end
33
+ end
34
+
35
+ describe '#generate_wonder' do
36
+ it 'manually creates a wonder' do
37
+ wonder = client.generate_wonder(
38
+ question: 'How does Consul ACL work?',
39
+ domain: :consul,
40
+ gap_type: :unknown
41
+ )
42
+ expect(wonder[:wonder_id]).to be_a(String)
43
+ expect(wonder[:question]).to eq('How does Consul ACL work?')
44
+ expect(wonder[:domain]).to eq(:consul)
45
+ end
46
+ end
47
+
48
+ describe '#explore_wonder' do
49
+ let(:wonder) do
50
+ client.generate_wonder(question: 'test?', domain: :test)
51
+ end
52
+
53
+ it 'marks a wonder as being explored' do
54
+ result = client.explore_wonder(wonder_id: wonder[:wonder_id])
55
+ expect(result[:exploring]).to be true
56
+ expect(result[:attempt]).to eq(1)
57
+ end
58
+
59
+ it 'returns error for unknown wonder' do
60
+ result = client.explore_wonder(wonder_id: 'nonexistent')
61
+ expect(result[:error]).to eq(:not_found)
62
+ end
63
+
64
+ it 'returns error for resolved wonder' do
65
+ client.resolve_wonder(wonder_id: wonder[:wonder_id], resolution: 'done')
66
+ result = client.explore_wonder(wonder_id: wonder[:wonder_id])
67
+ expect(result[:error]).to eq(:already_resolved)
68
+ end
69
+ end
70
+
71
+ describe '#resolve_wonder' do
72
+ let(:wonder) do
73
+ client.generate_wonder(question: 'test?', domain: :test, information_gain: 0.7)
74
+ end
75
+
76
+ it 'resolves with reward calculation' do
77
+ result = client.resolve_wonder(
78
+ wonder_id: wonder[:wonder_id],
79
+ resolution: 'Found the answer in the docs',
80
+ actual_gain: 0.8
81
+ )
82
+ expect(result[:resolved]).to be true
83
+ expect(result[:actual_gain]).to eq(0.8)
84
+ expect(result[:expected_gain]).to eq(0.7)
85
+ expect(result[:reward]).to eq(0.8 * Legion::Extensions::Agentic::Learning::Curiosity::Helpers::Constants::CURIOSITY_REWARD_MULTIPLIER)
86
+ end
87
+
88
+ it 'returns error for unknown wonder' do
89
+ result = client.resolve_wonder(wonder_id: 'nope', resolution: 'x')
90
+ expect(result[:error]).to eq(:not_found)
91
+ end
92
+
93
+ it 'returns error for already resolved wonder' do
94
+ client.resolve_wonder(wonder_id: wonder[:wonder_id], resolution: 'first')
95
+ result = client.resolve_wonder(wonder_id: wonder[:wonder_id], resolution: 'second')
96
+ expect(result[:error]).to eq(:already_resolved)
97
+ end
98
+ end
99
+
100
+ describe '#curiosity_intensity' do
101
+ it 'returns 0.0 with no wonders' do
102
+ result = client.curiosity_intensity
103
+ expect(result[:intensity]).to eq(0.0)
104
+ end
105
+
106
+ it 'increases with more wonders' do
107
+ 5.times { |i| client.generate_wonder(question: "q#{i}?", domain: :test) }
108
+ result = client.curiosity_intensity
109
+ expect(result[:intensity]).to be > 0.0
110
+ expect(result[:active_wonders]).to eq(5)
111
+ end
112
+ end
113
+
114
+ describe '#top_wonders' do
115
+ it 'returns top wonders by balanced score' do
116
+ client.generate_wonder(question: 'high?', domain: :a, salience: 0.9)
117
+ client.generate_wonder(question: 'low?', domain: :b, salience: 0.2)
118
+ result = client.top_wonders(limit: 5)
119
+ expect(result[:wonders].first[:question]).to eq('high?')
120
+ end
121
+ end
122
+
123
+ describe '#form_agenda' do
124
+ it 'converts top wonders to agenda items' do
125
+ client.generate_wonder(question: 'Why?', domain: :test)
126
+ result = client.form_agenda
127
+ expect(result[:agenda_items].size).to eq(1)
128
+ expect(result[:agenda_items].first[:type]).to eq(:curious)
129
+ expect(result[:agenda_items].first[:source]).to eq(:curiosity)
130
+ end
131
+
132
+ it 'returns empty agenda with no wonders' do
133
+ result = client.form_agenda
134
+ expect(result[:agenda_items]).to be_empty
135
+ end
136
+ end
137
+
138
+ describe '#wonder_stats' do
139
+ it 'returns comprehensive statistics' do
140
+ client.generate_wonder(question: 'q1?', domain: :a)
141
+ w = client.generate_wonder(question: 'q2?', domain: :b)
142
+ client.resolve_wonder(wonder_id: w[:wonder_id], resolution: 'done')
143
+
144
+ stats = client.wonder_stats
145
+ expect(stats[:total_generated]).to eq(2)
146
+ expect(stats[:active]).to eq(1)
147
+ expect(stats[:resolved]).to eq(1)
148
+ expect(stats[:resolution_rate]).to eq(0.5)
149
+ end
150
+ end
151
+
152
+ describe '#decay_wonders' do
153
+ it 'prunes exhausted wonders' do
154
+ client.generate_wonder(question: 'fading?', salience: 0.01)
155
+ result = client.decay_wonders(hours_elapsed: 10.0)
156
+ expect(result[:pruned]).to eq(1)
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/learning/epistemic_curiosity/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Client do
6
+ let(:client) { described_class.new }
7
+
8
+ it 'responds to create_gap' do
9
+ expect(client).to respond_to(:create_gap)
10
+ end
11
+
12
+ it 'responds to explore_gap' do
13
+ expect(client).to respond_to(:explore_gap)
14
+ end
15
+
16
+ it 'responds to satisfy_gap' do
17
+ expect(client).to respond_to(:satisfy_gap)
18
+ end
19
+
20
+ it 'responds to resolve_gap' do
21
+ expect(client).to respond_to(:resolve_gap)
22
+ end
23
+
24
+ it 'responds to most_urgent_gaps' do
25
+ expect(client).to respond_to(:most_urgent_gaps)
26
+ end
27
+
28
+ it 'responds to gaps_by_domain' do
29
+ expect(client).to respond_to(:gaps_by_domain)
30
+ end
31
+
32
+ it 'responds to gaps_by_type' do
33
+ expect(client).to respond_to(:gaps_by_type)
34
+ end
35
+
36
+ it 'responds to decay_gaps' do
37
+ expect(client).to respond_to(:decay_gaps)
38
+ end
39
+
40
+ it 'responds to curiosity_report' do
41
+ expect(client).to respond_to(:curiosity_report)
42
+ end
43
+
44
+ it 'responds to curiosity_status' do
45
+ expect(client).to respond_to(:curiosity_status)
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Helpers::Constants do
4
+ it 'defines MAX_GAPS' do
5
+ expect(described_class::MAX_GAPS).to eq(300)
6
+ end
7
+
8
+ it 'defines DEFAULT_URGENCY' do
9
+ expect(described_class::DEFAULT_URGENCY).to eq(0.5)
10
+ end
11
+
12
+ it 'defines URGENCY_BOOST' do
13
+ expect(described_class::URGENCY_BOOST).to eq(0.08)
14
+ end
15
+
16
+ it 'defines URGENCY_DECAY' do
17
+ expect(described_class::URGENCY_DECAY).to eq(0.03)
18
+ end
19
+
20
+ it 'defines SATISFACTION_THRESHOLD' do
21
+ expect(described_class::SATISFACTION_THRESHOLD).to eq(0.8)
22
+ end
23
+
24
+ it 'defines all five GAP_TYPES' do
25
+ expect(described_class::GAP_TYPES).to contain_exactly(:factual, :conceptual, :procedural, :causal, :relational)
26
+ end
27
+
28
+ it 'GAP_TYPES is frozen' do
29
+ expect(described_class::GAP_TYPES).to be_frozen
30
+ end
31
+
32
+ it 'URGENCY_LABELS covers burning range' do
33
+ entry = described_class::URGENCY_LABELS.find { |e| e[:label] == :burning }
34
+ expect(entry[:range]).to cover(0.9)
35
+ end
36
+
37
+ it 'URGENCY_LABELS covers satisfied range' do
38
+ entry = described_class::URGENCY_LABELS.find { |e| e[:label] == :satisfied }
39
+ expect(entry[:range]).to cover(0.1)
40
+ end
41
+
42
+ it 'URGENCY_LABELS has five entries' do
43
+ expect(described_class::URGENCY_LABELS.size).to eq(5)
44
+ end
45
+ end