lex-reflection 0.1.1

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.
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Reflection
6
+ module Helpers
7
+ class ReflectionStore
8
+ attr_reader :total_generated
9
+
10
+ def initialize
11
+ @reflections = {}
12
+ @total_generated = 0
13
+ @category_scores = Hash.new(1.0)
14
+ end
15
+
16
+ def store(reflection)
17
+ prune_oldest if @reflections.size >= Constants::MAX_REFLECTIONS
18
+ @reflections[reflection[:reflection_id]] = reflection
19
+ @total_generated += 1
20
+ reflection
21
+ end
22
+
23
+ def get(reflection_id)
24
+ @reflections[reflection_id]
25
+ end
26
+
27
+ def recent(limit: 10)
28
+ @reflections.values
29
+ .sort_by { |r| r[:created_at] }
30
+ .last(limit)
31
+ .reverse
32
+ end
33
+
34
+ def by_category(category)
35
+ @reflections.values.select { |r| r[:category] == category }
36
+ end
37
+
38
+ def by_severity(severity)
39
+ @reflections.values.select { |r| r[:severity] == severity }
40
+ end
41
+
42
+ def mark_acted_on(reflection_id)
43
+ reflection = @reflections[reflection_id]
44
+ return nil unless reflection
45
+
46
+ @reflections[reflection_id] = reflection.merge(acted_on: true)
47
+ end
48
+
49
+ def unacted
50
+ @reflections.values.reject { |r| r[:acted_on] }
51
+ end
52
+
53
+ def count
54
+ @reflections.size
55
+ end
56
+
57
+ def severity_counts
58
+ counts = Hash.new(0)
59
+ @reflections.each_value { |r| counts[r[:severity]] += 1 }
60
+ counts
61
+ end
62
+
63
+ def category_counts
64
+ counts = Hash.new(0)
65
+ @reflections.each_value { |r| counts[r[:category]] += 1 }
66
+ counts
67
+ end
68
+
69
+ def update_category_score(category, score)
70
+ @category_scores[category] = score.clamp(0.0, 1.0)
71
+ end
72
+
73
+ def cognitive_health
74
+ total_weight = Constants::HEALTH_WEIGHTS.values.sum
75
+ weighted_sum = Constants::HEALTH_WEIGHTS.sum do |cat, weight|
76
+ @category_scores[cat] * weight
77
+ end
78
+ (weighted_sum / total_weight).round(3)
79
+ end
80
+
81
+ def category_score(category)
82
+ @category_scores[category]
83
+ end
84
+
85
+ private
86
+
87
+ def prune_oldest
88
+ oldest = @reflections.values.min_by { |r| r[:created_at] }
89
+ @reflections.delete(oldest[:reflection_id]) if oldest
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Reflection
6
+ module Runners
7
+ module Reflection
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def reflect(tick_results: {}, **)
12
+ @metric_history ||= []
13
+ @metric_history << tick_results
14
+ @metric_history = @metric_history.last(Helpers::Constants::METRIC_WINDOW_SIZE)
15
+
16
+ new_reflections = Helpers::Monitors.run_all(tick_results, @metric_history)
17
+ new_reflections.each { |r| reflection_store.store(r) }
18
+
19
+ update_category_scores(tick_results)
20
+
21
+ if Helpers::LlmEnhancer.available? && new_reflections.any?
22
+ health_scores = Helpers::Constants::CATEGORIES.to_h { |c| [c, reflection_store.category_score(c)] }
23
+ llm_result = Helpers::LlmEnhancer.enhance_reflection(
24
+ monitors_data: new_reflections,
25
+ health_scores: health_scores
26
+ )
27
+ if llm_result
28
+ new_reflections.each do |entry|
29
+ enhanced = llm_result[:observations][entry[:category]]
30
+ next unless enhanced
31
+
32
+ entry[:observation] = enhanced
33
+ entry[:source] = :llm
34
+ end
35
+ end
36
+ end
37
+
38
+ Legion::Logging.debug "[reflection] generated #{new_reflections.size} reflections, health=#{reflection_store.cognitive_health}"
39
+
40
+ {
41
+ reflections_generated: new_reflections.size,
42
+ cognitive_health: reflection_store.cognitive_health,
43
+ new_reflections: new_reflections.map { |r| format_reflection(r) },
44
+ total_reflections: reflection_store.count
45
+ }
46
+ end
47
+
48
+ def reflect_on_dream(dream_results: {}, **)
49
+ source = :mechanical
50
+ reflection = nil
51
+
52
+ if Helpers::LlmEnhancer.available?
53
+ llm_result = Helpers::LlmEnhancer.reflect_on_dream(dream_results: dream_results)
54
+ if llm_result&.fetch(:reflection, nil)
55
+ reflection = llm_result[:reflection]
56
+ source = :llm
57
+ end
58
+ end
59
+
60
+ reflection ||= build_mechanical_dream_reflection(dream_results)
61
+
62
+ Legion::Logging.debug "[reflection] dream reflection generated source=#{source}"
63
+ { reflection: reflection, source: source }
64
+ end
65
+
66
+ def cognitive_health(**)
67
+ health = reflection_store.cognitive_health
68
+ Legion::Logging.debug "[reflection] cognitive health: #{health}"
69
+ {
70
+ health: health,
71
+ category_scores: Helpers::Constants::CATEGORIES.to_h { |c| [c, reflection_store.category_score(c)] },
72
+ unacted_count: reflection_store.unacted.size,
73
+ critical_count: reflection_store.by_severity(:critical).size,
74
+ significant_count: reflection_store.by_severity(:significant).size
75
+ }
76
+ end
77
+
78
+ def recent_reflections(limit: 10, **)
79
+ reflections = reflection_store.recent(limit: limit)
80
+ { reflections: reflections.map { |r| format_reflection(r) } }
81
+ end
82
+
83
+ def reflections_by_category(category:, **)
84
+ cat = category.to_sym
85
+ reflections = reflection_store.by_category(cat)
86
+ { category: cat, reflections: reflections.map { |r| format_reflection(r) } }
87
+ end
88
+
89
+ def adapt(reflection_id:, **)
90
+ reflection = reflection_store.get(reflection_id)
91
+ return { error: :not_found } unless reflection
92
+ return { error: :already_acted } if reflection[:acted_on]
93
+
94
+ reflection_store.mark_acted_on(reflection_id)
95
+ Legion::Logging.info "[reflection] adapted: #{reflection[:observation]}"
96
+ { adapted: true, reflection_id: reflection_id, recommendation: reflection[:recommendation] }
97
+ end
98
+
99
+ def reflection_stats(**)
100
+ {
101
+ total_generated: reflection_store.total_generated,
102
+ current_count: reflection_store.count,
103
+ cognitive_health: reflection_store.cognitive_health,
104
+ severity_counts: reflection_store.severity_counts,
105
+ category_counts: reflection_store.category_counts,
106
+ unacted: reflection_store.unacted.size
107
+ }
108
+ end
109
+
110
+ private
111
+
112
+ def build_mechanical_dream_reflection(dream_results)
113
+ return 'Dream cycle completed.' unless dream_results.is_a?(Hash) && dream_results.any?
114
+
115
+ parts = []
116
+ if (audit = dream_results[:memory_audit]).is_a?(Hash)
117
+ parts << "Memory audit: #{audit[:decayed] || 0} traces decayed, #{audit[:unresolved_count] || 0} unresolved."
118
+ end
119
+ if (contra = dream_results[:contradiction_resolution]).is_a?(Hash)
120
+ parts << "Contradictions: #{contra[:detected] || 0} detected, #{contra[:resolved] || 0} resolved."
121
+ end
122
+ if (agenda = dream_results[:agenda_formation]).is_a?(Hash)
123
+ parts << "Agenda formed with #{agenda[:agenda_items] || 0} items."
124
+ end
125
+ parts.empty? ? 'Dream cycle completed.' : parts.join(' ')
126
+ end
127
+
128
+ def reflection_store
129
+ @reflection_store ||= Helpers::ReflectionStore.new
130
+ end
131
+
132
+ def format_reflection(reflection)
133
+ {
134
+ reflection_id: reflection[:reflection_id],
135
+ category: reflection[:category],
136
+ observation: reflection[:observation],
137
+ severity: reflection[:severity],
138
+ recommendation: reflection[:recommendation],
139
+ acted_on: reflection[:acted_on],
140
+ created_at: reflection[:created_at]
141
+ }
142
+ end
143
+
144
+ def update_category_scores(tick_results)
145
+ update_prediction_score(tick_results)
146
+ update_curiosity_score(tick_results)
147
+ update_emotion_score(tick_results)
148
+ update_memory_score(tick_results)
149
+ update_load_score(tick_results)
150
+ end
151
+
152
+ def update_prediction_score(tick_results)
153
+ prediction = tick_results[:prediction_engine]
154
+ return unless prediction.is_a?(Hash) && prediction[:confidence].is_a?(Numeric)
155
+
156
+ reflection_store.update_category_score(:prediction_calibration, prediction[:confidence])
157
+ end
158
+
159
+ def update_curiosity_score(tick_results)
160
+ curiosity = tick_results[:working_memory_integration]
161
+ return unless curiosity.is_a?(Hash) && curiosity[:curiosity_intensity].is_a?(Numeric)
162
+
163
+ score = 1.0 - ([curiosity[:curiosity_intensity], 1.0].min * 0.3)
164
+ reflection_store.update_category_score(:curiosity_effectiveness, score)
165
+ end
166
+
167
+ def update_emotion_score(tick_results)
168
+ emotion = tick_results[:emotional_evaluation]
169
+ stability = emotion.is_a?(Hash) ? (emotion[:stability] || emotion.dig(:momentum, :stability)) : nil
170
+ return unless stability.is_a?(Numeric)
171
+
172
+ reflection_store.update_category_score(:emotional_stability, stability)
173
+ end
174
+
175
+ def update_memory_score(tick_results)
176
+ memory = tick_results[:memory_consolidation]
177
+ return unless memory.is_a?(Hash) && memory[:total].is_a?(Numeric) && memory[:total].positive?
178
+
179
+ ratio = (memory[:pruned] || 0).to_f / memory[:total]
180
+ reflection_store.update_category_score(:memory_health, 1.0 - ratio)
181
+ end
182
+
183
+ def update_load_score(tick_results)
184
+ elapsed = tick_results[:elapsed]
185
+ budget = tick_results[:budget]
186
+ return unless elapsed.is_a?(Numeric) && budget.is_a?(Numeric) && budget.positive?
187
+
188
+ utilization = elapsed / budget
189
+ reflection_store.update_category_score(:cognitive_load, [1.0 - utilization, 0.0].max)
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Reflection
6
+ VERSION = '0.1.1'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/reflection/version'
4
+ require 'legion/extensions/reflection/helpers/constants'
5
+ require 'legion/extensions/reflection/helpers/reflection'
6
+ require 'legion/extensions/reflection/helpers/reflection_store'
7
+ require 'legion/extensions/reflection/helpers/monitors'
8
+ require 'legion/extensions/reflection/helpers/llm_enhancer'
9
+ require 'legion/extensions/reflection/runners/reflection'
10
+ require 'legion/extensions/reflection/client'
11
+
12
+ module Legion
13
+ module Extensions
14
+ module Reflection
15
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Reflection::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'initializes with a default reflection store' do
7
+ expect(client.reflection_store).to be_a(Legion::Extensions::Reflection::Helpers::ReflectionStore)
8
+ end
9
+
10
+ it 'accepts an injected store' do
11
+ custom = Legion::Extensions::Reflection::Helpers::ReflectionStore.new
12
+ client = described_class.new(store: custom)
13
+ expect(client.reflection_store).to be(custom)
14
+ end
15
+
16
+ it 'includes the Reflection runner' do
17
+ expect(client).to respond_to(:reflect)
18
+ expect(client).to respond_to(:cognitive_health)
19
+ expect(client).to respond_to(:recent_reflections)
20
+ expect(client).to respond_to(:reflections_by_category)
21
+ expect(client).to respond_to(:adapt)
22
+ expect(client).to respond_to(:reflection_stats)
23
+ end
24
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Reflection::Helpers::LlmEnhancer do
4
+ describe '.available?' do
5
+ context 'when Legion::LLM is not defined' do
6
+ it 'returns a falsy value' do
7
+ # Legion::LLM is not defined in the test environment
8
+ expect(described_class.available?).to be_falsy
9
+ end
10
+ end
11
+
12
+ context 'when Legion::LLM is defined but not started' do
13
+ before do
14
+ stub_const('Legion::LLM', double(respond_to?: true, started?: false))
15
+ end
16
+
17
+ it 'returns false' do
18
+ expect(described_class.available?).to be false
19
+ end
20
+ end
21
+
22
+ context 'when Legion::LLM is started' do
23
+ before do
24
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
25
+ end
26
+
27
+ it 'returns true' do
28
+ expect(described_class.available?).to be true
29
+ end
30
+ end
31
+
32
+ context 'when an error is raised' do
33
+ before do
34
+ stub_const('Legion::LLM', double)
35
+ allow(Legion::LLM).to receive(:respond_to?).and_raise(StandardError)
36
+ end
37
+
38
+ it 'returns false' do
39
+ expect(described_class.available?).to be false
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '.enhance_reflection' do
45
+ let(:fake_response) do
46
+ double(content: <<~TEXT)
47
+ EMOTION: My arousal is elevated at 0.7 while stability holds at 0.8, suggesting urgency without destabilization.
48
+ PREDICTION: Confidence at 65% with a declining trend and 3 pending predictions signals I am losing ground in forward modeling.
49
+ MEMORY: Memory health appears nominal with no concerning decay patterns detected this cycle.
50
+ TRUST: Trust scores remain stable with no significant drift observed.
51
+ CURIOSITY: Curiosity intensity is moderate; resolution rates suggest effective exploration.
52
+ IDENTITY: Identity entropy is within expected bounds; no drift detected.
53
+ TEXT
54
+ end
55
+ let(:fake_chat) { double }
56
+ let(:monitors_data) do
57
+ [
58
+ {
59
+ category: :emotional_stability,
60
+ observation: 'original',
61
+ severity: :notable,
62
+ metrics: { stability: 0.8 },
63
+ recommendation: :no_action
64
+ },
65
+ {
66
+ category: :prediction_calibration,
67
+ observation: 'original',
68
+ severity: :notable,
69
+ metrics: { confidence: 0.65 },
70
+ recommendation: :increase_curiosity
71
+ }
72
+ ]
73
+ end
74
+ let(:health_scores) do
75
+ {
76
+ prediction_calibration: 0.65,
77
+ curiosity_effectiveness: 0.8,
78
+ emotional_stability: 0.8,
79
+ trust_drift: 1.0,
80
+ memory_health: 0.95,
81
+ cognitive_load: 0.9,
82
+ mode_patterns: 1.0
83
+ }
84
+ end
85
+
86
+ before do
87
+ stub_const('Legion::LLM', double)
88
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
89
+ allow(fake_chat).to receive(:with_instructions)
90
+ allow(fake_chat).to receive(:ask).and_return(fake_response)
91
+ end
92
+
93
+ it 'returns observations hash with at least some categories' do
94
+ result = described_class.enhance_reflection(
95
+ monitors_data: monitors_data,
96
+ health_scores: health_scores
97
+ )
98
+ expect(result).to be_a(Hash)
99
+ expect(result[:observations]).to be_a(Hash)
100
+ expect(result[:observations]).not_to be_empty
101
+ end
102
+
103
+ it 'parses per-category observation text' do
104
+ result = described_class.enhance_reflection(
105
+ monitors_data: monitors_data,
106
+ health_scores: health_scores
107
+ )
108
+ expect(result[:observations][:emotional_stability]).to include('arousal')
109
+ expect(result[:observations][:prediction_calibration]).to include('Confidence')
110
+ end
111
+
112
+ context 'when LLM returns nil content' do
113
+ before { allow(fake_chat).to receive(:ask).and_return(double(content: nil)) }
114
+
115
+ it 'returns nil' do
116
+ result = described_class.enhance_reflection(
117
+ monitors_data: monitors_data,
118
+ health_scores: health_scores
119
+ )
120
+ expect(result).to be_nil
121
+ end
122
+ end
123
+
124
+ context 'when LLM raises an error' do
125
+ before { allow(fake_chat).to receive(:ask).and_raise(StandardError, 'LLM timeout') }
126
+
127
+ it 'returns nil and logs a warning' do
128
+ expect(Legion::Logging).to receive(:warn).with(/enhance_reflection failed/)
129
+ result = described_class.enhance_reflection(
130
+ monitors_data: monitors_data,
131
+ health_scores: health_scores
132
+ )
133
+ expect(result).to be_nil
134
+ end
135
+ end
136
+ end
137
+
138
+ describe '.reflect_on_dream' do
139
+ let(:fake_response) do
140
+ double(content: <<~TEXT)
141
+ REFLECTION: The dream cycle surfaced 3 unresolved traces and resolved 1 contradiction. Memory consolidation is progressing normally with agenda items focused on identity coherence.
142
+ TEXT
143
+ end
144
+ let(:fake_chat) { double }
145
+ let(:dream_results) do
146
+ {
147
+ memory_audit: { decayed: 5, pruned: 2, unresolved_count: 3 },
148
+ contradiction_resolution: { detected: 2, resolved: 1 },
149
+ agenda_formation: { agenda_items: 4 }
150
+ }
151
+ end
152
+
153
+ before do
154
+ stub_const('Legion::LLM', double)
155
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
156
+ allow(fake_chat).to receive(:with_instructions)
157
+ allow(fake_chat).to receive(:ask).and_return(fake_response)
158
+ end
159
+
160
+ it 'returns a reflection string' do
161
+ result = described_class.reflect_on_dream(dream_results: dream_results)
162
+ expect(result).to be_a(Hash)
163
+ expect(result[:reflection]).to be_a(String)
164
+ expect(result[:reflection]).not_to be_empty
165
+ end
166
+
167
+ it 'includes dream cycle content in the reflection' do
168
+ result = described_class.reflect_on_dream(dream_results: dream_results)
169
+ expect(result[:reflection]).to include('unresolved traces')
170
+ end
171
+
172
+ context 'when LLM returns nil content' do
173
+ before { allow(fake_chat).to receive(:ask).and_return(double(content: nil)) }
174
+
175
+ it 'returns nil' do
176
+ result = described_class.reflect_on_dream(dream_results: dream_results)
177
+ expect(result).to be_nil
178
+ end
179
+ end
180
+
181
+ context 'when LLM raises an error' do
182
+ before { allow(fake_chat).to receive(:ask).and_raise(StandardError, 'model error') }
183
+
184
+ it 'returns nil and logs a warning' do
185
+ expect(Legion::Logging).to receive(:warn).with(/reflect_on_dream failed/)
186
+ result = described_class.reflect_on_dream(dream_results: dream_results)
187
+ expect(result).to be_nil
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Reflection::Helpers::Monitors do
4
+ describe '.monitor_predictions' do
5
+ it 'generates reflection for low confidence' do
6
+ results = described_class.monitor_predictions(
7
+ { prediction_engine: { confidence: 0.2 } },
8
+ []
9
+ )
10
+ expect(results.size).to be >= 1
11
+ expect(results.first[:category]).to eq(:prediction_calibration)
12
+ expect(results.first[:recommendation]).to eq(:increase_curiosity)
13
+ end
14
+
15
+ it 'returns empty for good confidence' do
16
+ results = described_class.monitor_predictions(
17
+ { prediction_engine: { confidence: 0.9 } },
18
+ []
19
+ )
20
+ expect(results).to be_empty
21
+ end
22
+
23
+ it 'detects accuracy trend drop' do
24
+ history = Array.new(5) { { prediction_engine: { confidence: 0.9 } } } +
25
+ Array.new(5) { { prediction_engine: { confidence: 0.5 } } }
26
+
27
+ results = described_class.monitor_predictions(
28
+ { prediction_engine: { confidence: 0.5 } },
29
+ history
30
+ )
31
+ trend = results.find { |r| r[:metrics][:trend_drop] }
32
+ expect(trend).not_to be_nil
33
+ end
34
+ end
35
+
36
+ describe '.monitor_curiosity' do
37
+ it 'generates reflection for low resolution rate' do
38
+ results = described_class.monitor_curiosity(
39
+ working_memory_integration: { resolution_rate: 0.1 }
40
+ )
41
+ expect(results.size).to eq(1)
42
+ expect(results.first[:recommendation]).to eq(:decrease_curiosity)
43
+ end
44
+
45
+ it 'celebrates high resolution rate' do
46
+ results = described_class.monitor_curiosity(
47
+ working_memory_integration: { resolution_rate: 0.9 }
48
+ )
49
+ expect(results.size).to eq(1)
50
+ expect(results.first[:recommendation]).to eq(:celebrate_success)
51
+ end
52
+ end
53
+
54
+ describe '.monitor_emotions' do
55
+ it 'generates reflection for instability' do
56
+ results = described_class.monitor_emotions(
57
+ emotional_evaluation: { stability: 0.1 }
58
+ )
59
+ expect(results.size).to eq(1)
60
+ expect(results.first[:category]).to eq(:emotional_stability)
61
+ expect(results.first[:severity]).to eq(:significant)
62
+ end
63
+
64
+ it 'detects emotional flatness' do
65
+ results = described_class.monitor_emotions(
66
+ emotional_evaluation: { stability: 0.99 }
67
+ )
68
+ expect(results.size).to eq(1)
69
+ expect(results.first[:observation]).to include('flat')
70
+ end
71
+ end
72
+
73
+ describe '.monitor_memory' do
74
+ it 'generates reflection for high decay ratio' do
75
+ results = described_class.monitor_memory(
76
+ memory_consolidation: { pruned: 90, total: 100 }
77
+ )
78
+ expect(results.size).to eq(1)
79
+ expect(results.first[:recommendation]).to eq(:consolidate_memory)
80
+ end
81
+
82
+ it 'returns empty for healthy memory' do
83
+ results = described_class.monitor_memory(
84
+ memory_consolidation: { pruned: 5, total: 100 }
85
+ )
86
+ expect(results).to be_empty
87
+ end
88
+ end
89
+
90
+ describe '.monitor_cognitive_load' do
91
+ it 'generates reflection when near budget' do
92
+ results = described_class.monitor_cognitive_load(
93
+ elapsed: 4.8, budget: 5.0
94
+ )
95
+ expect(results.size).to eq(1)
96
+ expect(results.first[:category]).to eq(:cognitive_load)
97
+ end
98
+
99
+ it 'returns empty when within budget' do
100
+ results = described_class.monitor_cognitive_load(
101
+ elapsed: 1.0, budget: 5.0
102
+ )
103
+ expect(results).to be_empty
104
+ end
105
+ end
106
+
107
+ describe '.run_all' do
108
+ it 'aggregates results from all monitors' do
109
+ tick_results = {
110
+ prediction_engine: { confidence: 0.2 },
111
+ emotional_evaluation: { stability: 0.1 },
112
+ memory_consolidation: { pruned: 90, total: 100 },
113
+ elapsed: 4.8,
114
+ budget: 5.0
115
+ }
116
+ results = described_class.run_all(tick_results, [])
117
+ expect(results.size).to be >= 3
118
+ end
119
+ end
120
+ end