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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77ee2421158cb9a62de788b3ccfb8333a133a51205a2de77cb4676717fb03f5f
4
- data.tar.gz: c91dd8d5e753495d06a7bab45305a0c6bbb8708d0ac3c465aeacc67bd7ad8ed9
3
+ metadata.gz: 2505b31b1c5c73daeaae75d58a8c90334a1737cb6404bbdc5dfe16a2ad725487
4
+ data.tar.gz: 56c698c91719584872f5d5b3363304554520810853e507bf9978441d1cc4f1d6
5
5
  SHA512:
6
- metadata.gz: 33885d264cb29034efa973f6a501bc55ee1ad29556c70dcf5e22b88604f618f467143b2617d66cbb4a03b5b17b790a636ab35f9b04f8578443884b9a28fb8ddf
7
- data.tar.gz: 9bc2bdd0c4f97a573d637bacdb495557f3aea405a19f7830eeb381f41af4a6e6fec3e5af36b9c0b668709c09374a105d014e355972660cc18ce143f4e7e9d6e7
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
- attr_reader :bootstrap
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
- @started_at = Time.now.utc
20
- @calibration_state = :imprinting
21
- save_to_local
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
- @firmware_loaded = true
26
- save_to_local
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
- @observation_count += 1
31
- check_calibration_progress
32
- save_to_local
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, observations: @observation_count)
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: @firmware_loaded,
48
- imprint_active: imprint_active?,
49
- imprint_progress: Imprint.imprint_progress(@started_at),
50
- observation_count: @observation_count,
51
- calibration_state: @calibration_state,
52
- current_layer: 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
- if @observation_count >= Imprint::IMPRINT_ENTROPY_BASELINE && !imprint_active?
60
- @calibration_state = :calibrated
61
- elsif @observation_count >= Imprint::IMPRINT_ENTROPY_BASELINE
62
- @calibration_state = :baseline_established
63
- end
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
- # Imprint window parameters
12
- IMPRINT_DURATION = 7 * 86_400 # 7 days
13
- IMPRINT_MULTIPLIER = 3.0 # consolidation rate multiplier during imprint
14
- IMPRINT_CONSENT_TIER = :consult # conservative consent during imprint
15
- IMPRINT_ENTROPY_BASELINE = 50 # minimum observations before entropy is meaningful
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
- (Time.now.utc - started_at) < IMPRINT_DURATION
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
- elapsed = Time.now.utc - started_at
33
- [elapsed / IMPRINT_DURATION.to_f, 1.0].min
32
+ total = IMPRINT_ENTROPY_BASELINE + IMPRINT_RAMP_LENGTH
33
+ [observation_count.to_f / total, 1.0].min
34
34
  end
35
35
 
36
- def current_layer(started_at, observations:)
37
- if (observations < IMPRINT_ENTROPY_BASELINE && imprint_active?(started_at)) ||
38
- imprint_active?(started_at)
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
- dur = Helpers::Imprint::IMPRINT_DURATION
15
- mul = Helpers::Imprint::IMPRINT_MULTIPLIER
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: true,
20
- imprint_duration: Helpers::Imprint::IMPRINT_DURATION,
21
- multiplier: Helpers::Imprint::IMPRINT_MULTIPLIER,
22
- consent_tier: Helpers::Imprint::IMPRINT_CONSENT_TIER
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: bootstrap.observation_count,
32
- calibration_state: bootstrap.calibration_state,
33
- current_layer: bootstrap.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
- @bootstrap ||= Helpers::Bootstrap.new
59
+ Helpers::Bootstrap.instance
60
60
  end
61
61
  end
62
62
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Coldstart
6
- VERSION = '0.1.3'
6
+ VERSION = '0.1.5'
7
7
  end
8
8
  end
9
9
  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(:baseline_established)
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 during imprint' do
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 7-day window continues from the original time' do
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 within the 7-day window' do
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 baseline_established calibrated].each do |state|
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,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-coldstart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity