lex-coldstart 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/legion/extensions/coldstart/client.rb +3 -5
- data/lib/legion/extensions/coldstart/helpers/bootstrap.rb +55 -22
- data/lib/legion/extensions/coldstart/helpers/imprint.rb +28 -13
- data/lib/legion/extensions/coldstart/runners/coldstart.rb +15 -15
- data/lib/legion/extensions/coldstart/version.rb +1 -1
- data/spec/legion/extensions/coldstart/runners/coldstart_spec.rb +40 -3
- data/spec/local_persistence_spec.rb +33 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2505b31b1c5c73daeaae75d58a8c90334a1737cb6404bbdc5dfe16a2ad725487
|
|
4
|
+
data.tar.gz: 56c698c91719584872f5d5b3363304554520810853e507bf9978441d1cc4f1d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 025cbbf8e9199150819ba6439f0ba80ec65d15276bc9b729574f146713d09181bb80fd9a4df3e18c0da9503fd0b6752817fda1c04a1be0d5a7c255e902a9049c
|
|
7
|
+
data.tar.gz: 54fa4158dcabdee1125bcee466020a4cd92bb5af86e554671fc285d65cdda2c90548c87fdf25b4b02d67f3af0ca579f566193b2512e5f9a0254a930b595e943f
|
|
@@ -10,13 +10,11 @@ module Legion
|
|
|
10
10
|
class Client
|
|
11
11
|
include Runners::Coldstart
|
|
12
12
|
|
|
13
|
-
def initialize(**)
|
|
14
|
-
@bootstrap = Helpers::Bootstrap.new
|
|
15
|
-
end
|
|
16
|
-
|
|
17
13
|
private
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
def bootstrap
|
|
16
|
+
Helpers::Bootstrap.instance
|
|
17
|
+
end
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
20
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'monitor'
|
|
4
|
+
|
|
3
5
|
module Legion
|
|
4
6
|
module Extensions
|
|
5
7
|
module Coldstart
|
|
@@ -7,6 +9,21 @@ module Legion
|
|
|
7
9
|
class Bootstrap
|
|
8
10
|
attr_reader :started_at, :observation_count, :firmware_loaded, :calibration_state
|
|
9
11
|
|
|
12
|
+
LOCK = Monitor.new
|
|
13
|
+
@instance = nil
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def instance
|
|
17
|
+
LOCK.synchronize do
|
|
18
|
+
@instance ||= new
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reset!
|
|
23
|
+
LOCK.synchronize { @instance = nil }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
10
27
|
def initialize
|
|
11
28
|
@started_at = nil
|
|
12
29
|
@observation_count = 0
|
|
@@ -15,52 +32,68 @@ module Legion
|
|
|
15
32
|
load_from_local
|
|
16
33
|
end
|
|
17
34
|
|
|
18
|
-
def begin_imprint
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
def begin_imprint(force: false)
|
|
36
|
+
LOCK.synchronize do
|
|
37
|
+
return if @started_at && !force
|
|
38
|
+
|
|
39
|
+
@started_at = Time.now.utc
|
|
40
|
+
@observation_count = 0 if force
|
|
41
|
+
@calibration_state = :imprinting
|
|
42
|
+
save_to_local
|
|
43
|
+
end
|
|
22
44
|
end
|
|
23
45
|
|
|
24
46
|
def load_firmware
|
|
25
|
-
|
|
26
|
-
|
|
47
|
+
LOCK.synchronize do
|
|
48
|
+
@firmware_loaded = true
|
|
49
|
+
save_to_local
|
|
50
|
+
end
|
|
27
51
|
end
|
|
28
52
|
|
|
29
53
|
def record_observation
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
LOCK.synchronize do
|
|
55
|
+
@observation_count += 1
|
|
56
|
+
check_calibration_progress
|
|
57
|
+
save_to_local
|
|
58
|
+
end
|
|
33
59
|
end
|
|
34
60
|
|
|
35
61
|
def imprint_active?
|
|
36
|
-
Imprint.imprint_active?(@started_at)
|
|
62
|
+
Imprint.imprint_active?(started_at: @started_at, observation_count: @observation_count)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def current_multiplier
|
|
66
|
+
Imprint.current_multiplier(started_at: @started_at, observation_count: @observation_count)
|
|
37
67
|
end
|
|
38
68
|
|
|
39
69
|
def current_layer
|
|
40
70
|
return :firmware unless @firmware_loaded
|
|
41
71
|
|
|
42
|
-
Imprint.current_layer(@started_at,
|
|
72
|
+
Imprint.current_layer(started_at: @started_at, observation_count: @observation_count)
|
|
43
73
|
end
|
|
44
74
|
|
|
45
75
|
def progress
|
|
46
76
|
{
|
|
47
|
-
firmware_loaded:
|
|
48
|
-
imprint_active:
|
|
49
|
-
imprint_progress:
|
|
50
|
-
observation_count:
|
|
51
|
-
calibration_state:
|
|
52
|
-
current_layer:
|
|
77
|
+
firmware_loaded: @firmware_loaded,
|
|
78
|
+
imprint_active: imprint_active?,
|
|
79
|
+
imprint_progress: Imprint.imprint_progress(started_at: @started_at, observation_count: @observation_count),
|
|
80
|
+
observation_count: @observation_count,
|
|
81
|
+
calibration_state: @calibration_state,
|
|
82
|
+
current_layer: current_layer,
|
|
83
|
+
current_multiplier: current_multiplier
|
|
53
84
|
}
|
|
54
85
|
end
|
|
55
86
|
|
|
56
87
|
private
|
|
57
88
|
|
|
58
89
|
def check_calibration_progress
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
90
|
+
return unless @observation_count >= Imprint::IMPRINT_ENTROPY_BASELINE
|
|
91
|
+
|
|
92
|
+
@calibration_state = if imprint_active?
|
|
93
|
+
:consolidating
|
|
94
|
+
else
|
|
95
|
+
:calibrated
|
|
96
|
+
end
|
|
64
97
|
end
|
|
65
98
|
|
|
66
99
|
def save_to_local
|
|
@@ -8,11 +8,11 @@ module Legion
|
|
|
8
8
|
# Three learning layers (spec: cold-start-spec.md)
|
|
9
9
|
LAYERS = %i[firmware imprint_window continuous_learning].freeze
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
# Interaction-denominated imprint parameters
|
|
12
|
+
IMPRINT_ENTROPY_BASELINE = 50 # observations before window begins narrowing
|
|
13
|
+
IMPRINT_RAMP_LENGTH = 50 # additional observations over which multiplier ramps down
|
|
14
|
+
IMPRINT_MAX_MULTIPLIER = 3.0 # consolidation rate multiplier at full imprint
|
|
15
|
+
IMPRINT_SAFETY_BOUND = 30 * 86_400 # wall-clock safety: warn if window open >30 days
|
|
16
16
|
|
|
17
17
|
# Self-play bootstrap parameters
|
|
18
18
|
SELF_PLAY_ITERATIONS = 100
|
|
@@ -20,27 +20,42 @@ module Legion
|
|
|
20
20
|
|
|
21
21
|
module_function
|
|
22
22
|
|
|
23
|
-
def imprint_active?(started_at)
|
|
23
|
+
def imprint_active?(started_at:, observation_count:)
|
|
24
24
|
return false unless started_at
|
|
25
25
|
|
|
26
|
-
(
|
|
26
|
+
observation_count < (IMPRINT_ENTROPY_BASELINE + IMPRINT_RAMP_LENGTH)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def imprint_progress(started_at)
|
|
29
|
+
def imprint_progress(started_at:, observation_count:)
|
|
30
30
|
return 1.0 unless started_at
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
[
|
|
32
|
+
total = IMPRINT_ENTROPY_BASELINE + IMPRINT_RAMP_LENGTH
|
|
33
|
+
[observation_count.to_f / total, 1.0].min
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
def current_multiplier(started_at:, observation_count:)
|
|
37
|
+
return 1.0 unless started_at
|
|
38
|
+
return IMPRINT_MAX_MULTIPLIER if observation_count < IMPRINT_ENTROPY_BASELINE
|
|
39
|
+
|
|
40
|
+
ramp_progress = [(observation_count - IMPRINT_ENTROPY_BASELINE).to_f / IMPRINT_RAMP_LENGTH, 1.0].min
|
|
41
|
+
IMPRINT_MAX_MULTIPLIER - ((IMPRINT_MAX_MULTIPLIER - 1.0) * ramp_progress)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def current_layer(started_at:, observation_count:)
|
|
45
|
+
return :continuous_learning unless started_at
|
|
46
|
+
|
|
47
|
+
if observation_count < (IMPRINT_ENTROPY_BASELINE + IMPRINT_RAMP_LENGTH)
|
|
39
48
|
:imprint_window
|
|
40
49
|
else
|
|
41
50
|
:continuous_learning
|
|
42
51
|
end
|
|
43
52
|
end
|
|
53
|
+
|
|
54
|
+
def safety_bound_exceeded?(started_at:)
|
|
55
|
+
return false unless started_at
|
|
56
|
+
|
|
57
|
+
(Time.now.utc - started_at) > IMPRINT_SAFETY_BOUND
|
|
58
|
+
end
|
|
44
59
|
end
|
|
45
60
|
end
|
|
46
61
|
end
|
|
@@ -8,18 +8,17 @@ module Legion
|
|
|
8
8
|
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
|
|
9
9
|
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
10
10
|
|
|
11
|
-
def begin_imprint(**)
|
|
11
|
+
def begin_imprint(force: false, **)
|
|
12
12
|
bootstrap.load_firmware
|
|
13
|
-
bootstrap.begin_imprint
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
tier = Helpers::Imprint::IMPRINT_CONSENT_TIER
|
|
17
|
-
log.info "[coldstart] imprint begun: duration=#{dur}s multiplier=#{mul}x consent=#{tier}"
|
|
13
|
+
bootstrap.begin_imprint(force: force)
|
|
14
|
+
mul = bootstrap.current_multiplier
|
|
15
|
+
log.info "[coldstart] imprint begun: force=#{force} multiplier=#{mul}x"
|
|
18
16
|
{
|
|
19
|
-
started:
|
|
20
|
-
|
|
21
|
-
multiplier:
|
|
22
|
-
|
|
17
|
+
started: true,
|
|
18
|
+
imprint_active: bootstrap.imprint_active?,
|
|
19
|
+
multiplier: mul,
|
|
20
|
+
observation_count: bootstrap.observation_count,
|
|
21
|
+
calibration_state: bootstrap.calibration_state
|
|
23
22
|
}
|
|
24
23
|
end
|
|
25
24
|
|
|
@@ -28,9 +27,10 @@ module Legion
|
|
|
28
27
|
log.debug "[coldstart] observation: count=#{bootstrap.observation_count} " \
|
|
29
28
|
"calibration=#{bootstrap.calibration_state} layer=#{bootstrap.current_layer}"
|
|
30
29
|
{
|
|
31
|
-
observation_count:
|
|
32
|
-
calibration_state:
|
|
33
|
-
current_layer:
|
|
30
|
+
observation_count: bootstrap.observation_count,
|
|
31
|
+
calibration_state: bootstrap.calibration_state,
|
|
32
|
+
current_layer: bootstrap.current_layer,
|
|
33
|
+
current_multiplier: bootstrap.current_multiplier
|
|
34
34
|
}
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -47,8 +47,8 @@ module Legion
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def current_multiplier(**)
|
|
50
|
+
multiplier = bootstrap.current_multiplier
|
|
50
51
|
active = bootstrap.imprint_active?
|
|
51
|
-
multiplier = active ? Helpers::Imprint::IMPRINT_MULTIPLIER : 1.0
|
|
52
52
|
log.debug "[coldstart] multiplier=#{multiplier} imprint_active=#{active}"
|
|
53
53
|
{ multiplier: multiplier, imprint_active: active }
|
|
54
54
|
end
|
|
@@ -56,7 +56,7 @@ module Legion
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
58
|
def bootstrap
|
|
59
|
-
|
|
59
|
+
Helpers::Bootstrap.instance
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
end
|
|
@@ -5,12 +5,22 @@ require 'legion/extensions/coldstart/client'
|
|
|
5
5
|
RSpec.describe Legion::Extensions::Coldstart::Runners::Coldstart do
|
|
6
6
|
let(:client) { Legion::Extensions::Coldstart::Client.new }
|
|
7
7
|
|
|
8
|
+
before { Legion::Extensions::Coldstart::Helpers::Bootstrap.reset! }
|
|
9
|
+
|
|
8
10
|
describe '#begin_imprint' do
|
|
9
11
|
it 'starts the imprint window' do
|
|
10
12
|
result = client.begin_imprint
|
|
11
13
|
expect(result[:started]).to be true
|
|
12
14
|
expect(result[:multiplier]).to eq(3.0)
|
|
13
15
|
end
|
|
16
|
+
|
|
17
|
+
it 'supports force restart' do
|
|
18
|
+
client.begin_imprint
|
|
19
|
+
5.times { client.record_observation }
|
|
20
|
+
result = client.begin_imprint(force: true)
|
|
21
|
+
expect(result[:started]).to be true
|
|
22
|
+
expect(result[:observation_count]).to eq(0)
|
|
23
|
+
end
|
|
14
24
|
end
|
|
15
25
|
|
|
16
26
|
describe '#record_observation' do
|
|
@@ -20,11 +30,19 @@ RSpec.describe Legion::Extensions::Coldstart::Runners::Coldstart do
|
|
|
20
30
|
expect(result[:observation_count]).to eq(1)
|
|
21
31
|
end
|
|
22
32
|
|
|
23
|
-
it 'transitions calibration state at baseline threshold' do
|
|
33
|
+
it 'transitions calibration state to consolidating at baseline threshold' do
|
|
24
34
|
client.begin_imprint
|
|
25
35
|
50.times { client.record_observation }
|
|
26
36
|
result = client.record_observation
|
|
27
|
-
expect(result[:calibration_state]).to eq(:
|
|
37
|
+
expect(result[:calibration_state]).to eq(:consolidating)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'returns current multiplier that ramps down after baseline' do
|
|
41
|
+
client.begin_imprint
|
|
42
|
+
51.times { client.record_observation }
|
|
43
|
+
result = client.current_multiplier
|
|
44
|
+
expect(result[:multiplier]).to be < 3.0
|
|
45
|
+
expect(result[:multiplier]).to be > 1.0
|
|
28
46
|
end
|
|
29
47
|
end
|
|
30
48
|
|
|
@@ -49,6 +67,13 @@ RSpec.describe Legion::Extensions::Coldstart::Runners::Coldstart do
|
|
|
49
67
|
result = client.imprint_active?
|
|
50
68
|
expect(result[:active]).to be true
|
|
51
69
|
end
|
|
70
|
+
|
|
71
|
+
it 'returns false after full observation ramp completes' do
|
|
72
|
+
client.begin_imprint
|
|
73
|
+
100.times { client.record_observation }
|
|
74
|
+
result = client.imprint_active?
|
|
75
|
+
expect(result[:active]).to be false
|
|
76
|
+
end
|
|
52
77
|
end
|
|
53
78
|
|
|
54
79
|
describe '#current_multiplier' do
|
|
@@ -57,10 +82,22 @@ RSpec.describe Legion::Extensions::Coldstart::Runners::Coldstart do
|
|
|
57
82
|
expect(result[:multiplier]).to eq(1.0)
|
|
58
83
|
end
|
|
59
84
|
|
|
60
|
-
it 'returns 3.0
|
|
85
|
+
it 'returns 3.0 at start of imprint' do
|
|
61
86
|
client.begin_imprint
|
|
62
87
|
result = client.current_multiplier
|
|
63
88
|
expect(result[:multiplier]).to eq(3.0)
|
|
64
89
|
end
|
|
90
|
+
|
|
91
|
+
it 'ramps from 3.0 to 1.0 over the tail' do
|
|
92
|
+
client.begin_imprint
|
|
93
|
+
75.times { client.record_observation }
|
|
94
|
+
mid_result = client.current_multiplier
|
|
95
|
+
expect(mid_result[:multiplier]).to be < 3.0
|
|
96
|
+
expect(mid_result[:multiplier]).to be > 1.0
|
|
97
|
+
|
|
98
|
+
25.times { client.record_observation }
|
|
99
|
+
end_result = client.current_multiplier
|
|
100
|
+
expect(end_result[:multiplier]).to eq(1.0)
|
|
101
|
+
end
|
|
65
102
|
end
|
|
66
103
|
end
|
|
@@ -13,6 +13,7 @@ RSpec.describe 'lex-coldstart local persistence' do
|
|
|
13
13
|
before do
|
|
14
14
|
Sequel::TimestampMigrator.new(db, migration_path).run
|
|
15
15
|
stub_const('Legion::Data::Local', local_mod)
|
|
16
|
+
Legion::Extensions::Coldstart::Helpers::Bootstrap.reset!
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
let(:local_mod) do
|
|
@@ -24,6 +25,7 @@ RSpec.describe 'lex-coldstart local persistence' do
|
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def fresh_bootstrap
|
|
28
|
+
Legion::Extensions::Coldstart::Helpers::Bootstrap.reset!
|
|
27
29
|
Legion::Extensions::Coldstart::Helpers::Bootstrap.new
|
|
28
30
|
end
|
|
29
31
|
|
|
@@ -52,7 +54,7 @@ RSpec.describe 'lex-coldstart local persistence' do
|
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
describe 'started_at preservation (imprint window survives restarts)' do
|
|
55
|
-
it 'restores started_at so the
|
|
57
|
+
it 'restores started_at so the window continues from the original time' do
|
|
56
58
|
b = fresh_bootstrap
|
|
57
59
|
b.begin_imprint
|
|
58
60
|
original_started_at = b.started_at
|
|
@@ -62,7 +64,7 @@ RSpec.describe 'lex-coldstart local persistence' do
|
|
|
62
64
|
expect(b2.started_at.to_i).to be_within(1).of(original_started_at.to_i)
|
|
63
65
|
end
|
|
64
66
|
|
|
65
|
-
it 'reports imprint still active after reload
|
|
67
|
+
it 'reports imprint still active after reload when under baseline' do
|
|
66
68
|
b = fresh_bootstrap
|
|
67
69
|
b.begin_imprint
|
|
68
70
|
b2 = fresh_bootstrap
|
|
@@ -75,13 +77,40 @@ RSpec.describe 'lex-coldstart local persistence' do
|
|
|
75
77
|
b2 = fresh_bootstrap
|
|
76
78
|
expect(b2.started_at).not_to be_nil
|
|
77
79
|
end
|
|
80
|
+
|
|
81
|
+
it 'does not reset started_at when begin_imprint is called again without force' do
|
|
82
|
+
first_time = Time.utc(2026, 4, 2, 12, 0, 0)
|
|
83
|
+
second_time = first_time + 60
|
|
84
|
+
allow(Time).to receive(:now).and_return(first_time, second_time)
|
|
85
|
+
|
|
86
|
+
b = fresh_bootstrap
|
|
87
|
+
b.begin_imprint
|
|
88
|
+
original_started_at = b.started_at
|
|
89
|
+
|
|
90
|
+
b.begin_imprint
|
|
91
|
+
|
|
92
|
+
expect(b.started_at.to_i).to eq(original_started_at.to_i)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'resets started_at when begin_imprint is called with force: true' do
|
|
96
|
+
first_time = Time.utc(2026, 4, 2, 12, 0, 0)
|
|
97
|
+
second_time = first_time + 60
|
|
98
|
+
allow(Time).to receive(:now).and_return(first_time, second_time)
|
|
99
|
+
|
|
100
|
+
b = fresh_bootstrap
|
|
101
|
+
b.begin_imprint
|
|
102
|
+
original_started_at = b.started_at
|
|
103
|
+
|
|
104
|
+
b.begin_imprint(force: true)
|
|
105
|
+
|
|
106
|
+
expect(b.started_at.to_i).to be > original_started_at.to_i
|
|
107
|
+
end
|
|
78
108
|
end
|
|
79
109
|
|
|
80
110
|
describe 'calibration_state symbol round-trip' do
|
|
81
|
-
%i[not_started imprinting
|
|
111
|
+
%i[not_started imprinting consolidating calibrated].each do |state|
|
|
82
112
|
it "persists and restores :#{state}" do
|
|
83
113
|
fresh_bootstrap
|
|
84
|
-
# Force the state directly through the DB to test all symbol variants
|
|
85
114
|
db[:bootstrap_state].where(id: 1).delete
|
|
86
115
|
db[:bootstrap_state].insert(
|
|
87
116
|
id: 1,
|