lex-agentic-language 0.1.8 → 0.1.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b04c9da9a6fcda5c086ee1e27fd8c2a2007faa556737be3d88219a2e505663ba
4
- data.tar.gz: fa94cc1d6fda5c3b9fe38a2e6aa9254ee238a9af4f2f3802aeeb94813e98f352
3
+ metadata.gz: 2e44972f87bca2dccf617db64c48e8e3b99d2174bb45f1d21cd71a72a2489a59
4
+ data.tar.gz: 1e9fb62235879f874ef6c2ca07b6f58dc104fcc5fcb3d64591dcdf5d6477484f
5
5
  SHA512:
6
- metadata.gz: 0b08cdd7dc45ec57422597aa41d7a75a6c375b4f5f70cacc8a6abfcb14e6326528dda67ec4c6392596cfcd19b05fdd0bf244cb8a48b3be3d7d84921e5e38b453
7
- data.tar.gz: 59f6320175d8e48918b8d45ff9e3b8ea25796205f5d23703d12a978bd722441f48d1f91253178f237b9cde97aad458478eb06de288f1913489775a9934ea7089
6
+ metadata.gz: aed85b23f9a856e2a26ac20bfdacd601c0ad09c8394f38bedb8195d28852fab393ee170b29d176109c83d6ab7cc426883270621c0bc629a7cab6244f94d38cc5
7
+ data.tar.gz: e57dbce2e2254462c9940b57b7dcac4b349e8edb3dad723e4c4d3c06bcc0dc6882cf6a4e52dfdb75bed12fdde5bd755f18c6e447c242fbd5d3fe4dcc42b5917b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.10] - 2026-04-27
4
+ ### Fixed
5
+ - Narrator LLM enhancement now skips empty idle narration and rate-limits provider failure warnings without warning-level backtrace floods. Fixes #7
6
+
7
+ ## [0.1.9] - 2026-04-22
8
+ ### Added
9
+ - InnerSpeech::Actor::DecayInnerSpeech (60s) — first actor in the extension, enables autonomous salience decay
10
+ ### Fixed
11
+ - LlmEnhancer `available?` now logs errors at debug level instead of swallowing silently
12
+ - Documented PragmaticInference::Utterance vs InnerSpeech::Utterance schema distinctions
13
+
3
14
  ## [0.1.8] - 2026-04-15
4
15
  ### Changed
5
16
  - Set `mcp_tools?`, `mcp_tools_deferred?`, and `transport_required?` to `false` — internal cognitive pipeline extension
data/README.md CHANGED
@@ -1,26 +1,50 @@
1
1
  # lex-agentic-language
2
2
 
3
- Domain consolidation gem for language processing and communication. Bundles 9 source extensions into one loadable unit under `Legion::Extensions::Agentic::Language`.
3
+ Domain consolidation gem for language processing and communication. Bundles 9 sub-modules into one loadable unit under `Legion::Extensions::Agentic::Language`.
4
4
 
5
5
  ## Overview
6
6
 
7
7
  **Gem**: `lex-agentic-language`
8
- **Version**: 0.1.8
8
+ **Version**: 0.1.9
9
9
  **Namespace**: `Legion::Extensions::Agentic::Language`
10
10
 
11
11
  ## Sub-Modules
12
12
 
13
- | Sub-Module | Source Gem | Purpose |
14
- |---|---|---|
15
- | `Language::Language` | `lex-language` | Core language processing — token parsing, semantic framing, pragmatic intent |
16
- | `Language::Grammar` | `lex-cognitive-grammar` | Grammatical structure processing |
17
- | `Language::InnerSpeech` | `lex-inner-speech` | Vygotsky inner speech — private verbal thought for problem solving |
18
- | `Language::Narrator` | `lex-narrator` | Real-time narrative stream of internal state (optional LLM enhancement) |
19
- | `Language::NarrativeReasoning` | `lex-narrative-reasoning` | Narrative as a reasoning mode — story-schema activation |
20
- | `Language::FrameSemantics` | `lex-frame-semantics` | Fillmore frame semantics — conceptual frames, slots, fillers |
21
- | `Language::PragmaticInference` | `lex-pragmatic-inference` | Gricean maxims and conversational implicature |
22
- | `Language::ConceptualBlending` | `lex-conceptual-blending` | Fauconnier & Turner — emergent blended structure from two input spaces |
23
- | `Language::ConceptualMetaphor` | `lex-conceptual-metaphor` | Lakoff & Johnson — structural mappings between conceptual domains |
13
+ | Sub-Module | Purpose |
14
+ |---|---|
15
+ | `Language::Language` | Core language processing — token parsing, semantic framing, pragmatic intent |
16
+ | `Language::Grammar` | Grammatical construction processing (construal, construction, grammar engine) |
17
+ | `Language::InnerSpeech` | Vygotsky inner speech — private verbal thought for problem solving |
18
+ | `Language::Narrator` | Real-time narrative stream of internal state (optional LLM enhancement) |
19
+ | `Language::NarrativeReasoning` | Narrative as a reasoning mode — story-schema activation |
20
+ | `Language::FrameSemantics` | Fillmore frame semantics — conceptual frames, slots, fillers |
21
+ | `Language::PragmaticInference` | Gricean maxims and conversational implicature |
22
+ | `Language::ConceptualBlending` | Fauconnier & Turner — emergent blended structure from two input spaces |
23
+ | `Language::ConceptualMetaphor` | Lakoff & Johnson — structural mappings between conceptual domains |
24
+
25
+ ## Actors
26
+
27
+ - `Language::InnerSpeech::Actor::DecayInnerSpeech` — every 60s, decays utterance salience in the inner voice stream via `update_inner_speech`
28
+
29
+ This is the first actor added to this gem. Without it the inner speech stream grows indefinitely.
30
+
31
+ ## Quick Usage
32
+
33
+ ```ruby
34
+ require 'legion/extensions/agentic/language'
35
+
36
+ # Use the InnerSpeech runner
37
+ include Legion::Extensions::Agentic::Language::InnerSpeech::Runners::InnerSpeech
38
+
39
+ inner_speak(content: "Should I accept this task?", mode: :questioning, topic: :task_eval)
40
+ # => { success: true, utterance_id: "...", mode: :questioning }
41
+
42
+ inner_plan(content: "First verify the input, then transform")
43
+ # => { success: true, utterance_id: "..." }
44
+
45
+ recent_inner_speech(count: 3)
46
+ # => { success: true, utterances: [...], count: 3 }
47
+ ```
24
48
 
25
49
  ## Installation
26
50
 
@@ -32,8 +56,8 @@ gem 'lex-agentic-language'
32
56
 
33
57
  ```bash
34
58
  bundle install
35
- bundle exec rspec # 735 examples, 0 failures
36
- bundle exec rubocop # 0 offenses
59
+ bundle exec rspec
60
+ bundle exec rubocop
37
61
  ```
38
62
 
39
63
  ## License
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/actors/every'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Agentic
8
+ module Language
9
+ module InnerSpeech
10
+ module Actor
11
+ # DecayInnerSpeech runs every 60 seconds and ticks the inner voice stream,
12
+ # decaying the salience of all queued utterances. Without this actor the
13
+ # stream would grow indefinitely and old utterances would remain at full
14
+ # salience, distorting urgency calculations.
15
+ #
16
+ # Delegates to Runners::InnerSpeech#update_inner_speech, which calls
17
+ # InnerVoice#tick → SpeechStream#decay_all.
18
+ class DecayInnerSpeech < Legion::Extensions::Actors::Every
19
+ time 60
20
+
21
+ def runner_class
22
+ Legion::Extensions::Agentic::Language::InnerSpeech::Runners::InnerSpeech
23
+ end
24
+
25
+ def runner_function
26
+ 'update_inner_speech'
27
+ end
28
+
29
+ def run_now?
30
+ false
31
+ end
32
+
33
+ def use_runner?
34
+ false
35
+ end
36
+
37
+ def check_subtask?
38
+ false
39
+ end
40
+
41
+ def generate_task?
42
+ false
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # InnerSpeech::Helpers::Utterance — represents a single token of inner-voice activity.
4
+ #
5
+ # Key fields: mode (narrating/planning/questioning/…), voice (rational/bold/cautious/…),
6
+ # urgency (0–1), salience (0–1, subject to temporal decay via #decay_salience!).
7
+ # Salience decays over time via the InnerSpeech decay actor (every 60 s), which calls
8
+ # Runners::InnerSpeech#update_inner_speech → InnerVoice#tick → SpeechStream#decay_all.
9
+ #
10
+ # Compare with PragmaticInference::Helpers::Utterance, which models an external
11
+ # communicative act scored against Gricean maxims with implicature accumulation.
12
+ # That class carries NO urgency, salience, or decay logic.
13
+
3
14
  module Legion
4
15
  module Extensions
5
16
  module Agentic
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'concurrent/atomic/atomic_reference'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Agentic
@@ -13,12 +15,20 @@ module Legion
13
15
  Write 3-5 sentences that feel like genuine introspection, not a report.
14
16
  Vary your sentence structure. Use present tense. Be concise and vivid.
15
17
  PROMPT
18
+ FAILURE_LOG_INTERVAL = 60
19
+ FAILURE_LOGGED_AT = Concurrent::AtomicReference.new
16
20
 
17
21
  module_function
18
22
 
23
+ def log
24
+ Legion::Logging
25
+ end
26
+ private_class_method :log
27
+
19
28
  def available?
20
29
  !!(defined?(Legion::LLM) && Legion::LLM.respond_to?(:started?) && Legion::LLM.started?)
21
- rescue StandardError => _e
30
+ rescue StandardError => e
31
+ log.debug("[narrator:llm] available? check failed: #{e.class}: #{e.message}")
22
32
  false
23
33
  end
24
34
 
@@ -27,8 +37,7 @@ module Legion
27
37
  response = llm_ask(prompt)
28
38
  parse_narrate_response(response)
29
39
  rescue StandardError => e
30
- Legion::Logging.warn("[narrator:llm] narrate failed: #{e.message}") # rubocop:disable Legion/HelperMigration/DirectLogging
31
- Legion::Logging.warn(e.backtrace) # rubocop:disable Legion/HelperMigration/DirectLogging
40
+ log_failure(e)
32
41
  nil
33
42
  end
34
43
 
@@ -57,12 +66,31 @@ module Legion
57
66
  Legion::LLM.respond_to?(:pipeline_enabled?) &&
58
67
  Legion::LLM.pipeline_enabled?)
59
68
  rescue StandardError => e
60
- Legion::Logging.warn("pipeline_available? #{e.class}: #{e.message}") # rubocop:disable Legion/HelperMigration/DirectLogging
69
+ log.warn("[narrator:llm] pipeline_available? #{e.class}: #{e.message}")
61
70
  false
62
71
  end
63
72
 
64
73
  private_class_method :pipeline_available?
65
74
 
75
+ def log_failure(error)
76
+ if log_failure_now?
77
+ log.warn("[narrator:llm] narrate failed: #{error.class}: #{error.message}")
78
+ FAILURE_LOGGED_AT.set(Time.now.utc)
79
+ end
80
+ log.debug(error.backtrace)
81
+ end
82
+
83
+ private_class_method :log_failure
84
+
85
+ def log_failure_now?
86
+ last_logged_at = FAILURE_LOGGED_AT.get
87
+ return true unless last_logged_at
88
+
89
+ (Time.now.utc - last_logged_at) >= FAILURE_LOG_INTERVAL
90
+ end
91
+
92
+ private_class_method :log_failure_now?
93
+
66
94
  def build_narrate_prompt(sections_data)
67
95
  parts = [
68
96
  'Generate a first-person internal monologue based on the following cognitive state:',
@@ -13,7 +13,7 @@ module Legion
13
13
  def narrate(tick_results: {}, cognitive_state: {}, **)
14
14
  entry = Helpers::Synthesizer.narrate(tick_results: tick_results, cognitive_state: cognitive_state)
15
15
 
16
- if Helpers::LlmEnhancer.available?
16
+ if Helpers::LlmEnhancer.available? && meaningful_for_llm?(tick_results, cognitive_state)
17
17
  sections_data = build_llm_sections_data(tick_results, cognitive_state, entry)
18
18
  llm_result = Helpers::LlmEnhancer.narrate(sections_data: sections_data)
19
19
  if llm_result
@@ -103,6 +103,25 @@ module Legion
103
103
  @journal ||= Helpers::Journal.new
104
104
  end
105
105
 
106
+ def meaningful_for_llm?(tick_results, cognitive_state)
107
+ meaningful_hash?(tick_results) || meaningful_hash?(cognitive_state)
108
+ end
109
+
110
+ def meaningful_hash?(value)
111
+ return false unless value.is_a?(Hash)
112
+
113
+ value.any? do |_, nested|
114
+ case nested
115
+ when Hash
116
+ meaningful_hash?(nested)
117
+ when Array
118
+ nested.any?
119
+ else
120
+ !nested.nil?
121
+ end
122
+ end
123
+ end
124
+
106
125
  def build_llm_sections_data(tick_results, cognitive_state, entry)
107
126
  {
108
127
  emotion: llm_emotion_data(tick_results, cognitive_state),
@@ -1,5 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # PragmaticInference::Helpers::Utterance — represents a single communicative act
4
+ # evaluated through Gricean maxims (quantity, quality, relation, manner).
5
+ #
6
+ # Key fields: speaker, speech_act, literal_meaning, maxim_scores, implicatures, confidence.
7
+ # Maxim compliance is scored per-maxim and aggregated via #overall_compliance.
8
+ # Violations and implicatures are accumulated after construction via mutation methods.
9
+ #
10
+ # Compare with InnerSpeech::Helpers::Utterance, which models inner-voice tokens
11
+ # (mode, urgency, salience, decay) and carries NO maxim or implicature logic.
12
+
3
13
  require 'securerandom'
4
14
 
5
15
  module Legion
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Language
7
- VERSION = '0.1.8'
7
+ VERSION = '0.1.10'
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Stub the base class before loading the actor so the spec can run in isolation
4
+ # without requiring the full Legion framework.
5
+ #
6
+ # The stub must support the `time` DSL accessor used by Every actors: a class-level
7
+ # setter (time 60) and an instance-level reader that delegates to the class method.
8
+ module Legion
9
+ module Extensions
10
+ module Actors
11
+ class Every
12
+ def self.time(val = :_unset)
13
+ if val == :_unset
14
+ @time || 1
15
+ else
16
+ @time = val
17
+ end
18
+ end
19
+
20
+ def time
21
+ self.class.time
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ $LOADED_FEATURES << 'legion/extensions/actors/every'
29
+
30
+ require_relative '../../../../../../../lib/legion/extensions/agentic/language/inner_speech/actors/decay_inner_speech'
31
+
32
+ RSpec.describe Legion::Extensions::Agentic::Language::InnerSpeech::Actor::DecayInnerSpeech do
33
+ subject(:actor) { described_class.new }
34
+
35
+ describe '#runner_class' do
36
+ it do
37
+ expect(actor.runner_class).to eq(
38
+ Legion::Extensions::Agentic::Language::InnerSpeech::Runners::InnerSpeech
39
+ )
40
+ end
41
+ end
42
+
43
+ describe '#runner_function' do
44
+ it { expect(actor.runner_function).to eq 'update_inner_speech' }
45
+ end
46
+
47
+ describe '#time' do
48
+ it { expect(actor.time).to eq 60 }
49
+ end
50
+
51
+ describe '#run_now?' do
52
+ it { expect(actor.run_now?).to be false }
53
+ end
54
+
55
+ describe '#use_runner?' do
56
+ it { expect(actor.use_runner?).to be false }
57
+ end
58
+
59
+ describe '#check_subtask?' do
60
+ it { expect(actor.check_subtask?).to be false }
61
+ end
62
+
63
+ describe '#generate_task?' do
64
+ it { expect(actor.generate_task?).to be false }
65
+ end
66
+ end
@@ -78,16 +78,28 @@ RSpec.describe Legion::Extensions::Agentic::Language::Narrator::Helpers::LlmEnha
78
78
  end
79
79
 
80
80
  context 'when an error occurs' do
81
- it 'returns nil and logs a warning' do
81
+ before { described_class::FAILURE_LOGGED_AT.set(nil) }
82
+
83
+ it 'returns nil and logs a concise warning with debug backtrace' do
82
84
  llm_double = double('Legion::LLM', started?: true)
83
85
  allow(llm_double).to receive(:chat).and_raise(StandardError, 'connection failed')
84
86
  stub_const('Legion::LLM', llm_double)
85
87
 
86
- expect(Legion::Logging).to receive(:warn).with(/narrator:llm.*narrate failed/)
87
- expect(Legion::Logging).to receive(:warn).with(instance_of(Array))
88
+ expect(Legion::Logging).to receive(:warn).with(/narrator:llm.*StandardError: connection failed/)
89
+ expect(Legion::Logging).to receive(:debug).with(instance_of(Array))
88
90
  result = described_class.narrate(sections_data: sections_data)
89
91
  expect(result).to be_nil
90
92
  end
93
+
94
+ it 'throttles repeated failure warnings' do
95
+ llm_double = double('Legion::LLM', started?: true)
96
+ allow(llm_double).to receive(:chat).and_raise(StandardError, 'connection failed')
97
+ stub_const('Legion::LLM', llm_double)
98
+ allow(Legion::Logging).to receive(:debug)
99
+
100
+ expect(Legion::Logging).to receive(:warn).once
101
+ 2.times { described_class.narrate(sections_data: sections_data) }
102
+ end
91
103
  end
92
104
 
93
105
  context 'with empty sections_data' do
@@ -4,9 +4,10 @@ RSpec.describe Legion::Extensions::Agentic::Language::Narrator::Runners::Narrato
4
4
  let(:client) { Legion::Extensions::Agentic::Language::Narrator::Client.new }
5
5
 
6
6
  describe '#narrate with LLM available' do
7
+ let(:chat_double) { double('chat') }
8
+
7
9
  before do
8
10
  response_double = double('response', content: 'I feel a deep sense of focus and possibility.')
9
- chat_double = double('chat')
10
11
  allow(chat_double).to receive(:with_instructions)
11
12
  allow(chat_double).to receive(:ask).and_return(response_double)
12
13
  llm_double = double('Legion::LLM', started?: true)
@@ -22,8 +23,8 @@ RSpec.describe Legion::Extensions::Agentic::Language::Narrator::Runners::Narrato
22
23
  expect(result[:source]).to eq(:llm)
23
24
  end
24
25
 
25
- it 'returns the LLM narrative string' do
26
- result = client.narrate(tick_results: {}, cognitive_state: {})
26
+ it 'returns the LLM narrative string when there is meaningful cognitive data' do
27
+ result = client.narrate(tick_results: { emotional_evaluation: { valence: 0.6 } }, cognitive_state: {})
27
28
  expect(result[:narrative]).to eq('I feel a deep sense of focus and possibility.')
28
29
  end
29
30
 
@@ -34,9 +35,18 @@ RSpec.describe Legion::Extensions::Agentic::Language::Narrator::Runners::Narrato
34
35
  end
35
36
 
36
37
  it 'appends to journal' do
37
- client.narrate(tick_results: {}, cognitive_state: {})
38
+ client.narrate(tick_results: { emotional_evaluation: { valence: 0.6 } }, cognitive_state: {})
38
39
  expect(client.journal.size).to eq(1)
39
40
  end
41
+
42
+ it 'uses the mechanical pipeline for empty idle narration' do
43
+ expect(chat_double).not_to receive(:ask)
44
+
45
+ result = client.narrate(tick_results: {}, cognitive_state: {})
46
+
47
+ expect(result).not_to have_key(:source)
48
+ expect(result[:narrative]).to be_a(String)
49
+ end
40
50
  end
41
51
 
42
52
  describe '#narrate with LLM available but narrate returns nil' do
@@ -50,7 +60,7 @@ RSpec.describe Legion::Extensions::Agentic::Language::Narrator::Runners::Narrato
50
60
  end
51
61
 
52
62
  it 'falls back to mechanical pipeline' do
53
- result = client.narrate(tick_results: {}, cognitive_state: {})
63
+ result = client.narrate(tick_results: { emotional_evaluation: { valence: 0.6 } }, cognitive_state: {})
54
64
  expect(result).not_to have_key(:source)
55
65
  expect(result[:narrative]).to be_a(String)
56
66
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-language
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -208,6 +208,7 @@ files:
208
208
  - lib/legion/extensions/agentic/language/grammar/runners/cognitive_grammar.rb
209
209
  - lib/legion/extensions/agentic/language/grammar/version.rb
210
210
  - lib/legion/extensions/agentic/language/inner_speech.rb
211
+ - lib/legion/extensions/agentic/language/inner_speech/actors/decay_inner_speech.rb
211
212
  - lib/legion/extensions/agentic/language/inner_speech/client.rb
212
213
  - lib/legion/extensions/agentic/language/inner_speech/helpers/constants.rb
213
214
  - lib/legion/extensions/agentic/language/inner_speech/helpers/inner_voice.rb
@@ -266,6 +267,7 @@ files:
266
267
  - spec/legion/extensions/agentic/language/grammar/helpers/construction_spec.rb
267
268
  - spec/legion/extensions/agentic/language/grammar/helpers/grammar_engine_spec.rb
268
269
  - spec/legion/extensions/agentic/language/grammar/runners/cognitive_grammar_spec.rb
270
+ - spec/legion/extensions/agentic/language/inner_speech/actors/decay_inner_speech_spec.rb
269
271
  - spec/legion/extensions/agentic/language/inner_speech/client_spec.rb
270
272
  - spec/legion/extensions/agentic/language/inner_speech/helpers/inner_voice_spec.rb
271
273
  - spec/legion/extensions/agentic/language/inner_speech/helpers/speech_stream_spec.rb