lex-qualia 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: 90d75ec1c119d5845728a944d020abb717b6db31563bb414137c699e59e30359
4
+ data.tar.gz: ce574fbede579585641bd4849e55ea43a82511226c1df03daeb3924bcf6fe618
5
+ SHA512:
6
+ metadata.gz: '03910f6dd5f0234a0a99b232c8b8217caaee6f0fb2d1f5bcc5fb50ddfe797f9b16f4226c3382a59004e61ba1cf0d77727b5a466d7b4c34ceee6cd212d53242c7'
7
+ data.tar.gz: ddeea4d67e15449bca97dac7125799859ea663045146544d6ac4f1b4fb3b3921c38f86e573961e7f31b6ee028768e845494973870d3de5518bd592472c4e1eb3
@@ -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,87 @@
1
+ # lex-qualia
2
+
3
+ **Level 3 Leaf Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Gem**: `lex-qualia`
6
+ - **Version**: 0.1.0
7
+ - **Namespace**: `Legion::Extensions::Qualia`
8
+
9
+ ## Purpose
10
+
11
+ Phenomenal experience registry. Represents the subjective "what it is like" quality of the agent's processing. Each quale is defined by modality, quality, texture, vividness, and valence. Vividness decays; intense experiences are retained longer. Provides phenomenal richness scoring, experiential diversity, and qualitative classification of the agent's experience palette.
12
+
13
+ ## Gem Info
14
+
15
+ - **Homepage**: https://github.com/LegionIO/lex-qualia
16
+ - **License**: MIT
17
+ - **Ruby**: >= 3.4
18
+
19
+ ## File Structure
20
+
21
+ ```
22
+ lib/legion/extensions/qualia/
23
+ version.rb
24
+ client.rb
25
+ helpers/
26
+ constants.rb # MODALITIES, PHENOMENAL_QUALITIES, TEXTURE_TYPES, thresholds, labels
27
+ quale.rb # Quale class — single phenomenal experience
28
+ qualia_engine.rb # QualiaEngine — experience registry and analysis
29
+ runners/
30
+ qualia.rb # Runner module
31
+ spec/
32
+ helpers/quale_spec.rb
33
+ helpers/qualia_engine_spec.rb
34
+ runners/qualia_spec.rb (runners_spec.rb)
35
+ client_spec.rb
36
+ ```
37
+
38
+ ## Key Constants
39
+
40
+ From `Helpers::Constants`:
41
+ - `MAX_EXPERIENCES = 500`, `MAX_PALETTE_SIZE = 100`
42
+ - `DEFAULT_VIVIDNESS = 0.5`, `DEFAULT_VALENCE = 0.0`, `DEFAULT_TEXTURE = 0.5`
43
+ - `VIVIDNESS_DECAY = 0.03`, `VIVIDNESS_BOOST = 0.1`
44
+ - `VIVID_THRESHOLD = 0.7`, `FAINT_THRESHOLD = 0.2`, `INTENSE_THRESHOLD = 0.8`
45
+ - `MODALITIES = %i[visual auditory tactile gustatory olfactory kinesthetic emotional abstract]`
46
+ - `PHENOMENAL_QUALITIES = %i[sharp smooth warm cool heavy light bright dark flowing rigid pulsing still]`
47
+ - `TEXTURE_TYPES = %i[crystalline fluid granular electric velvet metallic organic ethereal]`
48
+ - `VIVIDNESS_LABELS`: `:overwhelming` (0.8+), `:vivid`, `:moderate`, `:faint`, `:ghost`
49
+ - `VALENCE_LABELS`: `:pleasant` (>= 0.5), `:mildly_pleasant`, `:neutral`, `:mildly_unpleasant`, `:unpleasant`
50
+ - `RICHNESS_LABELS`: `:synesthetic` (0.8+), `:rich`, `:moderate`, `:sparse`, `:flat`
51
+ - `Constants.label_for(labels_hash, value)` module-level lookup method
52
+
53
+ ## Runners
54
+
55
+ | Method | Key Parameters | Returns |
56
+ |---|---|---|
57
+ | `create_quale` | `content:`, `modality:`, `quality:`, `texture:`, `vividness:`, `valence:` | `{ success:, quale: }` |
58
+ | `intensify_quale` | `quale_id:`, `amount:` | `{ success:, quale: }` |
59
+ | `fade_all` | — | `{ success:, remaining: }` (decay + prune faint) |
60
+ | `vivid_experiences` | — | `{ success:, count:, qualia: }` |
61
+ | `by_modality` | `modality:` | `{ success:, count:, qualia: }` |
62
+ | `phenomenal_richness` | — | `{ success:, richness:, label: }` |
63
+ | `qualia_status` | — | full report: totals, vivid/faint/pleasant/unpleasant counts, avg vividness/valence, richness, most vivid |
64
+
65
+ ## Helpers
66
+
67
+ ### `Helpers::Quale`
68
+ Single experience: `id`, `content`, `modality`, `quality`, `texture`, `vividness` (clamped 0–1), `valence` (clamped -1–1). `vivid?` = vividness >= 0.7. `faint?` = vividness <= 0.2. `intense?` = vividness >= 0.8. `pleasant?` = valence > 0.2. `unpleasant?` = valence < -0.2. `intensify!(amount:)` adds to vividness. `fade!` subtracts `VIVIDNESS_DECAY`. `phenomenal_richness` = vividness*0.5 + quality_bonus + texture_bonus.
69
+
70
+ ### `Helpers::QualiaEngine`
71
+ Manages `@experiences` hash. `create_quale` prunes faint/weakest when at `MAX_EXPERIENCES`. `intensify(quale_id:, amount:)` delegates to quale. `fade_all!` decays all and prunes faint. `vivid_experiences`, `faint_experiences`, `intense_experiences`, `pleasant_experiences`, `unpleasant_experiences` filter by thresholds. `most_vivid(limit:)` sorts by vividness desc. `average_vividness`, `average_valence` mean over all. `phenomenal_richness` mean richness score. `modality_distribution` counts per modality. `quality_palette`, `texture_palette` tally by attribute. `experiential_diversity` = unique modalities / total modalities.
72
+
73
+ ## Integration Points
74
+
75
+ - `create_quale` can accept perceptual binding output from `lex-phenomenal-binding`
76
+ - Quale `valence` feeds `lex-emotion` as a secondary affective signal
77
+ - `phenomenal_richness` can feed `lex-narrator` for prose richness modulation
78
+ - `vivid_experiences` with negative valence feed `lex-cognitive-reappraisal` as reappraisal candidates
79
+ - `fade_all` called each tick for natural experience fading
80
+
81
+ ## Development Notes
82
+
83
+ - `phenomenal_richness` per quale = vividness*0.5 + quality_bonus + texture_bonus (texture/quality diversity bonuses)
84
+ - Prune on capacity: first removes entries with vividness <= 0, then removes weakest if still over capacity
85
+ - `qualia_status` uses `QualiaEngine#qualia_report` which calls `Constants.label_for` for richness label
86
+ - `experiential_diversity` = unique modalities used / `MODALITIES.size`
87
+ - All state is in-memory; reset on process restart
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,64 @@
1
+ # lex-qualia
2
+
3
+ Phenomenal experience registry for the LegionIO cognitive architecture. Represents and tracks the qualitative character of the agent's cognitive experiences.
4
+
5
+ ## What It Does
6
+
7
+ Maintains a registry of qualia — the "what it is like" quality of experiences. Each quale is described by its sensory modality, phenomenal quality, texture, vividness, and emotional valence. Vividness decays naturally, with more intense experiences lasting longer. Provides richness scoring across the experience space and qualitative analysis of the agent's current phenomenal palette.
8
+
9
+ ## Usage
10
+
11
+ ```ruby
12
+ client = Legion::Extensions::Qualia::Client.new
13
+
14
+ # Register a phenomenal experience
15
+ q = client.create_quale(
16
+ content: 'processing a novel algorithm',
17
+ modality: :abstract,
18
+ quality: :sharp,
19
+ texture: :crystalline,
20
+ vividness: 0.8,
21
+ valence: 0.3
22
+ )
23
+ quale_id = q[:quale][:id]
24
+
25
+ # Intensify with attention
26
+ client.intensify_quale(quale_id: quale_id, amount: 0.1)
27
+
28
+ # Query current experiences
29
+ client.vivid_experiences
30
+ client.by_modality(modality: :abstract)
31
+
32
+ # Phenomenal richness (diversity and vividness score)
33
+ client.phenomenal_richness
34
+ # => { success: true, richness: 0.42, label: :moderate }
35
+
36
+ # Full status report
37
+ client.qualia_status
38
+ # => { success: true, total_experiences: 1, vivid_count: 1, faint_count: 0,
39
+ # average_vividness: 0.9, phenomenal_richness: 0.42, richness_label: :moderate,
40
+ # most_vivid: [...] }
41
+
42
+ # Periodic fade (vividness decays)
43
+ client.fade_all
44
+ ```
45
+
46
+ ## Modalities
47
+
48
+ `:visual`, `:auditory`, `:tactile`, `:gustatory`, `:olfactory`, `:kinesthetic`, `:emotional`, `:abstract`
49
+
50
+ ## Richness Labels
51
+
52
+ `:synesthetic` (>= 0.8), `:rich`, `:moderate`, `:sparse`, `:flat`
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ bundle install
58
+ bundle exec rspec
59
+ bundle exec rubocop
60
+ ```
61
+
62
+ ## License
63
+
64
+ MIT
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/qualia/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-qualia'
7
+ spec.version = Legion::Extensions::Qualia::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+ spec.summary = 'Subjective phenomenal experience simulation for LegionIO agents'
11
+ spec.description = 'Models qualia — the subjective phenomenal qualities of experience ' \
12
+ 'with vividness, valence, texture, and modality for the LegionIO architecture'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-qualia'
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 Qualia
6
+ class Client
7
+ include Runners::Qualia
8
+
9
+ def initialize
10
+ @default_engine = Helpers::QualiaEngine.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 Qualia
6
+ module Helpers
7
+ module Constants
8
+ MAX_EXPERIENCES = 500
9
+ MAX_PALETTE_SIZE = 100
10
+
11
+ # Phenomenal dimensions
12
+ DEFAULT_VIVIDNESS = 0.5
13
+ DEFAULT_VALENCE = 0.0
14
+ DEFAULT_TEXTURE = 0.5
15
+ VIVIDNESS_DECAY = 0.03
16
+ VIVIDNESS_BOOST = 0.1
17
+
18
+ # Thresholds
19
+ VIVID_THRESHOLD = 0.7
20
+ FAINT_THRESHOLD = 0.2
21
+ INTENSE_THRESHOLD = 0.8
22
+
23
+ PHENOMENAL_QUALITIES = %i[
24
+ sharp smooth warm cool heavy light
25
+ bright dark flowing rigid pulsing still
26
+ ].freeze
27
+
28
+ TEXTURE_TYPES = %i[
29
+ crystalline fluid granular electric
30
+ velvet metallic organic ethereal
31
+ ].freeze
32
+
33
+ MODALITIES = %i[
34
+ visual auditory tactile gustatory
35
+ olfactory kinesthetic emotional abstract
36
+ ].freeze
37
+
38
+ VIVIDNESS_LABELS = {
39
+ (0.8..) => :overwhelming,
40
+ (0.6...0.8) => :vivid,
41
+ (0.4...0.6) => :moderate,
42
+ (0.2...0.4) => :faint,
43
+ (..0.2) => :ghost
44
+ }.freeze
45
+
46
+ VALENCE_LABELS = {
47
+ (0.5..) => :pleasant,
48
+ (0.2...0.5) => :mildly_pleasant,
49
+ (-0.2...0.2) => :neutral,
50
+ (-0.5...-0.2) => :mildly_unpleasant,
51
+ (..-0.5) => :unpleasant
52
+ }.freeze
53
+
54
+ RICHNESS_LABELS = {
55
+ (0.8..) => :synesthetic,
56
+ (0.6...0.8) => :rich,
57
+ (0.4...0.6) => :moderate,
58
+ (0.2...0.4) => :sparse,
59
+ (..0.2) => :flat
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,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Qualia
8
+ module Helpers
9
+ class Quale
10
+ include Constants
11
+
12
+ attr_reader :id, :content, :modality, :quality, :texture,
13
+ :valence, :original_vividness, :created_at
14
+ attr_accessor :vividness
15
+
16
+ def initialize(content:, modality: :abstract, quality: :smooth, texture: :fluid,
17
+ vividness: DEFAULT_VIVIDNESS, valence: DEFAULT_VALENCE)
18
+ @id = SecureRandom.uuid
19
+ @content = content.to_s
20
+ @modality = valid_modality(modality)
21
+ @quality = valid_quality(quality)
22
+ @texture = valid_texture(texture)
23
+ @vividness = vividness.to_f.clamp(0.0, 1.0).round(10)
24
+ @original_vividness = @vividness
25
+ @valence = valence.to_f.clamp(-1.0, 1.0).round(10)
26
+ @created_at = Time.now
27
+ end
28
+
29
+ def intensify!(amount: VIVIDNESS_BOOST)
30
+ @vividness = (@vividness + amount).clamp(0.0, 1.0).round(10)
31
+ self
32
+ end
33
+
34
+ def fade!
35
+ @vividness = (@vividness - VIVIDNESS_DECAY).clamp(0.0, 1.0).round(10)
36
+ self
37
+ end
38
+
39
+ def vivid? = @vividness >= VIVID_THRESHOLD
40
+ def faint? = @vividness <= FAINT_THRESHOLD
41
+ def intense? = @vividness >= INTENSE_THRESHOLD
42
+ def pleasant? = @valence > 0.2
43
+ def unpleasant? = @valence < -0.2
44
+ def neutral_valence? = @valence.abs <= 0.2
45
+
46
+ def phenomenal_richness
47
+ base = @vividness * 0.5
48
+ quality_bonus = PHENOMENAL_QUALITIES.index(@quality).to_f / PHENOMENAL_QUALITIES.size * 0.25
49
+ texture_bonus = TEXTURE_TYPES.index(@texture).to_f / TEXTURE_TYPES.size * 0.25
50
+ (base + quality_bonus + texture_bonus).clamp(0.0, 1.0).round(10)
51
+ end
52
+
53
+ def persistence = (@vividness / [@original_vividness, 0.01].max).clamp(0.0, 1.0).round(10)
54
+ def vividness_label = Constants.label_for(VIVIDNESS_LABELS, @vividness)
55
+ def valence_label = Constants.label_for(VALENCE_LABELS, @valence)
56
+ def richness_label = Constants.label_for(RICHNESS_LABELS, phenomenal_richness)
57
+
58
+ def to_h
59
+ {
60
+ id: @id,
61
+ content: @content,
62
+ modality: @modality,
63
+ quality: @quality,
64
+ texture: @texture,
65
+ vividness: @vividness,
66
+ original_vividness: @original_vividness,
67
+ valence: @valence,
68
+ phenomenal_richness: phenomenal_richness,
69
+ vivid: vivid?,
70
+ faint: faint?,
71
+ intense: intense?,
72
+ vividness_label: vividness_label,
73
+ valence_label: valence_label,
74
+ persistence: persistence,
75
+ created_at: @created_at.iso8601
76
+ }
77
+ end
78
+
79
+ private
80
+
81
+ def valid_modality(mod)
82
+ sym = mod.to_sym
83
+ MODALITIES.include?(sym) ? sym : :abstract
84
+ end
85
+
86
+ def valid_quality(qual)
87
+ sym = qual.to_sym
88
+ PHENOMENAL_QUALITIES.include?(sym) ? sym : :smooth
89
+ end
90
+
91
+ def valid_texture(tex)
92
+ sym = tex.to_sym
93
+ TEXTURE_TYPES.include?(sym) ? sym : :fluid
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Qualia
6
+ module Helpers
7
+ class QualiaEngine
8
+ include Constants
9
+
10
+ def initialize
11
+ @experiences = {}
12
+ end
13
+
14
+ def create_quale(content:, modality: :abstract, quality: :smooth, texture: :fluid,
15
+ vividness: DEFAULT_VIVIDNESS, valence: DEFAULT_VALENCE)
16
+ prune_faint
17
+ quale = Quale.new(content: content, modality: modality, quality: quality,
18
+ texture: texture, vividness: vividness, valence: valence)
19
+ @experiences[quale.id] = quale
20
+ quale
21
+ end
22
+
23
+ def intensify(quale_id:, amount: VIVIDNESS_BOOST)
24
+ quale = @experiences[quale_id]
25
+ return nil unless quale
26
+
27
+ quale.intensify!(amount: amount)
28
+ quale
29
+ end
30
+
31
+ def fade_all!
32
+ @experiences.each_value(&:fade!)
33
+ prune_faint
34
+ { remaining: @experiences.size }
35
+ end
36
+
37
+ def vivid_experiences = @experiences.values.select(&:vivid?)
38
+ def faint_experiences = @experiences.values.select(&:faint?)
39
+ def intense_experiences = @experiences.values.select(&:intense?)
40
+
41
+ def by_modality(modality:)
42
+ @experiences.values.select { |q| q.modality == modality.to_sym }
43
+ end
44
+
45
+ def by_quality(quality:)
46
+ @experiences.values.select { |q| q.quality == quality.to_sym }
47
+ end
48
+
49
+ def by_texture(texture:)
50
+ @experiences.values.select { |q| q.texture == texture.to_sym }
51
+ end
52
+
53
+ def pleasant_experiences = @experiences.values.select(&:pleasant?)
54
+ def unpleasant_experiences = @experiences.values.select(&:unpleasant?)
55
+
56
+ def most_vivid(limit: 5) = @experiences.values.sort_by { |q| -q.vividness }.first(limit)
57
+
58
+ def average_vividness
59
+ return 0.0 if @experiences.empty?
60
+
61
+ (@experiences.values.sum(&:vividness) / @experiences.size).round(10)
62
+ end
63
+
64
+ def average_valence
65
+ return 0.0 if @experiences.empty?
66
+
67
+ (@experiences.values.sum(&:valence) / @experiences.size).round(10)
68
+ end
69
+
70
+ def phenomenal_richness
71
+ return 0.0 if @experiences.empty?
72
+
73
+ (@experiences.values.sum(&:phenomenal_richness) / @experiences.size).round(10)
74
+ end
75
+
76
+ def modality_distribution
77
+ MODALITIES.to_h do |mod|
78
+ [mod, @experiences.values.count { |q| q.modality == mod }]
79
+ end
80
+ end
81
+
82
+ def quality_palette
83
+ @experiences.values.map(&:quality).tally.sort_by { |_, v| -v }.to_h
84
+ end
85
+
86
+ def texture_palette
87
+ @experiences.values.map(&:texture).tally.sort_by { |_, v| -v }.to_h
88
+ end
89
+
90
+ def experiential_diversity
91
+ return 0.0 if @experiences.empty?
92
+
93
+ modalities_used = @experiences.values.map(&:modality).uniq.size
94
+ (modalities_used.to_f / MODALITIES.size).clamp(0.0, 1.0).round(10)
95
+ end
96
+
97
+ def qualia_report
98
+ {
99
+ total_experiences: @experiences.size,
100
+ vivid_count: vivid_experiences.size,
101
+ faint_count: faint_experiences.size,
102
+ pleasant_count: pleasant_experiences.size,
103
+ unpleasant_count: unpleasant_experiences.size,
104
+ average_vividness: average_vividness,
105
+ average_valence: average_valence,
106
+ phenomenal_richness: phenomenal_richness,
107
+ experiential_diversity: experiential_diversity,
108
+ richness_label: Constants.label_for(RICHNESS_LABELS, phenomenal_richness),
109
+ most_vivid: most_vivid(limit: 3).map(&:to_h)
110
+ }
111
+ end
112
+
113
+ def to_h
114
+ {
115
+ total_experiences: @experiences.size,
116
+ vivid: vivid_experiences.size,
117
+ avg_vividness: average_vividness,
118
+ avg_valence: average_valence,
119
+ richness: phenomenal_richness
120
+ }
121
+ end
122
+
123
+ private
124
+
125
+ def prune_faint
126
+ return if @experiences.size < MAX_EXPERIENCES
127
+
128
+ @experiences.reject! { |_, q| q.vividness <= 0.0 }
129
+ return unless @experiences.size >= MAX_EXPERIENCES
130
+
131
+ weakest = @experiences.values.min_by(&:vividness)
132
+ @experiences.delete(weakest.id) if weakest
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Qualia
6
+ module Runners
7
+ module Qualia
8
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+
10
+ def create_quale(content:, modality: :abstract, quality: :smooth, texture: :fluid, vividness: nil,
11
+ valence: nil, engine: nil, **)
12
+ eng = engine || @default_engine
13
+ quale = eng.create_quale(content: content, modality: modality, quality: quality,
14
+ texture: texture,
15
+ vividness: vividness || Helpers::Constants::DEFAULT_VIVIDNESS,
16
+ valence: valence || Helpers::Constants::DEFAULT_VALENCE)
17
+ { success: true, quale: quale.to_h }
18
+ end
19
+
20
+ def intensify_quale(quale_id:, amount: nil, engine: nil, **)
21
+ eng = engine || @default_engine
22
+ quale = eng.intensify(quale_id: quale_id,
23
+ amount: amount || Helpers::Constants::VIVIDNESS_BOOST)
24
+ return { success: false, error: 'quale not found' } unless quale
25
+
26
+ { success: true, quale: quale.to_h }
27
+ end
28
+
29
+ def fade_all(engine: nil, **)
30
+ eng = engine || @default_engine
31
+ result = eng.fade_all!
32
+ { success: true, **result }
33
+ end
34
+
35
+ def vivid_experiences(engine: nil, **)
36
+ eng = engine || @default_engine
37
+ qualia = eng.vivid_experiences
38
+ { success: true, count: qualia.size, qualia: qualia.map(&:to_h) }
39
+ end
40
+
41
+ def by_modality(modality:, engine: nil, **)
42
+ eng = engine || @default_engine
43
+ qualia = eng.by_modality(modality: modality)
44
+ { success: true, count: qualia.size, qualia: qualia.map(&:to_h) }
45
+ end
46
+
47
+ def phenomenal_richness(engine: nil, **)
48
+ eng = engine || @default_engine
49
+ richness = eng.phenomenal_richness
50
+ label = Helpers::Constants.label_for(Helpers::Constants::RICHNESS_LABELS, richness)
51
+ { success: true, richness: richness, label: label }
52
+ end
53
+
54
+ def qualia_status(engine: nil, **)
55
+ eng = engine || @default_engine
56
+ report = eng.qualia_report
57
+ { success: true, **report }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Qualia
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'qualia/version'
4
+ require_relative 'qualia/helpers/constants'
5
+ require_relative 'qualia/helpers/quale'
6
+ require_relative 'qualia/helpers/qualia_engine'
7
+ require_relative 'qualia/runners/qualia'
8
+ require_relative 'qualia/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module Qualia
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Qualia::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'responds to runner methods' do
7
+ expect(client).to respond_to(:create_quale, :fade_all, :qualia_status)
8
+ end
9
+
10
+ it 'runs a full qualia lifecycle' do
11
+ result = client.create_quale(content: 'sharp insight', modality: :emotional,
12
+ quality: :sharp, texture: :crystalline, vividness: 0.8)
13
+ quale_id = result[:quale][:id]
14
+
15
+ client.intensify_quale(quale_id: quale_id)
16
+ client.fade_all
17
+
18
+ status = client.qualia_status
19
+ expect(status[:total_experiences]).to eq(1)
20
+ end
21
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Qualia::Helpers::Quale do
4
+ subject(:quale) { described_class.new(content: 'warmth of recognition') }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a UUID id' do
8
+ expect(quale.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'stores content' do
12
+ expect(quale.content).to eq('warmth of recognition')
13
+ end
14
+
15
+ it 'defaults modality to :abstract' do
16
+ expect(quale.modality).to eq(:abstract)
17
+ end
18
+
19
+ it 'defaults quality to :smooth' do
20
+ expect(quale.quality).to eq(:smooth)
21
+ end
22
+
23
+ it 'defaults texture to :fluid' do
24
+ expect(quale.texture).to eq(:fluid)
25
+ end
26
+
27
+ it 'defaults vividness' do
28
+ expect(quale.vividness).to eq(0.5)
29
+ end
30
+
31
+ it 'defaults valence to 0.0' do
32
+ expect(quale.valence).to eq(0.0)
33
+ end
34
+
35
+ it 'clamps vividness' do
36
+ high = described_class.new(content: 'x', vividness: 5.0)
37
+ expect(high.vividness).to eq(1.0)
38
+ end
39
+
40
+ it 'clamps valence' do
41
+ extreme = described_class.new(content: 'x', valence: -5.0)
42
+ expect(extreme.valence).to eq(-1.0)
43
+ end
44
+
45
+ it 'validates modality' do
46
+ bad = described_class.new(content: 'x', modality: :nonexistent)
47
+ expect(bad.modality).to eq(:abstract)
48
+ end
49
+
50
+ it 'validates quality' do
51
+ bad = described_class.new(content: 'x', quality: :nonexistent)
52
+ expect(bad.quality).to eq(:smooth)
53
+ end
54
+
55
+ it 'validates texture' do
56
+ bad = described_class.new(content: 'x', texture: :nonexistent)
57
+ expect(bad.texture).to eq(:fluid)
58
+ end
59
+ end
60
+
61
+ describe '#intensify!' do
62
+ it 'increases vividness' do
63
+ original = quale.vividness
64
+ quale.intensify!
65
+ expect(quale.vividness).to be > original
66
+ end
67
+
68
+ it 'clamps at 1.0' do
69
+ 20.times { quale.intensify! }
70
+ expect(quale.vividness).to eq(1.0)
71
+ end
72
+ end
73
+
74
+ describe '#fade!' do
75
+ it 'decreases vividness' do
76
+ original = quale.vividness
77
+ quale.fade!
78
+ expect(quale.vividness).to be < original
79
+ end
80
+
81
+ it 'clamps at 0.0' do
82
+ 50.times { quale.fade! }
83
+ expect(quale.vividness).to eq(0.0)
84
+ end
85
+ end
86
+
87
+ describe '#vivid?' do
88
+ it 'is false at default vividness' do
89
+ expect(quale.vivid?).to be false
90
+ end
91
+
92
+ it 'is true when vivid' do
93
+ vivid = described_class.new(content: 'x', vividness: 0.8)
94
+ expect(vivid.vivid?).to be true
95
+ end
96
+ end
97
+
98
+ describe '#faint?' do
99
+ it 'is false at default vividness' do
100
+ expect(quale.faint?).to be false
101
+ end
102
+
103
+ it 'is true when faint' do
104
+ faint = described_class.new(content: 'x', vividness: 0.1)
105
+ expect(faint.faint?).to be true
106
+ end
107
+ end
108
+
109
+ describe '#intense?' do
110
+ it 'is true when very vivid' do
111
+ intense = described_class.new(content: 'x', vividness: 0.9)
112
+ expect(intense.intense?).to be true
113
+ end
114
+ end
115
+
116
+ describe '#pleasant?' do
117
+ it 'is true for positive valence' do
118
+ pleasant = described_class.new(content: 'x', valence: 0.5)
119
+ expect(pleasant.pleasant?).to be true
120
+ end
121
+
122
+ it 'is false for negative valence' do
123
+ expect(quale.pleasant?).to be false
124
+ end
125
+ end
126
+
127
+ describe '#unpleasant?' do
128
+ it 'is true for negative valence' do
129
+ unpleasant = described_class.new(content: 'x', valence: -0.5)
130
+ expect(unpleasant.unpleasant?).to be true
131
+ end
132
+ end
133
+
134
+ describe '#neutral_valence?' do
135
+ it 'is true at default valence' do
136
+ expect(quale.neutral_valence?).to be true
137
+ end
138
+ end
139
+
140
+ describe '#phenomenal_richness' do
141
+ it 'returns a value between 0 and 1' do
142
+ expect(quale.phenomenal_richness).to be_between(0.0, 1.0)
143
+ end
144
+ end
145
+
146
+ describe '#persistence' do
147
+ it 'is 1.0 initially' do
148
+ expect(quale.persistence).to eq(1.0)
149
+ end
150
+
151
+ it 'decreases after fading' do
152
+ quale.fade!
153
+ expect(quale.persistence).to be < 1.0
154
+ end
155
+ end
156
+
157
+ describe '#to_h' do
158
+ it 'includes all fields' do
159
+ hash = quale.to_h
160
+ expect(hash).to include(
161
+ :id, :content, :modality, :quality, :texture, :vividness,
162
+ :original_vividness, :valence, :phenomenal_richness,
163
+ :vivid, :faint, :intense, :vividness_label, :valence_label,
164
+ :persistence, :created_at
165
+ )
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Qualia::Helpers::QualiaEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ describe '#create_quale' do
7
+ it 'creates and stores a quale' do
8
+ quale = engine.create_quale(content: 'sharpness of insight')
9
+ expect(quale.content).to eq('sharpness of insight')
10
+ end
11
+ end
12
+
13
+ describe '#intensify' do
14
+ it 'increases vividness' do
15
+ quale = engine.create_quale(content: 'test')
16
+ original = quale.vividness
17
+ engine.intensify(quale_id: quale.id)
18
+ expect(quale.vividness).to be > original
19
+ end
20
+
21
+ it 'returns nil for unknown quale' do
22
+ expect(engine.intensify(quale_id: 'bad')).to be_nil
23
+ end
24
+ end
25
+
26
+ describe '#fade_all!' do
27
+ it 'fades all qualia' do
28
+ quale = engine.create_quale(content: 'test')
29
+ original = quale.vividness
30
+ engine.fade_all!
31
+ expect(quale.vividness).to be < original
32
+ end
33
+ end
34
+
35
+ describe '#vivid_experiences' do
36
+ it 'returns vivid qualia' do
37
+ engine.create_quale(content: 'vivid', vividness: 0.8)
38
+ engine.create_quale(content: 'faint', vividness: 0.2)
39
+ expect(engine.vivid_experiences.size).to eq(1)
40
+ end
41
+ end
42
+
43
+ describe '#faint_experiences' do
44
+ it 'returns faint qualia' do
45
+ engine.create_quale(content: 'faint', vividness: 0.1)
46
+ expect(engine.faint_experiences.size).to eq(1)
47
+ end
48
+ end
49
+
50
+ describe '#by_modality' do
51
+ it 'filters by modality' do
52
+ engine.create_quale(content: 'a', modality: :visual)
53
+ engine.create_quale(content: 'b', modality: :auditory)
54
+ expect(engine.by_modality(modality: :visual).size).to eq(1)
55
+ end
56
+ end
57
+
58
+ describe '#by_quality' do
59
+ it 'filters by quality' do
60
+ engine.create_quale(content: 'a', quality: :sharp)
61
+ engine.create_quale(content: 'b', quality: :warm)
62
+ expect(engine.by_quality(quality: :sharp).size).to eq(1)
63
+ end
64
+ end
65
+
66
+ describe '#by_texture' do
67
+ it 'filters by texture' do
68
+ engine.create_quale(content: 'a', texture: :metallic)
69
+ engine.create_quale(content: 'b', texture: :velvet)
70
+ expect(engine.by_texture(texture: :metallic).size).to eq(1)
71
+ end
72
+ end
73
+
74
+ describe '#pleasant_experiences' do
75
+ it 'returns pleasant qualia' do
76
+ engine.create_quale(content: 'nice', valence: 0.5)
77
+ engine.create_quale(content: 'bad', valence: -0.5)
78
+ expect(engine.pleasant_experiences.size).to eq(1)
79
+ end
80
+ end
81
+
82
+ describe '#unpleasant_experiences' do
83
+ it 'returns unpleasant qualia' do
84
+ engine.create_quale(content: 'bad', valence: -0.5)
85
+ expect(engine.unpleasant_experiences.size).to eq(1)
86
+ end
87
+ end
88
+
89
+ describe '#most_vivid' do
90
+ it 'returns sorted by vividness descending' do
91
+ engine.create_quale(content: 'dim', vividness: 0.3)
92
+ bright = engine.create_quale(content: 'bright', vividness: 0.9)
93
+ expect(engine.most_vivid(limit: 1).first.id).to eq(bright.id)
94
+ end
95
+ end
96
+
97
+ describe '#average_vividness' do
98
+ it 'returns 0.0 with no qualia' do
99
+ expect(engine.average_vividness).to eq(0.0)
100
+ end
101
+
102
+ it 'computes average' do
103
+ engine.create_quale(content: 'a', vividness: 0.4)
104
+ engine.create_quale(content: 'b', vividness: 0.6)
105
+ expect(engine.average_vividness).to eq(0.5)
106
+ end
107
+ end
108
+
109
+ describe '#average_valence' do
110
+ it 'returns 0.0 with no qualia' do
111
+ expect(engine.average_valence).to eq(0.0)
112
+ end
113
+ end
114
+
115
+ describe '#phenomenal_richness' do
116
+ it 'returns 0.0 with no qualia' do
117
+ expect(engine.phenomenal_richness).to eq(0.0)
118
+ end
119
+
120
+ it 'returns positive with qualia' do
121
+ engine.create_quale(content: 'test', vividness: 0.8)
122
+ expect(engine.phenomenal_richness).to be > 0.0
123
+ end
124
+ end
125
+
126
+ describe '#modality_distribution' do
127
+ it 'returns counts per modality' do
128
+ engine.create_quale(content: 'a', modality: :visual)
129
+ dist = engine.modality_distribution
130
+ expect(dist[:visual]).to eq(1)
131
+ expect(dist[:auditory]).to eq(0)
132
+ end
133
+ end
134
+
135
+ describe '#experiential_diversity' do
136
+ it 'returns 0.0 with no qualia' do
137
+ expect(engine.experiential_diversity).to eq(0.0)
138
+ end
139
+
140
+ it 'increases with diverse modalities' do
141
+ engine.create_quale(content: 'a', modality: :visual)
142
+ engine.create_quale(content: 'b', modality: :auditory)
143
+ expect(engine.experiential_diversity).to be > 0.0
144
+ end
145
+ end
146
+
147
+ describe '#quality_palette' do
148
+ it 'returns quality frequencies' do
149
+ engine.create_quale(content: 'a', quality: :sharp)
150
+ engine.create_quale(content: 'b', quality: :sharp)
151
+ palette = engine.quality_palette
152
+ expect(palette[:sharp]).to eq(2)
153
+ end
154
+ end
155
+
156
+ describe '#qualia_report' do
157
+ it 'includes all report fields' do
158
+ report = engine.qualia_report
159
+ expect(report).to include(
160
+ :total_experiences, :vivid_count, :faint_count,
161
+ :pleasant_count, :unpleasant_count, :average_vividness,
162
+ :average_valence, :phenomenal_richness, :experiential_diversity,
163
+ :richness_label, :most_vivid
164
+ )
165
+ end
166
+ end
167
+
168
+ describe '#to_h' do
169
+ it 'includes summary fields' do
170
+ hash = engine.to_h
171
+ expect(hash).to include(:total_experiences, :vivid, :avg_vividness, :avg_valence, :richness)
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Qualia::Runners::Qualia do
4
+ let(:engine) { Legion::Extensions::Qualia::Helpers::QualiaEngine.new }
5
+ let(:runner) do
6
+ obj = Object.new
7
+ obj.extend(described_class)
8
+ obj.instance_variable_set(:@default_engine, engine)
9
+ obj
10
+ end
11
+
12
+ describe '#create_quale' do
13
+ it 'returns success with quale hash' do
14
+ result = runner.create_quale(content: 'test', engine: engine)
15
+ expect(result[:success]).to be true
16
+ expect(result[:quale][:content]).to eq('test')
17
+ end
18
+ end
19
+
20
+ describe '#intensify_quale' do
21
+ it 'returns success for known quale' do
22
+ quale = engine.create_quale(content: 'test')
23
+ result = runner.intensify_quale(quale_id: quale.id, engine: engine)
24
+ expect(result[:success]).to be true
25
+ end
26
+
27
+ it 'returns failure for unknown quale' do
28
+ result = runner.intensify_quale(quale_id: 'bad', engine: engine)
29
+ expect(result[:success]).to be false
30
+ end
31
+ end
32
+
33
+ describe '#fade_all' do
34
+ it 'returns success' do
35
+ result = runner.fade_all(engine: engine)
36
+ expect(result[:success]).to be true
37
+ end
38
+ end
39
+
40
+ describe '#vivid_experiences' do
41
+ it 'returns vivid list' do
42
+ engine.create_quale(content: 'vivid', vividness: 0.8)
43
+ result = runner.vivid_experiences(engine: engine)
44
+ expect(result[:count]).to eq(1)
45
+ end
46
+ end
47
+
48
+ describe '#by_modality' do
49
+ it 'filters by modality' do
50
+ engine.create_quale(content: 'a', modality: :visual)
51
+ result = runner.by_modality(modality: :visual, engine: engine)
52
+ expect(result[:count]).to eq(1)
53
+ end
54
+ end
55
+
56
+ describe '#phenomenal_richness' do
57
+ it 'returns richness level' do
58
+ result = runner.phenomenal_richness(engine: engine)
59
+ expect(result[:success]).to be true
60
+ end
61
+ end
62
+
63
+ describe '#qualia_status' do
64
+ it 'returns comprehensive status' do
65
+ result = runner.qualia_status(engine: engine)
66
+ expect(result[:success]).to be true
67
+ expect(result).to include(:total_experiences, :phenomenal_richness)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Qualia do
4
+ it 'has a version number' do
5
+ expect(Legion::Extensions::Qualia::VERSION).to eq('0.1.0')
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/qualia'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Helpers
8
+ module Lex; end
9
+ end
10
+ end
11
+
12
+ module Logging
13
+ def self.method_missing(_, *) = nil
14
+ def self.respond_to_missing?(_, _ = false) = true
15
+ end
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.expect_with :rspec do |expectations|
20
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
21
+ end
22
+ config.mock_with :rspec do |mocks|
23
+ mocks.verify_partial_doubles = true
24
+ end
25
+ config.shared_context_metadata_behavior = :apply_to_host_groups
26
+ config.order = :random
27
+ Kernel.srand config.seed
28
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-qualia
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 qualia — the subjective phenomenal qualities of experience with
27
+ vividness, valence, texture, and modality for the LegionIO architecture
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-qualia.gemspec
42
+ - lib/legion/extensions/qualia.rb
43
+ - lib/legion/extensions/qualia/client.rb
44
+ - lib/legion/extensions/qualia/helpers/constants.rb
45
+ - lib/legion/extensions/qualia/helpers/quale.rb
46
+ - lib/legion/extensions/qualia/helpers/qualia_engine.rb
47
+ - lib/legion/extensions/qualia/runners/qualia.rb
48
+ - lib/legion/extensions/qualia/version.rb
49
+ - spec/legion/extensions/qualia/client_spec.rb
50
+ - spec/legion/extensions/qualia/helpers/quale_spec.rb
51
+ - spec/legion/extensions/qualia/helpers/qualia_engine_spec.rb
52
+ - spec/legion/extensions/qualia/runners_spec.rb
53
+ - spec/legion/extensions/qualia_spec.rb
54
+ - spec/spec_helper.rb
55
+ homepage: https://github.com/LegionIO/lex-qualia
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ homepage_uri: https://github.com/LegionIO/lex-qualia
60
+ source_code_uri: https://github.com/LegionIO/lex-qualia
61
+ documentation_uri: https://github.com/LegionIO/lex-qualia/blob/origin/README.md
62
+ changelog_uri: https://github.com/LegionIO/lex-qualia/blob/origin/CHANGELOG.md
63
+ bug_tracker_uri: https://github.com/LegionIO/lex-qualia/issues
64
+ rubygems_mfa_required: 'true'
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '3.4'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.6.9
80
+ specification_version: 4
81
+ summary: Subjective phenomenal experience simulation for LegionIO agents
82
+ test_files: []