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,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module PreferenceLearning
8
+ module Helpers
9
+ class PreferenceEngine
10
+ def initialize
11
+ @options = {} # id => Option
12
+ @comparisons = 0
13
+ end
14
+
15
+ def register_option(label:, domain: :general)
16
+ return { error: 'max options reached' } if @options.size >= Constants::MAX_OPTIONS
17
+
18
+ option = Option.new(label: label, domain: domain)
19
+ @options[option.id] = option
20
+ option.to_h
21
+ end
22
+
23
+ def record_comparison(winner_id:, loser_id:)
24
+ winner = @options[winner_id]
25
+ loser = @options[loser_id]
26
+ return { error: 'option not found' } unless winner && loser
27
+
28
+ @comparisons += 1
29
+ winner.win!
30
+ loser.lose!
31
+
32
+ {
33
+ winner_id: winner_id,
34
+ loser_id: loser_id,
35
+ comparisons: @comparisons,
36
+ winner_score: winner.preference_score,
37
+ loser_score: loser.preference_score
38
+ }
39
+ end
40
+
41
+ def predict_preference(option_a_id:, option_b_id:)
42
+ a = @options[option_a_id]
43
+ b = @options[option_b_id]
44
+ return { error: 'option not found' } unless a && b
45
+
46
+ diff = (a.preference_score - b.preference_score).abs
47
+ confidence = diff.clamp(0.0, 1.0)
48
+ preferred = a.preference_score >= b.preference_score ? a : b
49
+
50
+ {
51
+ preferred_id: preferred.id,
52
+ preferred_label: preferred.label,
53
+ confidence: confidence,
54
+ score_a: a.preference_score,
55
+ score_b: b.preference_score
56
+ }
57
+ end
58
+
59
+ def top_preferences(domain: nil, limit: 5)
60
+ filtered(domain).sort_by { |o| -o.preference_score }.first(limit).map(&:to_h)
61
+ end
62
+
63
+ def bottom_preferences(domain: nil, limit: 5)
64
+ filtered(domain).sort_by(&:preference_score).first(limit).map(&:to_h)
65
+ end
66
+
67
+ def preferences_by_domain(domain:)
68
+ @options.values
69
+ .select { |o| o.domain == domain }
70
+ .sort_by { |o| -o.preference_score }
71
+ .map(&:to_h)
72
+ end
73
+
74
+ def preference_stability
75
+ scores = @options.values.map(&:preference_score)
76
+ return 0.0 if scores.size < 2
77
+
78
+ mean = scores.sum / scores.size.to_f
79
+ variance = scores.sum { |s| (s - mean)**2 } / scores.size.to_f
80
+ Math.sqrt(variance)
81
+ end
82
+
83
+ def most_compared(limit: 10)
84
+ @options.values
85
+ .sort_by { |o| -o.times_seen }
86
+ .first(limit)
87
+ .map(&:to_h)
88
+ end
89
+
90
+ def decay_all
91
+ @options.each_value do |option|
92
+ delta = (option.preference_score - Constants::DEFAULT_PREFERENCE) * Constants::DECAY_RATE
93
+ option.preference_score = (option.preference_score - delta)
94
+ .clamp(Constants::PREFERENCE_FLOOR, Constants::PREFERENCE_CEILING)
95
+ end
96
+ @options.size
97
+ end
98
+
99
+ def to_h
100
+ {
101
+ total_options: @options.size,
102
+ comparisons: @comparisons,
103
+ stability: preference_stability,
104
+ options: @options.values.map(&:to_h)
105
+ }
106
+ end
107
+
108
+ private
109
+
110
+ def filtered(domain)
111
+ return @options.values if domain.nil?
112
+
113
+ @options.values.select { |o| o.domain == domain }
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module PreferenceLearning
8
+ module Runners
9
+ module PreferenceLearning
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ def register_preference_option(label:, domain: :general, **)
14
+ result = preference_engine.register_option(label: label, domain: domain)
15
+ if result[:error]
16
+ Legion::Logging.warn "[preference_learning] register failed: #{result[:error]}"
17
+ else
18
+ Legion::Logging.debug "[preference_learning] registered option id=#{result[:id]} label=#{label} domain=#{domain}"
19
+ end
20
+ result
21
+ end
22
+
23
+ def record_preference_comparison(winner_id:, loser_id:, **)
24
+ result = preference_engine.record_comparison(winner_id: winner_id, loser_id: loser_id)
25
+ if result[:error]
26
+ Legion::Logging.warn "[preference_learning] comparison failed: #{result[:error]}"
27
+ else
28
+ Legion::Logging.info "[preference_learning] comparison: winner=#{winner_id} loser=#{loser_id} total=#{result[:comparisons]}"
29
+ end
30
+ result
31
+ end
32
+
33
+ def predict_preference_outcome(option_a_id:, option_b_id:, **)
34
+ result = preference_engine.predict_preference(option_a_id: option_a_id, option_b_id: option_b_id)
35
+ if result[:error]
36
+ Legion::Logging.warn "[preference_learning] predict failed: #{result[:error]}"
37
+ else
38
+ Legion::Logging.debug "[preference_learning] predict: preferred=#{result[:preferred_label]} confidence=#{result[:confidence].round(2)}"
39
+ end
40
+ result
41
+ end
42
+
43
+ def top_preferences_report(domain: nil, limit: 5, **)
44
+ options = preference_engine.top_preferences(domain: domain, limit: limit)
45
+ Legion::Logging.debug "[preference_learning] top #{limit} preferences domain=#{domain.inspect} count=#{options.size}"
46
+ { domain: domain, limit: limit, options: options }
47
+ end
48
+
49
+ def preference_stability_report(**)
50
+ stability = preference_engine.preference_stability
51
+ label = stability < 0.1 ? :stable : :variable
52
+ Legion::Logging.debug "[preference_learning] stability=#{stability.round(4)} label=#{label}"
53
+ { stability: stability, label: label }
54
+ end
55
+
56
+ def update_preference_learning(**)
57
+ count = preference_engine.decay_all
58
+ Legion::Logging.debug "[preference_learning] decay cycle: options_updated=#{count}"
59
+ { decayed: count }
60
+ end
61
+
62
+ def preference_learning_stats(**)
63
+ engine_hash = preference_engine.to_h
64
+ Legion::Logging.debug "[preference_learning] stats: total_options=#{engine_hash[:total_options]} comparisons=#{engine_hash[:comparisons]}"
65
+ engine_hash.merge(stability_label: preference_engine_stability_label)
66
+ end
67
+
68
+ private
69
+
70
+ def preference_engine
71
+ @preference_engine ||= Helpers::PreferenceEngine.new
72
+ end
73
+
74
+ def preference_engine_stability_label
75
+ stability = preference_engine.preference_stability
76
+ stability < 0.1 ? :stable : :variable
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module PreferenceLearning
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/learning/preference_learning/version'
4
+ require 'legion/extensions/agentic/learning/preference_learning/helpers/constants'
5
+ require 'legion/extensions/agentic/learning/preference_learning/helpers/option'
6
+ require 'legion/extensions/agentic/learning/preference_learning/helpers/preference_engine'
7
+ require 'legion/extensions/agentic/learning/preference_learning/runners/preference_learning'
8
+ require 'legion/extensions/agentic/learning/preference_learning/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module Agentic
13
+ module Learning
14
+ module PreferenceLearning
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module Procedural
8
+ class Client
9
+ include Runners::ProceduralLearning
10
+
11
+ def initialize(engine: nil)
12
+ @engine = engine || Helpers::LearningEngine.new
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module Procedural
8
+ module Helpers
9
+ module Constants
10
+ MAX_SKILLS = 200
11
+ MAX_PRODUCTIONS = 500
12
+ MAX_HISTORY = 300
13
+
14
+ DEFAULT_PROFICIENCY = 0.1
15
+ PROFICIENCY_FLOOR = 0.0
16
+ PROFICIENCY_CEILING = 1.0
17
+
18
+ PRACTICE_GAIN = 0.08
19
+ COMPILATION_THRESHOLD = 0.6
20
+ AUTOMATION_THRESHOLD = 0.85
21
+ DECAY_RATE = 0.01
22
+ STALE_THRESHOLD = 300
23
+
24
+ SKILL_STAGES = %i[declarative associative autonomous].freeze
25
+
26
+ STAGE_LABELS = {
27
+ (0.0...0.3) => :declarative,
28
+ (0.3...0.6) => :associative,
29
+ (0.6...0.85) => :compiled,
30
+ (0.85..1.0) => :autonomous
31
+ }.freeze
32
+
33
+ PROFICIENCY_LABELS = {
34
+ (0.8..) => :expert,
35
+ (0.6...0.8) => :proficient,
36
+ (0.4...0.6) => :intermediate,
37
+ (0.2...0.4) => :beginner,
38
+ (..0.2) => :novice
39
+ }.freeze
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module Procedural
8
+ module Helpers
9
+ class LearningEngine
10
+ include Constants
11
+
12
+ attr_reader :history
13
+
14
+ def initialize
15
+ @skills = {}
16
+ @productions = {}
17
+ @history = []
18
+ end
19
+
20
+ def create_skill(name:, domain:)
21
+ evict_oldest_skill if @skills.size >= MAX_SKILLS
22
+
23
+ skill = Skill.new(name: name, domain: domain)
24
+ @skills[skill.id] = skill
25
+ record_history(:skill_created, skill.id)
26
+ skill
27
+ end
28
+
29
+ def add_production(skill_id:, condition:, action:, domain:)
30
+ skill = @skills[skill_id]
31
+ return { success: false, reason: :skill_not_found } unless skill
32
+ return { success: false, reason: :max_productions } if @productions.size >= MAX_PRODUCTIONS
33
+
34
+ production = Production.new(
35
+ condition: condition, action: action,
36
+ domain: domain, skill_id: skill_id
37
+ )
38
+ @productions[production.id] = production
39
+ skill.add_production(production.id)
40
+ record_history(:production_added, production.id)
41
+ production
42
+ end
43
+
44
+ def practice_skill(skill_id:, success:)
45
+ skill = @skills[skill_id]
46
+ return { success: false, reason: :not_found } unless skill
47
+
48
+ skill.practice!(success: success)
49
+ record_history(:practiced, skill_id)
50
+ build_practice_result(skill)
51
+ end
52
+
53
+ def execute_production(production_id:, success:)
54
+ production = @productions[production_id]
55
+ return { success: false, reason: :not_found } unless production
56
+
57
+ production.execute!(success: success)
58
+ record_history(:production_executed, production_id)
59
+ { success: true, production_id: production_id, success_rate: production.success_rate }
60
+ end
61
+
62
+ def skill_assessment(skill_id:)
63
+ skill = @skills[skill_id]
64
+ return { success: false, reason: :not_found } unless skill
65
+
66
+ productions = skill.productions.filter_map { |pid| @productions[pid] }
67
+ build_assessment(skill, productions)
68
+ end
69
+
70
+ def compiled_skills
71
+ @skills.values.select(&:compiled?)
72
+ end
73
+
74
+ def autonomous_skills
75
+ @skills.values.select(&:autonomous?)
76
+ end
77
+
78
+ def by_domain(domain:)
79
+ @skills.values.select { |s| s.domain == domain }
80
+ end
81
+
82
+ def most_practiced(limit: 5)
83
+ @skills.values.sort_by { |s| -s.practice_count }.first(limit)
84
+ end
85
+
86
+ def decay_all
87
+ @skills.each_value(&:decay!)
88
+ end
89
+
90
+ def prune_stale
91
+ stale_ids = @skills.select { |_id, s| s.proficiency <= 0.02 }.keys
92
+ stale_ids.each do |sid|
93
+ remove_skill_productions(sid)
94
+ @skills.delete(sid)
95
+ end
96
+ stale_ids.size
97
+ end
98
+
99
+ def to_h
100
+ {
101
+ total_skills: @skills.size,
102
+ total_productions: @productions.size,
103
+ compiled_count: compiled_skills.size,
104
+ autonomous_count: autonomous_skills.size,
105
+ history_count: @history.size,
106
+ stage_counts: stage_counts
107
+ }
108
+ end
109
+
110
+ private
111
+
112
+ def build_practice_result(skill)
113
+ {
114
+ success: true,
115
+ skill_id: skill.id,
116
+ proficiency: skill.proficiency,
117
+ stage: skill.stage,
118
+ stage_label: skill.stage_label
119
+ }
120
+ end
121
+
122
+ def build_assessment(skill, productions)
123
+ {
124
+ success: true,
125
+ skill: skill.to_h,
126
+ productions: productions.map(&:to_h),
127
+ reliable_count: productions.count(&:reliable?),
128
+ total_executions: productions.sum(&:execution_count)
129
+ }
130
+ end
131
+
132
+ def remove_skill_productions(skill_id)
133
+ @productions.delete_if { |_id, prod| prod.skill_id == skill_id }
134
+ end
135
+
136
+ def evict_oldest_skill
137
+ oldest_id = @skills.min_by { |_id, s| s.last_practiced_at }&.first
138
+ return unless oldest_id
139
+
140
+ remove_skill_productions(oldest_id)
141
+ @skills.delete(oldest_id)
142
+ end
143
+
144
+ def record_history(event, subject_id)
145
+ @history << { event: event, subject_id: subject_id, at: Time.now.utc }
146
+ @history.shift while @history.size > MAX_HISTORY
147
+ end
148
+
149
+ def stage_counts
150
+ @skills.values.each_with_object(Hash.new(0)) do |skill, counts|
151
+ counts[skill.stage] += 1
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Agentic
8
+ module Learning
9
+ module Procedural
10
+ module Helpers
11
+ class Production
12
+ include Constants
13
+
14
+ attr_reader :id, :condition, :action, :domain, :skill_id,
15
+ :execution_count, :success_count, :created_at, :last_executed_at
16
+
17
+ def initialize(condition:, action:, domain:, skill_id:)
18
+ @id = SecureRandom.uuid
19
+ @condition = condition
20
+ @action = action
21
+ @domain = domain
22
+ @skill_id = skill_id
23
+ @execution_count = 0
24
+ @success_count = 0
25
+ @created_at = Time.now.utc
26
+ @last_executed_at = @created_at
27
+ end
28
+
29
+ def execute!(success:)
30
+ @execution_count += 1
31
+ @success_count += 1 if success
32
+ @last_executed_at = Time.now.utc
33
+ end
34
+
35
+ def success_rate
36
+ return 0.0 if @execution_count.zero?
37
+
38
+ @success_count.to_f / @execution_count
39
+ end
40
+
41
+ def reliable?
42
+ success_rate >= 0.7 && @execution_count >= 3
43
+ end
44
+
45
+ def to_h
46
+ {
47
+ id: @id,
48
+ condition: @condition,
49
+ action: @action,
50
+ domain: @domain,
51
+ skill_id: @skill_id,
52
+ execution_count: @execution_count,
53
+ success_count: @success_count,
54
+ success_rate: success_rate,
55
+ reliable: reliable?,
56
+ created_at: @created_at,
57
+ last_executed_at: @last_executed_at
58
+ }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Agentic
8
+ module Learning
9
+ module Procedural
10
+ module Helpers
11
+ class Skill
12
+ include Constants
13
+
14
+ attr_reader :id, :name, :domain, :proficiency, :practice_count,
15
+ :stage, :productions, :created_at, :last_practiced_at
16
+
17
+ def initialize(name:, domain:)
18
+ @id = SecureRandom.uuid
19
+ @name = name
20
+ @domain = domain
21
+ @proficiency = DEFAULT_PROFICIENCY
22
+ @practice_count = 0
23
+ @stage = :declarative
24
+ @productions = []
25
+ @created_at = Time.now.utc
26
+ @last_practiced_at = @created_at
27
+ end
28
+
29
+ def practice!(success:)
30
+ @practice_count += 1
31
+ @last_practiced_at = Time.now.utc
32
+ gain = success ? PRACTICE_GAIN : PRACTICE_GAIN * 0.3
33
+ @proficiency = (@proficiency + gain).clamp(PROFICIENCY_FLOOR, PROFICIENCY_CEILING)
34
+ update_stage
35
+ end
36
+
37
+ def add_production(production_id)
38
+ @productions << production_id unless @productions.include?(production_id)
39
+ end
40
+
41
+ def compiled?
42
+ @proficiency >= COMPILATION_THRESHOLD
43
+ end
44
+
45
+ def autonomous?
46
+ @proficiency >= AUTOMATION_THRESHOLD
47
+ end
48
+
49
+ def stage_label
50
+ STAGE_LABELS.find { |range, _| range.cover?(@proficiency) }&.last || :declarative
51
+ end
52
+
53
+ def proficiency_label
54
+ PROFICIENCY_LABELS.find { |range, _| range.cover?(@proficiency) }&.last || :novice
55
+ end
56
+
57
+ def decay!
58
+ @proficiency = (@proficiency - DECAY_RATE).clamp(PROFICIENCY_FLOOR, PROFICIENCY_CEILING)
59
+ update_stage
60
+ end
61
+
62
+ def stale?
63
+ (Time.now.utc - @last_practiced_at) > STALE_THRESHOLD
64
+ end
65
+
66
+ def to_h
67
+ {
68
+ id: @id,
69
+ name: @name,
70
+ domain: @domain,
71
+ proficiency: @proficiency,
72
+ proficiency_label: proficiency_label,
73
+ stage: @stage,
74
+ stage_label: stage_label,
75
+ practice_count: @practice_count,
76
+ production_count: @productions.size,
77
+ compiled: compiled?,
78
+ autonomous: autonomous?,
79
+ created_at: @created_at,
80
+ last_practiced_at: @last_practiced_at
81
+ }
82
+ end
83
+
84
+ private
85
+
86
+ def update_stage
87
+ @stage = if autonomous?
88
+ :autonomous
89
+ elsif compiled?
90
+ :associative
91
+ else
92
+ :declarative
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end