lex-cognitive-dwell 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: '083991382a820bcb5efce7990358f9b62dfe275c50b1192007ecb1dabd8f29ff'
4
+ data.tar.gz: f912d80e94ac82fc7a05521d207f815f7a92641c293834339cab598e0895c4e4
5
+ SHA512:
6
+ metadata.gz: 77353af88cf0948623549061d73903d474e42c2a8536846fb32f5344bf078c62fe547270264669addab356e9704a2d015369ca5855a28c237b20505689b30125
7
+ data.tar.gz: 46191064d7a67ce8ebfc24783140995c4db538cd4eb3ecdf9c1214f0650c5791cf8e2101f1b334015956b469369fa9381f180494d71b46a88035a4f05a76becb
@@ -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
+ Gemfile.lock
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
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,79 @@
1
+ # lex-cognitive-dwell
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 how long the system lingers on topics based on salience, novelty, emotional intensity, and complexity. Detects sticky topics and rumination. Provides attention-as-duration modeling: some topics naturally attract longer dwell times; others are fleeting. Rumination is detected when dwell time reaches critical levels.
10
+
11
+ ## Gem Info
12
+
13
+ - **Gem name**: `lex-cognitive-dwell`
14
+ - **Version**: `0.1.0`
15
+ - **Module**: `Legion::Extensions::CognitiveDwell`
16
+ - **Ruby**: `>= 3.4`
17
+ - **License**: MIT
18
+
19
+ ## File Structure
20
+
21
+ ```
22
+ lib/legion/extensions/cognitive_dwell/
23
+ cognitive_dwell.rb
24
+ version.rb
25
+ client.rb
26
+ helpers/
27
+ constants.rb
28
+ dwell_engine.rb
29
+ dwell_topic.rb
30
+ runners/
31
+ cognitive_dwell.rb
32
+ ```
33
+
34
+ ## Key Constants
35
+
36
+ From `helpers/constants.rb`:
37
+
38
+ - `TOPIC_TYPES` — `%i[problem concept conversation task memory emotion plan decision observation]`
39
+ - `MAX_TOPICS` = `200`, `MAX_DWELL_HISTORY` = `500`
40
+ - `BASE_DWELL` = `0.3`
41
+ - Dwell score weights: `SALIENCE_WEIGHT` = `0.25`, `NOVELTY_WEIGHT` = `0.25`, `EMOTION_WEIGHT` = `0.3`, `COMPLEXITY_WEIGHT` = `0.2`
42
+ - `DWELL_DECAY` = `0.05`, `ENGAGEMENT_BOOST` = `0.08`
43
+ - `STICKY_THRESHOLD` = `0.7`, `FLEETING_THRESHOLD` = `0.2`, `RUMINATION_THRESHOLD` = `0.9`
44
+ - `DWELL_LABELS` — `0.8+` = `:stuck`, `0.6` = `:engrossed`, `0.4` = `:attending`, `0.2` = `:browsing`, below = `:fleeting`
45
+ - `ENGAGEMENT_LABELS` — `0.8+` = `:deeply_engaged` through below `0.2` = `:disengaged`
46
+ - `DISENGAGE_LABELS` — `0.8+` = `:very_hard` through below `0.2` = `:effortless`
47
+
48
+ ## Runners
49
+
50
+ All methods in `Runners::CognitiveDwell`:
51
+
52
+ - `add_topic(content:, topic_type: :concept, salience: 0.5, novelty: 0.5, emotional_intensity: 0.3, complexity: 0.5)` — adds a topic with initial dwell score computed from weighted inputs
53
+ - `focus_on(topic_id:)` — boosts engagement score on a topic; increases dwell time
54
+ - `disengage(topic_id:, force: 0.0)` — reduces engagement; `force` parameter accelerates disengagement
55
+ - `decay(engine: nil)` — applies dwell decay to all topics; returns decayed count
56
+ - `current_topic` — the topic with the highest current dwell score
57
+ - `sticky_topics` — topics above `STICKY_THRESHOLD` (dwell >= 0.7)
58
+ - `ruminating_topics` — topics above `RUMINATION_THRESHOLD` (dwell >= 0.9)
59
+ - `most_engaging(limit: 5)` — top topics by engagement score
60
+ - `dwell_report` — full report: totals, sticky count, rumination count, average dwell
61
+ - `status` — engine summary
62
+
63
+ ## Helpers
64
+
65
+ - `DwellEngine` — manages topics. Dwell score = `BASE_DWELL + (salience * SALIENCE_WEIGHT) + (novelty * NOVELTY_WEIGHT) + (emotional_intensity * EMOTION_WEIGHT) + (complexity * COMPLEXITY_WEIGHT)`. `decay_all!` applies `DWELL_DECAY` to all topics.
66
+ - `DwellTopic` — has `content`, `topic_type`, `dwell_score`, `engagement`, `salience`, `novelty`, `emotional_intensity`, `complexity`. `focus!` boosts engagement. `disengage!(force)` reduces both dwell and engagement.
67
+
68
+ ## Integration Points
69
+
70
+ - `lex-cognitive-echo` models residual activation from past processing — dwell is the active attention complement: echo is background, dwell is foreground.
71
+ - `lex-tick` can check `ruminating_topics` in the health phase to detect unhealthy attention loops and trigger defusion or disengagement.
72
+ - Emotional weight (`EMOTION_WEIGHT = 0.3`) is the highest single factor — emotionally intense topics naturally attract longer dwell, modeling the affect-attention link.
73
+
74
+ ## Development Notes
75
+
76
+ - `current_topic` returns the single highest-dwell topic — the agent's most attended-to concept at any moment.
77
+ - `RUMINATION_THRESHOLD = 0.9` is deliberately high — rumination is a clinical-level concern, not normal engagement.
78
+ - `disengage(force: 0.0)` with default force is gentle; `force: 1.0` enables hard disengagement override.
79
+ - Dwell decay applies to all topics including the current topic — active engagement requires periodic `focus_on` calls to maintain dwell level.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem 'rspec', '~> 3.13'
9
+ gem 'rubocop', '~> 1.75'
10
+ gem 'rubocop-rspec'
11
+ end
12
+
13
+ gem 'legion-gaia', path: '../../legion-gaia'
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # lex-cognitive-dwell
2
+
3
+ Cognitive dwell time modeling for LegionIO. Models how long the system lingers on topics based on salience, novelty, emotional intensity, and complexity. Detects sticky topics and rumination.
4
+
5
+ ## What It Does
6
+
7
+ Not all topics receive equal attention. This extension models dwell time — how long a topic captures cognitive focus — as a weighted function of salience, novelty, emotional intensity, and complexity. Topics with high emotional intensity (weight 0.3) attract the most attention; salience and novelty each contribute equally (0.25); complexity adds a smaller pull (0.2). Dwell decays over time unless actively reinforced by focus.
8
+
9
+ Three critical thresholds: sticky (0.7, noteworthy persistence), and rumination (0.9, unhealthy fixation requiring intervention).
10
+
11
+ ## Usage
12
+
13
+ ```ruby
14
+ client = Legion::Extensions::CognitiveDwell::Client.new
15
+
16
+ topic = client.add_topic(
17
+ content: 'unresolved question about consent tier promotion criteria',
18
+ topic_type: :problem,
19
+ salience: 0.8,
20
+ novelty: 0.6,
21
+ emotional_intensity: 0.7,
22
+ complexity: 0.5
23
+ )
24
+
25
+ client.focus_on(topic_id: topic[:topic][:id])
26
+ client.sticky_topics
27
+ client.ruminating_topics
28
+
29
+ client.decay # call each tick
30
+ client.dwell_report
31
+ ```
32
+
33
+ ## Development
34
+
35
+ ```bash
36
+ bundle install
37
+ bundle exec rspec
38
+ bundle exec rubocop
39
+ ```
40
+
41
+ ## License
42
+
43
+ MIT
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_dwell/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-dwell'
7
+ spec.version = Legion::Extensions::CognitiveDwell::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'Cognitive dwell time modeling for LegionIO'
12
+ spec.description = 'Models how long the system lingers on topics based on salience, novelty, emotional ' \
13
+ 'intensity, and complexity. Detects sticky topics and rumination.'
14
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-dwell'
15
+ spec.license = 'MIT'
16
+
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-dwell'
21
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-dwell/blob/master/README.md'
22
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-dwell/blob/master/CHANGELOG.md'
23
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-dwell/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 CognitiveDwell
6
+ class Client
7
+ include Runners::CognitiveDwell
8
+
9
+ def initialize(engine: nil)
10
+ @default_engine = engine || Helpers::DwellEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveDwell
6
+ module Helpers
7
+ module Constants
8
+ MAX_TOPICS = 200
9
+ MAX_DWELL_HISTORY = 500
10
+
11
+ # Dwell dynamics
12
+ BASE_DWELL = 0.3
13
+ SALIENCE_WEIGHT = 0.25
14
+ NOVELTY_WEIGHT = 0.25
15
+ EMOTION_WEIGHT = 0.3
16
+ COMPLEXITY_WEIGHT = 0.2
17
+ DWELL_DECAY = 0.05
18
+ ENGAGEMENT_BOOST = 0.08
19
+
20
+ # Thresholds
21
+ STICKY_THRESHOLD = 0.7
22
+ FLEETING_THRESHOLD = 0.2
23
+ RUMINATION_THRESHOLD = 0.9
24
+
25
+ # Topic types
26
+ TOPIC_TYPES = %i[
27
+ problem concept conversation task memory
28
+ emotion plan decision observation
29
+ ].freeze
30
+
31
+ # Dwell duration labels
32
+ DWELL_LABELS = {
33
+ (0.8..) => :stuck,
34
+ (0.6...0.8) => :engrossed,
35
+ (0.4...0.6) => :attending,
36
+ (0.2...0.4) => :browsing,
37
+ (..0.2) => :fleeting
38
+ }.freeze
39
+
40
+ # Engagement labels
41
+ ENGAGEMENT_LABELS = {
42
+ (0.8..) => :deeply_engaged,
43
+ (0.6...0.8) => :engaged,
44
+ (0.4...0.6) => :moderate,
45
+ (0.2...0.4) => :light,
46
+ (..0.2) => :disengaged
47
+ }.freeze
48
+
49
+ # Disengagement difficulty labels
50
+ DISENGAGE_LABELS = {
51
+ (0.8..) => :very_hard,
52
+ (0.6...0.8) => :hard,
53
+ (0.4...0.6) => :moderate,
54
+ (0.2...0.4) => :easy,
55
+ (..0.2) => :effortless
56
+ }.freeze
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveDwell
6
+ module Helpers
7
+ class DwellEngine
8
+ include Constants
9
+
10
+ def initialize
11
+ @topics = {}
12
+ @current_topic_id = nil
13
+ end
14
+
15
+ def add_topic(content:, topic_type: :concept, salience: 0.5,
16
+ novelty: 0.5, emotional_intensity: 0.3, complexity: 0.5)
17
+ prune_if_needed
18
+ topic = DwellTopic.new(
19
+ content: content, topic_type: topic_type, salience: salience,
20
+ novelty: novelty, emotional_intensity: emotional_intensity, complexity: complexity
21
+ )
22
+ @topics[topic.id] = topic
23
+ topic
24
+ end
25
+
26
+ def focus_on(topic_id:)
27
+ topic = @topics[topic_id]
28
+ return nil unless topic
29
+
30
+ topic.engage!
31
+ @current_topic_id = topic_id
32
+ topic
33
+ end
34
+
35
+ def disengage(topic_id:, force: 0.0)
36
+ topic = @topics[topic_id]
37
+ return nil unless topic
38
+
39
+ topic.disengage!(force: force)
40
+ @current_topic_id = nil if @current_topic_id == topic_id
41
+ topic
42
+ end
43
+
44
+ def decay_all!
45
+ @topics.each_value(&:decay!)
46
+ { topics_decayed: @topics.size }
47
+ end
48
+
49
+ def current_topic
50
+ @topics[@current_topic_id]
51
+ end
52
+
53
+ def sticky_topics
54
+ @topics.values.select(&:sticky?)
55
+ end
56
+
57
+ def fleeting_topics
58
+ @topics.values.select(&:fleeting?)
59
+ end
60
+
61
+ def ruminating_topics
62
+ @topics.values.select(&:ruminating?)
63
+ end
64
+
65
+ def most_engaging(limit: 5)
66
+ @topics.values.sort_by { |t| -t.dwell_level }.first(limit)
67
+ end
68
+
69
+ def hardest_to_disengage(limit: 5)
70
+ @topics.values.sort_by { |t| -t.disengagement_difficulty }.first(limit)
71
+ end
72
+
73
+ def average_dwell
74
+ return BASE_DWELL if @topics.empty?
75
+
76
+ vals = @topics.values.map(&:dwell_level)
77
+ (vals.sum / vals.size).round(10)
78
+ end
79
+
80
+ def average_disengagement_difficulty
81
+ return 0.0 if @topics.empty?
82
+
83
+ vals = @topics.values.map(&:disengagement_difficulty)
84
+ (vals.sum / vals.size).round(10)
85
+ end
86
+
87
+ def dwell_report
88
+ {
89
+ total_topics: @topics.size,
90
+ current_topic: current_topic&.to_h,
91
+ sticky_count: sticky_topics.size,
92
+ fleeting_count: fleeting_topics.size,
93
+ ruminating_count: ruminating_topics.size,
94
+ average_dwell: average_dwell,
95
+ average_disengagement_difficulty: average_disengagement_difficulty,
96
+ most_engaging: most_engaging(limit: 3).map(&:to_h)
97
+ }
98
+ end
99
+
100
+ def to_h
101
+ {
102
+ total_topics: @topics.size,
103
+ current_topic_id: @current_topic_id,
104
+ sticky_count: sticky_topics.size,
105
+ ruminating_count: ruminating_topics.size,
106
+ average_dwell: average_dwell
107
+ }
108
+ end
109
+
110
+ private
111
+
112
+ def prune_if_needed
113
+ return if @topics.size < MAX_TOPICS
114
+
115
+ least_engaged = @topics.values.min_by(&:dwell_level)
116
+ @topics.delete(least_engaged.id) if least_engaged
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveDwell
8
+ module Helpers
9
+ class DwellTopic
10
+ include Constants
11
+
12
+ attr_reader :id, :content, :topic_type, :salience, :novelty,
13
+ :emotional_intensity, :complexity, :dwell_level,
14
+ :engagement_count, :created_at
15
+
16
+ def initialize(content:, topic_type: :concept, salience: 0.5,
17
+ novelty: 0.5, emotional_intensity: 0.3, complexity: 0.5)
18
+ @id = SecureRandom.uuid
19
+ @content = content
20
+ @topic_type = topic_type.to_sym
21
+ @salience = salience.to_f.clamp(0.0, 1.0).round(10)
22
+ @novelty = novelty.to_f.clamp(0.0, 1.0).round(10)
23
+ @emotional_intensity = emotional_intensity.to_f.clamp(0.0, 1.0).round(10)
24
+ @complexity = complexity.to_f.clamp(0.0, 1.0).round(10)
25
+ @dwell_level = compute_initial_dwell
26
+ @engagement_count = 0
27
+ @created_at = Time.now.utc
28
+ end
29
+
30
+ def engage!
31
+ @engagement_count += 1
32
+ @dwell_level = (@dwell_level + ENGAGEMENT_BOOST).clamp(0.0, 1.0).round(10)
33
+ self
34
+ end
35
+
36
+ def decay!
37
+ @dwell_level = (@dwell_level - DWELL_DECAY).clamp(0.0, 1.0).round(10)
38
+ @novelty = (@novelty - 0.02).clamp(0.0, 1.0).round(10)
39
+ self
40
+ end
41
+
42
+ def disengage!(force: 0.0)
43
+ reduction = (0.1 + force).clamp(0.0, 1.0)
44
+ @dwell_level = (@dwell_level - reduction).clamp(0.0, 1.0).round(10)
45
+ self
46
+ end
47
+
48
+ def sticky?
49
+ @dwell_level >= STICKY_THRESHOLD
50
+ end
51
+
52
+ def fleeting?
53
+ @dwell_level <= FLEETING_THRESHOLD
54
+ end
55
+
56
+ def ruminating?
57
+ @dwell_level >= RUMINATION_THRESHOLD
58
+ end
59
+
60
+ def disengagement_difficulty
61
+ ((@emotional_intensity * 0.4) + (@dwell_level * 0.4) + (@complexity * 0.2)).round(10)
62
+ end
63
+
64
+ def dwell_label
65
+ match = DWELL_LABELS.find { |range, _| range.cover?(@dwell_level) }
66
+ match ? match.last : :fleeting
67
+ end
68
+
69
+ def engagement_label
70
+ score = ((@dwell_level * 0.6) + (@salience * 0.4)).round(10)
71
+ match = ENGAGEMENT_LABELS.find { |range, _| range.cover?(score) }
72
+ match ? match.last : :disengaged
73
+ end
74
+
75
+ def disengage_label
76
+ match = DISENGAGE_LABELS.find { |range, _| range.cover?(disengagement_difficulty) }
77
+ match ? match.last : :effortless
78
+ end
79
+
80
+ def to_h
81
+ {
82
+ id: @id,
83
+ content: @content,
84
+ topic_type: @topic_type,
85
+ salience: @salience,
86
+ novelty: @novelty,
87
+ emotional_intensity: @emotional_intensity,
88
+ complexity: @complexity,
89
+ dwell_level: @dwell_level,
90
+ dwell_label: dwell_label,
91
+ sticky: sticky?,
92
+ fleeting: fleeting?,
93
+ ruminating: ruminating?,
94
+ disengagement_difficulty: disengagement_difficulty,
95
+ disengage_label: disengage_label,
96
+ engagement_count: @engagement_count,
97
+ engagement_label: engagement_label,
98
+ created_at: @created_at
99
+ }
100
+ end
101
+
102
+ private
103
+
104
+ def compute_initial_dwell
105
+ (BASE_DWELL +
106
+ (@salience * SALIENCE_WEIGHT) +
107
+ (@novelty * NOVELTY_WEIGHT) +
108
+ (@emotional_intensity * EMOTION_WEIGHT) +
109
+ (@complexity * COMPLEXITY_WEIGHT)).clamp(0.0, 1.0).round(10)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveDwell
6
+ module Runners
7
+ module CognitiveDwell
8
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+
10
+ def add_topic(content:, topic_type: :concept, salience: 0.5,
11
+ novelty: 0.5, emotional_intensity: 0.3, complexity: 0.5, engine: nil, **)
12
+ eng = engine || default_engine
13
+ topic = eng.add_topic(content: content, topic_type: topic_type, salience: salience,
14
+ novelty: novelty, emotional_intensity: emotional_intensity,
15
+ complexity: complexity)
16
+ { success: true, topic: topic.to_h }
17
+ end
18
+
19
+ def focus_on(topic_id:, engine: nil, **)
20
+ eng = engine || default_engine
21
+ topic = eng.focus_on(topic_id: topic_id)
22
+ return { success: false, error: 'topic not found' } unless topic
23
+
24
+ { success: true, topic: topic.to_h }
25
+ end
26
+
27
+ def disengage(topic_id:, force: 0.0, engine: nil, **)
28
+ eng = engine || default_engine
29
+ topic = eng.disengage(topic_id: topic_id, force: force)
30
+ return { success: false, error: 'topic not found' } unless topic
31
+
32
+ { success: true, topic: topic.to_h }
33
+ end
34
+
35
+ def decay(engine: nil, **)
36
+ eng = engine || default_engine
37
+ result = eng.decay_all!
38
+ { success: true, **result }
39
+ end
40
+
41
+ def current_topic(engine: nil, **)
42
+ eng = engine || default_engine
43
+ topic = eng.current_topic
44
+ return { success: false, error: 'no current topic' } unless topic
45
+
46
+ { success: true, topic: topic.to_h }
47
+ end
48
+
49
+ def sticky_topics(engine: nil, **)
50
+ eng = engine || default_engine
51
+ { success: true, topics: eng.sticky_topics.map(&:to_h) }
52
+ end
53
+
54
+ def ruminating_topics(engine: nil, **)
55
+ eng = engine || default_engine
56
+ { success: true, topics: eng.ruminating_topics.map(&:to_h) }
57
+ end
58
+
59
+ def most_engaging(limit: 5, engine: nil, **)
60
+ eng = engine || default_engine
61
+ { success: true, topics: eng.most_engaging(limit: limit).map(&:to_h) }
62
+ end
63
+
64
+ def dwell_report(engine: nil, **)
65
+ eng = engine || default_engine
66
+ { success: true, report: eng.dwell_report }
67
+ end
68
+
69
+ def status(engine: nil, **)
70
+ eng = engine || default_engine
71
+ { success: true, **eng.to_h }
72
+ end
73
+
74
+ private
75
+
76
+ def default_engine
77
+ @default_engine ||= Helpers::DwellEngine.new
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveDwell
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cognitive_dwell/version'
4
+ require_relative 'cognitive_dwell/helpers/constants'
5
+ require_relative 'cognitive_dwell/helpers/dwell_topic'
6
+ require_relative 'cognitive_dwell/helpers/dwell_engine'
7
+ require_relative 'cognitive_dwell/runners/cognitive_dwell'
8
+ require_relative 'cognitive_dwell/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module CognitiveDwell
13
+ extend Legion::Extensions::Core if defined?(Legion::Extensions::Core)
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-dwell
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 how long the system lingers on topics based on salience, novelty,
27
+ emotional intensity, and complexity. Detects sticky topics and rumination.
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-dwell.gemspec
42
+ - lib/legion/extensions/cognitive_dwell.rb
43
+ - lib/legion/extensions/cognitive_dwell/client.rb
44
+ - lib/legion/extensions/cognitive_dwell/helpers/constants.rb
45
+ - lib/legion/extensions/cognitive_dwell/helpers/dwell_engine.rb
46
+ - lib/legion/extensions/cognitive_dwell/helpers/dwell_topic.rb
47
+ - lib/legion/extensions/cognitive_dwell/runners/cognitive_dwell.rb
48
+ - lib/legion/extensions/cognitive_dwell/version.rb
49
+ homepage: https://github.com/LegionIO/lex-cognitive-dwell
50
+ licenses:
51
+ - MIT
52
+ metadata:
53
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-dwell
54
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-dwell
55
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-dwell/blob/master/README.md
56
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-dwell/blob/master/CHANGELOG.md
57
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-dwell/issues
58
+ rubygems_mfa_required: 'true'
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '3.4'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 3.6.9
74
+ specification_version: 4
75
+ summary: Cognitive dwell time modeling for LegionIO
76
+ test_files: []