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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +41 -3
- data/lex-agentic-learning.gemspec +8 -0
- data/lib/legion/extensions/agentic/learning/anchoring/runners/anchoring.rb +12 -12
- data/lib/legion/extensions/agentic/learning/catalyst/runners/cognitive_catalyst.rb +16 -16
- data/lib/legion/extensions/agentic/learning/chrysalis/runners/cognitive_chrysalis.rb +8 -8
- data/lib/legion/extensions/agentic/learning/curiosity/runners/curiosity.rb +10 -10
- data/lib/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity.rb +13 -13
- data/lib/legion/extensions/agentic/learning/fermentation/helpers/fermentation_engine.rb +4 -0
- data/lib/legion/extensions/agentic/learning/fermentation/version.rb +1 -1
- data/lib/legion/extensions/agentic/learning/habit/runners/habit.rb +7 -7
- data/lib/legion/extensions/agentic/learning/hebbian/runners/hebbian_assembly.rb +10 -10
- data/lib/legion/extensions/agentic/learning/learning_rate/runners/learning_rate.rb +4 -4
- data/lib/legion/extensions/agentic/learning/meta_learning/runners/meta_learning.rb +20 -20
- data/lib/legion/extensions/agentic/learning/preference_learning/runners/preference_learning.rb +10 -10
- data/lib/legion/extensions/agentic/learning/procedural/runners/procedural_learning.rb +14 -14
- data/lib/legion/extensions/agentic/learning/scaffolding/runners/cognitive_scaffolding.rb +19 -19
- data/lib/legion/extensions/agentic/learning/version.rb +1 -1
- data/spec/legion/extensions/agentic/learning/fermentation/helpers/fermentation_engine_spec.rb +26 -0
- data/spec/spec_helper.rb +21 -21
- metadata +99 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8e389e9f54e7a9c30da3458b1cfda5723454543d12b47b76bc8a99c9a35e72f
|
|
4
|
+
data.tar.gz: 228ba88c5b55ee8a8886de351f10ce0a10abad53cbdca36e0131ad1b0e013ccb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) }
|
data/lib/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity.rb
CHANGED
|
@@ -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
|
-
|
|
16
|
+
log.info "[epistemic_curiosity] gap created: id=#{result[:gap][:id]} domain=#{domain} type=#{gap_type}"
|
|
17
17
|
else
|
|
18
|
-
|
|
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
|
-
|
|
27
|
+
log.debug "[epistemic_curiosity] explore: id=#{gap_id} explorations=#{gap[:explorations]} urgency=#{gap[:urgency]}"
|
|
28
28
|
else
|
|
29
|
-
|
|
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
|
-
|
|
38
|
+
log.debug "[epistemic_curiosity] satisfy: id=#{gap_id} satisfaction=#{gap[:satisfaction]} resolved=#{gap[:resolved]}"
|
|
39
39
|
else
|
|
40
|
-
|
|
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
|
-
|
|
48
|
+
log.info "[epistemic_curiosity] resolved: id=#{gap_id}"
|
|
49
49
|
else
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -11,10 +11,10 @@ module Legion
|
|
|
11
11
|
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
12
12
|
|
|
13
13
|
def observe_action(action:, context: {}, **)
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|