lex-cognitive-surplus 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4d1218ba7a84db0e3fce82ba7d687bc72d748be64f25685f533ac917bb2f2d7e
4
+ data.tar.gz: 54e81c8dbf7544966cbb68a8d9bcd0452743cb65692ae2ba49c5a15937ba9193
5
+ SHA512:
6
+ metadata.gz: 23b974597e6d8e95cf1a41542be23b73f381c886d4093b04b320e24f63be5aa610247e70d044d34de0e1799cd39c194a1d4f0aa8c094d2c50f168daafab48ef0
7
+ data.tar.gz: a644352549a5675bdaf311ad9e7e695ce1b3a96ffbdc639e456c8d6703466518d9b957e1dee3edc7f70d41f78460f0e369ad7a5e919316c35654332fff6e361e
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'rspec', '~> 3.13'
8
+ gem 'rubocop', '~> 1.75', require: false
9
+ gem 'rubocop-rspec', require: false
10
+
11
+ gem 'legion-gaia', path: '../../legion-gaia'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Iverson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # lex-cognitive-surplus
2
+
3
+ A LegionIO cognitive architecture extension that models cognitive surplus — the available mental bandwidth beyond what is committed to current tasks. Capacity can be committed, allocated to named activities, and automatically replenished over time.
4
+
5
+ ## What It Does
6
+
7
+ Tracks a single cognitive capacity pool (total = 1.0) divided into three layers:
8
+
9
+ - **Committed**: capacity actively consumed by ongoing work
10
+ - **Reserved**: baseline always held back (0.1) — never allocatable
11
+ - **Active allocations**: named reservations for specific activity types
12
+
13
+ Available surplus is what remains after all three. When surplus falls below 0.15 (the threshold), no new allocations are accepted. The built-in actor replenishes capacity automatically every 60 seconds by reducing committed load.
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ require 'lex-cognitive-surplus'
19
+
20
+ client = Legion::Extensions::CognitiveSurplus::Client.new
21
+
22
+ # Check current surplus
23
+ client.surplus_status
24
+ # => { available_surplus: 0.9, surplus_label: :abundant, quality: 1.0, quality_label: :peak, ... }
25
+
26
+ # Commit capacity to a task
27
+ client.commit_capacity(amount: 0.4)
28
+ # => { committed: 0.4, available_surplus: 0.5 }
29
+
30
+ # Allocate some surplus for exploration
31
+ result = client.allocate_surplus(activity_type: :exploration, amount: 0.2)
32
+ # => { allocated: true, allocation_id: "uuid...", amount: 0.2, quality: 0.6, activity_type: :exploration }
33
+
34
+ # Release the allocation when done
35
+ client.release_surplus(allocation_id: result[:allocation_id])
36
+ # => { released: true, allocation_id: "uuid...", amount: 0.2 }
37
+
38
+ # Uncommit task capacity when work is finished
39
+ client.uncommit_capacity(amount: 0.4)
40
+ # => { committed: 0.0, available_surplus: 0.9 }
41
+
42
+ # Simulate a demanding event depleting surplus
43
+ client.deplete_surplus(amount: 0.3)
44
+ # => { depleted: true, amount: 0.3, available_surplus: 0.6 }
45
+
46
+ # Manually trigger replenishment (also fires automatically every 60s)
47
+ client.replenish_surplus
48
+ # => { replenished: true, gained: 0.05, available_surplus: 0.65 }
49
+
50
+ # List active allocations
51
+ client.surplus_allocations
52
+ # => { allocations: [...], count: 0 }
53
+ ```
54
+
55
+ ## Development
56
+
57
+ ```bash
58
+ bundle install
59
+ bundle exec rspec
60
+ bundle exec rubocop
61
+ ```
62
+
63
+ ## License
64
+
65
+ MIT
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_surplus/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-surplus'
7
+ spec.version = Legion::Extensions::CognitiveSurplus::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Cognitive Surplus'
12
+ spec.description = 'Cognitive surplus capacity modeling for brain-modeled agentic AI'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-surplus'
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-cognitive-surplus'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-surplus'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-surplus'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-surplus/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-cognitive-surplus.gemspec Gemfile LICENSE README.md]
26
+ end
27
+ spec.require_paths = ['lib']
28
+ spec.add_development_dependency 'legion-gaia'
29
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/actors/every'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveSurplus
8
+ module Actor
9
+ class Replenish < Legion::Extensions::Actors::Every
10
+ def runner_class
11
+ Legion::Extensions::CognitiveSurplus::Runners::Surplus
12
+ end
13
+
14
+ def runner_function
15
+ 'replenish_surplus'
16
+ end
17
+
18
+ def time
19
+ 60
20
+ end
21
+
22
+ def run_now?
23
+ false
24
+ end
25
+
26
+ def use_runner?
27
+ false
28
+ end
29
+
30
+ def check_subtask?
31
+ false
32
+ end
33
+
34
+ def generate_task?
35
+ false
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_surplus/helpers/constants'
4
+ require 'legion/extensions/cognitive_surplus/helpers/allocation'
5
+ require 'legion/extensions/cognitive_surplus/helpers/surplus_engine'
6
+ require 'legion/extensions/cognitive_surplus/runners/surplus'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module CognitiveSurplus
11
+ class Client
12
+ include Runners::Surplus
13
+
14
+ def initialize(**)
15
+ @surplus_engine = Helpers::SurplusEngine.new
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :surplus_engine
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveSurplus
8
+ module Helpers
9
+ class Allocation
10
+ attr_reader :id, :activity_type, :amount, :quality, :created_at
11
+
12
+ def initialize(activity_type:, amount:, quality:)
13
+ @id = SecureRandom.uuid
14
+ @activity_type = activity_type
15
+ @amount = amount.round(10)
16
+ @quality = quality.round(10)
17
+ @created_at = Time.now.utc
18
+ @released = false
19
+ end
20
+
21
+ def release!
22
+ @released = true
23
+ end
24
+
25
+ def active?
26
+ !@released
27
+ end
28
+
29
+ def to_h
30
+ {
31
+ id: @id,
32
+ activity_type: @activity_type,
33
+ amount: @amount,
34
+ quality: @quality,
35
+ active: active?,
36
+ created_at: @created_at
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSurplus
6
+ module Helpers
7
+ module Constants
8
+ TOTAL_CAPACITY = 1.0
9
+ MIN_RESERVE = 0.1
10
+ SURPLUS_THRESHOLD = 0.15
11
+ REPLENISH_RATE = 0.05
12
+ DEPLETION_RATE = 0.08
13
+
14
+ SURPLUS_LABELS = {
15
+ abundant: (0.6..1.0),
16
+ moderate: (0.3...0.6),
17
+ scarce: (0.15...0.3),
18
+ depleted: (0.0...0.15)
19
+ }.freeze
20
+
21
+ QUALITY_LABELS = {
22
+ peak: (0.8..1.0),
23
+ rested: (0.6...0.8),
24
+ residual: (0.3...0.6),
25
+ degraded: (0.0...0.3)
26
+ }.freeze
27
+
28
+ ALLOCATION_TYPES = %i[exploration consolidation speculation maintenance creative].freeze
29
+
30
+ module_function
31
+
32
+ def surplus_label(amount)
33
+ SURPLUS_LABELS.find { |_label, range| range.cover?(amount) }&.first || :none
34
+ end
35
+
36
+ def quality_label(quality)
37
+ QUALITY_LABELS.find { |_label, range| range.cover?(quality) }&.first || :unknown
38
+ end
39
+
40
+ def valid_allocation_type?(type)
41
+ ALLOCATION_TYPES.include?(type)
42
+ end
43
+
44
+ def clamp(value, min = 0.0, max = 1.0)
45
+ value.clamp(min, max)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSurplus
6
+ module Helpers
7
+ class SurplusEngine
8
+ include Constants
9
+
10
+ attr_reader :committed, :reserved, :allocations
11
+
12
+ def initialize
13
+ @committed = 0.0
14
+ @reserved = Constants::MIN_RESERVE
15
+ @allocations = {}
16
+ end
17
+
18
+ def available_surplus
19
+ used = @committed + @reserved + active_allocated
20
+ Constants.clamp(Constants::TOTAL_CAPACITY - used).round(10)
21
+ end
22
+
23
+ def surplus_quality
24
+ idle = Constants::TOTAL_CAPACITY - @committed
25
+ raw = idle / Constants::TOTAL_CAPACITY
26
+ Constants.clamp(raw).round(10)
27
+ end
28
+
29
+ def allocate!(activity_type:, amount:)
30
+ return { allocated: false, reason: :invalid_type } unless Constants.valid_allocation_type?(activity_type)
31
+ return { allocated: false, reason: :below_threshold } if available_surplus < Constants::SURPLUS_THRESHOLD
32
+
33
+ clamped = Constants.clamp(amount, 0.0, available_surplus).round(10)
34
+ return { allocated: false, reason: :insufficient_surplus } if clamped <= 0.0
35
+
36
+ quality = surplus_quality
37
+ allocation = Allocation.new(activity_type: activity_type, amount: clamped, quality: quality)
38
+ @allocations[allocation.id] = allocation
39
+
40
+ {
41
+ allocated: true,
42
+ allocation_id: allocation.id,
43
+ amount: clamped,
44
+ quality: quality,
45
+ activity_type: activity_type
46
+ }
47
+ end
48
+
49
+ def release!(allocation_id:)
50
+ allocation = @allocations[allocation_id]
51
+ return { released: false, reason: :not_found } unless allocation
52
+ return { released: false, reason: :already_released } unless allocation.active?
53
+
54
+ allocation.release!
55
+ { released: true, allocation_id: allocation_id, amount: allocation.amount }
56
+ end
57
+
58
+ def commit!(amount:)
59
+ clamped = Constants.clamp(amount.round(10), 0.0, Constants::TOTAL_CAPACITY - @reserved)
60
+ @committed = Constants.clamp((@committed + clamped).round(10))
61
+ { committed: @committed, available_surplus: available_surplus }
62
+ end
63
+
64
+ def uncommit!(amount:)
65
+ clamped = Constants.clamp(amount.round(10), 0.0, @committed)
66
+ @committed = Constants.clamp((@committed - clamped).round(10))
67
+ { committed: @committed, available_surplus: available_surplus }
68
+ end
69
+
70
+ def replenish!
71
+ old_committed = @committed
72
+ @committed = Constants.clamp((@committed - Constants::REPLENISH_RATE).round(10))
73
+ gained = (old_committed - @committed).round(10)
74
+ { replenished: true, gained: gained, available_surplus: available_surplus }
75
+ end
76
+
77
+ def deplete!(amount:)
78
+ delta = Constants.clamp(amount.round(10), 0.0, available_surplus)
79
+ @committed = Constants.clamp((@committed + delta).round(10))
80
+ { depleted: true, amount: delta, available_surplus: available_surplus }
81
+ end
82
+
83
+ def surplus_report
84
+ surplus = available_surplus
85
+ {
86
+ total_capacity: Constants::TOTAL_CAPACITY,
87
+ committed: @committed.round(10),
88
+ reserved: @reserved.round(10),
89
+ active_allocated: active_allocated.round(10),
90
+ available_surplus: surplus,
91
+ surplus_label: Constants.surplus_label(surplus),
92
+ quality: surplus_quality,
93
+ quality_label: Constants.quality_label(surplus_quality),
94
+ allocation_count: active_allocations.size
95
+ }
96
+ end
97
+
98
+ def to_h
99
+ surplus_report.merge(allocations: @allocations.transform_values(&:to_h))
100
+ end
101
+
102
+ private
103
+
104
+ def active_allocations
105
+ @allocations.values.select(&:active?)
106
+ end
107
+
108
+ def active_allocated
109
+ active_allocations.sum(&:amount)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSurplus
6
+ module Runners
7
+ module Surplus
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def surplus_status(**)
12
+ report = surplus_engine.surplus_report
13
+ Legion::Logging.debug "[cognitive_surplus] status surplus=#{report[:available_surplus].round(2)} " \
14
+ "label=#{report[:surplus_label]} quality=#{report[:quality_label]}"
15
+ report
16
+ end
17
+
18
+ def allocate_surplus(activity_type:, amount: Helpers::Constants::SURPLUS_THRESHOLD, **)
19
+ result = surplus_engine.allocate!(activity_type: activity_type, amount: amount)
20
+ if result[:allocated]
21
+ Legion::Logging.info "[cognitive_surplus] allocated #{amount.round(2)} to " \
22
+ "#{activity_type} quality=#{result[:quality].round(2)} id=#{result[:allocation_id][0..7]}"
23
+ else
24
+ Legion::Logging.debug "[cognitive_surplus] allocate failed: #{result[:reason]}"
25
+ end
26
+ result
27
+ end
28
+
29
+ def release_surplus(allocation_id:, **)
30
+ result = surplus_engine.release!(allocation_id: allocation_id)
31
+ if result[:released]
32
+ Legion::Logging.info "[cognitive_surplus] released allocation #{allocation_id[0..7]} amount=#{result[:amount].round(2)}"
33
+ else
34
+ Legion::Logging.debug "[cognitive_surplus] release failed: #{result[:reason]} id=#{allocation_id[0..7]}"
35
+ end
36
+ result
37
+ end
38
+
39
+ def commit_capacity(amount:, **)
40
+ result = surplus_engine.commit!(amount: amount)
41
+ Legion::Logging.debug "[cognitive_surplus] committed #{amount.round(2)} committed=#{result[:committed].round(2)} " \
42
+ "surplus=#{result[:available_surplus].round(2)}"
43
+ result
44
+ end
45
+
46
+ def uncommit_capacity(amount:, **)
47
+ result = surplus_engine.uncommit!(amount: amount)
48
+ Legion::Logging.debug "[cognitive_surplus] uncommitted #{amount.round(2)} committed=#{result[:committed].round(2)} " \
49
+ "surplus=#{result[:available_surplus].round(2)}"
50
+ result
51
+ end
52
+
53
+ def replenish_surplus(**)
54
+ result = surplus_engine.replenish!
55
+ Legion::Logging.debug "[cognitive_surplus] replenished gained=#{result[:gained].round(2)} " \
56
+ "surplus=#{result[:available_surplus].round(2)}"
57
+ result
58
+ end
59
+
60
+ def deplete_surplus(amount:, **)
61
+ result = surplus_engine.deplete!(amount: amount)
62
+ Legion::Logging.debug "[cognitive_surplus] depleted amount=#{result[:amount].round(2)} " \
63
+ "surplus=#{result[:available_surplus].round(2)}"
64
+ result
65
+ end
66
+
67
+ def surplus_allocations(**)
68
+ allocations = surplus_engine.allocations.values.select(&:active?).map(&:to_h)
69
+ Legion::Logging.debug "[cognitive_surplus] allocations count=#{allocations.size}"
70
+ { allocations: allocations, count: allocations.size }
71
+ end
72
+
73
+ private
74
+
75
+ def surplus_engine
76
+ @surplus_engine ||= Helpers::SurplusEngine.new
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSurplus
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_surplus/version'
4
+ require 'legion/extensions/cognitive_surplus/helpers/constants'
5
+ require 'legion/extensions/cognitive_surplus/helpers/allocation'
6
+ require 'legion/extensions/cognitive_surplus/helpers/surplus_engine'
7
+ require 'legion/extensions/cognitive_surplus/runners/surplus'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module CognitiveSurplus
12
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_surplus/client'
4
+
5
+ RSpec.describe Legion::Extensions::CognitiveSurplus::Client do
6
+ it 'responds to surplus runner methods' do
7
+ client = described_class.new
8
+ expect(client).to respond_to(:surplus_status)
9
+ expect(client).to respond_to(:allocate_surplus)
10
+ expect(client).to respond_to(:release_surplus)
11
+ expect(client).to respond_to(:commit_capacity)
12
+ expect(client).to respond_to(:uncommit_capacity)
13
+ expect(client).to respond_to(:replenish_surplus)
14
+ expect(client).to respond_to(:deplete_surplus)
15
+ expect(client).to respond_to(:surplus_allocations)
16
+ end
17
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveSurplus::Helpers::Allocation do
4
+ subject(:allocation) { described_class.new(activity_type: :exploration, amount: 0.2, quality: 0.75) }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a uuid id' do
8
+ expect(allocation.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'stores activity_type' do
12
+ expect(allocation.activity_type).to eq(:exploration)
13
+ end
14
+
15
+ it 'rounds amount to 10 decimal places' do
16
+ expect(allocation.amount).to eq(0.2)
17
+ end
18
+
19
+ it 'rounds quality to 10 decimal places' do
20
+ expect(allocation.quality).to eq(0.75)
21
+ end
22
+
23
+ it 'sets created_at' do
24
+ expect(allocation.created_at).to be_a(Time)
25
+ end
26
+
27
+ it 'starts active' do
28
+ expect(allocation.active?).to be true
29
+ end
30
+ end
31
+
32
+ describe '#release!' do
33
+ it 'deactivates the allocation' do
34
+ allocation.release!
35
+ expect(allocation.active?).to be false
36
+ end
37
+ end
38
+
39
+ describe '#to_h' do
40
+ it 'returns a hash with expected keys' do
41
+ h = allocation.to_h
42
+ expect(h).to include(:id, :activity_type, :amount, :quality, :active, :created_at)
43
+ end
44
+
45
+ it 'reflects active state' do
46
+ expect(allocation.to_h[:active]).to be true
47
+ allocation.release!
48
+ expect(allocation.to_h[:active]).to be false
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveSurplus::Helpers::Constants do
4
+ describe 'constants' do
5
+ it 'defines TOTAL_CAPACITY as 1.0' do
6
+ expect(described_class::TOTAL_CAPACITY).to eq(1.0)
7
+ end
8
+
9
+ it 'defines MIN_RESERVE as 0.1' do
10
+ expect(described_class::MIN_RESERVE).to eq(0.1)
11
+ end
12
+
13
+ it 'defines SURPLUS_THRESHOLD as 0.15' do
14
+ expect(described_class::SURPLUS_THRESHOLD).to eq(0.15)
15
+ end
16
+
17
+ it 'defines REPLENISH_RATE as 0.05' do
18
+ expect(described_class::REPLENISH_RATE).to eq(0.05)
19
+ end
20
+
21
+ it 'defines DEPLETION_RATE as 0.08' do
22
+ expect(described_class::DEPLETION_RATE).to eq(0.08)
23
+ end
24
+
25
+ it 'defines all ALLOCATION_TYPES' do
26
+ expect(described_class::ALLOCATION_TYPES).to include(:exploration, :consolidation, :speculation, :maintenance, :creative)
27
+ end
28
+ end
29
+
30
+ describe '.surplus_label' do
31
+ it 'returns :abundant for high surplus' do
32
+ expect(described_class.surplus_label(0.8)).to eq(:abundant)
33
+ end
34
+
35
+ it 'returns :moderate for moderate surplus' do
36
+ expect(described_class.surplus_label(0.4)).to eq(:moderate)
37
+ end
38
+
39
+ it 'returns :scarce for scarce surplus' do
40
+ expect(described_class.surplus_label(0.2)).to eq(:scarce)
41
+ end
42
+
43
+ it 'returns :depleted for very low surplus' do
44
+ expect(described_class.surplus_label(0.05)).to eq(:depleted)
45
+ end
46
+
47
+ it 'returns :none for zero surplus' do
48
+ expect(described_class.surplus_label(0.0)).to eq(:depleted)
49
+ end
50
+ end
51
+
52
+ describe '.quality_label' do
53
+ it 'returns :peak for high quality' do
54
+ expect(described_class.quality_label(0.9)).to eq(:peak)
55
+ end
56
+
57
+ it 'returns :rested for good quality' do
58
+ expect(described_class.quality_label(0.7)).to eq(:rested)
59
+ end
60
+
61
+ it 'returns :residual for moderate quality' do
62
+ expect(described_class.quality_label(0.4)).to eq(:residual)
63
+ end
64
+
65
+ it 'returns :degraded for low quality' do
66
+ expect(described_class.quality_label(0.1)).to eq(:degraded)
67
+ end
68
+ end
69
+
70
+ describe '.valid_allocation_type?' do
71
+ it 'returns true for valid types' do
72
+ expect(described_class.valid_allocation_type?(:exploration)).to be true
73
+ expect(described_class.valid_allocation_type?(:creative)).to be true
74
+ end
75
+
76
+ it 'returns false for invalid types' do
77
+ expect(described_class.valid_allocation_type?(:unknown)).to be false
78
+ end
79
+ end
80
+
81
+ describe '.clamp' do
82
+ it 'clamps to [0.0, 1.0] by default' do
83
+ expect(described_class.clamp(1.5)).to eq(1.0)
84
+ expect(described_class.clamp(-0.5)).to eq(0.0)
85
+ expect(described_class.clamp(0.5)).to eq(0.5)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveSurplus::Helpers::SurplusEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ describe '#available_surplus' do
7
+ it 'starts at TOTAL_CAPACITY minus MIN_RESERVE' do
8
+ expected = (Legion::Extensions::CognitiveSurplus::Helpers::Constants::TOTAL_CAPACITY -
9
+ Legion::Extensions::CognitiveSurplus::Helpers::Constants::MIN_RESERVE).round(10)
10
+ expect(engine.available_surplus).to eq(expected)
11
+ end
12
+
13
+ it 'decreases after commit' do
14
+ before = engine.available_surplus
15
+ engine.commit!(amount: 0.2)
16
+ expect(engine.available_surplus).to be < before
17
+ end
18
+ end
19
+
20
+ describe '#surplus_quality' do
21
+ it 'returns 1.0 when nothing is committed' do
22
+ expect(engine.surplus_quality).to eq(1.0)
23
+ end
24
+
25
+ it 'decreases as committed increases' do
26
+ engine.commit!(amount: 0.5)
27
+ expect(engine.surplus_quality).to be < 1.0
28
+ end
29
+ end
30
+
31
+ describe '#allocate!' do
32
+ it 'returns allocated: true for valid type with sufficient surplus' do
33
+ result = engine.allocate!(activity_type: :exploration, amount: 0.1)
34
+ expect(result[:allocated]).to be true
35
+ expect(result[:allocation_id]).to match(/\A[0-9a-f-]{36}\z/)
36
+ end
37
+
38
+ it 'returns error for invalid allocation type' do
39
+ result = engine.allocate!(activity_type: :invalid, amount: 0.1)
40
+ expect(result[:allocated]).to be false
41
+ expect(result[:reason]).to eq(:invalid_type)
42
+ end
43
+
44
+ it 'returns below_threshold when surplus is too low' do
45
+ engine.commit!(amount: 0.9)
46
+ result = engine.allocate!(activity_type: :exploration, amount: 0.05)
47
+ expect(result[:allocated]).to be false
48
+ expect(result[:reason]).to eq(:below_threshold)
49
+ end
50
+
51
+ it 'includes quality in result' do
52
+ result = engine.allocate!(activity_type: :creative, amount: 0.1)
53
+ expect(result[:quality]).to be_between(0.0, 1.0)
54
+ end
55
+ end
56
+
57
+ describe '#release!' do
58
+ it 'releases an active allocation' do
59
+ alloc = engine.allocate!(activity_type: :consolidation, amount: 0.1)
60
+ result = engine.release!(allocation_id: alloc[:allocation_id])
61
+ expect(result[:released]).to be true
62
+ end
63
+
64
+ it 'returns not_found for unknown id' do
65
+ result = engine.release!(allocation_id: 'nonexistent')
66
+ expect(result[:released]).to be false
67
+ expect(result[:reason]).to eq(:not_found)
68
+ end
69
+
70
+ it 'returns already_released for double release' do
71
+ alloc = engine.allocate!(activity_type: :speculation, amount: 0.1)
72
+ engine.release!(allocation_id: alloc[:allocation_id])
73
+ result = engine.release!(allocation_id: alloc[:allocation_id])
74
+ expect(result[:released]).to be false
75
+ expect(result[:reason]).to eq(:already_released)
76
+ end
77
+ end
78
+
79
+ describe '#commit!' do
80
+ it 'increases committed and decreases surplus' do
81
+ surplus_before = engine.available_surplus
82
+ result = engine.commit!(amount: 0.3)
83
+ expect(result[:committed]).to eq(0.3)
84
+ expect(result[:available_surplus]).to be < surplus_before
85
+ end
86
+ end
87
+
88
+ describe '#uncommit!' do
89
+ it 'decreases committed and increases surplus' do
90
+ engine.commit!(amount: 0.4)
91
+ committed_before = engine.committed
92
+ result = engine.uncommit!(amount: 0.2)
93
+ expect(result[:committed]).to be < committed_before
94
+ end
95
+
96
+ it 'does not go below zero' do
97
+ result = engine.uncommit!(amount: 1.0)
98
+ expect(result[:committed]).to eq(0.0)
99
+ end
100
+ end
101
+
102
+ describe '#replenish!' do
103
+ it 'reduces committed by REPLENISH_RATE' do
104
+ engine.commit!(amount: 0.5)
105
+ before = engine.committed
106
+ engine.replenish!
107
+ expect(engine.committed).to be < before
108
+ end
109
+
110
+ it 'returns gained amount' do
111
+ engine.commit!(amount: 0.3)
112
+ result = engine.replenish!
113
+ expect(result[:gained]).to eq(Legion::Extensions::CognitiveSurplus::Helpers::Constants::REPLENISH_RATE)
114
+ end
115
+
116
+ it 'does not go below zero committed' do
117
+ engine.replenish!
118
+ expect(engine.committed).to eq(0.0)
119
+ end
120
+ end
121
+
122
+ describe '#deplete!' do
123
+ it 'increases committed by amount' do
124
+ before = engine.committed
125
+ engine.deplete!(amount: 0.1)
126
+ expect(engine.committed).to be > before
127
+ end
128
+
129
+ it 'does not exceed available surplus' do
130
+ result = engine.deplete!(amount: 2.0)
131
+ expect(engine.available_surplus).to be >= 0.0
132
+ expect(result[:depleted]).to be true
133
+ end
134
+ end
135
+
136
+ describe '#surplus_report' do
137
+ it 'returns all required fields' do
138
+ report = engine.surplus_report
139
+ expect(report).to include(
140
+ :total_capacity, :committed, :reserved, :active_allocated,
141
+ :available_surplus, :surplus_label, :quality, :quality_label, :allocation_count
142
+ )
143
+ end
144
+
145
+ it 'surplus_label reflects current amount' do
146
+ report = engine.surplus_report
147
+ expect(report[:surplus_label]).to be_a(Symbol)
148
+ end
149
+ end
150
+
151
+ describe '#to_h' do
152
+ it 'includes allocations hash' do
153
+ engine.allocate!(activity_type: :maintenance, amount: 0.1)
154
+ h = engine.to_h
155
+ expect(h[:allocations]).to be_a(Hash)
156
+ expect(h[:allocations].size).to eq(1)
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_surplus/client'
4
+
5
+ RSpec.describe Legion::Extensions::CognitiveSurplus::Runners::Surplus do
6
+ let(:client) { Legion::Extensions::CognitiveSurplus::Client.new }
7
+
8
+ describe '#surplus_status' do
9
+ it 'returns a surplus report hash' do
10
+ result = client.surplus_status
11
+ expect(result).to include(:available_surplus, :surplus_label, :quality, :quality_label)
12
+ end
13
+
14
+ it 'available_surplus is between 0 and 1' do
15
+ result = client.surplus_status
16
+ expect(result[:available_surplus]).to be_between(0.0, 1.0)
17
+ end
18
+ end
19
+
20
+ describe '#allocate_surplus' do
21
+ it 'allocates surplus for a valid activity type' do
22
+ result = client.allocate_surplus(activity_type: :exploration, amount: 0.1)
23
+ expect(result[:allocated]).to be true
24
+ expect(result[:allocation_id]).to match(/\A[0-9a-f-]{36}\z/)
25
+ end
26
+
27
+ it 'returns error for invalid activity type' do
28
+ result = client.allocate_surplus(activity_type: :bogus, amount: 0.1)
29
+ expect(result[:allocated]).to be false
30
+ end
31
+
32
+ it 'uses default amount of SURPLUS_THRESHOLD' do
33
+ result = client.allocate_surplus(activity_type: :creative)
34
+ expect(result[:allocated]).to be true
35
+ end
36
+ end
37
+
38
+ describe '#release_surplus' do
39
+ it 'releases an active allocation' do
40
+ alloc = client.allocate_surplus(activity_type: :consolidation, amount: 0.1)
41
+ result = client.release_surplus(allocation_id: alloc[:allocation_id])
42
+ expect(result[:released]).to be true
43
+ end
44
+
45
+ it 'returns not_found for unknown allocation id' do
46
+ result = client.release_surplus(allocation_id: 'no-such-id')
47
+ expect(result[:released]).to be false
48
+ expect(result[:reason]).to eq(:not_found)
49
+ end
50
+ end
51
+
52
+ describe '#commit_capacity' do
53
+ it 'commits capacity and reduces available surplus' do
54
+ before = client.surplus_status[:available_surplus]
55
+ client.commit_capacity(amount: 0.2)
56
+ after = client.surplus_status[:available_surplus]
57
+ expect(after).to be < before
58
+ end
59
+ end
60
+
61
+ describe '#uncommit_capacity' do
62
+ it 'uncommits capacity and increases available surplus' do
63
+ client.commit_capacity(amount: 0.3)
64
+ before = client.surplus_status[:available_surplus]
65
+ client.uncommit_capacity(amount: 0.15)
66
+ after = client.surplus_status[:available_surplus]
67
+ expect(after).to be > before
68
+ end
69
+ end
70
+
71
+ describe '#replenish_surplus' do
72
+ it 'returns replenished: true' do
73
+ result = client.replenish_surplus
74
+ expect(result[:replenished]).to be true
75
+ end
76
+
77
+ it 'includes gained amount' do
78
+ client.commit_capacity(amount: 0.3)
79
+ result = client.replenish_surplus
80
+ expect(result[:gained]).to be >= 0.0
81
+ end
82
+ end
83
+
84
+ describe '#deplete_surplus' do
85
+ it 'reduces available surplus' do
86
+ before = client.surplus_status[:available_surplus]
87
+ client.deplete_surplus(amount: 0.1)
88
+ after = client.surplus_status[:available_surplus]
89
+ expect(after).to be < before
90
+ end
91
+
92
+ it 'returns depleted: true' do
93
+ result = client.deplete_surplus(amount: 0.05)
94
+ expect(result[:depleted]).to be true
95
+ end
96
+ end
97
+
98
+ describe '#surplus_allocations' do
99
+ it 'returns empty allocations initially' do
100
+ result = client.surplus_allocations
101
+ expect(result[:count]).to eq(0)
102
+ expect(result[:allocations]).to eq([])
103
+ end
104
+
105
+ it 'lists active allocations' do
106
+ client.allocate_surplus(activity_type: :speculation, amount: 0.1)
107
+ client.allocate_surplus(activity_type: :maintenance, amount: 0.1)
108
+ result = client.surplus_allocations
109
+ expect(result[:count]).to eq(2)
110
+ end
111
+
112
+ it 'excludes released allocations' do
113
+ alloc = client.allocate_surplus(activity_type: :exploration, amount: 0.1)
114
+ client.release_surplus(allocation_id: alloc[:allocation_id])
115
+ result = client.surplus_allocations
116
+ expect(result[:count]).to eq(0)
117
+ end
118
+ end
119
+ end
@@ -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/cognitive_surplus'
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,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-surplus
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: Cognitive surplus capacity modeling for brain-modeled agentic AI
27
+ email:
28
+ - matthewdiverson@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - Gemfile
34
+ - LICENSE
35
+ - README.md
36
+ - lex-cognitive-surplus.gemspec
37
+ - lib/legion/extensions/cognitive_surplus.rb
38
+ - lib/legion/extensions/cognitive_surplus/actors/replenish.rb
39
+ - lib/legion/extensions/cognitive_surplus/client.rb
40
+ - lib/legion/extensions/cognitive_surplus/helpers/allocation.rb
41
+ - lib/legion/extensions/cognitive_surplus/helpers/constants.rb
42
+ - lib/legion/extensions/cognitive_surplus/helpers/surplus_engine.rb
43
+ - lib/legion/extensions/cognitive_surplus/runners/surplus.rb
44
+ - lib/legion/extensions/cognitive_surplus/version.rb
45
+ - spec/legion/extensions/cognitive_surplus/client_spec.rb
46
+ - spec/legion/extensions/cognitive_surplus/helpers/allocation_spec.rb
47
+ - spec/legion/extensions/cognitive_surplus/helpers/constants_spec.rb
48
+ - spec/legion/extensions/cognitive_surplus/helpers/surplus_engine_spec.rb
49
+ - spec/legion/extensions/cognitive_surplus/runners/surplus_spec.rb
50
+ - spec/spec_helper.rb
51
+ homepage: https://github.com/LegionIO/lex-cognitive-surplus
52
+ licenses:
53
+ - MIT
54
+ metadata:
55
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-surplus
56
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-surplus
57
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-surplus
58
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-surplus
59
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-surplus/issues
60
+ rubygems_mfa_required: 'true'
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.9
76
+ specification_version: 4
77
+ summary: LEX Cognitive Surplus
78
+ test_files: []