lex-working-memory 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: edc8c24f643c909f8a668cf4f6912cca4b8fd080bd92627015b954fb58a12027
4
+ data.tar.gz: bd91d9be62389bff276be79db7074224d937ecd3cfcfb45abd1fd6366515c4ab
5
+ SHA512:
6
+ metadata.gz: 56bae8720995069cdb1b4e54bd43045af761eeeacb1a82a3a67bc59c664b56caf730f5a475c1cffe51395290d038f49142965f7f81630e101798bd3be8731e81
7
+ data.tar.gz: 4f55b708fe07e3a954ce1f89b67a7d9e5906095888347824e224350da32533e1ac31d972b71c2ff642cdde70f4502aa4df3c1f1d2dd885370f6b193323ee4638
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module WorkingMemory
6
+ class Client
7
+ include Runners::WorkingMemory
8
+
9
+ attr_reader :buffer
10
+
11
+ def initialize(buffer: nil, **)
12
+ @buffer = buffer || Helpers::Buffer.new
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module WorkingMemory
6
+ module Helpers
7
+ class Buffer
8
+ attr_reader :items
9
+
10
+ def initialize
11
+ @items = []
12
+ end
13
+
14
+ def store(content:, buffer_type: :episodic, priority: :normal, tags: [])
15
+ item = BufferItem.new(content: content, buffer_type: buffer_type, priority: priority, tags: tags)
16
+ @items << item
17
+ evict_if_over_capacity
18
+ item
19
+ end
20
+
21
+ def retrieve(id)
22
+ @items.find { |i| i.id == id }
23
+ end
24
+
25
+ def retrieve_by_tag(tag)
26
+ @items.select { |i| i.tags.include?(tag) }
27
+ end
28
+
29
+ def retrieve_by_type(buffer_type)
30
+ @items.select { |i| i.buffer_type == buffer_type }
31
+ end
32
+
33
+ def rehearse(id)
34
+ item = retrieve(id)
35
+ return nil unless item
36
+
37
+ item.rehearse
38
+ item
39
+ end
40
+
41
+ def remove(id)
42
+ @items.reject! { |i| i.id == id }
43
+ end
44
+
45
+ def tick_decay
46
+ @items.each(&:decay)
47
+ @items.reject!(&:expired?)
48
+ end
49
+
50
+ def consolidation_candidates
51
+ @items.select(&:consolidation_ready?)
52
+ end
53
+
54
+ def current_load
55
+ return 0.0 if capacity.zero?
56
+
57
+ (@items.size.to_f / capacity).clamp(0.0, 1.0)
58
+ end
59
+
60
+ def load_level
61
+ load = current_load
62
+ Constants::LOAD_LEVELS.each do |level, threshold|
63
+ return level if load >= threshold
64
+ end
65
+ :idle
66
+ end
67
+
68
+ def capacity
69
+ Constants::CAPACITY + chunk_bonus
70
+ end
71
+
72
+ def available_slots
73
+ [capacity - @items.size, 0].max
74
+ end
75
+
76
+ def full?
77
+ @items.size >= capacity
78
+ end
79
+
80
+ def clear
81
+ @items.clear
82
+ end
83
+
84
+ def size
85
+ @items.size
86
+ end
87
+
88
+ def to_h
89
+ {
90
+ size: @items.size,
91
+ capacity: capacity,
92
+ load: current_load.round(4),
93
+ load_level: load_level,
94
+ by_type: items_by_type,
95
+ available: available_slots
96
+ }
97
+ end
98
+
99
+ private
100
+
101
+ def chunk_bonus
102
+ chunked = @items.group_by { |i| i.tags.first }.count { |_, group| group.size > 1 }
103
+ [chunked, Constants::CHUNK_BONUS].min
104
+ end
105
+
106
+ def evict_if_over_capacity
107
+ return unless @items.size > capacity
108
+
109
+ @items.sort_by!(&:activation)
110
+ @items.shift(@items.size - capacity)
111
+ end
112
+
113
+ def items_by_type
114
+ Constants::BUFFER_TYPES.to_h { |t| [t, @items.count { |i| i.buffer_type == t }] }
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module WorkingMemory
8
+ module Helpers
9
+ class BufferItem
10
+ attr_reader :id, :content, :buffer_type, :priority, :tags, :created_at
11
+ attr_accessor :activation, :rehearsal_count, :age_ticks
12
+
13
+ def initialize(content:, buffer_type: :episodic, priority: :normal, tags: [])
14
+ @id = SecureRandom.uuid
15
+ @content = content
16
+ @buffer_type = buffer_type
17
+ @priority = priority
18
+ @tags = tags
19
+ @activation = Constants::PRIORITY_LEVELS[priority] || 0.5
20
+ @rehearsal_count = 0
21
+ @age_ticks = 0
22
+ @created_at = Time.now.utc
23
+ end
24
+
25
+ def rehearse
26
+ @rehearsal_count += 1
27
+ @activation = [@activation + Constants::REHEARSAL_BOOST, 1.0].min
28
+ @age_ticks = 0
29
+ end
30
+
31
+ def decay
32
+ @age_ticks += 1
33
+ @activation = [@activation - Constants::DECAY_RATE, 0.0].max
34
+ end
35
+
36
+ def expired?
37
+ @age_ticks >= Constants::MAX_AGE_TICKS || @activation <= 0.0
38
+ end
39
+
40
+ def consolidation_ready?
41
+ @activation >= Constants::CONSOLIDATION_THRESHOLD
42
+ end
43
+
44
+ def interferes_with?(other)
45
+ return false unless other.is_a?(BufferItem)
46
+ return false if @buffer_type != other.buffer_type
47
+
48
+ @tags.any? { |t| other.tags.include?(t) } && (@activation - other.activation).abs < Constants::INTERFERENCE_THRESHOLD
49
+ end
50
+
51
+ def to_h
52
+ {
53
+ id: @id,
54
+ content: @content,
55
+ buffer_type: @buffer_type,
56
+ priority: @priority,
57
+ activation: @activation.round(4),
58
+ rehearsal_count: @rehearsal_count,
59
+ age_ticks: @age_ticks,
60
+ expired: expired?,
61
+ consolidation_ready: consolidation_ready?
62
+ }
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module WorkingMemory
6
+ module Helpers
7
+ module Constants
8
+ CAPACITY = 7
9
+
10
+ CHUNK_BONUS = 3
11
+
12
+ BUFFER_TYPES = %i[verbal spatial episodic].freeze
13
+
14
+ DECAY_RATE = 0.15
15
+
16
+ REHEARSAL_BOOST = 0.3
17
+
18
+ PRIORITY_LEVELS = {
19
+ critical: 1.0,
20
+ high: 0.75,
21
+ normal: 0.5,
22
+ low: 0.25,
23
+ background: 0.1
24
+ }.freeze
25
+
26
+ MAX_AGE_TICKS = 30
27
+
28
+ INTERFERENCE_THRESHOLD = 0.7
29
+
30
+ CONSOLIDATION_THRESHOLD = 0.8
31
+
32
+ LOAD_LEVELS = {
33
+ overloaded: 1.0,
34
+ high: 0.75,
35
+ moderate: 0.5,
36
+ low: 0.25,
37
+ idle: 0.0
38
+ }.freeze
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module WorkingMemory
6
+ module Runners
7
+ module WorkingMemory
8
+ include Legion::Extensions::Helpers::Lex
9
+
10
+ def buffer
11
+ @buffer ||= Helpers::Buffer.new
12
+ end
13
+
14
+ def update_working_memory(**)
15
+ buffer.tick_decay
16
+ candidates = buffer.consolidation_candidates
17
+ Legion::Logging.debug "[working_memory] tick: size=#{buffer.size} load=#{buffer.load_level} candidates=#{candidates.size}"
18
+ { success: true, status: buffer.to_h, consolidation_candidates: candidates.map(&:to_h) }
19
+ end
20
+
21
+ def store_item(content:, buffer_type: :episodic, priority: :normal, tags: [], **)
22
+ Legion::Logging.debug '[working_memory] buffer full, eviction will occur' if buffer.full?
23
+
24
+ item = buffer.store(content: content, buffer_type: buffer_type, priority: priority, tags: tags)
25
+ Legion::Logging.debug "[working_memory] stored item=#{item.id} type=#{buffer_type} priority=#{priority}"
26
+ { success: true, item: item.to_h }
27
+ end
28
+
29
+ def retrieve_item(id:, **)
30
+ item = buffer.retrieve(id)
31
+ return { success: false, reason: :not_found } unless item
32
+
33
+ { success: true, item: item.to_h }
34
+ end
35
+
36
+ def rehearse_item(id:, **)
37
+ item = buffer.rehearse(id)
38
+ return { success: false, reason: :not_found } unless item
39
+
40
+ Legion::Logging.debug "[working_memory] rehearsed item=#{id} activation=#{item.activation.round(4)}"
41
+ { success: true, item: item.to_h }
42
+ end
43
+
44
+ def retrieve_by_tag(tag:, **)
45
+ items = buffer.retrieve_by_tag(tag)
46
+ { success: true, items: items.map(&:to_h), count: items.size }
47
+ end
48
+
49
+ def retrieve_by_type(buffer_type:, **)
50
+ items = buffer.retrieve_by_type(buffer_type)
51
+ { success: true, items: items.map(&:to_h), count: items.size }
52
+ end
53
+
54
+ def remove_item(id:, **)
55
+ buffer.remove(id)
56
+ Legion::Logging.debug "[working_memory] removed item=#{id}"
57
+ { success: true }
58
+ end
59
+
60
+ def buffer_status(**)
61
+ { success: true, status: buffer.to_h }
62
+ end
63
+
64
+ def consolidation_candidates(**)
65
+ candidates = buffer.consolidation_candidates
66
+ { success: true, candidates: candidates.map(&:to_h), count: candidates.size }
67
+ end
68
+
69
+ def working_memory_stats(**)
70
+ {
71
+ success: true,
72
+ size: buffer.size,
73
+ capacity: buffer.capacity,
74
+ load: buffer.current_load.round(4),
75
+ load_level: buffer.load_level,
76
+ full: buffer.full?,
77
+ available: buffer.available_slots
78
+ }
79
+ end
80
+
81
+ def clear_buffer(**)
82
+ buffer.clear
83
+ Legion::Logging.debug '[working_memory] buffer cleared'
84
+ { success: true }
85
+ end
86
+
87
+ def find_interference(id:, **)
88
+ item = buffer.retrieve(id)
89
+ return { success: false, reason: :not_found } unless item
90
+
91
+ interfering = buffer.items.select { |other| other.id != id && item.interferes_with?(other) }
92
+ { success: true, item_id: id, interfering: interfering.map(&:to_h), count: interfering.size }
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module WorkingMemory
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'working_memory/version'
4
+ require_relative 'working_memory/helpers/constants'
5
+ require_relative 'working_memory/helpers/buffer_item'
6
+ require_relative 'working_memory/helpers/buffer'
7
+ require_relative 'working_memory/runners/working_memory'
8
+ require_relative 'working_memory/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module WorkingMemory
13
+ extend Legion::Extensions::Core if defined?(Legion::Extensions::Core)
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-working-memory
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: Short-term active maintenance of task-relevant information with capacity
27
+ limits, decay, and rehearsal
28
+ email:
29
+ - matt@legionIO.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/legion/extensions/working_memory.rb
35
+ - lib/legion/extensions/working_memory/client.rb
36
+ - lib/legion/extensions/working_memory/helpers/buffer.rb
37
+ - lib/legion/extensions/working_memory/helpers/buffer_item.rb
38
+ - lib/legion/extensions/working_memory/helpers/constants.rb
39
+ - lib/legion/extensions/working_memory/runners/working_memory.rb
40
+ - lib/legion/extensions/working_memory/version.rb
41
+ homepage: https://github.com/LegionIO/lex-working-memory
42
+ licenses:
43
+ - MIT
44
+ metadata:
45
+ rubygems_mfa_required: 'true'
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '3.4'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.6.9
61
+ specification_version: 4
62
+ summary: Working memory buffer for LegionIO cognitive agents
63
+ test_files: []