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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -2
  3. data/lib/generators/llmemory/install/templates/create_llmemory_tables.rb +20 -0
  4. data/lib/llmemory/cli/commands/base.rb +8 -0
  5. data/lib/llmemory/cli/commands/episodic.rb +42 -0
  6. data/lib/llmemory/cli/commands/forget_log.rb +36 -0
  7. data/lib/llmemory/cli/commands/procedural.rb +44 -0
  8. data/lib/llmemory/cli/commands/working.rb +31 -0
  9. data/lib/llmemory/cli.rb +12 -0
  10. data/lib/llmemory/configuration.rb +4 -0
  11. data/lib/llmemory/long_term/episodic/memory.rb +71 -16
  12. data/lib/llmemory/long_term/episodic/storage.rb +7 -5
  13. data/lib/llmemory/long_term/episodic/storages/active_record_models.rb +17 -0
  14. data/lib/llmemory/long_term/episodic/storages/active_record_storage.rb +93 -0
  15. data/lib/llmemory/long_term/episodic/storages/database_storage.rb +135 -0
  16. data/lib/llmemory/long_term/episodic/storages/file_storage.rb +2 -3
  17. data/lib/llmemory/long_term/episodic/storages/memory_storage.rb +2 -3
  18. data/lib/llmemory/long_term/file_based/storages/active_record_storage.rb +11 -4
  19. data/lib/llmemory/long_term/file_based/storages/database_storage.rb +16 -6
  20. data/lib/llmemory/long_term/file_based/storages/file_storage.rb +2 -4
  21. data/lib/llmemory/long_term/file_based/storages/memory_storage.rb +2 -4
  22. data/lib/llmemory/long_term/graph_based/memory.rb +77 -60
  23. data/lib/llmemory/long_term/procedural/memory.rb +71 -17
  24. data/lib/llmemory/long_term/procedural/storage.rb +7 -5
  25. data/lib/llmemory/long_term/procedural/storages/active_record_models.rb +17 -0
  26. data/lib/llmemory/long_term/procedural/storages/active_record_storage.rb +104 -0
  27. data/lib/llmemory/long_term/procedural/storages/database_storage.rb +148 -0
  28. data/lib/llmemory/long_term/procedural/storages/file_storage.rb +2 -3
  29. data/lib/llmemory/long_term/procedural/storages/memory_storage.rb +2 -3
  30. data/lib/llmemory/mcp/server.rb +13 -1
  31. data/lib/llmemory/mcp/tools/memory_episode_record.rb +48 -0
  32. data/lib/llmemory/mcp/tools/memory_episodes.rb +43 -0
  33. data/lib/llmemory/mcp/tools/memory_forget.rb +53 -0
  34. data/lib/llmemory/mcp/tools/memory_retrieve.rb +10 -2
  35. data/lib/llmemory/mcp/tools/memory_skill_register.rb +35 -0
  36. data/lib/llmemory/mcp/tools/memory_skill_report.rb +35 -0
  37. data/lib/llmemory/mcp/tools/memory_skills.rb +43 -0
  38. data/lib/llmemory/memory.rb +28 -3
  39. data/lib/llmemory/retrieval/bm25_scorer.rb +1 -1
  40. data/lib/llmemory/retrieval/mmr_reranker.rb +1 -1
  41. data/lib/llmemory/short_term/session_lifecycle.rb +19 -3
  42. data/lib/llmemory/tokenizer.rb +27 -0
  43. data/lib/llmemory/vector_store/active_record_store.rb +4 -3
  44. data/lib/llmemory/vector_store.rb +14 -0
  45. data/lib/llmemory/version.rb +1 -1
  46. data/lib/llmemory.rb +1 -0
  47. 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
- @store.list_sessions(user_id: user_id).each do |session_id|
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
- @store.list_sessions(user_id: user_id).each do |session_id|
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 = @store.list_sessions(user_id: user_id)
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: "edge",
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Llmemory
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
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.0
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