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,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::PreferenceLearning::Helpers::Option do
4
+ subject(:option) { described_class.new(label: 'Option A', domain: :taste) }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a uuid id' do
8
+ expect(option.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'sets label and domain' do
12
+ expect(option.label).to eq('Option A')
13
+ expect(option.domain).to eq(:taste)
14
+ end
15
+
16
+ it 'starts with default preference score' do
17
+ expect(option.preference_score).to eq(0.5)
18
+ end
19
+
20
+ it 'starts with zero wins and losses' do
21
+ expect(option.wins).to eq(0)
22
+ expect(option.losses).to eq(0)
23
+ expect(option.times_seen).to eq(0)
24
+ end
25
+
26
+ it 'records created_at' do
27
+ expect(option.created_at).to be_a(Time)
28
+ end
29
+ end
30
+
31
+ describe '#win!' do
32
+ it 'increments wins and times_seen' do
33
+ option.win!
34
+ expect(option.wins).to eq(1)
35
+ expect(option.times_seen).to eq(1)
36
+ end
37
+
38
+ it 'increases preference score' do
39
+ before = option.preference_score
40
+ option.win!
41
+ expect(option.preference_score).to be > before
42
+ end
43
+
44
+ it 'does not exceed ceiling' do
45
+ 20.times { option.win! }
46
+ expect(option.preference_score).to be <= 1.0
47
+ end
48
+ end
49
+
50
+ describe '#lose!' do
51
+ it 'increments losses and times_seen' do
52
+ option.lose!
53
+ expect(option.losses).to eq(1)
54
+ expect(option.times_seen).to eq(1)
55
+ end
56
+
57
+ it 'decreases preference score' do
58
+ before = option.preference_score
59
+ option.lose!
60
+ expect(option.preference_score).to be < before
61
+ end
62
+
63
+ it 'does not go below floor' do
64
+ 20.times { option.lose! }
65
+ expect(option.preference_score).to be >= 0.0
66
+ end
67
+ end
68
+
69
+ describe '#win_rate' do
70
+ it 'returns 0.0 when no comparisons' do
71
+ expect(option.win_rate).to eq(0.0)
72
+ end
73
+
74
+ it 'returns a value between 0 and 1 after comparisons' do
75
+ option.win!
76
+ option.lose!
77
+ expect(option.win_rate).to be_between(0.0, 1.0)
78
+ end
79
+ end
80
+
81
+ describe '#preference_label' do
82
+ it 'returns :neutral for default score' do
83
+ expect(option.preference_label).to eq(:neutral)
84
+ end
85
+
86
+ it 'returns :strongly_preferred after many wins' do
87
+ 15.times { option.win! }
88
+ expect(option.preference_label).to eq(:strongly_preferred)
89
+ end
90
+
91
+ it 'returns :strongly_disliked after many losses' do
92
+ 15.times { option.lose! }
93
+ expect(option.preference_label).to eq(:strongly_disliked)
94
+ end
95
+ end
96
+
97
+ describe '#to_h' do
98
+ it 'includes all expected keys' do
99
+ h = option.to_h
100
+ expect(h.keys).to include(:id, :label, :domain, :preference_score, :wins, :losses,
101
+ :times_seen, :win_rate, :preference_label, :created_at)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::PreferenceLearning::Helpers::PreferenceEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:opt_a_id) { engine.register_option(label: 'Apple', domain: :food)[:id] }
7
+ let(:opt_b_id) { engine.register_option(label: 'Banana', domain: :food)[:id] }
8
+
9
+ describe '#register_option' do
10
+ it 'returns a hash with id, label, domain' do
11
+ result = engine.register_option(label: 'Cherry', domain: :food)
12
+ expect(result[:id]).to match(/\A[0-9a-f-]{36}\z/)
13
+ expect(result[:label]).to eq('Cherry')
14
+ expect(result[:domain]).to eq(:food)
15
+ end
16
+
17
+ it 'starts with default preference score' do
18
+ result = engine.register_option(label: 'Date')
19
+ expect(result[:preference_score]).to eq(0.5)
20
+ end
21
+ end
22
+
23
+ describe '#record_comparison' do
24
+ it 'increases winner score and decreases loser score' do
25
+ engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id)
26
+ top = engine.top_preferences(domain: :food, limit: 1)
27
+ expect(top.first[:id]).to eq(opt_a_id)
28
+ end
29
+
30
+ it 'returns comparison summary' do
31
+ result = engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id)
32
+ expect(result[:comparisons]).to eq(1)
33
+ expect(result[:winner_score]).to be > 0.5
34
+ expect(result[:loser_score]).to be < 0.5
35
+ end
36
+
37
+ it 'returns error for unknown option' do
38
+ result = engine.record_comparison(winner_id: 'bad-id', loser_id: opt_b_id)
39
+ expect(result[:error]).to eq('option not found')
40
+ end
41
+ end
42
+
43
+ describe '#predict_preference' do
44
+ it 'predicts winner after comparisons' do
45
+ 3.times { engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id) }
46
+ result = engine.predict_preference(option_a_id: opt_a_id, option_b_id: opt_b_id)
47
+ expect(result[:preferred_id]).to eq(opt_a_id)
48
+ end
49
+
50
+ it 'returns confidence as a 0..1 value' do
51
+ engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id)
52
+ result = engine.predict_preference(option_a_id: opt_a_id, option_b_id: opt_b_id)
53
+ expect(result[:confidence]).to be_between(0.0, 1.0)
54
+ end
55
+
56
+ it 'returns error for unknown option' do
57
+ result = engine.predict_preference(option_a_id: 'bad-id', option_b_id: opt_b_id)
58
+ expect(result[:error]).to eq('option not found')
59
+ end
60
+ end
61
+
62
+ describe '#top_preferences' do
63
+ it 'returns options sorted by score descending' do
64
+ engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id)
65
+ top = engine.top_preferences(domain: :food, limit: 2)
66
+ expect(top.first[:id]).to eq(opt_a_id)
67
+ end
68
+
69
+ it 'respects limit' do
70
+ 3.times { engine.register_option(label: "opt#{it}") }
71
+ top = engine.top_preferences(limit: 2)
72
+ expect(top.size).to be <= 2
73
+ end
74
+
75
+ it 'returns all domains when domain is nil' do
76
+ opt_a_id
77
+ engine.register_option(label: 'X', domain: :color)
78
+ result = engine.top_preferences(domain: nil, limit: 10)
79
+ domains = result.map { |o| o[:domain] }.uniq
80
+ expect(domains).to include(:food, :color)
81
+ end
82
+ end
83
+
84
+ describe '#bottom_preferences' do
85
+ it 'returns options sorted by score ascending' do
86
+ engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id)
87
+ bottom = engine.bottom_preferences(domain: :food, limit: 1)
88
+ expect(bottom.first[:id]).to eq(opt_b_id)
89
+ end
90
+ end
91
+
92
+ describe '#preferences_by_domain' do
93
+ it 'returns only options for given domain' do
94
+ fresh = described_class.new
95
+ fresh.register_option(label: 'Apple', domain: :food)
96
+ fresh.register_option(label: 'Red', domain: :color)
97
+ result = fresh.preferences_by_domain(domain: :food)
98
+ expect(result.map { |o| o[:domain] }.uniq).to eq([:food])
99
+ end
100
+ end
101
+
102
+ describe '#preference_stability' do
103
+ it 'returns 0.0 with fewer than 2 options' do
104
+ engine2 = described_class.new
105
+ engine2.register_option(label: 'Solo')
106
+ expect(engine2.preference_stability).to eq(0.0)
107
+ end
108
+
109
+ it 'returns a non-negative float with multiple options' do
110
+ opt_a_id
111
+ opt_b_id
112
+ 3.times { engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id) }
113
+ expect(engine.preference_stability).to be >= 0.0
114
+ end
115
+ end
116
+
117
+ describe '#most_compared' do
118
+ it 'returns options sorted by times_seen descending' do
119
+ 3.times { engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id) }
120
+ result = engine.most_compared(limit: 2)
121
+ expect(result.first[:times_seen]).to be >= result.last[:times_seen]
122
+ end
123
+ end
124
+
125
+ describe '#decay_all' do
126
+ it 'returns the count of options' do
127
+ opt_a_id
128
+ opt_b_id
129
+ expect(engine.decay_all).to eq(2)
130
+ end
131
+
132
+ it 'nudges scores toward default (0.5)' do
133
+ 3.times { engine.record_comparison(winner_id: opt_a_id, loser_id: opt_b_id) }
134
+ before_a = engine.top_preferences(domain: :food, limit: 1).first[:preference_score]
135
+ engine.decay_all
136
+ after_a = engine.top_preferences(domain: :food, limit: 1).first[:preference_score]
137
+ expect(after_a).to be < before_a
138
+ end
139
+ end
140
+
141
+ describe '#to_h' do
142
+ it 'includes total_options, comparisons, stability, options' do
143
+ opt_a_id
144
+ opt_b_id
145
+ h = engine.to_h
146
+ expect(h[:total_options]).to eq(2)
147
+ expect(h[:comparisons]).to eq(0)
148
+ expect(h[:options]).to be_an(Array)
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/learning/preference_learning/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Learning::PreferenceLearning::Runners::PreferenceLearning do
6
+ let(:client) { Legion::Extensions::Agentic::Learning::PreferenceLearning::Client.new }
7
+
8
+ let(:opt_a_id) { client.register_preference_option(label: 'Alpha', domain: :general)[:id] }
9
+ let(:opt_b_id) { client.register_preference_option(label: 'Beta', domain: :general)[:id] }
10
+
11
+ describe '#register_preference_option' do
12
+ it 'registers an option and returns id' do
13
+ result = client.register_preference_option(label: 'Gamma')
14
+ expect(result[:id]).not_to be_nil
15
+ expect(result[:label]).to eq('Gamma')
16
+ end
17
+ end
18
+
19
+ describe '#record_preference_comparison' do
20
+ it 'records winner/loser and returns comparison data' do
21
+ result = client.record_preference_comparison(winner_id: opt_a_id, loser_id: opt_b_id)
22
+ expect(result[:comparisons]).to eq(1)
23
+ expect(result[:winner_score]).to be > 0.5
24
+ end
25
+
26
+ it 'returns error for invalid ids' do
27
+ result = client.record_preference_comparison(winner_id: 'nope', loser_id: opt_b_id)
28
+ expect(result[:error]).to eq('option not found')
29
+ end
30
+ end
31
+
32
+ describe '#predict_preference_outcome' do
33
+ it 'returns preferred_id and confidence' do
34
+ 3.times { client.record_preference_comparison(winner_id: opt_a_id, loser_id: opt_b_id) }
35
+ result = client.predict_preference_outcome(option_a_id: opt_a_id, option_b_id: opt_b_id)
36
+ expect(result[:preferred_id]).to eq(opt_a_id)
37
+ expect(result[:confidence]).to be_between(0.0, 1.0)
38
+ end
39
+
40
+ it 'returns error for invalid id' do
41
+ result = client.predict_preference_outcome(option_a_id: 'bad', option_b_id: opt_b_id)
42
+ expect(result[:error]).to eq('option not found')
43
+ end
44
+ end
45
+
46
+ describe '#top_preferences_report' do
47
+ it 'returns domain, limit, and options array' do
48
+ opt_a_id
49
+ opt_b_id
50
+ result = client.top_preferences_report(domain: :general, limit: 5)
51
+ expect(result[:domain]).to eq(:general)
52
+ expect(result[:limit]).to eq(5)
53
+ expect(result[:options]).to be_an(Array)
54
+ end
55
+ end
56
+
57
+ describe '#preference_stability_report' do
58
+ it 'returns stability float and label' do
59
+ opt_a_id
60
+ opt_b_id
61
+ result = client.preference_stability_report
62
+ expect(result[:stability]).to be_a(Float)
63
+ expect(%i[stable variable]).to include(result[:label])
64
+ end
65
+ end
66
+
67
+ describe '#update_preference_learning' do
68
+ it 'returns decayed count' do
69
+ opt_a_id
70
+ opt_b_id
71
+ result = client.update_preference_learning
72
+ expect(result[:decayed]).to eq(2)
73
+ end
74
+ end
75
+
76
+ describe '#preference_learning_stats' do
77
+ it 'returns total_options, comparisons, stability, stability_label' do
78
+ opt_a_id
79
+ opt_b_id
80
+ result = client.preference_learning_stats
81
+ expect(result[:total_options]).to eq(2)
82
+ expect(result[:comparisons]).to eq(0)
83
+ expect(result[:stability_label]).not_to be_nil
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Procedural::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'creates a skill' do
7
+ result = client.create_skill(name: 'test', domain: :test)
8
+ expect(result[:success]).to be true
9
+ end
10
+
11
+ it 'practices and compiles a skill' do
12
+ created = client.create_skill(name: 'test', domain: :test)
13
+ 8.times { client.practice_skill(skill_id: created[:skill_id], success: true) }
14
+ compiled = client.compiled_skills
15
+ expect(compiled[:count]).to eq(1)
16
+ end
17
+
18
+ it 'returns stats' do
19
+ result = client.procedural_learning_stats
20
+ expect(result[:success]).to be true
21
+ end
22
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Procedural::Helpers::LearningEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:skill) { engine.create_skill(name: 'api_retry', domain: :http) }
7
+
8
+ describe '#create_skill' do
9
+ it 'creates and returns a skill' do
10
+ result = skill
11
+ expect(result).to be_a(Legion::Extensions::Agentic::Learning::Procedural::Helpers::Skill)
12
+ expect(result.name).to eq('api_retry')
13
+ end
14
+
15
+ it 'records history' do
16
+ skill
17
+ expect(engine.history.size).to eq(1)
18
+ end
19
+ end
20
+
21
+ describe '#add_production' do
22
+ it 'creates a production for a skill' do
23
+ result = engine.add_production(
24
+ skill_id: skill.id, condition: 'if_error',
25
+ action: 'retry', domain: :http
26
+ )
27
+ expect(result).to be_a(Legion::Extensions::Agentic::Learning::Procedural::Helpers::Production)
28
+ end
29
+
30
+ it 'returns error for unknown skill' do
31
+ result = engine.add_production(
32
+ skill_id: 'bad', condition: 'test',
33
+ action: 'test', domain: :test
34
+ )
35
+ expect(result[:success]).to be false
36
+ end
37
+ end
38
+
39
+ describe '#practice_skill' do
40
+ it 'increases proficiency' do
41
+ result = engine.practice_skill(skill_id: skill.id, success: true)
42
+ expect(result[:success]).to be true
43
+ expect(result[:proficiency]).to be > 0.1
44
+ end
45
+
46
+ it 'returns error for unknown skill' do
47
+ result = engine.practice_skill(skill_id: 'bad', success: true)
48
+ expect(result[:success]).to be false
49
+ end
50
+ end
51
+
52
+ describe '#execute_production' do
53
+ it 'executes and records outcome' do
54
+ prod = engine.add_production(
55
+ skill_id: skill.id, condition: 'test',
56
+ action: 'act', domain: :test
57
+ )
58
+ result = engine.execute_production(production_id: prod.id, success: true)
59
+ expect(result[:success]).to be true
60
+ expect(result[:success_rate]).to eq(1.0)
61
+ end
62
+ end
63
+
64
+ describe '#skill_assessment' do
65
+ it 'returns assessment with productions' do
66
+ prod = engine.add_production(
67
+ skill_id: skill.id, condition: 'test',
68
+ action: 'act', domain: :test
69
+ )
70
+ engine.execute_production(production_id: prod.id, success: true)
71
+ result = engine.skill_assessment(skill_id: skill.id)
72
+ expect(result[:success]).to be true
73
+ expect(result[:productions].size).to eq(1)
74
+ end
75
+ end
76
+
77
+ describe '#compiled_skills' do
78
+ it 'returns skills above compilation threshold' do
79
+ 8.times { engine.practice_skill(skill_id: skill.id, success: true) }
80
+ expect(engine.compiled_skills.size).to eq(1)
81
+ end
82
+ end
83
+
84
+ describe '#autonomous_skills' do
85
+ it 'returns skills above automation threshold' do
86
+ 12.times { engine.practice_skill(skill_id: skill.id, success: true) }
87
+ expect(engine.autonomous_skills.size).to eq(1)
88
+ end
89
+ end
90
+
91
+ describe '#by_domain' do
92
+ it 'filters by domain' do
93
+ skill
94
+ engine.create_skill(name: 'other', domain: :dns)
95
+ expect(engine.by_domain(domain: :http).size).to eq(1)
96
+ end
97
+ end
98
+
99
+ describe '#most_practiced' do
100
+ it 'returns skills sorted by practice count' do
101
+ 3.times { engine.practice_skill(skill_id: skill.id, success: true) }
102
+ other = engine.create_skill(name: 'other', domain: :dns)
103
+ engine.practice_skill(skill_id: other.id, success: true)
104
+ results = engine.most_practiced(limit: 2)
105
+ expect(results.first.practice_count).to be >= results.last.practice_count
106
+ end
107
+ end
108
+
109
+ describe '#decay_all' do
110
+ it 'reduces proficiency of all skills' do
111
+ 5.times { engine.practice_skill(skill_id: skill.id, success: true) }
112
+ original = skill.proficiency
113
+ engine.decay_all
114
+ expect(skill.proficiency).to be < original
115
+ end
116
+ end
117
+
118
+ describe '#prune_stale' do
119
+ it 'removes skills with near-zero proficiency' do
120
+ skill
121
+ 50.times { skill.decay! }
122
+ pruned = engine.prune_stale
123
+ expect(pruned).to be >= 0
124
+ end
125
+ end
126
+
127
+ describe '#to_h' do
128
+ it 'returns summary stats' do
129
+ skill
130
+ stats = engine.to_h
131
+ expect(stats[:total_skills]).to eq(1)
132
+ expect(stats).to include(:compiled_count, :autonomous_count, :stage_counts)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Procedural::Helpers::Production do
4
+ subject(:production) do
5
+ described_class.new(condition: 'if_error', action: 'retry_request', domain: :http, skill_id: 'skill-123')
6
+ end
7
+
8
+ describe '#initialize' do
9
+ it 'assigns a UUID' do
10
+ expect(production.id).to match(/\A[0-9a-f-]{36}\z/)
11
+ end
12
+
13
+ it 'stores condition and action' do
14
+ expect(production.condition).to eq('if_error')
15
+ expect(production.action).to eq('retry_request')
16
+ end
17
+
18
+ it 'starts with zero execution count' do
19
+ expect(production.execution_count).to eq(0)
20
+ end
21
+ end
22
+
23
+ describe '#execute!' do
24
+ it 'increments execution count' do
25
+ expect { production.execute!(success: true) }.to change(production, :execution_count).by(1)
26
+ end
27
+
28
+ it 'increments success count on success' do
29
+ expect { production.execute!(success: true) }.to change(production, :success_count).by(1)
30
+ end
31
+
32
+ it 'does not increment success count on failure' do
33
+ expect { production.execute!(success: false) }.not_to change(production, :success_count)
34
+ end
35
+ end
36
+
37
+ describe '#success_rate' do
38
+ it 'returns 0.0 when no executions' do
39
+ expect(production.success_rate).to eq(0.0)
40
+ end
41
+
42
+ it 'computes success ratio' do
43
+ 3.times { production.execute!(success: true) }
44
+ production.execute!(success: false)
45
+ expect(production.success_rate).to eq(0.75)
46
+ end
47
+ end
48
+
49
+ describe '#reliable?' do
50
+ it 'returns false initially' do
51
+ expect(production).not_to be_reliable
52
+ end
53
+
54
+ it 'returns true after sufficient successful executions' do
55
+ 5.times { production.execute!(success: true) }
56
+ expect(production).to be_reliable
57
+ end
58
+ end
59
+
60
+ describe '#to_h' do
61
+ it 'returns hash representation' do
62
+ hash = production.to_h
63
+ expect(hash).to include(:id, :condition, :action, :domain, :success_rate, :reliable)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Procedural::Helpers::Skill do
4
+ subject(:skill) { described_class.new(name: 'api_retry', domain: :http) }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a UUID' do
8
+ expect(skill.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'starts at declarative stage' do
12
+ expect(skill.stage).to eq(:declarative)
13
+ end
14
+
15
+ it 'starts with low proficiency' do
16
+ expect(skill.proficiency).to be < 0.2
17
+ end
18
+ end
19
+
20
+ describe '#practice!' do
21
+ it 'increases proficiency on success' do
22
+ original = skill.proficiency
23
+ skill.practice!(success: true)
24
+ expect(skill.proficiency).to be > original
25
+ end
26
+
27
+ it 'increases proficiency less on failure' do
28
+ success_skill = described_class.new(name: 'test', domain: :test)
29
+ failure_skill = described_class.new(name: 'test', domain: :test)
30
+ success_skill.practice!(success: true)
31
+ failure_skill.practice!(success: false)
32
+ expect(success_skill.proficiency).to be > failure_skill.proficiency
33
+ end
34
+
35
+ it 'transitions stage with practice' do
36
+ 10.times { skill.practice!(success: true) }
37
+ expect(skill.stage).not_to eq(:declarative)
38
+ end
39
+ end
40
+
41
+ describe '#compiled?' do
42
+ it 'returns false initially' do
43
+ expect(skill).not_to be_compiled
44
+ end
45
+
46
+ it 'returns true after sufficient practice' do
47
+ 8.times { skill.practice!(success: true) }
48
+ expect(skill).to be_compiled
49
+ end
50
+ end
51
+
52
+ describe '#autonomous?' do
53
+ it 'returns false initially' do
54
+ expect(skill).not_to be_autonomous
55
+ end
56
+
57
+ it 'returns true after extensive practice' do
58
+ 12.times { skill.practice!(success: true) }
59
+ expect(skill).to be_autonomous
60
+ end
61
+ end
62
+
63
+ describe '#stage_label' do
64
+ it 'returns a label symbol' do
65
+ expect(skill.stage_label).to be_a(Symbol)
66
+ end
67
+ end
68
+
69
+ describe '#proficiency_label' do
70
+ it 'returns a label symbol' do
71
+ expect(skill.proficiency_label).to be_a(Symbol)
72
+ end
73
+ end
74
+
75
+ describe '#decay!' do
76
+ it 'reduces proficiency' do
77
+ 5.times { skill.practice!(success: true) }
78
+ original = skill.proficiency
79
+ skill.decay!
80
+ expect(skill.proficiency).to be < original
81
+ end
82
+ end
83
+
84
+ describe '#add_production' do
85
+ it 'adds production id' do
86
+ skill.add_production('prod-123')
87
+ expect(skill.productions).to include('prod-123')
88
+ end
89
+
90
+ it 'does not add duplicates' do
91
+ 2.times { skill.add_production('prod-123') }
92
+ expect(skill.productions.size).to eq(1)
93
+ end
94
+ end
95
+
96
+ describe '#to_h' do
97
+ it 'returns hash representation' do
98
+ hash = skill.to_h
99
+ expect(hash).to include(:id, :name, :domain, :proficiency, :stage, :compiled, :autonomous)
100
+ end
101
+ end
102
+ end