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,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module Hebbian
8
+ module Helpers
9
+ class AssemblyNetwork
10
+ include Constants
11
+
12
+ attr_reader :units, :assemblies, :activation_history
13
+
14
+ def initialize
15
+ @units = {}
16
+ @assemblies = {}
17
+ @activation_history = []
18
+ @assembly_counter = 0
19
+ end
20
+
21
+ def add_unit(id:, domain: :general)
22
+ return @units[id] if @units.key?(id)
23
+ return nil if @units.size >= MAX_UNITS
24
+
25
+ @units[id] = Unit.new(id: id, domain: domain)
26
+ end
27
+
28
+ def activate_unit(id:, level: 1.0)
29
+ ensure_unit(id)
30
+ unit = @units[id]
31
+ unit.activate(level: level)
32
+ record_activation(id)
33
+ hebbian_update(id)
34
+ detect_assemblies
35
+ unit
36
+ end
37
+
38
+ def co_activate(ids:, level: 1.0)
39
+ ids = Array(ids)
40
+ ids.each do |id|
41
+ ensure_unit(id)
42
+ @units[id].activate(level: level)
43
+ record_activation(id)
44
+ end
45
+
46
+ ids.combination(2).each do |a, b|
47
+ ensure_connection(a, b)
48
+ @units[a].strengthen(b)
49
+ @units[b].strengthen(a)
50
+ end
51
+
52
+ detect_assemblies
53
+ ids.map { |id| @units[id].to_h }
54
+ end
55
+
56
+ def query_weight(from:, to:)
57
+ return 0.0 unless @units.key?(from)
58
+
59
+ @units[from].weight_to(to)
60
+ end
61
+
62
+ def query_assembly(id:)
63
+ @assemblies[id]
64
+ end
65
+
66
+ def assemblies_containing(unit_id:)
67
+ @assemblies.values.select { |a| a.includes?(unit_id) }
68
+ end
69
+
70
+ def pattern_complete(partial_ids:)
71
+ partial = Array(partial_ids)
72
+ candidates = partial.flat_map { |uid| assemblies_containing(unit_id: uid) }.uniq(&:id)
73
+ return nil if candidates.empty?
74
+
75
+ best = candidates.max_by { |a| (partial & a.member_ids).size }
76
+ missing = best.member_ids - partial
77
+ { assembly_id: best.id, known: partial & best.member_ids, predicted: missing, coherence: best.coherence }
78
+ end
79
+
80
+ def strongest_units(count = 10)
81
+ @units.values
82
+ .sort_by { |u| -u.activation_count }
83
+ .first(count)
84
+ .map(&:to_h)
85
+ end
86
+
87
+ def decay_all
88
+ @units.each_value(&:decay_weights)
89
+ @assemblies.each_value(&:decay)
90
+ @assemblies.reject! { |_, a| a.dissolving? }
91
+ end
92
+
93
+ def unit_count
94
+ @units.size
95
+ end
96
+
97
+ def assembly_count
98
+ @assemblies.size
99
+ end
100
+
101
+ def to_h
102
+ {
103
+ units: @units.size,
104
+ assemblies: @assemblies.size,
105
+ total_connections: @units.values.sum(&:connection_count),
106
+ history_size: @activation_history.size,
107
+ active_units: @units.values.count(&:active?)
108
+ }
109
+ end
110
+
111
+ private
112
+
113
+ def ensure_unit(id)
114
+ add_unit(id: id) unless @units.key?(id)
115
+ end
116
+
117
+ def ensure_connection(src, dst)
118
+ @units[src].connect(dst) unless @units[src].connections.key?(dst)
119
+ @units[dst].connect(src) unless @units[dst].connections.key?(src)
120
+ end
121
+
122
+ def record_activation(id)
123
+ @activation_history << { unit_id: id, at: Time.now.utc }
124
+ @activation_history.shift while @activation_history.size > MAX_ACTIVATION_HISTORY
125
+ end
126
+
127
+ def hebbian_update(active_id)
128
+ recent_window = Time.now.utc - CO_ACTIVATION_WINDOW
129
+ recent_ids = @activation_history
130
+ .select { |h| h[:at] >= recent_window && h[:unit_id] != active_id }
131
+ .map { |h| h[:unit_id] }
132
+ .uniq
133
+
134
+ recent_ids.each do |other_id|
135
+ next unless @units.key?(other_id) && @units[other_id].active?
136
+
137
+ ensure_connection(active_id, other_id)
138
+ @units[active_id].strengthen(other_id)
139
+ @units[other_id].strengthen(active_id)
140
+ end
141
+ end
142
+
143
+ def detect_assemblies
144
+ strongly_connected = find_strongly_connected
145
+ return if strongly_connected.size < ASSEMBLY_THRESHOLD
146
+
147
+ existing = find_matching_assembly(strongly_connected)
148
+ existing ? reinforce_assembly(existing) : create_assembly(strongly_connected)
149
+ end
150
+
151
+ def find_strongly_connected
152
+ active_ids = @units.values.select(&:active?).map(&:id)
153
+ return [] if active_ids.size < ASSEMBLY_THRESHOLD
154
+
155
+ active_ids.select do |uid|
156
+ peers = active_ids - [uid]
157
+ peers.count { |p| @units[uid].weight_to(p) >= ASSEMBLY_MIN_WEIGHT } >= (ASSEMBLY_THRESHOLD - 1)
158
+ end
159
+ end
160
+
161
+ def find_matching_assembly(member_ids)
162
+ @assemblies.values.find do |a|
163
+ (member_ids - a.member_ids).empty? && (a.member_ids - member_ids).empty?
164
+ end
165
+ end
166
+
167
+ def reinforce_assembly(assembly)
168
+ assembly.activate
169
+ assembly.consolidate
170
+ end
171
+
172
+ def create_assembly(member_ids)
173
+ @assembly_counter += 1
174
+ id = :"assembly_#{@assembly_counter}"
175
+ @assemblies[id] = Assembly.new(id: id, member_ids: member_ids)
176
+ @assemblies[id].activate
177
+ prune_assemblies if @assemblies.size > MAX_ASSEMBLIES
178
+ end
179
+
180
+ def prune_assemblies
181
+ weakest = @assemblies.min_by { |_, a| a.coherence }&.first
182
+ @assemblies.delete(weakest) if weakest
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module Hebbian
8
+ module Helpers
9
+ module Constants
10
+ MAX_UNITS = 500
11
+ MAX_ASSEMBLIES = 100
12
+ MAX_CONNECTIONS_PER_UNIT = 30
13
+ MAX_ACTIVATION_HISTORY = 200
14
+
15
+ DEFAULT_WEIGHT = 0.1
16
+ WEIGHT_FLOOR = 0.01
17
+ MAX_WEIGHT = 1.0
18
+ LEARNING_RATE = 0.05
19
+ WEIGHT_DECAY = 0.002
20
+
21
+ ACTIVATION_THRESHOLD = 0.3
22
+ CO_ACTIVATION_WINDOW = 5
23
+ ASSEMBLY_THRESHOLD = 3
24
+ ASSEMBLY_MIN_WEIGHT = 0.3
25
+
26
+ CONSOLIDATION_BOOST = 0.02
27
+ COMPETITION_FACTOR = 0.01
28
+
29
+ WEIGHT_LABELS = {
30
+ (0.8..) => :bonded,
31
+ (0.6...0.8) => :strong,
32
+ (0.4...0.6) => :moderate,
33
+ (0.2...0.4) => :weak,
34
+ (..0.2) => :nascent
35
+ }.freeze
36
+
37
+ ASSEMBLY_STATE_LABELS = {
38
+ active: 'assembly currently firing',
39
+ primed: 'assembly recently active',
40
+ dormant: 'assembly stable but inactive',
41
+ forming: 'assembly still consolidating',
42
+ dissolving: 'assembly losing coherence'
43
+ }.freeze
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module Hebbian
8
+ module Helpers
9
+ class Unit
10
+ include Constants
11
+
12
+ attr_reader :id, :domain, :connections, :activation_count, :last_activated
13
+ attr_accessor :activation_level
14
+
15
+ def initialize(id:, domain: :general)
16
+ @id = id
17
+ @domain = domain
18
+ @activation_level = 0.0
19
+ @connections = {}
20
+ @activation_count = 0
21
+ @last_activated = nil
22
+ end
23
+
24
+ def activate(level: 1.0)
25
+ @activation_level = level.to_f.clamp(0.0, 1.0)
26
+ @activation_count += 1
27
+ @last_activated = Time.now.utc
28
+ end
29
+
30
+ def active?
31
+ @activation_level >= ACTIVATION_THRESHOLD
32
+ end
33
+
34
+ def connect(other_id, weight: DEFAULT_WEIGHT)
35
+ return if @connections.size >= MAX_CONNECTIONS_PER_UNIT && !@connections.key?(other_id)
36
+
37
+ @connections[other_id] = weight.to_f.clamp(WEIGHT_FLOOR, MAX_WEIGHT)
38
+ end
39
+
40
+ def strengthen(other_id, amount: LEARNING_RATE)
41
+ return unless @connections.key?(other_id)
42
+
43
+ @connections[other_id] = [@connections[other_id] + amount, MAX_WEIGHT].min
44
+ end
45
+
46
+ def weaken(other_id, amount: COMPETITION_FACTOR)
47
+ return unless @connections.key?(other_id)
48
+
49
+ @connections[other_id] = [@connections[other_id] - amount, WEIGHT_FLOOR].max
50
+ end
51
+
52
+ def weight_to(other_id)
53
+ @connections[other_id] || 0.0
54
+ end
55
+
56
+ def weight_label(other_id)
57
+ w = weight_to(other_id)
58
+ WEIGHT_LABELS.each { |range, lbl| return lbl if range.cover?(w) }
59
+ :nascent
60
+ end
61
+
62
+ def decay_weights
63
+ @connections.each do |k, w|
64
+ @connections[k] = [w - WEIGHT_DECAY, WEIGHT_FLOOR].max
65
+ end
66
+ @connections.reject! { |_, w| w <= WEIGHT_FLOOR }
67
+ end
68
+
69
+ def connection_count
70
+ @connections.size
71
+ end
72
+
73
+ def strongest_connections(count = 5)
74
+ @connections.sort_by { |_, w| -w }.first(count).to_h
75
+ end
76
+
77
+ def to_h
78
+ {
79
+ id: @id,
80
+ domain: @domain,
81
+ activation_level: @activation_level.round(4),
82
+ active: active?,
83
+ connections: @connections.size,
84
+ activation_count: @activation_count,
85
+ last_activated: @last_activated
86
+ }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module Hebbian
8
+ module Runners
9
+ module HebbianAssembly
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ def activate_unit(id:, level: 1.0, domain: :general, **)
14
+ Legion::Logging.debug "[hebbian] activate: id=#{id} level=#{level}"
15
+ network.add_unit(id: id, domain: domain)
16
+ unit = network.activate_unit(id: id, level: level)
17
+ { success: true, unit: unit.to_h, assemblies: network.assembly_count }
18
+ end
19
+
20
+ def co_activate_units(ids:, level: 1.0, **)
21
+ Legion::Logging.debug "[hebbian] co_activate: ids=#{ids}"
22
+ results = network.co_activate(ids: ids, level: level)
23
+ { success: true, units: results, assemblies: network.assembly_count }
24
+ end
25
+
26
+ def query_weight(from:, to:, **)
27
+ w = network.query_weight(from: from, to: to)
28
+ label = Helpers::Constants::WEIGHT_LABELS.each { |range, l| break l if range.cover?(w) }
29
+ label = :nascent unless label.is_a?(Symbol)
30
+ Legion::Logging.debug "[hebbian] weight: #{from}->#{to} = #{w}"
31
+ { success: true, from: from, to: to, weight: w.round(4), label: label }
32
+ end
33
+
34
+ def list_assemblies(**)
35
+ assemblies = network.assemblies.values.map(&:to_h)
36
+ Legion::Logging.debug "[hebbian] list_assemblies: #{assemblies.size}"
37
+ { success: true, assemblies: assemblies, count: assemblies.size }
38
+ end
39
+
40
+ def query_assembly(id:, **)
41
+ asm = network.query_assembly(id: id.to_sym)
42
+ Legion::Logging.debug "[hebbian] query_assembly: id=#{id} found=#{!asm.nil?}"
43
+ if asm
44
+ { success: true, assembly: asm.to_h }
45
+ else
46
+ { success: false, reason: :not_found }
47
+ end
48
+ end
49
+
50
+ def pattern_complete(partial_ids:, **)
51
+ Legion::Logging.debug "[hebbian] pattern_complete: partial=#{partial_ids}"
52
+ result = network.pattern_complete(partial_ids: partial_ids)
53
+ if result
54
+ { success: true, completion: result }
55
+ else
56
+ { success: false, reason: :no_matching_assembly }
57
+ end
58
+ end
59
+
60
+ def strongest_units(limit: 10, **)
61
+ units = network.strongest_units(limit.to_i)
62
+ Legion::Logging.debug "[hebbian] strongest_units: #{units.size}"
63
+ { success: true, units: units }
64
+ end
65
+
66
+ def assemblies_for(unit_id:, **)
67
+ asms = network.assemblies_containing(unit_id: unit_id).map(&:to_h)
68
+ Legion::Logging.debug "[hebbian] assemblies_for: unit=#{unit_id} count=#{asms.size}"
69
+ { success: true, assemblies: asms, count: asms.size }
70
+ end
71
+
72
+ def update_hebbian(**)
73
+ Legion::Logging.debug '[hebbian] decay tick'
74
+ network.decay_all
75
+ { success: true, units: network.unit_count, assemblies: network.assembly_count }
76
+ end
77
+
78
+ def hebbian_stats(**)
79
+ Legion::Logging.debug '[hebbian] stats'
80
+ { success: true, stats: network.to_h }
81
+ end
82
+
83
+ private
84
+
85
+ def network
86
+ @network ||= Helpers::AssemblyNetwork.new
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ 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 Hebbian
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/learning/hebbian/version'
4
+ require 'legion/extensions/agentic/learning/hebbian/helpers/constants'
5
+ require 'legion/extensions/agentic/learning/hebbian/helpers/unit'
6
+ require 'legion/extensions/agentic/learning/hebbian/helpers/assembly'
7
+ require 'legion/extensions/agentic/learning/hebbian/helpers/assembly_network'
8
+ require 'legion/extensions/agentic/learning/hebbian/runners/hebbian_assembly'
9
+ require 'legion/extensions/agentic/learning/hebbian/client'
10
+
11
+ module Legion
12
+ module Extensions
13
+ module Agentic
14
+ module Learning
15
+ module Hebbian
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/learning/learning_rate/helpers/constants'
4
+ require 'legion/extensions/agentic/learning/learning_rate/helpers/rate_model'
5
+ require 'legion/extensions/agentic/learning/learning_rate/runners/learning_rate'
6
+
7
+ module Legion
8
+ module Extensions
9
+ module Agentic
10
+ module Learning
11
+ module LearningRate
12
+ class Client
13
+ include Runners::LearningRate
14
+
15
+ attr_reader :rate_model
16
+
17
+ def initialize(rate_model: nil, **)
18
+ @rate_model = rate_model || Helpers::RateModel.new
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module LearningRate
8
+ module Helpers
9
+ module Constants
10
+ DEFAULT_RATE = 0.15
11
+ MIN_RATE = 0.01
12
+ MAX_RATE = 0.5
13
+ RATE_INCREASE = 0.03
14
+ RATE_DECREASE = 0.02
15
+ RATE_DECAY = 0.005
16
+ ACCURACY_WINDOW = 20
17
+ SURPRISE_BOOST = 0.05
18
+ ERROR_BOOST = 0.04
19
+ CONFIDENCE_DAMPENING = 0.03
20
+ MAX_DOMAINS = 50
21
+ MAX_RATE_HISTORY = 200
22
+
23
+ RATE_LABELS = {
24
+ (0.3..) => :fast_learning,
25
+ (0.15...0.3) => :moderate_learning,
26
+ (0.05...0.15) => :slow_learning,
27
+ (..0.05) => :consolidated
28
+ }.freeze
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Learning
7
+ module LearningRate
8
+ module Helpers
9
+ class RateModel
10
+ include Constants
11
+
12
+ attr_reader :rates, :accuracy_buffers, :rate_history
13
+
14
+ def initialize
15
+ @rates = {}
16
+ @accuracy_buffers = {}
17
+ @rate_history = []
18
+ end
19
+
20
+ def rate_for(domain)
21
+ @rates.fetch(domain, DEFAULT_RATE)
22
+ end
23
+
24
+ def record_prediction(domain:, correct:)
25
+ ensure_domain(domain)
26
+ @accuracy_buffers[domain] << (correct ? 1.0 : 0.0)
27
+ @accuracy_buffers[domain].shift while @accuracy_buffers[domain].size > ACCURACY_WINDOW
28
+ adjust_rate(domain, correct: correct)
29
+ end
30
+
31
+ def record_surprise(domain:, magnitude:)
32
+ ensure_domain(domain)
33
+ boost = magnitude * SURPRISE_BOOST
34
+ @rates[domain] = (@rates[domain] + boost).clamp(MIN_RATE, MAX_RATE)
35
+ record_event(domain, :surprise, @rates[domain])
36
+ end
37
+
38
+ def record_error(domain:, magnitude:)
39
+ ensure_domain(domain)
40
+ boost = magnitude * ERROR_BOOST
41
+ @rates[domain] = (@rates[domain] + boost).clamp(MIN_RATE, MAX_RATE)
42
+ record_event(domain, :error, @rates[domain])
43
+ end
44
+
45
+ def accuracy_for(domain)
46
+ buffer = @accuracy_buffers.fetch(domain, [])
47
+ return 0.0 if buffer.empty?
48
+
49
+ buffer.sum / buffer.size
50
+ end
51
+
52
+ def decay
53
+ @rates.each_key do |domain|
54
+ current = @rates[domain]
55
+ delta = (current - DEFAULT_RATE) * RATE_DECAY
56
+ @rates[domain] = (current - delta).clamp(MIN_RATE, MAX_RATE)
57
+ end
58
+ end
59
+
60
+ def label_for(domain)
61
+ rate = rate_for(domain)
62
+ RATE_LABELS.each do |range, lbl|
63
+ return lbl if range.cover?(rate)
64
+ end
65
+ :consolidated
66
+ end
67
+
68
+ def fastest_domains(count = 5)
69
+ @rates.sort_by { |_, r| -r }.first(count).to_h
70
+ end
71
+
72
+ def slowest_domains(count = 5)
73
+ @rates.sort_by { |_, r| r }.first(count).to_h
74
+ end
75
+
76
+ def overall_rate
77
+ return DEFAULT_RATE if @rates.empty?
78
+
79
+ @rates.values.sum / @rates.size
80
+ end
81
+
82
+ def domain_count
83
+ @rates.size
84
+ end
85
+
86
+ def to_h
87
+ {
88
+ domain_count: @rates.size,
89
+ overall_rate: overall_rate.round(4),
90
+ rates: @rates.dup,
91
+ history_size: @rate_history.size
92
+ }
93
+ end
94
+
95
+ private
96
+
97
+ def ensure_domain(domain)
98
+ @rates[domain] ||= DEFAULT_RATE
99
+ @accuracy_buffers[domain] ||= []
100
+ trim_domains(protect: domain) if @rates.size > MAX_DOMAINS
101
+ end
102
+
103
+ def adjust_rate(domain, correct:)
104
+ @rates[domain] = if correct
105
+ (@rates[domain] - RATE_DECREASE).clamp(MIN_RATE, MAX_RATE)
106
+ else
107
+ (@rates[domain] + RATE_INCREASE).clamp(MIN_RATE, MAX_RATE)
108
+ end
109
+ record_event(domain, correct ? :correct : :incorrect, @rates[domain])
110
+ end
111
+
112
+ def record_event(domain, event_type, rate)
113
+ @rate_history << { domain: domain, event: event_type, rate: rate, at: Time.now.utc }
114
+ @rate_history.shift while @rate_history.size > MAX_RATE_HISTORY
115
+ end
116
+
117
+ def trim_domains(protect: nil)
118
+ candidates = @rates.reject { |k, _| k == protect }
119
+ sorted = candidates.sort_by { |_, r| (r - DEFAULT_RATE).abs }
120
+ excess = @rates.size - MAX_DOMAINS
121
+ remove_keys = sorted.first(excess).map(&:first)
122
+ remove_keys.each do |key|
123
+ @rates.delete(key)
124
+ @accuracy_buffers.delete(key)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end