lex-tick 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 +10 -0
- data/lex-tick.gemspec +29 -0
- data/lib/legion/extensions/tick/actors/tick.rb +59 -0
- data/lib/legion/extensions/tick/client.rb +23 -0
- data/lib/legion/extensions/tick/helpers/constants.rb +95 -0
- data/lib/legion/extensions/tick/helpers/state.rb +71 -0
- data/lib/legion/extensions/tick/runners/orchestrator.rb +166 -0
- data/lib/legion/extensions/tick/version.rb +9 -0
- data/lib/legion/extensions/tick.rb +14 -0
- data/spec/legion/extensions/tick/actors/tick_spec.rb +104 -0
- data/spec/legion/extensions/tick/client_spec.rb +24 -0
- data/spec/legion/extensions/tick/helpers/constants_spec.rb +90 -0
- data/spec/legion/extensions/tick/helpers/state_spec.rb +72 -0
- data/spec/legion/extensions/tick/runners/orchestrator_spec.rb +153 -0
- data/spec/spec_helper.rb +20 -0
- metadata +76 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4a3808a8152389198268ec0a2064aee1a4acdcf8fc7aa5593118e1cd7f82deda
|
|
4
|
+
data.tar.gz: 770f22284ee087b9319597ebe194b34bcc4e1b17b901db13f4bc8d45b38a6d3c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 80498e4cfb7859876f8efb0e72ad89bad390f307d56f7a27dea617092f946d828db57522591af4629a3fe00e6c1ae279e7effca892a35cb5d14fe4085ad5eb3d
|
|
7
|
+
data.tar.gz: f9a62ee7072446e4c0caf5b55c05fff8d12323ab3619fb7d9c923e0e252649ec9acab3d287f46a1358b6298ecb7285e519499838bf6e6c0b3c1f66b36c7662a6
|
data/Gemfile
ADDED
data/lex-tick.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/tick/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-tick'
|
|
7
|
+
spec.version = Legion::Extensions::Tick::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Tick'
|
|
12
|
+
spec.description = 'Atomic cognitive processing cycle (11 phases, 3 modes) for brain-modeled agentic AI'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-tick'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 3.4'
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-tick'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-tick'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-tick'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-tick/issues'
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
23
|
+
|
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
Dir.glob('{lib,spec}/**/*') + %w[lex-tick.gemspec Gemfile]
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
spec.add_development_dependency 'legion-gaia'
|
|
29
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Tick
|
|
8
|
+
module Actor
|
|
9
|
+
# Disabled: lex-cortex's Think actor replaces this.
|
|
10
|
+
# Cortex wires phase_handlers from all agentic extensions
|
|
11
|
+
# and calls execute_tick with real handlers instead of empty ones.
|
|
12
|
+
# To use tick standalone (without cortex), re-enable this actor.
|
|
13
|
+
class Tick < Legion::Extensions::Actors::Every
|
|
14
|
+
def initialize(**opts)
|
|
15
|
+
return unless enabled?
|
|
16
|
+
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def runner_class
|
|
21
|
+
Legion::Extensions::Tick::Runners::Orchestrator
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def runner_function
|
|
25
|
+
'execute_tick'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def enabled?
|
|
29
|
+
!Legion::Extensions.const_defined?(:Cortex)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def time
|
|
33
|
+
1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def run_now?
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def use_runner?
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def check_subtask?
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def generate_task?
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def args
|
|
53
|
+
{ signals: [], phase_handlers: {} }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/tick/helpers/constants'
|
|
4
|
+
require 'legion/extensions/tick/helpers/state'
|
|
5
|
+
require 'legion/extensions/tick/runners/orchestrator'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module Tick
|
|
10
|
+
class Client
|
|
11
|
+
include Runners::Orchestrator
|
|
12
|
+
|
|
13
|
+
def initialize(mode: :dormant, **)
|
|
14
|
+
@tick_state = Helpers::State.new(mode: mode)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :tick_state
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Tick
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
# Tick modes
|
|
9
|
+
MODES = %i[dormant dormant_active sentinel full_active].freeze
|
|
10
|
+
|
|
11
|
+
# 12 phases of a full active tick
|
|
12
|
+
PHASES = %i[
|
|
13
|
+
sensory_processing
|
|
14
|
+
emotional_evaluation
|
|
15
|
+
memory_retrieval
|
|
16
|
+
identity_entropy_check
|
|
17
|
+
working_memory_integration
|
|
18
|
+
procedural_check
|
|
19
|
+
prediction_engine
|
|
20
|
+
mesh_interface
|
|
21
|
+
gut_instinct
|
|
22
|
+
action_selection
|
|
23
|
+
memory_consolidation
|
|
24
|
+
post_tick_reflection
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
# Phases for dream cycle (dormant_active mode)
|
|
28
|
+
DREAM_PHASES = %i[
|
|
29
|
+
memory_audit
|
|
30
|
+
association_walk
|
|
31
|
+
contradiction_resolution
|
|
32
|
+
identity_entropy_check
|
|
33
|
+
agenda_formation
|
|
34
|
+
consolidation_commit
|
|
35
|
+
dream_reflection
|
|
36
|
+
dream_narration
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
# Which phases run in each mode
|
|
40
|
+
MODE_PHASES = {
|
|
41
|
+
dormant: %i[memory_consolidation],
|
|
42
|
+
dormant_active: DREAM_PHASES,
|
|
43
|
+
sentinel: %i[sensory_processing emotional_evaluation memory_retrieval prediction_engine memory_consolidation],
|
|
44
|
+
full_active: PHASES
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
# Timing constants (in seconds)
|
|
48
|
+
ACTIVE_TIMEOUT = 300 # seconds without high-salience signal before demotion
|
|
49
|
+
SENTINEL_TIMEOUT = 3600 # seconds without any signal before demotion to dormant
|
|
50
|
+
DREAM_IDLE_THRESHOLD = 1800 # seconds dormant with no signal before entering dream cycle
|
|
51
|
+
SENTINEL_TO_DREAM_THRESHOLD = 600 # seconds sentinel with no signal before entering dream cycle
|
|
52
|
+
MAX_TICK_DURATION = 5.0 # hard ceiling for full active tick (seconds)
|
|
53
|
+
SENTINEL_TICK_BUDGET = 0.5 # time budget for sentinel tick
|
|
54
|
+
DORMANT_TICK_BUDGET = 0.2 # time budget for dormant tick
|
|
55
|
+
EMERGENCY_PROMOTION_BUDGET = 0.05 # max latency for emergency mode promotion
|
|
56
|
+
|
|
57
|
+
# Phase timing budgets (fraction of total tick time)
|
|
58
|
+
PHASE_BUDGETS = {
|
|
59
|
+
sensory_processing: 0.15,
|
|
60
|
+
emotional_evaluation: 0.10,
|
|
61
|
+
memory_retrieval: 0.20,
|
|
62
|
+
identity_entropy_check: 0.05,
|
|
63
|
+
working_memory_integration: 0.05,
|
|
64
|
+
procedural_check: 0.10,
|
|
65
|
+
prediction_engine: 0.15,
|
|
66
|
+
mesh_interface: 0.05,
|
|
67
|
+
gut_instinct: 0.05,
|
|
68
|
+
action_selection: 0.05,
|
|
69
|
+
memory_consolidation: 0.05,
|
|
70
|
+
post_tick_reflection: 0.05
|
|
71
|
+
}.freeze
|
|
72
|
+
|
|
73
|
+
# Salience thresholds for mode transitions
|
|
74
|
+
HIGH_SALIENCE_THRESHOLD = 0.7
|
|
75
|
+
EMERGENCY_TRIGGERS = %i[firmware_violation extinction_protocol].freeze
|
|
76
|
+
|
|
77
|
+
module_function
|
|
78
|
+
|
|
79
|
+
def phases_for_mode(mode)
|
|
80
|
+
MODE_PHASES.fetch(mode, PHASES)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def tick_budget(mode)
|
|
84
|
+
case mode
|
|
85
|
+
when :dormant then DORMANT_TICK_BUDGET
|
|
86
|
+
when :dormant_active then Float::INFINITY
|
|
87
|
+
when :sentinel then SENTINEL_TICK_BUDGET
|
|
88
|
+
else MAX_TICK_DURATION
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Tick
|
|
6
|
+
module Helpers
|
|
7
|
+
class State
|
|
8
|
+
attr_reader :mode, :tick_count, :last_signal_at, :last_high_salience_at,
|
|
9
|
+
:phase_results, :current_phase, :mode_history
|
|
10
|
+
|
|
11
|
+
def initialize(mode: :dormant)
|
|
12
|
+
@mode = mode
|
|
13
|
+
@tick_count = 0
|
|
14
|
+
@last_signal_at = nil
|
|
15
|
+
@last_high_salience_at = nil
|
|
16
|
+
@phase_results = {}
|
|
17
|
+
@current_phase = nil
|
|
18
|
+
@mode_history = [{ mode: mode, at: Time.now.utc }]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def record_signal(salience: 0.0)
|
|
22
|
+
@last_signal_at = Time.now.utc
|
|
23
|
+
@last_high_salience_at = Time.now.utc if salience >= Constants::HIGH_SALIENCE_THRESHOLD
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def record_phase(phase, result)
|
|
27
|
+
@current_phase = phase
|
|
28
|
+
@phase_results[phase] = result
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def increment_tick
|
|
32
|
+
@tick_count += 1
|
|
33
|
+
@phase_results = {}
|
|
34
|
+
@current_phase = nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def transition_to(new_mode)
|
|
38
|
+
return if new_mode == @mode
|
|
39
|
+
|
|
40
|
+
@mode = new_mode
|
|
41
|
+
@mode_history << { mode: new_mode, at: Time.now.utc }
|
|
42
|
+
@mode_history.shift while @mode_history.size > 50
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def seconds_since_signal
|
|
46
|
+
return Float::INFINITY unless @last_signal_at
|
|
47
|
+
|
|
48
|
+
Time.now.utc - @last_signal_at
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def seconds_since_high_salience
|
|
52
|
+
return Float::INFINITY unless @last_high_salience_at
|
|
53
|
+
|
|
54
|
+
Time.now.utc - @last_high_salience_at
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_h
|
|
58
|
+
{
|
|
59
|
+
mode: @mode,
|
|
60
|
+
tick_count: @tick_count,
|
|
61
|
+
current_phase: @current_phase,
|
|
62
|
+
last_signal_at: @last_signal_at,
|
|
63
|
+
last_high_salience_at: @last_high_salience_at,
|
|
64
|
+
phases_completed: @phase_results.keys
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Tick
|
|
6
|
+
module Runners
|
|
7
|
+
module Orchestrator
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def execute_tick(signals: [], phase_handlers: {}, **)
|
|
12
|
+
state = tick_state
|
|
13
|
+
state.increment_tick
|
|
14
|
+
|
|
15
|
+
max_salience = signals.map { |s| s.is_a?(Hash) ? (s[:salience] || 0.0) : 0.0 }.max || 0.0
|
|
16
|
+
state.record_signal(salience: max_salience) unless signals.empty?
|
|
17
|
+
|
|
18
|
+
Legion::Logging.debug "[tick] ##{state.tick_count} starting | mode=#{state.mode} signals=#{signals.size} max_salience=#{max_salience.round(2)}"
|
|
19
|
+
|
|
20
|
+
transition = evaluate_mode_transition(signals: signals)
|
|
21
|
+
if transition[:transitioned]
|
|
22
|
+
Legion::Logging.info "[tick] mode transition: #{transition[:previous_mode]} -> #{transition[:new_mode]} (#{transition[:reason]})"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
phases = Helpers::Constants.phases_for_mode(state.mode)
|
|
26
|
+
budget = Helpers::Constants.tick_budget(state.mode)
|
|
27
|
+
start_time = Time.now.utc
|
|
28
|
+
ctx = { budget: budget, start_time: start_time, phase_handlers: phase_handlers, signals: signals }
|
|
29
|
+
results = run_phases(phases, state, ctx)
|
|
30
|
+
|
|
31
|
+
total_elapsed = Time.now.utc - start_time
|
|
32
|
+
skipped = phases - results.keys
|
|
33
|
+
log_tick_complete(state, results, phases, total_elapsed, skipped)
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
tick_number: state.tick_count,
|
|
37
|
+
mode: state.mode,
|
|
38
|
+
phases_executed: results.keys,
|
|
39
|
+
phases_skipped: skipped,
|
|
40
|
+
results: results,
|
|
41
|
+
elapsed: total_elapsed
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def evaluate_mode_transition(signals: [], emergency: nil, dream_complete: false, **) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
46
|
+
state = tick_state
|
|
47
|
+
previous_mode = state.mode
|
|
48
|
+
|
|
49
|
+
# Emergency promotion
|
|
50
|
+
if emergency && Helpers::Constants::EMERGENCY_TRIGGERS.include?(emergency)
|
|
51
|
+
Legion::Logging.warn "[tick] emergency promotion triggered: #{emergency}"
|
|
52
|
+
state.transition_to(:full_active)
|
|
53
|
+
return { transitioned: true, new_mode: :full_active, previous_mode: previous_mode, reason: :emergency }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check signal-based promotions
|
|
57
|
+
max_salience = signals.map { |s| s.is_a?(Hash) ? (s[:salience] || 0.0) : 0.0 }.max || 0.0
|
|
58
|
+
has_human = signals.any? { |s| s.is_a?(Hash) && s[:source_type] == :human_direct }
|
|
59
|
+
|
|
60
|
+
new_mode = case state.mode
|
|
61
|
+
when :dormant
|
|
62
|
+
if signals.any?
|
|
63
|
+
:sentinel
|
|
64
|
+
elsif state.seconds_since_signal >= Helpers::Constants::DREAM_IDLE_THRESHOLD
|
|
65
|
+
:dormant_active
|
|
66
|
+
else
|
|
67
|
+
:dormant
|
|
68
|
+
end
|
|
69
|
+
when :dormant_active
|
|
70
|
+
if max_salience >= Helpers::Constants::HIGH_SALIENCE_THRESHOLD || has_human
|
|
71
|
+
:sentinel
|
|
72
|
+
elsif dream_complete
|
|
73
|
+
:dormant
|
|
74
|
+
else
|
|
75
|
+
:dormant_active
|
|
76
|
+
end
|
|
77
|
+
when :sentinel
|
|
78
|
+
if has_human || max_salience >= Helpers::Constants::HIGH_SALIENCE_THRESHOLD
|
|
79
|
+
:full_active
|
|
80
|
+
elsif state.seconds_since_signal >= Helpers::Constants::SENTINEL_TO_DREAM_THRESHOLD
|
|
81
|
+
:dormant_active
|
|
82
|
+
elsif state.seconds_since_signal >= Helpers::Constants::SENTINEL_TIMEOUT
|
|
83
|
+
:dormant
|
|
84
|
+
else
|
|
85
|
+
:sentinel
|
|
86
|
+
end
|
|
87
|
+
when :full_active
|
|
88
|
+
if state.seconds_since_high_salience >= Helpers::Constants::ACTIVE_TIMEOUT
|
|
89
|
+
:sentinel
|
|
90
|
+
else
|
|
91
|
+
:full_active
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if new_mode == state.mode
|
|
96
|
+
{ transitioned: false, current_mode: state.mode }
|
|
97
|
+
else
|
|
98
|
+
state.transition_to(new_mode)
|
|
99
|
+
Legion::Logging.info "[tick] mode transition: #{previous_mode} -> #{new_mode} (threshold)"
|
|
100
|
+
{ transitioned: true, new_mode: new_mode, previous_mode: previous_mode, reason: :threshold }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def tick_status(**)
|
|
105
|
+
status = tick_state.to_h
|
|
106
|
+
Legion::Logging.debug "[tick] status query: mode=#{status[:mode]} tick_count=#{status[:tick_count]}"
|
|
107
|
+
status
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def set_mode(mode:, **)
|
|
111
|
+
unless Helpers::Constants::MODES.include?(mode)
|
|
112
|
+
Legion::Logging.warn "[tick] invalid mode requested: #{mode}"
|
|
113
|
+
return { error: :invalid_mode, valid_modes: Helpers::Constants::MODES }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
previous = tick_state.mode
|
|
117
|
+
tick_state.transition_to(mode)
|
|
118
|
+
Legion::Logging.info "[tick] mode forced: #{previous} -> #{mode}"
|
|
119
|
+
{ mode: mode }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def run_phases(phases, state, ctx)
|
|
125
|
+
results = {}
|
|
126
|
+
budget = ctx[:budget]
|
|
127
|
+
start_time = ctx[:start_time]
|
|
128
|
+
Legion::Logging.debug "[tick] ##{state.tick_count} running #{phases.size} phases with #{budget}s budget"
|
|
129
|
+
phases.each do |phase|
|
|
130
|
+
elapsed = Time.now.utc - start_time
|
|
131
|
+
if elapsed >= budget
|
|
132
|
+
Legion::Logging.debug "[tick] ##{state.tick_count} budget exhausted at #{elapsed.round(3)}s, skipping remaining phases"
|
|
133
|
+
break
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
result = run_single_phase(phase, ctx[:phase_handlers][phase], state, ctx[:signals], results)
|
|
137
|
+
state.record_phase(phase, result)
|
|
138
|
+
results[phase] = result
|
|
139
|
+
end
|
|
140
|
+
results
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def run_single_phase(phase, handler, state, signals, results)
|
|
144
|
+
phase_start = Time.now.utc
|
|
145
|
+
result = handler ? handler.call(state: state, signals: signals, prior_results: results) : { status: :no_handler }
|
|
146
|
+
phase_elapsed = ((Time.now.utc - phase_start) * 1000).round(1)
|
|
147
|
+
status = result.is_a?(Hash) ? (result[:status] || :ok) : :ok
|
|
148
|
+
Legion::Logging.debug "[tick] ##{state.tick_count} phase=#{phase} status=#{status} (#{phase_elapsed}ms)"
|
|
149
|
+
result
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def log_tick_complete(state, results, phases, total_elapsed, skipped)
|
|
153
|
+
skipped_suffix = skipped.empty? ? '' : " skipped=#{skipped}"
|
|
154
|
+
Legion::Logging.info "[tick] ##{state.tick_count} complete | mode=#{state.mode} " \
|
|
155
|
+
"phases=#{results.size}/#{phases.size} " \
|
|
156
|
+
"elapsed=#{(total_elapsed * 1000).round(1)}ms#{skipped_suffix}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def tick_state
|
|
160
|
+
@tick_state ||= Helpers::State.new
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/tick/version'
|
|
4
|
+
require 'legion/extensions/tick/helpers/constants'
|
|
5
|
+
require 'legion/extensions/tick/helpers/state'
|
|
6
|
+
require 'legion/extensions/tick/runners/orchestrator'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Tick
|
|
11
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Stub the framework actor base class since legionio gem is not available in test
|
|
4
|
+
module Legion
|
|
5
|
+
module Extensions
|
|
6
|
+
module Actors
|
|
7
|
+
class Every # rubocop:disable Lint/EmptyClass
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Intercept the require in the actor file so it doesn't fail
|
|
14
|
+
$LOADED_FEATURES << 'legion/extensions/actors/every'
|
|
15
|
+
|
|
16
|
+
require 'legion/extensions/tick/actors/tick'
|
|
17
|
+
|
|
18
|
+
RSpec.describe Legion::Extensions::Tick::Actor::Tick do
|
|
19
|
+
subject(:actor) { described_class.new }
|
|
20
|
+
|
|
21
|
+
describe '#initialize' do
|
|
22
|
+
context 'when Cortex is NOT defined' do
|
|
23
|
+
before { hide_const('Legion::Extensions::Cortex') }
|
|
24
|
+
|
|
25
|
+
it 'instantiates without error' do
|
|
26
|
+
expect { described_class.new }.not_to raise_error
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'when Cortex IS defined' do
|
|
31
|
+
before { stub_const('Legion::Extensions::Cortex', Module.new) }
|
|
32
|
+
|
|
33
|
+
it 'instantiates without error (returns early, skips super)' do
|
|
34
|
+
expect { described_class.new }.not_to raise_error
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#enabled?' do
|
|
40
|
+
context 'when Cortex is NOT defined' do
|
|
41
|
+
before { hide_const('Legion::Extensions::Cortex') }
|
|
42
|
+
|
|
43
|
+
it 'returns truthy' do
|
|
44
|
+
expect(actor.enabled?).to be_truthy
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context 'when Cortex IS defined' do
|
|
49
|
+
before { stub_const('Legion::Extensions::Cortex', Module.new) }
|
|
50
|
+
|
|
51
|
+
it 'returns falsey' do
|
|
52
|
+
expect(actor.enabled?).to be_falsey
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '#runner_class' do
|
|
58
|
+
it 'returns the Orchestrator module' do
|
|
59
|
+
expect(actor.runner_class).to eq(Legion::Extensions::Tick::Runners::Orchestrator)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe '#runner_function' do
|
|
64
|
+
it 'returns execute_tick' do
|
|
65
|
+
expect(actor.runner_function).to eq('execute_tick')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe '#time' do
|
|
70
|
+
it 'returns 1' do
|
|
71
|
+
expect(actor.time).to eq(1)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe '#run_now?' do
|
|
76
|
+
it 'returns true' do
|
|
77
|
+
expect(actor.run_now?).to be true
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe '#use_runner?' do
|
|
82
|
+
it 'returns false' do
|
|
83
|
+
expect(actor.use_runner?).to be false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe '#check_subtask?' do
|
|
88
|
+
it 'returns false' do
|
|
89
|
+
expect(actor.check_subtask?).to be false
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe '#generate_task?' do
|
|
94
|
+
it 'returns false' do
|
|
95
|
+
expect(actor.generate_task?).to be false
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe '#args' do
|
|
100
|
+
it 'returns a hash with empty signals and phase_handlers' do
|
|
101
|
+
expect(actor.args).to eq({ signals: [], phase_handlers: {} })
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/tick/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Tick::Client do
|
|
6
|
+
it 'responds to orchestrator methods' do
|
|
7
|
+
client = described_class.new
|
|
8
|
+
expect(client).to respond_to(:execute_tick)
|
|
9
|
+
expect(client).to respond_to(:evaluate_mode_transition)
|
|
10
|
+
expect(client).to respond_to(:tick_status)
|
|
11
|
+
expect(client).to respond_to(:set_mode)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'accepts initial mode' do
|
|
15
|
+
client = described_class.new(mode: :sentinel)
|
|
16
|
+
expect(client.tick_status[:mode]).to eq(:sentinel)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'runs a full active tick with all 12 phases' do
|
|
20
|
+
client = described_class.new(mode: :full_active)
|
|
21
|
+
result = client.execute_tick(signals: [{ salience: 0.9, source_type: :human_direct }])
|
|
22
|
+
expect(result[:phases_executed].size).to eq(12)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Tick::Helpers::Constants do
|
|
4
|
+
describe '.phases_for_mode' do
|
|
5
|
+
it 'returns 1 phase for dormant' do
|
|
6
|
+
phases = described_class.phases_for_mode(:dormant)
|
|
7
|
+
expect(phases).to eq([:memory_consolidation])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'returns 5 phases for sentinel' do
|
|
11
|
+
phases = described_class.phases_for_mode(:sentinel)
|
|
12
|
+
expect(phases.size).to eq(5)
|
|
13
|
+
expect(phases).to include(:sensory_processing, :memory_retrieval)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'returns all 12 phases for full_active' do
|
|
17
|
+
phases = described_class.phases_for_mode(:full_active)
|
|
18
|
+
expect(phases.size).to eq(12)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe '.tick_budget' do
|
|
23
|
+
it 'returns 0.2s for dormant' do
|
|
24
|
+
expect(described_class.tick_budget(:dormant)).to eq(0.2)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'returns 0.5s for sentinel' do
|
|
28
|
+
expect(described_class.tick_budget(:sentinel)).to eq(0.5)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'returns 5.0s for full_active' do
|
|
32
|
+
expect(described_class.tick_budget(:full_active)).to eq(5.0)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'defines exactly 12 phases' do
|
|
37
|
+
expect(described_class::PHASES.size).to eq(12)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'defines phase budgets for all active phases' do
|
|
41
|
+
described_class::PHASES.each do |phase|
|
|
42
|
+
expect(described_class::PHASE_BUDGETS).to have_key(phase)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe 'MODES' do
|
|
47
|
+
it 'includes dormant_active' do
|
|
48
|
+
expect(described_class::MODES).to include(:dormant_active)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'has exactly 4 modes' do
|
|
52
|
+
expect(described_class::MODES.size).to eq(4)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe 'DREAM_PHASES' do
|
|
57
|
+
it 'defines 8 dream phases' do
|
|
58
|
+
expect(described_class::DREAM_PHASES.size).to eq(8)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'includes all expected dream phase symbols' do
|
|
62
|
+
expected = %i[memory_audit association_walk contradiction_resolution
|
|
63
|
+
identity_entropy_check agenda_formation consolidation_commit
|
|
64
|
+
dream_reflection dream_narration]
|
|
65
|
+
expect(described_class::DREAM_PHASES).to eq(expected)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe 'MODE_PHASES' do
|
|
70
|
+
it 'maps dormant_active to the 8 dream phases' do
|
|
71
|
+
expect(described_class::MODE_PHASES[:dormant_active]).to eq(described_class::DREAM_PHASES)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe '.tick_budget' do
|
|
76
|
+
it 'returns Float::INFINITY for dormant_active' do
|
|
77
|
+
expect(described_class.tick_budget(:dormant_active)).to eq(Float::INFINITY)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe 'dream thresholds' do
|
|
82
|
+
it 'defines DREAM_IDLE_THRESHOLD as 1800' do
|
|
83
|
+
expect(described_class::DREAM_IDLE_THRESHOLD).to eq(1800)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'defines SENTINEL_TO_DREAM_THRESHOLD as 600' do
|
|
87
|
+
expect(described_class::SENTINEL_TO_DREAM_THRESHOLD).to eq(600)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Tick::Helpers::State do
|
|
4
|
+
let(:state) { described_class.new }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'starts in dormant mode' do
|
|
8
|
+
expect(state.mode).to eq(:dormant)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'starts with zero tick count' do
|
|
12
|
+
expect(state.tick_count).to eq(0)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '#record_signal' do
|
|
17
|
+
it 'updates last_signal_at' do
|
|
18
|
+
state.record_signal
|
|
19
|
+
expect(state.last_signal_at).not_to be_nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'updates last_high_salience_at for high salience signals' do
|
|
23
|
+
state.record_signal(salience: 0.8)
|
|
24
|
+
expect(state.last_high_salience_at).not_to be_nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'does not update last_high_salience_at for low salience' do
|
|
28
|
+
state.record_signal(salience: 0.3)
|
|
29
|
+
expect(state.last_high_salience_at).to be_nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#increment_tick' do
|
|
34
|
+
it 'increments the tick count' do
|
|
35
|
+
state.increment_tick
|
|
36
|
+
expect(state.tick_count).to eq(1)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'clears phase results' do
|
|
40
|
+
state.record_phase(:test, { result: true })
|
|
41
|
+
state.increment_tick
|
|
42
|
+
expect(state.phase_results).to be_empty
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '#transition_to' do
|
|
47
|
+
it 'changes mode' do
|
|
48
|
+
state.transition_to(:sentinel)
|
|
49
|
+
expect(state.mode).to eq(:sentinel)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'tracks mode history' do
|
|
53
|
+
state.transition_to(:sentinel)
|
|
54
|
+
state.transition_to(:full_active)
|
|
55
|
+
expect(state.mode_history.size).to eq(3) # initial + 2 transitions
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'does not add duplicate entries for same mode' do
|
|
59
|
+
state.transition_to(:dormant) # already dormant
|
|
60
|
+
expect(state.mode_history.size).to eq(1)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe '#to_h' do
|
|
65
|
+
it 'returns state as hash' do
|
|
66
|
+
h = state.to_h
|
|
67
|
+
expect(h).to have_key(:mode)
|
|
68
|
+
expect(h).to have_key(:tick_count)
|
|
69
|
+
expect(h).to have_key(:phases_completed)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/tick/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Tick::Runners::Orchestrator do
|
|
6
|
+
let(:client) { Legion::Extensions::Tick::Client.new }
|
|
7
|
+
|
|
8
|
+
describe '#execute_tick' do
|
|
9
|
+
before do
|
|
10
|
+
allow(client.send(:tick_state)).to receive(:seconds_since_signal).and_return(60.0)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'executes phases for current mode' do
|
|
14
|
+
result = client.execute_tick
|
|
15
|
+
expect(result[:mode]).to eq(:dormant)
|
|
16
|
+
expect(result[:phases_executed]).to include(:memory_consolidation)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'increments tick number' do
|
|
20
|
+
r1 = client.execute_tick
|
|
21
|
+
r2 = client.execute_tick
|
|
22
|
+
expect(r2[:tick_number]).to eq(r1[:tick_number] + 1)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'uses phase handlers when provided' do
|
|
26
|
+
handler_called = false
|
|
27
|
+
handlers = {
|
|
28
|
+
memory_consolidation: lambda { |**|
|
|
29
|
+
handler_called = true
|
|
30
|
+
{ status: :ok }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
client.execute_tick(phase_handlers: handlers)
|
|
34
|
+
expect(handler_called).to be true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'reports no_handler for unhandled phases' do
|
|
38
|
+
result = client.execute_tick
|
|
39
|
+
expect(result[:results][:memory_consolidation][:status]).to eq(:no_handler)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'promotes to sentinel on incoming signals' do
|
|
43
|
+
client.execute_tick(signals: [{ salience: 0.3 }])
|
|
44
|
+
result = client.execute_tick(signals: [{ salience: 0.3 }])
|
|
45
|
+
expect(result[:mode]).to eq(:sentinel)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe '#evaluate_mode_transition' do
|
|
50
|
+
it 'promotes dormant to sentinel on any signal' do
|
|
51
|
+
result = client.evaluate_mode_transition(signals: [{ salience: 0.1 }])
|
|
52
|
+
expect(result[:transitioned]).to be true
|
|
53
|
+
expect(result[:new_mode]).to eq(:sentinel)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'promotes sentinel to full_active on high salience' do
|
|
57
|
+
client.set_mode(mode: :sentinel)
|
|
58
|
+
result = client.evaluate_mode_transition(signals: [{ salience: 0.9 }])
|
|
59
|
+
expect(result[:transitioned]).to be true
|
|
60
|
+
expect(result[:new_mode]).to eq(:full_active)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'promotes sentinel to full_active on human interaction' do
|
|
64
|
+
client.set_mode(mode: :sentinel)
|
|
65
|
+
result = client.evaluate_mode_transition(signals: [{ source_type: :human_direct, salience: 0.3 }])
|
|
66
|
+
expect(result[:transitioned]).to be true
|
|
67
|
+
expect(result[:new_mode]).to eq(:full_active)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'promotes to full_active on emergency' do
|
|
71
|
+
result = client.evaluate_mode_transition(emergency: :firmware_violation)
|
|
72
|
+
expect(result[:transitioned]).to be true
|
|
73
|
+
expect(result[:new_mode]).to eq(:full_active)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'does not transition without trigger when recently active' do
|
|
77
|
+
state = client.send(:tick_state)
|
|
78
|
+
allow(state).to receive(:seconds_since_signal).and_return(60.0)
|
|
79
|
+
result = client.evaluate_mode_transition
|
|
80
|
+
expect(result[:transitioned]).to be false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'dormant_active transitions' do
|
|
84
|
+
it 'transitions dormant -> dormant_active after DREAM_IDLE_THRESHOLD with no signals' do
|
|
85
|
+
state = client.send(:tick_state)
|
|
86
|
+
allow(state).to receive(:seconds_since_signal).and_return(1801.0)
|
|
87
|
+
result = client.evaluate_mode_transition
|
|
88
|
+
expect(result[:transitioned]).to be true
|
|
89
|
+
expect(result[:new_mode]).to eq(:dormant_active)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'transitions dormant -> sentinel when signals arrive (not dormant_active)' do
|
|
93
|
+
state = client.send(:tick_state)
|
|
94
|
+
allow(state).to receive(:seconds_since_signal).and_return(1801.0)
|
|
95
|
+
result = client.evaluate_mode_transition(signals: [{ salience: 0.3 }])
|
|
96
|
+
expect(result[:transitioned]).to be true
|
|
97
|
+
expect(result[:new_mode]).to eq(:sentinel)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'transitions dormant_active -> sentinel on high-salience signal' do
|
|
101
|
+
client.set_mode(mode: :dormant_active)
|
|
102
|
+
result = client.evaluate_mode_transition(signals: [{ salience: 0.9 }])
|
|
103
|
+
expect(result[:transitioned]).to be true
|
|
104
|
+
expect(result[:new_mode]).to eq(:sentinel)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'transitions dormant_active -> dormant when dream_complete: true with no signals' do
|
|
108
|
+
client.set_mode(mode: :dormant_active)
|
|
109
|
+
result = client.evaluate_mode_transition(dream_complete: true)
|
|
110
|
+
expect(result[:transitioned]).to be true
|
|
111
|
+
expect(result[:new_mode]).to eq(:dormant)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'stays dormant_active when no signals and dream not complete' do
|
|
115
|
+
client.set_mode(mode: :dormant_active)
|
|
116
|
+
result = client.evaluate_mode_transition
|
|
117
|
+
expect(result[:transitioned]).to be false
|
|
118
|
+
expect(result[:current_mode]).to eq(:dormant_active)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context 'sentinel -> dormant_active' do
|
|
123
|
+
it 'transitions sentinel -> dormant_active after SENTINEL_TO_DREAM_THRESHOLD with no signals' do
|
|
124
|
+
client.set_mode(mode: :sentinel)
|
|
125
|
+
state = client.send(:tick_state)
|
|
126
|
+
allow(state).to receive(:seconds_since_signal).and_return(601.0)
|
|
127
|
+
result = client.evaluate_mode_transition
|
|
128
|
+
expect(result[:transitioned]).to be true
|
|
129
|
+
expect(result[:new_mode]).to eq(:dormant_active)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '#set_mode' do
|
|
135
|
+
it 'sets valid mode' do
|
|
136
|
+
result = client.set_mode(mode: :full_active)
|
|
137
|
+
expect(result[:mode]).to eq(:full_active)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'rejects invalid mode' do
|
|
141
|
+
result = client.set_mode(mode: :invalid)
|
|
142
|
+
expect(result[:error]).to eq(:invalid_mode)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
describe '#tick_status' do
|
|
147
|
+
it 'returns current state' do
|
|
148
|
+
status = client.tick_status
|
|
149
|
+
expect(status[:mode]).to eq(:dormant)
|
|
150
|
+
expect(status[:tick_count]).to eq(0)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Logging
|
|
7
|
+
def self.debug(_msg); end
|
|
8
|
+
def self.info(_msg); end
|
|
9
|
+
def self.warn(_msg); end
|
|
10
|
+
def self.error(_msg); end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require 'legion/extensions/tick'
|
|
15
|
+
|
|
16
|
+
RSpec.configure do |config|
|
|
17
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
|
18
|
+
config.disable_monkey_patching!
|
|
19
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-tick
|
|
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: Atomic cognitive processing cycle (11 phases, 3 modes) for brain-modeled
|
|
27
|
+
agentic AI
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- Gemfile
|
|
35
|
+
- lex-tick.gemspec
|
|
36
|
+
- lib/legion/extensions/tick.rb
|
|
37
|
+
- lib/legion/extensions/tick/actors/tick.rb
|
|
38
|
+
- lib/legion/extensions/tick/client.rb
|
|
39
|
+
- lib/legion/extensions/tick/helpers/constants.rb
|
|
40
|
+
- lib/legion/extensions/tick/helpers/state.rb
|
|
41
|
+
- lib/legion/extensions/tick/runners/orchestrator.rb
|
|
42
|
+
- lib/legion/extensions/tick/version.rb
|
|
43
|
+
- spec/legion/extensions/tick/actors/tick_spec.rb
|
|
44
|
+
- spec/legion/extensions/tick/client_spec.rb
|
|
45
|
+
- spec/legion/extensions/tick/helpers/constants_spec.rb
|
|
46
|
+
- spec/legion/extensions/tick/helpers/state_spec.rb
|
|
47
|
+
- spec/legion/extensions/tick/runners/orchestrator_spec.rb
|
|
48
|
+
- spec/spec_helper.rb
|
|
49
|
+
homepage: https://github.com/LegionIO/lex-tick
|
|
50
|
+
licenses:
|
|
51
|
+
- MIT
|
|
52
|
+
metadata:
|
|
53
|
+
homepage_uri: https://github.com/LegionIO/lex-tick
|
|
54
|
+
source_code_uri: https://github.com/LegionIO/lex-tick
|
|
55
|
+
documentation_uri: https://github.com/LegionIO/lex-tick
|
|
56
|
+
changelog_uri: https://github.com/LegionIO/lex-tick
|
|
57
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-tick/issues
|
|
58
|
+
rubygems_mfa_required: 'true'
|
|
59
|
+
rdoc_options: []
|
|
60
|
+
require_paths:
|
|
61
|
+
- lib
|
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.4'
|
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '0'
|
|
72
|
+
requirements: []
|
|
73
|
+
rubygems_version: 3.6.9
|
|
74
|
+
specification_version: 4
|
|
75
|
+
summary: LEX Tick
|
|
76
|
+
test_files: []
|