lex-subliminal 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: 942de14ddf12c8a592ed87370978cd88ed2e6e239c67cdcce276b80d59aaa76f
4
+ data.tar.gz: fead526f92b11f15af80d1d21ce18c6065c2f8c319389e3a684f9e13a2a79363
5
+ SHA512:
6
+ metadata.gz: ed24c0d2f0e071cfa8e5f27d00f9a4bc0de45fc652309b319c3b9c0dcc11022e30e2689e097a9ccd1129773ed9affceba65a1d3b398962217d13c27a957cbd6c
7
+ data.tar.gz: 948dbd1b4733e98618010563ca944670348dbb300d86a5a24aa60fa5d26c031aad2ed05520795fb6a7694e047b024186ec92eebf0f60780b3c328bdbc5bc76a7
@@ -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,10 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ pkg
7
+ spec/reports
8
+ tmp
9
+ Gemfile.lock
10
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.4
4
+
5
+ Style/Documentation:
6
+ Enabled: false
7
+
8
+ Naming/PredicateMethod:
9
+ Enabled: false
10
+
11
+ Naming/PredicatePrefix:
12
+ Enabled: false
13
+
14
+ Metrics/ClassLength:
15
+ Max: 150
16
+
17
+ Metrics/MethodLength:
18
+ Max: 25
19
+
20
+ Metrics/AbcSize:
21
+ Max: 25
22
+
23
+ Metrics/ParameterLists:
24
+ Max: 8
25
+ MaxOptionalParameters: 8
26
+
27
+ Layout/HashAlignment:
28
+ EnforcedColonStyle: table
29
+ EnforcedHashRocketStyle: table
30
+
31
+ Metrics/BlockLength:
32
+ Exclude:
33
+ - spec/**/*
34
+
35
+ Style/OneClassPerFile:
36
+ Exclude:
37
+ - spec/spec_helper.rb
data/CLAUDE.md ADDED
@@ -0,0 +1,120 @@
1
+ # lex-subliminal
2
+
3
+ **Level 3 Leaf Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Gem**: `lex-subliminal`
6
+ - **Version**: `0.1.0`
7
+ - **Namespace**: `Legion::Extensions::Subliminal`
8
+
9
+ ## Purpose
10
+
11
+ Models subliminal (below-threshold) influences on cognition. Subliminal traces carry an activation level that is structurally prevented from reaching the conscious threshold — they cannot be consciously attended to, but they exert a measurable influence on named cognitive targets (attention, memory, decision, emotion, etc.). Traces decay over time; those that fall to near-zero are extinct. Useful for modeling background priming, emotional contagion, implicit learning, and attentional drift.
12
+
13
+ ## Gem Info
14
+
15
+ - **Gem name**: `lex-subliminal`
16
+ - **License**: MIT
17
+ - **Ruby**: >= 3.4
18
+ - **No runtime dependencies** beyond the Legion framework
19
+
20
+ ## File Structure
21
+
22
+ ```
23
+ lib/legion/extensions/subliminal/
24
+ version.rb # VERSION = '0.1.0'
25
+ helpers/
26
+ constants.rb # thresholds, decay rates, influence strength, max caps, type/target lists, labels
27
+ subliminal_trace.rb # SubliminalTrace class — single below-threshold activation trace
28
+ subliminal_engine.rb # SubliminalEngine class — collection with bulk influence processing
29
+ runners/
30
+ subliminal.rb # Runners::Subliminal module — all public runner methods
31
+ client.rb # Client class including Runners::Subliminal
32
+ ```
33
+
34
+ ## Key Constants
35
+
36
+ | Constant | Value | Purpose |
37
+ |---|---|---|
38
+ | `CONSCIOUS_THRESHOLD` | 0.4 | The threshold subliminal traces are prevented from reaching |
39
+ | `SUBLIMINAL_CEILING` | 0.39 | Hard cap on trace activation (just below CONSCIOUS_THRESHOLD) |
40
+ | `SUBLIMINAL_FLOOR` | 0.02 | Minimum activation before approaching extinction |
41
+ | `EXTINCTION_THRESHOLD` | 0.01 | At or below this, trace is considered extinct |
42
+ | `DEFAULT_ACTIVATION` | 0.2 | Starting activation for new traces |
43
+ | `ACTIVATION_BOOST` | 0.08 | Activation increase per `boost!` call |
44
+ | `ACTIVATION_DECAY` | 0.015 | Per-tick activation decrease |
45
+ | `INFLUENCE_STRENGTH` | 0.05 | Magnitude multiplier: `activation * INFLUENCE_STRENGTH` |
46
+ | `MAX_INFLUENCE_PER_DOMAIN` | 0.3 | Maximum total influence on any single domain |
47
+ | `MAX_TOTAL_INFLUENCE` | 0.5 | Maximum combined influence across all targets |
48
+ | `TRACE_TYPES` | 8 symbols | `:priming`, `:emotional`, `:procedural`, `:semantic`, `:inhibitory`, `:motivational`, `:contextual`, `:evaluative` |
49
+ | `INFLUENCE_TARGETS` | 8 symbols | `:attention`, `:memory`, `:decision`, `:emotion`, `:action`, `:perception`, `:language`, `:reasoning` |
50
+
51
+ ## Helpers
52
+
53
+ ### `Helpers::SubliminalTrace`
54
+
55
+ Single below-threshold activation trace.
56
+
57
+ - `initialize(id:, content:, trace_type:, domain: :general, target: :attention, activation: DEFAULT_ACTIVATION)` — clamps activation to SUBLIMINAL_CEILING on init
58
+ - `boost!(amount = ACTIVATION_BOOST)` — increments activation; hard-clamps to SUBLIMINAL_CEILING (never reaches CONSCIOUS_THRESHOLD)
59
+ - `decay!` — decrements activation by ACTIVATION_DECAY; floors at 0.0
60
+ - `exert_influence!` — decrements activation slightly, returns `influence_magnitude`
61
+ - `influence_magnitude` — `activation * INFLUENCE_STRENGTH`
62
+ - `near_threshold?` — activation between 0.3 and 0.39
63
+ - `active?` — activation > EXTINCTION_THRESHOLD
64
+ - `extinct?` — activation <= EXTINCTION_THRESHOLD
65
+ - `potent?` — activation >= 0.2
66
+ - `breached_threshold?` — activation >= 0.4 (anomaly: should never occur due to ceiling, logged if detected)
67
+ - `activation_label` — `:extinct`, `:faint`, `:weak`, `:moderate`, `:strong` based on activation
68
+
69
+ ### `Helpers::SubliminalEngine`
70
+
71
+ Collection of SubliminalTrace objects with bulk influence processing.
72
+
73
+ - `initialize` — traces hash, keyed by trace id
74
+ - `create_trace(content:, trace_type:, domain: :general, target: :attention, activation: DEFAULT_ACTIVATION)` — rejects invalid types/targets; appends trace
75
+ - `boost_trace(trace_id, amount: ACTIVATION_BOOST)` — calls `trace.boost!`
76
+ - `process_influences!` — all active traces call `exert_influence!`; generates InfluenceEvent objects per trace; returns events array
77
+ - `decay_all!` — decays all traces; removes extinct ones
78
+ - `active_traces` — all with `active? == true`
79
+ - `near_threshold_traces` — all with `near_threshold? == true`
80
+ - `potent_traces` — all with `potent? == true`
81
+ - `traces_by_domain(domain)` — filter by domain
82
+ - `traces_by_type(trace_type)` — filter by trace_type
83
+ - `traces_by_target(target)` — filter by influence target
84
+ - `influence_on(target:)` — sum of influence_magnitude across all active traces targeting `target`; capped at MAX_INFLUENCE_PER_DOMAIN
85
+ - `domain_saturation(domain)` — total influence magnitude across all traces in domain
86
+ - `overall_subliminal_load` — sum of all active trace activations; capped at MAX_TOTAL_INFLUENCE
87
+ - `strongest_traces(limit: 5)` — sorted by activation descending
88
+ - `breached_traces` — any traces with `breached_threshold? == true` (anomaly detection)
89
+ - `subliminal_report` — summary: trace counts, overall load, influence per target, near-threshold count
90
+
91
+ ## Runners
92
+
93
+ All runners are in `Runners::Subliminal`. The `Client` includes this module. Note: the runner uses `@default_engine` internally (not `@engine`).
94
+
95
+ | Runner | Parameters | Returns |
96
+ |---|---|---|
97
+ | `create_subliminal_trace` | `content:, trace_type:, domain: :general, target: :attention, activation: DEFAULT_ACTIVATION` | `{ success:, trace_id:, activation:, trace_type: }` |
98
+ | `boost_trace` | `trace_id:, amount: ACTIVATION_BOOST` | `{ success:, trace_id:, activation: }` |
99
+ | `process_influences` | (none) | `{ success:, events:, count: }` — runs `process_influences!` |
100
+ | `decay_all` | (none) | `{ success:, active_count:, removed_count: }` |
101
+ | `active_traces` | (none) | `{ success:, traces:, count: }` |
102
+ | `near_threshold` | (none) | `{ success:, traces:, count: }` — traces approaching conscious threshold |
103
+ | `influence_on` | `target:` | `{ success:, target:, influence: }` |
104
+ | `subliminal_status` | (none) | Full `SubliminalEngine#subliminal_report` hash |
105
+
106
+ ## Integration Points
107
+
108
+ - **lex-tick / lex-cortex**: `process_influences` wired as a tick phase handler to apply subliminal influence each cycle; influence events can be forwarded to target extensions (e.g., influence on `:attention` biases the sensory gating thresholds)
109
+ - **lex-sensory-gating**: subliminal influence on `:perception` can modulate gate thresholds without the agent's explicit awareness
110
+ - **lex-emotion**: `:emotional` trace type targeting `:emotion` models emotional contagion and implicit mood induction
111
+ - **lex-dream**: dream consolidation may create subliminal traces from unresolved material; `:contextual` traces targeting `:decision` model incubation effects
112
+ - **lex-semantic-priming**: near-threshold traces can trigger priming events when their target aligns with active concept nodes
113
+
114
+ ## Development Notes
115
+
116
+ - `SUBLIMINAL_CEILING = 0.39` is a hard cap enforced in both `initialize` and `boost!` — traces structurally cannot reach CONSCIOUS_THRESHOLD, making the subliminal/conscious boundary a guaranteed architectural constraint
117
+ - `breached_threshold?` >= 0.4 should never trigger in normal operation; it is an anomaly detection flag
118
+ - `process_influences!` slightly decrements activation on each call — sustained influence gradually weakens the trace
119
+ - `@default_engine` naming (vs `@engine`) is specific to this runner module; specs and injection must reference `@default_engine`
120
+ - InfluenceEvent objects from `process_influences!` are plain structs (trace_id, target, magnitude, domain) — callers can route them to downstream handlers by target
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'
9
+ gem 'rubocop-rspec'
10
+
11
+ gem 'legion-gaia', path: '../../legion-gaia'
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # lex-subliminal
2
+
3
+ Subliminal influence modeling for LegionIO cognitive agents. Traces operate below the conscious threshold but exert measurable influence on attention, memory, decision-making, and emotion.
4
+
5
+ ## What It Does
6
+
7
+ `lex-subliminal` maintains a set of activation traces that are structurally capped below the conscious threshold (0.39 max, threshold at 0.4). These traces cannot be consciously attended to but exert influence on named cognitive targets each tick. Traces decay over time; those that reach near-zero are extinct.
8
+
9
+ - **Trace types**: `:priming`, `:emotional`, `:procedural`, `:semantic`, `:inhibitory`, `:motivational`, `:contextual`, `:evaluative`
10
+ - **Influence targets**: `:attention`, `:memory`, `:decision`, `:emotion`, `:action`, `:perception`, `:language`, `:reasoning`
11
+ - **Activation ceiling**: 0.39 — structurally enforced; traces can never breach conscious threshold
12
+ - **Influence magnitude**: `activation * 0.05` per trace per tick
13
+ - **Potent traces**: activation >= 0.2; near-threshold traces: activation 0.3–0.39
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ require 'legion/extensions/subliminal'
19
+
20
+ client = Legion::Extensions::Subliminal::Client.new
21
+
22
+ # Create a subliminal trace
23
+ result = client.create_subliminal_trace(
24
+ content: 'unresolved conflict',
25
+ trace_type: :emotional,
26
+ domain: :relationships,
27
+ target: :decision,
28
+ activation: 0.25
29
+ )
30
+ trace_id = result[:trace_id]
31
+
32
+ # Boost a trace (activation can never exceed 0.39)
33
+ client.boost_trace(trace_id: trace_id, amount: 0.08)
34
+
35
+ # Process influences (called each tick)
36
+ client.process_influences
37
+ # => { events: [{ target: :decision, magnitude: 0.017, ... }], count: 1 }
38
+
39
+ # Check influence on a specific target
40
+ client.influence_on(target: :decision)
41
+ # => { target: :decision, influence: 0.017 }
42
+
43
+ # Near-threshold traces (approaching conscious awareness)
44
+ client.near_threshold
45
+ # => { traces: [...], count: 0 }
46
+
47
+ # Decay all traces
48
+ client.decay_all
49
+ # => { active_count: 1, removed_count: 0 }
50
+
51
+ # Full status report
52
+ client.subliminal_status
53
+ # => { trace_count:, active_count:, overall_load:, influence_per_target: { ... }, ... }
54
+ ```
55
+
56
+ ## Development
57
+
58
+ ```bash
59
+ bundle install
60
+ bundle exec rspec
61
+ bundle exec rubocop
62
+ ```
63
+
64
+ ## License
65
+
66
+ MIT
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/subliminal/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-subliminal'
7
+ spec.version = Legion::Extensions::Subliminal::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+ spec.summary = 'Subliminal activation and below-threshold influence for LegionIO agents'
11
+ spec.description = 'Models below-threshold cognitive traces that influence behavior ' \
12
+ 'without reaching conscious awareness in the LegionIO architecture'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-subliminal'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata = {
18
+ 'homepage_uri' => spec.homepage,
19
+ 'source_code_uri' => spec.homepage,
20
+ 'documentation_uri' => "#{spec.homepage}/blob/origin/README.md",
21
+ 'changelog_uri' => "#{spec.homepage}/blob/origin/CHANGELOG.md",
22
+ 'bug_tracker_uri' => "#{spec.homepage}/issues",
23
+ 'rubygems_mfa_required' => 'true'
24
+ }
25
+
26
+ spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
27
+ spec.require_paths = ['lib']
28
+ spec.add_development_dependency 'legion-gaia'
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Subliminal
6
+ class Client
7
+ include Runners::Subliminal
8
+
9
+ def initialize
10
+ @default_engine = Helpers::SubliminalEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Subliminal
6
+ module Helpers
7
+ module Constants
8
+ MAX_TRACES = 500
9
+ MAX_INFLUENCES = 1000
10
+
11
+ # Threshold boundaries
12
+ CONSCIOUS_THRESHOLD = 0.4
13
+ SUBLIMINAL_CEILING = 0.39
14
+ SUBLIMINAL_FLOOR = 0.02
15
+ EXTINCTION_THRESHOLD = 0.01
16
+
17
+ # Dynamics
18
+ DEFAULT_ACTIVATION = 0.2
19
+ ACTIVATION_BOOST = 0.08
20
+ ACTIVATION_DECAY = 0.015
21
+ INFLUENCE_STRENGTH = 0.05
22
+ ACCUMULATION_RATE = 0.03
23
+
24
+ # Influence caps
25
+ MAX_INFLUENCE_PER_DOMAIN = 0.3
26
+ MAX_TOTAL_INFLUENCE = 0.5
27
+
28
+ TRACE_TYPES = %i[
29
+ perceptual emotional associative procedural
30
+ semantic motivational aversive appetitive
31
+ ].freeze
32
+
33
+ INFLUENCE_TARGETS = %i[
34
+ attention emotion decision memory
35
+ preference avoidance approach valence
36
+ ].freeze
37
+
38
+ ACTIVATION_LABELS = {
39
+ (0.3...0.4) => :near_threshold,
40
+ (0.2...0.3) => :moderate,
41
+ (0.1...0.2) => :faint,
42
+ (0.02...0.1) => :trace,
43
+ (..0.02) => :extinct
44
+ }.freeze
45
+
46
+ INFLUENCE_LABELS = {
47
+ (0.2..) => :strong,
48
+ (0.1...0.2) => :moderate,
49
+ (0.05...0.1) => :subtle,
50
+ (0.01...0.05) => :minimal,
51
+ (..0.01) => :none
52
+ }.freeze
53
+
54
+ SATURATION_LABELS = {
55
+ (0.8..) => :saturated,
56
+ (0.6...0.8) => :heavy,
57
+ (0.4...0.6) => :moderate,
58
+ (0.2...0.4) => :light,
59
+ (..0.2) => :clear
60
+ }.freeze
61
+
62
+ def self.label_for(labels, value)
63
+ labels.each { |range, label| return label if range.cover?(value) }
64
+ :unknown
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Subliminal
8
+ module Helpers
9
+ class InfluenceEvent
10
+ include Constants
11
+
12
+ attr_reader :id, :trace_id, :target, :magnitude, :domain, :created_at
13
+
14
+ def initialize(trace_id:, target:, magnitude:, domain: :general)
15
+ @id = SecureRandom.uuid
16
+ @trace_id = trace_id
17
+ @target = target.to_sym
18
+ @magnitude = magnitude.to_f.clamp(0.0, MAX_INFLUENCE_PER_DOMAIN).round(10)
19
+ @domain = domain.to_sym
20
+ @created_at = Time.now
21
+ end
22
+
23
+ def significant? = @magnitude >= 0.05
24
+ def subtle? = @magnitude < 0.05 && @magnitude > 0.0
25
+
26
+ def to_h
27
+ {
28
+ id: @id,
29
+ trace_id: @trace_id,
30
+ target: @target,
31
+ magnitude: @magnitude,
32
+ domain: @domain,
33
+ created_at: @created_at.iso8601
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Subliminal
6
+ module Helpers
7
+ class SubliminalEngine
8
+ include Constants
9
+
10
+ def initialize
11
+ @traces = {}
12
+ @influences = []
13
+ end
14
+
15
+ def create_trace(content:, trace_type: :associative, domain: :general, activation: DEFAULT_ACTIVATION,
16
+ influence_target: :attention)
17
+ prune_extinct
18
+ trace = SubliminalTrace.new(content: content, trace_type: trace_type, domain: domain,
19
+ activation: activation, influence_target: influence_target)
20
+ @traces[trace.id] = trace
21
+ trace
22
+ end
23
+
24
+ def boost_trace(trace_id:, amount: ACTIVATION_BOOST)
25
+ trace = @traces[trace_id]
26
+ return nil unless trace
27
+
28
+ trace.boost!(amount: amount)
29
+ trace
30
+ end
31
+
32
+ def process_influences!
33
+ active = active_traces
34
+ events = []
35
+ active.each do |trace|
36
+ mag = trace.exert_influence!
37
+ next if mag <= 0.0
38
+
39
+ event = InfluenceEvent.new(trace_id: trace.id, target: trace.influence_target,
40
+ magnitude: mag, domain: trace.domain)
41
+ events << event
42
+ @influences << event
43
+ end
44
+ prune_influences
45
+ { influences_generated: events.size, events: events.map(&:to_h) }
46
+ end
47
+
48
+ def decay_all!
49
+ @traces.each_value(&:decay!)
50
+ prune_extinct
51
+ { traces_remaining: @traces.size }
52
+ end
53
+
54
+ def active_traces = @traces.values.select(&:active?)
55
+ def near_threshold_traces = @traces.values.select(&:near_threshold?)
56
+ def potent_traces = @traces.values.select(&:potent?)
57
+ def extinct_traces = @traces.values.select(&:extinct?)
58
+
59
+ def traces_by_domain(domain:)
60
+ @traces.values.select { |t| t.domain == domain.to_sym }
61
+ end
62
+
63
+ def traces_by_type(trace_type:)
64
+ @traces.values.select { |t| t.trace_type == trace_type.to_sym }
65
+ end
66
+
67
+ def traces_by_target(target:)
68
+ @traces.values.select { |t| t.influence_target == target.to_sym }
69
+ end
70
+
71
+ def influence_on(target:)
72
+ recent = @influences.last(100).select { |e| e.target == target.to_sym }
73
+ return 0.0 if recent.empty?
74
+
75
+ recent.sum(&:magnitude).clamp(0.0, MAX_TOTAL_INFLUENCE).round(10)
76
+ end
77
+
78
+ def domain_saturation(domain:)
79
+ domain_traces = traces_by_domain(domain: domain)
80
+ return 0.0 if domain_traces.empty?
81
+
82
+ total_activation = domain_traces.sum(&:activation)
83
+ (total_activation / (domain_traces.size * SUBLIMINAL_CEILING)).clamp(0.0, 1.0).round(10)
84
+ end
85
+
86
+ def overall_subliminal_load
87
+ return 0.0 if @traces.empty?
88
+
89
+ active = active_traces
90
+ return 0.0 if active.empty?
91
+
92
+ (active.sum(&:activation) / (active.size * SUBLIMINAL_CEILING)).clamp(0.0, 1.0).round(10)
93
+ end
94
+
95
+ def saturation_label = Constants.label_for(SATURATION_LABELS, overall_subliminal_load)
96
+
97
+ def strongest_traces(limit: 5) = @traces.values.sort_by { |t| -t.activation }.first(limit)
98
+
99
+ def breached_traces
100
+ @traces.values.select(&:breached_threshold?)
101
+ end
102
+
103
+ def subliminal_report
104
+ {
105
+ total_traces: @traces.size,
106
+ active_count: active_traces.size,
107
+ near_threshold: near_threshold_traces.size,
108
+ potent_count: potent_traces.size,
109
+ total_influences: @influences.size,
110
+ subliminal_load: overall_subliminal_load,
111
+ saturation_label: saturation_label,
112
+ breached_count: breached_traces.size,
113
+ strongest: strongest_traces(limit: 3).map(&:to_h)
114
+ }
115
+ end
116
+
117
+ def to_h
118
+ {
119
+ total_traces: @traces.size,
120
+ active: active_traces.size,
121
+ subliminal_load: overall_subliminal_load,
122
+ total_influences: @influences.size
123
+ }
124
+ end
125
+
126
+ private
127
+
128
+ def prune_extinct
129
+ return if @traces.size < MAX_TRACES
130
+
131
+ @traces.reject! { |_, t| t.extinct? }
132
+ return unless @traces.size >= MAX_TRACES
133
+
134
+ weakest = @traces.values.min_by(&:activation)
135
+ @traces.delete(weakest.id) if weakest
136
+ end
137
+
138
+ def prune_influences
139
+ @influences.shift while @influences.size > MAX_INFLUENCES
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Subliminal
8
+ module Helpers
9
+ class SubliminalTrace
10
+ include Constants
11
+
12
+ attr_reader :id, :content, :trace_type, :domain, :original_activation,
13
+ :influence_target, :created_at, :influence_count
14
+ attr_accessor :activation
15
+
16
+ def initialize(content:, trace_type: :associative, domain: :general, activation: DEFAULT_ACTIVATION,
17
+ influence_target: :attention)
18
+ @id = SecureRandom.uuid
19
+ @content = content.to_s
20
+ @trace_type = valid_trace_type(trace_type)
21
+ @domain = domain.to_sym
22
+ @activation = activation.to_f.clamp(SUBLIMINAL_FLOOR, SUBLIMINAL_CEILING).round(10)
23
+ @original_activation = @activation
24
+ @influence_target = valid_influence_target(influence_target)
25
+ @influence_count = 0
26
+ @created_at = Time.now
27
+ end
28
+
29
+ def boost!(amount: ACTIVATION_BOOST)
30
+ @activation = (@activation + amount).clamp(0.0, SUBLIMINAL_CEILING).round(10)
31
+ self
32
+ end
33
+
34
+ def decay!
35
+ @activation = (@activation - ACTIVATION_DECAY).clamp(0.0, 1.0).round(10)
36
+ self
37
+ end
38
+
39
+ def exert_influence!
40
+ @influence_count += 1
41
+ @activation = (@activation - (INFLUENCE_STRENGTH * 0.5)).clamp(0.0, 1.0).round(10)
42
+ influence_magnitude
43
+ end
44
+
45
+ def influence_magnitude
46
+ (@activation * INFLUENCE_STRENGTH).clamp(0.0, MAX_INFLUENCE_PER_DOMAIN).round(10)
47
+ end
48
+
49
+ def near_threshold? = @activation >= 0.3 && @activation < CONSCIOUS_THRESHOLD
50
+ def active? = @activation >= SUBLIMINAL_FLOOR
51
+ def extinct? = @activation < EXTINCTION_THRESHOLD
52
+ def potent? = @activation >= 0.2
53
+
54
+ def breached_threshold?
55
+ @activation >= CONSCIOUS_THRESHOLD
56
+ end
57
+
58
+ def persistence = (@activation / @original_activation).clamp(0.0, 1.0).round(10)
59
+ def activation_label = Constants.label_for(ACTIVATION_LABELS, @activation)
60
+ def influence_label = Constants.label_for(INFLUENCE_LABELS, influence_magnitude)
61
+
62
+ def to_h
63
+ {
64
+ id: @id,
65
+ content: @content,
66
+ trace_type: @trace_type,
67
+ domain: @domain,
68
+ activation: @activation,
69
+ original_activation: @original_activation,
70
+ influence_target: @influence_target,
71
+ influence_count: @influence_count,
72
+ influence_magnitude: influence_magnitude,
73
+ near_threshold: near_threshold?,
74
+ active: active?,
75
+ extinct: extinct?,
76
+ activation_label: activation_label,
77
+ persistence: persistence,
78
+ created_at: @created_at.iso8601
79
+ }
80
+ end
81
+
82
+ private
83
+
84
+ def valid_trace_type(type)
85
+ sym = type.to_sym
86
+ TRACE_TYPES.include?(sym) ? sym : :associative
87
+ end
88
+
89
+ def valid_influence_target(target)
90
+ sym = target.to_sym
91
+ INFLUENCE_TARGETS.include?(sym) ? sym : :attention
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end