lex-cognitive-gravity 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: 3b5fd5b947f6fdcf87b9a1ea06d1ea1c7d4751b0374710f1a31dbdd776415992
4
+ data.tar.gz: 79e9aa02e8a16d53fc050806e2f2a12b4a8f6fc46a23eaa6be6ad136ae9a21e4
5
+ SHA512:
6
+ metadata.gz: 2f0efb7bc240c78ce18cf5f2db4ce2821cadcdc19af7b16850e5170b896abebe3dd68826a69bd1b1be2d6271de5e67e4c04c2b89dd9d08de3c635786a673da61
7
+ data.tar.gz: 0311d5bec4510aa19a4fdfe49c3cd4a0ba6b8c9d3aef9e31bde14ea9f70de7e39e7bf7f1a6206f7963205c751f05099e8a50dadb23daff8df0ae00c9ab66f5aa
@@ -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,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.gem
10
+ .rspec_status
11
+ 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,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/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/SymbolArray:
47
+ Enabled: true
48
+
49
+ Style/FrozenStringLiteralComment:
50
+ Enabled: true
51
+ EnforcedStyle: always
52
+
53
+ Style/OneClassPerFile:
54
+ Exclude:
55
+ - 'spec/spec_helper.rb'
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,123 @@
1
+ # lex-cognitive-gravity
2
+
3
+ **Level 3 Leaf Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Gem**: `lex-cognitive-gravity`
6
+
7
+ ## Purpose
8
+
9
+ Models attractor dynamics in cognition. Attractors are high-mass cognitive concepts that exert gravitational pull on orbiting thoughts. Thoughts within an attractor's capture radius are pulled in; those too distant escape. Each simulation tick determines captures and escapes based on mass and orbital distance. Attractors gain mass through accretion (reinforcement) and lose mass through erosion (decay). Collapsed attractors (mass < COLLAPSE_THRESHOLD) can no longer capture thoughts. Supermassive attractors (mass >= SUPERMASSIVE_THRESHOLD) have extended capture radius.
10
+
11
+ ## Gem Info
12
+
13
+ | Field | Value |
14
+ |---|---|
15
+ | Gem name | `lex-cognitive-gravity` |
16
+ | Version | `0.1.0` |
17
+ | Namespace | `Legion::Extensions::CognitiveGravity` |
18
+ | Ruby | `>= 3.4` |
19
+ | License | MIT |
20
+ | GitHub | https://github.com/LegionIO/lex-cognitive-gravity |
21
+
22
+ ## File Structure
23
+
24
+ ```
25
+ lib/legion/extensions/cognitive_gravity/
26
+ cognitive_gravity.rb # Top-level require
27
+ version.rb # VERSION = '0.1.0'
28
+ client.rb # Client class
29
+ helpers/
30
+ constants.rb # Attractor domains, mass/density/orbit labels, thresholds
31
+ attractor.rb # Attractor value object
32
+ orbiting_thought.rb # OrbitingThought value object
33
+ gravity_engine.rb # Engine: attractors, thoughts, tick, accretion, erosion
34
+ runners/
35
+ gravity.rb # Runner module
36
+ ```
37
+
38
+ ## Key Constants
39
+
40
+ | Constant | Value | Meaning |
41
+ |---|---|---|
42
+ | `MAX_ATTRACTORS` | 200 | Attractor cap |
43
+ | `MAX_ORBITING` | 500 | Orbiting thought cap |
44
+ | `DEFAULT_MASS` | 1.0 | Starting attractor mass |
45
+ | `MASS_ACCRETION` | 0.15 | Mass increase per accretion call |
46
+ | `MASS_EROSION` | 0.05 | Mass decrease per erosion call |
47
+ | `CAPTURE_RADIUS` | 0.2 | Default orbital radius within which thoughts are captured |
48
+ | `COLLAPSE_THRESHOLD` | 0.1 | Mass below this = collapsed; cannot capture |
49
+ | `SUPERMASSIVE_THRESHOLD` | 3.0 | Mass above this = supermassive; extended capture radius |
50
+ | `ATTRACTOR_DOMAINS` | array | `[:fear, :desire, :belief, :habit, :memory, :identity, :goal, :trauma]` |
51
+ | `MASS_LABELS` | hash | `supermassive` (3.0+) through `collapsed` |
52
+ | `DENSITY_LABELS` | hash | `dense` (0.8+) through `sparse` |
53
+ | `ORBIT_LABELS` | hash | `captured` through `escaped` |
54
+
55
+ ## Helpers
56
+
57
+ ### `Attractor`
58
+
59
+ A dominant cognitive concept with gravitational mass.
60
+
61
+ - `initialize(domain:, content:, mass: DEFAULT_MASS, attractor_id: nil)`
62
+ - `accrete!(rate)` — increases mass by `MASS_ACCRETION`
63
+ - `erode!(rate)` — decreases mass by `MASS_EROSION`, floor 0.0
64
+ - `collapsed?` — mass < `COLLAPSE_THRESHOLD`
65
+ - `supermassive?` — mass >= `SUPERMASSIVE_THRESHOLD`
66
+ - `capture_radius` — extended if supermassive
67
+ - `mass_label`
68
+ - `to_h`
69
+
70
+ ### `OrbitingThought`
71
+
72
+ A cognitive element in orbital relationship with an attractor.
73
+
74
+ - `initialize(content:, domain:, orbital_distance: 0.5, thought_id: nil)`
75
+ - `captured?` — orbital_distance <= attractor's capture_radius
76
+ - `escaped?` — orbital_distance > 1.0
77
+ - `to_h`
78
+
79
+ ### `GravityEngine`
80
+
81
+ - `add_attractor(domain:, content:, mass: DEFAULT_MASS)` — returns `{ added:, attractor_id:, attractor: }` or capacity error
82
+ - `add_orbiting_thought(content:, domain:, orbital_distance: 0.5)` — returns `{ added:, thought_id:, thought: }` or capacity error
83
+ - `simulate_tick` — for each non-collapsed attractor, evaluates all thoughts within capture radius (captures) and marks distant thoughts (escapes); returns capture/escape counts
84
+ - `accrete_attractor(attractor_id:)` — increases mass
85
+ - `erode_attractor(attractor_id:)` — decreases mass
86
+ - `strongest_attractors(limit: 10)` — sorted by mass descending
87
+ - `cognitive_density_map` — domain -> mean mass map
88
+ - `gravity_report` — full stats
89
+
90
+ ## Runners
91
+
92
+ **Module**: `Legion::Extensions::CognitiveGravity::Runners::Gravity`
93
+
94
+ | Method | Key Args | Returns |
95
+ |---|---|---|
96
+ | `create_attractor` | `domain:`, `content:`, `mass: DEFAULT_MASS` | `{ success:, attractor_id:, attractor: }` |
97
+ | `add_thought` | `content:`, `domain:`, `orbital_distance: 0.5` | `{ success:, thought_id:, thought: }` |
98
+ | `tick_gravity` | — | `{ success:, captures:, escapes: }` |
99
+ | `accrete` | `attractor_id:` | `{ success:, mass:, label: }` |
100
+ | `erode` | `attractor_id:` | `{ success:, mass:, label: }` |
101
+ | `strongest_attractors` | `limit: 10` | `{ success:, attractors: }` |
102
+ | `thought_distribution` | — | `{ success:, distribution: }` |
103
+ | `cognitive_density_map` | — | `{ success:, density_map: }` |
104
+ | `gravity_report` | — | Full report hash |
105
+
106
+ Private: `gravity_engine` — memoized `GravityEngine`. Logs via `log_debug` helper.
107
+
108
+ ## Integration Points
109
+
110
+ - **`lex-memory`**: High-mass attractors correspond to high-strength memory traces. `simulate_tick` results (captured thoughts) can reinforce corresponding memory traces.
111
+ - **`lex-emotion`**: Attractor domains map to emotional categories (`:fear`, `:desire`). Strong emotional valence from `lex-emotion` can trigger accretion of matching domain attractors.
112
+ - **`lex-cognitive-inertia`**: Entrenched beliefs in `lex-cognitive-inertia` correspond to high-mass attractors in gravity. Both model resistance to change in different metaphors.
113
+
114
+ ## Development Notes
115
+
116
+ - `simulate_tick` is an O(attractors × thoughts) operation. With MAX_ATTRACTORS=200 and MAX_ORBITING=500, the worst case is 100,000 comparisons per tick. Keep attractor and thought counts reasonable for performance.
117
+ - Collapsed attractors (`mass < 0.1`) are skipped during tick but remain in the store. Callers should prune collapsed attractors periodically.
118
+ - `add_orbiting_thought` assigns a random orbital distance if none is provided; callers can provide explicit distance for deterministic behavior.
119
+ - In-memory only.
120
+
121
+ ---
122
+
123
+ **Maintained By**: Matthew Iverson (@Esity)
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/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # lex-cognitive-gravity
2
+
3
+ Cognitive attractor dynamics engine for brain-modeled agentic AI in the LegionIO ecosystem.
4
+
5
+ ## What It Does
6
+
7
+ Models gravitational attraction in thought space. Attractors are dominant cognitive concepts (fears, desires, beliefs, habits, goals) with mass that exerts pull on orbiting thoughts. Each simulation tick evaluates which thoughts fall within an attractor's capture radius (pulled in) and which escape. Attractors gain mass through accretion (reinforcement) and lose mass through erosion (decay). Collapsed attractors (mass below 0.1) can no longer capture thoughts. Supermassive attractors (mass >= 3.0) have extended capture radius.
8
+
9
+ ## Usage
10
+
11
+ ```ruby
12
+ require 'legion/extensions/cognitive_gravity'
13
+
14
+ client = Legion::Extensions::CognitiveGravity::Client.new
15
+
16
+ # Create a dominant attractor concept
17
+ result = client.create_attractor(domain: :goal, content: 'build reliable distributed system', mass: 2.0)
18
+ attractor_id = result[:attractor_id]
19
+
20
+ # Add orbiting thoughts
21
+ client.add_thought(content: 'consider retry backoff strategy', domain: :goal, orbital_distance: 0.15)
22
+ client.add_thought(content: 'unrelated distraction', domain: :habit, orbital_distance: 0.8)
23
+
24
+ # Simulate gravitational tick
25
+ client.tick_gravity
26
+ # => { success: true, captures: 1, escapes: 0 }
27
+
28
+ # Strengthen an attractor
29
+ client.accrete(attractor_id: attractor_id)
30
+ # => { success: true, mass: 2.15, label: :heavy }
31
+
32
+ # See strongest attractors
33
+ client.strongest_attractors(limit: 5)
34
+ # => { success: true, attractors: [...] }
35
+ ```
36
+
37
+ ## Development
38
+
39
+ ```bash
40
+ bundle install
41
+ bundle exec rspec
42
+ bundle exec rubocop
43
+ ```
44
+
45
+ ## License
46
+
47
+ MIT
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_gravity/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-gravity'
7
+ spec.version = Legion::Extensions::CognitiveGravity::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Cognitive Gravity'
12
+ spec.description = 'Attractor basins in thought space — cognitive gravity wells for LegionIO agentic AI'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-gravity'
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-gravity'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-gravity'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-gravity'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-gravity/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.start_with?('spec/') }
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_gravity/helpers/constants'
4
+ require 'legion/extensions/cognitive_gravity/helpers/attractor'
5
+ require 'legion/extensions/cognitive_gravity/helpers/orbiting_thought'
6
+ require 'legion/extensions/cognitive_gravity/helpers/gravity_engine'
7
+ require 'legion/extensions/cognitive_gravity/runners/gravity'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module CognitiveGravity
12
+ class Client
13
+ include Runners::Gravity
14
+
15
+ def initialize(**)
16
+ @gravity_engine = Helpers::GravityEngine.new
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :gravity_engine
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveGravity
8
+ module Helpers
9
+ class Attractor
10
+ attr_reader :id, :content, :domain, :mass, :pull_radius, :decay_rate, :created_at, :reinforcement_count
11
+
12
+ def initialize(content:, domain: :unknown, mass: Constants::DEFAULT_MASS,
13
+ pull_radius: 1.0, decay_rate: 0.01)
14
+ @id = SecureRandom.uuid
15
+ @content = content
16
+ @domain = domain
17
+ @mass = mass.to_f.round(10)
18
+ @pull_radius = pull_radius.clamp(0.01, Float::INFINITY)
19
+ @decay_rate = decay_rate.clamp(0.0, 1.0)
20
+ @created_at = Time.now.utc
21
+ @reinforcement_count = 0
22
+ end
23
+
24
+ def accrete!(amount = Constants::MASS_ACCRETION)
25
+ @mass = (@mass + amount.to_f).round(10)
26
+ @reinforcement_count += 1
27
+ self
28
+ end
29
+
30
+ def erode!(amount = Constants::MASS_EROSION)
31
+ @mass = [(@mass - amount.to_f).round(10), 0.0].max
32
+ self
33
+ end
34
+
35
+ def pull_strength_at(distance:)
36
+ return 0.0 if distance <= 0 || distance > @pull_radius
37
+
38
+ raw = (Constants::PULL_CONSTANT * @mass) / (distance**2)
39
+ raw.clamp(0.0, @mass)
40
+ end
41
+
42
+ def collapsed?
43
+ @mass < Constants::COLLAPSE_THRESHOLD
44
+ end
45
+
46
+ def supermassive?
47
+ @mass >= Constants::SUPERMASSIVE_THRESHOLD
48
+ end
49
+
50
+ def mass_label
51
+ Constants.label_for(Constants::MASS_LABELS, @mass)
52
+ end
53
+
54
+ def to_h
55
+ {
56
+ id: @id,
57
+ content: @content,
58
+ domain: @domain,
59
+ mass: @mass,
60
+ pull_radius: @pull_radius,
61
+ decay_rate: @decay_rate,
62
+ reinforcement_count: @reinforcement_count,
63
+ collapsed: collapsed?,
64
+ supermassive: supermassive?,
65
+ mass_label: mass_label,
66
+ created_at: @created_at
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveGravity
6
+ module Helpers
7
+ module Constants
8
+ MAX_ATTRACTORS = 200
9
+ MAX_ORBITING = 500
10
+ DEFAULT_MASS = 1.0
11
+ MASS_ACCRETION = 0.15
12
+ MASS_EROSION = 0.05
13
+ CAPTURE_RADIUS = 0.2
14
+ ESCAPE_RADIUS = 1.5
15
+ PULL_CONSTANT = 0.1
16
+ COLLAPSE_THRESHOLD = 0.1
17
+ SUPERMASSIVE_THRESHOLD = 3.0
18
+
19
+ ATTRACTOR_DOMAINS = %i[
20
+ problem
21
+ curiosity
22
+ anxiety
23
+ obsession
24
+ interest
25
+ fear
26
+ desire
27
+ unknown
28
+ ].freeze
29
+
30
+ MASS_LABELS = {
31
+ (0.0..0.1) => :collapsing,
32
+ (0.1..0.5) => :weak,
33
+ (0.5..1.0) => :nascent,
34
+ (1.0..2.0) => :moderate,
35
+ (2.0..3.0) => :strong,
36
+ (3.0..Float::INFINITY) => :supermassive
37
+ }.freeze
38
+
39
+ DENSITY_LABELS = {
40
+ (0..0) => :empty,
41
+ (1..2) => :sparse,
42
+ (3..5) => :light,
43
+ (6..10) => :moderate,
44
+ (11..20) => :dense,
45
+ (21..Float::INFINITY) => :crowded
46
+ }.freeze
47
+
48
+ ORBIT_LABELS = {
49
+ (0.0..0.2) => :captured,
50
+ (0.2..0.5) => :tight,
51
+ (0.5..1.0) => :stable,
52
+ (1.0..1.5) => :loose,
53
+ (1.5..Float::INFINITY) => :escaped
54
+ }.freeze
55
+
56
+ module_function
57
+
58
+ def label_for(labels_hash, value)
59
+ labels_hash.each do |range, label|
60
+ return label if range.cover?(value)
61
+ end
62
+ :unknown
63
+ end
64
+
65
+ def valid_domain?(domain)
66
+ ATTRACTOR_DOMAINS.include?(domain)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveGravity
6
+ module Helpers
7
+ class GravityEngine
8
+ attr_reader :attractors, :orbiting_thoughts, :capture_events, :escape_events
9
+
10
+ def initialize
11
+ @attractors = {}
12
+ @orbiting_thoughts = {}
13
+ @capture_events = []
14
+ @escape_events = []
15
+ end
16
+
17
+ def add_attractor(content:, domain: :unknown, mass: Constants::DEFAULT_MASS,
18
+ pull_radius: 1.0, decay_rate: 0.01)
19
+ return { error: :capacity_exceeded, max: Constants::MAX_ATTRACTORS } if at_attractor_capacity?
20
+
21
+ attractor = Attractor.new(
22
+ content: content,
23
+ domain: domain,
24
+ mass: mass,
25
+ pull_radius: pull_radius,
26
+ decay_rate: decay_rate
27
+ )
28
+ @attractors[attractor.id] = attractor
29
+ attractor
30
+ end
31
+
32
+ def add_orbiting_thought(content:, attractor_id:, orbital_distance: 1.0, velocity: 0.0)
33
+ return { error: :attractor_not_found } unless @attractors.key?(attractor_id)
34
+ return { error: :capacity_exceeded, max: Constants::MAX_ORBITING } if at_orbiting_capacity?
35
+
36
+ thought = OrbitingThought.new(
37
+ content: content,
38
+ attractor_id: attractor_id,
39
+ orbital_distance: orbital_distance,
40
+ velocity: velocity
41
+ )
42
+ @orbiting_thoughts[thought.id] = thought
43
+ thought
44
+ end
45
+
46
+ def simulate_tick
47
+ captures = []
48
+ escapes = []
49
+
50
+ @attractors.each_value do |attractor|
51
+ next if attractor.collapsed?
52
+
53
+ thoughts_for(attractor.id).each do |thought|
54
+ pull = attractor.pull_strength_at(distance: thought.orbital_distance)
55
+ next unless pull.positive?
56
+
57
+ was_captured = thought.captured?
58
+ was_escaped = thought.escaped?
59
+
60
+ thought.approach!(pull)
61
+
62
+ if !was_captured && thought.captured?
63
+ event = build_event(:capture, attractor, thought)
64
+ captures << event
65
+ @capture_events << event
66
+ end
67
+
68
+ next unless !was_escaped && thought.escaped?
69
+
70
+ event = build_event(:escape, attractor, thought)
71
+ escapes << event
72
+ @escape_events << event
73
+ end
74
+ end
75
+
76
+ { captures: captures, escapes: escapes, tick_processed: true }
77
+ end
78
+
79
+ def accrete_attractor(attractor_id, amount: Constants::MASS_ACCRETION)
80
+ attractor = @attractors[attractor_id]
81
+ return { error: :not_found } unless attractor
82
+
83
+ attractor.accrete!(amount)
84
+ { accreted: true, id: attractor_id, mass: attractor.mass }
85
+ end
86
+
87
+ def erode_attractor(attractor_id, amount: Constants::MASS_EROSION)
88
+ attractor = @attractors[attractor_id]
89
+ return { error: :not_found } unless attractor
90
+
91
+ attractor.erode!(amount)
92
+ { eroded: true, id: attractor_id, mass: attractor.mass, collapsed: attractor.collapsed? }
93
+ end
94
+
95
+ def strongest_attractors(limit: 5)
96
+ @attractors.values
97
+ .reject(&:collapsed?)
98
+ .sort_by { |a| -a.mass }
99
+ .first(limit)
100
+ end
101
+
102
+ def thought_distribution
103
+ @attractors.transform_values { |a| thoughts_for(a.id).size }
104
+ end
105
+
106
+ def cognitive_density_map
107
+ thought_distribution.transform_values do |count|
108
+ Constants.label_for(Constants::DENSITY_LABELS, count)
109
+ end
110
+ end
111
+
112
+ def gravity_report
113
+ collapsed, active = @attractors.values.partition(&:collapsed?)
114
+ supermass = @attractors.values.select(&:supermassive?)
115
+ captured = @orbiting_thoughts.values.select(&:captured?)
116
+ escaped = @orbiting_thoughts.values.select(&:escaped?)
117
+
118
+ {
119
+ total_attractors: @attractors.size,
120
+ active_attractors: active.size,
121
+ collapsed_attractors: collapsed.size,
122
+ supermassive_count: supermass.size,
123
+ total_orbiting: @orbiting_thoughts.size,
124
+ captured_count: captured.size,
125
+ escaped_count: escaped.size,
126
+ total_captures: @capture_events.size,
127
+ total_escapes: @escape_events.size,
128
+ strongest: strongest_attractors(limit: 3).map(&:to_h)
129
+ }
130
+ end
131
+
132
+ private
133
+
134
+ def thoughts_for(attractor_id)
135
+ @orbiting_thoughts.values.select { |t| t.attractor_id == attractor_id }
136
+ end
137
+
138
+ def at_attractor_capacity?
139
+ @attractors.size >= Constants::MAX_ATTRACTORS
140
+ end
141
+
142
+ def at_orbiting_capacity?
143
+ @orbiting_thoughts.size >= Constants::MAX_ORBITING
144
+ end
145
+
146
+ def build_event(type, attractor, thought)
147
+ {
148
+ type: type,
149
+ attractor_id: attractor.id,
150
+ thought_id: thought.id,
151
+ mass: attractor.mass,
152
+ distance: thought.orbital_distance,
153
+ at: Time.now.utc
154
+ }
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveGravity
8
+ module Helpers
9
+ class OrbitingThought
10
+ attr_reader :id, :content, :attractor_id, :orbital_distance, :velocity, :created_at
11
+
12
+ def initialize(content:, attractor_id:, orbital_distance: 1.0, velocity: 0.0)
13
+ @id = SecureRandom.uuid
14
+ @content = content
15
+ @attractor_id = attractor_id
16
+ @orbital_distance = orbital_distance.clamp(0.0, Float::INFINITY).round(10)
17
+ @velocity = velocity.to_f.round(10)
18
+ @created_at = Time.now.utc
19
+ end
20
+
21
+ def approach!(delta)
22
+ @orbital_distance = [(@orbital_distance - delta.abs).round(10), 0.0].max
23
+ self
24
+ end
25
+
26
+ def escape!(delta)
27
+ @orbital_distance = (@orbital_distance + delta.abs).round(10)
28
+ self
29
+ end
30
+
31
+ def captured?
32
+ @orbital_distance < Constants::CAPTURE_RADIUS
33
+ end
34
+
35
+ def escaped?
36
+ @orbital_distance > Constants::ESCAPE_RADIUS
37
+ end
38
+
39
+ def orbit_label
40
+ Constants.label_for(Constants::ORBIT_LABELS, @orbital_distance)
41
+ end
42
+
43
+ def to_h
44
+ {
45
+ id: @id,
46
+ content: @content,
47
+ attractor_id: @attractor_id,
48
+ orbital_distance: @orbital_distance,
49
+ velocity: @velocity,
50
+ captured: captured?,
51
+ escaped: escaped?,
52
+ orbit_label: orbit_label,
53
+ created_at: @created_at
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveGravity
6
+ module Runners
7
+ module Gravity
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def create_attractor(content:, domain: :unknown, mass: Helpers::Constants::DEFAULT_MASS,
12
+ pull_radius: 1.0, decay_rate: 0.01, engine: nil, **)
13
+ eng = engine || gravity_engine
14
+ unless Helpers::Constants.valid_domain?(domain)
15
+ return { success: false, error: :invalid_domain,
16
+ valid_domains: Helpers::Constants::ATTRACTOR_DOMAINS }
17
+ end
18
+
19
+ result = eng.add_attractor(
20
+ content: content,
21
+ domain: domain,
22
+ mass: mass,
23
+ pull_radius: pull_radius,
24
+ decay_rate: decay_rate
25
+ )
26
+
27
+ if result.is_a?(Hash) && result[:error]
28
+ Legion::Logging.warn "[cognitive_gravity] create_attractor failed: #{result[:error]}"
29
+ return { success: false, **result }
30
+ end
31
+
32
+ Legion::Logging.debug "[cognitive_gravity] attractor created id=#{result.id[0..7]} " \
33
+ "domain=#{domain} mass=#{mass}"
34
+ { success: true, attractor: result.to_h }
35
+ end
36
+
37
+ def add_thought(content:, attractor_id:, orbital_distance: 1.0, velocity: 0.0,
38
+ engine: nil, **)
39
+ eng = engine || gravity_engine
40
+ result = eng.add_orbiting_thought(
41
+ content: content,
42
+ attractor_id: attractor_id,
43
+ orbital_distance: orbital_distance,
44
+ velocity: velocity
45
+ )
46
+
47
+ if result.is_a?(Hash) && result[:error]
48
+ Legion::Logging.warn "[cognitive_gravity] add_thought failed: #{result[:error]}"
49
+ return { success: false, **result }
50
+ end
51
+
52
+ Legion::Logging.debug "[cognitive_gravity] thought added id=#{result.id[0..7]} " \
53
+ "attractor=#{attractor_id[0..7]} distance=#{orbital_distance}"
54
+ { success: true, thought: result.to_h }
55
+ end
56
+
57
+ def tick_gravity(engine: nil, **)
58
+ eng = engine || gravity_engine
59
+ result = eng.simulate_tick
60
+ Legion::Logging.debug "[cognitive_gravity] tick: captures=#{result[:captures].size} " \
61
+ "escapes=#{result[:escapes].size}"
62
+ { success: true, **result }
63
+ end
64
+
65
+ def accrete(attractor_id:, amount: Helpers::Constants::MASS_ACCRETION, engine: nil, **)
66
+ eng = engine || gravity_engine
67
+ result = eng.accrete_attractor(attractor_id, amount: amount)
68
+
69
+ if result[:error]
70
+ Legion::Logging.warn "[cognitive_gravity] accrete failed: #{result[:error]}"
71
+ return { success: false, **result }
72
+ end
73
+
74
+ Legion::Logging.debug "[cognitive_gravity] accreted id=#{attractor_id[0..7]} mass=#{result[:mass]}"
75
+ { success: true, **result }
76
+ end
77
+
78
+ def erode(attractor_id:, amount: Helpers::Constants::MASS_EROSION, engine: nil, **)
79
+ eng = engine || gravity_engine
80
+ result = eng.erode_attractor(attractor_id, amount: amount)
81
+
82
+ if result[:error]
83
+ Legion::Logging.warn "[cognitive_gravity] erode failed: #{result[:error]}"
84
+ return { success: false, **result }
85
+ end
86
+
87
+ Legion::Logging.debug "[cognitive_gravity] eroded id=#{attractor_id[0..7]} " \
88
+ "mass=#{result[:mass]} collapsed=#{result[:collapsed]}"
89
+ { success: true, **result }
90
+ end
91
+
92
+ def strongest_attractors(limit: 5, engine: nil, **)
93
+ eng = engine || gravity_engine
94
+ attractors = eng.strongest_attractors(limit: limit)
95
+ Legion::Logging.debug "[cognitive_gravity] strongest_attractors count=#{attractors.size}"
96
+ { success: true, attractors: attractors.map(&:to_h), count: attractors.size }
97
+ end
98
+
99
+ def thought_distribution(engine: nil, **)
100
+ eng = engine || gravity_engine
101
+ distribution = eng.thought_distribution
102
+ { success: true, distribution: distribution }
103
+ end
104
+
105
+ def cognitive_density_map(engine: nil, **)
106
+ eng = engine || gravity_engine
107
+ density_map = eng.cognitive_density_map
108
+ { success: true, density_map: density_map }
109
+ end
110
+
111
+ def gravity_report(engine: nil, **)
112
+ eng = engine || gravity_engine
113
+ report = eng.gravity_report
114
+ Legion::Logging.debug "[cognitive_gravity] report: attractors=#{report[:total_attractors]} " \
115
+ "orbiting=#{report[:total_orbiting]} supermassive=#{report[:supermassive_count]}"
116
+ { success: true, report: report }
117
+ end
118
+
119
+ private
120
+
121
+ def gravity_engine
122
+ @gravity_engine ||= Helpers::GravityEngine.new
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveGravity
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_gravity/version'
4
+ require 'legion/extensions/cognitive_gravity/helpers/constants'
5
+ require 'legion/extensions/cognitive_gravity/helpers/attractor'
6
+ require 'legion/extensions/cognitive_gravity/helpers/orbiting_thought'
7
+ require 'legion/extensions/cognitive_gravity/helpers/gravity_engine'
8
+ require 'legion/extensions/cognitive_gravity/runners/gravity'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module CognitiveGravity
13
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-gravity
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: Attractor basins in thought space — cognitive gravity wells for LegionIO
27
+ agentic AI
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
+ - README.md
41
+ - lex-cognitive-gravity.gemspec
42
+ - lib/legion/extensions/cognitive_gravity.rb
43
+ - lib/legion/extensions/cognitive_gravity/client.rb
44
+ - lib/legion/extensions/cognitive_gravity/helpers/attractor.rb
45
+ - lib/legion/extensions/cognitive_gravity/helpers/constants.rb
46
+ - lib/legion/extensions/cognitive_gravity/helpers/gravity_engine.rb
47
+ - lib/legion/extensions/cognitive_gravity/helpers/orbiting_thought.rb
48
+ - lib/legion/extensions/cognitive_gravity/runners/gravity.rb
49
+ - lib/legion/extensions/cognitive_gravity/version.rb
50
+ homepage: https://github.com/LegionIO/lex-cognitive-gravity
51
+ licenses:
52
+ - MIT
53
+ metadata:
54
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-gravity
55
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-gravity
56
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-gravity
57
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-gravity
58
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-gravity/issues
59
+ rubygems_mfa_required: 'true'
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '3.4'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.6.9
75
+ specification_version: 4
76
+ summary: LEX Cognitive Gravity
77
+ test_files: []