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,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Helpers::CuriosityEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:basic_gap_args) { { question: 'What is X?', domain: :science, gap_type: :factual } }
7
+
8
+ describe '#create_gap' do
9
+ it 'creates a gap and returns created: true' do
10
+ result = engine.create_gap(**basic_gap_args)
11
+ expect(result[:created]).to be true
12
+ end
13
+
14
+ it 'returns the gap hash' do
15
+ result = engine.create_gap(**basic_gap_args)
16
+ expect(result[:gap]).to be_a(Hash)
17
+ expect(result[:gap][:question]).to eq('What is X?')
18
+ end
19
+
20
+ it 'assigns incremental ids' do
21
+ r1 = engine.create_gap(**basic_gap_args)
22
+ r2 = engine.create_gap(**basic_gap_args)
23
+ expect(r1[:gap][:id]).not_to eq(r2[:gap][:id])
24
+ end
25
+
26
+ it 'defaults gap_type to :factual for unknown type' do
27
+ result = engine.create_gap(question: 'q', domain: :d, gap_type: :unknown_type)
28
+ expect(result[:gap][:gap_type]).to eq(:factual)
29
+ end
30
+
31
+ it 'accepts custom urgency' do
32
+ result = engine.create_gap(**basic_gap_args, urgency: 0.9)
33
+ expect(result[:gap][:urgency]).to eq(0.9)
34
+ end
35
+ end
36
+
37
+ describe '#explore_gap' do
38
+ it 'returns found: false for unknown gap' do
39
+ result = engine.explore_gap(gap_id: :nonexistent)
40
+ expect(result[:found]).to be false
41
+ end
42
+
43
+ it 'increments explorations' do
44
+ id = engine.create_gap(**basic_gap_args)[:gap][:id]
45
+ result = engine.explore_gap(gap_id: id)
46
+ expect(result[:gap][:explorations]).to eq(1)
47
+ end
48
+
49
+ it 'boosts urgency' do
50
+ id = engine.create_gap(**basic_gap_args, urgency: 0.5)[:gap][:id]
51
+ after = engine.explore_gap(gap_id: id)[:gap][:urgency]
52
+ expect(after).to be > 0.5
53
+ end
54
+ end
55
+
56
+ describe '#satisfy_gap' do
57
+ it 'returns found: false for unknown gap' do
58
+ expect(engine.satisfy_gap(gap_id: :nope)[:found]).to be false
59
+ end
60
+
61
+ it 'increases satisfaction' do
62
+ id = engine.create_gap(**basic_gap_args)[:gap][:id]
63
+ engine.satisfy_gap(gap_id: id, amount: 0.4)
64
+ result = engine.satisfy_gap(gap_id: id, amount: 0.4)
65
+ expect(result[:gap][:satisfaction]).to be > 0.4
66
+ end
67
+ end
68
+
69
+ describe '#resolve_gap' do
70
+ it 'returns found: false for unknown gap' do
71
+ expect(engine.resolve_gap(gap_id: :nope)[:found]).to be false
72
+ end
73
+
74
+ it 'sets satisfaction to 1.0' do
75
+ id = engine.create_gap(**basic_gap_args)[:gap][:id]
76
+ engine.resolve_gap(gap_id: id)
77
+ gap = engine.resolved_gaps.first
78
+ expect(gap.satisfaction).to eq(1.0)
79
+ end
80
+
81
+ it 'moves gap to resolved_gaps' do
82
+ id = engine.create_gap(**basic_gap_args)[:gap][:id]
83
+ engine.resolve_gap(gap_id: id)
84
+ expect(engine.resolved_gaps.size).to eq(1)
85
+ end
86
+ end
87
+
88
+ describe '#open_gaps / #resolved_gaps' do
89
+ before do
90
+ engine.create_gap(**basic_gap_args)
91
+ id2 = engine.create_gap(**basic_gap_args)[:gap][:id]
92
+ engine.resolve_gap(gap_id: id2)
93
+ end
94
+
95
+ it 'open_gaps returns unresolved gaps' do
96
+ expect(engine.open_gaps.size).to eq(1)
97
+ end
98
+
99
+ it 'resolved_gaps returns resolved gaps' do
100
+ expect(engine.resolved_gaps.size).to eq(1)
101
+ end
102
+ end
103
+
104
+ describe '#most_urgent' do
105
+ it 'returns gaps sorted by urgency descending' do
106
+ engine.create_gap(**basic_gap_args, urgency: 0.3)
107
+ engine.create_gap(**basic_gap_args, urgency: 0.9)
108
+ engine.create_gap(**basic_gap_args, urgency: 0.6)
109
+ urgent = engine.most_urgent(limit: 2)
110
+ expect(urgent.first.urgency).to be >= urgent.last.urgency
111
+ end
112
+
113
+ it 'respects the limit' do
114
+ 5.times { engine.create_gap(**basic_gap_args) }
115
+ expect(engine.most_urgent(limit: 2).size).to eq(2)
116
+ end
117
+ end
118
+
119
+ describe '#by_domain' do
120
+ it 'filters by domain' do
121
+ engine.create_gap(question: 'q1', domain: :science, gap_type: :factual)
122
+ engine.create_gap(question: 'q2', domain: :history, gap_type: :factual)
123
+ expect(engine.by_domain(:science).size).to eq(1)
124
+ end
125
+ end
126
+
127
+ describe '#by_type' do
128
+ it 'filters by gap_type' do
129
+ engine.create_gap(question: 'q1', domain: :d, gap_type: :causal)
130
+ engine.create_gap(question: 'q2', domain: :d, gap_type: :factual)
131
+ expect(engine.by_type(:causal).size).to eq(1)
132
+ end
133
+ end
134
+
135
+ describe '#information_debt' do
136
+ it 'sums urgency of open gaps' do
137
+ engine.create_gap(**basic_gap_args, urgency: 0.4)
138
+ engine.create_gap(**basic_gap_args, urgency: 0.6)
139
+ expect(engine.information_debt).to be_within(0.01).of(1.0)
140
+ end
141
+
142
+ it 'excludes resolved gaps' do
143
+ id = engine.create_gap(**basic_gap_args, urgency: 0.8)[:gap][:id]
144
+ engine.resolve_gap(gap_id: id)
145
+ expect(engine.information_debt).to eq(0.0)
146
+ end
147
+ end
148
+
149
+ describe '#exploration_efficiency' do
150
+ it 'returns 0.0 with no gaps' do
151
+ expect(engine.exploration_efficiency).to eq(0.0)
152
+ end
153
+
154
+ it 'returns 1.0 when all gaps resolved' do
155
+ id = engine.create_gap(**basic_gap_args)[:gap][:id]
156
+ engine.resolve_gap(gap_id: id)
157
+ expect(engine.exploration_efficiency).to eq(1.0)
158
+ end
159
+
160
+ it 'returns 0.5 when half resolved' do
161
+ engine.create_gap(**basic_gap_args)
162
+ id2 = engine.create_gap(**basic_gap_args)[:gap][:id]
163
+ engine.resolve_gap(gap_id: id2)
164
+ expect(engine.exploration_efficiency).to be_within(0.01).of(0.5)
165
+ end
166
+ end
167
+
168
+ describe '#decay_all' do
169
+ it 'returns the number of gaps affected' do
170
+ 2.times { engine.create_gap(**basic_gap_args) }
171
+ expect(engine.decay_all).to eq(2)
172
+ end
173
+
174
+ it 'reduces urgency of open gaps' do
175
+ id = engine.create_gap(**basic_gap_args, urgency: 0.5)[:gap][:id]
176
+ before = engine.open_gaps.find { |g| g.id == id }.urgency
177
+ engine.decay_all
178
+ after = engine.open_gaps.find { |g| g.id == id }.urgency
179
+ expect(after).to be < before
180
+ end
181
+ end
182
+
183
+ describe '#prune_resolved' do
184
+ it 'removes resolved gaps' do
185
+ id = engine.create_gap(**basic_gap_args)[:gap][:id]
186
+ engine.resolve_gap(gap_id: id)
187
+ engine.prune_resolved
188
+ expect(engine.resolved_gaps.size).to eq(0)
189
+ end
190
+
191
+ it 'keeps open gaps' do
192
+ engine.create_gap(**basic_gap_args)
193
+ id2 = engine.create_gap(**basic_gap_args)[:gap][:id]
194
+ engine.resolve_gap(gap_id: id2)
195
+ engine.prune_resolved
196
+ expect(engine.open_gaps.size).to eq(1)
197
+ end
198
+ end
199
+
200
+ describe '#curiosity_report' do
201
+ it 'returns expected keys' do
202
+ report = engine.curiosity_report
203
+ expect(report.keys).to include(:total_gaps, :open_gaps, :resolved_gaps, :information_debt, :exploration_efficiency, :most_urgent)
204
+ end
205
+
206
+ it 'most_urgent is an array of hashes' do
207
+ engine.create_gap(**basic_gap_args)
208
+ expect(engine.curiosity_report[:most_urgent]).to be_an(Array)
209
+ expect(engine.curiosity_report[:most_urgent].first).to be_a(Hash)
210
+ end
211
+ end
212
+
213
+ describe '#to_h' do
214
+ it 'returns a hash with gaps key' do
215
+ engine.create_gap(**basic_gap_args)
216
+ result = engine.to_h
217
+ expect(result).to have_key(:gaps)
218
+ expect(result[:gaps]).to be_a(Hash)
219
+ end
220
+
221
+ it 'includes information_debt' do
222
+ expect(engine.to_h).to have_key(:information_debt)
223
+ end
224
+
225
+ it 'includes exploration_efficiency' do
226
+ expect(engine.to_h).to have_key(:exploration_efficiency)
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Helpers::KnowledgeGap do
4
+ subject(:gap) do
5
+ described_class.new(
6
+ id: :gap_1, # rubocop:disable Naming/VariableNumber
7
+ question: 'Why does the cache miss?',
8
+ domain: :infrastructure,
9
+ gap_type: :causal,
10
+ urgency: 0.5
11
+ )
12
+ end
13
+
14
+ describe '#initialize' do
15
+ it 'sets id' do
16
+ expect(gap.id).to eq(:gap_1) # rubocop:disable Naming/VariableNumber
17
+ end
18
+
19
+ it 'sets question' do
20
+ expect(gap.question).to eq('Why does the cache miss?')
21
+ end
22
+
23
+ it 'sets domain' do
24
+ expect(gap.domain).to eq(:infrastructure)
25
+ end
26
+
27
+ it 'sets gap_type' do
28
+ expect(gap.gap_type).to eq(:causal)
29
+ end
30
+
31
+ it 'sets urgency' do
32
+ expect(gap.urgency).to eq(0.5)
33
+ end
34
+
35
+ it 'starts satisfaction at 0.0' do
36
+ expect(gap.satisfaction).to eq(0.0)
37
+ end
38
+
39
+ it 'starts explorations at 0' do
40
+ expect(gap.explorations).to eq(0)
41
+ end
42
+
43
+ it 'sets created_at' do
44
+ expect(gap.created_at).to be_a(Time)
45
+ end
46
+
47
+ it 'resolved_at is nil initially' do
48
+ expect(gap.resolved_at).to be_nil
49
+ end
50
+
51
+ it 'clamps urgency above 1.0' do
52
+ g = described_class.new(id: :x, question: 'q', domain: :d, gap_type: :factual, urgency: 2.0)
53
+ expect(g.urgency).to eq(1.0)
54
+ end
55
+
56
+ it 'clamps urgency below 0.0' do
57
+ g = described_class.new(id: :x, question: 'q', domain: :d, gap_type: :factual, urgency: -1.0)
58
+ expect(g.urgency).to eq(0.0)
59
+ end
60
+ end
61
+
62
+ describe '#explore!' do
63
+ it 'increments explorations' do
64
+ gap.explore!
65
+ expect(gap.explorations).to eq(1)
66
+ end
67
+
68
+ it 'boosts urgency by URGENCY_BOOST' do
69
+ before = gap.urgency
70
+ gap.explore!
71
+ expect(gap.urgency).to be_within(0.001).of(before + Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Helpers::Constants::URGENCY_BOOST)
72
+ end
73
+
74
+ it 'returns self' do
75
+ expect(gap.explore!).to eq(gap)
76
+ end
77
+
78
+ it 'does not exceed 1.0 on urgency' do
79
+ g = described_class.new(id: :x, question: 'q', domain: :d, gap_type: :factual, urgency: 0.99)
80
+ g.explore!
81
+ expect(g.urgency).to be <= 1.0
82
+ end
83
+ end
84
+
85
+ describe '#satisfy!' do
86
+ it 'increases satisfaction' do
87
+ gap.satisfy!(amount: 0.3)
88
+ expect(gap.satisfaction).to be_within(0.001).of(0.3)
89
+ end
90
+
91
+ it 'returns self' do
92
+ expect(gap.satisfy!).to eq(gap)
93
+ end
94
+
95
+ it 'clamps satisfaction at 1.0' do
96
+ gap.satisfy!(amount: 0.6)
97
+ gap.satisfy!(amount: 0.6)
98
+ expect(gap.satisfaction).to eq(1.0)
99
+ end
100
+
101
+ it 'uses default amount 0.3' do
102
+ gap.satisfy!
103
+ expect(gap.satisfaction).to be_within(0.001).of(0.3)
104
+ end
105
+ end
106
+
107
+ describe '#resolved?' do
108
+ it 'returns false when satisfaction is low' do
109
+ expect(gap.resolved?).to be false
110
+ end
111
+
112
+ it 'returns true when satisfaction >= SATISFACTION_THRESHOLD' do
113
+ gap.satisfy!(amount: 1.0)
114
+ expect(gap.resolved?).to be true
115
+ end
116
+
117
+ it 'returns true at exactly SATISFACTION_THRESHOLD' do
118
+ gap.satisfaction = Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Helpers::Constants::SATISFACTION_THRESHOLD
119
+ expect(gap.resolved?).to be true
120
+ end
121
+ end
122
+
123
+ describe '#urgency_label' do
124
+ it 'returns :burning for urgency 0.9' do
125
+ gap.urgency = 0.9
126
+ expect(gap.urgency_label).to eq(:burning)
127
+ end
128
+
129
+ it 'returns :intense for urgency 0.7' do
130
+ gap.urgency = 0.7
131
+ expect(gap.urgency_label).to eq(:intense)
132
+ end
133
+
134
+ it 'returns :moderate for urgency 0.5' do
135
+ gap.urgency = 0.5
136
+ expect(gap.urgency_label).to eq(:moderate)
137
+ end
138
+
139
+ it 'returns :mild for urgency 0.3' do
140
+ gap.urgency = 0.3
141
+ expect(gap.urgency_label).to eq(:mild)
142
+ end
143
+
144
+ it 'returns :satisfied for urgency 0.1' do
145
+ gap.urgency = 0.1
146
+ expect(gap.urgency_label).to eq(:satisfied)
147
+ end
148
+ end
149
+
150
+ describe '#decay!' do
151
+ it 'decreases urgency by URGENCY_DECAY' do
152
+ before = gap.urgency
153
+ gap.decay!
154
+ expect(gap.urgency).to be_within(0.001).of(before - Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Helpers::Constants::URGENCY_DECAY)
155
+ end
156
+
157
+ it 'returns self' do
158
+ expect(gap.decay!).to eq(gap)
159
+ end
160
+
161
+ it 'does not go below 0.0' do
162
+ g = described_class.new(id: :x, question: 'q', domain: :d, gap_type: :factual, urgency: 0.01)
163
+ g.decay!
164
+ expect(g.urgency).to eq(0.0)
165
+ end
166
+ end
167
+
168
+ describe '#to_h' do
169
+ it 'returns a hash' do
170
+ expect(gap.to_h).to be_a(Hash)
171
+ end
172
+
173
+ it 'includes all expected keys' do
174
+ h = gap.to_h
175
+ expect(h.keys).to include(:id, :question, :domain, :gap_type, :urgency, :urgency_label, :satisfaction, :explorations, :resolved, :created_at,
176
+ :resolved_at)
177
+ end
178
+
179
+ it 'rounds urgency to 4 decimal places' do
180
+ gap.urgency = 0.123456789
181
+ expect(gap.to_h[:urgency]).to eq(0.1235)
182
+ end
183
+
184
+ it 'includes resolved status' do
185
+ expect(gap.to_h[:resolved]).to be false
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/learning/epistemic_curiosity/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Runners::EpistemicCuriosity do
6
+ let(:client) { Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Client.new }
7
+
8
+ let(:gap_args) { { question: 'What causes latency spikes?', domain: :infrastructure, gap_type: :causal } }
9
+
10
+ describe '#create_gap' do
11
+ it 'creates a gap with created: true' do
12
+ result = client.create_gap(**gap_args)
13
+ expect(result[:created]).to be true
14
+ end
15
+
16
+ it 'returns gap data with question' do
17
+ result = client.create_gap(**gap_args)
18
+ expect(result[:gap][:question]).to eq('What causes latency spikes?')
19
+ end
20
+
21
+ it 'uses default urgency when not specified' do
22
+ result = client.create_gap(**gap_args)
23
+ expect(result[:gap][:urgency]).to eq(Legion::Extensions::Agentic::Learning::EpistemicCuriosity::Helpers::Constants::DEFAULT_URGENCY)
24
+ end
25
+
26
+ it 'accepts custom urgency' do
27
+ result = client.create_gap(**gap_args, urgency: 0.9)
28
+ expect(result[:gap][:urgency]).to eq(0.9)
29
+ end
30
+ end
31
+
32
+ describe '#explore_gap' do
33
+ it 'returns found: false for unknown gap_id' do
34
+ result = client.explore_gap(gap_id: :nonexistent)
35
+ expect(result[:found]).to be false
36
+ end
37
+
38
+ it 'increments explorations for known gap' do
39
+ id = client.create_gap(**gap_args)[:gap][:id]
40
+ result = client.explore_gap(gap_id: id)
41
+ expect(result[:gap][:explorations]).to eq(1)
42
+ end
43
+
44
+ it 'boosts urgency on explore' do
45
+ id = client.create_gap(**gap_args, urgency: 0.4)[:gap][:id]
46
+ result = client.explore_gap(gap_id: id)
47
+ expect(result[:gap][:urgency]).to be > 0.4
48
+ end
49
+ end
50
+
51
+ describe '#satisfy_gap' do
52
+ it 'returns found: false for unknown gap_id' do
53
+ result = client.satisfy_gap(gap_id: :ghost)
54
+ expect(result[:found]).to be false
55
+ end
56
+
57
+ it 'increases satisfaction' do
58
+ id = client.create_gap(**gap_args)[:gap][:id]
59
+ result = client.satisfy_gap(gap_id: id, amount: 0.5)
60
+ expect(result[:gap][:satisfaction]).to be_within(0.001).of(0.5)
61
+ end
62
+
63
+ it 'shows resolved: true when satisfaction crosses threshold' do
64
+ id = client.create_gap(**gap_args)[:gap][:id]
65
+ result = client.satisfy_gap(gap_id: id, amount: 1.0)
66
+ expect(result[:gap][:resolved]).to be true
67
+ end
68
+ end
69
+
70
+ describe '#resolve_gap' do
71
+ it 'returns found: false for unknown gap_id' do
72
+ result = client.resolve_gap(gap_id: :ghost)
73
+ expect(result[:found]).to be false
74
+ end
75
+
76
+ it 'marks gap as resolved' do
77
+ id = client.create_gap(**gap_args)[:gap][:id]
78
+ result = client.resolve_gap(gap_id: id)
79
+ expect(result[:resolved]).to be true
80
+ end
81
+
82
+ it 'sets satisfaction to 1.0' do
83
+ id = client.create_gap(**gap_args)[:gap][:id]
84
+ client.resolve_gap(gap_id: id)
85
+ expect(client.curiosity_status[:resolved_gaps]).to eq(1)
86
+ end
87
+ end
88
+
89
+ describe '#most_urgent_gaps' do
90
+ it 'returns gaps and count' do
91
+ client.create_gap(**gap_args, urgency: 0.9)
92
+ result = client.most_urgent_gaps(limit: 5)
93
+ expect(result).to have_key(:gaps)
94
+ expect(result).to have_key(:count)
95
+ end
96
+
97
+ it 'respects limit' do
98
+ 3.times { client.create_gap(**gap_args) }
99
+ result = client.most_urgent_gaps(limit: 2)
100
+ expect(result[:count]).to eq(2)
101
+ end
102
+
103
+ it 'returns sorted by urgency descending' do
104
+ client.create_gap(**gap_args, urgency: 0.2)
105
+ client.create_gap(**gap_args, urgency: 0.8)
106
+ gaps = client.most_urgent_gaps[:gaps]
107
+ expect(gaps.first[:urgency]).to be >= gaps.last[:urgency]
108
+ end
109
+ end
110
+
111
+ describe '#gaps_by_domain' do
112
+ it 'returns gaps matching domain' do
113
+ client.create_gap(question: 'q1', domain: :infra, gap_type: :factual)
114
+ client.create_gap(question: 'q2', domain: :finance, gap_type: :factual)
115
+ result = client.gaps_by_domain(domain: :infra)
116
+ expect(result[:count]).to eq(1)
117
+ expect(result[:domain]).to eq(:infra)
118
+ end
119
+ end
120
+
121
+ describe '#gaps_by_type' do
122
+ it 'returns gaps matching type' do
123
+ client.create_gap(question: 'q1', domain: :d, gap_type: :causal)
124
+ client.create_gap(question: 'q2', domain: :d, gap_type: :relational)
125
+ result = client.gaps_by_type(gap_type: :causal)
126
+ expect(result[:count]).to eq(1)
127
+ expect(result[:gap_type]).to eq(:causal)
128
+ end
129
+ end
130
+
131
+ describe '#decay_gaps' do
132
+ it 'returns count of decayed gaps' do
133
+ client.create_gap(**gap_args)
134
+ result = client.decay_gaps
135
+ expect(result[:decayed]).to eq(1)
136
+ end
137
+
138
+ it 'reduces urgency of gaps' do
139
+ id = client.create_gap(**gap_args, urgency: 0.5)[:gap][:id]
140
+ before = client.most_urgent_gaps[:gaps].find { |g| g[:id] == id }[:urgency]
141
+ client.decay_gaps
142
+ after = client.most_urgent_gaps[:gaps].find { |g| g[:id] == id }[:urgency]
143
+ expect(after).to be < before
144
+ end
145
+ end
146
+
147
+ describe '#curiosity_report' do
148
+ it 'returns a report hash with expected keys' do
149
+ result = client.curiosity_report
150
+ expect(result.keys).to include(:total_gaps, :open_gaps, :resolved_gaps, :information_debt, :exploration_efficiency, :most_urgent)
151
+ end
152
+
153
+ it 'reflects current state' do
154
+ client.create_gap(**gap_args)
155
+ expect(client.curiosity_report[:open_gaps]).to eq(1)
156
+ end
157
+ end
158
+
159
+ describe '#curiosity_status' do
160
+ it 'returns status hash' do
161
+ result = client.curiosity_status
162
+ expect(result.keys).to include(:total_gaps, :open_gaps, :resolved_gaps, :information_debt, :exploration_efficiency)
163
+ end
164
+
165
+ it 'total_gaps reflects all gaps' do
166
+ 2.times { client.create_gap(**gap_args) }
167
+ expect(client.curiosity_status[:total_gaps]).to eq(2)
168
+ end
169
+
170
+ it 'information_debt is non-negative' do
171
+ client.create_gap(**gap_args, urgency: 0.7)
172
+ expect(client.curiosity_status[:information_debt]).to be >= 0.0
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Fermentation::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'responds to runner methods' do
7
+ expect(client).to respond_to(:create_substrate, :ferment, :catalyze, :fermentation_status)
8
+ end
9
+
10
+ it 'runs a full fermentation lifecycle' do
11
+ result = client.create_substrate(substrate_type: :raw_idea, domain: :cognitive, content: 'test')
12
+ sub_id = result[:substrate][:id]
13
+
14
+ client.catalyze(substrate_id: sub_id, catalyst_type: :analogy)
15
+ 10.times { client.ferment(substrate_id: sub_id) }
16
+
17
+ status = client.fermentation_status
18
+ expect(status[:total_substrates]).to eq(1)
19
+ expect(status[:success]).to be true
20
+ end
21
+
22
+ it 'lists ripe substrates' do
23
+ result = client.create_substrate(substrate_type: :raw_idea, domain: :cognitive, potency: 0.8)
24
+ sub_id = result[:substrate][:id]
25
+ 15.times { client.ferment(substrate_id: sub_id, rate: 0.05) }
26
+ ripe = client.list_ripe
27
+ expect(ripe[:success]).to be true
28
+ end
29
+
30
+ it 'ferments all substrates' do
31
+ client.create_substrate(substrate_type: :raw_idea, domain: :cognitive)
32
+ client.create_substrate(substrate_type: :raw_idea, domain: :emotional)
33
+ result = client.ferment_all
34
+ expect(result[:success]).to be true
35
+ end
36
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Fermentation::Helpers::Batch do
4
+ subject(:batch) { described_class.new(domain: :cognitive) }
5
+
6
+ let(:substrate_class) { Legion::Extensions::Agentic::Learning::Fermentation::Helpers::Substrate }
7
+
8
+ describe '#initialize' do
9
+ it 'assigns a uuid id' do
10
+ expect(batch.id).to match(/\A[0-9a-f-]{36}\z/)
11
+ end
12
+
13
+ it 'stores domain' do
14
+ expect(batch.domain).to eq(:cognitive)
15
+ end
16
+
17
+ it 'starts with empty substrates' do
18
+ expect(batch.substrates).to be_empty
19
+ end
20
+ end
21
+
22
+ describe '#add_substrate' do
23
+ it 'adds substrate to batch' do
24
+ sub = substrate_class.new(substrate_type: :raw_idea, domain: :cognitive)
25
+ batch.add_substrate(sub)
26
+ expect(batch.substrates.size).to eq(1)
27
+ end
28
+ end
29
+
30
+ describe '#ferment_all!' do
31
+ it 'ferments all substrates' do
32
+ sub = substrate_class.new(substrate_type: :raw_idea, domain: :cognitive)
33
+ batch.add_substrate(sub)
34
+ batch.ferment_all!
35
+ expect(sub.maturity).to be > 0.0
36
+ end
37
+ end
38
+
39
+ describe '#average_potency' do
40
+ it 'returns 0.0 for empty batch' do
41
+ expect(batch.average_potency).to eq(0.0)
42
+ end
43
+
44
+ it 'computes average' do
45
+ s1 = substrate_class.new(substrate_type: :raw_idea, domain: :cognitive, potency: 0.6)
46
+ s2 = substrate_class.new(substrate_type: :raw_idea, domain: :cognitive, potency: 0.4)
47
+ batch.add_substrate(s1)
48
+ batch.add_substrate(s2)
49
+ expect(batch.average_potency).to eq(0.5)
50
+ end
51
+ end
52
+
53
+ describe '#yield_rate' do
54
+ it 'returns 0.0 for empty batch' do
55
+ expect(batch.yield_rate).to eq(0.0)
56
+ end
57
+ end
58
+
59
+ describe '#ready_to_harvest?' do
60
+ it 'returns false when no ripe substrates' do
61
+ expect(batch).not_to be_ready_to_harvest
62
+ end
63
+ end
64
+
65
+ describe '#to_h' do
66
+ it 'returns hash with batch stats' do
67
+ h = batch.to_h
68
+ expect(h).to include(:id, :domain, :substrate_count, :average_potency,
69
+ :average_maturity, :ripe_count, :yield_rate)
70
+ end
71
+ end
72
+ end