memories-lite 0.9.1 → 0.9.3

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.
package/README.md CHANGED
@@ -2,80 +2,53 @@
2
2
 
3
3
  > **A lightweight memory layer for AI agents, leveraging LLMs for fact extraction and vector embeddings for retrieval.**
4
4
 
5
- Inspired by concepts from research papers like **A-MEM** (Lu et al., 2025) for its approach to atomized, embedded memories and similarity search, **MemoryLLM** (Wang et al., 2024) for its insights into memory decay, and **Reflexion** (Shinn et al., 2023) for self-correction loops, `memories-lite` provides a practical implementation focusing initially on the core memory capture and retrieval mechanisms.
5
+ ## 📋 Table of Contents
6
+ - [Quick Start](#-quick-start)
7
+ - [Installation](#-installation)
8
+ - [Basic Usage](#-basic-usage)
9
+ - [Key Features](#-key-features)
10
+ - [Memory Types](#-memory-types)
11
+ - [Use Cases](#-use-cases)
12
+ - [Advanced Configuration](#-advanced-configuration)
13
+ - [Documentation](#-documentation)
14
+ - [Acknowledgements](#-acknowledgements)
15
+
16
+ ## 🚀 Quick Start
6
17
 
7
- **For detailed technical implementation specifics, see [TECHNICAL.md](./TECHNICAL.md).**
8
-
9
- ---
10
-
11
- ## Goal
12
-
13
- Memories-lite provides contextual memory for AI agents. It uses Language Models (LLMs) like OpenAI's GPT models to extract key information (memories) from conversations and stores them using vector embeddings for efficient retrieval. Unlike purely stateless approaches, it utilizes configurable vector stores and an optional history manager (defaulting to in-memory SQLite) for persistence and tracking changes.
14
-
15
- ---
16
-
17
- ## Use Cases
18
-
19
- - **Personalized AI Assistants**: Enable more natural interactions through contextual memory.
20
- - **Autonomous Agents**: Maintain conversational context without heavy infrastructure.
21
- - **Local or Serverless Applications**: Add memory capabilities to embedded bots or assistants.
22
-
23
-
24
- ## Core Features
25
- - **Semantic Memory Typing**: Explicitly tagging and utilizing memory types (factual, episodic, semantic, procedural).
26
- - **Memory Capture**: Processes user messages or structured input, uses an LLM to extract relevant facts/memories, generates embeddings, and stores them in a vector database (inspired by **A-MEM**).
27
- - **Contextual Retrieval**: Searches the vector store based on a query embedding to find relevant memories using vector similarity (inspired by **A-MEM**).
28
- - **Memory Management**: Provides methods to `get`, `update`, `delete`, `getAll`, and `deleteAll` memories associated with a specific user ID.
29
- - **Configurable Backends**: Supports different providers for LLMs (e.g., OpenAI), Embedders (e.g., OpenAI, Google), and Vector Stores.
30
- - **User-centric Persistence**: Memories are securely stored and isolated by user ID, ensuring data privacy and delegate storage control to the server.
31
-
32
-
33
- ## Roadmap & TODO
34
- Features planned or under development:
35
-
36
- - [ ] **Memory Decay & Scoring**: Implement hybrid scoring combining vector similarity with recency decay (e.g., exponential decay based on half-life per memory type, inspired by **MemoryLLM**) and explicit importance weights.
37
- - [ ] **Reflexion Pattern Integration**: Add optional self-correction/reflection loops where the agent evaluates and potentially refines memories (inspired by **Reflexion**).
38
- - [ ] **Memory Recency**: Implementing mechanisms to prioritize memories based on importance, relevance, or time decay.
39
- - [X] **Semantic Memory Typing & Structuring**: Explicitly tagging and utilizing memory types (factual, episodic, semantic, procedural) within the storage and retrieval logic beyond basic metadata.
40
- - [X] **Implicit Memory Updates**: Automatically updating or merging memories based on conversational context or corrections, rather than requiring explicit `update` calls with memory IDs.
41
- - [X] **Virtual Sessions/Context Grouping**: Logic for grouping memories related to specific conversational contexts or sessions automatically.
42
-
43
- ## Memory Types (Conceptual)
44
-
45
- While the internal storage is primarily based on vectorized text facts, `memories-lite` can be used to manage different conceptual types of memory through prompting and metadata:
46
-
47
- ### 1. Factual Memory
48
- - **Description**: Explicit knowledge about the user, such as preferences, skills, or personal information.
49
- - **Example**: "Likes Italian cuisine", "Speaks Spanish fluently".
50
-
51
- ### 2. Episodic Memory
52
- - **Description**: Memories of past events or interactions, often tied to a specific time or place.
53
- - **Example**: "Attended a concert in Paris in 2023", "Met Marie at a conference".
54
- - **Context Dependency**: Highly dependent on history to establish a timeline and narrative coherence.
55
-
56
- ### 3. Semantic Memory
57
- - **Description**: Understanding of general concepts, relationships, and meanings.
58
- - **Example**: "Yoga is beneficial for mental health", "Cats are domestic animals".
59
- - **Context Dependency**: Generally independent, but can be influenced by past interactions to refine understanding.
60
-
61
- ### 4. Procedural Memory
62
- - **Description**: Knowledge of processes or sequences of actions, often acquired through practice.
63
- - **Example**: "Knows how to make a latte", "Can configure a home Wi-Fi network".
64
- - **Context Dependency**: May require history to adapt procedures to user preferences or habits.
18
+ ```bash
19
+ # Install the package
20
+ npm install memories-lite
65
21
 
66
- ## 📌 Example: Transforming Input into Memory
22
+ # Basic usage
23
+ import { MemoriesLite } from 'memories-lite';
67
24
 
68
- **User Interaction**: `"I started learning piano last week."`
25
+ const memory = new MemoriesLite({
26
+ llm: {
27
+ provider: 'openai',
28
+ config: { apiKey: 'YOUR_API_KEY' }
29
+ },
30
+ embedder: {
31
+ provider: 'openai',
32
+ config: { apiKey: 'YOUR_API_KEY', model: 'text-embedding-3-small' }
33
+ }
34
+ });
69
35
 
70
- **Potential Memories Extracted (via LLM)**:
71
- - `"User is learning piano"`
72
- - `"User started learning piano recently"` (or specific date if LLM extracts it)
36
+ // Add a memory for a user
37
+ await memory.capture("I prefer dark chocolate over milk chocolate", "user123");
73
38
 
74
- These extracted facts are then embedded and stored in the vector store associated with the `userId`.
39
+ // Retrieve relevant memories
40
+ const results = await memory.retrieve("What are my food preferences?", "user123");
41
+ ```
75
42
 
43
+ ## 🌟 Highlights
76
44
 
45
+ - **Higher Performance**: Optimized memory operations that run significantly faster than mem0
46
+ - **Business-Centric Design**: Simplified API and workflows specifically tailored for business use cases
47
+ - **Advanced Hybrid Scoring**: Improved relevance through a custom scoring algorithm that balances vector similarity, recency, and importance
48
+ - **Enhanced Security**: One database per user architecture that provides stronger isolation and data protection
49
+ - **Streamlined Implementation**: Focused on essential features with minimal dependencies
77
50
 
78
- ## Installation
51
+ ## 📥 Installation
79
52
 
80
53
  ```bash
81
54
  npm install memories-lite
@@ -83,139 +56,141 @@ npm install memories-lite
83
56
  yarn add memories-lite
84
57
  ```
85
58
 
86
- ---
87
-
88
- ## 🚀 Usage
59
+ ## 🔍 Basic Usage
89
60
 
90
61
  ```typescript
91
- import { MemoriesLite, OpenAIEmbedder, OpenAILLM } from 'memories-lite';
62
+ import { MemoriesLite } from 'memories-lite';
92
63
 
93
- // Basic configuration (uses defaults: in-memory SQLite history, OpenAI)
94
- const apiKey = 'YOUR_OPENAI_API_KEY';
95
- const memories = new MemoriesLite({
64
+ // Basic configuration
65
+ const memory = new MemoriesLite({
96
66
  llm: {
97
67
  provider: 'openai',
98
- config: { apiKey }
68
+ config: { apiKey: 'YOUR_OPENAI_API_KEY' }
99
69
  },
100
70
  embedder: {
101
71
  provider: 'openai',
102
- config: { apiKey }
72
+ config: { apiKey: 'YOUR_OPENAI_API_KEY' }
103
73
  }
104
74
  // Vector store defaults to an in-memory store
105
- // History defaults to in-memory SQLite
106
75
  });
107
76
 
77
+ // Unique ID for each user
108
78
  const userId = 'user-123';
109
79
 
110
- async function runExample() {
111
- // Capture a memory
112
- await memories.capture('I am passionate about the humanitarianism of Pol Pot.', userId); // Note: Example text used for demonstration.
113
-
114
- // Retrieve relevant memories
115
- const searchResults = await memories.retrieve('What are my interests?', userId);
80
+ // Add memories
81
+ await memory.capture('I love Italian food', userId);
116
82
 
117
- console.log('Relevant Memories:', searchResults.memories.map(m => m.memory));
118
- // Example Output might include: ["User is passionate about the humanitarianism of Pol Pot."]
119
- // The exact output depends on the LLM's fact extraction.
83
+ // Retrieve relevant memories
84
+ const results = await memory.retrieve('What foods do I like?', userId);
85
+ console.log('Relevant memories:', results.results.map(m => m.memory));
120
86
 
121
- // --- Example integrating retrieval into a response generation flow ---
122
- const userMessage = 'Tell me about my hobbies.';
123
- const relevantMemories = await memories.retrieve(userMessage, userId);
124
-
125
- const memoryContext = relevantMemories.memories.map(m => m.memory).join('\n');
126
-
127
- // You would typically pass this context to your main application's LLM call
128
- console.log('\nContext for LLM:', memoryContext);
87
+ // Update a memory
88
+ if (results.results.length > 0) {
89
+ await memory.update(results.results[0].id, 'I love Italian and French cuisine', userId);
129
90
  }
130
91
 
131
- runExample().catch(console.error);
92
+ // Delete a memory
93
+ if (results.results.length > 0) {
94
+ await memory.delete(results.results[0].id, userId);
95
+ }
132
96
 
97
+ // Get all memories for a user
98
+ const allMemories = await memory.getAll(userId, {});
133
99
  ```
134
100
 
135
- ## 📄 License
101
+ ## 🔑 Key Features
136
102
 
137
- MIT
103
+ - **Memory Capture**: Extract and store relevant information from conversations
104
+ - **Contextual Retrieval**: Find memories most relevant to the current query
105
+ - **User Isolation**: Each user's memories are stored separately for privacy and security
106
+ - **Memory Types**: Support for different types of memories (factual, episodic, etc.)
107
+ - **Custom Scoring**: Hybrid scoring system balancing similarity, recency, and importance
138
108
 
139
- ---
109
+ ## 🧩 Memory Types
140
110
 
141
- ## Acknowledgements
142
- Forked from the [Mem0](https://github.com/mem0ai/mem0) project ❤️.
111
+ Memories-lite supports four main types of memory:
143
112
 
144
- ## Useful Links and research
113
+ 1. **Factual Memory**
114
+ - User preferences, traits, and personal information
115
+ - Example: "User likes Italian cuisine"
145
116
 
146
- - [**Zep: A Temporal Knowledge Graph Architecture for Agent Memory** (arXiv:2501.13956)](https://arxiv.org/abs/2501.13956)
147
- - [**A-MEM: Agentic Memory for LLM Agents** (arXiv:2402.12110)](https://arxiv.org/abs/2402.12110) *(Note: Link points to 2402.12110, the user-provided 2502.12110 might be a typo)*
148
- - [**Reflexion: Language Agents with Verbal Reinforcement Learning** (arXiv:2303.11366)](https://arxiv.org/abs/2303.11366)
149
- - [**MemoryLLM: Towards Self-Updatable Large Language Models** (arXiv:2402.04624)](https://arxiv.org/abs/2402.04624)
117
+ 2. **Episodic Memory** ⏱️
118
+ - Time-based events and interactions
119
+ - Example: "User has a meeting tomorrow at 2pm"
150
120
 
121
+ 3. **Semantic Memory** 🧠
122
+ - General knowledge and concepts
123
+ - Example: "Yoga is beneficial for mental health"
151
124
 
152
- - **Exponential Decay**: Applying a decay function \( w(t) = e^{-\lambda t} \) where the decay rate \( \lambda \) depends on the memory type (e.g., episodic memories decay faster than factual ones).
153
- - **Half-Life**: Defining a half-life (HL) for different memory types to calculate \( \lambda = \ln(2) / \text{HL} \).
154
- - **Hybrid Scoring**: Integrating the decay factor into the memory retrieval score alongside vector similarity and potentially other metadata.
125
+ 4. **Procedural Memory** 🔄
126
+ - Step-by-step processes and workflows
127
+ - Example: "Steps to configure the company VPN"
155
128
 
129
+ ## 💼 Use Cases
130
+
131
+ - **Customer Support Bots**: Remember customer preferences and past interactions
132
+ - **Personal Assistants**: Build context-aware AI assistants that learn about user preferences
133
+ - **Business Applications**: Integrate with enterprise systems to maintain contextual awareness
134
+ - **Educational Tools**: Create learning assistants that remember student progress
135
+
136
+ ## ⚙️ Advanced Configuration
156
137
 
157
138
  ```typescript
158
- // --- Advanced Configuration Example: Custom Scoring ---
159
- import { MemoriesLite, MemoryScoringConfig } from 'memories-lite';
160
-
161
- const apiKey = 'YOUR_OPENAI_API_KEY';
162
-
163
- // Define custom scoring rules
164
- const customScoring: MemoryScoringConfig = {
165
- // Make Factual memory very durable (long half-life, high similarity weight)
166
- factual: { alpha: 0.7, beta: 0.1, gamma: 0.1, halfLifeDays: 365 * 2 }, // 2 years HL
167
- // Make Assistant Preferences permanent (infinite half-life, high base weight)
168
- assistant_preference: { alpha: 0.5, beta: 0.0, gamma: 0.5, halfLifeDays: Infinity },
169
- // Make Procedural memory decay extremely fast (useless after ~1 hour)
170
- procedural: { alpha: 0.1, beta: 0.1, gamma: 0.0, halfLifeDays: 1 / 24 }, // 1 hour HL
171
- // Keep defaults for others (or customize as needed)
172
- episodic: { alpha: 0.40, beta: 0.50, gamma: 0.10, halfLifeDays: 7 },
173
- semantic: { alpha: 0.50, beta: 0.25, gamma: 0.25, halfLifeDays: 120 },
174
- default: { alpha: 0.5, beta: 0.3, gamma: 0.1, halfLifeDays: 30 },
175
- };
176
-
177
- const memoriesWithCustomScoring = new MemoriesLite({
139
+ // Custom scoring for different memory types
140
+ const customMemory = new MemoriesLite({
178
141
  llm: {
179
142
  provider: 'openai',
180
- config: { apiKey }
143
+ config: { apiKey: 'YOUR_API_KEY' }
181
144
  },
182
145
  embedder: {
183
- provider: 'openai',
184
- config: { apiKey }
146
+ provider: 'openai',
147
+ config: { apiKey: 'YOUR_API_KEY' }
185
148
  },
186
149
  vectorStore: {
187
- provider: 'lite', // Assuming LiteVectorStore which uses scoring
150
+ provider: 'lite',
188
151
  config: {
189
- // Other vector store config...
190
- scoring: customScoring // Pass the custom scoring rules
152
+ dimension: 1536,
153
+ scoring: {
154
+ // Prioritize factual memories with long retention
155
+ factual: { alpha: 0.7, beta: 0.2, gamma: 0.1, halfLifeDays: 365 },
156
+ // Make preferences permanently available
157
+ assistant_preference: { alpha: 0.6, beta: 0.0, gamma: 0.4, halfLifeDays: Infinity },
158
+ }
191
159
  }
192
160
  }
193
161
  });
194
-
195
- // Now use memoriesWithCustomScoring instance...
196
- // const userId = 'user-456';
197
- // await memoriesWithCustomScoring.capture('User learned how to bake bread.', userId, { type: 'procedural' });
198
- // await memoriesWithCustomScoring.capture('User prefers results in French.', userId, { type: 'assistant_preference' });
199
162
  ```
200
163
 
201
- ## 📄 License
164
+ ## 📚 Documentation
202
165
 
203
- MIT
166
+ For detailed technical information and implementation details, see:
204
167
 
205
- ---
168
+ - [TECHNICAL.md](./TECHNICAL.md) - Technical implementation details
169
+ - [MEMORIES.md](./MEMORIES.md) - Detailed memory models and concepts
206
170
 
207
- ## Acknowledgements
208
- Forked from the [Mem0](https://github.com/mem0ai/mem0) project ❤️.
209
-
210
- ## Useful Links and research
171
+ ## 🙏 Acknowledgements
211
172
 
212
- - [**Zep: A Temporal Knowledge Graph Architecture for Agent Memory** (arXiv:2501.13956)](https://arxiv.org/abs/2501.13956)
213
- - [**A-MEM: Agentic Memory for LLM Agents** (arXiv:2402.12110)](https://arxiv.org/abs/2402.12110) *(Note: Link points to 2402.12110, the user-provided 2502.12110 might be a typo)*
214
- - [**Reflexion: Language Agents with Verbal Reinforcement Learning** (arXiv:2303.11366)](https://arxiv.org/abs/2303.11366)
215
- - [**MemoryLLM: Towards Self-Updatable Large Language Models** (arXiv:2402.04624)](https://arxiv.org/abs/2402.04624)
173
+ Forked from the [Mem0](https://github.com/mem0ai/mem0) project ❤️.
216
174
 
175
+ Inspired by research concepts from:
176
+ - **A-MEM**: Agentic Memory for LLM Agents
177
+ - **MemoryLLM**: Self-Updatable Large Language Models
178
+ - **Reflexion**: Language Agents with Verbal Reinforcement Learning
179
+
180
+ ## 📝 Development Roadmap
181
+
182
+ - [x] **Semantic Memory Typing & Structuring**: Explicitly tagging and utilizing memory types (factual, episodic, semantic, procedural)
183
+ - [x] **Implicit Memory Updates**: Auto-merging memories based on context without explicit ID references
184
+ - [x] **Virtual Sessions/Context Grouping**: Group memories related to specific conversation contexts
185
+ - [x] **User Isolation**: Separate storage per user for enhanced security and data privacy
186
+ - [x] **Memory Type Detection**: LLM-based automatic classification of memory types
187
+ - [x] **Core Memory Operations**: Basic CRUD operations with user-specific isolation
188
+ - [x] **Memory Decay & Scoring**: Hybrid scoring with recency decay and importance weights
189
+ - [ ] **Reflexion Pattern Integration**: Self-correction loops for memory refinement
190
+ - [x] **Memory Recency**: Prioritizing memories based on importance and time decay
191
+ - [x] **Edge Case Tests**: Complete unit tests for episodic and factual memory edge cases
192
+ - [ ] **Middleware Support**: Hooks and middleware for custom processing pipelines
217
193
 
218
- - **Exponential Decay**: Applying a decay function \( w(t) = e^{-\lambda t} \) where the decay rate \( \lambda \) depends on the memory type (e.g., episodic memories decay faster than factual ones).
219
- - **Half-Life**: Defining a half-life (HL) for different memory types to calculate \( \lambda = \ln(2) / \text{HL} \).
220
- - **Hybrid Scoring**: Integrating the decay factor into the memory retrieval score alongside vector similarity and potentially other metadata.
194
+ ## 📄 License
221
195
 
196
+ MIT
@@ -124,6 +124,9 @@ class MemoriesLite {
124
124
  // Get embeddings for new facts
125
125
  const newMessageEmbeddings = {};
126
126
  const retrievedOldMemory = [];
127
+ //
128
+ // add the userId to the filters
129
+ filters.userId = userId;
127
130
  // Create embeddings and search for similar memories
128
131
  for (const elem of facts) {
129
132
  const fact = elem.fact;
@@ -155,7 +158,7 @@ class MemoriesLite {
155
158
  console.log(`-- ⛔ LLM Error: ${action.event}, ${action.type}, "${action.text}"`);
156
159
  continue;
157
160
  }
158
- console.log(`-- DBG memory action: ${action.event}, ${action.type}, "${action.text}", why: "${action.reason}"`);
161
+ console.log(`-- DBG memory "${userId}": ${action.event}, ${action.type}, "${action.text}", why: "${action.reason}"`);
159
162
  try {
160
163
  switch (action.event) {
161
164
  case "ADD": {
@@ -176,7 +179,7 @@ class MemoriesLite {
176
179
  }
177
180
  case "UPDATE": {
178
181
  const realMemoryId = tempUuidMapping[action.id];
179
- const type = uniqueOldMemories[action.id].type;
182
+ const type = metadata.type = uniqueOldMemories[action.id].type || action.type;
180
183
  await this.updateMemory(realMemoryId, action.text, newMessageEmbeddings, metadata, userId);
181
184
  results.push({
182
185
  id: realMemoryId,
@@ -307,6 +310,7 @@ class MemoriesLite {
307
310
  throw new Error("One of the filters: userId, agentId or runId is required!");
308
311
  }
309
312
  const vectorStore = await this.getVectorStore(userId);
313
+ filters.userId = userId;
310
314
  // Search vector store
311
315
  const queryEmbedding = await this.embedder.embed(query);
312
316
  const memories = await vectorStore.search(queryEmbedding, limit, filters);
@@ -420,6 +424,7 @@ class MemoriesLite {
420
424
  filters.runId = runId;
421
425
  if (type)
422
426
  filters.type = type;
427
+ filters.userId = userId;
423
428
  const [memories] = await vectorStore.list(filters, limit);
424
429
  const excludedKeys = new Set([
425
430
  "userId",
@@ -453,6 +458,7 @@ class MemoriesLite {
453
458
  ...metadata,
454
459
  data,
455
460
  hash: (0, crypto_1.createHash)("md5").update(data).digest("hex"),
461
+ userId,
456
462
  createdAt: new Date().toISOString(),
457
463
  };
458
464
  await vectorStore.insert([embedding], [memoryId], [memoryMetadata]);
@@ -471,6 +477,7 @@ class MemoriesLite {
471
477
  ...metadata,
472
478
  data,
473
479
  hash: (0, crypto_1.createHash)("md5").update(data).digest("hex"),
480
+ type: existingMemory.payload.type,
474
481
  createdAt: existingMemory.payload.createdAt,
475
482
  updatedAt: new Date().toISOString(),
476
483
  ...(existingMemory.payload.agentId && {
@@ -128,15 +128,15 @@ You must strictly extract {Subject, Predicate, Object} triplets by following the
128
128
  - Extract triplets that describe facts *about the user* based on their statements, covering areas like preferences, beliefs, actions, experiences, learning, identity, work, or relationships (e.g., "I love working").
129
129
  - Apply explicit, precise, and unambiguous predicates (e.g., "owns", "is located at", "is a", "has function", "causes", etc.).
130
130
  - Determine the triplet type (e.g., "factual", "episodic", "procedural", "semantic") based on the content and meaning.
131
- - "episodic" for time-based events (e.g., "I went to the park yesterday").
131
+ - "episodic" If a fact depends on a temporal, situational, or immediate personal context, then that fact AND ALL OF ITS sub-facts MUST be classified as episodic.
132
132
  - "procedural" for business processes (e.g., "Looking for customer John Doe address", "How to create a new contract").
133
133
  - "factual" for stable user data (except procedural that prevails).
134
134
 
135
135
  - Eliminate introductions, sub-facts, detailed repetitive elements, stylistic fillers, or vague statements. General facts always takes precedence over multiple sub-facts (signal vs noise).
136
136
  - The query intention can include specific preferences about how the Assistant should respond (e.g., "answer concisely", "explain in detail").
137
- - Compress each fact and reason (less than 12 words).
137
+ - Compress each OUTPUT (fact and reason) with less than 10 words.
138
138
  - DO NOT infer personal facts from third-party informations.
139
- - Treat "Assistant Answer:" as external responses from the Assistant to enrich your reasoning process about the user.
139
+ - Treat "**ASSISTANT**:" as responses to enrich context of your reasoning process about the USER query.
140
140
  2. Use pronoun "I" instead of "The user" in the subject of the triplet.
141
141
  3. Do not output any facts already present in section # PRE-EXISTING FACTS.
142
142
  - If you find facts already present in section # PRE-EXISTING FACTS, use field "existing" to store them.
@@ -12,6 +12,7 @@ import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
12
12
  */
13
13
  export declare class LiteVectorStore implements VectorStore {
14
14
  private db;
15
+ private dbPath;
15
16
  private isSecure;
16
17
  private dimension;
17
18
  private currentUserId;
@@ -36,5 +37,14 @@ export declare class LiteVectorStore implements VectorStore {
36
37
  list(filters?: SearchFilters, limit?: number): Promise<[VectorStoreResult[], number]>;
37
38
  private calculateRecencyScore;
38
39
  private calculateHybridScore;
40
+ /**
41
+ * Internal method to clean up vectors based on recency score threshold.
42
+ *
43
+ * @param threshold - The minimum recency score required for a memory to be retained.
44
+ * - Recency score is calculated using exponential decay: 1.0 means brand new, 0.5 means at half-life, 0.0 means fully decayed.
45
+ * - Memories with a recency score below this threshold will be deleted (unless their half-life is infinite or zero).
46
+ * - For example, a threshold of 0.25 will remove all memories whose recency score has decayed 2 times the half-life.
47
+ * - Use a lower threshold to keep more old memories, or a higher threshold to keep only fresher ones.
48
+ */
39
49
  private _cleanupByRecency;
40
50
  }
@@ -8,6 +8,7 @@ exports.LiteVectorStore = void 0;
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const sqlite3_1 = __importDefault(require("sqlite3"));
10
10
  const crypto_1 = require("crypto");
11
+ const fs_1 = require("fs");
11
12
  /**
12
13
  * LiteVectorStore provides a simple vector storage implementation.
13
14
  *
@@ -27,27 +28,32 @@ class LiteVectorStore {
27
28
  this.currentUserId = currentUserId;
28
29
  this.isSecure = config.secure || false;
29
30
  this.scoringConfig = config.scoring;
30
- this.cleanupThreshold = config.recencyCleanupThreshold; // Store threshold
31
+ this.cleanupThreshold = config.recencyCleanupThreshold || 0.25; // (default 0.25 means 2 times the half-life )
31
32
  config.rootPath = config.rootPath || process.cwd();
32
33
  const filename = this.isSecure ? `memories-lite-${currentUserId}.db` : `memories-lite-global.db`;
33
- const dbPath = (config.rootPath == ':memory:') ? ':memory:' : path_1.default.join(config.rootPath, filename);
34
+ this.dbPath = (config.rootPath == ':memory:') ? ':memory:' : path_1.default.join(config.rootPath, filename);
34
35
  // Add error handling callback for the database connection
35
- this.db = new sqlite3_1.default.Database(dbPath);
36
+ this.db = new sqlite3_1.default.Database(this.dbPath);
36
37
  }
37
38
  async init() {
38
- await this.run(`
39
- CREATE TABLE IF NOT EXISTS vectors (
40
- id TEXT PRIMARY KEY,
39
+ try {
40
+ await this.run(`
41
+ CREATE TABLE IF NOT EXISTS vectors (
42
+ id TEXT PRIMARY KEY,
41
43
  vector BLOB NOT NULL,
42
44
  payload TEXT NOT NULL
43
45
  )
44
46
  `);
45
- await this.run(`
47
+ await this.run(`
46
48
  CREATE TABLE IF NOT EXISTS memory_migrations (
47
49
  id INTEGER PRIMARY KEY AUTOINCREMENT,
48
50
  user_id TEXT NOT NULL UNIQUE
49
51
  )
50
52
  `);
53
+ }
54
+ catch (err) {
55
+ console.log("-- DBG init error:", err);
56
+ }
51
57
  }
52
58
  async run(sql, params = []) {
53
59
  return new Promise((resolve, reject) => {
@@ -129,9 +135,11 @@ class LiteVectorStore {
129
135
  if (cachedStore) {
130
136
  Object.setPrototypeOf(cachedStore, LiteVectorStore.prototype);
131
137
  cachedStore.currentUserId = hashedUserId;
132
- // Ensure scoring config and threshold are updated if config object changed
133
- cachedStore.scoringConfig = config.scoring;
134
- cachedStore.cleanupThreshold = config.recencyCleanupThreshold;
138
+ //
139
+ // if the database file does not exist, we need to reinitialize the store
140
+ if (cachedStore.dbPath !== ':memory:' && !(0, fs_1.existsSync)(cachedStore.dbPath)) {
141
+ return new LiteVectorStore(config, hashedUserId);
142
+ }
135
143
  return cachedStore;
136
144
  }
137
145
  // Pass the full config (including scoring) to the constructor
@@ -145,17 +153,28 @@ class LiteVectorStore {
145
153
  if (vectors[i].length !== this.dimension) {
146
154
  throw new Error(`Vector dimension mismatch. Expected ${this.dimension}, got ${vectors[i].length}`);
147
155
  }
156
+ const payload = { ...payloads[i] };
157
+ //
158
+ // case of global store (insecure)
159
+ if (!payload.userId) {
160
+ throw new Error("userId is required in payload");
161
+ }
148
162
  //
149
163
  // remove the userId from the payload as sensitive data
150
- this.isSecure && delete payloads[i].userId;
164
+ this.isSecure && delete payload.userId;
151
165
  const vectorBuffer = Buffer.from(new Float32Array(vectors[i]).buffer);
152
- await this.run(`INSERT OR REPLACE INTO vectors (id, vector, payload) VALUES (?, ?, ?)`, [ids[i], vectorBuffer, JSON.stringify(payloads[i])]);
166
+ await this.run(`INSERT OR REPLACE INTO vectors (id, vector, payload) VALUES (?, ?, ?)`, [ids[i], vectorBuffer, JSON.stringify(payload)]);
153
167
  }
154
168
  }
155
169
  async search(query, limit = 10, filters) {
156
170
  if (query.length !== this.dimension) {
157
171
  throw new Error(`Query dimension mismatch. Expected ${this.dimension}, got ${query.length}`);
158
172
  }
173
+ if (!filters || !filters.userId) {
174
+ throw new Error("userId is mandatory in search");
175
+ }
176
+ filters = { ...filters };
177
+ this.isSecure && delete filters.userId;
159
178
  const results = [];
160
179
  const rows = await this.all(`SELECT * FROM vectors`);
161
180
  for (const row of rows) {
@@ -225,17 +244,21 @@ class LiteVectorStore {
225
244
  async list(filters, limit = 100) {
226
245
  const rows = await this.all(`SELECT * FROM vectors`);
227
246
  const results = [];
247
+ //
248
+ // remove the userId from the payload as sensitive data
249
+ filters = { ...filters };
250
+ this.isSecure && delete filters?.userId;
228
251
  for (const row of rows) {
229
252
  const memoryVector = {
230
253
  id: row.id,
231
254
  vector: Array.from(new Float32Array(row.vector.buffer)),
232
- payload: {},
255
+ payload: JSON.parse(row.payload),
233
256
  };
234
257
  if (this.filterVector(memoryVector, filters)) {
235
258
  // load payload at the end
236
259
  results.push({
237
260
  id: memoryVector.id,
238
- payload: JSON.parse(row.payload),
261
+ payload: memoryVector.payload,
239
262
  });
240
263
  }
241
264
  }
@@ -278,7 +301,15 @@ class LiteVectorStore {
278
301
  // Ensure score is within a reasonable range (e.g., 0 to alpha+beta+gamma)
279
302
  return Math.max(0, hybridScore);
280
303
  }
281
- // Internal method to clean up vectors based on recency score threshold
304
+ /**
305
+ * Internal method to clean up vectors based on recency score threshold.
306
+ *
307
+ * @param threshold - The minimum recency score required for a memory to be retained.
308
+ * - Recency score is calculated using exponential decay: 1.0 means brand new, 0.5 means at half-life, 0.0 means fully decayed.
309
+ * - Memories with a recency score below this threshold will be deleted (unless their half-life is infinite or zero).
310
+ * - For example, a threshold of 0.25 will remove all memories whose recency score has decayed 2 times the half-life.
311
+ * - Use a lower threshold to keep more old memories, or a higher threshold to keep only fresher ones.
312
+ */
282
313
  async _cleanupByRecency(threshold) {
283
314
  const rows = await this.all(`SELECT id, payload FROM vectors`);
284
315
  let deletedCount = 0;
package/package.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "memories-lite",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
+ "description": "A lightweight memory system for LLM agents",
5
+ "author": "olivier@evaletolab.ch",
4
6
  "main": "dist/index.js",
5
7
  "types": "dist/index.d.ts",
6
8
  "scripts": {
@@ -202,6 +202,9 @@ export class MemoriesLite {
202
202
  const newMessageEmbeddings: Record<string, number[]> = {};
203
203
  const retrievedOldMemory: Array<{ id: string; text: string; type: string }> = [];
204
204
 
205
+ //
206
+ // add the userId to the filters
207
+ filters.userId = userId;
205
208
  // Create embeddings and search for similar memories
206
209
  for (const elem of facts) {
207
210
  const fact = elem.fact;
@@ -250,7 +253,7 @@ export class MemoriesLite {
250
253
  console.log(`-- ⛔ LLM Error: ${action.event}, ${action.type}, "${action.text}"`);
251
254
  continue;
252
255
  }
253
- console.log(`-- DBG memory action: ${action.event}, ${action.type}, "${action.text}", why: "${action.reason}"`);
256
+ console.log(`-- DBG memory "${userId}": ${action.event}, ${action.type}, "${action.text}", why: "${action.reason}"`);
254
257
  try {
255
258
  switch (action.event) {
256
259
  case "ADD": {
@@ -276,7 +279,7 @@ export class MemoriesLite {
276
279
  }
277
280
  case "UPDATE": {
278
281
  const realMemoryId = tempUuidMapping[action.id];
279
- const type = uniqueOldMemories[action.id].type as MemoryType;
282
+ const type = metadata.type = uniqueOldMemories[action.id].type || action.type;
280
283
  await this.updateMemory(
281
284
  realMemoryId,
282
285
  action.text,
@@ -455,7 +458,7 @@ export class MemoriesLite {
455
458
  }
456
459
 
457
460
  const vectorStore = await this.getVectorStore(userId);
458
-
461
+ filters.userId = userId;
459
462
 
460
463
  // Search vector store
461
464
  const queryEmbedding = await this.embedder.embed(query);
@@ -597,6 +600,7 @@ export class MemoriesLite {
597
600
  if (agentId) filters.agentId = agentId;
598
601
  if (runId) filters.runId = runId;
599
602
  if (type) filters.type = type;
603
+ filters.userId = userId;
600
604
  const [memories] = await vectorStore.list(filters, limit);
601
605
 
602
606
  const excludedKeys = new Set([
@@ -640,6 +644,7 @@ export class MemoriesLite {
640
644
  ...metadata,
641
645
  data,
642
646
  hash: createHash("md5").update(data).digest("hex"),
647
+ userId,
643
648
  createdAt: new Date().toISOString(),
644
649
  };
645
650
 
@@ -676,6 +681,7 @@ export class MemoriesLite {
676
681
  ...metadata,
677
682
  data,
678
683
  hash: createHash("md5").update(data).digest("hex"),
684
+ type: existingMemory.payload.type,
679
685
  createdAt: existingMemory.payload.createdAt,
680
686
  updatedAt: new Date().toISOString(),
681
687
  ...(existingMemory.payload.agentId && {