lex-agentic-language 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.
Files changed (126) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-language.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/language/conceptual_blending/client.rb +25 -0
  8. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/blend.rb +91 -0
  9. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/blending_engine.rb +171 -0
  10. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/constants.rb +35 -0
  11. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/mental_space.rb +51 -0
  12. data/lib/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending.rb +106 -0
  13. data/lib/legion/extensions/agentic/language/conceptual_blending/version.rb +13 -0
  14. data/lib/legion/extensions/agentic/language/conceptual_blending.rb +20 -0
  15. data/lib/legion/extensions/agentic/language/conceptual_metaphor/client.rb +19 -0
  16. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/constants.rb +49 -0
  17. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor.rb +109 -0
  18. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_engine.rb +154 -0
  19. data/lib/legion/extensions/agentic/language/conceptual_metaphor/runners/conceptual_metaphor.rb +107 -0
  20. data/lib/legion/extensions/agentic/language/conceptual_metaphor/version.rb +13 -0
  21. data/lib/legion/extensions/agentic/language/conceptual_metaphor.rb +19 -0
  22. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/client.rb +23 -0
  23. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/constants.rb +32 -0
  24. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame.rb +109 -0
  25. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame_engine.rb +139 -0
  26. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame_instance.rb +51 -0
  27. data/lib/legion/extensions/agentic/language/frame_semantics/runners/frame_semantics.rb +108 -0
  28. data/lib/legion/extensions/agentic/language/frame_semantics/version.rb +13 -0
  29. data/lib/legion/extensions/agentic/language/frame_semantics.rb +20 -0
  30. data/lib/legion/extensions/agentic/language/grammar/client.rb +29 -0
  31. data/lib/legion/extensions/agentic/language/grammar/helpers/constants.rb +38 -0
  32. data/lib/legion/extensions/agentic/language/grammar/helpers/construal.rb +68 -0
  33. data/lib/legion/extensions/agentic/language/grammar/helpers/construction.rb +68 -0
  34. data/lib/legion/extensions/agentic/language/grammar/helpers/grammar_engine.rb +119 -0
  35. data/lib/legion/extensions/agentic/language/grammar/runners/cognitive_grammar.rb +100 -0
  36. data/lib/legion/extensions/agentic/language/grammar/version.rb +13 -0
  37. data/lib/legion/extensions/agentic/language/grammar.rb +20 -0
  38. data/lib/legion/extensions/agentic/language/inner_speech/client.rb +19 -0
  39. data/lib/legion/extensions/agentic/language/inner_speech/helpers/constants.rb +53 -0
  40. data/lib/legion/extensions/agentic/language/inner_speech/helpers/inner_voice.rb +141 -0
  41. data/lib/legion/extensions/agentic/language/inner_speech/helpers/speech_stream.rb +128 -0
  42. data/lib/legion/extensions/agentic/language/inner_speech/helpers/utterance.rb +90 -0
  43. data/lib/legion/extensions/agentic/language/inner_speech/runners/inner_speech.rb +87 -0
  44. data/lib/legion/extensions/agentic/language/inner_speech/version.rb +13 -0
  45. data/lib/legion/extensions/agentic/language/inner_speech.rb +20 -0
  46. data/lib/legion/extensions/agentic/language/language/client.rb +26 -0
  47. data/lib/legion/extensions/agentic/language/language/helpers/constants.rb +43 -0
  48. data/lib/legion/extensions/agentic/language/language/helpers/lexicon.rb +63 -0
  49. data/lib/legion/extensions/agentic/language/language/helpers/summarizer.rb +167 -0
  50. data/lib/legion/extensions/agentic/language/language/runners/language.rb +134 -0
  51. data/lib/legion/extensions/agentic/language/language/version.rb +13 -0
  52. data/lib/legion/extensions/agentic/language/language.rb +19 -0
  53. data/lib/legion/extensions/agentic/language/narrative_reasoning/client.rb +28 -0
  54. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative.rb +123 -0
  55. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine.rb +122 -0
  56. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event.rb +41 -0
  57. data/lib/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning.rb +122 -0
  58. data/lib/legion/extensions/agentic/language/narrative_reasoning/version.rb +13 -0
  59. data/lib/legion/extensions/agentic/language/narrative_reasoning.rb +18 -0
  60. data/lib/legion/extensions/agentic/language/narrator/client.rb +27 -0
  61. data/lib/legion/extensions/agentic/language/narrator/helpers/constants.rb +69 -0
  62. data/lib/legion/extensions/agentic/language/narrator/helpers/journal.rb +68 -0
  63. data/lib/legion/extensions/agentic/language/narrator/helpers/llm_enhancer.rb +105 -0
  64. data/lib/legion/extensions/agentic/language/narrator/helpers/prose.rb +122 -0
  65. data/lib/legion/extensions/agentic/language/narrator/helpers/synthesizer.rb +138 -0
  66. data/lib/legion/extensions/agentic/language/narrator/runners/narrator.rb +196 -0
  67. data/lib/legion/extensions/agentic/language/narrator/version.rb +13 -0
  68. data/lib/legion/extensions/agentic/language/narrator.rb +21 -0
  69. data/lib/legion/extensions/agentic/language/pragmatic_inference/client.rb +28 -0
  70. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/constants.rb +52 -0
  71. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine.rb +164 -0
  72. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/utterance.rb +84 -0
  73. data/lib/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference.rb +136 -0
  74. data/lib/legion/extensions/agentic/language/pragmatic_inference/version.rb +13 -0
  75. data/lib/legion/extensions/agentic/language/pragmatic_inference.rb +18 -0
  76. data/lib/legion/extensions/agentic/language/version.rb +11 -0
  77. data/lib/legion/extensions/agentic/language.rb +28 -0
  78. data/spec/legion/extensions/agentic/language/conceptual_blending/client_spec.rb +78 -0
  79. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/blend_spec.rb +141 -0
  80. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/blending_engine_spec.rb +211 -0
  81. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/mental_space_spec.rb +85 -0
  82. data/spec/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending_spec.rb +162 -0
  83. data/spec/legion/extensions/agentic/language/conceptual_metaphor/client_spec.rb +29 -0
  84. data/spec/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_engine_spec.rb +166 -0
  85. data/spec/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_spec.rb +133 -0
  86. data/spec/legion/extensions/agentic/language/conceptual_metaphor/runners/conceptual_metaphor_spec.rb +133 -0
  87. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_engine_spec.rb +227 -0
  88. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_instance_spec.rb +83 -0
  89. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_spec.rb +213 -0
  90. data/spec/legion/extensions/agentic/language/frame_semantics/runners/frame_semantics_spec.rb +155 -0
  91. data/spec/legion/extensions/agentic/language/grammar/client_spec.rb +121 -0
  92. data/spec/legion/extensions/agentic/language/grammar/cognitive_grammar_spec.rb +18 -0
  93. data/spec/legion/extensions/agentic/language/grammar/helpers/constants_spec.rb +67 -0
  94. data/spec/legion/extensions/agentic/language/grammar/helpers/construal_spec.rb +124 -0
  95. data/spec/legion/extensions/agentic/language/grammar/helpers/construction_spec.rb +155 -0
  96. data/spec/legion/extensions/agentic/language/grammar/helpers/grammar_engine_spec.rb +206 -0
  97. data/spec/legion/extensions/agentic/language/grammar/runners/cognitive_grammar_spec.rb +189 -0
  98. data/spec/legion/extensions/agentic/language/inner_speech/client_spec.rb +39 -0
  99. data/spec/legion/extensions/agentic/language/inner_speech/helpers/inner_voice_spec.rb +185 -0
  100. data/spec/legion/extensions/agentic/language/inner_speech/helpers/speech_stream_spec.rb +158 -0
  101. data/spec/legion/extensions/agentic/language/inner_speech/helpers/utterance_spec.rb +121 -0
  102. data/spec/legion/extensions/agentic/language/inner_speech/runners/inner_speech_spec.rb +102 -0
  103. data/spec/legion/extensions/agentic/language/language/client_spec.rb +20 -0
  104. data/spec/legion/extensions/agentic/language/language/helpers/constants_spec.rb +31 -0
  105. data/spec/legion/extensions/agentic/language/language/helpers/lexicon_spec.rb +116 -0
  106. data/spec/legion/extensions/agentic/language/language/helpers/summarizer_spec.rb +224 -0
  107. data/spec/legion/extensions/agentic/language/language/runners/language_spec.rb +169 -0
  108. data/spec/legion/extensions/agentic/language/narrative_reasoning/client_spec.rb +19 -0
  109. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine_spec.rb +182 -0
  110. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event_spec.rb +61 -0
  111. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_spec.rb +168 -0
  112. data/spec/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning_spec.rb +174 -0
  113. data/spec/legion/extensions/agentic/language/narrator/client_spec.rb +24 -0
  114. data/spec/legion/extensions/agentic/language/narrator/helpers/journal_spec.rb +95 -0
  115. data/spec/legion/extensions/agentic/language/narrator/helpers/llm_enhancer_spec.rb +107 -0
  116. data/spec/legion/extensions/agentic/language/narrator/helpers/prose_spec.rb +134 -0
  117. data/spec/legion/extensions/agentic/language/narrator/helpers/synthesizer_spec.rb +89 -0
  118. data/spec/legion/extensions/agentic/language/narrator/runners/narrator_llm_spec.rb +74 -0
  119. data/spec/legion/extensions/agentic/language/narrator/runners/narrator_spec.rb +126 -0
  120. data/spec/legion/extensions/agentic/language/pragmatic_inference/client_spec.rb +19 -0
  121. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/constants_spec.rb +73 -0
  122. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine_spec.rb +185 -0
  123. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/utterance_spec.rb +111 -0
  124. data/spec/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference_spec.rb +231 -0
  125. data/spec/spec_helper.rb +33 -0
  126. metadata +210 -0
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module Grammar
8
+ module Runners
9
+ module CognitiveGrammar
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ def create_grammar_construction(form:, meaning:, expression_type:, domain:, **)
14
+ construction = engine.create_construction(
15
+ form: form,
16
+ meaning: meaning,
17
+ expression_type: expression_type,
18
+ domain: domain
19
+ )
20
+ Legion::Logging.debug "[cognitive_grammar] created construction form=#{form} type=#{expression_type} domain=#{domain}"
21
+ construction.to_h
22
+ end
23
+
24
+ def create_grammar_construal(scene:, perspective:, figure:, ground:, # rubocop:disable Metrics/ParameterLists
25
+ specificity: :intermediate, scope: :local,
26
+ dynamicity: 0.5, construction_id: nil, **)
27
+ construal = engine.create_construal(
28
+ scene: scene,
29
+ perspective: perspective,
30
+ figure: figure,
31
+ ground: ground,
32
+ specificity: specificity,
33
+ scope: scope,
34
+ dynamicity: dynamicity,
35
+ construction_id: construction_id
36
+ )
37
+ Legion::Logging.debug "[cognitive_grammar] created construal scene=#{scene} figure=#{figure}"
38
+ construal.to_h
39
+ end
40
+
41
+ def use_grammar_construction(construction_id:, **)
42
+ construction = engine.use_construction(construction_id: construction_id)
43
+ return { found: false, construction_id: construction_id } unless construction
44
+
45
+ msg = "[cognitive_grammar] used construction id=#{construction_id[0..7]} " \
46
+ "usage_count=#{construction.usage_count} activation=#{construction.activation.round(3)}"
47
+ Legion::Logging.debug msg
48
+ { found: true, construction: construction.to_h }
49
+ end
50
+
51
+ def construals_for_scene_report(scene:, **)
52
+ construals = engine.construals_for_scene(scene: scene)
53
+ Legion::Logging.debug "[cognitive_grammar] construals_for_scene scene=#{scene} count=#{construals.size}"
54
+ { scene: scene, count: construals.size, construals: construals.map(&:to_h) }
55
+ end
56
+
57
+ def entrenched_constructions_report(**)
58
+ constructions = engine.entrenched_constructions
59
+ Legion::Logging.debug "[cognitive_grammar] entrenched constructions count=#{constructions.size}"
60
+ { count: constructions.size, constructions: constructions.map(&:to_h) }
61
+ end
62
+
63
+ def constructions_by_domain_report(domain:, **)
64
+ constructions = engine.constructions_by_domain(domain: domain)
65
+ Legion::Logging.debug "[cognitive_grammar] constructions_by_domain domain=#{domain} count=#{constructions.size}"
66
+ { domain: domain, count: constructions.size, constructions: constructions.map(&:to_h) }
67
+ end
68
+
69
+ def most_used_constructions(limit: 5, **)
70
+ constructions = engine.most_used(limit: limit)
71
+ Legion::Logging.debug "[cognitive_grammar] most_used limit=#{limit} count=#{constructions.size}"
72
+ { limit: limit, count: constructions.size, constructions: constructions.map(&:to_h) }
73
+ end
74
+
75
+ def update_cognitive_grammar(**)
76
+ engine.decay_all
77
+ pruned = engine.prune_inactive
78
+ stats = engine.to_h
79
+ Legion::Logging.debug "[cognitive_grammar] update: pruned=#{pruned} remaining=#{stats[:constructions_count]}"
80
+ { pruned: pruned, stats: stats }
81
+ end
82
+
83
+ def cognitive_grammar_stats(**)
84
+ stats = engine.to_h
85
+ Legion::Logging.debug "[cognitive_grammar] stats constructions=#{stats[:constructions_count]} construals=#{stats[:construals_count]}"
86
+ stats
87
+ end
88
+
89
+ private
90
+
91
+ def engine
92
+ @engine ||= Helpers::GrammarEngine.new
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module Grammar
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/grammar/version'
4
+ require 'legion/extensions/agentic/language/grammar/helpers/constants'
5
+ require 'legion/extensions/agentic/language/grammar/helpers/construction'
6
+ require 'legion/extensions/agentic/language/grammar/helpers/construal'
7
+ require 'legion/extensions/agentic/language/grammar/helpers/grammar_engine'
8
+ require 'legion/extensions/agentic/language/grammar/runners/cognitive_grammar'
9
+ require 'legion/extensions/agentic/language/grammar/client'
10
+
11
+ module Legion
12
+ module Extensions
13
+ module Agentic
14
+ module Language
15
+ module Grammar
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module InnerSpeech
8
+ class Client
9
+ include Runners::InnerSpeech
10
+
11
+ def initialize(voice: nil)
12
+ @voice = voice || Helpers::InnerVoice.new
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module InnerSpeech
8
+ module Helpers
9
+ module Constants
10
+ MAX_UTTERANCES = 500
11
+ MAX_STREAM_LENGTH = 100
12
+ MAX_HISTORY = 200
13
+
14
+ # Speech condensation: inner speech is ~3x compressed vs external
15
+ CONDENSATION_RATIO = 0.33
16
+
17
+ # Rumination detection: same topic repeated N+ times
18
+ RUMINATION_THRESHOLD = 3
19
+
20
+ # Speed constants (utterances per tick)
21
+ AUTOMATIC_SPEED = 3
22
+ CONTROLLED_SPEED = 1
23
+ EGOCENTRIC_SPEED = 0.5
24
+
25
+ # Decay: how quickly old utterances lose salience
26
+ SALIENCE_DECAY = 0.05
27
+ SALIENCE_FLOOR = 0.01
28
+
29
+ SPEECH_MODES = %i[
30
+ planning rehearsal monitoring evaluating
31
+ questioning affirming narrating debating
32
+ comforting warning remembering imagining
33
+ ].freeze
34
+
35
+ VOICE_TYPES = %i[
36
+ rational emotional cautious bold
37
+ critical supportive curious skeptical
38
+ ].freeze
39
+
40
+ URGENCY_LABELS = {
41
+ (0.8..) => :critical,
42
+ (0.6...0.8) => :high,
43
+ (0.4...0.6) => :moderate,
44
+ (0.2...0.4) => :low,
45
+ (..0.2) => :background
46
+ }.freeze
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module InnerSpeech
8
+ module Helpers
9
+ class InnerVoice
10
+ include Constants
11
+
12
+ attr_reader :stream, :voices, :active_voice, :history
13
+
14
+ def initialize
15
+ @stream = SpeechStream.new
16
+ @voices = VOICE_TYPES.dup
17
+ @active_voice = :rational
18
+ @history = []
19
+ @speed = CONTROLLED_SPEED
20
+ end
21
+
22
+ def speak(content:, mode: :narrating, topic: :general, **)
23
+ utterance = @stream.append(
24
+ content: content,
25
+ mode: mode,
26
+ voice: @active_voice,
27
+ topic: topic,
28
+ **
29
+ )
30
+ record_event(:speak, utterance_id: utterance&.id) if utterance
31
+ utterance
32
+ end
33
+
34
+ def switch_voice(voice:)
35
+ sym = voice.to_sym
36
+ return nil unless VOICE_TYPES.include?(sym)
37
+
38
+ old_voice = @active_voice
39
+ @active_voice = sym
40
+ record_event(:switch_voice, from: old_voice, to: sym)
41
+ @active_voice
42
+ end
43
+
44
+ def plan(content:, topic: :general, **)
45
+ speak(content: content, mode: :planning, topic: topic, **)
46
+ end
47
+
48
+ def rehearse(content:, topic: :general, **)
49
+ speak(content: content, mode: :rehearsal, topic: topic, **)
50
+ end
51
+
52
+ def question(content:, topic: :general, **)
53
+ speak(content: content, mode: :questioning, topic: topic, **)
54
+ end
55
+
56
+ def evaluate(content:, topic: :general, **)
57
+ speak(content: content, mode: :evaluating, topic: topic, **)
58
+ end
59
+
60
+ def warn(content:, topic: :general, **)
61
+ speak(content: content, mode: :warning, topic: topic, urgency: 0.8, **)
62
+ end
63
+
64
+ def debate(content_a:, content_b:, topic: :general, **)
65
+ old_voice = @active_voice
66
+ @active_voice = :bold
67
+ utt_a = speak(content: content_a, mode: :debating, topic: topic, **)
68
+ @active_voice = :cautious
69
+ utt_b = speak(content: content_b, mode: :debating, topic: topic, **)
70
+ @active_voice = old_voice
71
+ [utt_a, utt_b].compact
72
+ end
73
+
74
+ def interrupt(content:, **)
75
+ utterance = @stream.interrupt(content: content, **)
76
+ record_event(:interrupt, utterance_id: utterance&.id) if utterance
77
+ utterance
78
+ end
79
+
80
+ SPEED_MAP = {
81
+ automatic: AUTOMATIC_SPEED,
82
+ controlled: CONTROLLED_SPEED,
83
+ egocentric: EGOCENTRIC_SPEED
84
+ }.freeze
85
+
86
+ def set_speed(mode:)
87
+ @speed = SPEED_MAP.fetch(mode, CONTROLLED_SPEED)
88
+ end
89
+
90
+ def ruminating?
91
+ @stream.ruminating?
92
+ end
93
+
94
+ def break_rumination(redirect_topic:)
95
+ return false unless ruminating?
96
+
97
+ speak(content: 'Let me think about something else.', mode: :narrating, topic: redirect_topic)
98
+ true
99
+ end
100
+
101
+ def recent_speech(count: 5)
102
+ @stream.recent(count: count)
103
+ end
104
+
105
+ def narrative
106
+ @stream.narrative
107
+ end
108
+
109
+ def condensed_narrative
110
+ @stream.condensed_stream.join(' ')
111
+ end
112
+
113
+ def tick
114
+ @stream.decay_all
115
+ @stream.to_h.merge(active_voice: @active_voice, speed: @speed)
116
+ end
117
+
118
+ def to_h
119
+ {
120
+ active_voice: @active_voice,
121
+ speed: @speed,
122
+ stream_size: @stream.size,
123
+ ruminating: ruminating?,
124
+ total_utterances: @stream.counter,
125
+ history_size: @history.size
126
+ }
127
+ end
128
+
129
+ private
130
+
131
+ def record_event(type, **details)
132
+ @history << { type: type, at: Time.now.utc }.merge(details)
133
+ @history.shift while @history.size > MAX_HISTORY
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module InnerSpeech
8
+ module Helpers
9
+ class SpeechStream
10
+ include Constants
11
+
12
+ attr_reader :utterances, :counter
13
+
14
+ def initialize
15
+ @utterances = []
16
+ @counter = 0
17
+ end
18
+
19
+ def append(content:, mode: :narrating, **)
20
+ return nil if @utterances.size >= MAX_STREAM_LENGTH
21
+
22
+ @counter += 1
23
+ utterance = Utterance.new(
24
+ id: :"utt_#{@counter}",
25
+ content: content,
26
+ mode: mode,
27
+ **
28
+ )
29
+ @utterances << utterance
30
+ utterance
31
+ end
32
+
33
+ def current
34
+ @utterances.last
35
+ end
36
+
37
+ def recent(count: 5)
38
+ @utterances.last(count).map(&:to_h)
39
+ end
40
+
41
+ def by_mode(mode:)
42
+ @utterances.select { |u| u.mode == mode }.map(&:to_h)
43
+ end
44
+
45
+ def by_voice(voice:)
46
+ @utterances.select { |u| u.voice == voice }.map(&:to_h)
47
+ end
48
+
49
+ def by_topic(topic:)
50
+ @utterances.select { |u| u.topic == topic }.map(&:to_h)
51
+ end
52
+
53
+ def salient
54
+ @utterances.select(&:salient?).map(&:to_h)
55
+ end
56
+
57
+ def urgent
58
+ @utterances.select(&:urgent?).map(&:to_h)
59
+ end
60
+
61
+ def ruminating?
62
+ return false if @utterances.size < RUMINATION_THRESHOLD
63
+
64
+ recent_topics = @utterances.last(RUMINATION_THRESHOLD).map(&:topic)
65
+ recent_topics.uniq.size == 1
66
+ end
67
+
68
+ def rumination_topic
69
+ return nil unless ruminating?
70
+
71
+ @utterances.last&.topic
72
+ end
73
+
74
+ def decay_all
75
+ @utterances.each(&:decay_salience!)
76
+ prune_stale
77
+ end
78
+
79
+ def interrupt(content:, mode: :warning, **)
80
+ append(content: content, mode: mode, urgency: 0.9, salience: 0.9, **)
81
+ end
82
+
83
+ def condensed_stream
84
+ @utterances.select(&:salient?).map(&:condensed_content)
85
+ end
86
+
87
+ def narrative
88
+ @utterances.map(&:content).join(' ')
89
+ end
90
+
91
+ def clear
92
+ @utterances.clear
93
+ end
94
+
95
+ def size
96
+ @utterances.size
97
+ end
98
+
99
+ def to_h
100
+ {
101
+ size: @utterances.size,
102
+ total_generated: @counter,
103
+ ruminating: ruminating?,
104
+ rumination_topic: rumination_topic,
105
+ salient_count: @utterances.count(&:salient?),
106
+ urgent_count: @utterances.count(&:urgent?),
107
+ mode_distribution: mode_distribution
108
+ }
109
+ end
110
+
111
+ private
112
+
113
+ def prune_stale
114
+ @utterances.reject! { |u| u.salience <= SALIENCE_FLOOR }
115
+ end
116
+
117
+ def mode_distribution
118
+ dist = Hash.new(0)
119
+ @utterances.each { |u| dist[u.mode] += 1 }
120
+ dist
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module InnerSpeech
8
+ module Helpers
9
+ class Utterance
10
+ include Constants
11
+
12
+ attr_reader :id, :content, :mode, :voice, :topic,
13
+ :urgency, :salience, :source_subsystem, :created_at
14
+
15
+ def initialize(id:, content:, mode: :narrating, **opts)
16
+ @id = id
17
+ @content = content
18
+ @mode = resolve_mode(mode)
19
+ apply_opts(opts)
20
+ @created_at = Time.now.utc
21
+ end
22
+
23
+ def condensed_content
24
+ words = @content.split
25
+ keep = [(words.size * CONDENSATION_RATIO).ceil, 1].max
26
+ words.first(keep).join(' ')
27
+ end
28
+
29
+ def urgent?
30
+ @urgency >= 0.6
31
+ end
32
+
33
+ def background?
34
+ @urgency < 0.2
35
+ end
36
+
37
+ def decay_salience!
38
+ @salience = [@salience - SALIENCE_DECAY, SALIENCE_FLOOR].max
39
+ end
40
+
41
+ def salient?
42
+ @salience >= 0.3
43
+ end
44
+
45
+ def urgency_label
46
+ URGENCY_LABELS.each { |range, lbl| return lbl if range.cover?(@urgency) }
47
+ :background
48
+ end
49
+
50
+ def to_h
51
+ {
52
+ id: @id,
53
+ content: @content,
54
+ condensed: condensed_content,
55
+ mode: @mode,
56
+ voice: @voice,
57
+ topic: @topic,
58
+ urgency: @urgency.round(4),
59
+ salience: @salience.round(4),
60
+ source_subsystem: @source_subsystem,
61
+ urgency_label: urgency_label
62
+ }
63
+ end
64
+
65
+ private
66
+
67
+ def apply_opts(opts)
68
+ @voice = resolve_voice(opts.fetch(:voice, :rational))
69
+ @topic = opts.fetch(:topic, :general)
70
+ @urgency = opts.fetch(:urgency, 0.5).to_f.clamp(0.0, 1.0)
71
+ @salience = opts.fetch(:salience, 0.5).to_f.clamp(0.0, 1.0)
72
+ @source_subsystem = opts.fetch(:source_subsystem, :unknown)
73
+ end
74
+
75
+ def resolve_mode(mode)
76
+ sym = mode.to_sym
77
+ SPEECH_MODES.include?(sym) ? sym : :narrating
78
+ end
79
+
80
+ def resolve_voice(voice)
81
+ sym = voice.to_sym
82
+ VOICE_TYPES.include?(sym) ? sym : :rational
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module InnerSpeech
8
+ module Runners
9
+ module InnerSpeech
10
+ include Helpers::Constants
11
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
12
+
13
+ def inner_speak(content:, mode: :narrating, topic: :general, **)
14
+ utterance = voice.speak(content: content, mode: mode, topic: topic)
15
+ return { success: false, reason: :stream_full } unless utterance
16
+
17
+ { success: true, utterance_id: utterance.id, mode: utterance.mode }
18
+ end
19
+
20
+ def inner_plan(content:, topic: :general, **)
21
+ utterance = voice.plan(content: content, topic: topic)
22
+ return { success: false, reason: :stream_full } unless utterance
23
+
24
+ { success: true, utterance_id: utterance.id }
25
+ end
26
+
27
+ def inner_question(content:, topic: :general, **)
28
+ utterance = voice.question(content: content, topic: topic)
29
+ return { success: false, reason: :stream_full } unless utterance
30
+
31
+ { success: true, utterance_id: utterance.id }
32
+ end
33
+
34
+ def inner_debate(content_a:, content_b:, topic: :general, **)
35
+ results = voice.debate(content_a: content_a, content_b: content_b, topic: topic)
36
+ { success: true, utterances: results.map(&:id), count: results.size }
37
+ end
38
+
39
+ def switch_inner_voice(voice_type:, **)
40
+ result = voice.switch_voice(voice: voice_type)
41
+ return { success: false, reason: :invalid_voice } unless result
42
+
43
+ { success: true, active_voice: result }
44
+ end
45
+
46
+ def inner_interrupt(content:, **)
47
+ utterance = voice.interrupt(content: content)
48
+ return { success: false, reason: :stream_full } unless utterance
49
+
50
+ { success: true, utterance_id: utterance.id }
51
+ end
52
+
53
+ def break_inner_rumination(redirect_topic: :general, **)
54
+ result = voice.break_rumination(redirect_topic: redirect_topic)
55
+ { success: true, rumination_broken: result }
56
+ end
57
+
58
+ def recent_inner_speech(count: 5, **)
59
+ speech = voice.recent_speech(count: count)
60
+ { success: true, utterances: speech, count: speech.size }
61
+ end
62
+
63
+ def inner_narrative(**)
64
+ { success: true, narrative: voice.narrative, condensed: voice.condensed_narrative }
65
+ end
66
+
67
+ def update_inner_speech(**)
68
+ result = voice.tick
69
+ { success: true }.merge(result)
70
+ end
71
+
72
+ def inner_speech_stats(**)
73
+ { success: true }.merge(voice.to_h)
74
+ end
75
+
76
+ private
77
+
78
+ def voice
79
+ @voice ||= Helpers::InnerVoice.new
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module InnerSpeech
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/inner_speech/version'
4
+ require 'legion/extensions/agentic/language/inner_speech/helpers/constants'
5
+ require 'legion/extensions/agentic/language/inner_speech/helpers/utterance'
6
+ require 'legion/extensions/agentic/language/inner_speech/helpers/inner_voice'
7
+ require 'legion/extensions/agentic/language/inner_speech/helpers/speech_stream'
8
+ require 'legion/extensions/agentic/language/inner_speech/runners/inner_speech'
9
+ require 'legion/extensions/agentic/language/inner_speech/client'
10
+
11
+ module Legion
12
+ module Extensions
13
+ module Agentic
14
+ module Language
15
+ module InnerSpeech
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end