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 +7 -0
- data/lib/legion/extensions/working_memory/client.rb +17 -0
- data/lib/legion/extensions/working_memory/helpers/buffer.rb +120 -0
- data/lib/legion/extensions/working_memory/helpers/buffer_item.rb +68 -0
- data/lib/legion/extensions/working_memory/helpers/constants.rb +43 -0
- data/lib/legion/extensions/working_memory/runners/working_memory.rb +98 -0
- data/lib/legion/extensions/working_memory/version.rb +9 -0
- data/lib/legion/extensions/working_memory.rb +16 -0
- metadata +63 -0
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,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: []
|