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.
@@ -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
@@ -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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Memory
6
+ VERSION = '0.1.2'
7
+ end
8
+ end
9
+ 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: []