llmemory 0.2.0 → 0.2.1
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 +4 -4
- data/README.md +7 -2
- data/lib/generators/llmemory/install/templates/create_llmemory_tables.rb +20 -0
- data/lib/llmemory/cli/commands/base.rb +8 -0
- data/lib/llmemory/cli/commands/episodic.rb +42 -0
- data/lib/llmemory/cli/commands/forget_log.rb +36 -0
- data/lib/llmemory/cli/commands/procedural.rb +44 -0
- data/lib/llmemory/cli/commands/working.rb +31 -0
- data/lib/llmemory/cli.rb +12 -0
- data/lib/llmemory/configuration.rb +4 -0
- data/lib/llmemory/long_term/episodic/memory.rb +71 -16
- data/lib/llmemory/long_term/episodic/storage.rb +7 -5
- data/lib/llmemory/long_term/episodic/storages/active_record_models.rb +17 -0
- data/lib/llmemory/long_term/episodic/storages/active_record_storage.rb +93 -0
- data/lib/llmemory/long_term/episodic/storages/database_storage.rb +135 -0
- data/lib/llmemory/long_term/episodic/storages/file_storage.rb +2 -3
- data/lib/llmemory/long_term/episodic/storages/memory_storage.rb +2 -3
- data/lib/llmemory/long_term/file_based/storages/active_record_storage.rb +11 -4
- data/lib/llmemory/long_term/file_based/storages/database_storage.rb +16 -6
- data/lib/llmemory/long_term/file_based/storages/file_storage.rb +2 -4
- data/lib/llmemory/long_term/file_based/storages/memory_storage.rb +2 -4
- data/lib/llmemory/long_term/graph_based/memory.rb +77 -60
- data/lib/llmemory/long_term/procedural/memory.rb +71 -17
- data/lib/llmemory/long_term/procedural/storage.rb +7 -5
- data/lib/llmemory/long_term/procedural/storages/active_record_models.rb +17 -0
- data/lib/llmemory/long_term/procedural/storages/active_record_storage.rb +104 -0
- data/lib/llmemory/long_term/procedural/storages/database_storage.rb +148 -0
- data/lib/llmemory/long_term/procedural/storages/file_storage.rb +2 -3
- data/lib/llmemory/long_term/procedural/storages/memory_storage.rb +2 -3
- data/lib/llmemory/mcp/server.rb +13 -1
- data/lib/llmemory/mcp/tools/memory_episode_record.rb +48 -0
- data/lib/llmemory/mcp/tools/memory_episodes.rb +43 -0
- data/lib/llmemory/mcp/tools/memory_forget.rb +53 -0
- data/lib/llmemory/mcp/tools/memory_retrieve.rb +10 -2
- data/lib/llmemory/mcp/tools/memory_skill_register.rb +35 -0
- data/lib/llmemory/mcp/tools/memory_skill_report.rb +35 -0
- data/lib/llmemory/mcp/tools/memory_skills.rb +43 -0
- data/lib/llmemory/memory.rb +28 -3
- data/lib/llmemory/retrieval/bm25_scorer.rb +1 -1
- data/lib/llmemory/retrieval/mmr_reranker.rb +1 -1
- data/lib/llmemory/short_term/session_lifecycle.rb +19 -3
- data/lib/llmemory/tokenizer.rb +27 -0
- data/lib/llmemory/vector_store/active_record_store.rb +4 -3
- data/lib/llmemory/vector_store.rb +14 -0
- data/lib/llmemory/version.rb +1 -1
- data/lib/llmemory.rb +1 -0
- metadata +18 -1
|
@@ -5,6 +5,18 @@ require_relative "stores"
|
|
|
5
5
|
module Llmemory
|
|
6
6
|
module ShortTerm
|
|
7
7
|
class SessionLifecycle
|
|
8
|
+
# Pseudo-sessions used by ForgetLog, FeedbackStore and WorkingMemory share
|
|
9
|
+
# the short-term K/V store but are not user sessions — they must not be
|
|
10
|
+
# idle-pruned, stale-pruned, or evicted by enforce_max_entries.
|
|
11
|
+
PSEUDO_SESSION_PATTERNS = [
|
|
12
|
+
/\A__[a-z_]+__\z/, # e.g. "__forget_log__", "__retrieval_feedback__"
|
|
13
|
+
/:working_memory\z/ # WorkingMemory uses "<session>:working_memory"
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
def self.pseudo_session?(session_id)
|
|
17
|
+
PSEUDO_SESSION_PATTERNS.any? { |p| session_id.to_s.match?(p) }
|
|
18
|
+
end
|
|
19
|
+
|
|
8
20
|
def initialize(store: nil)
|
|
9
21
|
@store = store || build_store
|
|
10
22
|
end
|
|
@@ -14,7 +26,7 @@ module Llmemory
|
|
|
14
26
|
cutoff = Time.now - (idle_minutes * 60)
|
|
15
27
|
deleted = 0
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
user_sessions(user_id).each do |session_id|
|
|
18
30
|
state = @store.load(user_id, session_id)
|
|
19
31
|
next unless state.is_a?(Hash)
|
|
20
32
|
|
|
@@ -36,7 +48,7 @@ module Llmemory
|
|
|
36
48
|
cutoff = Time.now - (prune_after_days * 86400)
|
|
37
49
|
deleted = 0
|
|
38
50
|
|
|
39
|
-
|
|
51
|
+
user_sessions(user_id).each do |session_id|
|
|
40
52
|
state = @store.load(user_id, session_id)
|
|
41
53
|
next unless state.is_a?(Hash)
|
|
42
54
|
|
|
@@ -55,7 +67,7 @@ module Llmemory
|
|
|
55
67
|
|
|
56
68
|
def enforce_max_entries!(user_id:, max_entries: nil)
|
|
57
69
|
max_entries ||= Llmemory.configuration.session_max_entries_per_user
|
|
58
|
-
sessions =
|
|
70
|
+
sessions = user_sessions(user_id)
|
|
59
71
|
return 0 if sessions.size <= max_entries
|
|
60
72
|
|
|
61
73
|
session_ages = sessions.map do |session_id|
|
|
@@ -73,6 +85,10 @@ module Llmemory
|
|
|
73
85
|
|
|
74
86
|
private
|
|
75
87
|
|
|
88
|
+
def user_sessions(user_id)
|
|
89
|
+
@store.list_sessions(user_id: user_id).reject { |s| self.class.pseudo_session?(s) }
|
|
90
|
+
end
|
|
91
|
+
|
|
76
92
|
def build_store
|
|
77
93
|
Stores.build
|
|
78
94
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Llmemory
|
|
4
|
+
# Shared word tokenizer for keyword search and lexical scoring (BM25, MMR).
|
|
5
|
+
# Centralizes the tokenization regex that was duplicated across the codebase.
|
|
6
|
+
module Tokenizer
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
WORD = /\b[a-z0-9]{2,}\b/
|
|
10
|
+
|
|
11
|
+
def tokenize(text)
|
|
12
|
+
text.to_s.downcase.scan(WORD)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Lexical match used by storage-level keyword search. A query is split into
|
|
16
|
+
# tokens and matched as an OR of per-token substrings, so multi-word queries
|
|
17
|
+
# work (a single contiguous substring of the whole query is no longer
|
|
18
|
+
# required) while single-term/partial matches are preserved. An empty query
|
|
19
|
+
# (no tokens) matches everything, keeping prior "return all" behavior.
|
|
20
|
+
def matches?(text, query)
|
|
21
|
+
tokens = tokenize(query)
|
|
22
|
+
return true if tokens.empty?
|
|
23
|
+
haystack = text.to_s.downcase
|
|
24
|
+
tokens.any? { |t| haystack.include?(t) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -7,9 +7,10 @@ module Llmemory
|
|
|
7
7
|
# Persists embeddings in llmemory_embeddings (pgvector).
|
|
8
8
|
# Use when long_term_store is :active_record so hybrid search finds persisted embeddings.
|
|
9
9
|
class ActiveRecordStore < Base
|
|
10
|
-
def initialize(embedding_provider: nil)
|
|
10
|
+
def initialize(embedding_provider: nil, source_type: "edge")
|
|
11
11
|
self.class.load_model!
|
|
12
12
|
@embedding_provider = embedding_provider
|
|
13
|
+
@source_type = source_type.to_s
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def self.load_model!
|
|
@@ -29,7 +30,7 @@ module Llmemory
|
|
|
29
30
|
text_content = (metadata || {}).dig("text") || (metadata || {}).dig(:text)
|
|
30
31
|
rec = Llmemory::VectorStore::ActiveRecordEmbedding.find_or_initialize_by(
|
|
31
32
|
user_id: user_id.to_s,
|
|
32
|
-
source_type:
|
|
33
|
+
source_type: @source_type,
|
|
33
34
|
source_id: id.to_s
|
|
34
35
|
)
|
|
35
36
|
rec.embedding = embedding.to_a.map(&:to_f)
|
|
@@ -46,7 +47,7 @@ module Llmemory
|
|
|
46
47
|
sanitized_vec = vec.map { |v| v.finite? ? v : 0.0 }
|
|
47
48
|
vector_literal = "[#{sanitized_vec.join(',')}]"
|
|
48
49
|
# pgvector cosine distance <=> (0 = same, 2 = opposite); score = 1 - distance for similarity
|
|
49
|
-
scope = Llmemory::VectorStore::ActiveRecordEmbedding.where(user_id: user_id.to_s)
|
|
50
|
+
scope = Llmemory::VectorStore::ActiveRecordEmbedding.where(user_id: user_id.to_s, source_type: @source_type)
|
|
50
51
|
rows = scope.select(
|
|
51
52
|
Llmemory::VectorStore::ActiveRecordEmbedding.arel_table[Arel.star],
|
|
52
53
|
Arel.sql("(embedding <=> '#{vector_literal}'::vector) AS distance")
|
|
@@ -6,5 +6,19 @@ require_relative "vector_store/memory_store"
|
|
|
6
6
|
|
|
7
7
|
module Llmemory
|
|
8
8
|
module VectorStore
|
|
9
|
+
# Builds a vector store wired to OpenAI embeddings, selecting the backend
|
|
10
|
+
# from config (:active_record persists in llmemory_embeddings; otherwise
|
|
11
|
+
# in-process). `source_type` namespaces persisted embeddings so different
|
|
12
|
+
# memory types (edges, episodes, skills) never collide in the shared table.
|
|
13
|
+
def self.build(source_type: "edge")
|
|
14
|
+
embeddings = OpenAIEmbeddings.new
|
|
15
|
+
store_type = (Llmemory.configuration.long_term_store || :memory).to_s.to_sym
|
|
16
|
+
if store_type == :active_record || store_type == :activerecord
|
|
17
|
+
require_relative "vector_store/active_record_store"
|
|
18
|
+
ActiveRecordStore.new(embedding_provider: embeddings, source_type: source_type)
|
|
19
|
+
else
|
|
20
|
+
MemoryStore.new(embedding_provider: embeddings)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
9
23
|
end
|
|
10
24
|
end
|
data/lib/llmemory/version.rb
CHANGED
data/lib/llmemory.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "llmemory/version"
|
|
4
4
|
require_relative "llmemory/configuration"
|
|
5
5
|
require_relative "llmemory/provenance"
|
|
6
|
+
require_relative "llmemory/tokenizer"
|
|
6
7
|
require_relative "llmemory/memory_module"
|
|
7
8
|
require_relative "llmemory/forget_log"
|
|
8
9
|
require_relative "llmemory/llm"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llmemory
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- llmemory
|
|
@@ -131,6 +131,8 @@ files:
|
|
|
131
131
|
- lib/llmemory/actions/reason.rb
|
|
132
132
|
- lib/llmemory/cli.rb
|
|
133
133
|
- lib/llmemory/cli/commands/base.rb
|
|
134
|
+
- lib/llmemory/cli/commands/episodic.rb
|
|
135
|
+
- lib/llmemory/cli/commands/forget_log.rb
|
|
134
136
|
- lib/llmemory/cli/commands/long_term.rb
|
|
135
137
|
- lib/llmemory/cli/commands/long_term/categories.rb
|
|
136
138
|
- lib/llmemory/cli/commands/long_term/edges.rb
|
|
@@ -139,10 +141,12 @@ files:
|
|
|
139
141
|
- lib/llmemory/cli/commands/long_term/nodes.rb
|
|
140
142
|
- lib/llmemory/cli/commands/long_term/resources.rb
|
|
141
143
|
- lib/llmemory/cli/commands/mcp.rb
|
|
144
|
+
- lib/llmemory/cli/commands/procedural.rb
|
|
142
145
|
- lib/llmemory/cli/commands/search.rb
|
|
143
146
|
- lib/llmemory/cli/commands/short_term.rb
|
|
144
147
|
- lib/llmemory/cli/commands/stats.rb
|
|
145
148
|
- lib/llmemory/cli/commands/users.rb
|
|
149
|
+
- lib/llmemory/cli/commands/working.rb
|
|
146
150
|
- lib/llmemory/configuration.rb
|
|
147
151
|
- lib/llmemory/dashboard.rb
|
|
148
152
|
- lib/llmemory/dashboard/engine.rb
|
|
@@ -159,7 +163,10 @@ files:
|
|
|
159
163
|
- lib/llmemory/long_term/episodic/episode.rb
|
|
160
164
|
- lib/llmemory/long_term/episodic/memory.rb
|
|
161
165
|
- lib/llmemory/long_term/episodic/storage.rb
|
|
166
|
+
- lib/llmemory/long_term/episodic/storages/active_record_models.rb
|
|
167
|
+
- lib/llmemory/long_term/episodic/storages/active_record_storage.rb
|
|
162
168
|
- lib/llmemory/long_term/episodic/storages/base.rb
|
|
169
|
+
- lib/llmemory/long_term/episodic/storages/database_storage.rb
|
|
163
170
|
- lib/llmemory/long_term/episodic/storages/file_storage.rb
|
|
164
171
|
- lib/llmemory/long_term/episodic/storages/memory_storage.rb
|
|
165
172
|
- lib/llmemory/long_term/file_based.rb
|
|
@@ -190,7 +197,10 @@ files:
|
|
|
190
197
|
- lib/llmemory/long_term/procedural/memory.rb
|
|
191
198
|
- lib/llmemory/long_term/procedural/skill.rb
|
|
192
199
|
- lib/llmemory/long_term/procedural/storage.rb
|
|
200
|
+
- lib/llmemory/long_term/procedural/storages/active_record_models.rb
|
|
201
|
+
- lib/llmemory/long_term/procedural/storages/active_record_storage.rb
|
|
193
202
|
- lib/llmemory/long_term/procedural/storages/base.rb
|
|
203
|
+
- lib/llmemory/long_term/procedural/storages/database_storage.rb
|
|
194
204
|
- lib/llmemory/long_term/procedural/storages/file_storage.rb
|
|
195
205
|
- lib/llmemory/long_term/procedural/storages/memory_storage.rb
|
|
196
206
|
- lib/llmemory/maintenance.rb
|
|
@@ -203,10 +213,16 @@ files:
|
|
|
203
213
|
- lib/llmemory/mcp/server.rb
|
|
204
214
|
- lib/llmemory/mcp/tools/memory_add_message.rb
|
|
205
215
|
- lib/llmemory/mcp/tools/memory_consolidate.rb
|
|
216
|
+
- lib/llmemory/mcp/tools/memory_episode_record.rb
|
|
217
|
+
- lib/llmemory/mcp/tools/memory_episodes.rb
|
|
218
|
+
- lib/llmemory/mcp/tools/memory_forget.rb
|
|
206
219
|
- lib/llmemory/mcp/tools/memory_info.rb
|
|
207
220
|
- lib/llmemory/mcp/tools/memory_retrieve.rb
|
|
208
221
|
- lib/llmemory/mcp/tools/memory_save.rb
|
|
209
222
|
- lib/llmemory/mcp/tools/memory_search.rb
|
|
223
|
+
- lib/llmemory/mcp/tools/memory_skill_register.rb
|
|
224
|
+
- lib/llmemory/mcp/tools/memory_skill_report.rb
|
|
225
|
+
- lib/llmemory/mcp/tools/memory_skills.rb
|
|
210
226
|
- lib/llmemory/mcp/tools/memory_stats.rb
|
|
211
227
|
- lib/llmemory/mcp/tools/memory_timeline.rb
|
|
212
228
|
- lib/llmemory/mcp/tools/memory_timeline_context.rb
|
|
@@ -235,6 +251,7 @@ files:
|
|
|
235
251
|
- lib/llmemory/short_term/stores/memory_store.rb
|
|
236
252
|
- lib/llmemory/short_term/stores/postgres_store.rb
|
|
237
253
|
- lib/llmemory/short_term/stores/redis_store.rb
|
|
254
|
+
- lib/llmemory/tokenizer.rb
|
|
238
255
|
- lib/llmemory/vector_store.rb
|
|
239
256
|
- lib/llmemory/vector_store/active_record_embedding.rb
|
|
240
257
|
- lib/llmemory/vector_store/active_record_store.rb
|