lex-cognitive-phantom 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: 6b3ee604853dcc9f14a6d0d0d97fd23f8766ece92475d5abc06257139552b22c
4
+ data.tar.gz: 87bcb8aa7479fa4155c610e3969fdb667cee1b2f9962c3a7fd6e1f8c82ea69c7
5
+ SHA512:
6
+ metadata.gz: 61f7abd1e3575004f8f7d3153b8b9838159b0662bc10ab6f9343876edcbd514cc5d6efd4685d428b17fe2beb2a61d9092fb02acb49364e6faccddf02d9258a51
7
+ data.tar.gz: e2356291933cea4b484e8b7fa8f239cd53d5bf7f7f0714c5c945d3dfa5eefbae24509ed12a60e0de306b6f4d46014c09e73d891239d7b5da18b525ea8dd765ee
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ ci:
9
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
10
+
11
+ release:
12
+ needs: ci
13
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
+ uses: LegionIO/.github/.github/workflows/release.yml@main
15
+ secrets:
16
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,64 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.4
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Layout/LineLength:
7
+ Max: 160
8
+
9
+ Layout/SpaceAroundEqualsInParameterDefault:
10
+ EnforcedStyle: space
11
+
12
+ Layout/HashAlignment:
13
+ EnforcedHashRocketStyle: table
14
+ EnforcedColonStyle: table
15
+
16
+ Metrics/MethodLength:
17
+ Max: 25
18
+
19
+ Metrics/ClassLength:
20
+ Max: 150
21
+
22
+ Metrics/ModuleLength:
23
+ Max: 1500
24
+
25
+ Metrics/BlockLength:
26
+ Max: 40
27
+ Exclude:
28
+ - 'spec/**/*'
29
+
30
+ Metrics/AbcSize:
31
+ Max: 25
32
+
33
+ Metrics/CyclomaticComplexity:
34
+ Max: 15
35
+
36
+ Metrics/PerceivedComplexity:
37
+ Max: 17
38
+
39
+ Metrics/ParameterLists:
40
+ Max: 8
41
+ MaxOptionalParameters: 8
42
+
43
+ Style/Documentation:
44
+ Enabled: false
45
+
46
+ Style/OneClassPerFile:
47
+ Exclude:
48
+ - 'spec/spec_helper.rb'
49
+
50
+ Style/SymbolArray:
51
+ Enabled: true
52
+
53
+ Style/FrozenStringLiteralComment:
54
+ Enabled: true
55
+ EnforcedStyle: always
56
+
57
+ Naming/FileName:
58
+ Enabled: false
59
+
60
+ Naming/PredicateMethod:
61
+ Enabled: false
62
+
63
+ Naming/PredicatePrefix:
64
+ Enabled: false
data/CLAUDE.md ADDED
@@ -0,0 +1,86 @@
1
+ # lex-cognitive-phantom
2
+
3
+ **Level 3 Leaf Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+
6
+ ## Purpose
7
+
8
+ Phantom limb metaphor for removed cognitive capabilities. When a capability (tool, permission, access, skill, memory, context, etc.) is removed from the agent, a `PhantomLimb` is created representing the lingering ghost signal. Intensity decays over time; incoming stimuli that match the phantom's capability type trigger the phantom and slightly boost intensity. States progress from `acute` → `adapting` → `residual` → `resolved` as intensity decays.
9
+
10
+ ## Gem Info
11
+
12
+ - **Gem name**: `lex-cognitive-phantom`
13
+ - **Module**: `Legion::Extensions::CognitivePhantom`
14
+ - **Version**: `0.1.0`
15
+ - **Ruby**: `>= 3.4`
16
+ - **License**: MIT
17
+
18
+ ## File Structure
19
+
20
+ ```
21
+ lib/legion/extensions/cognitive_phantom/
22
+ version.rb
23
+ client.rb
24
+ helpers/
25
+ constants.rb
26
+ phantom_limb.rb
27
+ runners/
28
+ cognitive_phantom.rb
29
+ ```
30
+
31
+ ## Key Constants
32
+
33
+ | Constant | Value | Purpose |
34
+ |---|---|---|
35
+ | `MAX_PHANTOMS` | `100` | Per-engine phantom capacity |
36
+ | `INITIAL_INTENSITY` | `0.8` | Intensity when a phantom is first created |
37
+ | `INTENSITY_DECAY` | `0.05` | Per-cycle intensity decay |
38
+ | `MIN_INTENSITY` | `0.01` | Below this intensity, phantom is resolved |
39
+ | `PHANTOM_STATES` | `%i[acute adapting residual resolved]` | Lifecycle states |
40
+ | `TRIGGER_TYPES` | symbol array | Valid trigger categories (e.g., `:stimulus_match`, `:context_reminder`) |
41
+ | `STATE_THRESHOLDS` | hash | Intensity thresholds per state |
42
+ | `PHANTOM_LABELS` | range hash | Phantom intensity labels |
43
+
44
+ ## Helpers
45
+
46
+ ### `Helpers::PhantomLimb`
47
+ Individual phantom record. Has `id`, `capability_type`, `domain`, `content`, `intensity`, `signals` (array of `PhantomSignal`), `trigger_count`, and `resolved_at`.
48
+
49
+ - `trigger!(stimulus)` — classifies trigger type from stimulus, boosts intensity by 5% of remaining headroom, creates `PhantomSignal` record
50
+ - `decay!` — reduces intensity by `INTENSITY_DECAY`; sets `resolved_at` if intensity drops to `MIN_INTENSITY`
51
+ - `adapt!` — applies 2.5× decay rate (accelerated adaptation)
52
+ - `resolved?` — intensity <= `MIN_INTENSITY`
53
+ - `state` — returns `:acute` (>= 0.6), `:adapting` (>= 0.3), `:residual` (> MIN), or `:resolved`
54
+ - `intensity` — current signal intensity
55
+
56
+ ### `PhantomSignal` (inline value object)
57
+ Immutable record of a trigger event: `trigger_type`, `stimulus`, and `occurred_at`.
58
+
59
+ ## Runners
60
+
61
+ Module: `Runners::CognitivePhantom`
62
+
63
+ | Runner Method | Description |
64
+ |---|---|
65
+ | `register_removal(capability_type:, domain:, content:)` | Register a removed capability as a phantom |
66
+ | `process_stimulus(phantom_id:, stimulus:)` | Process incoming stimulus against phantom |
67
+ | `acknowledge_phantom(phantom_id:)` | Apply accelerated adaptation (adapt!) |
68
+ | `phantom_status` | Aggregate phantom report |
69
+ | `decay_all` | Apply standard decay to all phantoms; prune resolved ones |
70
+
71
+ All runners return `{success: true/false, ...}` hashes.
72
+
73
+ ## Integration Points
74
+
75
+ - `lex-extinction` level 1/2 operations: when capabilities are removed by an extinction protocol, register them as phantoms
76
+ - `lex-tick` `action_selection`: check for triggered phantoms before executing actions that require the removed capability
77
+ - `lex-emotion`: acute phantoms contribute negative valence; resolved phantoms are a positive signal
78
+ - `lex-identity`: phantom signals reflect behavioral pattern disruption — high trigger count phantoms affect behavioral fingerprint
79
+
80
+ ## Development Notes
81
+
82
+ - `Client` instantiates `@phantom_engine = Helpers::PhantomEngine.new`
83
+ - `trigger!` boost is small (5% of headroom) to prevent re-escalation to acute state from residual
84
+ - `adapt!` applies 2.5× decay — allows the agent to explicitly accelerate acceptance of capability loss
85
+ - `decay_all` removes resolved phantoms from memory (garbage collection)
86
+ - `INITIAL_INTENSITY = 0.8` models the immediate strong signal when something important is removed; starting intensity below `PHANTOM_STATES` `:acute` threshold (0.6) would bypass the acute state
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/Gemfile.lock ADDED
@@ -0,0 +1,78 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lex-cognitive-phantom (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.8.9)
10
+ public_suffix (>= 2.0.2, < 8.0)
11
+ ast (2.4.3)
12
+ bigdecimal (4.0.1)
13
+ diff-lcs (1.6.2)
14
+ json (2.19.1)
15
+ json-schema (6.2.0)
16
+ addressable (~> 2.8)
17
+ bigdecimal (>= 3.1, < 5)
18
+ language_server-protocol (3.17.0.5)
19
+ lint_roller (1.1.0)
20
+ mcp (0.8.0)
21
+ json-schema (>= 4.1)
22
+ parallel (1.27.0)
23
+ parser (3.3.10.2)
24
+ ast (~> 2.4.1)
25
+ racc
26
+ prism (1.9.0)
27
+ public_suffix (7.0.5)
28
+ racc (1.8.1)
29
+ rainbow (3.1.1)
30
+ regexp_parser (2.11.3)
31
+ rspec (3.13.2)
32
+ rspec-core (~> 3.13.0)
33
+ rspec-expectations (~> 3.13.0)
34
+ rspec-mocks (~> 3.13.0)
35
+ rspec-core (3.13.6)
36
+ rspec-support (~> 3.13.0)
37
+ rspec-expectations (3.13.5)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.13.0)
40
+ rspec-mocks (3.13.8)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.13.0)
43
+ rspec-support (3.13.7)
44
+ rubocop (1.85.1)
45
+ json (~> 2.3)
46
+ language_server-protocol (~> 3.17.0.2)
47
+ lint_roller (~> 1.1.0)
48
+ mcp (~> 0.6)
49
+ parallel (~> 1.10)
50
+ parser (>= 3.3.0.2)
51
+ rainbow (>= 2.2.2, < 4.0)
52
+ regexp_parser (>= 2.9.3, < 3.0)
53
+ rubocop-ast (>= 1.49.0, < 2.0)
54
+ ruby-progressbar (~> 1.7)
55
+ unicode-display_width (>= 2.4.0, < 4.0)
56
+ rubocop-ast (1.49.1)
57
+ parser (>= 3.3.7.2)
58
+ prism (~> 1.7)
59
+ rubocop-rspec (3.9.0)
60
+ lint_roller (~> 1.1)
61
+ rubocop (~> 1.81)
62
+ ruby-progressbar (1.13.0)
63
+ unicode-display_width (3.2.0)
64
+ unicode-emoji (~> 4.1)
65
+ unicode-emoji (4.2.0)
66
+
67
+ PLATFORMS
68
+ arm64-darwin-25
69
+ ruby
70
+
71
+ DEPENDENCIES
72
+ lex-cognitive-phantom!
73
+ rspec (~> 3.13)
74
+ rubocop (~> 1.75)
75
+ rubocop-rspec
76
+
77
+ BUNDLED WITH
78
+ 2.6.9
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # lex-cognitive-phantom
2
+
3
+ Phantom limb model for removed cognitive capabilities in LegionIO agents. When a capability is removed, a phantom is registered with high initial intensity. Intensity decays over time; matching stimuli trigger the phantom; acknowledging it accelerates adaptation.
4
+
5
+ ## What It Does
6
+
7
+ - Register removed capabilities (tool, permission, memory, context, skill, etc.) as phantoms
8
+ - Initial intensity: 0.8 (acute state)
9
+ - Four states: acute (>= 0.6) → adapting (>= 0.3) → residual (> 0.01) → resolved
10
+ - Incoming stimuli trigger the phantom (small intensity boost + signal record)
11
+ - `acknowledge_phantom` applies 2.5× decay for accelerated adaptation
12
+ - Standard decay (INTENSITY_DECAY = 0.05/cycle) runs via `decay_all`
13
+ - Resolved phantoms are pruned from memory automatically
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ # Register a removed capability
19
+ result = runner.register_removal(
20
+ capability_type: :tool_access, domain: :operations,
21
+ content: 'database write permission removed by governance'
22
+ )
23
+ phantom_id = result[:phantom][:id]
24
+
25
+ # Process a stimulus that touches the removed capability
26
+ runner.process_stimulus(phantom_id: phantom_id,
27
+ stimulus: 'attempted to write to DB')
28
+ # => { success: true, triggered: true, intensity: 0.81, trigger_type: :stimulus_match, state: :acute }
29
+
30
+ # Acknowledge to accelerate adaptation
31
+ runner.acknowledge_phantom(phantom_id: phantom_id)
32
+ # => { success: true, phantom: { intensity: 0.68, state: :adapting } }
33
+
34
+ # Apply standard decay each tick
35
+ runner.decay_all
36
+ # => { success: true, decayed: 1, resolved: 0 }
37
+
38
+ # Overall status
39
+ runner.phantom_status
40
+ # => { success: true, total: 1, acute: 0, adapting: 1, residual: 0, resolved: 0 }
41
+ ```
42
+
43
+ ## Development
44
+
45
+ ```bash
46
+ bundle install
47
+ bundle exec rspec
48
+ bundle exec rubocop
49
+ ```
50
+
51
+ ## License
52
+
53
+ MIT
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_phantom/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-phantom'
7
+ spec.version = Legion::Extensions::CognitivePhantom::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Cognitive Phantom'
12
+ spec.description = 'Phantom limb model for AI: ghost signals from removed capabilities fade over time as the agent adapts'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-phantom'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata['homepage_uri'] = 'https://github.com/LegionIO/lex-cognitive-phantom'
18
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-phantom'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-phantom'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-phantom'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-phantom/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
+ spec.require_paths = ['lib']
26
+ spec.add_development_dependency 'legion-gaia'
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_phantom/helpers/constants'
4
+ require 'legion/extensions/cognitive_phantom/helpers/phantom_signal'
5
+ require 'legion/extensions/cognitive_phantom/helpers/phantom_limb'
6
+ require 'legion/extensions/cognitive_phantom/helpers/phantom_engine'
7
+ require 'legion/extensions/cognitive_phantom/runners/cognitive_phantom'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module CognitivePhantom
12
+ class Client
13
+ include Runners::CognitivePhantom
14
+
15
+ def initialize(**)
16
+ @phantom_engine = Helpers::PhantomEngine.new
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :phantom_engine
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitivePhantom
6
+ module Helpers
7
+ module Constants
8
+ MAX_PHANTOMS = 100
9
+ INITIAL_INTENSITY = 0.8
10
+ INTENSITY_DECAY = 0.05
11
+ MIN_INTENSITY = 0.01
12
+
13
+ PHANTOM_STATES = %i[acute adapting residual resolved].freeze
14
+ TRIGGER_TYPES = %i[stimulus_match contextual_association temporal_pattern habitual].freeze
15
+
16
+ STATE_THRESHOLDS = {
17
+ acute: 0.6,
18
+ adapting: 0.3,
19
+ residual: MIN_INTENSITY
20
+ }.freeze
21
+
22
+ PHANTOM_LABELS = {
23
+ acute: 'Active phantom — strong ghost signals firing',
24
+ adapting: 'Adapting — agent learning to cope with absence',
25
+ residual: 'Residual — faint ghost signals, near resolution',
26
+ resolved: 'Resolved — phantom fully integrated and silent'
27
+ }.freeze
28
+
29
+ module_function
30
+
31
+ def label_for(state)
32
+ PHANTOM_LABELS.fetch(state, 'Unknown state')
33
+ end
34
+
35
+ def state_for(intensity)
36
+ if intensity >= STATE_THRESHOLDS[:acute]
37
+ :acute
38
+ elsif intensity >= STATE_THRESHOLDS[:adapting]
39
+ :adapting
40
+ elsif intensity > MIN_INTENSITY
41
+ :residual
42
+ else
43
+ :resolved
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitivePhantom
6
+ module Helpers
7
+ class PhantomEngine
8
+ def initialize
9
+ @phantoms = {}
10
+ end
11
+
12
+ def register_removal(capability_name:, capability_domain: :general)
13
+ if @phantoms.size >= Constants::MAX_PHANTOMS
14
+ Legion::Logging.warn "[cognitive_phantom] MAX_PHANTOMS (#{Constants::MAX_PHANTOMS}) reached, skipping #{capability_name}"
15
+ return nil
16
+ end
17
+
18
+ limb = PhantomLimb.new(capability_name: capability_name, capability_domain: capability_domain)
19
+ @phantoms[limb.id] = limb
20
+ Legion::Logging.info "[cognitive_phantom] registered phantom: capability=#{capability_name} domain=#{capability_domain} id=#{limb.id[0..7]}"
21
+ limb
22
+ end
23
+
24
+ def process_stimulus(stimulus:, domain: :general)
25
+ fired = []
26
+ @phantoms.each_value do |limb|
27
+ next if limb.resolved?
28
+ next unless domain_match?(limb, domain)
29
+
30
+ signal = limb.trigger!(stimulus)
31
+ next unless signal
32
+
33
+ fired << signal
34
+ intensity_str = limb.intensity.round(3).to_s
35
+ Legion::Logging.debug "[cognitive_phantom] phantom fired: cap=#{limb.capability_name} trigger=#{signal.trigger_type} i=#{intensity_str}"
36
+ end
37
+ fired
38
+ end
39
+
40
+ def decay_all!
41
+ @phantoms.each_value(&:decay!)
42
+ resolve_check!
43
+ end
44
+
45
+ def acknowledge(phantom_id:)
46
+ limb = @phantoms[phantom_id]
47
+ return { acknowledged: false, reason: :not_found } unless limb
48
+
49
+ limb.adapt!
50
+ Legion::Logging.info "[cognitive_phantom] acknowledged phantom id=#{phantom_id[0..7]} intensity=#{limb.intensity.round(3)} state=#{limb.state}"
51
+ { acknowledged: true, phantom_id: phantom_id, state: limb.state, intensity: limb.intensity }
52
+ end
53
+
54
+ def all_phantoms
55
+ @phantoms.values
56
+ end
57
+
58
+ def active_phantoms
59
+ @phantoms.values.reject(&:resolved?)
60
+ end
61
+
62
+ def phantom_activity_report
63
+ all = @phantoms.values
64
+ by_state = Constants::PHANTOM_STATES.to_h { |s| [s, all.count { |p| p.state == s }] }
65
+ {
66
+ total: all.size,
67
+ active: active_phantoms.size,
68
+ by_state: by_state,
69
+ total_activations: all.sum(&:activation_count)
70
+ }
71
+ end
72
+
73
+ def most_persistent(limit: 5)
74
+ active_phantoms.sort_by(&:activation_count).last(limit).reverse
75
+ end
76
+
77
+ def recently_triggered(limit: 5)
78
+ active_phantoms
79
+ .select(&:last_triggered)
80
+ .sort_by(&:last_triggered)
81
+ .last(limit)
82
+ .reverse
83
+ end
84
+
85
+ def resolve_check!
86
+ newly_resolved = @phantoms.values.select(&:resolved?)
87
+ newly_resolved.each do |limb|
88
+ Legion::Logging.info "[cognitive_phantom] resolved: capability=#{limb.capability_name} activations=#{limb.activation_count}"
89
+ end
90
+ newly_resolved.size
91
+ end
92
+
93
+ private
94
+
95
+ def domain_match?(limb, domain)
96
+ domain == :any || limb.capability_domain == domain || limb.capability_domain == :general
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitivePhantom
8
+ module Helpers
9
+ class PhantomLimb
10
+ ADAPT_DECAY_MULTIPLIER = 2.5
11
+
12
+ attr_reader :id, :capability_name, :capability_domain, :removed_at,
13
+ :activation_count, :last_triggered, :trigger_history
14
+
15
+ def initialize(capability_name:, capability_domain:)
16
+ @id = SecureRandom.uuid
17
+ @capability_name = capability_name
18
+ @capability_domain = capability_domain
19
+ @removed_at = Time.now.utc
20
+ @intensity = Constants::INITIAL_INTENSITY
21
+ @activation_count = 0
22
+ @last_triggered = nil
23
+ @trigger_history = []
24
+ end
25
+
26
+ def intensity
27
+ @intensity.round(10)
28
+ end
29
+
30
+ def state
31
+ Constants.state_for(@intensity)
32
+ end
33
+
34
+ def trigger!(stimulus)
35
+ return false if resolved?
36
+
37
+ @activation_count += 1
38
+ prev_triggered = @last_triggered
39
+ @last_triggered = Time.now.utc
40
+ signal = PhantomSignal.new(
41
+ phantom_limb_id: @id,
42
+ stimulus: stimulus,
43
+ trigger_type: classify_trigger(stimulus, prev_triggered),
44
+ intensity_at_trigger: @intensity
45
+ )
46
+ @trigger_history << signal
47
+ @trigger_history.shift while @trigger_history.size > 50
48
+ boost = (@intensity * 0.05).clamp(0.0, 0.1)
49
+ @intensity = (@intensity + boost).clamp(Constants::MIN_INTENSITY, 1.0)
50
+ signal
51
+ end
52
+
53
+ def decay!
54
+ return if resolved?
55
+
56
+ @intensity = (@intensity - Constants::INTENSITY_DECAY).clamp(Constants::MIN_INTENSITY, 1.0)
57
+ end
58
+
59
+ def adapt!
60
+ return if resolved?
61
+
62
+ accelerated = Constants::INTENSITY_DECAY * ADAPT_DECAY_MULTIPLIER
63
+ @intensity = (@intensity - accelerated).clamp(Constants::MIN_INTENSITY, 1.0)
64
+ end
65
+
66
+ def resolved?
67
+ @intensity <= Constants::MIN_INTENSITY
68
+ end
69
+
70
+ def to_h
71
+ {
72
+ id: @id,
73
+ capability_name: @capability_name,
74
+ capability_domain: @capability_domain,
75
+ removed_at: @removed_at,
76
+ intensity: intensity,
77
+ activation_count: @activation_count,
78
+ last_triggered: @last_triggered,
79
+ state: state,
80
+ resolved: resolved?
81
+ }
82
+ end
83
+
84
+ private
85
+
86
+ def classify_trigger(stimulus, prev_triggered)
87
+ return :stimulus_match if stimulus.is_a?(String) && stimulus.include?(@capability_name.to_s)
88
+
89
+ return :temporal_pattern if prev_triggered && (Time.now.utc - prev_triggered) < 60
90
+
91
+ return :habitual if @activation_count > 10
92
+
93
+ :contextual_association
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitivePhantom
8
+ module Helpers
9
+ class PhantomSignal
10
+ attr_reader :id, :phantom_limb_id, :stimulus, :trigger_type,
11
+ :intensity_at_trigger, :timestamp
12
+
13
+ def initialize(phantom_limb_id:, stimulus:, trigger_type:, intensity_at_trigger:)
14
+ @id = SecureRandom.uuid
15
+ @phantom_limb_id = phantom_limb_id
16
+ @stimulus = stimulus
17
+ @trigger_type = trigger_type
18
+ @intensity_at_trigger = intensity_at_trigger.clamp(0.0, 1.0)
19
+ @timestamp = Time.now.utc
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ id: @id,
25
+ phantom_limb_id: @phantom_limb_id,
26
+ stimulus: @stimulus,
27
+ trigger_type: @trigger_type,
28
+ intensity_at_trigger: @intensity_at_trigger.round(10),
29
+ timestamp: @timestamp
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitivePhantom
6
+ module Runners
7
+ module CognitivePhantom
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def register_removal(capability_name:, capability_domain: :general, engine: nil, **)
12
+ raise ArgumentError, 'capability_name is required' if capability_name.nil? || capability_name.to_s.strip.empty?
13
+
14
+ eng = engine || phantom_engine
15
+ limb = eng.register_removal(capability_name: capability_name, capability_domain: capability_domain)
16
+ return { success: false, error: 'MAX_PHANTOMS limit reached' } unless limb
17
+
18
+ Legion::Logging.debug "[cognitive_phantom] register_removal capability=#{capability_name}"
19
+ { success: true, phantom_id: limb.id, capability_name: capability_name, state: limb.state, intensity: limb.intensity }
20
+ rescue ArgumentError => e
21
+ { success: false, error: e.message }
22
+ end
23
+
24
+ def process_stimulus(stimulus:, domain: :general, engine: nil, **)
25
+ raise ArgumentError, 'stimulus is required' if stimulus.nil?
26
+
27
+ eng = engine || phantom_engine
28
+ fired = eng.process_stimulus(stimulus: stimulus, domain: domain)
29
+ Legion::Logging.debug "[cognitive_phantom] process_stimulus domain=#{domain} fired=#{fired.size}"
30
+ {
31
+ success: true,
32
+ fired_count: fired.size,
33
+ signals: fired.map(&:to_h),
34
+ domain: domain
35
+ }
36
+ rescue ArgumentError => e
37
+ { success: false, error: e.message }
38
+ end
39
+
40
+ def acknowledge_phantom(phantom_id:, engine: nil, **)
41
+ raise ArgumentError, 'phantom_id is required' if phantom_id.nil? || phantom_id.to_s.strip.empty?
42
+
43
+ eng = engine || phantom_engine
44
+ result = eng.acknowledge(phantom_id: phantom_id)
45
+ Legion::Logging.debug "[cognitive_phantom] acknowledge phantom_id=#{phantom_id[0..7]}"
46
+ result.merge(success: result[:acknowledged])
47
+ rescue ArgumentError => e
48
+ { success: false, error: e.message }
49
+ end
50
+
51
+ def phantom_status(engine: nil, **)
52
+ eng = engine || phantom_engine
53
+ report = eng.phantom_activity_report
54
+ Legion::Logging.debug "[cognitive_phantom] phantom_status total=#{report[:total]} active=#{report[:active]}"
55
+ { success: true, **report }
56
+ end
57
+
58
+ def decay_all(engine: nil, **)
59
+ eng = engine || phantom_engine
60
+ resolved_count = eng.decay_all!
61
+ report = eng.phantom_activity_report
62
+ Legion::Logging.debug "[cognitive_phantom] decay_all resolved=#{resolved_count}"
63
+ { success: true, resolved_this_cycle: resolved_count, **report }
64
+ end
65
+
66
+ private
67
+
68
+ def phantom_engine
69
+ @phantom_engine ||= Helpers::PhantomEngine.new
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitivePhantom
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'legion/extensions/cognitive_phantom/version'
5
+ require 'legion/extensions/cognitive_phantom/helpers/constants'
6
+ require 'legion/extensions/cognitive_phantom/helpers/phantom_signal'
7
+ require 'legion/extensions/cognitive_phantom/helpers/phantom_limb'
8
+ require 'legion/extensions/cognitive_phantom/helpers/phantom_engine'
9
+ require 'legion/extensions/cognitive_phantom/runners/cognitive_phantom'
10
+
11
+ module Legion
12
+ module Extensions
13
+ module CognitivePhantom
14
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
15
+ end
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-phantom
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: 'Phantom limb model for AI: ghost signals from removed capabilities fade
27
+ over time as the agent adapts'
28
+ email:
29
+ - matthewdiverson@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".github/workflows/ci.yml"
35
+ - ".gitignore"
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - CLAUDE.md
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - README.md
42
+ - lex-cognitive-phantom.gemspec
43
+ - lib/legion/extensions/cognitive_phantom.rb
44
+ - lib/legion/extensions/cognitive_phantom/client.rb
45
+ - lib/legion/extensions/cognitive_phantom/helpers/constants.rb
46
+ - lib/legion/extensions/cognitive_phantom/helpers/phantom_engine.rb
47
+ - lib/legion/extensions/cognitive_phantom/helpers/phantom_limb.rb
48
+ - lib/legion/extensions/cognitive_phantom/helpers/phantom_signal.rb
49
+ - lib/legion/extensions/cognitive_phantom/runners/cognitive_phantom.rb
50
+ - lib/legion/extensions/cognitive_phantom/version.rb
51
+ homepage: https://github.com/LegionIO/lex-cognitive-phantom
52
+ licenses:
53
+ - MIT
54
+ metadata:
55
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-phantom
56
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-phantom
57
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-phantom
58
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-phantom
59
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-phantom/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 Phantom
78
+ test_files: []