lex-cognitive-echo-chamber 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: 9b14c32b92c7a3820386d857c0c8f447fc85ac2d0b4054d7a7c1caad9438eae0
4
+ data.tar.gz: 6fa1fb07bfa0c474f3e60d0ed47dc4dac10c2dbaa0f244ea251a8ce3f83af24e
5
+ SHA512:
6
+ metadata.gz: 37da8e8f476ddb16589aa2ae0fb8adfe81795c16ac1e692ff72e5efb1e04c8d7de1d707b7161f5d959aa26dc094cd4d74446db682e1bb8260dfb47692c8c8944
7
+ data.tar.gz: '0841afec55c9c980ded78f596e9ade8f64340264ee4759e2a9b5c20c271680f04510919e2c2b8ef7ce7aca273134c9410dece066e58cc8412a985cecd7ef62a3'
@@ -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,2 @@
1
+ .rspec_status
2
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,61 @@
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: 150
24
+
25
+ Metrics/BlockLength:
26
+ Max: 40
27
+ Exclude:
28
+ - 'spec/**/*'
29
+
30
+ Metrics/AbcSize:
31
+ Max: 25
32
+
33
+ Metrics/ParameterLists:
34
+ Max: 8
35
+ MaxOptionalParameters: 8
36
+
37
+ Metrics/CyclomaticComplexity:
38
+ Max: 15
39
+
40
+ Metrics/PerceivedComplexity:
41
+ Max: 17
42
+
43
+ Style/Documentation:
44
+ Enabled: false
45
+
46
+ Style/FrozenStringLiteralComment:
47
+ Enabled: true
48
+ EnforcedStyle: always
49
+
50
+ Naming/FileName:
51
+ Enabled: false
52
+
53
+ Naming/PredicateMethod:
54
+ Enabled: false
55
+
56
+ Naming/PredicatePrefix:
57
+ Enabled: false
58
+
59
+ Style/OneClassPerFile:
60
+ Exclude:
61
+ - 'spec/spec_helper.rb'
data/CLAUDE.md ADDED
@@ -0,0 +1,82 @@
1
+ # lex-cognitive-echo-chamber
2
+
3
+ **Level 3 Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
6
+
7
+ ## Purpose
8
+
9
+ Models self-reinforcing belief loops — the cognitive echo chamber phenomenon where ideas amplify themselves without external correction. Unlike `lex-cognitive-echo` (which tracks individual transient residual activations), the echo chamber models a structured enclosure: echoes of type belief/assumption/bias/hypothesis/conviction circulate inside a Chamber with defined wall thickness. Disruption requires force exceeding the wall. Sealed chambers (wall_thickness >= 0.8) are nearly impervious to external correction.
10
+
11
+ ## Gem Info
12
+
13
+ - **Gem name**: `lex-cognitive-echo-chamber`
14
+ - **Version**: `0.1.0`
15
+ - **Module**: `Legion::Extensions::CognitiveEchoChamber`
16
+ - **Ruby**: `>= 3.4`
17
+ - **License**: MIT
18
+
19
+ ## File Structure
20
+
21
+ ```
22
+ lib/legion/extensions/cognitive_echo_chamber/
23
+ cognitive_echo_chamber.rb
24
+ version.rb
25
+ client.rb
26
+ helpers/
27
+ constants.rb
28
+ chamber_engine.rb
29
+ echo.rb
30
+ chamber.rb
31
+ runners/
32
+ cognitive_echo_chamber.rb
33
+ ```
34
+
35
+ ## Key Constants
36
+
37
+ From `helpers/constants.rb`:
38
+
39
+ - `ECHO_TYPES` — `%i[belief assumption bias hypothesis conviction]`
40
+ - `CHAMBER_STATES` — `%i[forming resonating saturated disrupted collapsed]`
41
+ - `MAX_ECHOES` = `500`, `MAX_CHAMBERS` = `50`
42
+ - `AMPLIFICATION_RATE` = `0.1`, `DECAY_RATE` = `0.02`
43
+ - `DISRUPTION_THRESHOLD` = `0.7` (resonance level at which a chamber is considered saturated; also the echo amplitude threshold for `resonate?`)
44
+ - `SEALED_THRESHOLD` = `0.8` (wall_thickness >= this = sealed, nearly impossible to disrupt)
45
+ - `POROUS_THRESHOLD` = `0.3` (wall_thickness <= this = porous, easily disrupted)
46
+ - `BREAKTHROUGH_BONUS` = `0.15` (fraction of breakthrough force subtracted from wall_thickness on successful disruption)
47
+ - `SILENT_THRESHOLD` = `0.05`, `DEFAULT_AMPLITUDE` = `0.5`, `DEFAULT_WALL_THICKNESS` = `0.5`
48
+ - `RESONANCE_LABELS` — range hash: `0.8+` = `:thunderous`, `0.6` = `:resonant`, `0.4` = `:humming`, `0.2` = `:fading`, below = `:silent`
49
+ - `AMPLIFICATION_LABELS` — `0.8+` = `:deafening`, `0.6` = `:loud`, `0.4` = `:moderate`, `0.2` = `:quiet`, below = `:muted`
50
+ - `CHAMBER_STATE_LABELS` — human-readable descriptions of each chamber state
51
+
52
+ ## Runners
53
+
54
+ All methods in `Runners::CognitiveEchoChamber`:
55
+
56
+ - `create_echo(content:, echo_type: :belief, domain: :general, source_agent: nil, amplitude: 0.5, engine: nil)` — creates a new echo in the pool; validates content non-empty; invalid echo_type defaults to `:belief`
57
+ - `create_chamber(label:, domain: :general, wall_thickness: 0.5, engine: nil)` — creates a new named chamber enclosure
58
+ - `amplify(echo_id:, rate: 0.1, engine: nil)` — increases echo amplitude; also increments echo frequency counter
59
+ - `disrupt(chamber_id:, force:, engine: nil)` — attempts to break through a chamber's walls; fails if force <= wall_thickness; success reduces wall_thickness by `breakthrough * BREAKTHROUGH_BONUS` and dampens all enclosed echoes by the breakthrough amount
60
+ - `list_echoes(echo_type: nil, domain: nil, engine: nil)` — returns active echoes; optionally filtered by type and/or domain
61
+ - `chamber_status(engine: nil)` — full report: total echoes, active echoes, resonating echoes, total chambers, sealed/porous counts, disruption count, loudest 3 echoes
62
+
63
+ ## Helpers
64
+
65
+ - `ChamberEngine` — manages `@echoes` hash and `@chambers` hash. `decay_all!` applies `DECAY_RATE` to all echoes and prunes silent ones. `add_echo_to_chamber(echo_id:, chamber_id:)` links an echo into a chamber. `disruption_history` returns a copy of the disruption log. Pruning: on overflow, silent echoes removed first; if still over limit, the faintest echo removed.
66
+ - `Echo` — amplitude-tracked belief unit. `amplify!(rate)` increments both amplitude and frequency. `dampen!(rate)` reduces amplitude. Predicates: `resonate?` (amplitude >= 0.7), `fading?` (amplitude <= 0.3), `silent?` (amplitude <= 0.05). `frequency_label` uses `RESONANCE_LABELS` against a normalized frequency score (frequency / 20, capped at 1.0).
67
+ - `Chamber` — enclosure with `wall_thickness`, `resonance_frequency`, `state`. `add_echo(echo)` adds to internal echo pool and recalculates resonance. `amplify_all!(rate)` amplifies every enclosed echo. `disrupt!(force)` returns `{ success: false }` if force <= wall_thickness, otherwise reduces wall and dampens echoes. `resonance_frequency` = ratio of resonating echoes to total echoes. State transitions: `:forming` -> `:resonating` (resonance >= 0.3) -> `:saturated` (resonance >= 0.7); disruption locks state to `:disrupted`.
68
+
69
+ ## Integration Points
70
+
71
+ - `lex-cognitive-echo` models individual transient residual activations; `lex-cognitive-echo-chamber` models the structural enclosure effect where similar echoes reinforce each other and resist correction.
72
+ - `disrupt` is the mechanism for injecting counter-evidence. Callers from `lex-tick` can call `disrupt` with external input force to model perspective-challenging interactions.
73
+ - `source_agent` on echoes links chamber beliefs back to the originating agent — useful for `lex-mesh` multi-agent scenarios where one agent's beliefs propagate into another's echo chamber.
74
+ - `chamber_status` resonating/sealed metrics can be surfaced during `lex-tick`'s `post_tick_reflection` phase to flag cognitive rigidity.
75
+
76
+ ## Development Notes
77
+
78
+ - Chambers and echoes are separate pools — an echo must be explicitly added to a chamber via `add_echo_to_chamber`. Creating an echo does not auto-enroll it in any chamber.
79
+ - `Chamber#disrupt!` reduces `wall_thickness` by `breakthrough * BREAKTHROUGH_BONUS`, not by the full breakthrough amount — chambers become easier to disrupt over time but never instantly destroyed.
80
+ - Chamber state transitions are blocked once state is `:disrupted` or `:collapsed` — the engine must explicitly manage transition out of these states.
81
+ - `resonance_frequency` on a Chamber is the ratio of `resonate?` echoes to total echoes — distinct from the global engine's `resonating_echoes` count.
82
+ - `Echo#frequency` counts amplify calls, not time — it is a repetition counter, not a temporal frequency.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :test do
8
+ gem 'rspec', '~> 3.13'
9
+ gem 'rubocop', '~> 1.75', require: false
10
+ gem 'rubocop-rspec', require: false
11
+ end
12
+
13
+ gem 'legion-gaia', path: '../../legion-gaia'
data/Gemfile.lock ADDED
@@ -0,0 +1,78 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lex-cognitive-echo-chamber (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-echo-chamber!
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,57 @@
1
+ # lex-cognitive-echo-chamber
2
+
3
+ Self-reinforcing belief loop modeling for the LegionIO cognitive architecture. Tracks how beliefs, assumptions, and biases amplify inside an enclosed cognitive space — and models how external disruption can break through chamber walls.
4
+
5
+ ## What It Does
6
+
7
+ An echo chamber is a named enclosure with a wall thickness that resists external input. Echoes (of type belief, assumption, bias, hypothesis, or conviction) are created in a shared pool and can be added to a chamber. Echoes inside a chamber amplify each other — the more a belief echoes, the stronger its amplitude grows. Disruption requires applying a force that exceeds the wall thickness; a successful disruption reduces the wall (making future disruptions easier) and dampens enclosed echoes.
8
+
9
+ Chambers progress through states: `forming` (sparse) -> `resonating` (active reinforcement) -> `saturated` (maximum self-reinforcement). A sealed chamber (wall_thickness >= 0.8) is nearly impervious to correction. A porous chamber (wall_thickness <= 0.3) absorbs external input readily.
10
+
11
+ ## Usage
12
+
13
+ ```ruby
14
+ client = Legion::Extensions::CognitiveEchoChamber::Client.new
15
+
16
+ # Create an echo in the pool
17
+ result = client.create_echo(
18
+ content: 'all uncertainty should be resolved before acting',
19
+ echo_type: :belief,
20
+ domain: :decision_making
21
+ )
22
+ echo_id = result[:echo][:id]
23
+
24
+ # Create a chamber enclosure
25
+ chamber_result = client.create_chamber(
26
+ label: 'conservative_bias',
27
+ domain: :decision_making,
28
+ wall_thickness: 0.6
29
+ )
30
+ chamber_id = chamber_result[:chamber][:id]
31
+
32
+ # Amplify the echo — each call increases amplitude and frequency count
33
+ client.amplify(echo_id: echo_id)
34
+
35
+ # Attempt disruption with external counter-evidence
36
+ client.disrupt(chamber_id: chamber_id, force: 0.8)
37
+ # Returns: { success: true, breakthrough: 0.2, wall_remaining: 0.57 }
38
+ # Or: { success: false, reason: 'insufficient_force' }
39
+
40
+ # Query echoes
41
+ client.list_echoes(echo_type: :belief, domain: :decision_making)
42
+
43
+ # Status report
44
+ client.chamber_status
45
+ ```
46
+
47
+ ## Development
48
+
49
+ ```bash
50
+ bundle install
51
+ bundle exec rspec
52
+ bundle exec rubocop
53
+ ```
54
+
55
+ ## License
56
+
57
+ MIT
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_echo_chamber/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-echo-chamber'
7
+ spec.version = Legion::Extensions::CognitiveEchoChamber::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'Cognitive echo chamber dynamics for LegionIO agentic architecture'
12
+ spec.description = 'Models cognitive echo chambers — when thoughts reinforce themselves without ' \
13
+ 'external challenge, creating self-reinforcing belief loops. Tracks amplification, ' \
14
+ 'resonance, and breakthrough events when external input disrupts the echo.'
15
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-echo-chamber'
16
+ spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 3.4'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-echo-chamber'
21
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-echo-chamber/blob/master/README.md'
22
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-echo-chamber/blob/master/CHANGELOG.md'
23
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-echo-chamber/issues'
24
+ spec.metadata['rubygems_mfa_required'] = 'true'
25
+
26
+ spec.files = Dir.chdir(__dir__) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
28
+ end
29
+ spec.require_paths = ['lib']
30
+ spec.add_development_dependency 'legion-gaia'
31
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEchoChamber
6
+ class Client
7
+ include Runners::CognitiveEchoChamber
8
+
9
+ def initialize(engine: nil)
10
+ @default_engine = engine || Helpers::ChamberEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveEchoChamber
8
+ module Helpers
9
+ class Chamber
10
+ include Constants
11
+
12
+ attr_reader :id, :label, :domain, :state, :wall_thickness,
13
+ :resonance_frequency, :created_at, :disruption_count
14
+
15
+ def initialize(label:, domain: :general, wall_thickness: DEFAULT_WALL_THICKNESS)
16
+ @id = SecureRandom.uuid
17
+ @label = label
18
+ @domain = domain.to_sym
19
+ @state = :forming
20
+ @wall_thickness = wall_thickness.to_f.clamp(0.0, 1.0).round(10)
21
+ @resonance_frequency = 0.0
22
+ @echoes = {}
23
+ @disruption_count = 0
24
+ @created_at = Time.now.utc
25
+ end
26
+
27
+ def add_echo(echo)
28
+ return false if @echoes.size >= MAX_ECHOES
29
+
30
+ @echoes[echo.id] = echo
31
+ recalculate_resonance
32
+ update_state
33
+ true
34
+ end
35
+
36
+ def remove_echo(echo_id)
37
+ removed = @echoes.delete(echo_id)
38
+ recalculate_resonance if removed
39
+ update_state
40
+ !removed.nil?
41
+ end
42
+
43
+ def amplify_all!(rate = AMPLIFICATION_RATE)
44
+ @echoes.each_value { |e| e.amplify!(rate) }
45
+ recalculate_resonance
46
+ update_state
47
+ { amplified: @echoes.size, resonance_frequency: @resonance_frequency }
48
+ end
49
+
50
+ def disrupt!(force)
51
+ force = force.to_f.clamp(0.0, 1.0)
52
+ return { success: false, reason: 'insufficient_force', force: force, wall: @wall_thickness } if force <= @wall_thickness
53
+
54
+ @disruption_count += 1
55
+ breakthrough = force - @wall_thickness
56
+ @wall_thickness = (@wall_thickness - (breakthrough * BREAKTHROUGH_BONUS)).clamp(0.0, 1.0).round(10)
57
+ @echoes.each_value { |e| e.dampen!(breakthrough.round(10)) }
58
+ @state = :disrupted
59
+ recalculate_resonance
60
+ { success: true, force: force, breakthrough: breakthrough.round(10), wall_remaining: @wall_thickness }
61
+ end
62
+
63
+ def sealed?
64
+ @wall_thickness >= SEALED_THRESHOLD
65
+ end
66
+
67
+ def porous?
68
+ @wall_thickness <= POROUS_THRESHOLD
69
+ end
70
+
71
+ def resonance_level
72
+ Constants.label_for(RESONANCE_LABELS, @resonance_frequency)
73
+ end
74
+
75
+ def echo_count
76
+ @echoes.size
77
+ end
78
+
79
+ def active_echoes
80
+ @echoes.values.reject(&:silent?)
81
+ end
82
+
83
+ def resonating_echoes
84
+ @echoes.values.select(&:resonate?)
85
+ end
86
+
87
+ def to_h
88
+ {
89
+ id: @id,
90
+ label: @label,
91
+ domain: @domain,
92
+ state: @state,
93
+ wall_thickness: @wall_thickness,
94
+ resonance_frequency: @resonance_frequency,
95
+ resonance_level: resonance_level,
96
+ sealed: sealed?,
97
+ porous: porous?,
98
+ echo_count: echo_count,
99
+ active_echo_count: active_echoes.size,
100
+ disruption_count: @disruption_count,
101
+ created_at: @created_at
102
+ }
103
+ end
104
+
105
+ private
106
+
107
+ def recalculate_resonance
108
+ return @resonance_frequency = 0.0 if @echoes.empty?
109
+
110
+ resonating = resonating_echoes
111
+ @resonance_frequency = (resonating.size.to_f / @echoes.size).round(10)
112
+ end
113
+
114
+ def update_state
115
+ return if @state == :disrupted || @state == :collapsed
116
+ return @state = :forming if @echoes.empty?
117
+
118
+ @state = if @resonance_frequency >= DISRUPTION_THRESHOLD
119
+ :saturated
120
+ elsif @resonance_frequency >= POROUS_THRESHOLD
121
+ :resonating
122
+ else
123
+ :forming
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEchoChamber
6
+ module Helpers
7
+ class ChamberEngine
8
+ include Constants
9
+
10
+ def initialize
11
+ @echoes = {}
12
+ @chambers = {}
13
+ @disruption_history = []
14
+ end
15
+
16
+ def create_echo(content:, echo_type: :belief, domain: :general,
17
+ source_agent: nil, amplitude: DEFAULT_AMPLITUDE)
18
+ prune_echoes
19
+ echo = Echo.new(
20
+ content: content,
21
+ echo_type: echo_type,
22
+ domain: domain,
23
+ source_agent: source_agent,
24
+ amplitude: amplitude
25
+ )
26
+ @echoes[echo.id] = echo
27
+ echo
28
+ end
29
+
30
+ def create_chamber(label:, domain: :general, wall_thickness: DEFAULT_WALL_THICKNESS)
31
+ prune_chambers
32
+ chamber = Chamber.new(label: label, domain: domain, wall_thickness: wall_thickness)
33
+ @chambers[chamber.id] = chamber
34
+ chamber
35
+ end
36
+
37
+ def amplify_echo(echo_id:, rate: AMPLIFICATION_RATE)
38
+ echo = @echoes[echo_id]
39
+ return nil unless echo
40
+
41
+ echo.amplify!(rate)
42
+ echo
43
+ end
44
+
45
+ def disrupt_chamber(chamber_id:, force:)
46
+ chamber = @chambers[chamber_id]
47
+ return { success: false, error: 'chamber not found' } unless chamber
48
+
49
+ result = chamber.disrupt!(force)
50
+ record_disruption(chamber_id: chamber_id, force: force, result: result) if result[:success]
51
+ result
52
+ end
53
+
54
+ def decay_all!
55
+ echo_count = @echoes.size
56
+ @echoes.each_value { |e| e.dampen!(DECAY_RATE) }
57
+ prune_silent_echoes
58
+ { decayed: echo_count, remaining: @echoes.size, pruned: echo_count - @echoes.size }
59
+ end
60
+
61
+ def echoes_by_type(echo_type:)
62
+ @echoes.values.select { |e| e.echo_type == echo_type.to_sym }
63
+ end
64
+
65
+ def loudest_echoes(limit: 5)
66
+ @echoes.values.sort_by { |e| -e.amplitude }.first(limit)
67
+ end
68
+
69
+ def most_sealed_chambers(limit: 5)
70
+ @chambers.values.sort_by { |c| -c.wall_thickness }.first(limit)
71
+ end
72
+
73
+ def disruption_history
74
+ @disruption_history.dup
75
+ end
76
+
77
+ def add_echo_to_chamber(echo_id:, chamber_id:)
78
+ echo = @echoes[echo_id]
79
+ chamber = @chambers[chamber_id]
80
+ return { success: false, error: 'echo not found' } unless echo
81
+ return { success: false, error: 'chamber not found' } unless chamber
82
+
83
+ added = chamber.add_echo(echo)
84
+ { success: added, echo_id: echo_id, chamber_id: chamber_id }
85
+ end
86
+
87
+ def echo_report
88
+ {
89
+ total_echoes: @echoes.size,
90
+ active_echoes: active_echoes.size,
91
+ resonating_echoes: resonating_echoes.size,
92
+ total_chambers: @chambers.size,
93
+ sealed_chambers: @chambers.values.count(&:sealed?),
94
+ porous_chambers: @chambers.values.count(&:porous?),
95
+ disruption_count: @disruption_history.size,
96
+ loudest: loudest_echoes(limit: 3).map(&:to_h)
97
+ }
98
+ end
99
+
100
+ def active_echoes
101
+ @echoes.values.reject(&:silent?)
102
+ end
103
+
104
+ def resonating_echoes
105
+ @echoes.values.select(&:resonate?)
106
+ end
107
+
108
+ def chamber_by_id(chamber_id)
109
+ @chambers[chamber_id]
110
+ end
111
+
112
+ def echo_by_id(echo_id)
113
+ @echoes[echo_id]
114
+ end
115
+
116
+ private
117
+
118
+ def prune_echoes
119
+ return if @echoes.size < MAX_ECHOES
120
+
121
+ silent = @echoes.values.select(&:silent?)
122
+ silent.each { |e| @echoes.delete(e.id) }
123
+ return unless @echoes.size >= MAX_ECHOES
124
+
125
+ faintest = @echoes.values.min_by(&:amplitude)
126
+ @echoes.delete(faintest.id) if faintest
127
+ end
128
+
129
+ def prune_chambers
130
+ return if @chambers.size < MAX_CHAMBERS
131
+
132
+ collapsed = @chambers.values.select { |c| c.state == :collapsed }
133
+ collapsed.each { |c| @chambers.delete(c.id) }
134
+ end
135
+
136
+ def prune_silent_echoes
137
+ @echoes.select { |_, e| e.silent? }.each_key { |id| @echoes.delete(id) }
138
+ end
139
+
140
+ def record_disruption(chamber_id:, force:, result:)
141
+ @disruption_history << {
142
+ chamber_id: chamber_id,
143
+ force: force,
144
+ breakthrough: result[:breakthrough],
145
+ occurred_at: Time.now.utc
146
+ }
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEchoChamber
6
+ module Helpers
7
+ module Constants
8
+ MAX_ECHOES = 500
9
+ MAX_CHAMBERS = 50
10
+ AMPLIFICATION_RATE = 0.1
11
+ DECAY_RATE = 0.02
12
+ DISRUPTION_THRESHOLD = 0.7
13
+ SEALED_THRESHOLD = 0.8
14
+ POROUS_THRESHOLD = 0.3
15
+ BREAKTHROUGH_BONUS = 0.15
16
+ SILENT_THRESHOLD = 0.05
17
+ DEFAULT_AMPLITUDE = 0.5
18
+ DEFAULT_WALL_THICKNESS = 0.5
19
+
20
+ ECHO_TYPES = %i[belief assumption bias hypothesis conviction].freeze
21
+
22
+ CHAMBER_STATES = %i[forming resonating saturated disrupted collapsed].freeze
23
+
24
+ RESONANCE_LABELS = {
25
+ (0.8..) => :thunderous,
26
+ (0.6...0.8) => :resonant,
27
+ (0.4...0.6) => :humming,
28
+ (0.2...0.4) => :fading,
29
+ (..0.2) => :silent
30
+ }.freeze
31
+
32
+ AMPLIFICATION_LABELS = {
33
+ (0.8..) => :deafening,
34
+ (0.6...0.8) => :loud,
35
+ (0.4...0.6) => :moderate,
36
+ (0.2...0.4) => :quiet,
37
+ (..0.2) => :muted
38
+ }.freeze
39
+
40
+ CHAMBER_STATE_LABELS = {
41
+ forming: 'Chamber is newly formed, echoes still accumulating',
42
+ resonating: 'Chamber is actively reinforcing beliefs',
43
+ saturated: 'Chamber has reached maximum self-reinforcement',
44
+ disrupted: 'External input has broken through the chamber walls',
45
+ collapsed: 'Chamber has lost coherence and dissolved'
46
+ }.freeze
47
+
48
+ def self.label_for(table, value)
49
+ match = table.find { |range, _| range.cover?(value) }
50
+ match&.last
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveEchoChamber
8
+ module Helpers
9
+ class Echo
10
+ include Constants
11
+
12
+ attr_reader :id, :content, :echo_type, :domain, :source_agent,
13
+ :amplitude, :frequency, :original_amplitude, :created_at
14
+
15
+ def initialize(content:, echo_type: :belief, domain: :general,
16
+ source_agent: nil, amplitude: DEFAULT_AMPLITUDE)
17
+ @id = SecureRandom.uuid
18
+ @content = content
19
+ @echo_type = validate_type(echo_type)
20
+ @domain = domain.to_sym
21
+ @source_agent = source_agent
22
+ @amplitude = amplitude.to_f.clamp(0.0, 1.0).round(10)
23
+ @original_amplitude = @amplitude
24
+ @frequency = 1
25
+ @created_at = Time.now.utc
26
+ end
27
+
28
+ def amplify!(rate = AMPLIFICATION_RATE)
29
+ @frequency += 1
30
+ @amplitude = (@amplitude + rate).clamp(0.0, 1.0).round(10)
31
+ self
32
+ end
33
+
34
+ def dampen!(rate = DECAY_RATE)
35
+ @amplitude = (@amplitude - rate).clamp(0.0, 1.0).round(10)
36
+ self
37
+ end
38
+
39
+ def resonate?
40
+ @amplitude >= DISRUPTION_THRESHOLD
41
+ end
42
+
43
+ def fading?
44
+ @amplitude <= POROUS_THRESHOLD
45
+ end
46
+
47
+ def silent?
48
+ @amplitude <= SILENT_THRESHOLD
49
+ end
50
+
51
+ def frequency_label
52
+ Constants.label_for(RESONANCE_LABELS, frequency_score)
53
+ end
54
+
55
+ def amplitude_label
56
+ Constants.label_for(AMPLIFICATION_LABELS, @amplitude)
57
+ end
58
+
59
+ def to_h
60
+ {
61
+ id: @id,
62
+ content: @content,
63
+ echo_type: @echo_type,
64
+ domain: @domain,
65
+ source_agent: @source_agent,
66
+ amplitude: @amplitude,
67
+ original_amplitude: @original_amplitude,
68
+ frequency: @frequency,
69
+ frequency_label: frequency_label,
70
+ amplitude_label: amplitude_label,
71
+ resonate: resonate?,
72
+ fading: fading?,
73
+ silent: silent?,
74
+ created_at: @created_at
75
+ }
76
+ end
77
+
78
+ private
79
+
80
+ def validate_type(type)
81
+ sym = type.to_sym
82
+ ECHO_TYPES.include?(sym) ? sym : :belief
83
+ end
84
+
85
+ def frequency_score
86
+ [@frequency.to_f / 20.0, 1.0].min.round(10)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEchoChamber
6
+ module Runners
7
+ module CognitiveEchoChamber
8
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+
10
+ def create_echo(content:, echo_type: :belief, domain: :general,
11
+ source_agent: nil, amplitude: 0.5, engine: nil, **)
12
+ raise ArgumentError, 'content cannot be empty' if content.to_s.strip.empty?
13
+
14
+ eng = engine || default_engine
15
+ echo = eng.create_echo(
16
+ content: content,
17
+ echo_type: echo_type,
18
+ domain: domain,
19
+ source_agent: source_agent,
20
+ amplitude: amplitude
21
+ )
22
+ Legion::Logging.debug "[cognitive_echo_chamber] echo created id=#{echo.id} type=#{echo_type} domain=#{domain}"
23
+ { success: true, echo: echo.to_h }
24
+ rescue ArgumentError => e
25
+ { success: false, error: e.message }
26
+ end
27
+
28
+ def create_chamber(label:, domain: :general, wall_thickness: 0.5, engine: nil, **)
29
+ raise ArgumentError, 'label cannot be empty' if label.to_s.strip.empty?
30
+
31
+ eng = engine || default_engine
32
+ chamber = eng.create_chamber(label: label, domain: domain, wall_thickness: wall_thickness)
33
+ Legion::Logging.debug "[cognitive_echo_chamber] chamber created id=#{chamber.id} label=#{label}"
34
+ { success: true, chamber: chamber.to_h }
35
+ rescue ArgumentError => e
36
+ { success: false, error: e.message }
37
+ end
38
+
39
+ def amplify(echo_id:, rate: 0.1, engine: nil, **)
40
+ eng = engine || default_engine
41
+ echo = eng.amplify_echo(echo_id: echo_id, rate: rate)
42
+ return { success: false, error: 'echo not found' } unless echo
43
+
44
+ Legion::Logging.debug "[cognitive_echo_chamber] amplified echo=#{echo_id} amplitude=#{echo.amplitude}"
45
+ { success: true, echo: echo.to_h }
46
+ end
47
+
48
+ def disrupt(chamber_id:, force:, engine: nil, **)
49
+ eng = engine || default_engine
50
+ result = eng.disrupt_chamber(chamber_id: chamber_id, force: force)
51
+ Legion::Logging.debug "[cognitive_echo_chamber] disrupt chamber=#{chamber_id} success=#{result[:success]}"
52
+ result
53
+ end
54
+
55
+ def list_echoes(echo_type: nil, domain: nil, engine: nil, **)
56
+ eng = engine || default_engine
57
+ echoes = if echo_type
58
+ eng.echoes_by_type(echo_type: echo_type)
59
+ else
60
+ eng.active_echoes
61
+ end
62
+ echoes = echoes.select { |e| e.domain == domain.to_sym } if domain
63
+ { success: true, echoes: echoes.map(&:to_h), count: echoes.size }
64
+ end
65
+
66
+ def chamber_status(engine: nil, **)
67
+ eng = engine || default_engine
68
+ { success: true, **eng.echo_report }
69
+ end
70
+
71
+ private
72
+
73
+ def default_engine
74
+ @default_engine ||= Helpers::ChamberEngine.new
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEchoChamber
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative 'cognitive_echo_chamber/version'
5
+ require_relative 'cognitive_echo_chamber/helpers/constants'
6
+ require_relative 'cognitive_echo_chamber/helpers/echo'
7
+ require_relative 'cognitive_echo_chamber/helpers/chamber'
8
+ require_relative 'cognitive_echo_chamber/helpers/chamber_engine'
9
+ require_relative 'cognitive_echo_chamber/runners/cognitive_echo_chamber'
10
+ require_relative 'cognitive_echo_chamber/client'
11
+
12
+ module Legion
13
+ module Extensions
14
+ module CognitiveEchoChamber
15
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
16
+ end
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-echo-chamber
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: Models cognitive echo chambers — when thoughts reinforce themselves without
27
+ external challenge, creating self-reinforcing belief loops. Tracks amplification,
28
+ resonance, and breakthrough events when external input disrupts the echo.
29
+ email:
30
+ - matthewdiverson@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".github/workflows/ci.yml"
36
+ - ".gitignore"
37
+ - ".rspec"
38
+ - ".rubocop.yml"
39
+ - CLAUDE.md
40
+ - Gemfile
41
+ - Gemfile.lock
42
+ - README.md
43
+ - lex-cognitive-echo-chamber.gemspec
44
+ - lib/legion/extensions/cognitive_echo_chamber.rb
45
+ - lib/legion/extensions/cognitive_echo_chamber/client.rb
46
+ - lib/legion/extensions/cognitive_echo_chamber/helpers/chamber.rb
47
+ - lib/legion/extensions/cognitive_echo_chamber/helpers/chamber_engine.rb
48
+ - lib/legion/extensions/cognitive_echo_chamber/helpers/constants.rb
49
+ - lib/legion/extensions/cognitive_echo_chamber/helpers/echo.rb
50
+ - lib/legion/extensions/cognitive_echo_chamber/runners/cognitive_echo_chamber.rb
51
+ - lib/legion/extensions/cognitive_echo_chamber/version.rb
52
+ homepage: https://github.com/LegionIO/lex-cognitive-echo-chamber
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-echo-chamber
57
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-echo-chamber
58
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-echo-chamber/blob/master/README.md
59
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-echo-chamber/blob/master/CHANGELOG.md
60
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-echo-chamber/issues
61
+ rubygems_mfa_required: 'true'
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '3.4'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.6.9
77
+ specification_version: 4
78
+ summary: Cognitive echo chamber dynamics for LegionIO agentic architecture
79
+ test_files: []