llmemory 0.2.0 → 0.2.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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +54 -3
  3. data/app/controllers/llmemory/dashboard/application_controller.rb +15 -1
  4. data/app/controllers/llmemory/dashboard/episodic_controller.rb +22 -0
  5. data/app/controllers/llmemory/dashboard/forget_log_controller.rb +12 -0
  6. data/app/controllers/llmemory/dashboard/maintenance_controller.rb +92 -0
  7. data/app/controllers/llmemory/dashboard/procedural_controller.rb +22 -0
  8. data/app/controllers/llmemory/dashboard/reflection_controller.rb +37 -0
  9. data/app/controllers/llmemory/dashboard/working_controller.rb +14 -0
  10. data/app/views/llmemory/dashboard/episodic/index.html.erb +37 -0
  11. data/app/views/llmemory/dashboard/forget_log/show.html.erb +23 -0
  12. data/app/views/llmemory/dashboard/maintenance/show.html.erb +65 -0
  13. data/app/views/llmemory/dashboard/procedural/index.html.erb +38 -0
  14. data/app/views/llmemory/dashboard/reflection/show.html.erb +29 -0
  15. data/app/views/llmemory/dashboard/users/show.html.erb +16 -0
  16. data/app/views/llmemory/dashboard/working/show.html.erb +20 -0
  17. data/config/routes.rb +14 -0
  18. data/lib/generators/llmemory/install/templates/create_llmemory_tables.rb +22 -0
  19. data/lib/llmemory/cli/commands/base.rb +8 -0
  20. data/lib/llmemory/cli/commands/episodic.rb +42 -0
  21. data/lib/llmemory/cli/commands/forget_log.rb +36 -0
  22. data/lib/llmemory/cli/commands/maintain.rb +62 -0
  23. data/lib/llmemory/cli/commands/mine_skills.rb +50 -0
  24. data/lib/llmemory/cli/commands/procedural.rb +44 -0
  25. data/lib/llmemory/cli/commands/working.rb +31 -0
  26. data/lib/llmemory/cli.rb +18 -0
  27. data/lib/llmemory/configuration.rb +11 -1
  28. data/lib/llmemory/instrumentation.rb +33 -0
  29. data/lib/llmemory/llm/anthropic.rb +19 -15
  30. data/lib/llmemory/llm/openai.rb +16 -12
  31. data/lib/llmemory/long_term/episodic/memory.rb +94 -26
  32. data/lib/llmemory/long_term/episodic/storage.rb +7 -5
  33. data/lib/llmemory/long_term/episodic/storages/active_record_models.rb +17 -0
  34. data/lib/llmemory/long_term/episodic/storages/active_record_storage.rb +103 -0
  35. data/lib/llmemory/long_term/episodic/storages/base.rb +15 -2
  36. data/lib/llmemory/long_term/episodic/storages/database_storage.rb +156 -0
  37. data/lib/llmemory/long_term/episodic/storages/file_storage.rb +28 -8
  38. data/lib/llmemory/long_term/episodic/storages/memory_storage.rb +36 -6
  39. data/lib/llmemory/long_term/file_based/memory.rb +12 -4
  40. data/lib/llmemory/long_term/file_based/storages/active_record_storage.rb +15 -6
  41. data/lib/llmemory/long_term/file_based/storages/base.rb +2 -2
  42. data/lib/llmemory/long_term/file_based/storages/database_storage.rb +20 -8
  43. data/lib/llmemory/long_term/file_based/storages/file_storage.rb +6 -6
  44. data/lib/llmemory/long_term/file_based/storages/memory_storage.rb +6 -6
  45. data/lib/llmemory/long_term/graph_based/memory.rb +89 -64
  46. data/lib/llmemory/long_term/graph_based/storages/active_record_storage.rb +4 -2
  47. data/lib/llmemory/long_term/graph_based/storages/base.rb +2 -2
  48. data/lib/llmemory/long_term/graph_based/storages/memory_storage.rb +4 -2
  49. data/lib/llmemory/long_term/procedural/memory.rb +97 -30
  50. data/lib/llmemory/long_term/procedural/skill.rb +6 -2
  51. data/lib/llmemory/long_term/procedural/storage.rb +7 -5
  52. data/lib/llmemory/long_term/procedural/storages/active_record_models.rb +17 -0
  53. data/lib/llmemory/long_term/procedural/storages/active_record_storage.rb +114 -0
  54. data/lib/llmemory/long_term/procedural/storages/base.rb +14 -1
  55. data/lib/llmemory/long_term/procedural/storages/database_storage.rb +169 -0
  56. data/lib/llmemory/long_term/procedural/storages/file_storage.rb +29 -9
  57. data/lib/llmemory/long_term/procedural/storages/memory_storage.rb +37 -7
  58. data/lib/llmemory/maintenance/cognitive_pass.rb +109 -0
  59. data/lib/llmemory/maintenance/ttl_expiry.rb +50 -0
  60. data/lib/llmemory/maintenance.rb +2 -0
  61. data/lib/llmemory/mcp/server.rb +17 -1
  62. data/lib/llmemory/mcp/tools/memory_episode_record.rb +48 -0
  63. data/lib/llmemory/mcp/tools/memory_episodes.rb +43 -0
  64. data/lib/llmemory/mcp/tools/memory_forget.rb +53 -0
  65. data/lib/llmemory/mcp/tools/memory_maintain.rb +53 -0
  66. data/lib/llmemory/mcp/tools/memory_mine_skills.rb +53 -0
  67. data/lib/llmemory/mcp/tools/memory_retrieve.rb +10 -2
  68. data/lib/llmemory/mcp/tools/memory_skill_register.rb +35 -0
  69. data/lib/llmemory/mcp/tools/memory_skill_report.rb +35 -0
  70. data/lib/llmemory/mcp/tools/memory_skills.rb +43 -0
  71. data/lib/llmemory/memory.rb +48 -3
  72. data/lib/llmemory/memory_module.rb +13 -6
  73. data/lib/llmemory/reflection/reflector.rb +24 -20
  74. data/lib/llmemory/retrieval/bm25_scorer.rb +1 -1
  75. data/lib/llmemory/retrieval/engine.rb +25 -16
  76. data/lib/llmemory/retrieval/mmr_reranker.rb +1 -1
  77. data/lib/llmemory/short_term/session_lifecycle.rb +19 -3
  78. data/lib/llmemory/skill_mining/miner.rb +163 -0
  79. data/lib/llmemory/skill_mining.rb +8 -0
  80. data/lib/llmemory/tokenizer.rb +27 -0
  81. data/lib/llmemory/vector_store/active_record_store.rb +4 -3
  82. data/lib/llmemory/vector_store/openai_embeddings.rb +11 -7
  83. data/lib/llmemory/vector_store.rb +14 -0
  84. data/lib/llmemory/version.rb +1 -1
  85. data/lib/llmemory.rb +3 -0
  86. metadata +39 -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,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Llmemory
6
+ module SkillMining
7
+ # Skill mining scans an agent's recent episodes (episodic memory) for
8
+ # repeated, successful trajectories and distills them into reusable skills
9
+ # (procedural memory). This is Voyager's actual contribution: rather than a
10
+ # passive, hand-written skill library, procedural memory grows from lived
11
+ # experience.
12
+ #
13
+ # Mining is human-in-the-loop by default: `mine` returns skill *proposals*
14
+ # and writes nothing. Pass `auto_register: true` to register them directly.
15
+ # Each registered skill carries provenance { method: "skill_mining",
16
+ # sources: [{ type: "episode", id: ... }] } so it stays traceable to the
17
+ # experiences it was distilled from.
18
+ #
19
+ # `procedural` must respond to:
20
+ # register_skill(name:, body:, description:, kind:, provenance:)
21
+ class Miner
22
+ DEFAULT_WINDOW = 20
23
+ DEFAULT_CONFIDENCE = 0.5
24
+ VALID_KINDS = %w[prompt template code].freeze
25
+
26
+ def initialize(episodic:, procedural:, llm: nil)
27
+ @episodic = episodic
28
+ @procedural = procedural
29
+ @llm = llm || Llmemory::LLM.client
30
+ end
31
+
32
+ # Mines the most recent `window` episodes for reusable skills. When
33
+ # `outcomes` (an allowlist of outcome labels) is given, only episodes whose
34
+ # outcome is in the set are considered — a deterministic pre-filter.
35
+ #
36
+ # Returns an array of proposal hashes
37
+ # ({ name:, kind:, body:, description:, confidence: }). When
38
+ # `auto_register: true`, registers each proposal and returns the new skill
39
+ # ids instead.
40
+ def mine(window: DEFAULT_WINDOW, outcomes: nil, auto_register: false)
41
+ result = []
42
+ Llmemory::Instrumentation.instrument(:mine_skills, window: window, auto_register: auto_register) do
43
+ episodes = @episodic.recent_episodes(limit: window)
44
+ episodes = filter_by_outcome(episodes, outcomes) if outcomes
45
+ next if episodes.empty?
46
+
47
+ proposals = distill(episodes)
48
+ next if proposals.empty?
49
+
50
+ result = auto_register ? register(proposals, episodes) : proposals
51
+ end
52
+ result
53
+ end
54
+
55
+ private
56
+
57
+ def filter_by_outcome(episodes, outcomes)
58
+ allowed = Array(outcomes).map { |o| o.to_s.strip.downcase }
59
+ episodes.select { |ep| allowed.include?(ep.outcome.to_s.strip.downcase) }
60
+ end
61
+
62
+ def register(proposals, episodes)
63
+ sources = episodes.map(&:id).compact.map { |id| { type: "episode", id: id } }
64
+ proposals.map do |p|
65
+ provenance = Llmemory::Provenance.build(
66
+ method: "skill_mining",
67
+ sources: sources,
68
+ confidence: p[:confidence]
69
+ )
70
+ @procedural.register_skill(
71
+ name: p[:name],
72
+ body: p[:body],
73
+ description: p[:description],
74
+ kind: p[:kind],
75
+ provenance: provenance
76
+ )
77
+ end
78
+ end
79
+
80
+ def distill(episodes)
81
+ response = @llm.invoke(build_prompt(episodes))
82
+ parse_proposals(response)
83
+ rescue Llmemory::LLMError
84
+ []
85
+ end
86
+
87
+ def build_prompt(episodes)
88
+ episodes_text = episodes.each_with_index.map do |ep, i|
89
+ "Episode #{i + 1} (outcome: #{ep.outcome || 'n/a'}):\n#{ep.searchable_text}"
90
+ end.join("\n\n")
91
+
92
+ <<~PROMPT
93
+ You are mining an agent's recent experiences for reusable skills. A skill
94
+ is a repeatable procedure the agent can apply again: a prompt, a template,
95
+ or a snippet of code. Only propose a skill when you see a SUCCESSFUL
96
+ pattern that recurs across episodes — generalize the steps into a reusable
97
+ procedure. Do not propose one-off actions or failures.
98
+
99
+ Recent episodes:
100
+ #{episodes_text}
101
+
102
+ Return a JSON array of objects with keys:
103
+ "name" (short snake_case identifier),
104
+ "kind" (one of "prompt", "template", "code"),
105
+ "body" (the reusable procedure itself),
106
+ "description" (one sentence on when to apply it),
107
+ "confidence" (0-1).
108
+ Return an empty array if no reusable skill can be distilled.
109
+ Example: [{"name": "rollback_on_deploy_failure", "kind": "prompt",
110
+ "body": "When a deploy fails, roll back to the last known-good release.",
111
+ "description": "Recover service after a failed deploy", "confidence": 0.8}]
112
+ PROMPT
113
+ end
114
+
115
+ def parse_proposals(response)
116
+ json = extract_json_array(response)
117
+ return [] unless json
118
+
119
+ json.filter_map do |item|
120
+ next nil unless item.is_a?(Hash)
121
+ name = (item["name"] || item[:name]).to_s.strip
122
+ body = (item["body"] || item[:body]).to_s.strip
123
+ next nil if name.empty? || body.empty?
124
+
125
+ {
126
+ name: name,
127
+ kind: normalize_kind(item["kind"] || item[:kind]),
128
+ body: body,
129
+ description: presence(item["description"] || item[:description]),
130
+ confidence: normalize_confidence(item["confidence"] || item[:confidence])
131
+ }
132
+ end
133
+ end
134
+
135
+ def normalize_kind(value)
136
+ k = value.to_s.strip.downcase
137
+ VALID_KINDS.include?(k) ? k : "prompt"
138
+ end
139
+
140
+ def normalize_confidence(value)
141
+ return DEFAULT_CONFIDENCE if value.nil?
142
+ v = value.to_f
143
+ v.between?(0, 1) ? v : DEFAULT_CONFIDENCE
144
+ end
145
+
146
+ def presence(value)
147
+ s = value.to_s.strip
148
+ s.empty? ? nil : s
149
+ end
150
+
151
+ def extract_json_array(response)
152
+ response = response.to_s.strip
153
+ start_idx = response.index("[")
154
+ end_idx = response.rindex("]")
155
+ return nil unless start_idx && end_idx
156
+
157
+ JSON.parse(response[start_idx..end_idx])
158
+ rescue JSON::ParserError
159
+ nil
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "skill_mining/miner"
4
+
5
+ module Llmemory
6
+ module SkillMining
7
+ end
8
+ 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")
@@ -54,14 +54,18 @@ module Llmemory
54
54
  end
55
55
 
56
56
  def fetch_embedding(text)
57
- response = connection.post("embeddings") do |req|
58
- req.headers["Authorization"] = "Bearer #{@api_key}"
59
- req.headers["Content-Type"] = "application/json"
60
- req.body = { input: text.to_s.strip, model: @model }.to_json
57
+ result = nil
58
+ Llmemory::Instrumentation.instrument(:llm_embed, provider: :openai, model: @model, text_chars: text.to_s.length) do
59
+ response = connection.post("embeddings") do |req|
60
+ req.headers["Authorization"] = "Bearer #{@api_key}"
61
+ req.headers["Content-Type"] = "application/json"
62
+ req.body = { input: text.to_s.strip, model: @model }.to_json
63
+ end
64
+ raise Llmemory::LLMError, "OpenAI Embeddings API error: #{response.body}" unless response.success?
65
+ body = response.body.is_a?(Hash) ? response.body : JSON.parse(response.body.to_s)
66
+ result = body.dig("data", 0, "embedding")&.map(&:to_f) || Array.new(DEFAULT_DIMS, 0.0)
61
67
  end
62
- raise Llmemory::LLMError, "OpenAI Embeddings API error: #{response.body}" unless response.success?
63
- body = response.body.is_a?(Hash) ? response.body : JSON.parse(response.body.to_s)
64
- body.dig("data", 0, "embedding")&.map(&:to_f) || Array.new(DEFAULT_DIMS, 0.0)
68
+ result
65
69
  end
66
70
 
67
71
  def connection
@@ -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.2"
5
5
  end
data/lib/llmemory.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require_relative "llmemory/version"
4
4
  require_relative "llmemory/configuration"
5
5
  require_relative "llmemory/provenance"
6
+ require_relative "llmemory/tokenizer"
7
+ require_relative "llmemory/instrumentation"
6
8
  require_relative "llmemory/memory_module"
7
9
  require_relative "llmemory/forget_log"
8
10
  require_relative "llmemory/llm"
@@ -14,6 +16,7 @@ require_relative "llmemory/vector_store"
14
16
  require_relative "llmemory/maintenance"
15
17
  require_relative "llmemory/extractors"
16
18
  require_relative "llmemory/reflection"
19
+ require_relative "llmemory/skill_mining"
17
20
  require_relative "llmemory/actions"
18
21
  require_relative "llmemory/memory"
19
22
 
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.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - llmemory
@@ -106,21 +106,33 @@ files:
106
106
  - LICENSE.txt
107
107
  - README.md
108
108
  - app/controllers/llmemory/dashboard/application_controller.rb
109
+ - app/controllers/llmemory/dashboard/episodic_controller.rb
110
+ - app/controllers/llmemory/dashboard/forget_log_controller.rb
109
111
  - app/controllers/llmemory/dashboard/graph_controller.rb
110
112
  - app/controllers/llmemory/dashboard/long_term_controller.rb
113
+ - app/controllers/llmemory/dashboard/maintenance_controller.rb
114
+ - app/controllers/llmemory/dashboard/procedural_controller.rb
115
+ - app/controllers/llmemory/dashboard/reflection_controller.rb
111
116
  - app/controllers/llmemory/dashboard/search_controller.rb
112
117
  - app/controllers/llmemory/dashboard/short_term_controller.rb
113
118
  - app/controllers/llmemory/dashboard/stats_controller.rb
114
119
  - app/controllers/llmemory/dashboard/users_controller.rb
120
+ - app/controllers/llmemory/dashboard/working_controller.rb
115
121
  - app/views/layouts/application.html.erb
122
+ - app/views/llmemory/dashboard/episodic/index.html.erb
123
+ - app/views/llmemory/dashboard/forget_log/show.html.erb
116
124
  - app/views/llmemory/dashboard/graph/index.html.erb
117
125
  - app/views/llmemory/dashboard/long_term/categories.html.erb
118
126
  - app/views/llmemory/dashboard/long_term/index.html.erb
127
+ - app/views/llmemory/dashboard/maintenance/show.html.erb
128
+ - app/views/llmemory/dashboard/procedural/index.html.erb
129
+ - app/views/llmemory/dashboard/reflection/show.html.erb
119
130
  - app/views/llmemory/dashboard/search/index.html.erb
120
131
  - app/views/llmemory/dashboard/short_term/show.html.erb
121
132
  - app/views/llmemory/dashboard/stats/index.html.erb
122
133
  - app/views/llmemory/dashboard/users/index.html.erb
123
134
  - app/views/llmemory/dashboard/users/show.html.erb
135
+ - app/views/llmemory/dashboard/working/show.html.erb
124
136
  - config/routes.rb
125
137
  - exe/llmemory
126
138
  - exe/llmemory-mcp
@@ -131,6 +143,8 @@ files:
131
143
  - lib/llmemory/actions/reason.rb
132
144
  - lib/llmemory/cli.rb
133
145
  - lib/llmemory/cli/commands/base.rb
146
+ - lib/llmemory/cli/commands/episodic.rb
147
+ - lib/llmemory/cli/commands/forget_log.rb
134
148
  - lib/llmemory/cli/commands/long_term.rb
135
149
  - lib/llmemory/cli/commands/long_term/categories.rb
136
150
  - lib/llmemory/cli/commands/long_term/edges.rb
@@ -138,11 +152,15 @@ files:
138
152
  - lib/llmemory/cli/commands/long_term/graph.rb
139
153
  - lib/llmemory/cli/commands/long_term/nodes.rb
140
154
  - lib/llmemory/cli/commands/long_term/resources.rb
155
+ - lib/llmemory/cli/commands/maintain.rb
141
156
  - lib/llmemory/cli/commands/mcp.rb
157
+ - lib/llmemory/cli/commands/mine_skills.rb
158
+ - lib/llmemory/cli/commands/procedural.rb
142
159
  - lib/llmemory/cli/commands/search.rb
143
160
  - lib/llmemory/cli/commands/short_term.rb
144
161
  - lib/llmemory/cli/commands/stats.rb
145
162
  - lib/llmemory/cli/commands/users.rb
163
+ - lib/llmemory/cli/commands/working.rb
146
164
  - lib/llmemory/configuration.rb
147
165
  - lib/llmemory/dashboard.rb
148
166
  - lib/llmemory/dashboard/engine.rb
@@ -150,6 +168,7 @@ files:
150
168
  - lib/llmemory/extractors/entity_relation_extractor.rb
151
169
  - lib/llmemory/extractors/fact_extractor.rb
152
170
  - lib/llmemory/forget_log.rb
171
+ - lib/llmemory/instrumentation.rb
153
172
  - lib/llmemory/llm.rb
154
173
  - lib/llmemory/llm/anthropic.rb
155
174
  - lib/llmemory/llm/base.rb
@@ -159,7 +178,10 @@ files:
159
178
  - lib/llmemory/long_term/episodic/episode.rb
160
179
  - lib/llmemory/long_term/episodic/memory.rb
161
180
  - lib/llmemory/long_term/episodic/storage.rb
181
+ - lib/llmemory/long_term/episodic/storages/active_record_models.rb
182
+ - lib/llmemory/long_term/episodic/storages/active_record_storage.rb
162
183
  - lib/llmemory/long_term/episodic/storages/base.rb
184
+ - lib/llmemory/long_term/episodic/storages/database_storage.rb
163
185
  - lib/llmemory/long_term/episodic/storages/file_storage.rb
164
186
  - lib/llmemory/long_term/episodic/storages/memory_storage.rb
165
187
  - lib/llmemory/long_term/file_based.rb
@@ -190,23 +212,36 @@ files:
190
212
  - lib/llmemory/long_term/procedural/memory.rb
191
213
  - lib/llmemory/long_term/procedural/skill.rb
192
214
  - lib/llmemory/long_term/procedural/storage.rb
215
+ - lib/llmemory/long_term/procedural/storages/active_record_models.rb
216
+ - lib/llmemory/long_term/procedural/storages/active_record_storage.rb
193
217
  - lib/llmemory/long_term/procedural/storages/base.rb
218
+ - lib/llmemory/long_term/procedural/storages/database_storage.rb
194
219
  - lib/llmemory/long_term/procedural/storages/file_storage.rb
195
220
  - lib/llmemory/long_term/procedural/storages/memory_storage.rb
196
221
  - lib/llmemory/maintenance.rb
222
+ - lib/llmemory/maintenance/cognitive_pass.rb
197
223
  - lib/llmemory/maintenance/consolidator.rb
198
224
  - lib/llmemory/maintenance/reindexer.rb
199
225
  - lib/llmemory/maintenance/runner.rb
200
226
  - lib/llmemory/maintenance/summarizer.rb
227
+ - lib/llmemory/maintenance/ttl_expiry.rb
201
228
  - lib/llmemory/mcp.rb
202
229
  - lib/llmemory/mcp/authentication.rb
203
230
  - lib/llmemory/mcp/server.rb
204
231
  - lib/llmemory/mcp/tools/memory_add_message.rb
205
232
  - lib/llmemory/mcp/tools/memory_consolidate.rb
233
+ - lib/llmemory/mcp/tools/memory_episode_record.rb
234
+ - lib/llmemory/mcp/tools/memory_episodes.rb
235
+ - lib/llmemory/mcp/tools/memory_forget.rb
206
236
  - lib/llmemory/mcp/tools/memory_info.rb
237
+ - lib/llmemory/mcp/tools/memory_maintain.rb
238
+ - lib/llmemory/mcp/tools/memory_mine_skills.rb
207
239
  - lib/llmemory/mcp/tools/memory_retrieve.rb
208
240
  - lib/llmemory/mcp/tools/memory_save.rb
209
241
  - lib/llmemory/mcp/tools/memory_search.rb
242
+ - lib/llmemory/mcp/tools/memory_skill_register.rb
243
+ - lib/llmemory/mcp/tools/memory_skill_report.rb
244
+ - lib/llmemory/mcp/tools/memory_skills.rb
210
245
  - lib/llmemory/mcp/tools/memory_stats.rb
211
246
  - lib/llmemory/mcp/tools/memory_timeline.rb
212
247
  - lib/llmemory/mcp/tools/memory_timeline_context.rb
@@ -235,6 +270,9 @@ files:
235
270
  - lib/llmemory/short_term/stores/memory_store.rb
236
271
  - lib/llmemory/short_term/stores/postgres_store.rb
237
272
  - lib/llmemory/short_term/stores/redis_store.rb
273
+ - lib/llmemory/skill_mining.rb
274
+ - lib/llmemory/skill_mining/miner.rb
275
+ - lib/llmemory/tokenizer.rb
238
276
  - lib/llmemory/vector_store.rb
239
277
  - lib/llmemory/vector_store/active_record_embedding.rb
240
278
  - lib/llmemory/vector_store/active_record_store.rb