lex-agentic-learning 0.1.3 → 0.1.5
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 +15 -0
- data/Gemfile +2 -0
- data/lex-agentic-learning.gemspec +3 -2
- data/lib/legion/extensions/agentic/learning/anchoring/runners/anchoring.rb +2 -2
- data/lib/legion/extensions/agentic/learning/catalyst/runners/cognitive_catalyst.rb +2 -2
- data/lib/legion/extensions/agentic/learning/chrysalis/runners/cognitive_chrysalis.rb +1 -1
- data/lib/legion/extensions/agentic/learning/curiosity/runners/curiosity.rb +2 -2
- data/lib/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity.rb +2 -2
- data/lib/legion/extensions/agentic/learning/habit/runners/habit.rb +2 -2
- data/lib/legion/extensions/agentic/learning/hebbian/runners/hebbian_assembly.rb +2 -2
- data/lib/legion/extensions/agentic/learning/learning_rate/runners/learning_rate.rb +2 -2
- data/lib/legion/extensions/agentic/learning/meta_learning/runners/meta_learning.rb +2 -2
- data/lib/legion/extensions/agentic/learning/outcome_listener/actors/outcome_listener.rb +57 -0
- data/lib/legion/extensions/agentic/learning/outcome_listener/client.rb +47 -0
- data/lib/legion/extensions/agentic/learning/outcome_listener/helpers/constants.rb +22 -0
- data/lib/legion/extensions/agentic/learning/outcome_listener/helpers/domain_extractor.rb +37 -0
- data/lib/legion/extensions/agentic/learning/outcome_listener/helpers/lesson_builder.rb +45 -0
- data/lib/legion/extensions/agentic/learning/outcome_listener/runners/outcome_listener.rb +119 -0
- data/lib/legion/extensions/agentic/learning/outcome_listener.rb +3 -0
- data/lib/legion/extensions/agentic/learning/preference_learning/runners/preference_learning.rb +2 -2
- data/lib/legion/extensions/agentic/learning/procedural/runners/procedural_learning.rb +2 -2
- data/lib/legion/extensions/agentic/learning/scaffolding/runners/cognitive_scaffolding.rb +2 -2
- data/lib/legion/extensions/agentic/learning/version.rb +1 -1
- data/lib/legion/extensions/agentic/learning.rb +2 -1
- data/spec/legion/extensions/agentic/learning/outcome_listener/client_spec.rb +75 -0
- data/spec/legion/extensions/agentic/learning/outcome_listener/helpers/domain_extractor_spec.rb +55 -0
- data/spec/legion/extensions/agentic/learning/outcome_listener/helpers/lesson_builder_spec.rb +78 -0
- data/spec/legion/extensions/agentic/learning/outcome_listener/runners/outcome_listener_spec.rb +118 -0
- metadata +34 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31725ad79b661a818e233bd94df4a06db34375f1d863e981ea41f5851fce4040
|
|
4
|
+
data.tar.gz: b63c48208160bbbbc292fd8a7563d51a53cc5a8bbed4d3cc9aac316bf19a6556
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 93f6862f753e160b1fa73d2fe10627e765a5a4b85e8e504a3835addee1d4d1544e1081fff84db2ebd179bfce9ef085b1709f3c1bb1b804c65d17db8c30160019
|
|
7
|
+
data.tar.gz: af4930204eeaaf926ea5a20f01d97a7525412925fdfc90b4535f72b64cea433a687c2c878e55feb186bf177f6750571676860b4461d618c0635e3f1617f637cf
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.5] - 2026-03-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- OutcomeListener sub-module: subscription actor wiring task completion events to cognitive model updates (#4)
|
|
7
|
+
- DomainExtractor helper: extracts learning domain from runner class names
|
|
8
|
+
- LessonBuilder helper: generates structured situation-lesson pairs for Apollo persistence
|
|
9
|
+
- MetaLearning, LearningRate, and Scaffolding models updated on each task outcome
|
|
10
|
+
- Per-agent scoped lessons written to Apollo knowledge store
|
|
11
|
+
- Settings toggles: outcome_listener, write_to_apollo, min_lesson_severity
|
|
12
|
+
|
|
13
|
+
## [0.1.4] - 2026-03-30
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- update to rubocop-legion 0.1.7, resolve all offenses
|
|
17
|
+
|
|
3
18
|
## [0.1.3] - 2026-03-26
|
|
4
19
|
|
|
5
20
|
### Changed
|
data/Gemfile
CHANGED
|
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
33
33
|
spec.add_dependency 'legion-transport', '>= 1.3.9'
|
|
34
34
|
|
|
35
35
|
spec.add_development_dependency 'rspec', '~> 3.13'
|
|
36
|
-
spec.add_development_dependency 'rubocop'
|
|
37
|
-
spec.add_development_dependency 'rubocop-
|
|
36
|
+
spec.add_development_dependency 'rubocop'
|
|
37
|
+
spec.add_development_dependency 'rubocop-legion'
|
|
38
|
+
spec.add_development_dependency 'rubocop-rspec'
|
|
38
39
|
end
|
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module Anchoring
|
|
8
8
|
module Runners
|
|
9
9
|
module Anchoring
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def record_anchor(value:, domain: :general, **)
|
|
14
14
|
anchor = anchor_store.add(value: value, domain: domain)
|
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module Catalyst
|
|
8
8
|
module Runners
|
|
9
9
|
module CognitiveCatalyst
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def create_catalyst(catalyst_type:, domain:, potency: nil, specificity: nil, engine: nil, **)
|
|
14
14
|
e = engine || default_engine
|
|
@@ -8,8 +8,8 @@ module Legion
|
|
|
8
8
|
module Runners
|
|
9
9
|
# Runner methods for the curiosity engine: gap detection, wonder lifecycle, agenda formation.
|
|
10
10
|
module Curiosity
|
|
11
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
12
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
11
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
12
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
13
13
|
|
|
14
14
|
def detect_gaps(prior_results: {}, **)
|
|
15
15
|
gaps = Helpers::GapDetector.detect(prior_results)
|
data/lib/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity.rb
CHANGED
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module EpistemicCuriosity
|
|
8
8
|
module Runners
|
|
9
9
|
module EpistemicCuriosity
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
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)
|
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module Habit
|
|
8
8
|
module Runners
|
|
9
9
|
module Habit
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def observe_action(action:, context: {}, **)
|
|
14
14
|
log.debug "[habit] observe_action: action=#{action} context=#{context}"
|
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module Hebbian
|
|
8
8
|
module Runners
|
|
9
9
|
module HebbianAssembly
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def activate_unit(id:, level: 1.0, domain: :general, **)
|
|
14
14
|
log.debug "[hebbian] activate: id=#{id} level=#{level}"
|
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module LearningRate
|
|
8
8
|
module Runners
|
|
9
9
|
module LearningRate
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def record_prediction(correct:, domain: :general, **)
|
|
14
14
|
rate_model.record_prediction(domain: domain, correct: correct)
|
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module MetaLearning
|
|
8
8
|
module Runners
|
|
9
9
|
module MetaLearning
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def create_learning_domain(name:, learning_rate: Helpers::Constants::DEFAULT_LEARNING_RATE,
|
|
14
14
|
related_domains: [], **)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
return unless defined?(Legion::Extensions::Actors::Subscription)
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Agentic
|
|
8
|
+
module Learning
|
|
9
|
+
module OutcomeListener
|
|
10
|
+
module Actor
|
|
11
|
+
class OutcomeListener < Legion::Extensions::Actors::Subscription
|
|
12
|
+
def runner_class
|
|
13
|
+
Legion::Extensions::Agentic::Learning::OutcomeListener::Runners::OutcomeListener
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def runner_function
|
|
17
|
+
'process_outcome'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def check_subtask?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def generate_task?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def use_runner?
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def enabled? # rubocop:disable Legion/Extension/ActorEnabledSideEffects
|
|
33
|
+
outcome_listener_setting? &&
|
|
34
|
+
defined?(Legion::Extensions::Agentic::Learning::OutcomeListener::Runners::OutcomeListener) &&
|
|
35
|
+
transport_connected?
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
log.warn "[outcome_listener] enabled? check failed: #{e.message}"
|
|
38
|
+
false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def outcome_listener_setting?
|
|
44
|
+
return true unless defined?(Legion::Settings)
|
|
45
|
+
|
|
46
|
+
Legion::Settings.dig(:agentic, :learning, :outcome_listener) != false
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
log.warn "[outcome_listener] settings check failed: #{e.message}"
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/learning/outcome_listener/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/learning/outcome_listener/helpers/domain_extractor'
|
|
5
|
+
require 'legion/extensions/agentic/learning/outcome_listener/helpers/lesson_builder'
|
|
6
|
+
require 'legion/extensions/agentic/learning/outcome_listener/runners/outcome_listener'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Agentic
|
|
11
|
+
module Learning
|
|
12
|
+
module OutcomeListener
|
|
13
|
+
class Client
|
|
14
|
+
include Runners::OutcomeListener
|
|
15
|
+
|
|
16
|
+
# rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
17
|
+
class << self
|
|
18
|
+
def meta_client
|
|
19
|
+
@meta_client ||= MetaLearning::Client.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def scaffolding_client
|
|
23
|
+
@scaffolding_client ||= Scaffolding::Client.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def learning_rate_client
|
|
27
|
+
@learning_rate_client ||= LearningRate::Client.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def domain_map
|
|
31
|
+
@domain_map ||= {}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reset!
|
|
35
|
+
@meta_client = nil
|
|
36
|
+
@scaffolding_client = nil
|
|
37
|
+
@learning_rate_client = nil
|
|
38
|
+
@domain_map = nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Learning
|
|
7
|
+
module OutcomeListener
|
|
8
|
+
module Helpers
|
|
9
|
+
module Constants
|
|
10
|
+
DEFAULT_MIN_LESSON_SEVERITY = 0.3
|
|
11
|
+
COMPLETED_STATUS = 'task.completed'
|
|
12
|
+
FAILED_STATUS = 'task.failed'
|
|
13
|
+
DEFAULT_DIFFICULTY = 0.5
|
|
14
|
+
DEFAULT_CONFIDENCE_SUCCESS = 0.7
|
|
15
|
+
DEFAULT_CONFIDENCE_FAILURE = 0.5
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Learning
|
|
7
|
+
module OutcomeListener
|
|
8
|
+
module Helpers
|
|
9
|
+
module DomainExtractor
|
|
10
|
+
RUNNER_PATTERN = /Legion::Extensions::(?:Agentic::)?(\w+)/
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def extract(runner_class_name)
|
|
15
|
+
return 'unknown' if runner_class_name.nil? || runner_class_name.empty?
|
|
16
|
+
|
|
17
|
+
match = runner_class_name.match(RUNNER_PATTERN)
|
|
18
|
+
return snake_case(match[1]) if match
|
|
19
|
+
|
|
20
|
+
segments = runner_class_name.split('::')
|
|
21
|
+
return snake_case(segments[-2]) if segments.size >= 2
|
|
22
|
+
|
|
23
|
+
snake_case(segments.last)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def snake_case(str)
|
|
27
|
+
str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
28
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
29
|
+
.downcase
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Learning
|
|
7
|
+
module OutcomeListener
|
|
8
|
+
module Helpers
|
|
9
|
+
module LessonBuilder
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def build(runner_class:, domain:, success:, status: nil, function: nil, source_agent: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
15
|
+
{
|
|
16
|
+
situation: build_situation(runner_class, function),
|
|
17
|
+
outcome: success ? :success : :failure,
|
|
18
|
+
lesson: build_lesson(domain, success),
|
|
19
|
+
domain: domain,
|
|
20
|
+
confidence: success ? DEFAULT_CONFIDENCE_SUCCESS : DEFAULT_CONFIDENCE_FAILURE,
|
|
21
|
+
source_agent: source_agent,
|
|
22
|
+
recorded_at: Time.now.utc
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def build_situation(runner_class, function)
|
|
27
|
+
parts = [runner_class.to_s]
|
|
28
|
+
parts << "##{function}" if function
|
|
29
|
+
parts.join
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def build_lesson(domain, success)
|
|
33
|
+
if success
|
|
34
|
+
"Task in domain '#{domain}' completed successfully"
|
|
35
|
+
else
|
|
36
|
+
"Task in domain '#{domain}' failed — review for corrective action"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Learning
|
|
7
|
+
module OutcomeListener
|
|
8
|
+
module Runners
|
|
9
|
+
module OutcomeListener
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
|
+
|
|
13
|
+
def process_outcome(payload = {}, **)
|
|
14
|
+
runner_class_name = payload[:runner_class].to_s
|
|
15
|
+
status = payload[:status].to_s
|
|
16
|
+
domain = Helpers::DomainExtractor.extract(runner_class_name)
|
|
17
|
+
success = status == Helpers::Constants::COMPLETED_STATUS
|
|
18
|
+
source_agent = payload[:source_agent] || payload[:agent_id]
|
|
19
|
+
|
|
20
|
+
updates = {}
|
|
21
|
+
updates[:meta_learning] = update_meta_learning_model(domain, success)
|
|
22
|
+
updates[:learning_rate] = update_learning_rate_model(domain, success)
|
|
23
|
+
updates[:scaffolding] = update_scaffolding_model(domain, success, payload)
|
|
24
|
+
|
|
25
|
+
if write_to_apollo?
|
|
26
|
+
lesson = Helpers::LessonBuilder.build(
|
|
27
|
+
runner_class: runner_class_name,
|
|
28
|
+
function: payload[:function],
|
|
29
|
+
status: status,
|
|
30
|
+
domain: domain,
|
|
31
|
+
success: success,
|
|
32
|
+
source_agent: source_agent
|
|
33
|
+
)
|
|
34
|
+
write_apollo_lesson(lesson, source_agent) if lesson[:confidence] >= min_lesson_severity
|
|
35
|
+
updates[:lesson] = lesson
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
log.debug "[outcome_listener] domain=#{domain} success=#{success} updates=#{updates.keys}"
|
|
39
|
+
{ success: true, domain: domain, outcome: success, updates: updates }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def update_meta_learning_model(domain, success)
|
|
45
|
+
meta = self.class.meta_client
|
|
46
|
+
domain_id = resolve_domain_id(meta, domain)
|
|
47
|
+
meta.record_learning_episode(domain_id: domain_id, success: success)
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
log.warn "[outcome_listener] meta_learning update failed: #{e.message}"
|
|
50
|
+
{ error: e.message }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def update_learning_rate_model(domain, success)
|
|
54
|
+
lr = self.class.learning_rate_client
|
|
55
|
+
lr.record_prediction(correct: success, domain: domain.to_sym)
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
log.warn "[outcome_listener] learning_rate update failed: #{e.message}"
|
|
58
|
+
{ error: e.message }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def update_scaffolding_model(domain, success, payload)
|
|
62
|
+
sc = self.class.scaffolding_client
|
|
63
|
+
scaffolds = sc.engine.by_domain(domain: domain)
|
|
64
|
+
return { skipped: true, reason: :no_scaffold } if scaffolds.empty?
|
|
65
|
+
|
|
66
|
+
scaffold = scaffolds.first
|
|
67
|
+
difficulty = payload[:complexity]&.to_f || Helpers::Constants::DEFAULT_DIFFICULTY
|
|
68
|
+
sc.attempt_scaffolded_task(scaffold_id: scaffold.id, difficulty: difficulty, success: success)
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
log.warn "[outcome_listener] scaffolding update failed: #{e.message}"
|
|
71
|
+
{ error: e.message }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def resolve_domain_id(meta, domain)
|
|
75
|
+
map = self.class.domain_map
|
|
76
|
+
return map[domain] if map.key?(domain)
|
|
77
|
+
|
|
78
|
+
result = meta.create_learning_domain(name: domain)
|
|
79
|
+
map[domain] = result[:id]
|
|
80
|
+
result[:id]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def write_apollo_lesson(lesson, source_agent)
|
|
84
|
+
return unless defined?(Legion::Apollo)
|
|
85
|
+
|
|
86
|
+
ingest_knowledge(content: json_generate(lesson),
|
|
87
|
+
content_type: 'task_outcome_lesson',
|
|
88
|
+
tags: ['task_outcome', lesson[:domain]],
|
|
89
|
+
source_agent: source_agent,
|
|
90
|
+
knowledge_domain: 'learning')
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
log.warn "[outcome_listener] apollo write failed: #{e.message}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def write_to_apollo?
|
|
96
|
+
return false unless defined?(Legion::Settings)
|
|
97
|
+
|
|
98
|
+
Legion::Settings.dig(:agentic, :learning, :write_to_apollo) != false
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
log.warn "[outcome_listener] write_to_apollo? check failed: #{e.message}"
|
|
101
|
+
true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def min_lesson_severity
|
|
105
|
+
return Helpers::Constants::DEFAULT_MIN_LESSON_SEVERITY unless defined?(Legion::Settings)
|
|
106
|
+
|
|
107
|
+
Legion::Settings.dig(:agentic, :learning, :min_lesson_severity) ||
|
|
108
|
+
Helpers::Constants::DEFAULT_MIN_LESSON_SEVERITY
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
log.warn "[outcome_listener] min_lesson_severity check failed: #{e.message}"
|
|
111
|
+
Helpers::Constants::DEFAULT_MIN_LESSON_SEVERITY
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
data/lib/legion/extensions/agentic/learning/preference_learning/runners/preference_learning.rb
CHANGED
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module PreferenceLearning
|
|
8
8
|
module Runners
|
|
9
9
|
module PreferenceLearning
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def register_preference_option(label:, domain: :general, **)
|
|
14
14
|
result = preference_engine.register_option(label: label, domain: domain)
|
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module Procedural
|
|
8
8
|
module Runners
|
|
9
9
|
module ProceduralLearning
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def create_skill(name:, domain:, **)
|
|
14
14
|
skill = engine.create_skill(name: name, domain: domain)
|
|
@@ -7,8 +7,8 @@ module Legion
|
|
|
7
7
|
module Scaffolding
|
|
8
8
|
module Runners
|
|
9
9
|
module CognitiveScaffolding
|
|
10
|
-
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
-
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
12
|
|
|
13
13
|
def create_scaffold(skill_name:, domain:, competence: nil, **)
|
|
14
14
|
comp = competence || Helpers::Constants::DEFAULT_COMPETENCE
|
|
@@ -15,12 +15,13 @@ require_relative 'learning/meta_learning'
|
|
|
15
15
|
require_relative 'learning/preference_learning'
|
|
16
16
|
require_relative 'learning/procedural'
|
|
17
17
|
require_relative 'learning/anchoring'
|
|
18
|
+
require_relative 'learning/outcome_listener'
|
|
18
19
|
|
|
19
20
|
module Legion
|
|
20
21
|
module Extensions
|
|
21
22
|
module Agentic
|
|
22
23
|
module Learning
|
|
23
|
-
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
24
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
|
|
24
25
|
|
|
25
26
|
def self.remote_invocable?
|
|
26
27
|
false
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/learning/outcome_listener/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Agentic::Learning::OutcomeListener::Client do
|
|
6
|
+
before { described_class.reset! }
|
|
7
|
+
|
|
8
|
+
it 'responds to process_outcome' do
|
|
9
|
+
client = described_class.new
|
|
10
|
+
expect(client).to respond_to(:process_outcome)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe '.meta_client' do
|
|
14
|
+
it 'returns a MetaLearning::Client' do
|
|
15
|
+
expect(described_class.meta_client).to be_a(
|
|
16
|
+
Legion::Extensions::Agentic::Learning::MetaLearning::Client
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'returns the same instance on repeated calls' do
|
|
21
|
+
expect(described_class.meta_client).to equal(described_class.meta_client)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '.scaffolding_client' do
|
|
26
|
+
it 'returns a Scaffolding::Client' do
|
|
27
|
+
expect(described_class.scaffolding_client).to be_a(
|
|
28
|
+
Legion::Extensions::Agentic::Learning::Scaffolding::Client
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'returns the same instance on repeated calls' do
|
|
33
|
+
expect(described_class.scaffolding_client).to equal(described_class.scaffolding_client)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '.learning_rate_client' do
|
|
38
|
+
it 'returns a LearningRate::Client' do
|
|
39
|
+
expect(described_class.learning_rate_client).to be_a(
|
|
40
|
+
Legion::Extensions::Agentic::Learning::LearningRate::Client
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'returns the same instance on repeated calls' do
|
|
45
|
+
expect(described_class.learning_rate_client).to equal(described_class.learning_rate_client)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe '.domain_map' do
|
|
50
|
+
it 'returns an empty hash initially' do
|
|
51
|
+
expect(described_class.domain_map).to eq({})
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'persists entries across calls' do
|
|
55
|
+
described_class.domain_map['test'] = 'id-123'
|
|
56
|
+
expect(described_class.domain_map['test']).to eq('id-123')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe '.reset!' do
|
|
61
|
+
it 'clears all shared state' do
|
|
62
|
+
described_class.meta_client
|
|
63
|
+
described_class.scaffolding_client
|
|
64
|
+
described_class.learning_rate_client
|
|
65
|
+
described_class.domain_map['test'] = 'id'
|
|
66
|
+
|
|
67
|
+
described_class.reset!
|
|
68
|
+
|
|
69
|
+
expect(described_class.domain_map).to eq({})
|
|
70
|
+
expect(described_class.instance_variable_get(:@meta_client)).to be_nil
|
|
71
|
+
expect(described_class.instance_variable_get(:@scaffolding_client)).to be_nil
|
|
72
|
+
expect(described_class.instance_variable_get(:@learning_rate_client)).to be_nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/spec/legion/extensions/agentic/learning/outcome_listener/helpers/domain_extractor_spec.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/learning/outcome_listener/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/learning/outcome_listener/helpers/domain_extractor'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Legion::Extensions::Agentic::Learning::OutcomeListener::Helpers::DomainExtractor do
|
|
7
|
+
describe '.extract' do
|
|
8
|
+
it 'extracts domain from standard runner class' do
|
|
9
|
+
expect(described_class.extract('Legion::Extensions::Http::Runners::Get')).to eq('http')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'extracts domain from agentic runner class' do
|
|
13
|
+
expect(described_class.extract('Legion::Extensions::Agentic::Learning::MetaLearning::Runners::MetaLearning'))
|
|
14
|
+
.to eq('learning')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'handles consul extension' do
|
|
18
|
+
expect(described_class.extract('Legion::Extensions::Consul::Runners::Kv')).to eq('consul')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'handles camel case domains' do
|
|
22
|
+
expect(described_class.extract('Legion::Extensions::SwarmGithub::Runners::Issue')).to eq('swarm_github')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'returns unknown for nil input' do
|
|
26
|
+
expect(described_class.extract(nil)).to eq('unknown')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'returns unknown for empty string' do
|
|
30
|
+
expect(described_class.extract('')).to eq('unknown')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'falls back to second-to-last segment for non-standard format' do
|
|
34
|
+
expect(described_class.extract('Custom::Runner::DoWork')).to eq('runner')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'handles single segment' do
|
|
38
|
+
expect(described_class.extract('Worker')).to eq('worker')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe '.snake_case' do
|
|
43
|
+
it 'converts CamelCase to snake_case' do
|
|
44
|
+
expect(described_class.snake_case('SwarmGithub')).to eq('swarm_github')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'converts simple word' do
|
|
48
|
+
expect(described_class.snake_case('Http')).to eq('http')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'handles consecutive capitals' do
|
|
52
|
+
expect(described_class.snake_case('LLMGateway')).to eq('llm_gateway')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/learning/outcome_listener/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/learning/outcome_listener/helpers/lesson_builder'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Legion::Extensions::Agentic::Learning::OutcomeListener::Helpers::LessonBuilder do
|
|
7
|
+
describe '.build' do
|
|
8
|
+
let(:success_lesson) do
|
|
9
|
+
described_class.build(
|
|
10
|
+
runner_class: 'Legion::Extensions::Http::Runners::Get',
|
|
11
|
+
function: 'fetch',
|
|
12
|
+
status: 'task.completed',
|
|
13
|
+
domain: 'http',
|
|
14
|
+
success: true,
|
|
15
|
+
source_agent: 'agent-1'
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:failure_lesson) do
|
|
20
|
+
described_class.build(
|
|
21
|
+
runner_class: 'Legion::Extensions::Consul::Runners::Kv',
|
|
22
|
+
function: 'put',
|
|
23
|
+
status: 'task.failed',
|
|
24
|
+
domain: 'consul',
|
|
25
|
+
success: false,
|
|
26
|
+
source_agent: 'agent-2'
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'builds a success lesson with correct outcome' do
|
|
31
|
+
expect(success_lesson[:outcome]).to eq(:success)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'builds a failure lesson with correct outcome' do
|
|
35
|
+
expect(failure_lesson[:outcome]).to eq(:failure)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'includes situation with runner class and function' do
|
|
39
|
+
expect(success_lesson[:situation]).to eq('Legion::Extensions::Http::Runners::Get#fetch')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'includes domain' do
|
|
43
|
+
expect(success_lesson[:domain]).to eq('http')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'assigns higher confidence to successes' do
|
|
47
|
+
expect(success_lesson[:confidence]).to be > failure_lesson[:confidence]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'includes source_agent' do
|
|
51
|
+
expect(success_lesson[:source_agent]).to eq('agent-1')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'includes recorded_at timestamp' do
|
|
55
|
+
expect(success_lesson[:recorded_at]).to be_a(Time)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'builds lesson text for success' do
|
|
59
|
+
expect(success_lesson[:lesson]).to include('completed successfully')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'builds lesson text for failure' do
|
|
63
|
+
expect(failure_lesson[:lesson]).to include('failed')
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe '.build_situation' do
|
|
68
|
+
it 'combines runner_class and function' do
|
|
69
|
+
result = described_class.build_situation('MyRunner', 'do_work')
|
|
70
|
+
expect(result).to eq('MyRunner#do_work')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'omits function when nil' do
|
|
74
|
+
result = described_class.build_situation('MyRunner', nil)
|
|
75
|
+
expect(result).to eq('MyRunner')
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/spec/legion/extensions/agentic/learning/outcome_listener/runners/outcome_listener_spec.rb
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/learning/outcome_listener/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Agentic::Learning::OutcomeListener::Runners::OutcomeListener do
|
|
6
|
+
let(:client) { Legion::Extensions::Agentic::Learning::OutcomeListener::Client.new }
|
|
7
|
+
|
|
8
|
+
before { Legion::Extensions::Agentic::Learning::OutcomeListener::Client.reset! }
|
|
9
|
+
|
|
10
|
+
let(:completed_payload) do
|
|
11
|
+
{
|
|
12
|
+
runner_class: 'Legion::Extensions::Http::Runners::Get',
|
|
13
|
+
function: 'fetch',
|
|
14
|
+
status: 'task.completed',
|
|
15
|
+
source_agent: 'agent-1'
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:failed_payload) do
|
|
20
|
+
{
|
|
21
|
+
runner_class: 'Legion::Extensions::Consul::Runners::Kv',
|
|
22
|
+
function: 'put',
|
|
23
|
+
status: 'task.failed',
|
|
24
|
+
source_agent: 'agent-2'
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '#process_outcome' do
|
|
29
|
+
it 'returns success hash for completed task' do
|
|
30
|
+
result = client.process_outcome(completed_payload)
|
|
31
|
+
expect(result[:success]).to be true
|
|
32
|
+
expect(result[:domain]).to eq('http')
|
|
33
|
+
expect(result[:outcome]).to be true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'returns success hash for failed task' do
|
|
37
|
+
result = client.process_outcome(failed_payload)
|
|
38
|
+
expect(result[:success]).to be true
|
|
39
|
+
expect(result[:domain]).to eq('consul')
|
|
40
|
+
expect(result[:outcome]).to be false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'updates meta_learning model on completion' do
|
|
44
|
+
result = client.process_outcome(completed_payload)
|
|
45
|
+
episode = result[:updates][:meta_learning]
|
|
46
|
+
expect(episode[:success]).to be true
|
|
47
|
+
expect(episode[:domain_name]).to eq('http')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'updates meta_learning model on failure' do
|
|
51
|
+
result = client.process_outcome(failed_payload)
|
|
52
|
+
episode = result[:updates][:meta_learning]
|
|
53
|
+
expect(episode[:success]).to be false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'updates learning_rate model' do
|
|
57
|
+
result = client.process_outcome(completed_payload)
|
|
58
|
+
lr = result[:updates][:learning_rate]
|
|
59
|
+
expect(lr[:success]).to be true
|
|
60
|
+
expect(lr[:domain]).to eq(:http)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'skips scaffolding when no scaffold exists for domain' do
|
|
64
|
+
result = client.process_outcome(completed_payload)
|
|
65
|
+
scaffolding = result[:updates][:scaffolding]
|
|
66
|
+
expect(scaffolding[:skipped]).to be true
|
|
67
|
+
expect(scaffolding[:reason]).to eq(:no_scaffold)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'updates scaffolding when scaffold exists for domain' do
|
|
71
|
+
sc = Legion::Extensions::Agentic::Learning::OutcomeListener::Client.scaffolding_client
|
|
72
|
+
sc.create_scaffold(skill_name: 'http_requests', domain: 'http')
|
|
73
|
+
result = client.process_outcome(completed_payload)
|
|
74
|
+
scaffolding = result[:updates][:scaffolding]
|
|
75
|
+
expect(scaffolding[:success]).to be true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'uses payload complexity for scaffolding difficulty' do
|
|
79
|
+
sc = Legion::Extensions::Agentic::Learning::OutcomeListener::Client.scaffolding_client
|
|
80
|
+
scaffold_result = sc.create_scaffold(skill_name: 'consul_kv', domain: 'consul')
|
|
81
|
+
original_competence = scaffold_result[:scaffold][:competence]
|
|
82
|
+
|
|
83
|
+
client.process_outcome(failed_payload.merge(complexity: 0.9))
|
|
84
|
+
scaffolding_client = Legion::Extensions::Agentic::Learning::OutcomeListener::Client.scaffolding_client
|
|
85
|
+
scaffold = scaffolding_client.engine.by_domain(domain: 'consul').first
|
|
86
|
+
expect(scaffold.competence).to be < original_competence
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'creates domain in meta_learning on first encounter' do
|
|
90
|
+
client.process_outcome(completed_payload)
|
|
91
|
+
meta = Legion::Extensions::Agentic::Learning::OutcomeListener::Client.meta_client
|
|
92
|
+
stats = meta.meta_learning_stats
|
|
93
|
+
expect(stats[:domain_count]).to eq(1)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'reuses existing domain on subsequent outcomes' do
|
|
97
|
+
client.process_outcome(completed_payload)
|
|
98
|
+
client.process_outcome(completed_payload)
|
|
99
|
+
meta = Legion::Extensions::Agentic::Learning::OutcomeListener::Client.meta_client
|
|
100
|
+
stats = meta.meta_learning_stats
|
|
101
|
+
expect(stats[:domain_count]).to eq(1)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'accumulates proficiency across multiple successes' do
|
|
105
|
+
3.times { client.process_outcome(completed_payload) }
|
|
106
|
+
meta = Legion::Extensions::Agentic::Learning::OutcomeListener::Client.meta_client
|
|
107
|
+
domain_id = Legion::Extensions::Agentic::Learning::OutcomeListener::Client.domain_map['http']
|
|
108
|
+
episodes = meta.send(:engine).domains[domain_id]
|
|
109
|
+
expect(episodes.proficiency).to be > 0.0
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'handles empty payload gracefully' do
|
|
113
|
+
result = client.process_outcome({})
|
|
114
|
+
expect(result[:success]).to be true
|
|
115
|
+
expect(result[:domain]).to eq('unknown')
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-agentic-learning
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -125,30 +125,44 @@ dependencies:
|
|
|
125
125
|
name: rubocop
|
|
126
126
|
requirement: !ruby/object:Gem::Requirement
|
|
127
127
|
requirements:
|
|
128
|
-
- - "
|
|
128
|
+
- - ">="
|
|
129
129
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '
|
|
130
|
+
version: '0'
|
|
131
131
|
type: :development
|
|
132
132
|
prerelease: false
|
|
133
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
134
134
|
requirements:
|
|
135
|
-
- - "
|
|
135
|
+
- - ">="
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
|
-
version: '
|
|
137
|
+
version: '0'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: rubocop-legion
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - ">="
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - ">="
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0'
|
|
138
152
|
- !ruby/object:Gem::Dependency
|
|
139
153
|
name: rubocop-rspec
|
|
140
154
|
requirement: !ruby/object:Gem::Requirement
|
|
141
155
|
requirements:
|
|
142
|
-
- - "
|
|
156
|
+
- - ">="
|
|
143
157
|
- !ruby/object:Gem::Version
|
|
144
|
-
version: '
|
|
158
|
+
version: '0'
|
|
145
159
|
type: :development
|
|
146
160
|
prerelease: false
|
|
147
161
|
version_requirements: !ruby/object:Gem::Requirement
|
|
148
162
|
requirements:
|
|
149
|
-
- - "
|
|
163
|
+
- - ">="
|
|
150
164
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: '
|
|
165
|
+
version: '0'
|
|
152
166
|
description: 'LEX agentic learning domain: adaptation, memory consolidation, skill
|
|
153
167
|
acquisition'
|
|
154
168
|
email:
|
|
@@ -239,6 +253,13 @@ files:
|
|
|
239
253
|
- lib/legion/extensions/agentic/learning/meta_learning/helpers/strategy.rb
|
|
240
254
|
- lib/legion/extensions/agentic/learning/meta_learning/runners/meta_learning.rb
|
|
241
255
|
- lib/legion/extensions/agentic/learning/meta_learning/version.rb
|
|
256
|
+
- lib/legion/extensions/agentic/learning/outcome_listener.rb
|
|
257
|
+
- lib/legion/extensions/agentic/learning/outcome_listener/actors/outcome_listener.rb
|
|
258
|
+
- lib/legion/extensions/agentic/learning/outcome_listener/client.rb
|
|
259
|
+
- lib/legion/extensions/agentic/learning/outcome_listener/helpers/constants.rb
|
|
260
|
+
- lib/legion/extensions/agentic/learning/outcome_listener/helpers/domain_extractor.rb
|
|
261
|
+
- lib/legion/extensions/agentic/learning/outcome_listener/helpers/lesson_builder.rb
|
|
262
|
+
- lib/legion/extensions/agentic/learning/outcome_listener/runners/outcome_listener.rb
|
|
242
263
|
- lib/legion/extensions/agentic/learning/plasticity.rb
|
|
243
264
|
- lib/legion/extensions/agentic/learning/plasticity/client.rb
|
|
244
265
|
- lib/legion/extensions/agentic/learning/plasticity/helpers/constants.rb
|
|
@@ -327,6 +348,10 @@ files:
|
|
|
327
348
|
- spec/legion/extensions/agentic/learning/meta_learning/helpers/meta_learning_engine_spec.rb
|
|
328
349
|
- spec/legion/extensions/agentic/learning/meta_learning/helpers/strategy_spec.rb
|
|
329
350
|
- spec/legion/extensions/agentic/learning/meta_learning/runners/meta_learning_spec.rb
|
|
351
|
+
- spec/legion/extensions/agentic/learning/outcome_listener/client_spec.rb
|
|
352
|
+
- spec/legion/extensions/agentic/learning/outcome_listener/helpers/domain_extractor_spec.rb
|
|
353
|
+
- spec/legion/extensions/agentic/learning/outcome_listener/helpers/lesson_builder_spec.rb
|
|
354
|
+
- spec/legion/extensions/agentic/learning/outcome_listener/runners/outcome_listener_spec.rb
|
|
330
355
|
- spec/legion/extensions/agentic/learning/plasticity/helpers/constants_spec.rb
|
|
331
356
|
- spec/legion/extensions/agentic/learning/plasticity/helpers/neural_pathway_spec.rb
|
|
332
357
|
- spec/legion/extensions/agentic/learning/plasticity/helpers/plasticity_engine_spec.rb
|