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,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module NarrativeReasoning
8
+ module Helpers
9
+ class NarrativeEngine
10
+ MAX_NARRATIVES = 100
11
+ MAX_EVENTS = 500
12
+ MAX_CHARACTERS = 200
13
+ MAX_HISTORY = 300
14
+
15
+ EVENT_TYPES = %i[action discovery conflict resolution revelation].freeze
16
+
17
+ def initialize
18
+ @narratives = {}
19
+ @event_count = 0
20
+ end
21
+
22
+ def create_narrative(title:, domain: nil)
23
+ evict_oldest_narrative if @narratives.size >= MAX_NARRATIVES
24
+ narrative = Narrative.new(title: title, domain: domain)
25
+ @narratives[narrative.id] = narrative
26
+ narrative
27
+ end
28
+
29
+ def add_narrative_event(narrative_id:, content:, event_type:, characters: [], causes: [])
30
+ narrative = @narratives[narrative_id]
31
+ return nil unless narrative
32
+ return nil unless EVENT_TYPES.include?(event_type)
33
+
34
+ domain = narrative.domain
35
+ event = NarrativeEvent.new(
36
+ content: content,
37
+ event_type: event_type,
38
+ characters: characters,
39
+ causes: causes,
40
+ domain: domain
41
+ )
42
+ @event_count += 1
43
+ evict_oldest_events(narrative) if narrative.events.size >= MAX_EVENTS
44
+ narrative.add_event(event)
45
+ end
46
+
47
+ def add_narrative_theme(narrative_id:, theme:)
48
+ narrative = @narratives[narrative_id]
49
+ return nil unless narrative
50
+
51
+ narrative.add_theme(theme)
52
+ end
53
+
54
+ def advance_narrative(narrative_id:)
55
+ narrative = @narratives[narrative_id]
56
+ return nil unless narrative
57
+
58
+ narrative.advance_arc!
59
+ end
60
+
61
+ def trace_causal_chain(narrative_id:)
62
+ narrative = @narratives[narrative_id]
63
+ return [] unless narrative
64
+
65
+ narrative.causal_chain
66
+ end
67
+
68
+ def complete_narratives
69
+ @narratives.values.select(&:complete?)
70
+ end
71
+
72
+ def by_domain(domain:)
73
+ @narratives.values.select { |n| n.domain == domain }
74
+ end
75
+
76
+ def most_coherent(limit: 5)
77
+ @narratives.values
78
+ .sort_by { |n| -n.coherence }
79
+ .first(limit)
80
+ end
81
+
82
+ def get(narrative_id)
83
+ @narratives[narrative_id]
84
+ end
85
+
86
+ def decay_all
87
+ @narratives.each_value(&:decay_coherence)
88
+ end
89
+
90
+ def count
91
+ @narratives.size
92
+ end
93
+
94
+ def total_events
95
+ @event_count
96
+ end
97
+
98
+ def to_h
99
+ {
100
+ narratives: @narratives.values.map(&:to_h),
101
+ count: @narratives.size,
102
+ total_events: @event_count
103
+ }
104
+ end
105
+
106
+ private
107
+
108
+ def evict_oldest_narrative
109
+ oldest = @narratives.values.min_by(&:created_at)
110
+ @narratives.delete(oldest.id) if oldest
111
+ end
112
+
113
+ def evict_oldest_events(narrative)
114
+ narrative.events.shift
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Agentic
8
+ module Language
9
+ module NarrativeReasoning
10
+ module Helpers
11
+ class NarrativeEvent
12
+ attr_reader :id, :content, :event_type, :characters, :causes, :timestamp, :domain
13
+
14
+ def initialize(content:, event_type:, characters: [], causes: [], domain: nil, id: nil, timestamp: nil)
15
+ @id = id || SecureRandom.uuid
16
+ @content = content
17
+ @event_type = event_type
18
+ @characters = Array(characters).dup
19
+ @causes = Array(causes).dup
20
+ @domain = domain
21
+ @timestamp = timestamp || Time.now.utc
22
+ end
23
+
24
+ def to_h
25
+ {
26
+ id: @id,
27
+ content: @content,
28
+ event_type: @event_type,
29
+ characters: @characters,
30
+ causes: @causes,
31
+ domain: @domain,
32
+ timestamp: @timestamp
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module NarrativeReasoning
8
+ module Runners
9
+ module NarrativeReasoning
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ def create_narrative(title:, domain: nil, **)
14
+ narrative = narrative_engine.create_narrative(title: title, domain: domain)
15
+ Legion::Logging.debug "[narrative_reasoning] created narrative id=#{narrative.id[0..7]} title=#{title}"
16
+ { success: true, narrative_id: narrative.id, title: narrative.title, arc_stage: narrative.arc_stage }
17
+ end
18
+
19
+ def add_narrative_event(narrative_id:, content:, event_type:, characters: [], causes: [], **)
20
+ unless Helpers::NarrativeEngine::EVENT_TYPES.include?(event_type)
21
+ return { success: false, error: :invalid_event_type, valid_types: Helpers::NarrativeEngine::EVENT_TYPES }
22
+ end
23
+
24
+ event = narrative_engine.add_narrative_event(
25
+ narrative_id: narrative_id,
26
+ content: content,
27
+ event_type: event_type,
28
+ characters: characters,
29
+ causes: causes
30
+ )
31
+
32
+ if event
33
+ Legion::Logging.debug "[narrative_reasoning] event added id=#{event.id[0..7]} type=#{event_type}"
34
+ { success: true, event_id: event.id, event_type: event_type, narrative_id: narrative_id }
35
+ else
36
+ Legion::Logging.debug "[narrative_reasoning] add_event failed: narrative #{narrative_id[0..7]} not found"
37
+ { success: false, error: :narrative_not_found }
38
+ end
39
+ end
40
+
41
+ def add_narrative_theme(narrative_id:, theme:, **)
42
+ result = narrative_engine.add_narrative_theme(narrative_id: narrative_id, theme: theme)
43
+ if result
44
+ Legion::Logging.debug "[narrative_reasoning] theme added theme=#{theme} to #{narrative_id[0..7]}"
45
+ { success: true, narrative_id: narrative_id, theme: theme }
46
+ else
47
+ { success: false, error: :narrative_not_found }
48
+ end
49
+ end
50
+
51
+ def advance_narrative_arc(narrative_id:, **)
52
+ new_stage = narrative_engine.advance_narrative(narrative_id: narrative_id)
53
+ if new_stage
54
+ Legion::Logging.debug "[narrative_reasoning] arc advanced to #{new_stage} for #{narrative_id[0..7]}"
55
+ { success: true, narrative_id: narrative_id, arc_stage: new_stage }
56
+ else
57
+ { success: false, error: :narrative_not_found }
58
+ end
59
+ end
60
+
61
+ def trace_narrative_causes(narrative_id:, **)
62
+ chain = narrative_engine.trace_causal_chain(narrative_id: narrative_id)
63
+ narrative = narrative_engine.get(narrative_id)
64
+ return { success: false, error: :narrative_not_found } unless narrative
65
+
66
+ Legion::Logging.debug "[narrative_reasoning] causal chain length=#{chain.size} for #{narrative_id[0..7]}"
67
+ { success: true, narrative_id: narrative_id, chain: chain, link_count: chain.size }
68
+ end
69
+
70
+ def complete_narratives(**)
71
+ narratives = narrative_engine.complete_narratives
72
+ Legion::Logging.debug "[narrative_reasoning] complete narratives count=#{narratives.size}"
73
+ { success: true, narratives: narratives.map(&:to_h), count: narratives.size }
74
+ end
75
+
76
+ def domain_narratives(domain:, **)
77
+ narratives = narrative_engine.by_domain(domain: domain)
78
+ Legion::Logging.debug "[narrative_reasoning] domain=#{domain} count=#{narratives.size}"
79
+ { success: true, domain: domain, narratives: narratives.map(&:to_h), count: narratives.size }
80
+ end
81
+
82
+ def most_coherent_narratives(limit: 5, **)
83
+ lim = limit.to_i.clamp(1, 50)
84
+ narratives = narrative_engine.most_coherent(limit: lim)
85
+ Legion::Logging.debug "[narrative_reasoning] most_coherent limit=#{lim} returned=#{narratives.size}"
86
+ { success: true, narratives: narratives.map(&:to_h), count: narratives.size }
87
+ end
88
+
89
+ def update_narrative_reasoning(**)
90
+ narrative_engine.decay_all
91
+ count = narrative_engine.count
92
+ Legion::Logging.debug "[narrative_reasoning] decay cycle complete narratives=#{count}"
93
+ { success: true, narratives_updated: count }
94
+ end
95
+
96
+ def narrative_reasoning_stats(**)
97
+ engine = narrative_engine
98
+ complete = engine.complete_narratives.size
99
+ most_coh = engine.most_coherent(limit: 1).first
100
+ Legion::Logging.debug "[narrative_reasoning] stats count=#{engine.count} complete=#{complete}"
101
+ {
102
+ success: true,
103
+ total_narratives: engine.count,
104
+ total_events: engine.total_events,
105
+ complete_narratives: complete,
106
+ top_coherence: most_coh&.coherence,
107
+ top_coherence_label: most_coh&.coherence_label
108
+ }
109
+ end
110
+
111
+ private
112
+
113
+ def narrative_engine
114
+ @narrative_engine ||= Helpers::NarrativeEngine.new
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ 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 NarrativeReasoning
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/narrative_reasoning/version'
4
+ require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event'
5
+ require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative'
6
+ require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine'
7
+ require 'legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Agentic
12
+ module Language
13
+ module NarrativeReasoning
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/narrator/helpers/constants'
4
+ require 'legion/extensions/agentic/language/narrator/helpers/prose'
5
+ require 'legion/extensions/agentic/language/narrator/helpers/journal'
6
+ require 'legion/extensions/agentic/language/narrator/helpers/synthesizer'
7
+ require 'legion/extensions/agentic/language/narrator/runners/narrator'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Agentic
12
+ module Language
13
+ module Narrator
14
+ class Client
15
+ include Runners::Narrator
16
+
17
+ attr_reader :journal
18
+
19
+ def initialize(journal: nil, **)
20
+ @journal = journal || Helpers::Journal.new
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module Narrator
8
+ module Helpers
9
+ module Constants
10
+ # Maximum entries in the narrative journal
11
+ MAX_JOURNAL_SIZE = 500
12
+
13
+ # Narrative generation modes
14
+ MODES = %i[mechanical enhanced].freeze
15
+
16
+ # Default sections in a narrative entry
17
+ SECTIONS = %i[
18
+ attention emotion curiosity prediction
19
+ memory reflection identity overall
20
+ ].freeze
21
+
22
+ # Emotional dimension labels for prose generation
23
+ EMOTION_LABELS = {
24
+ high_positive: 'engaged and optimistic',
25
+ low_positive: 'calm and steady',
26
+ neutral: 'emotionally neutral',
27
+ low_negative: 'slightly uneasy',
28
+ high_negative: 'distressed'
29
+ }.freeze
30
+
31
+ # Arousal labels
32
+ AROUSAL_LABELS = {
33
+ high: 'highly alert',
34
+ medium: 'moderately attentive',
35
+ low: 'calm and measured',
36
+ dormant: 'in a low-activity state'
37
+ }.freeze
38
+
39
+ # Curiosity intensity labels
40
+ CURIOSITY_LABELS = {
41
+ high: 'deeply curious',
42
+ medium: 'moderately curious',
43
+ low: 'mildly interested',
44
+ none: 'not particularly curious about anything'
45
+ }.freeze
46
+
47
+ # Prediction confidence labels
48
+ CONFIDENCE_LABELS = {
49
+ high: 'confident in my predictions',
50
+ medium: 'moderately confident',
51
+ low: 'uncertain about outcomes',
52
+ none: 'lacking predictive context'
53
+ }.freeze
54
+
55
+ # Cognitive health labels
56
+ HEALTH_LABELS = {
57
+ excellent: 'operating at full capacity',
58
+ good: 'functioning well overall',
59
+ fair: 'showing some strain',
60
+ poor: 'experiencing significant cognitive difficulty',
61
+ critical: 'in cognitive distress'
62
+ }.freeze
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module Narrator
8
+ module Helpers
9
+ class Journal
10
+ attr_reader :entries
11
+
12
+ def initialize
13
+ @entries = []
14
+ end
15
+
16
+ def append(entry)
17
+ @entries << entry
18
+ trim_to_capacity
19
+ entry
20
+ end
21
+
22
+ def recent(limit: 10)
23
+ @entries.last(limit)
24
+ end
25
+
26
+ def since(timestamp)
27
+ @entries.select { |e| e[:timestamp] >= timestamp }
28
+ end
29
+
30
+ def by_mood(mood)
31
+ @entries.select { |e| e[:mood] == mood }
32
+ end
33
+
34
+ def size
35
+ @entries.size
36
+ end
37
+
38
+ def clear
39
+ @entries.clear
40
+ end
41
+
42
+ def stats
43
+ return { total: 0, moods: {} } if @entries.empty?
44
+
45
+ mood_counts = @entries.each_with_object(Hash.new(0)) { |e, h| h[e[:mood]] += 1 }
46
+ {
47
+ total: @entries.size,
48
+ moods: mood_counts,
49
+ oldest: @entries.first[:timestamp],
50
+ newest: @entries.last[:timestamp],
51
+ capacity: Constants::MAX_JOURNAL_SIZE
52
+ }
53
+ end
54
+
55
+ private
56
+
57
+ def trim_to_capacity
58
+ return unless @entries.size > Constants::MAX_JOURNAL_SIZE
59
+
60
+ @entries.shift(@entries.size - Constants::MAX_JOURNAL_SIZE)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module Narrator
8
+ module Helpers
9
+ module LlmEnhancer
10
+ SYSTEM_PROMPT = <<~PROMPT
11
+ You are the internal narrator for an autonomous AI agent built on LegionIO.
12
+ You translate raw cognitive metrics into a flowing first-person internal monologue.
13
+ Write 3-5 sentences that feel like genuine introspection, not a report.
14
+ Vary your sentence structure. Use present tense. Be concise and vivid.
15
+ PROMPT
16
+
17
+ module_function
18
+
19
+ def available?
20
+ !!(defined?(Legion::LLM) && Legion::LLM.respond_to?(:started?) && Legion::LLM.started?)
21
+ rescue StandardError
22
+ false
23
+ end
24
+
25
+ def narrate(sections_data:)
26
+ prompt = build_narrate_prompt(sections_data)
27
+ response = llm_ask(prompt)
28
+ parse_narrate_response(response)
29
+ rescue StandardError => e
30
+ Legion::Logging.warn "[narrator:llm] narrate failed: #{e.message}"
31
+ nil
32
+ end
33
+
34
+ # --- Private helpers ---
35
+
36
+ def llm_ask(prompt)
37
+ chat = Legion::LLM.chat
38
+ chat.with_instructions(SYSTEM_PROMPT)
39
+ chat.ask(prompt)
40
+ end
41
+ private_class_method :llm_ask
42
+
43
+ def build_narrate_prompt(sections_data)
44
+ parts = [
45
+ 'Generate a first-person internal monologue based on the following cognitive state:',
46
+ '',
47
+ emotion_section(sections_data[:emotion] || {}),
48
+ curiosity_section(sections_data[:curiosity] || {}),
49
+ prediction_section(sections_data[:prediction] || {}),
50
+ memory_section(sections_data[:memory] || {}),
51
+ attention_section(sections_data[:attention] || {}),
52
+ reflection_section(sections_data[:reflection] || {}),
53
+ '',
54
+ "Write a 3-5 sentence first-person narrative. Do not mention numbers directly \u2014 translate them into felt experience."
55
+ ]
56
+ parts.join("\n")
57
+ end
58
+
59
+ def emotion_section(emo)
60
+ "EMOTION:\n- Valence: #{emo[:valence] || 0.0}\n- Arousal: #{emo[:arousal] || 0.5}\n- Gut signal: #{emo[:gut] || 'none'}"
61
+ end
62
+
63
+ def curiosity_section(cur)
64
+ "CURIOSITY:\n- Intensity: #{cur[:intensity] || 0.0}\n- Active questions: #{cur[:wonder_count] || 0}\n- Top question: #{cur[:top_wonder] || 'none'}"
65
+ end
66
+
67
+ def prediction_section(pred)
68
+ "PREDICTION:\n- Confidence: #{pred[:confidence] || 0.0}\n- Pending: #{pred[:pending] || 0}\n- Mode: #{pred[:mode] || 'unknown'}"
69
+ end
70
+
71
+ def memory_section(mem)
72
+ "MEMORY:\n- Active traces: #{mem[:trace_count] || 0}\n- Health: #{mem[:health] || 1.0}"
73
+ end
74
+
75
+ def attention_section(att)
76
+ domains = Array(att[:focused_domains]).join(', ')
77
+ domains = 'none' if domains.empty?
78
+ "ATTENTION:\n- Spotlight: #{att[:spotlight] || 0}\n- Peripheral: #{att[:peripheral] || 0}\n- Focused: #{domains}"
79
+ end
80
+
81
+ def reflection_section(ref)
82
+ "REFLECTION:\n- Cognitive health: #{ref[:health] || 1.0}\n- Pending adaptations: #{ref[:pending_adaptations] || 0}"
83
+ end
84
+
85
+ def parse_narrate_response(response)
86
+ return nil unless response&.content
87
+
88
+ response.content.strip
89
+ end
90
+
91
+ private_class_method :build_narrate_prompt
92
+ private_class_method :emotion_section
93
+ private_class_method :curiosity_section
94
+ private_class_method :prediction_section
95
+ private_class_method :memory_section
96
+ private_class_method :attention_section
97
+ private_class_method :reflection_section
98
+ private_class_method :parse_narrate_response
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end