lex-coldstart 0.1.4 → 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: d28431932d0756af6a589d30ed7e2046bd519140cfbf78de49f69ce3acc3d551
4
- data.tar.gz: 28cff73b6cd7cc2f0fa15c1376c8d618caeae322d7763390ac261a7fa493966d
3
+ metadata.gz: 2505b31b1c5c73daeaae75d58a8c90334a1737cb6404bbdc5dfe16a2ad725487
4
+ data.tar.gz: 56c698c91719584872f5d5b3363304554520810853e507bf9978441d1cc4f1d6
5
5
  SHA512:
6
- metadata.gz: 9616d3e437ac7c7a7850bca1e02ebc38e3c1c61af351f2c2c44e3a683cdbc2a845b71291b314252cbae8a034e0d90f584ce2c4723a920717cd9a5a5fe10f9242
7
- data.tar.gz: 07740be2f6c79f85948ef0617cdb750c477478fe436372ed421accb1213004f7d4788b9d7c964ca32aeb1dbfeb8287cdcaafa49873ab91f94ca9150cb656a0f8
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
@@ -16,53 +33,67 @@ module Legion
16
33
  end
17
34
 
18
35
  def begin_imprint(force: false)
19
- return if @started_at && !force
36
+ LOCK.synchronize do
37
+ return if @started_at && !force
20
38
 
21
- @started_at = Time.now.utc
22
- @calibration_state = :imprinting
23
- save_to_local
39
+ @started_at = Time.now.utc
40
+ @observation_count = 0 if force
41
+ @calibration_state = :imprinting
42
+ save_to_local
43
+ end
24
44
  end
25
45
 
26
46
  def load_firmware
27
- @firmware_loaded = true
28
- save_to_local
47
+ LOCK.synchronize do
48
+ @firmware_loaded = true
49
+ save_to_local
50
+ end
29
51
  end
30
52
 
31
53
  def record_observation
32
- @observation_count += 1
33
- check_calibration_progress
34
- save_to_local
54
+ LOCK.synchronize do
55
+ @observation_count += 1
56
+ check_calibration_progress
57
+ save_to_local
58
+ end
35
59
  end
36
60
 
37
61
  def imprint_active?
38
- 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)
39
67
  end
40
68
 
41
69
  def current_layer
42
70
  return :firmware unless @firmware_loaded
43
71
 
44
- Imprint.current_layer(@started_at, observations: @observation_count)
72
+ Imprint.current_layer(started_at: @started_at, observation_count: @observation_count)
45
73
  end
46
74
 
47
75
  def progress
48
76
  {
49
- firmware_loaded: @firmware_loaded,
50
- imprint_active: imprint_active?,
51
- imprint_progress: Imprint.imprint_progress(@started_at),
52
- observation_count: @observation_count,
53
- calibration_state: @calibration_state,
54
- 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
55
84
  }
56
85
  end
57
86
 
58
87
  private
59
88
 
60
89
  def check_calibration_progress
61
- if @observation_count >= Imprint::IMPRINT_ENTROPY_BASELINE && !imprint_active?
62
- @calibration_state = :calibrated
63
- elsif @observation_count >= Imprint::IMPRINT_ENTROPY_BASELINE
64
- @calibration_state = :baseline_established
65
- 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
66
97
  end
67
98
 
68
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.4'
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
@@ -106,10 +108,9 @@ RSpec.describe 'lex-coldstart local persistence' do
106
108
  end
107
109
 
108
110
  describe 'calibration_state symbol round-trip' do
109
- %i[not_started imprinting baseline_established calibrated].each do |state|
111
+ %i[not_started imprinting consolidating calibrated].each do |state|
110
112
  it "persists and restores :#{state}" do
111
113
  fresh_bootstrap
112
- # Force the state directly through the DB to test all symbol variants
113
114
  db[:bootstrap_state].where(id: 1).delete
114
115
  db[:bootstrap_state].insert(
115
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.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity