lex-procedural-learning 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +13 -0
- data/lex-procedural-learning.gemspec +30 -0
- data/lib/legion/extensions/procedural_learning/client.rb +15 -0
- data/lib/legion/extensions/procedural_learning/helpers/constants.rb +42 -0
- data/lib/legion/extensions/procedural_learning/helpers/learning_engine.rb +156 -0
- data/lib/legion/extensions/procedural_learning/helpers/production.rb +62 -0
- data/lib/legion/extensions/procedural_learning/helpers/skill.rb +97 -0
- data/lib/legion/extensions/procedural_learning/runners/procedural_learning.rb +92 -0
- data/lib/legion/extensions/procedural_learning/version.rb +9 -0
- data/lib/legion/extensions/procedural_learning.rb +17 -0
- data/spec/legion/extensions/procedural_learning/client_spec.rb +22 -0
- data/spec/legion/extensions/procedural_learning/helpers/learning_engine_spec.rb +135 -0
- data/spec/legion/extensions/procedural_learning/helpers/production_spec.rb +66 -0
- data/spec/legion/extensions/procedural_learning/helpers/skill_spec.rb +102 -0
- data/spec/legion/extensions/procedural_learning/runners/procedural_learning_spec.rb +94 -0
- data/spec/spec_helper.rb +34 -0
- metadata +77 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: beadefed7bdfbf63394e91a805c43d2fde712ba5f95ec1781f48999e1fb2a0f2
|
|
4
|
+
data.tar.gz: a5627d0dce87ba20527e1251d2564de55008d2f12b7a378da49f338bcf86ccf3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: de643f276210c3abaedf90f75df2a1b12fff7aaf9a49dbcf89e6a8456ff6c31fd87b4aba8321b8df2e6e4dcb3555e121074eecf367bbbed1c26f5672872e2333
|
|
7
|
+
data.tar.gz: 062b9255719b45a29a57ba1c0b78a49aa3714f882a89e9908b0a70b32ad8a465c658a22dc71c9fcae7a920a68ce076c72cf88278aa0f8c32eb879055037e7d90
|
data/Gemfile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/procedural_learning/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-procedural-learning'
|
|
7
|
+
spec.version = Legion::Extensions::ProceduralLearning::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Procedural Learning'
|
|
12
|
+
spec.description = "Anderson's ACT-R production rules — skill acquisition from declarative " \
|
|
13
|
+
'to autonomous for brain-modeled agentic AI'
|
|
14
|
+
spec.homepage = 'https://github.com/LegionIO/lex-procedural-learning'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
spec.required_ruby_version = '>= 3.4'
|
|
17
|
+
|
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-procedural-learning'
|
|
20
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-procedural-learning'
|
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-procedural-learning'
|
|
22
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-procedural-learning/issues'
|
|
23
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
24
|
+
|
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
26
|
+
Dir.glob('{lib,spec}/**/*') + %w[lex-procedural-learning.gemspec Gemfile]
|
|
27
|
+
end
|
|
28
|
+
spec.require_paths = ['lib']
|
|
29
|
+
spec.add_development_dependency 'legion-gaia'
|
|
30
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module ProceduralLearning
|
|
6
|
+
class Client
|
|
7
|
+
include Runners::ProceduralLearning
|
|
8
|
+
|
|
9
|
+
def initialize(engine: nil)
|
|
10
|
+
@engine = engine || Helpers::LearningEngine.new
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module ProceduralLearning
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_SKILLS = 200
|
|
9
|
+
MAX_PRODUCTIONS = 500
|
|
10
|
+
MAX_HISTORY = 300
|
|
11
|
+
|
|
12
|
+
DEFAULT_PROFICIENCY = 0.1
|
|
13
|
+
PROFICIENCY_FLOOR = 0.0
|
|
14
|
+
PROFICIENCY_CEILING = 1.0
|
|
15
|
+
|
|
16
|
+
PRACTICE_GAIN = 0.08
|
|
17
|
+
COMPILATION_THRESHOLD = 0.6
|
|
18
|
+
AUTOMATION_THRESHOLD = 0.85
|
|
19
|
+
DECAY_RATE = 0.01
|
|
20
|
+
STALE_THRESHOLD = 300
|
|
21
|
+
|
|
22
|
+
SKILL_STAGES = %i[declarative associative autonomous].freeze
|
|
23
|
+
|
|
24
|
+
STAGE_LABELS = {
|
|
25
|
+
(0.0...0.3) => :declarative,
|
|
26
|
+
(0.3...0.6) => :associative,
|
|
27
|
+
(0.6...0.85) => :compiled,
|
|
28
|
+
(0.85..1.0) => :autonomous
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
PROFICIENCY_LABELS = {
|
|
32
|
+
(0.8..) => :expert,
|
|
33
|
+
(0.6...0.8) => :proficient,
|
|
34
|
+
(0.4...0.6) => :intermediate,
|
|
35
|
+
(0.2...0.4) => :beginner,
|
|
36
|
+
(..0.2) => :novice
|
|
37
|
+
}.freeze
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module ProceduralLearning
|
|
6
|
+
module Helpers
|
|
7
|
+
class LearningEngine
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :history
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@skills = {}
|
|
14
|
+
@productions = {}
|
|
15
|
+
@history = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_skill(name:, domain:)
|
|
19
|
+
evict_oldest_skill if @skills.size >= MAX_SKILLS
|
|
20
|
+
|
|
21
|
+
skill = Skill.new(name: name, domain: domain)
|
|
22
|
+
@skills[skill.id] = skill
|
|
23
|
+
record_history(:skill_created, skill.id)
|
|
24
|
+
skill
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_production(skill_id:, condition:, action:, domain:)
|
|
28
|
+
skill = @skills[skill_id]
|
|
29
|
+
return { success: false, reason: :skill_not_found } unless skill
|
|
30
|
+
return { success: false, reason: :max_productions } if @productions.size >= MAX_PRODUCTIONS
|
|
31
|
+
|
|
32
|
+
production = Production.new(
|
|
33
|
+
condition: condition, action: action,
|
|
34
|
+
domain: domain, skill_id: skill_id
|
|
35
|
+
)
|
|
36
|
+
@productions[production.id] = production
|
|
37
|
+
skill.add_production(production.id)
|
|
38
|
+
record_history(:production_added, production.id)
|
|
39
|
+
production
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def practice_skill(skill_id:, success:)
|
|
43
|
+
skill = @skills[skill_id]
|
|
44
|
+
return { success: false, reason: :not_found } unless skill
|
|
45
|
+
|
|
46
|
+
skill.practice!(success: success)
|
|
47
|
+
record_history(:practiced, skill_id)
|
|
48
|
+
build_practice_result(skill)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def execute_production(production_id:, success:)
|
|
52
|
+
production = @productions[production_id]
|
|
53
|
+
return { success: false, reason: :not_found } unless production
|
|
54
|
+
|
|
55
|
+
production.execute!(success: success)
|
|
56
|
+
record_history(:production_executed, production_id)
|
|
57
|
+
{ success: true, production_id: production_id, success_rate: production.success_rate }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def skill_assessment(skill_id:)
|
|
61
|
+
skill = @skills[skill_id]
|
|
62
|
+
return { success: false, reason: :not_found } unless skill
|
|
63
|
+
|
|
64
|
+
productions = skill.productions.filter_map { |pid| @productions[pid] }
|
|
65
|
+
build_assessment(skill, productions)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def compiled_skills
|
|
69
|
+
@skills.values.select(&:compiled?)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def autonomous_skills
|
|
73
|
+
@skills.values.select(&:autonomous?)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def by_domain(domain:)
|
|
77
|
+
@skills.values.select { |s| s.domain == domain }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def most_practiced(limit: 5)
|
|
81
|
+
@skills.values.sort_by { |s| -s.practice_count }.first(limit)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def decay_all
|
|
85
|
+
@skills.each_value(&:decay!)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def prune_stale
|
|
89
|
+
stale_ids = @skills.select { |_id, s| s.proficiency <= 0.02 }.keys
|
|
90
|
+
stale_ids.each do |sid|
|
|
91
|
+
remove_skill_productions(sid)
|
|
92
|
+
@skills.delete(sid)
|
|
93
|
+
end
|
|
94
|
+
stale_ids.size
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def to_h
|
|
98
|
+
{
|
|
99
|
+
total_skills: @skills.size,
|
|
100
|
+
total_productions: @productions.size,
|
|
101
|
+
compiled_count: compiled_skills.size,
|
|
102
|
+
autonomous_count: autonomous_skills.size,
|
|
103
|
+
history_count: @history.size,
|
|
104
|
+
stage_counts: stage_counts
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def build_practice_result(skill)
|
|
111
|
+
{
|
|
112
|
+
success: true,
|
|
113
|
+
skill_id: skill.id,
|
|
114
|
+
proficiency: skill.proficiency,
|
|
115
|
+
stage: skill.stage,
|
|
116
|
+
stage_label: skill.stage_label
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def build_assessment(skill, productions)
|
|
121
|
+
{
|
|
122
|
+
success: true,
|
|
123
|
+
skill: skill.to_h,
|
|
124
|
+
productions: productions.map(&:to_h),
|
|
125
|
+
reliable_count: productions.count(&:reliable?),
|
|
126
|
+
total_executions: productions.sum(&:execution_count)
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def remove_skill_productions(skill_id)
|
|
131
|
+
@productions.delete_if { |_id, prod| prod.skill_id == skill_id }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def evict_oldest_skill
|
|
135
|
+
oldest_id = @skills.min_by { |_id, s| s.last_practiced_at }&.first
|
|
136
|
+
return unless oldest_id
|
|
137
|
+
|
|
138
|
+
remove_skill_productions(oldest_id)
|
|
139
|
+
@skills.delete(oldest_id)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def record_history(event, subject_id)
|
|
143
|
+
@history << { event: event, subject_id: subject_id, at: Time.now.utc }
|
|
144
|
+
@history.shift while @history.size > MAX_HISTORY
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def stage_counts
|
|
148
|
+
@skills.values.each_with_object(Hash.new(0)) do |skill, counts|
|
|
149
|
+
counts[skill.stage] += 1
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module ProceduralLearning
|
|
8
|
+
module Helpers
|
|
9
|
+
class Production
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :condition, :action, :domain, :skill_id,
|
|
13
|
+
:execution_count, :success_count, :created_at, :last_executed_at
|
|
14
|
+
|
|
15
|
+
def initialize(condition:, action:, domain:, skill_id:)
|
|
16
|
+
@id = SecureRandom.uuid
|
|
17
|
+
@condition = condition
|
|
18
|
+
@action = action
|
|
19
|
+
@domain = domain
|
|
20
|
+
@skill_id = skill_id
|
|
21
|
+
@execution_count = 0
|
|
22
|
+
@success_count = 0
|
|
23
|
+
@created_at = Time.now.utc
|
|
24
|
+
@last_executed_at = @created_at
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def execute!(success:)
|
|
28
|
+
@execution_count += 1
|
|
29
|
+
@success_count += 1 if success
|
|
30
|
+
@last_executed_at = Time.now.utc
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def success_rate
|
|
34
|
+
return 0.0 if @execution_count.zero?
|
|
35
|
+
|
|
36
|
+
@success_count.to_f / @execution_count
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reliable?
|
|
40
|
+
success_rate >= 0.7 && @execution_count >= 3
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_h
|
|
44
|
+
{
|
|
45
|
+
id: @id,
|
|
46
|
+
condition: @condition,
|
|
47
|
+
action: @action,
|
|
48
|
+
domain: @domain,
|
|
49
|
+
skill_id: @skill_id,
|
|
50
|
+
execution_count: @execution_count,
|
|
51
|
+
success_count: @success_count,
|
|
52
|
+
success_rate: success_rate,
|
|
53
|
+
reliable: reliable?,
|
|
54
|
+
created_at: @created_at,
|
|
55
|
+
last_executed_at: @last_executed_at
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module ProceduralLearning
|
|
8
|
+
module Helpers
|
|
9
|
+
class Skill
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :name, :domain, :proficiency, :practice_count,
|
|
13
|
+
:stage, :productions, :created_at, :last_practiced_at
|
|
14
|
+
|
|
15
|
+
def initialize(name:, domain:)
|
|
16
|
+
@id = SecureRandom.uuid
|
|
17
|
+
@name = name
|
|
18
|
+
@domain = domain
|
|
19
|
+
@proficiency = DEFAULT_PROFICIENCY
|
|
20
|
+
@practice_count = 0
|
|
21
|
+
@stage = :declarative
|
|
22
|
+
@productions = []
|
|
23
|
+
@created_at = Time.now.utc
|
|
24
|
+
@last_practiced_at = @created_at
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def practice!(success:)
|
|
28
|
+
@practice_count += 1
|
|
29
|
+
@last_practiced_at = Time.now.utc
|
|
30
|
+
gain = success ? PRACTICE_GAIN : PRACTICE_GAIN * 0.3
|
|
31
|
+
@proficiency = (@proficiency + gain).clamp(PROFICIENCY_FLOOR, PROFICIENCY_CEILING)
|
|
32
|
+
update_stage
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_production(production_id)
|
|
36
|
+
@productions << production_id unless @productions.include?(production_id)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def compiled?
|
|
40
|
+
@proficiency >= COMPILATION_THRESHOLD
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def autonomous?
|
|
44
|
+
@proficiency >= AUTOMATION_THRESHOLD
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def stage_label
|
|
48
|
+
STAGE_LABELS.find { |range, _| range.cover?(@proficiency) }&.last || :declarative
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def proficiency_label
|
|
52
|
+
PROFICIENCY_LABELS.find { |range, _| range.cover?(@proficiency) }&.last || :novice
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def decay!
|
|
56
|
+
@proficiency = (@proficiency - DECAY_RATE).clamp(PROFICIENCY_FLOOR, PROFICIENCY_CEILING)
|
|
57
|
+
update_stage
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def stale?
|
|
61
|
+
(Time.now.utc - @last_practiced_at) > STALE_THRESHOLD
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_h
|
|
65
|
+
{
|
|
66
|
+
id: @id,
|
|
67
|
+
name: @name,
|
|
68
|
+
domain: @domain,
|
|
69
|
+
proficiency: @proficiency,
|
|
70
|
+
proficiency_label: proficiency_label,
|
|
71
|
+
stage: @stage,
|
|
72
|
+
stage_label: stage_label,
|
|
73
|
+
practice_count: @practice_count,
|
|
74
|
+
production_count: @productions.size,
|
|
75
|
+
compiled: compiled?,
|
|
76
|
+
autonomous: autonomous?,
|
|
77
|
+
created_at: @created_at,
|
|
78
|
+
last_practiced_at: @last_practiced_at
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def update_stage
|
|
85
|
+
@stage = if autonomous?
|
|
86
|
+
:autonomous
|
|
87
|
+
elsif compiled?
|
|
88
|
+
:associative
|
|
89
|
+
else
|
|
90
|
+
:declarative
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module ProceduralLearning
|
|
6
|
+
module Runners
|
|
7
|
+
module ProceduralLearning
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def create_skill(name:, domain:, **)
|
|
12
|
+
skill = engine.create_skill(name: name, domain: domain)
|
|
13
|
+
Legion::Logging.debug "[procedural_learning] created skill=#{name} " \
|
|
14
|
+
"domain=#{domain} id=#{skill.id[0..7]}"
|
|
15
|
+
{ success: true, skill_id: skill.id, name: name, domain: domain,
|
|
16
|
+
proficiency: skill.proficiency, stage: skill.stage }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_skill_production(skill_id:, condition:, action:, domain:, **)
|
|
20
|
+
result = engine.add_production(
|
|
21
|
+
skill_id: skill_id, condition: condition,
|
|
22
|
+
action: action, domain: domain
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return result unless result.is_a?(Helpers::Production)
|
|
26
|
+
|
|
27
|
+
Legion::Logging.debug '[procedural_learning] production added ' \
|
|
28
|
+
"skill=#{skill_id[0..7]} condition=#{condition}"
|
|
29
|
+
{ success: true, production_id: result.id, skill_id: skill_id }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def practice_skill(skill_id:, success:, **)
|
|
33
|
+
result = engine.practice_skill(skill_id: skill_id, success: success)
|
|
34
|
+
Legion::Logging.debug "[procedural_learning] practice skill=#{skill_id[0..7]} " \
|
|
35
|
+
"success=#{success} proficiency=#{result[:proficiency]&.round(3)}"
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def execute_production(production_id:, success:, **)
|
|
40
|
+
result = engine.execute_production(production_id: production_id, success: success)
|
|
41
|
+
Legion::Logging.debug "[procedural_learning] execute production=#{production_id[0..7]} " \
|
|
42
|
+
"success=#{success}"
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def skill_assessment(skill_id:, **)
|
|
47
|
+
result = engine.skill_assessment(skill_id: skill_id)
|
|
48
|
+
Legion::Logging.debug "[procedural_learning] assessment skill=#{skill_id[0..7]}"
|
|
49
|
+
result
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def compiled_skills(**)
|
|
53
|
+
skills = engine.compiled_skills
|
|
54
|
+
Legion::Logging.debug "[procedural_learning] compiled count=#{skills.size}"
|
|
55
|
+
{ success: true, skills: skills.map(&:to_h), count: skills.size }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def autonomous_skills(**)
|
|
59
|
+
skills = engine.autonomous_skills
|
|
60
|
+
Legion::Logging.debug "[procedural_learning] autonomous count=#{skills.size}"
|
|
61
|
+
{ success: true, skills: skills.map(&:to_h), count: skills.size }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def most_practiced_skills(limit: 5, **)
|
|
65
|
+
skills = engine.most_practiced(limit: limit)
|
|
66
|
+
Legion::Logging.debug "[procedural_learning] most_practiced limit=#{limit}"
|
|
67
|
+
{ success: true, skills: skills.map(&:to_h), count: skills.size }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def update_procedural_learning(**)
|
|
71
|
+
engine.decay_all
|
|
72
|
+
pruned = engine.prune_stale
|
|
73
|
+
Legion::Logging.debug "[procedural_learning] decay+prune pruned=#{pruned}"
|
|
74
|
+
{ success: true, pruned: pruned }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def procedural_learning_stats(**)
|
|
78
|
+
stats = engine.to_h
|
|
79
|
+
Legion::Logging.debug "[procedural_learning] stats total=#{stats[:total_skills]}"
|
|
80
|
+
{ success: true }.merge(stats)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def engine
|
|
86
|
+
@engine ||= Helpers::LearningEngine.new
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/procedural_learning/version'
|
|
4
|
+
require 'legion/extensions/procedural_learning/helpers/constants'
|
|
5
|
+
require 'legion/extensions/procedural_learning/helpers/production'
|
|
6
|
+
require 'legion/extensions/procedural_learning/helpers/skill'
|
|
7
|
+
require 'legion/extensions/procedural_learning/helpers/learning_engine'
|
|
8
|
+
require 'legion/extensions/procedural_learning/runners/procedural_learning'
|
|
9
|
+
require 'legion/extensions/procedural_learning/client'
|
|
10
|
+
|
|
11
|
+
module Legion
|
|
12
|
+
module Extensions
|
|
13
|
+
module ProceduralLearning
|
|
14
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::ProceduralLearning::Client do
|
|
4
|
+
subject(:client) { described_class.new }
|
|
5
|
+
|
|
6
|
+
it 'creates a skill' do
|
|
7
|
+
result = client.create_skill(name: 'test', domain: :test)
|
|
8
|
+
expect(result[:success]).to be true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'practices and compiles a skill' do
|
|
12
|
+
created = client.create_skill(name: 'test', domain: :test)
|
|
13
|
+
8.times { client.practice_skill(skill_id: created[:skill_id], success: true) }
|
|
14
|
+
compiled = client.compiled_skills
|
|
15
|
+
expect(compiled[:count]).to eq(1)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'returns stats' do
|
|
19
|
+
result = client.procedural_learning_stats
|
|
20
|
+
expect(result[:success]).to be true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::ProceduralLearning::Helpers::LearningEngine do
|
|
4
|
+
subject(:engine) { described_class.new }
|
|
5
|
+
|
|
6
|
+
let(:skill) { engine.create_skill(name: 'api_retry', domain: :http) }
|
|
7
|
+
|
|
8
|
+
describe '#create_skill' do
|
|
9
|
+
it 'creates and returns a skill' do
|
|
10
|
+
result = skill
|
|
11
|
+
expect(result).to be_a(Legion::Extensions::ProceduralLearning::Helpers::Skill)
|
|
12
|
+
expect(result.name).to eq('api_retry')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'records history' do
|
|
16
|
+
skill
|
|
17
|
+
expect(engine.history.size).to eq(1)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe '#add_production' do
|
|
22
|
+
it 'creates a production for a skill' do
|
|
23
|
+
result = engine.add_production(
|
|
24
|
+
skill_id: skill.id, condition: 'if_error',
|
|
25
|
+
action: 'retry', domain: :http
|
|
26
|
+
)
|
|
27
|
+
expect(result).to be_a(Legion::Extensions::ProceduralLearning::Helpers::Production)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'returns error for unknown skill' do
|
|
31
|
+
result = engine.add_production(
|
|
32
|
+
skill_id: 'bad', condition: 'test',
|
|
33
|
+
action: 'test', domain: :test
|
|
34
|
+
)
|
|
35
|
+
expect(result[:success]).to be false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#practice_skill' do
|
|
40
|
+
it 'increases proficiency' do
|
|
41
|
+
result = engine.practice_skill(skill_id: skill.id, success: true)
|
|
42
|
+
expect(result[:success]).to be true
|
|
43
|
+
expect(result[:proficiency]).to be > 0.1
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns error for unknown skill' do
|
|
47
|
+
result = engine.practice_skill(skill_id: 'bad', success: true)
|
|
48
|
+
expect(result[:success]).to be false
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#execute_production' do
|
|
53
|
+
it 'executes and records outcome' do
|
|
54
|
+
prod = engine.add_production(
|
|
55
|
+
skill_id: skill.id, condition: 'test',
|
|
56
|
+
action: 'act', domain: :test
|
|
57
|
+
)
|
|
58
|
+
result = engine.execute_production(production_id: prod.id, success: true)
|
|
59
|
+
expect(result[:success]).to be true
|
|
60
|
+
expect(result[:success_rate]).to eq(1.0)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe '#skill_assessment' do
|
|
65
|
+
it 'returns assessment with productions' do
|
|
66
|
+
prod = engine.add_production(
|
|
67
|
+
skill_id: skill.id, condition: 'test',
|
|
68
|
+
action: 'act', domain: :test
|
|
69
|
+
)
|
|
70
|
+
engine.execute_production(production_id: prod.id, success: true)
|
|
71
|
+
result = engine.skill_assessment(skill_id: skill.id)
|
|
72
|
+
expect(result[:success]).to be true
|
|
73
|
+
expect(result[:productions].size).to eq(1)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe '#compiled_skills' do
|
|
78
|
+
it 'returns skills above compilation threshold' do
|
|
79
|
+
8.times { engine.practice_skill(skill_id: skill.id, success: true) }
|
|
80
|
+
expect(engine.compiled_skills.size).to eq(1)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe '#autonomous_skills' do
|
|
85
|
+
it 'returns skills above automation threshold' do
|
|
86
|
+
12.times { engine.practice_skill(skill_id: skill.id, success: true) }
|
|
87
|
+
expect(engine.autonomous_skills.size).to eq(1)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe '#by_domain' do
|
|
92
|
+
it 'filters by domain' do
|
|
93
|
+
skill
|
|
94
|
+
engine.create_skill(name: 'other', domain: :dns)
|
|
95
|
+
expect(engine.by_domain(domain: :http).size).to eq(1)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe '#most_practiced' do
|
|
100
|
+
it 'returns skills sorted by practice count' do
|
|
101
|
+
3.times { engine.practice_skill(skill_id: skill.id, success: true) }
|
|
102
|
+
other = engine.create_skill(name: 'other', domain: :dns)
|
|
103
|
+
engine.practice_skill(skill_id: other.id, success: true)
|
|
104
|
+
results = engine.most_practiced(limit: 2)
|
|
105
|
+
expect(results.first.practice_count).to be >= results.last.practice_count
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe '#decay_all' do
|
|
110
|
+
it 'reduces proficiency of all skills' do
|
|
111
|
+
5.times { engine.practice_skill(skill_id: skill.id, success: true) }
|
|
112
|
+
original = skill.proficiency
|
|
113
|
+
engine.decay_all
|
|
114
|
+
expect(skill.proficiency).to be < original
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe '#prune_stale' do
|
|
119
|
+
it 'removes skills with near-zero proficiency' do
|
|
120
|
+
skill
|
|
121
|
+
50.times { skill.decay! }
|
|
122
|
+
pruned = engine.prune_stale
|
|
123
|
+
expect(pruned).to be >= 0
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe '#to_h' do
|
|
128
|
+
it 'returns summary stats' do
|
|
129
|
+
skill
|
|
130
|
+
stats = engine.to_h
|
|
131
|
+
expect(stats[:total_skills]).to eq(1)
|
|
132
|
+
expect(stats).to include(:compiled_count, :autonomous_count, :stage_counts)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::ProceduralLearning::Helpers::Production do
|
|
4
|
+
subject(:production) do
|
|
5
|
+
described_class.new(condition: 'if_error', action: 'retry_request', domain: :http, skill_id: 'skill-123')
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe '#initialize' do
|
|
9
|
+
it 'assigns a UUID' do
|
|
10
|
+
expect(production.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'stores condition and action' do
|
|
14
|
+
expect(production.condition).to eq('if_error')
|
|
15
|
+
expect(production.action).to eq('retry_request')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'starts with zero execution count' do
|
|
19
|
+
expect(production.execution_count).to eq(0)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '#execute!' do
|
|
24
|
+
it 'increments execution count' do
|
|
25
|
+
expect { production.execute!(success: true) }.to change(production, :execution_count).by(1)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'increments success count on success' do
|
|
29
|
+
expect { production.execute!(success: true) }.to change(production, :success_count).by(1)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'does not increment success count on failure' do
|
|
33
|
+
expect { production.execute!(success: false) }.not_to change(production, :success_count)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#success_rate' do
|
|
38
|
+
it 'returns 0.0 when no executions' do
|
|
39
|
+
expect(production.success_rate).to eq(0.0)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'computes success ratio' do
|
|
43
|
+
3.times { production.execute!(success: true) }
|
|
44
|
+
production.execute!(success: false)
|
|
45
|
+
expect(production.success_rate).to eq(0.75)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe '#reliable?' do
|
|
50
|
+
it 'returns false initially' do
|
|
51
|
+
expect(production).not_to be_reliable
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'returns true after sufficient successful executions' do
|
|
55
|
+
5.times { production.execute!(success: true) }
|
|
56
|
+
expect(production).to be_reliable
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe '#to_h' do
|
|
61
|
+
it 'returns hash representation' do
|
|
62
|
+
hash = production.to_h
|
|
63
|
+
expect(hash).to include(:id, :condition, :action, :domain, :success_rate, :reliable)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::ProceduralLearning::Helpers::Skill do
|
|
4
|
+
subject(:skill) { described_class.new(name: 'api_retry', domain: :http) }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'assigns a UUID' do
|
|
8
|
+
expect(skill.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'starts at declarative stage' do
|
|
12
|
+
expect(skill.stage).to eq(:declarative)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'starts with low proficiency' do
|
|
16
|
+
expect(skill.proficiency).to be < 0.2
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe '#practice!' do
|
|
21
|
+
it 'increases proficiency on success' do
|
|
22
|
+
original = skill.proficiency
|
|
23
|
+
skill.practice!(success: true)
|
|
24
|
+
expect(skill.proficiency).to be > original
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'increases proficiency less on failure' do
|
|
28
|
+
success_skill = described_class.new(name: 'test', domain: :test)
|
|
29
|
+
failure_skill = described_class.new(name: 'test', domain: :test)
|
|
30
|
+
success_skill.practice!(success: true)
|
|
31
|
+
failure_skill.practice!(success: false)
|
|
32
|
+
expect(success_skill.proficiency).to be > failure_skill.proficiency
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'transitions stage with practice' do
|
|
36
|
+
10.times { skill.practice!(success: true) }
|
|
37
|
+
expect(skill.stage).not_to eq(:declarative)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '#compiled?' do
|
|
42
|
+
it 'returns false initially' do
|
|
43
|
+
expect(skill).not_to be_compiled
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns true after sufficient practice' do
|
|
47
|
+
8.times { skill.practice!(success: true) }
|
|
48
|
+
expect(skill).to be_compiled
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#autonomous?' do
|
|
53
|
+
it 'returns false initially' do
|
|
54
|
+
expect(skill).not_to be_autonomous
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'returns true after extensive practice' do
|
|
58
|
+
12.times { skill.practice!(success: true) }
|
|
59
|
+
expect(skill).to be_autonomous
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe '#stage_label' do
|
|
64
|
+
it 'returns a label symbol' do
|
|
65
|
+
expect(skill.stage_label).to be_a(Symbol)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe '#proficiency_label' do
|
|
70
|
+
it 'returns a label symbol' do
|
|
71
|
+
expect(skill.proficiency_label).to be_a(Symbol)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe '#decay!' do
|
|
76
|
+
it 'reduces proficiency' do
|
|
77
|
+
5.times { skill.practice!(success: true) }
|
|
78
|
+
original = skill.proficiency
|
|
79
|
+
skill.decay!
|
|
80
|
+
expect(skill.proficiency).to be < original
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe '#add_production' do
|
|
85
|
+
it 'adds production id' do
|
|
86
|
+
skill.add_production('prod-123')
|
|
87
|
+
expect(skill.productions).to include('prod-123')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'does not add duplicates' do
|
|
91
|
+
2.times { skill.add_production('prod-123') }
|
|
92
|
+
expect(skill.productions.size).to eq(1)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe '#to_h' do
|
|
97
|
+
it 'returns hash representation' do
|
|
98
|
+
hash = skill.to_h
|
|
99
|
+
expect(hash).to include(:id, :name, :domain, :proficiency, :stage, :compiled, :autonomous)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::ProceduralLearning::Runners::ProceduralLearning do
|
|
4
|
+
let(:runner_host) do
|
|
5
|
+
obj = Object.new
|
|
6
|
+
obj.extend(described_class)
|
|
7
|
+
obj
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe '#create_skill' do
|
|
11
|
+
it 'creates a skill' do
|
|
12
|
+
result = runner_host.create_skill(name: 'api_retry', domain: :http)
|
|
13
|
+
expect(result[:success]).to be true
|
|
14
|
+
expect(result[:skill_id]).to be_a(String)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe '#add_skill_production' do
|
|
19
|
+
it 'adds a production to a skill' do
|
|
20
|
+
created = runner_host.create_skill(name: 'test', domain: :test)
|
|
21
|
+
result = runner_host.add_skill_production(
|
|
22
|
+
skill_id: created[:skill_id], condition: 'if_error',
|
|
23
|
+
action: 'retry', domain: :test
|
|
24
|
+
)
|
|
25
|
+
expect(result[:success]).to be true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#practice_skill' do
|
|
30
|
+
it 'increases proficiency' do
|
|
31
|
+
created = runner_host.create_skill(name: 'test', domain: :test)
|
|
32
|
+
result = runner_host.practice_skill(skill_id: created[:skill_id], success: true)
|
|
33
|
+
expect(result[:success]).to be true
|
|
34
|
+
expect(result[:proficiency]).to be > 0.1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '#execute_production' do
|
|
39
|
+
it 'executes a production' do
|
|
40
|
+
created = runner_host.create_skill(name: 'test', domain: :test)
|
|
41
|
+
prod = runner_host.add_skill_production(
|
|
42
|
+
skill_id: created[:skill_id], condition: 'test',
|
|
43
|
+
action: 'act', domain: :test
|
|
44
|
+
)
|
|
45
|
+
result = runner_host.execute_production(production_id: prod[:production_id], success: true)
|
|
46
|
+
expect(result[:success]).to be true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#skill_assessment' do
|
|
51
|
+
it 'returns skill assessment' do
|
|
52
|
+
created = runner_host.create_skill(name: 'test', domain: :test)
|
|
53
|
+
result = runner_host.skill_assessment(skill_id: created[:skill_id])
|
|
54
|
+
expect(result[:success]).to be true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#compiled_skills' do
|
|
59
|
+
it 'returns compiled skills' do
|
|
60
|
+
result = runner_host.compiled_skills
|
|
61
|
+
expect(result[:success]).to be true
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe '#autonomous_skills' do
|
|
66
|
+
it 'returns autonomous skills' do
|
|
67
|
+
result = runner_host.autonomous_skills
|
|
68
|
+
expect(result[:success]).to be true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '#most_practiced_skills' do
|
|
73
|
+
it 'returns most practiced skills' do
|
|
74
|
+
result = runner_host.most_practiced_skills(limit: 3)
|
|
75
|
+
expect(result[:success]).to be true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe '#update_procedural_learning' do
|
|
80
|
+
it 'runs decay and prune cycle' do
|
|
81
|
+
result = runner_host.update_procedural_learning
|
|
82
|
+
expect(result[:success]).to be true
|
|
83
|
+
expect(result).to include(:pruned)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe '#procedural_learning_stats' do
|
|
88
|
+
it 'returns stats' do
|
|
89
|
+
result = runner_host.procedural_learning_stats
|
|
90
|
+
expect(result[:success]).to be true
|
|
91
|
+
expect(result).to include(:total_skills, :total_productions)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Helpers
|
|
6
|
+
module Lex; end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Logging
|
|
11
|
+
def self.debug(*); end
|
|
12
|
+
|
|
13
|
+
def self.info(*); end
|
|
14
|
+
|
|
15
|
+
def self.warn(*); end
|
|
16
|
+
|
|
17
|
+
def self.error(*); end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
require 'legion/extensions/procedural_learning'
|
|
22
|
+
|
|
23
|
+
RSpec.configure do |config|
|
|
24
|
+
config.expect_with :rspec do |expectations|
|
|
25
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
config.mock_with :rspec do |mocks|
|
|
29
|
+
mocks.verify_partial_doubles = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
config.filter_run_when_matching :focus
|
|
33
|
+
config.order = :random
|
|
34
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-procedural-learning
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Anderson's ACT-R production rules — skill acquisition from declarative
|
|
27
|
+
to autonomous for brain-modeled agentic AI
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- Gemfile
|
|
35
|
+
- lex-procedural-learning.gemspec
|
|
36
|
+
- lib/legion/extensions/procedural_learning.rb
|
|
37
|
+
- lib/legion/extensions/procedural_learning/client.rb
|
|
38
|
+
- lib/legion/extensions/procedural_learning/helpers/constants.rb
|
|
39
|
+
- lib/legion/extensions/procedural_learning/helpers/learning_engine.rb
|
|
40
|
+
- lib/legion/extensions/procedural_learning/helpers/production.rb
|
|
41
|
+
- lib/legion/extensions/procedural_learning/helpers/skill.rb
|
|
42
|
+
- lib/legion/extensions/procedural_learning/runners/procedural_learning.rb
|
|
43
|
+
- lib/legion/extensions/procedural_learning/version.rb
|
|
44
|
+
- spec/legion/extensions/procedural_learning/client_spec.rb
|
|
45
|
+
- spec/legion/extensions/procedural_learning/helpers/learning_engine_spec.rb
|
|
46
|
+
- spec/legion/extensions/procedural_learning/helpers/production_spec.rb
|
|
47
|
+
- spec/legion/extensions/procedural_learning/helpers/skill_spec.rb
|
|
48
|
+
- spec/legion/extensions/procedural_learning/runners/procedural_learning_spec.rb
|
|
49
|
+
- spec/spec_helper.rb
|
|
50
|
+
homepage: https://github.com/LegionIO/lex-procedural-learning
|
|
51
|
+
licenses:
|
|
52
|
+
- MIT
|
|
53
|
+
metadata:
|
|
54
|
+
homepage_uri: https://github.com/LegionIO/lex-procedural-learning
|
|
55
|
+
source_code_uri: https://github.com/LegionIO/lex-procedural-learning
|
|
56
|
+
documentation_uri: https://github.com/LegionIO/lex-procedural-learning
|
|
57
|
+
changelog_uri: https://github.com/LegionIO/lex-procedural-learning
|
|
58
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-procedural-learning/issues
|
|
59
|
+
rubygems_mfa_required: 'true'
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.4'
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 3.6.9
|
|
75
|
+
specification_version: 4
|
|
76
|
+
summary: LEX Procedural Learning
|
|
77
|
+
test_files: []
|