lex-memory 0.1.2
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/.github/workflows/ci.yml +16 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +53 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +129 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +99 -0
- data/README.md +158 -0
- data/lex-memory.gemspec +28 -0
- data/lib/legion/extensions/memory/actors/decay.rb +41 -0
- data/lib/legion/extensions/memory/actors/tier_migration.rb +41 -0
- data/lib/legion/extensions/memory/client.rb +28 -0
- data/lib/legion/extensions/memory/helpers/cache_store.rb +163 -0
- data/lib/legion/extensions/memory/helpers/decay.rb +64 -0
- data/lib/legion/extensions/memory/helpers/error_tracer.rb +90 -0
- data/lib/legion/extensions/memory/helpers/store.rb +256 -0
- data/lib/legion/extensions/memory/helpers/trace.rb +102 -0
- data/lib/legion/extensions/memory/local_migrations/20260316000001_create_memory_traces.rb +31 -0
- data/lib/legion/extensions/memory/local_migrations/20260316000002_create_memory_associations.rb +13 -0
- data/lib/legion/extensions/memory/runners/consolidation.rb +117 -0
- data/lib/legion/extensions/memory/runners/traces.rb +101 -0
- data/lib/legion/extensions/memory/version.rb +9 -0
- data/lib/legion/extensions/memory.rb +52 -0
- metadata +71 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
change do
|
|
5
|
+
create_table(:memory_traces) do
|
|
6
|
+
primary_key :id
|
|
7
|
+
String :trace_id, null: false, unique: true, index: true
|
|
8
|
+
String :trace_type, null: false, index: true
|
|
9
|
+
String :content, text: true, null: false
|
|
10
|
+
Float :strength, null: false, default: 1.0
|
|
11
|
+
Float :peak_strength, null: false, default: 1.0
|
|
12
|
+
Float :base_decay_rate, null: false, default: 0.02
|
|
13
|
+
String :emotional_valence, text: true
|
|
14
|
+
Float :emotional_intensity, default: 0.0
|
|
15
|
+
String :domain_tags, text: true
|
|
16
|
+
String :origin, default: 'direct_experience'
|
|
17
|
+
DateTime :created_at, null: false
|
|
18
|
+
DateTime :last_reinforced
|
|
19
|
+
DateTime :last_decayed
|
|
20
|
+
Integer :reinforcement_count, default: 0
|
|
21
|
+
Float :confidence, default: 0.5
|
|
22
|
+
String :storage_tier, default: 'hot', index: true
|
|
23
|
+
String :partition_id, index: true
|
|
24
|
+
String :associated_traces, text: true
|
|
25
|
+
String :parent_id
|
|
26
|
+
String :child_ids, text: true
|
|
27
|
+
TrueClass :unresolved, default: false, index: true
|
|
28
|
+
TrueClass :consolidation_candidate, default: false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/legion/extensions/memory/local_migrations/20260316000002_create_memory_associations.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
change do
|
|
5
|
+
create_table(:memory_associations) do
|
|
6
|
+
primary_key :id
|
|
7
|
+
String :trace_id_a, null: false, index: true
|
|
8
|
+
String :trace_id_b, null: false, index: true
|
|
9
|
+
Integer :coactivation_count, null: false, default: 1
|
|
10
|
+
unique %i[trace_id_a trace_id_b]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Memory
|
|
6
|
+
module Runners
|
|
7
|
+
module Consolidation
|
|
8
|
+
def reinforce(trace_id:, store: nil, imprint_active: false, **)
|
|
9
|
+
store ||= default_store
|
|
10
|
+
trace = store.get(trace_id)
|
|
11
|
+
return { found: false } unless trace
|
|
12
|
+
return { found: true, reinforced: false, reason: :firmware } if trace[:trace_type] == :firmware
|
|
13
|
+
|
|
14
|
+
new_strength = Helpers::Decay.compute_reinforcement(
|
|
15
|
+
current_strength: trace[:strength], imprint_active: imprint_active
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
now = Time.now.utc
|
|
19
|
+
trace[:strength] = new_strength
|
|
20
|
+
trace[:peak_strength] = [trace[:peak_strength], new_strength].max
|
|
21
|
+
trace[:last_reinforced] = now
|
|
22
|
+
trace[:reinforcement_count] += 1
|
|
23
|
+
|
|
24
|
+
store.store(trace)
|
|
25
|
+
|
|
26
|
+
Legion::Logging.debug "[memory] reinforced #{trace_id[0..7]} strength=#{new_strength.round(3)}#{' (imprint 3x)' if imprint_active}"
|
|
27
|
+
{ found: true, reinforced: true, trace_id: trace_id, new_strength: new_strength }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def decay_cycle(store: nil, tick_count: 1, **)
|
|
31
|
+
store ||= default_store
|
|
32
|
+
decayed = 0
|
|
33
|
+
pruned = 0
|
|
34
|
+
|
|
35
|
+
store.all_traces.each do |trace|
|
|
36
|
+
next if trace[:base_decay_rate].zero?
|
|
37
|
+
|
|
38
|
+
new_strength = Helpers::Decay.compute_decay(
|
|
39
|
+
peak_strength: trace[:peak_strength],
|
|
40
|
+
base_decay_rate: trace[:base_decay_rate],
|
|
41
|
+
ticks_since_access: tick_count,
|
|
42
|
+
emotional_intensity: trace[:emotional_intensity]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if new_strength <= Helpers::Trace::PRUNE_THRESHOLD
|
|
46
|
+
store.delete(trace[:trace_id])
|
|
47
|
+
pruned += 1
|
|
48
|
+
else
|
|
49
|
+
trace[:strength] = new_strength
|
|
50
|
+
trace[:last_decayed] = Time.now.utc
|
|
51
|
+
store.store(trace)
|
|
52
|
+
decayed += 1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Legion::Logging.debug "[memory] decay cycle: decayed=#{decayed} pruned=#{pruned}"
|
|
57
|
+
{ decayed: decayed, pruned: pruned }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def migrate_tier(store: nil, **)
|
|
61
|
+
store ||= default_store
|
|
62
|
+
migrated = 0
|
|
63
|
+
now = Time.now.utc
|
|
64
|
+
|
|
65
|
+
store.all_traces.each do |trace|
|
|
66
|
+
new_tier = Helpers::Decay.compute_storage_tier(trace: trace, now: now)
|
|
67
|
+
next if trace[:storage_tier] == new_tier
|
|
68
|
+
|
|
69
|
+
trace[:storage_tier] = new_tier
|
|
70
|
+
store.store(trace)
|
|
71
|
+
migrated += 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Legion::Logging.debug "[memory] tier migration: migrated=#{migrated}"
|
|
75
|
+
{ migrated: migrated }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def hebbian_link(trace_id_a: nil, trace_id_b: nil, store: nil, **)
|
|
79
|
+
return { linked: false, reason: :missing_trace_ids } if trace_id_a.nil? || trace_id_b.nil?
|
|
80
|
+
|
|
81
|
+
store ||= default_store
|
|
82
|
+
store.record_coactivation(trace_id_a, trace_id_b)
|
|
83
|
+
Legion::Logging.debug "[memory] hebbian link #{trace_id_a[0..7]} <-> #{trace_id_b[0..7]}"
|
|
84
|
+
{ linked: true }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def erase_by_type(type:, store: nil, **)
|
|
88
|
+
store ||= default_store
|
|
89
|
+
type = type.to_sym
|
|
90
|
+
traces = store.retrieve_by_type(type, min_strength: 0.0, limit: 100_000)
|
|
91
|
+
count = traces.size
|
|
92
|
+
traces.each { |t| store.delete(t[:trace_id]) }
|
|
93
|
+
Legion::Logging.info "[memory] erased #{count} traces of type=#{type}"
|
|
94
|
+
{ erased: count, type: type }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def erase_by_agent(partition_id:, store: nil, **)
|
|
98
|
+
store ||= default_store
|
|
99
|
+
traces = store.all_traces.select { |t| t[:partition_id] == partition_id }
|
|
100
|
+
count = traces.size
|
|
101
|
+
traces.each { |t| store.delete(t[:trace_id]) }
|
|
102
|
+
Legion::Logging.info "[memory] erased #{count} traces for partition=#{partition_id}"
|
|
103
|
+
{ erased: count, partition_id: partition_id }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def default_store
|
|
109
|
+
@default_store ||= Legion::Extensions::Memory.shared_store
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Memory
|
|
6
|
+
module Runners
|
|
7
|
+
module Traces
|
|
8
|
+
def store_trace(type:, content_payload:, store: nil, **)
|
|
9
|
+
store ||= default_store
|
|
10
|
+
trace = Helpers::Trace.new_trace(type: type.to_sym, content_payload: content_payload, **)
|
|
11
|
+
store.store(trace)
|
|
12
|
+
Legion::Logging.debug "[memory] stored trace #{trace[:trace_id][0..7]} type=#{trace[:trace_type]} strength=#{trace[:strength].round(2)}"
|
|
13
|
+
{ trace_id: trace[:trace_id], trace_type: trace[:trace_type], strength: trace[:strength] }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get_trace(trace_id:, store: nil, **)
|
|
17
|
+
store ||= default_store
|
|
18
|
+
trace = store.get(trace_id)
|
|
19
|
+
Legion::Logging.debug "[memory] get trace #{trace_id[0..7]} found=#{!trace.nil?}"
|
|
20
|
+
return { found: false } unless trace
|
|
21
|
+
|
|
22
|
+
{ found: true, trace: trace }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def retrieve_by_type(type:, store: nil, min_strength: 0.0, limit: 100, **)
|
|
26
|
+
store ||= default_store
|
|
27
|
+
traces = store.retrieve_by_type(type.to_sym, min_strength: min_strength, limit: limit)
|
|
28
|
+
Legion::Logging.debug "[memory] retrieve_by_type=#{type} count=#{traces.size} min_strength=#{min_strength}"
|
|
29
|
+
{ count: traces.size, traces: traces }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def retrieve_by_domain(domain_tag:, store: nil, min_strength: 0.0, limit: 100, **)
|
|
33
|
+
store ||= default_store
|
|
34
|
+
traces = store.retrieve_by_domain(domain_tag, min_strength: min_strength, limit: limit)
|
|
35
|
+
Legion::Logging.debug "[memory] retrieve_by_domain=#{domain_tag} count=#{traces.size}"
|
|
36
|
+
{ count: traces.size, traces: traces }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def retrieve_associated(trace_id:, store: nil, min_strength: 0.0, limit: 20, **)
|
|
40
|
+
store ||= default_store
|
|
41
|
+
traces = store.retrieve_associated(trace_id, min_strength: min_strength, limit: limit)
|
|
42
|
+
Legion::Logging.debug "[memory] retrieve_associated for #{trace_id[0..7]} count=#{traces.size}"
|
|
43
|
+
{ count: traces.size, traces: traces }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def retrieve_ranked(trace_ids: [], store: nil, query_time: nil, **)
|
|
47
|
+
store ||= default_store
|
|
48
|
+
query_time ||= Time.now.utc
|
|
49
|
+
associated_set = Set.new
|
|
50
|
+
|
|
51
|
+
traces = trace_ids.filter_map { |id| store.get(id) }
|
|
52
|
+
traces.each { |t| associated_set.merge(t[:associated_traces]) }
|
|
53
|
+
|
|
54
|
+
scored = traces.map do |t|
|
|
55
|
+
score = Helpers::Decay.compute_retrieval_score(
|
|
56
|
+
trace: t, query_time: query_time, associated: associated_set.include?(t[:trace_id])
|
|
57
|
+
)
|
|
58
|
+
{ trace: t, score: score }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
result = scored.sort_by { |s| -s[:score] }
|
|
62
|
+
Legion::Logging.debug "[memory] retrieve_ranked ids=#{trace_ids.size} scored=#{result.size}"
|
|
63
|
+
result
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def delete_trace(trace_id:, store: nil, **)
|
|
67
|
+
store ||= default_store
|
|
68
|
+
store.delete(trace_id)
|
|
69
|
+
Legion::Logging.debug "[memory] deleted trace #{trace_id[0..7]}"
|
|
70
|
+
{ deleted: true, trace_id: trace_id }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def retrieve_and_reinforce(limit: 10, store: nil, **)
|
|
74
|
+
store ||= default_store
|
|
75
|
+
all = store.all_traces(min_strength: 0.1)
|
|
76
|
+
top = all.sort_by { |t| -t[:strength] }.first(limit)
|
|
77
|
+
|
|
78
|
+
top.each do |trace|
|
|
79
|
+
next if trace[:base_decay_rate].zero? # skip firmware
|
|
80
|
+
|
|
81
|
+
trace[:reinforcement_count] += 1
|
|
82
|
+
trace[:last_reinforced] = Time.now.utc
|
|
83
|
+
store.store(trace)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
Legion::Logging.debug "[memory] retrieve_and_reinforce: retrieved=#{top.size} from=#{all.size} total"
|
|
87
|
+
{ count: top.size, traces: top }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def default_store
|
|
93
|
+
@default_store ||= Legion::Extensions::Memory.shared_store
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/memory/version'
|
|
4
|
+
require 'legion/extensions/memory/helpers/trace'
|
|
5
|
+
require 'legion/extensions/memory/helpers/decay'
|
|
6
|
+
require 'legion/extensions/memory/helpers/store'
|
|
7
|
+
require 'legion/extensions/memory/helpers/cache_store'
|
|
8
|
+
require 'legion/extensions/memory/helpers/error_tracer'
|
|
9
|
+
require 'legion/extensions/memory/runners/traces'
|
|
10
|
+
require 'legion/extensions/memory/runners/consolidation'
|
|
11
|
+
require 'legion/extensions/memory/client'
|
|
12
|
+
|
|
13
|
+
module Legion
|
|
14
|
+
module Extensions
|
|
15
|
+
module Memory
|
|
16
|
+
class << self
|
|
17
|
+
# Process-wide shared store. All memory runners delegate here so that
|
|
18
|
+
# traces written by one component (ErrorTracer, coldstart, tick) are
|
|
19
|
+
# visible to every other component (dream cycle, cortex, predictions).
|
|
20
|
+
# CacheStore adds cross-process sharing via memcached on top of this.
|
|
21
|
+
def shared_store
|
|
22
|
+
@shared_store ||= create_store
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reset_store!
|
|
26
|
+
@shared_store = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def create_store
|
|
32
|
+
if defined?(Legion::Cache) && Legion::Cache.respond_to?(:connected?) && Legion::Cache.connected?
|
|
33
|
+
Legion::Logging.debug '[memory] Using shared CacheStore (memcached)'
|
|
34
|
+
Helpers::CacheStore.new
|
|
35
|
+
else
|
|
36
|
+
Legion::Logging.debug '[memory] Using shared in-memory Store'
|
|
37
|
+
Helpers::Store.new
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if defined?(Legion::Data::Local)
|
|
48
|
+
Legion::Data::Local.register_migrations(
|
|
49
|
+
name: :memory,
|
|
50
|
+
path: File.join(__dir__, 'memory', 'local_migrations')
|
|
51
|
+
)
|
|
52
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-memory
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Memory trace system for brain-modeled agentic AI — consolidation, reinforcement,
|
|
13
|
+
and decay
|
|
14
|
+
email:
|
|
15
|
+
- matthewdiverson@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- ".github/workflows/ci.yml"
|
|
21
|
+
- ".gitignore"
|
|
22
|
+
- ".rspec"
|
|
23
|
+
- ".rubocop.yml"
|
|
24
|
+
- CHANGELOG.md
|
|
25
|
+
- CLAUDE.md
|
|
26
|
+
- Gemfile
|
|
27
|
+
- Gemfile.lock
|
|
28
|
+
- README.md
|
|
29
|
+
- lex-memory.gemspec
|
|
30
|
+
- lib/legion/extensions/memory.rb
|
|
31
|
+
- lib/legion/extensions/memory/actors/decay.rb
|
|
32
|
+
- lib/legion/extensions/memory/actors/tier_migration.rb
|
|
33
|
+
- lib/legion/extensions/memory/client.rb
|
|
34
|
+
- lib/legion/extensions/memory/helpers/cache_store.rb
|
|
35
|
+
- lib/legion/extensions/memory/helpers/decay.rb
|
|
36
|
+
- lib/legion/extensions/memory/helpers/error_tracer.rb
|
|
37
|
+
- lib/legion/extensions/memory/helpers/store.rb
|
|
38
|
+
- lib/legion/extensions/memory/helpers/trace.rb
|
|
39
|
+
- lib/legion/extensions/memory/local_migrations/20260316000001_create_memory_traces.rb
|
|
40
|
+
- lib/legion/extensions/memory/local_migrations/20260316000002_create_memory_associations.rb
|
|
41
|
+
- lib/legion/extensions/memory/runners/consolidation.rb
|
|
42
|
+
- lib/legion/extensions/memory/runners/traces.rb
|
|
43
|
+
- lib/legion/extensions/memory/version.rb
|
|
44
|
+
homepage: https://github.com/LegionIO/lex-memory
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
homepage_uri: https://github.com/LegionIO/lex-memory
|
|
49
|
+
source_code_uri: https://github.com/LegionIO/lex-memory
|
|
50
|
+
documentation_uri: https://github.com/LegionIO/lex-memory
|
|
51
|
+
changelog_uri: https://github.com/LegionIO/lex-memory
|
|
52
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-memory/issues
|
|
53
|
+
rubygems_mfa_required: 'true'
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.4'
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.6.9
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: LEX Memory
|
|
71
|
+
test_files: []
|