lex-agentic-learning 0.1.0 → 0.1.2

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 (22) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +41 -3
  4. data/lex-agentic-learning.gemspec +8 -0
  5. data/lib/legion/extensions/agentic/learning/anchoring/runners/anchoring.rb +12 -12
  6. data/lib/legion/extensions/agentic/learning/catalyst/runners/cognitive_catalyst.rb +16 -16
  7. data/lib/legion/extensions/agentic/learning/chrysalis/runners/cognitive_chrysalis.rb +8 -8
  8. data/lib/legion/extensions/agentic/learning/curiosity/runners/curiosity.rb +10 -10
  9. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity.rb +13 -13
  10. data/lib/legion/extensions/agentic/learning/fermentation/helpers/fermentation_engine.rb +4 -0
  11. data/lib/legion/extensions/agentic/learning/fermentation/version.rb +1 -1
  12. data/lib/legion/extensions/agentic/learning/habit/runners/habit.rb +7 -7
  13. data/lib/legion/extensions/agentic/learning/hebbian/runners/hebbian_assembly.rb +10 -10
  14. data/lib/legion/extensions/agentic/learning/learning_rate/runners/learning_rate.rb +4 -4
  15. data/lib/legion/extensions/agentic/learning/meta_learning/runners/meta_learning.rb +20 -20
  16. data/lib/legion/extensions/agentic/learning/preference_learning/runners/preference_learning.rb +10 -10
  17. data/lib/legion/extensions/agentic/learning/procedural/runners/procedural_learning.rb +14 -14
  18. data/lib/legion/extensions/agentic/learning/scaffolding/runners/cognitive_scaffolding.rb +19 -19
  19. data/lib/legion/extensions/agentic/learning/version.rb +1 -1
  20. data/spec/legion/extensions/agentic/learning/fermentation/helpers/fermentation_engine_spec.rb +26 -0
  21. data/spec/spec_helper.rb +21 -21
  22. metadata +99 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14ead87d8b9aba4266580c17d9f8ece03e76d64f15375e8ef3b89f078aa1eb20
4
- data.tar.gz: b9ca7ce1d976b330e1f66ed9bf8dc00cd2d4cedc7bd1adf45f161b356a5c87f9
3
+ metadata.gz: a8e389e9f54e7a9c30da3458b1cfda5723454543d12b47b76bc8a99c9a35e72f
4
+ data.tar.gz: 228ba88c5b55ee8a8886de351f10ce0a10abad53cbdca36e0131ad1b0e013ccb
5
5
  SHA512:
6
- metadata.gz: ff8f094be6c42376441676ae52450b091b42cfbd44ea39ff15631ce0a17cdc9b8e9a49e62b538c06813bf0712f6da9f902fd38305491384069dd196fee7220f5
7
- data.tar.gz: 1e1d0819d02c30a65b1c59de7c4822592eb6047b64fda08ccbb60782c836a3a35a444fd4afbccd430ac86346ceb1cd9be47f21ba4f9cfea1aaedbc203eabad02
6
+ metadata.gz: 638c8c4442081c5d1584b70cd6bbd579e468f4e63cc6aee9643dbc177ca9f254b4d562a5886a0ca5aa3686fe83b9bab16871bdcf44b65d7cbb70cafaa38746fe
7
+ data.tar.gz: c1ca765261443c56a76eadd376960950183b5435c7c53cdef9af0abcaa9a3cd8a205e47aa6dee4ebad0f97b5c144ced0cdc50d16c67f8b5406d86e25be99b25e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.1.2] - 2026-03-22
6
+
7
+ ### Changed
8
+ - Add legion-* sub-gems as runtime dependencies (logging, settings, json, cache, crypt, data, transport)
9
+ - Replace direct Legion::Logging calls with injected log helper in all runner modules
10
+ - Update spec_helper with real sub-gem helper stubs
11
+
12
+ ## [0.1.1] - 2026-03-18
13
+
14
+ ### Changed
15
+ - Enforce SUBSTRATE_TYPES validation in FermentationEngine#create_substrate (returns nil for invalid type)
16
+ - Enforce CATALYST_TYPES validation in FermentationEngine#catalyze (returns nil for invalid catalyst)
17
+
5
18
  ## [0.1.0] - 2026-03-18
6
19
 
7
20
  ### Added
data/README.md CHANGED
@@ -1,13 +1,51 @@
1
1
  # lex-agentic-learning
2
2
 
3
- LEX agentic learning domain: adaptation, memory consolidation, skill acquisition
3
+ Domain consolidation gem for learning, adaptation, and knowledge acquisition. Bundles 14 source extensions into one loadable unit under `Legion::Extensions::Agentic::Learning`.
4
4
 
5
- ## Sub-modules
5
+ ## Overview
6
6
 
7
- *(populated as extensions are consolidated)*
7
+ **Gem**: `lex-agentic-learning`
8
+ **Version**: 0.1.1
9
+ **Namespace**: `Legion::Extensions::Agentic::Learning`
10
+
11
+ ## Sub-Modules
12
+
13
+ | Sub-Module | Source Gem | Purpose |
14
+ |---|---|---|
15
+ | `Learning::Curiosity` | `lex-curiosity` | Intrinsic curiosity — knowledge gap detection, wonder queue, salience decay |
16
+ | `Learning::EpistemicCuriosity` | `lex-epistemic-curiosity` | Information-gap theory — specific vs. diversive curiosity |
17
+ | `Learning::Hebbian` | `lex-hebbian-assembly` | Cell assembly formation — neurons that fire together wire together |
18
+ | `Learning::Habit` | `lex-habit` | Habit formation — action sequence pattern recognition, maturity stages |
19
+ | `Learning::LearningRate` | `lex-learning-rate` | Dynamic learning rate adaptation based on accuracy and stability |
20
+ | `Learning::MetaLearning` | `lex-meta-learning` | Learning-to-learn — strategy selection per domain |
21
+ | `Learning::PreferenceLearning` | `lex-preference-learning` | Learns stable preferences from choices over time |
22
+ | `Learning::Procedural` | `lex-procedural-learning` | Skill acquisition through practice — automatization |
23
+ | `Learning::Anchoring` | `lex-anchoring` | Anchoring bias in estimation |
24
+ | `Learning::Plasticity` | `lex-cognitive-plasticity` | Neural-style plasticity — synaptic weight adjustment |
25
+ | `Learning::Scaffolding` | `lex-cognitive-scaffolding` | Temporary learning assists that fade as competence grows |
26
+ | `Learning::Fermentation` | `lex-cognitive-fermentation` | Time-based transformation of knowledge |
27
+ | `Learning::Chrysalis` | `lex-cognitive-chrysalis` | Metamorphic state change — transformation through withdrawal |
28
+ | `Learning::Catalyst` | `lex-cognitive-catalyst` | Accelerating cognitive transformation |
29
+
30
+ ## Actors
31
+
32
+ - `Learning::Hebbian::Actors::Decay` — interval actor, decays Hebbian assembly connection strength
33
+ - `Learning::PreferenceLearning::Actors::Decay` — interval actor, decays older preference observations
8
34
 
9
35
  ## Installation
10
36
 
11
37
  ```ruby
12
38
  gem 'lex-agentic-learning'
13
39
  ```
40
+
41
+ ## Development
42
+
43
+ ```bash
44
+ bundle install
45
+ bundle exec rspec # 1316 examples, 0 failures
46
+ bundle exec rubocop # 0 offenses
47
+ ```
48
+
49
+ ## License
50
+
51
+ MIT
@@ -24,6 +24,14 @@ Gem::Specification.new do |spec|
24
24
  end
25
25
  spec.require_paths = ['lib']
26
26
 
27
+ spec.add_dependency 'legion-cache', '>= 1.3.11'
28
+ spec.add_dependency 'legion-crypt', '>= 1.4.9'
29
+ spec.add_dependency 'legion-data', '>= 1.4.17'
30
+ spec.add_dependency 'legion-json', '>= 1.2.1'
31
+ spec.add_dependency 'legion-logging', '>= 1.3.2'
32
+ spec.add_dependency 'legion-settings', '>= 1.3.14'
33
+ spec.add_dependency 'legion-transport', '>= 1.3.9'
34
+
27
35
  spec.add_development_dependency 'rspec', '~> 3.13'
28
36
  spec.add_development_dependency 'rubocop', '~> 1.60'
29
37
  spec.add_development_dependency 'rubocop-rspec', '~> 2.26'
@@ -12,28 +12,28 @@ module Legion
12
12
 
13
13
  def record_anchor(value:, domain: :general, **)
14
14
  anchor = anchor_store.add(value: value, domain: domain)
15
- Legion::Logging.debug "[anchoring] record_anchor domain=#{domain} value=#{value} id=#{anchor.id}"
15
+ log.debug "[anchoring] record_anchor domain=#{domain} value=#{value} id=#{anchor.id}"
16
16
  { success: true, anchor: anchor.to_h }
17
17
  end
18
18
 
19
19
  def evaluate_estimate(estimate:, domain: :general, **)
20
20
  result = anchor_store.evaluate(estimate: estimate, domain: domain)
21
- Legion::Logging.debug "[anchoring] evaluate_estimate domain=#{domain} estimate=#{estimate} " \
22
- "anchored=#{result[:anchored_estimate].round(4)} pull=#{result[:pull_strength].round(4)}"
21
+ log.debug "[anchoring] evaluate_estimate domain=#{domain} estimate=#{estimate} " \
22
+ "anchored=#{result[:anchored_estimate].round(4)} pull=#{result[:pull_strength].round(4)}"
23
23
  { success: true }.merge(result)
24
24
  end
25
25
 
26
26
  def reference_frame(value:, domain: :general, **)
27
27
  result = anchor_store.reference_frame(value: value, domain: domain)
28
- Legion::Logging.debug "[anchoring] reference_frame domain=#{domain} value=#{value} " \
29
- "gain_or_loss=#{result[:gain_or_loss]}"
28
+ log.debug "[anchoring] reference_frame domain=#{domain} value=#{value} " \
29
+ "gain_or_loss=#{result[:gain_or_loss]}"
30
30
  { success: true }.merge(result)
31
31
  end
32
32
 
33
33
  def de_anchor(estimate:, domain: :general, **)
34
34
  anchor = anchor_store.strongest(domain: domain)
35
35
  if anchor.nil?
36
- Legion::Logging.debug "[anchoring] de_anchor domain=#{domain} no anchor found"
36
+ log.debug "[anchoring] de_anchor domain=#{domain} no anchor found"
37
37
  return { success: true, corrected_estimate: estimate.to_f, anchor_bias: 0.0, domain: domain }
38
38
  end
39
39
 
@@ -41,8 +41,8 @@ module Legion
41
41
  bias = biased - estimate.to_f
42
42
  corrected = estimate.to_f - bias
43
43
 
44
- Legion::Logging.debug "[anchoring] de_anchor domain=#{domain} estimate=#{estimate} " \
45
- "corrected=#{corrected.round(4)} bias=#{bias.round(4)}"
44
+ log.debug "[anchoring] de_anchor domain=#{domain} estimate=#{estimate} " \
45
+ "corrected=#{corrected.round(4)} bias=#{bias.round(4)}"
46
46
 
47
47
  {
48
48
  success: true,
@@ -56,13 +56,13 @@ module Legion
56
56
 
57
57
  def shift_reference(domain:, new_reference:, **)
58
58
  result = anchor_store.shift_reference(domain: domain, new_reference: new_reference)
59
- Legion::Logging.info "[anchoring] shift_reference domain=#{domain} new=#{new_reference} significant=#{result[:significant]}"
59
+ log.info "[anchoring] shift_reference domain=#{domain} new=#{new_reference} significant=#{result[:significant]}"
60
60
  { success: true }.merge(result)
61
61
  end
62
62
 
63
63
  def update_anchoring(**)
64
64
  pruned = anchor_store.decay_all
65
- Legion::Logging.debug "[anchoring] update_anchoring pruned=#{pruned}"
65
+ log.debug "[anchoring] update_anchoring pruned=#{pruned}"
66
66
  { success: true, pruned: pruned }
67
67
  end
68
68
 
@@ -70,7 +70,7 @@ module Legion
70
70
  domain = domain.to_sym
71
71
  anchor = anchor_store.strongest(domain: domain)
72
72
  all_list = anchor_store.instance_variable_get(:@anchors)[domain] || []
73
- Legion::Logging.debug "[anchoring] domain_anchors domain=#{domain} count=#{all_list.size}"
73
+ log.debug "[anchoring] domain_anchors domain=#{domain} count=#{all_list.size}"
74
74
  {
75
75
  success: true,
76
76
  domain: domain,
@@ -82,7 +82,7 @@ module Legion
82
82
 
83
83
  def anchoring_stats(**)
84
84
  stats = anchor_store.to_h
85
- Legion::Logging.debug "[anchoring] stats domains=#{stats[:domain_count]} total=#{stats[:total_anchors]}"
85
+ log.debug "[anchoring] stats domains=#{stats[:domain_count]} total=#{stats[:total_anchors]}"
86
86
  { success: true }.merge(stats)
87
87
  end
88
88
 
@@ -22,11 +22,11 @@ module Legion
22
22
  opts[:specificity] = specificity unless specificity.nil?
23
23
 
24
24
  catalyst = e.create_catalyst(**opts)
25
- Legion::Logging.debug "[cognitive_catalyst] create_catalyst id=#{catalyst.id[0..7]} " \
26
- "type=#{catalyst_type} domain=#{domain} potency=#{catalyst.potency.round(2)}"
25
+ log.debug "[cognitive_catalyst] create_catalyst id=#{catalyst.id[0..7]} " \
26
+ "type=#{catalyst_type} domain=#{domain} potency=#{catalyst.potency.round(2)}"
27
27
  { success: true, catalyst: catalyst.to_h }
28
28
  rescue ArgumentError => e
29
- Legion::Logging.warn "[cognitive_catalyst] create_catalyst failed: #{e.message}"
29
+ log.warn "[cognitive_catalyst] create_catalyst failed: #{e.message}"
30
30
  { success: false, reason: e.message }
31
31
  end
32
32
 
@@ -41,51 +41,51 @@ module Legion
41
41
  opts[:activation_energy] = activation_energy unless activation_energy.nil?
42
42
 
43
43
  reaction = e.create_reaction(**opts)
44
- Legion::Logging.debug "[cognitive_catalyst] create_reaction id=#{reaction.id[0..7]} " \
45
- "type=#{reaction_type} reactants=#{reaction.reactants.size}"
44
+ log.debug "[cognitive_catalyst] create_reaction id=#{reaction.id[0..7]} " \
45
+ "type=#{reaction_type} reactants=#{reaction.reactants.size}"
46
46
  { success: true, reaction: reaction.to_h }
47
47
  rescue ArgumentError => e
48
- Legion::Logging.warn "[cognitive_catalyst] create_reaction failed: #{e.message}"
48
+ log.warn "[cognitive_catalyst] create_reaction failed: #{e.message}"
49
49
  { success: false, reason: e.message }
50
50
  end
51
51
 
52
52
  def apply_catalyst(catalyst_id:, reaction_id:, engine: nil, **)
53
53
  e = engine || default_engine
54
54
  result = e.apply_catalyst(catalyst_id: catalyst_id, reaction_id: reaction_id)
55
- Legion::Logging.debug '[cognitive_catalyst] apply_catalyst ' \
56
- "catalyst=#{catalyst_id[0..7]} reaction=#{reaction_id[0..7]} " \
57
- "success=#{result[:success]}"
55
+ log.debug '[cognitive_catalyst] apply_catalyst ' \
56
+ "catalyst=#{catalyst_id[0..7]} reaction=#{reaction_id[0..7]} " \
57
+ "success=#{result[:success]}"
58
58
  result
59
59
  end
60
60
 
61
61
  def attempt_reaction(reaction_id:, energy_input:, engine: nil, **)
62
62
  e = engine || default_engine
63
63
  result = e.attempt_reaction(reaction_id: reaction_id, energy_input: energy_input)
64
- Legion::Logging.debug "[cognitive_catalyst] attempt_reaction id=#{reaction_id[0..7]} " \
65
- "energy=#{energy_input} completed=#{result[:completed]}"
64
+ log.debug "[cognitive_catalyst] attempt_reaction id=#{reaction_id[0..7]} " \
65
+ "energy=#{energy_input} completed=#{result[:completed]}"
66
66
  result
67
67
  end
68
68
 
69
69
  def recharge(catalyst_id:, amount:, engine: nil, **)
70
70
  e = engine || default_engine
71
71
  result = e.recharge_catalyst(catalyst_id: catalyst_id, amount: amount)
72
- Legion::Logging.debug "[cognitive_catalyst] recharge id=#{catalyst_id[0..7]} " \
73
- "amount=#{amount} potency=#{result[:potency]&.round(2)}"
72
+ log.debug "[cognitive_catalyst] recharge id=#{catalyst_id[0..7]} " \
73
+ "amount=#{amount} potency=#{result[:potency]&.round(2)}"
74
74
  result
75
75
  end
76
76
 
77
77
  def list_catalysts(engine: nil, **)
78
78
  e = engine || default_engine
79
79
  catalysts = e.all_catalysts
80
- Legion::Logging.debug "[cognitive_catalyst] list_catalysts count=#{catalysts.size}"
80
+ log.debug "[cognitive_catalyst] list_catalysts count=#{catalysts.size}"
81
81
  { success: true, catalysts: catalysts.map(&:to_h), count: catalysts.size }
82
82
  end
83
83
 
84
84
  def catalyst_status(engine: nil, **)
85
85
  e = engine || default_engine
86
86
  report = e.catalyst_report
87
- Legion::Logging.debug "[cognitive_catalyst] catalyst_status total=#{report[:total_catalysts]} " \
88
- "reactions=#{report[:total_reactions]} rate=#{report[:catalyzed_rate].round(2)}"
87
+ log.debug "[cognitive_catalyst] catalyst_status total=#{report[:total_catalysts]} " \
88
+ "reactions=#{report[:total_reactions]} rate=#{report[:catalyzed_rate].round(2)}"
89
89
  { success: true }.merge(report)
90
90
  end
91
91
 
@@ -22,7 +22,7 @@ module Legion
22
22
  return { success: false, reason: :invalid_type, valid_types: Helpers::Constants::CHRYSALIS_TYPES }
23
23
  end
24
24
 
25
- Legion::Logging.debug "[cognitive_chrysalis] creating chrysalis type=#{type}"
25
+ log.debug "[cognitive_chrysalis] creating chrysalis type=#{type}"
26
26
  engine.create_chrysalis(chrysalis_type: type, content: content)
27
27
  rescue ArgumentError => e
28
28
  { success: false, reason: e.message }
@@ -30,7 +30,7 @@ module Legion
30
30
 
31
31
  def create_cocoon(environment: 'default', temperature: 0.5, humidity: 0.5, engine: nil, **)
32
32
  engine ||= default_engine
33
- Legion::Logging.debug "[cognitive_chrysalis] creating cocoon environment=#{environment}"
33
+ log.debug "[cognitive_chrysalis] creating cocoon environment=#{environment}"
34
34
  engine.create_cocoon(environment: environment, temperature: temperature, humidity: humidity)
35
35
  rescue ArgumentError => e
36
36
  { success: false, reason: e.message }
@@ -40,7 +40,7 @@ module Legion
40
40
  return { success: false, reason: :missing_chrysalis_id } if chrysalis_id.nil?
41
41
 
42
42
  engine ||= default_engine
43
- Legion::Logging.debug "[cognitive_chrysalis] spinning chrysalis=#{chrysalis_id}"
43
+ log.debug "[cognitive_chrysalis] spinning chrysalis=#{chrysalis_id}"
44
44
  engine.spin(chrysalis_id: chrysalis_id)
45
45
  rescue ArgumentError => e
46
46
  { success: false, reason: e.message }
@@ -51,7 +51,7 @@ module Legion
51
51
  return { success: false, reason: :missing_cocoon_id } if cocoon_id.nil?
52
52
 
53
53
  engine ||= default_engine
54
- Legion::Logging.debug "[cognitive_chrysalis] enclosing chrysalis=#{chrysalis_id} in cocoon=#{cocoon_id}"
54
+ log.debug "[cognitive_chrysalis] enclosing chrysalis=#{chrysalis_id} in cocoon=#{cocoon_id}"
55
55
  engine.enclose(chrysalis_id: chrysalis_id, cocoon_id: cocoon_id)
56
56
  rescue ArgumentError => e
57
57
  { success: false, reason: e.message }
@@ -61,7 +61,7 @@ module Legion
61
61
  return { success: false, reason: :missing_chrysalis_id } if chrysalis_id.nil?
62
62
 
63
63
  engine ||= default_engine
64
- Legion::Logging.debug "[cognitive_chrysalis] incubating chrysalis=#{chrysalis_id}"
64
+ log.debug "[cognitive_chrysalis] incubating chrysalis=#{chrysalis_id}"
65
65
  engine.incubate(chrysalis_id: chrysalis_id)
66
66
  rescue ArgumentError => e
67
67
  { success: false, reason: e.message }
@@ -69,7 +69,7 @@ module Legion
69
69
 
70
70
  def incubate_all(engine: nil, **)
71
71
  engine ||= default_engine
72
- Legion::Logging.debug '[cognitive_chrysalis] incubating all eligible chrysalises'
72
+ log.debug '[cognitive_chrysalis] incubating all eligible chrysalises'
73
73
  engine.incubate_all!
74
74
  rescue ArgumentError => e
75
75
  { success: false, reason: e.message }
@@ -79,7 +79,7 @@ module Legion
79
79
  return { success: false, reason: :missing_chrysalis_id } if chrysalis_id.nil?
80
80
 
81
81
  engine ||= default_engine
82
- Legion::Logging.debug "[cognitive_chrysalis] emerging chrysalis=#{chrysalis_id} force=#{force}"
82
+ log.debug "[cognitive_chrysalis] emerging chrysalis=#{chrysalis_id} force=#{force}"
83
83
  if force
84
84
  engine.force_emerge(chrysalis_id: chrysalis_id)
85
85
  else
@@ -93,7 +93,7 @@ module Legion
93
93
  return { success: false, reason: :missing_cocoon_id } if cocoon_id.nil?
94
94
 
95
95
  engine ||= default_engine
96
- Legion::Logging.debug "[cognitive_chrysalis] disturbing cocoon=#{cocoon_id} force=#{force}"
96
+ log.debug "[cognitive_chrysalis] disturbing cocoon=#{cocoon_id} force=#{force}"
97
97
  engine.disturb_cocoon(cocoon_id: cocoon_id, force: force)
98
98
  rescue ArgumentError => e
99
99
  { success: false, reason: e.message }
@@ -13,7 +13,7 @@ module Legion
13
13
 
14
14
  def detect_gaps(prior_results: {}, **)
15
15
  gaps = Helpers::GapDetector.detect(prior_results)
16
- Legion::Logging.debug "[curiosity] detected #{gaps.size} knowledge gaps"
16
+ log.debug "[curiosity] detected #{gaps.size} knowledge gaps"
17
17
 
18
18
  created = create_wonders_from_gaps(gaps)
19
19
  build_detect_result(gaps, created)
@@ -28,7 +28,7 @@ module Legion
28
28
  source_trace_ids: source_trace_ids
29
29
  )
30
30
  wonder_store.store(wonder)
31
- Legion::Logging.info "[curiosity] manually generated wonder: #{question}"
31
+ log.info "[curiosity] manually generated wonder: #{question}"
32
32
  wonder
33
33
  end
34
34
 
@@ -39,7 +39,7 @@ module Legion
39
39
  return { error: :not_explorable, reason: :max_attempts } unless Helpers::Wonder.explorable?(wonder)
40
40
 
41
41
  wonder_store.update(wonder_id, attempts: wonder[:attempts] + 1, last_explored_at: Time.now.utc)
42
- Legion::Logging.info "[curiosity] exploring: #{wonder[:question]} (attempt ##{wonder[:attempts] + 1})"
42
+ log.info "[curiosity] exploring: #{wonder[:question]} (attempt ##{wonder[:attempts] + 1})"
43
43
  { exploring: true, wonder_id: wonder_id, attempt: wonder[:attempts] + 1 }
44
44
  end
45
45
 
@@ -54,20 +54,20 @@ module Legion
54
54
 
55
55
  def curiosity_intensity(**)
56
56
  intensity = compute_intensity
57
- Legion::Logging.debug "[curiosity] intensity=#{intensity.round(3)}"
57
+ log.debug "[curiosity] intensity=#{intensity.round(3)}"
58
58
  { intensity: intensity, active_wonders: wonder_store.active_count,
59
59
  resolution_rate: wonder_store.resolution_rate.round(3), top_domain: top_curiosity_domain }
60
60
  end
61
61
 
62
62
  def top_wonders(limit: 5, **)
63
63
  wonders = wonder_store.top_balanced(limit: limit)
64
- Legion::Logging.debug "[curiosity] top #{wonders.size} wonders requested"
64
+ log.debug "[curiosity] top #{wonders.size} wonders requested"
65
65
  { wonders: wonders.map { |w| format_wonder(w) } }
66
66
  end
67
67
 
68
68
  def form_agenda(**)
69
69
  wonders = wonder_store.top_balanced(limit: 5)
70
- Legion::Logging.debug "[curiosity] forming agenda from #{wonders.size} wonders"
70
+ log.debug "[curiosity] forming agenda from #{wonders.size} wonders"
71
71
  { agenda_items: wonders.map { |w| format_agenda_item(w) }, source: :curiosity }
72
72
  end
73
73
 
@@ -79,7 +79,7 @@ module Legion
79
79
 
80
80
  def decay_wonders(hours_elapsed: 1.0, **)
81
81
  pruned = wonder_store.decay_all(hours_elapsed: hours_elapsed)
82
- Legion::Logging.debug "[curiosity] decay: pruned=#{pruned} remaining=#{wonder_store.active_count}"
82
+ log.debug "[curiosity] decay: pruned=#{pruned} remaining=#{wonder_store.active_count}"
83
83
  { pruned: pruned, remaining: wonder_store.active_count }
84
84
  end
85
85
 
@@ -97,14 +97,14 @@ module Legion
97
97
  :salience, :information_gain, :source_trace_ids))
98
98
  wonder_store.store(wonder)
99
99
  created << wonder
100
- Legion::Logging.info "[curiosity] new wonder: #{wonder[:question]} (#{wonder[:gap_type]}/#{wonder[:domain]})"
100
+ log.info "[curiosity] new wonder: #{wonder[:question]} (#{wonder[:gap_type]}/#{wonder[:domain]})"
101
101
  end
102
102
  end
103
103
 
104
104
  def build_detect_result(gaps, created)
105
105
  intensity = compute_intensity
106
106
  top = wonder_store.top_balanced(limit: 3)
107
- Legion::Logging.debug "[curiosity] intensity=#{intensity.round(3)} active=#{wonder_store.active_count}"
107
+ log.debug "[curiosity] intensity=#{intensity.round(3)} active=#{wonder_store.active_count}"
108
108
  { gaps_detected: gaps.size, wonders_created: created.size, curiosity_intensity: intensity,
109
109
  top_wonders: top.map { |w| { wonder_id: w[:wonder_id], question: w[:question], score: Helpers::Wonder.score(w).round(3) } },
110
110
  active_count: wonder_store.active_count }
@@ -112,7 +112,7 @@ module Legion
112
112
 
113
113
  def build_resolve_result(wonder, resolved, actual_gain)
114
114
  reward = actual_gain * Helpers::Constants::CURIOSITY_REWARD_MULTIPLIER
115
- Legion::Logging.info "[curiosity] resolved: #{wonder[:question]} gain=#{actual_gain.round(2)}"
115
+ log.info "[curiosity] resolved: #{wonder[:question]} gain=#{actual_gain.round(2)}"
116
116
  { resolved: true, wonder_id: wonder[:wonder_id], actual_gain: actual_gain,
117
117
  expected_gain: wonder[:information_gain], reward: reward,
118
118
  domain: resolved[:domain], resolution_rate: wonder_store.resolution_rate.round(3) }
@@ -13,9 +13,9 @@ module Legion
13
13
  def create_gap(question:, domain:, gap_type: :factual, urgency: Helpers::Constants::DEFAULT_URGENCY, **)
14
14
  result = engine.create_gap(question: question, domain: domain, gap_type: gap_type, urgency: urgency)
15
15
  if result[:created]
16
- Legion::Logging.info "[epistemic_curiosity] gap created: id=#{result[:gap][:id]} domain=#{domain} type=#{gap_type}"
16
+ log.info "[epistemic_curiosity] gap created: id=#{result[:gap][:id]} domain=#{domain} type=#{gap_type}"
17
17
  else
18
- Legion::Logging.debug "[epistemic_curiosity] gap not created: reason=#{result[:reason]}"
18
+ log.debug "[epistemic_curiosity] gap not created: reason=#{result[:reason]}"
19
19
  end
20
20
  result
21
21
  end
@@ -24,9 +24,9 @@ module Legion
24
24
  result = engine.explore_gap(gap_id: gap_id)
25
25
  if result[:found]
26
26
  gap = result[:gap]
27
- Legion::Logging.debug "[epistemic_curiosity] explore: id=#{gap_id} explorations=#{gap[:explorations]} urgency=#{gap[:urgency]}"
27
+ log.debug "[epistemic_curiosity] explore: id=#{gap_id} explorations=#{gap[:explorations]} urgency=#{gap[:urgency]}"
28
28
  else
29
- Legion::Logging.debug "[epistemic_curiosity] explore: id=#{gap_id} not found"
29
+ log.debug "[epistemic_curiosity] explore: id=#{gap_id} not found"
30
30
  end
31
31
  result
32
32
  end
@@ -35,9 +35,9 @@ module Legion
35
35
  result = engine.satisfy_gap(gap_id: gap_id, amount: amount)
36
36
  if result[:found]
37
37
  gap = result[:gap]
38
- Legion::Logging.debug "[epistemic_curiosity] satisfy: id=#{gap_id} satisfaction=#{gap[:satisfaction]} resolved=#{gap[:resolved]}"
38
+ log.debug "[epistemic_curiosity] satisfy: id=#{gap_id} satisfaction=#{gap[:satisfaction]} resolved=#{gap[:resolved]}"
39
39
  else
40
- Legion::Logging.debug "[epistemic_curiosity] satisfy: id=#{gap_id} not found"
40
+ log.debug "[epistemic_curiosity] satisfy: id=#{gap_id} not found"
41
41
  end
42
42
  result
43
43
  end
@@ -45,40 +45,40 @@ module Legion
45
45
  def resolve_gap(gap_id:, **)
46
46
  result = engine.resolve_gap(gap_id: gap_id)
47
47
  if result[:found]
48
- Legion::Logging.info "[epistemic_curiosity] resolved: id=#{gap_id}"
48
+ log.info "[epistemic_curiosity] resolved: id=#{gap_id}"
49
49
  else
50
- Legion::Logging.debug "[epistemic_curiosity] resolve: id=#{gap_id} not found"
50
+ log.debug "[epistemic_curiosity] resolve: id=#{gap_id} not found"
51
51
  end
52
52
  result
53
53
  end
54
54
 
55
55
  def most_urgent_gaps(limit: 5, **)
56
56
  gaps = engine.most_urgent(limit: limit)
57
- Legion::Logging.debug "[epistemic_curiosity] most_urgent: limit=#{limit} returned=#{gaps.size}"
57
+ log.debug "[epistemic_curiosity] most_urgent: limit=#{limit} returned=#{gaps.size}"
58
58
  { gaps: gaps.map(&:to_h), count: gaps.size }
59
59
  end
60
60
 
61
61
  def gaps_by_domain(domain:, **)
62
62
  gaps = engine.by_domain(domain)
63
- Legion::Logging.debug "[epistemic_curiosity] by_domain: domain=#{domain} count=#{gaps.size}"
63
+ log.debug "[epistemic_curiosity] by_domain: domain=#{domain} count=#{gaps.size}"
64
64
  { gaps: gaps.map(&:to_h), count: gaps.size, domain: domain }
65
65
  end
66
66
 
67
67
  def gaps_by_type(gap_type:, **)
68
68
  gaps = engine.by_type(gap_type)
69
- Legion::Logging.debug "[epistemic_curiosity] by_type: gap_type=#{gap_type} count=#{gaps.size}"
69
+ log.debug "[epistemic_curiosity] by_type: gap_type=#{gap_type} count=#{gaps.size}"
70
70
  { gaps: gaps.map(&:to_h), count: gaps.size, gap_type: gap_type }
71
71
  end
72
72
 
73
73
  def decay_gaps(**)
74
74
  count = engine.decay_all
75
- Legion::Logging.debug "[epistemic_curiosity] decay cycle: gaps_updated=#{count}"
75
+ log.debug "[epistemic_curiosity] decay cycle: gaps_updated=#{count}"
76
76
  { decayed: count }
77
77
  end
78
78
 
79
79
  def curiosity_report(**)
80
80
  report = engine.curiosity_report
81
- Legion::Logging.debug "[epistemic_curiosity] report: open=#{report[:open_gaps]} debt=#{report[:information_debt]}"
81
+ log.debug "[epistemic_curiosity] report: open=#{report[:open_gaps]} debt=#{report[:information_debt]}"
82
82
  report
83
83
  end
84
84
 
@@ -15,6 +15,8 @@ module Legion
15
15
  end
16
16
 
17
17
  def create_substrate(substrate_type:, domain:, content: '', potency: nil, volatility: nil)
18
+ return nil unless SUBSTRATE_TYPES.include?(substrate_type.to_sym)
19
+
18
20
  sub = Substrate.new(substrate_type: substrate_type, domain: domain, content: content,
19
21
  potency: potency, volatility: volatility)
20
22
  @substrates[sub.id] = sub
@@ -33,6 +35,8 @@ module Legion
33
35
  end
34
36
 
35
37
  def catalyze(substrate_id:, catalyst_type:)
38
+ return nil unless CATALYST_TYPES.include?(catalyst_type.to_sym)
39
+
36
40
  sub = @substrates[substrate_id]
37
41
  return nil unless sub
38
42
 
@@ -5,7 +5,7 @@ module Legion
5
5
  module Agentic
6
6
  module Learning
7
7
  module Fermentation
8
- VERSION = '0.1.0'
8
+ VERSION = '0.1.1'
9
9
  end
10
10
  end
11
11
  end
@@ -11,10 +11,10 @@ module Legion
11
11
  Legion::Extensions::Helpers.const_defined?(:Lex)
12
12
 
13
13
  def observe_action(action:, context: {}, **)
14
- Legion::Logging.debug "[habit] observe_action: action=#{action} context=#{context}"
14
+ log.debug "[habit] observe_action: action=#{action} context=#{context}"
15
15
  habit_store.record_action(action, context: context)
16
16
  detected = habit_store.detect_patterns
17
- Legion::Logging.info "[habit] patterns detected: #{detected.size}" unless detected.empty?
17
+ log.info "[habit] patterns detected: #{detected.size}" unless detected.empty?
18
18
  {
19
19
  recorded: true,
20
20
  action: action,
@@ -25,7 +25,7 @@ module Legion
25
25
 
26
26
  def suggest_habit(context: {}, **)
27
27
  matches = habit_store.find_matching(context: context)
28
- Legion::Logging.debug "[habit] suggest_habit: context=#{context} matches=#{matches.size}"
28
+ log.debug "[habit] suggest_habit: context=#{context} matches=#{matches.size}"
29
29
  if matches.empty?
30
30
  { suggestion: nil, reason: :no_matching_habits }
31
31
  else
@@ -43,19 +43,19 @@ module Legion
43
43
  return { error: :not_found } unless habit
44
44
 
45
45
  habit.record_execution(success: success)
46
- Legion::Logging.debug "[habit] execute_habit: id=#{id} success=#{success} maturity=#{habit.maturity}"
46
+ log.debug "[habit] execute_habit: id=#{id} success=#{success} maturity=#{habit.maturity}"
47
47
  { executed: true, habit: habit.to_h, cognitive_cost: habit.cognitive_cost }
48
48
  end
49
49
 
50
50
  def decay_habits(**)
51
51
  removed = habit_store.decay_all
52
- Legion::Logging.debug "[habit] decay_habits: removed=#{removed}"
52
+ log.debug "[habit] decay_habits: removed=#{removed}"
53
53
  { decayed: true, removed_count: removed }
54
54
  end
55
55
 
56
56
  def merge_habits(**)
57
57
  merged = habit_store.merge_similar
58
- Legion::Logging.debug "[habit] merge_habits: merged=#{merged}"
58
+ log.debug "[habit] merge_habits: merged=#{merged}"
59
59
  { merged_count: merged }
60
60
  end
61
61
 
@@ -65,7 +65,7 @@ module Legion
65
65
 
66
66
  def habit_repertoire(maturity: nil, limit: 20, **)
67
67
  habits = maturity ? habit_store.by_maturity(maturity.to_sym) : habit_store.habits.values
68
- Legion::Logging.debug "[habit] habit_repertoire: maturity=#{maturity} total=#{habits.size}"
68
+ log.debug "[habit] habit_repertoire: maturity=#{maturity} total=#{habits.size}"
69
69
  {
70
70
  habits: habits.sort_by { |h| -h.strength }.first(limit).map(&:to_h),
71
71
  total: habits.size