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,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Hebbian::Helpers::AssemblyNetwork do
4
+ subject(:network) { described_class.new }
5
+
6
+ let(:constants) { Legion::Extensions::Agentic::Learning::Hebbian::Helpers::Constants }
7
+
8
+ describe '#add_unit' do
9
+ it 'adds a unit' do
10
+ unit = network.add_unit(id: :a)
11
+ expect(unit.id).to eq(:a)
12
+ expect(network.unit_count).to eq(1)
13
+ end
14
+
15
+ it 'returns existing unit on duplicate' do
16
+ first = network.add_unit(id: :a)
17
+ second = network.add_unit(id: :a)
18
+ expect(first).to equal(second)
19
+ end
20
+
21
+ it 'limits units' do
22
+ constants::MAX_UNITS.times { |i| network.add_unit(id: :"u_#{i}") }
23
+ expect(network.add_unit(id: :overflow)).to be_nil
24
+ end
25
+ end
26
+
27
+ describe '#activate_unit' do
28
+ it 'activates a unit' do
29
+ unit = network.activate_unit(id: :a, level: 0.8)
30
+ expect(unit.activation_level).to eq(0.8)
31
+ expect(unit.active?).to be true
32
+ end
33
+
34
+ it 'auto-creates unit if missing' do
35
+ network.activate_unit(id: :new_unit)
36
+ expect(network.unit_count).to eq(1)
37
+ end
38
+
39
+ it 'records activation history' do
40
+ network.activate_unit(id: :a)
41
+ expect(network.activation_history.size).to eq(1)
42
+ end
43
+ end
44
+
45
+ describe '#co_activate' do
46
+ it 'activates multiple units' do
47
+ results = network.co_activate(ids: %i[a b c])
48
+ expect(results.size).to eq(3)
49
+ expect(network.unit_count).to eq(3)
50
+ end
51
+
52
+ it 'strengthens connections between co-activated units' do
53
+ network.co_activate(ids: %i[a b])
54
+ expect(network.query_weight(from: :a, to: :b)).to be > 0
55
+ expect(network.query_weight(from: :b, to: :a)).to be > 0
56
+ end
57
+
58
+ it 'repeated co-activation increases weight' do
59
+ network.co_activate(ids: %i[a b])
60
+ first_weight = network.query_weight(from: :a, to: :b)
61
+ network.co_activate(ids: %i[a b])
62
+ expect(network.query_weight(from: :a, to: :b)).to be > first_weight
63
+ end
64
+ end
65
+
66
+ describe '#query_weight' do
67
+ it 'returns 0 for unconnected units' do
68
+ expect(network.query_weight(from: :a, to: :b)).to eq(0.0)
69
+ end
70
+ end
71
+
72
+ describe 'assembly detection' do
73
+ it 'forms assembly from repeated co-activation of 3+ units' do
74
+ 10.times { network.co_activate(ids: %i[a b c], level: 1.0) }
75
+ expect(network.assembly_count).to be >= 1
76
+ end
77
+
78
+ it 'consolidates existing assembly on repeated co-activation' do
79
+ 10.times { network.co_activate(ids: %i[a b c], level: 1.0) }
80
+ assembly = network.assemblies.values.first
81
+ first_coherence = assembly.coherence
82
+ 5.times { network.co_activate(ids: %i[a b c], level: 1.0) }
83
+ expect(assembly.coherence).to be >= first_coherence
84
+ end
85
+ end
86
+
87
+ describe '#assemblies_containing' do
88
+ it 'finds assemblies containing a unit' do
89
+ 10.times { network.co_activate(ids: %i[a b c], level: 1.0) }
90
+ asms = network.assemblies_containing(unit_id: :a)
91
+ expect(asms).not_to be_empty
92
+ expect(asms.first.includes?(:a)).to be true
93
+ end
94
+ end
95
+
96
+ describe '#pattern_complete' do
97
+ it 'completes partial pattern from assembly' do
98
+ 10.times { network.co_activate(ids: %i[a b c], level: 1.0) }
99
+ result = network.pattern_complete(partial_ids: %i[a b])
100
+ expect(result).not_to be_nil
101
+ expect(result[:predicted]).to include(:c)
102
+ end
103
+
104
+ it 'returns nil when no matching assembly' do
105
+ expect(network.pattern_complete(partial_ids: [:unknown])).to be_nil
106
+ end
107
+ end
108
+
109
+ describe '#strongest_units' do
110
+ it 'returns most activated units' do
111
+ 5.times { network.activate_unit(id: :frequent, level: 1.0) }
112
+ network.activate_unit(id: :rare, level: 1.0)
113
+ top = network.strongest_units(1)
114
+ expect(top.first[:id]).to eq(:frequent)
115
+ end
116
+ end
117
+
118
+ describe '#decay_all' do
119
+ it 'decays unit weights' do
120
+ network.co_activate(ids: %i[a b])
121
+ before = network.query_weight(from: :a, to: :b)
122
+ network.decay_all
123
+ expect(network.query_weight(from: :a, to: :b)).to be < before
124
+ end
125
+
126
+ it 'removes dissolving assemblies' do
127
+ 10.times { network.co_activate(ids: %i[a b c], level: 1.0) }
128
+ network.assemblies.each_value { |a| a.coherence = 0.1 }
129
+ network.decay_all
130
+ expect(network.assembly_count).to eq(0)
131
+ end
132
+ end
133
+
134
+ describe '#to_h' do
135
+ it 'returns stats' do
136
+ network.co_activate(ids: %i[a b c])
137
+ h = network.to_h
138
+ expect(h).to include(:units, :assemblies, :total_connections, :history_size, :active_units)
139
+ expect(h[:units]).to eq(3)
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Hebbian::Helpers::Assembly do
4
+ subject(:assembly) { described_class.new(id: :asm_one, member_ids: %i[a b c]) }
5
+
6
+ let(:constants) { Legion::Extensions::Agentic::Learning::Hebbian::Helpers::Constants }
7
+
8
+ describe '#initialize' do
9
+ it 'sets attributes' do
10
+ expect(assembly.id).to eq(:asm_one)
11
+ expect(assembly.member_ids).to eq(%i[a b c])
12
+ expect(assembly.coherence).to eq(0.5)
13
+ end
14
+
15
+ it 'deduplicates members' do
16
+ asm = described_class.new(id: :dup, member_ids: %i[a a b])
17
+ expect(asm.member_ids).to eq(%i[a b])
18
+ end
19
+ end
20
+
21
+ describe '#activate' do
22
+ it 'increments activation count' do
23
+ assembly.activate
24
+ expect(assembly.activation_count).to eq(1)
25
+ expect(assembly.last_activated).to be_a(Time)
26
+ end
27
+ end
28
+
29
+ describe '#consolidate' do
30
+ it 'increases coherence' do
31
+ before = assembly.coherence
32
+ assembly.consolidate
33
+ expect(assembly.coherence).to be > before
34
+ end
35
+ end
36
+
37
+ describe '#decay' do
38
+ it 'decreases coherence' do
39
+ before = assembly.coherence
40
+ assembly.decay
41
+ expect(assembly.coherence).to be < before
42
+ end
43
+ end
44
+
45
+ describe '#dissolving?' do
46
+ it 'returns false when coherent' do
47
+ expect(assembly.dissolving?).to be false
48
+ end
49
+
50
+ it 'returns true when coherence drops below threshold' do
51
+ assembly.coherence = 0.1
52
+ expect(assembly.dissolving?).to be true
53
+ end
54
+ end
55
+
56
+ describe '#includes?' do
57
+ it 'returns true for members' do
58
+ expect(assembly.includes?(:a)).to be true
59
+ end
60
+
61
+ it 'returns false for non-members' do
62
+ expect(assembly.includes?(:z)).to be false
63
+ end
64
+ end
65
+
66
+ describe '#state' do
67
+ it 'returns :dormant for stable inactive assembly' do
68
+ expect(assembly.state).to eq(:dormant)
69
+ end
70
+
71
+ it 'returns :active when recently activated' do
72
+ assembly.activate
73
+ expect(assembly.state).to eq(:active)
74
+ end
75
+
76
+ it 'returns :dissolving when coherence is low' do
77
+ assembly.coherence = 0.1
78
+ expect(assembly.state).to eq(:dissolving)
79
+ end
80
+ end
81
+
82
+ describe '#to_h' do
83
+ it 'returns hash with all fields' do
84
+ h = assembly.to_h
85
+ expect(h).to include(:id, :members, :member_count, :coherence, :state, :state_label, :activation_count)
86
+ expect(h[:member_count]).to eq(3)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Hebbian::Helpers::Unit do
4
+ subject(:unit) { described_class.new(id: :neuron_a, domain: :cognition) }
5
+
6
+ let(:constants) { Legion::Extensions::Agentic::Learning::Hebbian::Helpers::Constants }
7
+
8
+ describe '#initialize' do
9
+ it 'sets attributes' do
10
+ expect(unit.id).to eq(:neuron_a)
11
+ expect(unit.domain).to eq(:cognition)
12
+ expect(unit.activation_level).to eq(0.0)
13
+ expect(unit.activation_count).to eq(0)
14
+ end
15
+ end
16
+
17
+ describe '#activate' do
18
+ it 'sets activation level and increments count' do
19
+ unit.activate(level: 0.8)
20
+ expect(unit.activation_level).to eq(0.8)
21
+ expect(unit.activation_count).to eq(1)
22
+ expect(unit.last_activated).to be_a(Time)
23
+ end
24
+
25
+ it 'clamps activation' do
26
+ unit.activate(level: 1.5)
27
+ expect(unit.activation_level).to eq(1.0)
28
+ end
29
+ end
30
+
31
+ describe '#active?' do
32
+ it 'returns true when above threshold' do
33
+ unit.activate(level: 0.5)
34
+ expect(unit.active?).to be true
35
+ end
36
+
37
+ it 'returns false when below threshold' do
38
+ expect(unit.active?).to be false
39
+ end
40
+ end
41
+
42
+ describe '#connect' do
43
+ it 'creates a connection' do
44
+ unit.connect(:neuron_b, weight: 0.3)
45
+ expect(unit.weight_to(:neuron_b)).to eq(0.3)
46
+ end
47
+
48
+ it 'clamps weight' do
49
+ unit.connect(:neuron_b, weight: 2.0)
50
+ expect(unit.weight_to(:neuron_b)).to eq(1.0)
51
+ end
52
+
53
+ it 'limits connections' do
54
+ constants::MAX_CONNECTIONS_PER_UNIT.times { |i| unit.connect(:"n_#{i}") }
55
+ unit.connect(:overflow)
56
+ expect(unit.connection_count).to eq(constants::MAX_CONNECTIONS_PER_UNIT)
57
+ end
58
+ end
59
+
60
+ describe '#strengthen' do
61
+ it 'increases weight' do
62
+ unit.connect(:neuron_b, weight: 0.3)
63
+ unit.strengthen(:neuron_b)
64
+ expect(unit.weight_to(:neuron_b)).to eq(0.3 + constants::LEARNING_RATE)
65
+ end
66
+
67
+ it 'caps at MAX_WEIGHT' do
68
+ unit.connect(:neuron_b, weight: 0.98)
69
+ unit.strengthen(:neuron_b)
70
+ expect(unit.weight_to(:neuron_b)).to eq(constants::MAX_WEIGHT)
71
+ end
72
+ end
73
+
74
+ describe '#weaken' do
75
+ it 'decreases weight' do
76
+ unit.connect(:neuron_b, weight: 0.5)
77
+ unit.weaken(:neuron_b)
78
+ expect(unit.weight_to(:neuron_b)).to eq(0.5 - constants::COMPETITION_FACTOR)
79
+ end
80
+ end
81
+
82
+ describe '#decay_weights' do
83
+ it 'decays all connections' do
84
+ unit.connect(:neuron_b, weight: 0.5)
85
+ unit.decay_weights
86
+ expect(unit.weight_to(:neuron_b)).to eq(0.5 - constants::WEIGHT_DECAY)
87
+ end
88
+
89
+ it 'prunes connections at floor' do
90
+ unit.connect(:neuron_b, weight: constants::WEIGHT_FLOOR + 0.001)
91
+ unit.decay_weights
92
+ expect(unit.connection_count).to eq(0)
93
+ end
94
+ end
95
+
96
+ describe '#weight_label' do
97
+ it 'returns label for weight' do
98
+ unit.connect(:neuron_b, weight: 0.9)
99
+ expect(unit.weight_label(:neuron_b)).to eq(:bonded)
100
+ end
101
+ end
102
+
103
+ describe '#strongest_connections' do
104
+ it 'returns top connections sorted by weight' do
105
+ unit.connect(:a, weight: 0.3)
106
+ unit.connect(:b, weight: 0.8)
107
+ unit.connect(:c, weight: 0.5)
108
+ top = unit.strongest_connections(2)
109
+ expect(top.keys).to eq(%i[b c])
110
+ end
111
+ end
112
+
113
+ describe '#to_h' do
114
+ it 'returns hash' do
115
+ h = unit.to_h
116
+ expect(h).to include(:id, :domain, :activation_level, :active, :connections, :activation_count)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Hebbian::Runners::HebbianAssembly do
4
+ let(:client) { Legion::Extensions::Agentic::Learning::Hebbian::Client.new }
5
+
6
+ describe '#activate_unit' do
7
+ it 'activates a unit' do
8
+ result = client.activate_unit(id: :a, level: 0.8, domain: :cognition)
9
+ expect(result[:success]).to be true
10
+ expect(result[:unit][:id]).to eq(:a)
11
+ expect(result[:unit][:active]).to be true
12
+ end
13
+ end
14
+
15
+ describe '#co_activate_units' do
16
+ it 'co-activates multiple units' do
17
+ result = client.co_activate_units(ids: %i[a b c])
18
+ expect(result[:success]).to be true
19
+ expect(result[:units].size).to eq(3)
20
+ end
21
+ end
22
+
23
+ describe '#query_weight' do
24
+ it 'returns weight between units' do
25
+ client.co_activate_units(ids: %i[a b])
26
+ result = client.query_weight(from: :a, to: :b)
27
+ expect(result[:success]).to be true
28
+ expect(result[:weight]).to be > 0
29
+ expect(result[:label]).to be_a(Symbol)
30
+ end
31
+
32
+ it 'returns 0 for unconnected' do
33
+ result = client.query_weight(from: :x, to: :y)
34
+ expect(result[:weight]).to eq(0.0)
35
+ end
36
+ end
37
+
38
+ describe '#list_assemblies' do
39
+ it 'lists assemblies' do
40
+ 10.times { client.co_activate_units(ids: %i[a b c]) }
41
+ result = client.list_assemblies
42
+ expect(result[:success]).to be true
43
+ expect(result[:count]).to be >= 1
44
+ end
45
+ end
46
+
47
+ describe '#query_assembly' do
48
+ it 'queries a specific assembly' do
49
+ 10.times { client.co_activate_units(ids: %i[a b c]) }
50
+ all = client.list_assemblies
51
+ id = all[:assemblies].first[:id]
52
+ result = client.query_assembly(id: id)
53
+ expect(result[:success]).to be true
54
+ expect(result[:assembly][:members]).to include(:a, :b, :c)
55
+ end
56
+
57
+ it 'returns not_found for unknown' do
58
+ result = client.query_assembly(id: :nonexistent)
59
+ expect(result[:success]).to be false
60
+ end
61
+ end
62
+
63
+ describe '#pattern_complete' do
64
+ it 'completes partial pattern' do
65
+ 10.times { client.co_activate_units(ids: %i[a b c]) }
66
+ result = client.pattern_complete(partial_ids: %i[a b])
67
+ expect(result[:success]).to be true
68
+ expect(result[:completion][:predicted]).to include(:c)
69
+ end
70
+
71
+ it 'returns failure when no match' do
72
+ result = client.pattern_complete(partial_ids: [:unknown])
73
+ expect(result[:success]).to be false
74
+ end
75
+ end
76
+
77
+ describe '#strongest_units' do
78
+ it 'returns most activated units' do
79
+ 5.times { client.activate_unit(id: :freq) }
80
+ client.activate_unit(id: :rare)
81
+ result = client.strongest_units(limit: 1)
82
+ expect(result[:units].first[:id]).to eq(:freq)
83
+ end
84
+ end
85
+
86
+ describe '#assemblies_for' do
87
+ it 'finds assemblies for a unit' do
88
+ 10.times { client.co_activate_units(ids: %i[a b c]) }
89
+ result = client.assemblies_for(unit_id: :a)
90
+ expect(result[:success]).to be true
91
+ expect(result[:count]).to be >= 1
92
+ end
93
+ end
94
+
95
+ describe '#update_hebbian' do
96
+ it 'runs decay tick' do
97
+ result = client.update_hebbian
98
+ expect(result[:success]).to be true
99
+ end
100
+ end
101
+
102
+ describe '#hebbian_stats' do
103
+ it 'returns stats' do
104
+ result = client.hebbian_stats
105
+ expect(result[:success]).to be true
106
+ expect(result[:stats]).to include(:units, :assemblies, :total_connections)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::LearningRate::Client do
4
+ let(:client) { described_class.new }
5
+
6
+ it 'can be instantiated' do
7
+ expect(client).to be_a(described_class)
8
+ end
9
+
10
+ it 'includes all runner methods' do
11
+ expect(client).to respond_to(:record_prediction)
12
+ expect(client).to respond_to(:record_surprise)
13
+ expect(client).to respond_to(:record_error)
14
+ expect(client).to respond_to(:current_rate)
15
+ expect(client).to respond_to(:fastest_domains)
16
+ expect(client).to respond_to(:slowest_domains)
17
+ expect(client).to respond_to(:update_learning_rate)
18
+ expect(client).to respond_to(:learning_rate_stats)
19
+ end
20
+
21
+ it 'exposes the rate model' do
22
+ expect(client.rate_model).to be_a(Legion::Extensions::Agentic::Learning::LearningRate::Helpers::RateModel)
23
+ end
24
+
25
+ describe 'full lifecycle' do
26
+ it 'adapts learning rate based on prediction outcomes' do
27
+ # Initial state
28
+ initial = client.current_rate(domain: :coding)[:rate]
29
+
30
+ # Agent makes wrong predictions - rate increases
31
+ 5.times { client.record_prediction(domain: :coding, correct: false) }
32
+ after_errors = client.current_rate(domain: :coding)[:rate]
33
+ expect(after_errors).to be > initial
34
+
35
+ # Something surprising happens - rate boosts further
36
+ client.record_surprise(domain: :coding, magnitude: 0.8)
37
+ after_surprise = client.current_rate(domain: :coding)[:rate]
38
+ expect(after_surprise).to be > after_errors
39
+
40
+ # Agent starts getting things right - rate decreases
41
+ 10.times { client.record_prediction(domain: :coding, correct: true) }
42
+ after_learning = client.current_rate(domain: :coding)[:rate]
43
+ expect(after_learning).to be < after_surprise
44
+
45
+ # Tick decay
46
+ client.update_learning_rate
47
+ stats = client.learning_rate_stats[:stats]
48
+ expect(stats[:domain_count]).to eq(1)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::LearningRate::Helpers::Constants do
4
+ it 'defines DEFAULT_RATE' do
5
+ expect(described_class::DEFAULT_RATE).to eq(0.15)
6
+ end
7
+
8
+ it 'defines MIN_RATE and MAX_RATE' do
9
+ expect(described_class::MIN_RATE).to eq(0.01)
10
+ expect(described_class::MAX_RATE).to eq(0.5)
11
+ end
12
+
13
+ it 'defines ACCURACY_WINDOW' do
14
+ expect(described_class::ACCURACY_WINDOW).to eq(20)
15
+ end
16
+
17
+ it 'defines RATE_LABELS covering 0.0..1.0' do
18
+ labels = described_class::RATE_LABELS
19
+ [0.01, 0.05, 0.1, 0.2, 0.35, 0.5].each do |val|
20
+ matched = labels.any? { |range, _| range.cover?(val) }
21
+ expect(matched).to be(true), "Expected #{val} to match a label range"
22
+ end
23
+ end
24
+
25
+ it 'has all expected RATE_LABELS values' do
26
+ values = described_class::RATE_LABELS.values
27
+ expect(values).to contain_exactly(:fast_learning, :moderate_learning, :slow_learning, :consolidated)
28
+ end
29
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::LearningRate::Helpers::RateModel do
4
+ let(:model) { described_class.new }
5
+ let(:constants) { Legion::Extensions::Agentic::Learning::LearningRate::Helpers::Constants }
6
+
7
+ describe '#rate_for' do
8
+ it 'returns default rate for unknown domain' do
9
+ expect(model.rate_for(:new_domain)).to eq(constants::DEFAULT_RATE)
10
+ end
11
+
12
+ it 'returns tracked rate for known domain' do
13
+ model.record_prediction(domain: :math, correct: false)
14
+ expect(model.rate_for(:math)).not_to eq(constants::DEFAULT_RATE)
15
+ end
16
+ end
17
+
18
+ describe '#record_prediction' do
19
+ it 'increases rate on incorrect prediction' do
20
+ model.record_prediction(domain: :math, correct: false)
21
+ expect(model.rate_for(:math)).to be > constants::DEFAULT_RATE
22
+ end
23
+
24
+ it 'decreases rate on correct prediction' do
25
+ model.record_prediction(domain: :math, correct: true)
26
+ expect(model.rate_for(:math)).to be < constants::DEFAULT_RATE
27
+ end
28
+
29
+ it 'records accuracy history' do
30
+ model.record_prediction(domain: :math, correct: true)
31
+ model.record_prediction(domain: :math, correct: false)
32
+ expect(model.accuracy_for(:math)).to eq(0.5)
33
+ end
34
+
35
+ it 'caps accuracy buffer at ACCURACY_WINDOW' do
36
+ (constants::ACCURACY_WINDOW + 5).times { model.record_prediction(domain: :math, correct: true) }
37
+ buffer = model.accuracy_buffers[:math]
38
+ expect(buffer.size).to eq(constants::ACCURACY_WINDOW)
39
+ end
40
+ end
41
+
42
+ describe '#record_surprise' do
43
+ it 'increases rate proportional to magnitude' do
44
+ initial = constants::DEFAULT_RATE
45
+ model.record_surprise(domain: :math, magnitude: 0.8)
46
+ expect(model.rate_for(:math)).to be > initial
47
+ end
48
+
49
+ it 'records in history' do
50
+ model.record_surprise(domain: :math, magnitude: 0.5)
51
+ expect(model.rate_history.size).to eq(1)
52
+ expect(model.rate_history.last[:event]).to eq(:surprise)
53
+ end
54
+ end
55
+
56
+ describe '#record_error' do
57
+ it 'increases rate proportional to magnitude' do
58
+ initial = constants::DEFAULT_RATE
59
+ model.record_error(domain: :math, magnitude: 0.7)
60
+ expect(model.rate_for(:math)).to be > initial
61
+ end
62
+ end
63
+
64
+ describe '#accuracy_for' do
65
+ it 'returns 0.0 for unknown domain' do
66
+ expect(model.accuracy_for(:unknown)).to eq(0.0)
67
+ end
68
+
69
+ it 'computes rolling accuracy' do
70
+ 3.times { model.record_prediction(domain: :test, correct: true) }
71
+ model.record_prediction(domain: :test, correct: false)
72
+ expect(model.accuracy_for(:test)).to eq(0.75)
73
+ end
74
+ end
75
+
76
+ describe '#decay' do
77
+ it 'moves rates toward default' do
78
+ model.record_prediction(domain: :math, correct: false)
79
+ raised = model.rate_for(:math)
80
+ model.decay
81
+ expect(model.rate_for(:math)).to be < raised
82
+ end
83
+ end
84
+
85
+ describe '#label_for' do
86
+ it 'returns a symbol' do
87
+ expect(model.label_for(:math)).to be_a(Symbol)
88
+ end
89
+
90
+ it 'returns :moderate_learning for default rate' do
91
+ expect(model.label_for(:unknown)).to eq(:moderate_learning)
92
+ end
93
+ end
94
+
95
+ describe '#fastest_domains' do
96
+ it 'returns domains sorted by rate descending' do
97
+ model.record_prediction(domain: :fast, correct: false)
98
+ model.record_prediction(domain: :fast, correct: false)
99
+ model.record_prediction(domain: :slow, correct: true)
100
+ fastest = model.fastest_domains(2)
101
+ expect(fastest.keys.first).to eq(:fast)
102
+ end
103
+ end
104
+
105
+ describe '#slowest_domains' do
106
+ it 'returns domains sorted by rate ascending' do
107
+ model.record_prediction(domain: :fast, correct: false)
108
+ model.record_prediction(domain: :slow, correct: true)
109
+ slowest = model.slowest_domains(2)
110
+ expect(slowest.keys.first).to eq(:slow)
111
+ end
112
+ end
113
+
114
+ describe '#overall_rate' do
115
+ it 'returns default when no domains tracked' do
116
+ expect(model.overall_rate).to eq(constants::DEFAULT_RATE)
117
+ end
118
+
119
+ it 'averages all domain rates' do
120
+ model.record_prediction(domain: :a, correct: true)
121
+ model.record_prediction(domain: :b, correct: false)
122
+ overall = model.overall_rate
123
+ expect(overall).to be_a(Float)
124
+ end
125
+ end
126
+
127
+ describe '#domain_count' do
128
+ it 'counts tracked domains' do
129
+ model.record_prediction(domain: :a, correct: true)
130
+ model.record_prediction(domain: :b, correct: true)
131
+ expect(model.domain_count).to eq(2)
132
+ end
133
+ end
134
+
135
+ describe 'domain trimming' do
136
+ it 'caps at MAX_DOMAINS' do
137
+ (constants::MAX_DOMAINS + 5).times { |i| model.record_prediction(domain: :"d_#{i}", correct: true) }
138
+ expect(model.domain_count).to eq(constants::MAX_DOMAINS)
139
+ end
140
+ end
141
+
142
+ describe '#to_h' do
143
+ it 'contains expected keys' do
144
+ h = model.to_h
145
+ expect(h).to have_key(:domain_count)
146
+ expect(h).to have_key(:overall_rate)
147
+ expect(h).to have_key(:rates)
148
+ expect(h).to have_key(:history_size)
149
+ end
150
+ end
151
+ end