lex-narrative-self 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 +7 -0
- data/README.md +69 -0
- data/lib/legion/extensions/narrative_self/client.rb +23 -0
- data/lib/legion/extensions/narrative_self/helpers/autobiography.rb +183 -0
- data/lib/legion/extensions/narrative_self/helpers/constants.rb +38 -0
- data/lib/legion/extensions/narrative_self/helpers/episode.rb +77 -0
- data/lib/legion/extensions/narrative_self/helpers/narrative_thread.rb +61 -0
- data/lib/legion/extensions/narrative_self/runners/narrative_self.rb +82 -0
- data/lib/legion/extensions/narrative_self/version.rb +9 -0
- data/lib/legion/extensions/narrative_self.rb +17 -0
- metadata +66 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 336a5c4eb91fca23131bab8a2566fe8398bba7541dcc68e1c437c6a1a8c9067d
|
|
4
|
+
data.tar.gz: c43bcd16be3bca9f7a320b40e62e2dcb3aa08b5a0953685674924bbdb0269a4f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cf610636d9fce5865681b7b7dc143926f99bcb91b2b26d8526e2f23cfdbe0bade5ce1c746eaa8667e417f7c1de1dabc8f14dc9ec24ae86c72accbf6d1376ca33
|
|
7
|
+
data.tar.gz: a61cad3fb3f040028f902dec6c6f99a4a9291caadef117277f29421872403b2ca909e87ef938f5668facaa427daf9667dcca9ea1123681bd8a5c7d9269bbf0c9
|
data/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# lex-narrative-self
|
|
2
|
+
|
|
3
|
+
Autobiographical narrative and self-concept for the LegionIO cognitive architecture. Models the agent's personal history as episodes, threads, and an evolving identity.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Maintains an autobiography of personal episodes — each with a significance score, emotional valence, domain, and tags. Episodes are grouped into narrative threads by thematic overlap. A self-concept emerges from the statistical pattern of episode types over time via exponential moving average. Older low-significance episodes fade naturally.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
client = Legion::Extensions::NarrativeSelf::Client.new
|
|
13
|
+
|
|
14
|
+
# Record an experience
|
|
15
|
+
client.record_episode(
|
|
16
|
+
description: 'Successfully debugged a complex race condition',
|
|
17
|
+
episode_type: :achievement,
|
|
18
|
+
domain: :engineering,
|
|
19
|
+
significance: 0.8,
|
|
20
|
+
emotional_valence: 0.7,
|
|
21
|
+
tags: ['debugging', 'concurrency']
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Create a narrative thread to track a recurring theme
|
|
25
|
+
client.create_thread(theme: 'debugging', domain: :engineering)
|
|
26
|
+
|
|
27
|
+
# Retrieve recent and significant episodes
|
|
28
|
+
client.recent_episodes(count: 5)
|
|
29
|
+
client.significant_episodes(min_significance: 0.7)
|
|
30
|
+
|
|
31
|
+
# Self-concept summary
|
|
32
|
+
summary = client.self_summary
|
|
33
|
+
# => {
|
|
34
|
+
# total_episodes: 42,
|
|
35
|
+
# dominant_types: [:achievement, :discovery, :insight],
|
|
36
|
+
# dominant_domains: [:engineering, :coordination],
|
|
37
|
+
# active_threads: ['debugging', 'planning'],
|
|
38
|
+
# self_concept: { achievement: 0.72, discovery: 0.61, ... },
|
|
39
|
+
# narrative_richness: 0.64
|
|
40
|
+
# }
|
|
41
|
+
|
|
42
|
+
# Tick decay (call each processing cycle)
|
|
43
|
+
client.update_narrative_self
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Episode Types
|
|
47
|
+
|
|
48
|
+
`:achievement`, `:failure`, `:discovery`, `:connection`, `:conflict`, `:resolution`, `:insight`, `:surprise`, `:decision`, `:transition`, `:reflection`
|
|
49
|
+
|
|
50
|
+
## Significance Labels
|
|
51
|
+
|
|
52
|
+
| Range | Label |
|
|
53
|
+
|---|---|
|
|
54
|
+
| 0.8+ | `:pivotal` |
|
|
55
|
+
| 0.6–0.8 | `:important` |
|
|
56
|
+
| 0.3–0.6 | `:routine` |
|
|
57
|
+
| < 0.3 | `:minor` |
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
bundle install
|
|
63
|
+
bundle exec rspec
|
|
64
|
+
bundle exec rubocop
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/narrative_self/helpers/constants'
|
|
4
|
+
require 'legion/extensions/narrative_self/helpers/episode'
|
|
5
|
+
require 'legion/extensions/narrative_self/helpers/narrative_thread'
|
|
6
|
+
require 'legion/extensions/narrative_self/helpers/autobiography'
|
|
7
|
+
require 'legion/extensions/narrative_self/runners/narrative_self'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module NarrativeSelf
|
|
12
|
+
class Client
|
|
13
|
+
include Runners::NarrativeSelf
|
|
14
|
+
|
|
15
|
+
attr_reader :autobiography
|
|
16
|
+
|
|
17
|
+
def initialize(autobiography: nil, **)
|
|
18
|
+
@autobiography = autobiography || Helpers::Autobiography.new
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module NarrativeSelf
|
|
6
|
+
module Helpers
|
|
7
|
+
class Autobiography
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :episodes, :threads, :self_concept
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@episodes = []
|
|
14
|
+
@threads = []
|
|
15
|
+
@self_concept = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def record_episode(description:, episode_type: :insight, domain: :general,
|
|
19
|
+
significance: nil, emotional_valence: 0.0, tags: [])
|
|
20
|
+
episode = Episode.new(
|
|
21
|
+
description: description,
|
|
22
|
+
episode_type: episode_type,
|
|
23
|
+
domain: domain,
|
|
24
|
+
significance: significance,
|
|
25
|
+
emotional_valence: emotional_valence,
|
|
26
|
+
tags: tags
|
|
27
|
+
)
|
|
28
|
+
@episodes << episode
|
|
29
|
+
auto_link_threads(episode)
|
|
30
|
+
update_self_concept(episode)
|
|
31
|
+
trim_episodes
|
|
32
|
+
episode
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def find_episode(id)
|
|
36
|
+
@episodes.find { |e| e.id == id }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def recent_episodes(count = 10)
|
|
40
|
+
@episodes.last(count)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def significant_episodes(min_significance: 0.6)
|
|
44
|
+
@episodes.select { |e| e.significance >= min_significance }
|
|
45
|
+
.sort_by { |e| -e.significance }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def episodes_by_type(episode_type)
|
|
49
|
+
@episodes.select { |e| e.episode_type == episode_type }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def episodes_in_domain(domain)
|
|
53
|
+
@episodes.select { |e| e.domain == domain }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def create_thread(theme:, domain: :general)
|
|
57
|
+
thread = NarrativeThread.new(theme: theme, domain: domain)
|
|
58
|
+
@threads << thread
|
|
59
|
+
trim_threads
|
|
60
|
+
thread
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def find_thread(id)
|
|
64
|
+
@threads.find { |t| t.id == id }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def find_threads_by_theme(theme)
|
|
68
|
+
@threads.select { |t| t.theme == theme }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def strongest_threads(count = 5)
|
|
72
|
+
@threads.sort_by { |t| -t.strength }.first(count)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def timeline(window: MAX_TIMELINE_WINDOW)
|
|
76
|
+
@episodes.last(window).map(&:to_h)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self_summary
|
|
80
|
+
top_types = episode_type_distribution.first(3).map(&:first)
|
|
81
|
+
top_domains = domain_distribution.first(3).map(&:first)
|
|
82
|
+
top_threads = strongest_threads(3).map(&:theme)
|
|
83
|
+
{
|
|
84
|
+
total_episodes: @episodes.size,
|
|
85
|
+
dominant_types: top_types,
|
|
86
|
+
dominant_domains: top_domains,
|
|
87
|
+
active_threads: top_threads,
|
|
88
|
+
self_concept: @self_concept.dup,
|
|
89
|
+
pivotal_count: @episodes.count { |e| e.label == :pivotal },
|
|
90
|
+
narrative_richness: narrative_richness
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def decay_all
|
|
95
|
+
@episodes.each(&:decay)
|
|
96
|
+
@episodes.reject!(&:faded?)
|
|
97
|
+
@threads.each(&:decay)
|
|
98
|
+
@threads.reject!(&:weak?)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def to_h
|
|
102
|
+
{
|
|
103
|
+
episode_count: @episodes.size,
|
|
104
|
+
thread_count: @threads.size,
|
|
105
|
+
self_concept: @self_concept.dup,
|
|
106
|
+
by_type: @episodes.group_by(&:episode_type).transform_values(&:size),
|
|
107
|
+
by_domain: @episodes.group_by(&:domain).transform_values(&:size),
|
|
108
|
+
avg_significance: avg_significance
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def auto_link_threads(episode)
|
|
115
|
+
@threads.each do |thread|
|
|
116
|
+
relevance = episode.matches_tags?([thread.theme])
|
|
117
|
+
relevance += 0.2 if episode.domain == thread.domain
|
|
118
|
+
next unless relevance >= THREAD_MATCH_THRESHOLD
|
|
119
|
+
|
|
120
|
+
thread.add_episode(episode.id)
|
|
121
|
+
episode.link_thread(thread.id)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def update_self_concept(episode)
|
|
126
|
+
trait = episode.episode_type
|
|
127
|
+
current = @self_concept.fetch(trait, 0.0)
|
|
128
|
+
signal = episode.significance
|
|
129
|
+
@self_concept[trait] = ((TRAIT_ALPHA * signal) + ((1.0 - TRAIT_ALPHA) * current)).clamp(0.0, 1.0)
|
|
130
|
+
trim_self_concept
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def trim_self_concept
|
|
134
|
+
return unless @self_concept.size > MAX_SELF_CONCEPT_TRAITS
|
|
135
|
+
|
|
136
|
+
sorted = @self_concept.sort_by { |_, v| v }
|
|
137
|
+
@self_concept = sorted.last(MAX_SELF_CONCEPT_TRAITS).to_h
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def trim_episodes
|
|
141
|
+
return unless @episodes.size > MAX_EPISODES
|
|
142
|
+
|
|
143
|
+
@episodes.sort_by!(&:significance)
|
|
144
|
+
@episodes.shift(@episodes.size - MAX_EPISODES)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def trim_threads
|
|
148
|
+
return unless @threads.size > MAX_THREADS
|
|
149
|
+
|
|
150
|
+
@threads.sort_by!(&:strength)
|
|
151
|
+
@threads.shift(@threads.size - MAX_THREADS)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def episode_type_distribution
|
|
155
|
+
@episodes.group_by(&:episode_type)
|
|
156
|
+
.transform_values(&:size)
|
|
157
|
+
.sort_by { |_, count| -count }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def domain_distribution
|
|
161
|
+
@episodes.group_by(&:domain)
|
|
162
|
+
.transform_values(&:size)
|
|
163
|
+
.sort_by { |_, count| -count }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def avg_significance
|
|
167
|
+
return 0.0 if @episodes.empty?
|
|
168
|
+
|
|
169
|
+
@episodes.sum(&:significance) / @episodes.size
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def narrative_richness
|
|
173
|
+
return 0.0 if @episodes.empty?
|
|
174
|
+
|
|
175
|
+
type_diversity = @episodes.map(&:episode_type).uniq.size.to_f / EPISODE_TYPES.size
|
|
176
|
+
thread_activity = @threads.empty? ? 0.0 : @threads.count { |t| t.size > 1 }.to_f / @threads.size
|
|
177
|
+
((type_diversity + thread_activity) / 2.0).clamp(0.0, 1.0)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module NarrativeSelf
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_EPISODES = 500
|
|
9
|
+
MAX_THREADS = 50
|
|
10
|
+
MAX_CHAPTER_SIZE = 20
|
|
11
|
+
EPISODE_DECAY = 0.005
|
|
12
|
+
THREAD_DECAY = 0.01
|
|
13
|
+
SIGNIFICANCE_FLOOR = 0.05
|
|
14
|
+
SIGNIFICANCE_ALPHA = 0.15
|
|
15
|
+
DEFAULT_SIGNIFICANCE = 0.5
|
|
16
|
+
EMOTIONAL_BOOST = 0.3
|
|
17
|
+
THREAD_MATCH_THRESHOLD = 0.3
|
|
18
|
+
MAX_SELF_CONCEPT_TRAITS = 30
|
|
19
|
+
TRAIT_ALPHA = 0.1
|
|
20
|
+
MAX_TIMELINE_WINDOW = 100
|
|
21
|
+
|
|
22
|
+
SIGNIFICANCE_LABELS = {
|
|
23
|
+
(0.8..) => :pivotal,
|
|
24
|
+
(0.6...0.8) => :important,
|
|
25
|
+
(0.3...0.6) => :routine,
|
|
26
|
+
(..0.3) => :minor
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
EPISODE_TYPES = %i[
|
|
30
|
+
achievement failure discovery connection
|
|
31
|
+
conflict resolution insight surprise
|
|
32
|
+
decision transition reflection
|
|
33
|
+
].freeze
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module NarrativeSelf
|
|
8
|
+
module Helpers
|
|
9
|
+
class Episode
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :description, :episode_type, :domain, :significance,
|
|
13
|
+
:emotional_valence, :tags, :created_at, :thread_ids
|
|
14
|
+
|
|
15
|
+
def initialize(description:, episode_type: :insight, domain: :general,
|
|
16
|
+
significance: nil, emotional_valence: 0.0, tags: [])
|
|
17
|
+
@id = SecureRandom.uuid
|
|
18
|
+
@description = description
|
|
19
|
+
@episode_type = episode_type
|
|
20
|
+
@domain = domain
|
|
21
|
+
@significance = significance || DEFAULT_SIGNIFICANCE
|
|
22
|
+
@emotional_valence = emotional_valence.clamp(-1.0, 1.0)
|
|
23
|
+
@tags = tags
|
|
24
|
+
@created_at = Time.now.utc
|
|
25
|
+
@thread_ids = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def boost(amount)
|
|
29
|
+
emotional_factor = @emotional_valence.abs * EMOTIONAL_BOOST
|
|
30
|
+
@significance = [(@significance + amount + emotional_factor), 1.0].min
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def decay
|
|
34
|
+
@significance = [(@significance - EPISODE_DECAY), 0.0].max
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def faded?
|
|
38
|
+
@significance < SIGNIFICANCE_FLOOR
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def label
|
|
42
|
+
SIGNIFICANCE_LABELS.each do |range, lbl|
|
|
43
|
+
return lbl if range.cover?(@significance)
|
|
44
|
+
end
|
|
45
|
+
:minor
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def link_thread(thread_id)
|
|
49
|
+
@thread_ids << thread_id unless @thread_ids.include?(thread_id)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def matches_tags?(query_tags)
|
|
53
|
+
return false if query_tags.empty? || @tags.empty?
|
|
54
|
+
|
|
55
|
+
overlap = (@tags & query_tags).size
|
|
56
|
+
overlap.to_f / query_tags.size
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_h
|
|
60
|
+
{
|
|
61
|
+
id: @id,
|
|
62
|
+
description: @description,
|
|
63
|
+
episode_type: @episode_type,
|
|
64
|
+
domain: @domain,
|
|
65
|
+
significance: @significance,
|
|
66
|
+
emotional_valence: @emotional_valence,
|
|
67
|
+
label: label,
|
|
68
|
+
tags: @tags.dup,
|
|
69
|
+
thread_ids: @thread_ids.dup,
|
|
70
|
+
created_at: @created_at
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module NarrativeSelf
|
|
8
|
+
module Helpers
|
|
9
|
+
class NarrativeThread
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :theme, :domain, :strength, :episode_ids, :created_at
|
|
13
|
+
|
|
14
|
+
def initialize(theme:, domain: :general)
|
|
15
|
+
@id = SecureRandom.uuid
|
|
16
|
+
@theme = theme
|
|
17
|
+
@domain = domain
|
|
18
|
+
@strength = 0.5
|
|
19
|
+
@episode_ids = []
|
|
20
|
+
@created_at = Time.now.utc
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_episode(episode_id)
|
|
24
|
+
return if @episode_ids.include?(episode_id)
|
|
25
|
+
|
|
26
|
+
@episode_ids << episode_id
|
|
27
|
+
@episode_ids.shift if @episode_ids.size > MAX_CHAPTER_SIZE
|
|
28
|
+
reinforce
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def reinforce
|
|
32
|
+
@strength = [@strength + 0.1, 1.0].min
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def decay
|
|
36
|
+
@strength = [(@strength - THREAD_DECAY), 0.0].max
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def weak?
|
|
40
|
+
@strength < SIGNIFICANCE_FLOOR
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def size
|
|
44
|
+
@episode_ids.size
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_h
|
|
48
|
+
{
|
|
49
|
+
id: @id,
|
|
50
|
+
theme: @theme,
|
|
51
|
+
domain: @domain,
|
|
52
|
+
strength: @strength,
|
|
53
|
+
episodes: @episode_ids.size,
|
|
54
|
+
created_at: @created_at
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module NarrativeSelf
|
|
6
|
+
module Runners
|
|
7
|
+
module NarrativeSelf
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def record_episode(description:, episode_type: :insight, domain: :general,
|
|
12
|
+
significance: nil, emotional_valence: 0.0, tags: [], **)
|
|
13
|
+
episode = autobiography.record_episode(
|
|
14
|
+
description: description,
|
|
15
|
+
episode_type: episode_type,
|
|
16
|
+
domain: domain,
|
|
17
|
+
significance: significance,
|
|
18
|
+
emotional_valence: emotional_valence,
|
|
19
|
+
tags: tags
|
|
20
|
+
)
|
|
21
|
+
Legion::Logging.debug "[narrative_self] recorded episode=#{episode_type} domain=#{domain} sig=#{episode.significance.round(3)}"
|
|
22
|
+
{ success: true, episode: episode.to_h }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def recent_episodes(count: 10, **)
|
|
26
|
+
episodes = autobiography.recent_episodes(count)
|
|
27
|
+
{ success: true, episodes: episodes.map(&:to_h), count: episodes.size }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def significant_episodes(min_significance: 0.6, **)
|
|
31
|
+
episodes = autobiography.significant_episodes(min_significance: min_significance)
|
|
32
|
+
{ success: true, episodes: episodes.map(&:to_h), count: episodes.size }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def episodes_by_type(episode_type:, **)
|
|
36
|
+
episodes = autobiography.episodes_by_type(episode_type)
|
|
37
|
+
{ success: true, episodes: episodes.map(&:to_h), count: episodes.size }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create_thread(theme:, domain: :general, **)
|
|
41
|
+
thread = autobiography.create_thread(theme: theme, domain: domain)
|
|
42
|
+
Legion::Logging.debug "[narrative_self] created thread=#{theme} domain=#{domain}"
|
|
43
|
+
{ success: true, thread: thread.to_h }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def strongest_threads(count: 5, **)
|
|
47
|
+
threads = autobiography.strongest_threads(count)
|
|
48
|
+
{ success: true, threads: threads.map(&:to_h), count: threads.size }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def timeline(window: nil, **)
|
|
52
|
+
w = window || Helpers::Constants::MAX_TIMELINE_WINDOW
|
|
53
|
+
entries = autobiography.timeline(window: w)
|
|
54
|
+
{ success: true, timeline: entries, count: entries.size }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self_summary(**)
|
|
58
|
+
summary = autobiography.self_summary
|
|
59
|
+
Legion::Logging.debug "[narrative_self] summary: episodes=#{summary[:total_episodes]} richness=#{summary[:narrative_richness].round(3)}"
|
|
60
|
+
{ success: true, summary: summary }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def update_narrative_self(**)
|
|
64
|
+
autobiography.decay_all
|
|
65
|
+
Legion::Logging.debug "[narrative_self] tick: episodes=#{autobiography.episodes.size} threads=#{autobiography.threads.size}"
|
|
66
|
+
{ success: true, episode_count: autobiography.episodes.size, thread_count: autobiography.threads.size }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def narrative_self_stats(**)
|
|
70
|
+
{ success: true, stats: autobiography.to_h }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def autobiography
|
|
76
|
+
@autobiography ||= Helpers::Autobiography.new
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/narrative_self/version'
|
|
4
|
+
require 'legion/extensions/narrative_self/helpers/constants'
|
|
5
|
+
require 'legion/extensions/narrative_self/helpers/episode'
|
|
6
|
+
require 'legion/extensions/narrative_self/helpers/narrative_thread'
|
|
7
|
+
require 'legion/extensions/narrative_self/helpers/autobiography'
|
|
8
|
+
require 'legion/extensions/narrative_self/runners/narrative_self'
|
|
9
|
+
require 'legion/extensions/narrative_self/client'
|
|
10
|
+
|
|
11
|
+
module Legion
|
|
12
|
+
module Extensions
|
|
13
|
+
module NarrativeSelf
|
|
14
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-narrative-self
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Matthew Iverson
|
|
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 autonoetic consciousness — maintains an autobiographical narrative
|
|
27
|
+
of episodes, narrative threads, and an evolving self-concept that emerges from the
|
|
28
|
+
pattern of lived experience.
|
|
29
|
+
email:
|
|
30
|
+
- matt@iverson.io
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- README.md
|
|
36
|
+
- lib/legion/extensions/narrative_self.rb
|
|
37
|
+
- lib/legion/extensions/narrative_self/client.rb
|
|
38
|
+
- lib/legion/extensions/narrative_self/helpers/autobiography.rb
|
|
39
|
+
- lib/legion/extensions/narrative_self/helpers/constants.rb
|
|
40
|
+
- lib/legion/extensions/narrative_self/helpers/episode.rb
|
|
41
|
+
- lib/legion/extensions/narrative_self/helpers/narrative_thread.rb
|
|
42
|
+
- lib/legion/extensions/narrative_self/runners/narrative_self.rb
|
|
43
|
+
- lib/legion/extensions/narrative_self/version.rb
|
|
44
|
+
homepage: https://github.com/LegionIO/lex-narrative-self
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
rubygems_mfa_required: 'true'
|
|
49
|
+
rdoc_options: []
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '3.4'
|
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubygems_version: 3.6.9
|
|
64
|
+
specification_version: 4
|
|
65
|
+
summary: Autobiographical narrative and self-concept for LegionIO
|
|
66
|
+
test_files: []
|