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,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Anchoring::Helpers::Anchor do
4
+ subject(:anchor) { described_class.new(value: 100.0, domain: :financial) }
5
+
6
+ describe '#initialize' do
7
+ it 'sets value as float' do
8
+ expect(anchor.value).to eq(100.0)
9
+ end
10
+
11
+ it 'sets domain as symbol' do
12
+ expect(anchor.domain).to eq(:financial)
13
+ end
14
+
15
+ it 'sets initial strength to 1.0' do
16
+ expect(anchor.strength).to eq(1.0)
17
+ end
18
+
19
+ it 'generates a uuid id' do
20
+ expect(anchor.id).to match(/\A[0-9a-f-]{36}\z/)
21
+ end
22
+
23
+ it 'sets created_at' do
24
+ expect(anchor.created_at).to be_a(Time)
25
+ end
26
+
27
+ it 'sets last_accessed' do
28
+ expect(anchor.last_accessed).to be_a(Time)
29
+ end
30
+
31
+ it 'accepts integer value and converts to float' do
32
+ a = described_class.new(value: 50, domain: :general)
33
+ expect(a.value).to eq(50.0)
34
+ end
35
+
36
+ it 'defaults domain to :general when not specified' do
37
+ a = described_class.new(value: 10.0)
38
+ expect(a.domain).to eq(:general)
39
+ end
40
+ end
41
+
42
+ describe '#decay' do
43
+ it 'reduces strength by ANCHOR_DECAY' do
44
+ before = anchor.strength
45
+ anchor.decay
46
+ expect(anchor.strength).to be_within(0.001).of(before - Legion::Extensions::Agentic::Learning::Anchoring::Helpers::Constants::ANCHOR_DECAY)
47
+ end
48
+
49
+ it 'does not go below 0.0' do
50
+ 50.times { anchor.decay }
51
+ expect(anchor.strength).to eq(0.0)
52
+ end
53
+ end
54
+
55
+ describe '#reinforce' do
56
+ it 'updates the value toward observed_value via EMA' do
57
+ anchor.reinforce(observed_value: 200.0)
58
+ expect(anchor.value).to be > 100.0
59
+ expect(anchor.value).to be < 200.0
60
+ end
61
+
62
+ it 'bumps strength up (capped at 1.0)' do
63
+ anchor.decay
64
+ before = anchor.strength
65
+ anchor.reinforce(observed_value: 100.0)
66
+ expect(anchor.strength).to be > before
67
+ end
68
+
69
+ it 'updates last_accessed' do
70
+ before = anchor.last_accessed
71
+ sleep 0.001
72
+ anchor.reinforce(observed_value: 100.0)
73
+ expect(anchor.last_accessed).to be >= before
74
+ end
75
+ end
76
+
77
+ describe '#pull' do
78
+ it 'returns a value between anchor value and estimate' do
79
+ result = anchor.pull(estimate: 200.0)
80
+ expect(result).to be > 100.0
81
+ expect(result).to be < 200.0
82
+ end
83
+
84
+ it 'pulls estimate closer to anchor value' do
85
+ biased = anchor.pull(estimate: 200.0)
86
+ expect((biased - 100.0).abs).to be < (200.0 - 100.0).abs
87
+ end
88
+
89
+ it 'returns anchor value when estimate equals anchor value' do
90
+ result = anchor.pull(estimate: 100.0)
91
+ expect(result).to be_within(0.001).of(100.0)
92
+ end
93
+ end
94
+
95
+ describe '#label' do
96
+ it 'returns :strong at full strength' do
97
+ expect(anchor.label).to eq(:strong)
98
+ end
99
+
100
+ it 'returns :moderate at moderate strength' do
101
+ anchor.instance_variable_set(:@strength, 0.6)
102
+ expect(anchor.label).to eq(:moderate)
103
+ end
104
+
105
+ it 'returns :weak at weak strength' do
106
+ anchor.instance_variable_set(:@strength, 0.35)
107
+ expect(anchor.label).to eq(:weak)
108
+ end
109
+
110
+ it 'returns :fading at very low strength' do
111
+ anchor.instance_variable_set(:@strength, 0.1)
112
+ expect(anchor.label).to eq(:fading)
113
+ end
114
+ end
115
+
116
+ describe '#to_h' do
117
+ it 'returns a hash with all expected keys' do
118
+ h = anchor.to_h
119
+ expect(h).to include(:id, :value, :domain, :strength, :label, :created_at, :last_accessed)
120
+ end
121
+
122
+ it 'includes correct value' do
123
+ expect(anchor.to_h[:value]).to eq(100.0)
124
+ end
125
+
126
+ it 'includes label' do
127
+ expect(anchor.to_h[:label]).to eq(:strong)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Anchoring::Helpers::AnchorStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ describe '#add' do
7
+ it 'creates and returns an Anchor' do
8
+ anchor = store.add(value: 50.0, domain: :financial)
9
+ expect(anchor).to be_a(Legion::Extensions::Agentic::Learning::Anchoring::Helpers::Anchor)
10
+ end
11
+
12
+ it 'stores anchors per domain' do
13
+ store.add(value: 50.0, domain: :financial)
14
+ store.add(value: 80.0, domain: :financial)
15
+ store.add(value: 10.0, domain: :temporal)
16
+ expect(store.domains).to include(:financial, :temporal)
17
+ end
18
+
19
+ it 'enforces MAX_ANCHORS_PER_DOMAIN limit' do
20
+ max = Legion::Extensions::Agentic::Learning::Anchoring::Helpers::Constants::MAX_ANCHORS_PER_DOMAIN
21
+ (max + 5).times { |i| store.add(value: i.to_f, domain: :financial) }
22
+ all = store.instance_variable_get(:@anchors)[:financial]
23
+ expect(all.size).to be <= max
24
+ end
25
+
26
+ it 'accepts integer value' do
27
+ anchor = store.add(value: 100, domain: :general)
28
+ expect(anchor.value).to eq(100.0)
29
+ end
30
+ end
31
+
32
+ describe '#strongest' do
33
+ it 'returns nil for unknown domain' do
34
+ expect(store.strongest(domain: :unknown)).to be_nil
35
+ end
36
+
37
+ it 'returns the anchor with highest strength' do
38
+ a1 = store.add(value: 10.0, domain: :financial)
39
+ a2 = store.add(value: 20.0, domain: :financial)
40
+ a1.instance_variable_set(:@strength, 0.3)
41
+ a2.instance_variable_set(:@strength, 0.9)
42
+ expect(store.strongest(domain: :financial).value).to eq(20.0)
43
+ end
44
+ end
45
+
46
+ describe '#find' do
47
+ it 'finds anchor by id' do
48
+ anchor = store.add(value: 42.0, domain: :general)
49
+ found = store.find(id: anchor.id)
50
+ expect(found).to eq(anchor)
51
+ end
52
+
53
+ it 'returns nil for unknown id' do
54
+ expect(store.find(id: 'nonexistent')).to be_nil
55
+ end
56
+ end
57
+
58
+ describe '#evaluate' do
59
+ it 'returns no-pull result when domain has no anchors' do
60
+ result = store.evaluate(estimate: 100.0, domain: :empty)
61
+ expect(result[:pull_strength]).to eq(0.0)
62
+ expect(result[:anchored_estimate]).to eq(100.0)
63
+ expect(result[:anchor_value]).to be_nil
64
+ end
65
+
66
+ it 'returns biased estimate when anchor exists' do
67
+ store.add(value: 50.0, domain: :financial)
68
+ result = store.evaluate(estimate: 100.0, domain: :financial)
69
+ expect(result[:anchored_estimate]).to be > 50.0
70
+ expect(result[:anchored_estimate]).to be < 100.0
71
+ end
72
+
73
+ it 'includes pull_strength, anchor_value, correction keys' do
74
+ store.add(value: 50.0, domain: :financial)
75
+ result = store.evaluate(estimate: 100.0, domain: :financial)
76
+ expect(result).to include(:pull_strength, :anchor_value, :correction)
77
+ end
78
+
79
+ it 'correction is estimate minus anchored_estimate' do
80
+ store.add(value: 50.0, domain: :financial)
81
+ result = store.evaluate(estimate: 100.0, domain: :financial)
82
+ expect(result[:correction]).to be_within(0.001).of(100.0 - result[:anchored_estimate])
83
+ end
84
+ end
85
+
86
+ describe '#reference_frame' do
87
+ it 'returns neutral when no reference or anchor' do
88
+ result = store.reference_frame(value: 100.0, domain: :empty)
89
+ expect(result[:gain_or_loss]).to eq(:neutral)
90
+ expect(result[:magnitude]).to eq(0.0)
91
+ end
92
+
93
+ it 'detects gain when value above reference' do
94
+ store.shift_reference(domain: :financial, new_reference: 50.0)
95
+ result = store.reference_frame(value: 100.0, domain: :financial)
96
+ expect(result[:gain_or_loss]).to eq(:gain)
97
+ expect(result[:magnitude]).to be > 0
98
+ end
99
+
100
+ it 'detects loss when value below reference' do
101
+ store.shift_reference(domain: :financial, new_reference: 100.0)
102
+ result = store.reference_frame(value: 50.0, domain: :financial)
103
+ expect(result[:gain_or_loss]).to eq(:loss)
104
+ end
105
+
106
+ it 'applies loss aversion factor to losses' do
107
+ store.shift_reference(domain: :financial, new_reference: 100.0)
108
+ result = store.reference_frame(value: 50.0, domain: :financial)
109
+ raw_diff = 50.0
110
+ expected_magnitude = raw_diff * Legion::Extensions::Agentic::Learning::Anchoring::Helpers::Constants::LOSS_AVERSION_FACTOR
111
+ expect(result[:magnitude]).to be_within(0.001).of(expected_magnitude)
112
+ end
113
+
114
+ it 'does not apply loss aversion to gains' do
115
+ store.shift_reference(domain: :financial, new_reference: 50.0)
116
+ result = store.reference_frame(value: 100.0, domain: :financial)
117
+ expect(result[:magnitude]).to be_within(0.001).of(50.0)
118
+ end
119
+
120
+ it 'uses anchor as implicit reference when no explicit reference set' do
121
+ store.add(value: 50.0, domain: :financial)
122
+ result = store.reference_frame(value: 100.0, domain: :financial)
123
+ expect(result[:gain_or_loss]).to eq(:gain)
124
+ end
125
+ end
126
+
127
+ describe '#decay_all' do
128
+ it 'decays all anchors and returns pruned count' do
129
+ store.add(value: 10.0, domain: :financial)
130
+ pruned = store.decay_all
131
+ expect(pruned).to be >= 0
132
+ end
133
+
134
+ it 'prunes anchors that fall below ANCHOR_FLOOR' do
135
+ anchor = store.add(value: 10.0, domain: :financial)
136
+ anchor.instance_variable_set(:@strength, Legion::Extensions::Agentic::Learning::Anchoring::Helpers::Constants::ANCHOR_FLOOR - 0.001)
137
+ store.decay_all
138
+ expect(store.domains).not_to include(:financial)
139
+ end
140
+
141
+ it 'retains anchors above floor' do
142
+ store.add(value: 10.0, domain: :financial)
143
+ store.decay_all
144
+ all = store.instance_variable_get(:@anchors)[:financial]
145
+ expect(all).not_to be_nil
146
+ end
147
+ end
148
+
149
+ describe '#shift_reference' do
150
+ it 'sets new reference point for domain' do
151
+ store.shift_reference(domain: :financial, new_reference: 100.0)
152
+ refs = store.instance_variable_get(:@references)
153
+ expect(refs[:financial]).to eq(100.0)
154
+ end
155
+
156
+ it 'returns old and new reference' do
157
+ store.shift_reference(domain: :financial, new_reference: 50.0)
158
+ result = store.shift_reference(domain: :financial, new_reference: 100.0)
159
+ expect(result[:old_reference]).to eq(50.0)
160
+ expect(result[:new_reference]).to eq(100.0)
161
+ end
162
+
163
+ it 'marks shift as significant when diff >= REFERENCE_SHIFT_THRESHOLD' do
164
+ store.shift_reference(domain: :temporal, new_reference: 0.0)
165
+ result = store.shift_reference(domain: :temporal, new_reference: 1.0)
166
+ expect(result[:significant]).to be true
167
+ end
168
+
169
+ it 'marks shift as not significant when diff < REFERENCE_SHIFT_THRESHOLD' do
170
+ store.shift_reference(domain: :temporal, new_reference: 0.0)
171
+ result = store.shift_reference(domain: :temporal, new_reference: 0.1)
172
+ expect(result[:significant]).to be false
173
+ end
174
+ end
175
+
176
+ describe '#domains' do
177
+ it 'returns empty array when no anchors' do
178
+ expect(store.domains).to be_empty
179
+ end
180
+
181
+ it 'returns list of active domains' do
182
+ store.add(value: 1.0, domain: :financial)
183
+ store.add(value: 2.0, domain: :temporal)
184
+ expect(store.domains).to match_array(%i[financial temporal])
185
+ end
186
+ end
187
+
188
+ describe '#to_h' do
189
+ it 'returns summary hash' do
190
+ store.add(value: 10.0, domain: :financial)
191
+ h = store.to_h
192
+ expect(h).to include(:total_anchors, :domain_count, :domains, :references)
193
+ end
194
+
195
+ it 'reflects correct total_anchors count' do
196
+ store.add(value: 10.0, domain: :financial)
197
+ store.add(value: 20.0, domain: :financial)
198
+ expect(store.to_h[:total_anchors]).to eq(2)
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Anchoring::Helpers::Constants do
4
+ it 'defines DEFAULT_ANCHOR_WEIGHT as 0.6' do
5
+ expect(described_module::DEFAULT_ANCHOR_WEIGHT).to eq(0.6)
6
+ end
7
+
8
+ it 'defines ADJUSTMENT_RATE as 0.1' do
9
+ expect(described_module::ADJUSTMENT_RATE).to eq(0.1)
10
+ end
11
+
12
+ it 'defines ANCHOR_DECAY as 0.02' do
13
+ expect(described_module::ANCHOR_DECAY).to eq(0.02)
14
+ end
15
+
16
+ it 'defines ANCHOR_FLOOR as 0.05' do
17
+ expect(described_module::ANCHOR_FLOOR).to eq(0.05)
18
+ end
19
+
20
+ it 'defines MAX_ANCHORS_PER_DOMAIN as 20' do
21
+ expect(described_module::MAX_ANCHORS_PER_DOMAIN).to eq(20)
22
+ end
23
+
24
+ it 'defines MAX_DOMAINS as 50' do
25
+ expect(described_module::MAX_DOMAINS).to eq(50)
26
+ end
27
+
28
+ it 'defines REFERENCE_SHIFT_THRESHOLD as 0.3' do
29
+ expect(described_module::REFERENCE_SHIFT_THRESHOLD).to eq(0.3)
30
+ end
31
+
32
+ it 'defines LOSS_AVERSION_FACTOR as 2.25' do
33
+ expect(described_module::LOSS_AVERSION_FACTOR).to eq(2.25)
34
+ end
35
+
36
+ it 'defines ANCHOR_LABELS with 4 entries' do
37
+ expect(described_module::ANCHOR_LABELS.size).to eq(4)
38
+ end
39
+
40
+ it 'ANCHOR_LABELS maps high strength to :strong' do
41
+ label = described_module::ANCHOR_LABELS.find { |range, _| range.cover?(0.9) }&.last
42
+ expect(label).to eq(:strong)
43
+ end
44
+
45
+ it 'ANCHOR_LABELS maps mid strength to :moderate' do
46
+ label = described_module::ANCHOR_LABELS.find { |range, _| range.cover?(0.6) }&.last
47
+ expect(label).to eq(:moderate)
48
+ end
49
+
50
+ it 'ANCHOR_LABELS maps low strength to :weak' do
51
+ label = described_module::ANCHOR_LABELS.find { |range, _| range.cover?(0.3) }&.last
52
+ expect(label).to eq(:weak)
53
+ end
54
+
55
+ it 'ANCHOR_LABELS maps very low strength to :fading' do
56
+ label = described_module::ANCHOR_LABELS.find { |range, _| range.cover?(0.1) }&.last
57
+ expect(label).to eq(:fading)
58
+ end
59
+
60
+ def described_module
61
+ Legion::Extensions::Agentic::Learning::Anchoring::Helpers::Constants
62
+ end
63
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/learning/anchoring/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Learning::Anchoring::Runners::Anchoring do
6
+ let(:client) { Legion::Extensions::Agentic::Learning::Anchoring::Client.new }
7
+
8
+ describe '#record_anchor' do
9
+ it 'returns success: true' do
10
+ result = client.record_anchor(value: 100.0, domain: :financial)
11
+ expect(result[:success]).to be true
12
+ end
13
+
14
+ it 'returns anchor hash' do
15
+ result = client.record_anchor(value: 100.0, domain: :financial)
16
+ expect(result[:anchor]).to include(:id, :value, :domain, :strength)
17
+ end
18
+
19
+ it 'uses :general domain by default' do
20
+ result = client.record_anchor(value: 50.0)
21
+ expect(result[:anchor][:domain]).to eq(:general)
22
+ end
23
+
24
+ it 'accepts keyword splat' do
25
+ expect { client.record_anchor(value: 10.0, extra: :ignored) }.not_to raise_error
26
+ end
27
+ end
28
+
29
+ describe '#evaluate_estimate' do
30
+ it 'returns success: true' do
31
+ result = client.evaluate_estimate(estimate: 100.0, domain: :financial)
32
+ expect(result[:success]).to be true
33
+ end
34
+
35
+ it 'returns no-pull when domain has no anchors' do
36
+ result = client.evaluate_estimate(estimate: 100.0, domain: :noanchor)
37
+ expect(result[:pull_strength]).to eq(0.0)
38
+ expect(result[:anchored_estimate]).to eq(100.0)
39
+ end
40
+
41
+ it 'biases estimate toward anchor when anchor exists' do
42
+ client.record_anchor(value: 50.0, domain: :financial)
43
+ result = client.evaluate_estimate(estimate: 100.0, domain: :financial)
44
+ expect(result[:anchored_estimate]).to be < 100.0
45
+ expect(result[:anchored_estimate]).to be > 50.0
46
+ end
47
+
48
+ it 'includes pull_strength, anchor_value, correction' do
49
+ client.record_anchor(value: 50.0, domain: :financial)
50
+ result = client.evaluate_estimate(estimate: 100.0, domain: :financial)
51
+ expect(result).to include(:pull_strength, :anchor_value, :correction)
52
+ end
53
+ end
54
+
55
+ describe '#reference_frame' do
56
+ it 'returns success: true' do
57
+ result = client.reference_frame(value: 100.0, domain: :financial)
58
+ expect(result[:success]).to be true
59
+ end
60
+
61
+ it 'returns neutral for domain without reference' do
62
+ result = client.reference_frame(value: 100.0, domain: :empty_domain)
63
+ expect(result[:gain_or_loss]).to eq(:neutral)
64
+ end
65
+
66
+ it 'detects gain after setting reference' do
67
+ client.shift_reference(domain: :financial, new_reference: 50.0)
68
+ result = client.reference_frame(value: 100.0, domain: :financial)
69
+ expect(result[:gain_or_loss]).to eq(:gain)
70
+ end
71
+
72
+ it 'detects loss after setting reference' do
73
+ client.shift_reference(domain: :financial, new_reference: 100.0)
74
+ result = client.reference_frame(value: 50.0, domain: :financial)
75
+ expect(result[:gain_or_loss]).to eq(:loss)
76
+ end
77
+
78
+ it 'applies loss aversion factor to losses' do
79
+ client.shift_reference(domain: :financial, new_reference: 100.0)
80
+ result = client.reference_frame(value: 50.0, domain: :financial)
81
+ expect(result[:magnitude]).to be_within(0.001).of(50.0 * 2.25)
82
+ end
83
+ end
84
+
85
+ describe '#de_anchor' do
86
+ it 'returns success: true' do
87
+ result = client.de_anchor(estimate: 100.0, domain: :empty_de)
88
+ expect(result[:success]).to be true
89
+ end
90
+
91
+ it 'returns original estimate with zero bias when no anchor' do
92
+ result = client.de_anchor(estimate: 100.0, domain: :no_anchor_domain)
93
+ expect(result[:corrected_estimate]).to eq(100.0)
94
+ expect(result[:anchor_bias]).to eq(0.0)
95
+ end
96
+
97
+ it 'returns corrected estimate that removes anchor bias' do
98
+ client.record_anchor(value: 50.0, domain: :financial)
99
+ result = client.de_anchor(estimate: 100.0, domain: :financial)
100
+ expect(result[:corrected_estimate]).to be > 100.0
101
+ end
102
+
103
+ it 'returns anchor_value when anchor present' do
104
+ client.record_anchor(value: 50.0, domain: :financial)
105
+ result = client.de_anchor(estimate: 100.0, domain: :financial)
106
+ expect(result[:anchor_value]).to eq(50.0)
107
+ end
108
+
109
+ it 'includes original_estimate' do
110
+ client.record_anchor(value: 50.0, domain: :financial)
111
+ result = client.de_anchor(estimate: 100.0, domain: :financial)
112
+ expect(result[:original_estimate]).to eq(100.0)
113
+ end
114
+ end
115
+
116
+ describe '#shift_reference' do
117
+ it 'returns success: true' do
118
+ result = client.shift_reference(domain: :financial, new_reference: 100.0)
119
+ expect(result[:success]).to be true
120
+ end
121
+
122
+ it 'includes domain, old_reference, new_reference, significant' do
123
+ result = client.shift_reference(domain: :financial, new_reference: 100.0)
124
+ expect(result).to include(:domain, :old_reference, :new_reference, :significant)
125
+ end
126
+
127
+ it 'marks large shift as significant' do
128
+ client.shift_reference(domain: :financial, new_reference: 0.0)
129
+ result = client.shift_reference(domain: :financial, new_reference: 1.0)
130
+ expect(result[:significant]).to be true
131
+ end
132
+ end
133
+
134
+ describe '#update_anchoring' do
135
+ it 'returns success: true' do
136
+ result = client.update_anchoring
137
+ expect(result[:success]).to be true
138
+ end
139
+
140
+ it 'returns pruned count' do
141
+ result = client.update_anchoring
142
+ expect(result).to have_key(:pruned)
143
+ end
144
+
145
+ it 'decays existing anchors' do
146
+ client.record_anchor(value: 100.0, domain: :financial)
147
+ anchor = client.anchor_store.strongest(domain: :financial)
148
+ before_strength = anchor.strength
149
+ client.update_anchoring
150
+ expect(anchor.strength).to be < before_strength
151
+ end
152
+ end
153
+
154
+ describe '#domain_anchors' do
155
+ it 'returns success: true' do
156
+ result = client.domain_anchors(domain: :financial)
157
+ expect(result[:success]).to be true
158
+ end
159
+
160
+ it 'returns empty anchors for unknown domain' do
161
+ result = client.domain_anchors(domain: :no_such_domain)
162
+ expect(result[:count]).to eq(0)
163
+ expect(result[:anchors]).to eq([])
164
+ end
165
+
166
+ it 'returns anchors for known domain' do
167
+ client.record_anchor(value: 100.0, domain: :financial)
168
+ result = client.domain_anchors(domain: :financial)
169
+ expect(result[:count]).to eq(1)
170
+ expect(result[:anchors].first[:value]).to eq(100.0)
171
+ end
172
+
173
+ it 'includes strongest anchor' do
174
+ client.record_anchor(value: 100.0, domain: :financial)
175
+ result = client.domain_anchors(domain: :financial)
176
+ expect(result[:strongest]).not_to be_nil
177
+ end
178
+ end
179
+
180
+ describe '#anchoring_stats' do
181
+ it 'returns success: true' do
182
+ result = client.anchoring_stats
183
+ expect(result[:success]).to be true
184
+ end
185
+
186
+ it 'includes total_anchors, domain_count, domains' do
187
+ result = client.anchoring_stats
188
+ expect(result).to include(:total_anchors, :domain_count, :domains)
189
+ end
190
+
191
+ it 'reflects anchor counts' do
192
+ client.record_anchor(value: 10.0, domain: :financial)
193
+ client.record_anchor(value: 20.0, domain: :temporal)
194
+ result = client.anchoring_stats
195
+ expect(result[:total_anchors]).to eq(2)
196
+ expect(result[:domain_count]).to eq(2)
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Catalyst::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'includes Runners::CognitiveCatalyst' do
7
+ expect(client).to respond_to(:create_catalyst)
8
+ expect(client).to respond_to(:create_reaction)
9
+ expect(client).to respond_to(:apply_catalyst)
10
+ expect(client).to respond_to(:attempt_reaction)
11
+ expect(client).to respond_to(:recharge)
12
+ expect(client).to respond_to(:list_catalysts)
13
+ expect(client).to respond_to(:catalyst_status)
14
+ end
15
+
16
+ it 'can create and then apply a catalyst to a reaction' do
17
+ engine = Legion::Extensions::Agentic::Learning::Catalyst::Helpers::CatalystEngine.new
18
+ cat = client.create_catalyst(catalyst_type: :experience, domain: :learning,
19
+ potency: 0.9, specificity: 0.9, engine: engine)
20
+ rxn = client.create_reaction(reaction_type: :synthesis, reactants: %w[a b], engine: engine)
21
+ result = client.apply_catalyst(catalyst_id: cat[:catalyst][:id],
22
+ reaction_id: rxn[:reaction][:id],
23
+ engine: engine)
24
+ expect(result[:success]).to be true
25
+ expect(result[:activation_energy]).to be < 0.6
26
+ end
27
+
28
+ it 'can complete a full catalyst workflow: create -> apply -> attempt -> report' do
29
+ engine = Legion::Extensions::Agentic::Learning::Catalyst::Helpers::CatalystEngine.new
30
+ cat = client.create_catalyst(catalyst_type: :insight, domain: :reasoning,
31
+ potency: 1.0, specificity: 1.0, engine: engine)
32
+ rxn = client.create_reaction(reaction_type: :decomposition,
33
+ reactants: %w[complex_idea],
34
+ activation_energy: 0.1,
35
+ engine: engine)
36
+ client.apply_catalyst(catalyst_id: cat[:catalyst][:id],
37
+ reaction_id: rxn[:reaction][:id],
38
+ engine: engine)
39
+ result = client.attempt_reaction(reaction_id: rxn[:reaction][:id],
40
+ energy_input: 0.8,
41
+ engine: engine)
42
+ expect(result[:completed]).to be true
43
+ expect(result[:catalyzed]).to be true
44
+ status = client.catalyst_status(engine: engine)
45
+ expect(status[:catalyzed_count]).to eq(1)
46
+ expect(status[:catalyzed_rate]).to eq(1.0)
47
+ end
48
+
49
+ it 'maintains separate state per client instance' do
50
+ c1 = described_class.new
51
+ c2 = described_class.new
52
+ c1.create_catalyst(catalyst_type: :insight, domain: :d)
53
+ s1 = c1.catalyst_status
54
+ s2 = c2.catalyst_status
55
+ expect(s1[:total_catalysts]).to eq(1)
56
+ expect(s2[:total_catalysts]).to eq(0)
57
+ end
58
+ end