memories-lite 0.9.5 → 0.99.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.
@@ -16,10 +16,8 @@ import {
16
16
  HistoryManagerFactory,
17
17
  } from "../utils/factory";
18
18
  import {
19
- FactRetrievalSchema_extended,
20
- getFactRetrievalMessages,
21
- getUpdateMemoryMessages,
22
- MemoryUpdateSchema,
19
+ DiscussionSynthesisSchema,
20
+ getDiscussionSynthesisMessages,
23
21
  removeCodeBlocks,
24
22
  } from "../prompts";
25
23
  import { DummyHistoryManager } from "../storage/DummyHistoryManager";
@@ -152,179 +150,98 @@ export class MemoriesLite {
152
150
  }
153
151
 
154
152
 
153
+ /**
154
+ * Capture une discussion et génère une synthèse (title + summary)
155
+ * Utilise getDiscussionSynthesisMessages pour produire la synthèse
156
+ */
155
157
  private async addToVectorStore(
156
158
  messages: Message[],
157
159
  metadata: Record<string, any>,
158
160
  userId: string,
159
161
  filters: SearchFilters,
160
- customFacts?: string,
162
+ capturePrompt?: string,
161
163
  ): Promise<MemoryItem[]> {
162
164
 
163
165
  const $t = this.$t;
164
166
  const vectorStore = await this.getVectorStore(userId);
165
- const parsedMessages = messages.filter((m) => typeof m.content === 'string' && m.role=='user').map((m) => `${m.role=='user' ? '**USER**: ' : '**ASSISTANT**: '}${$t(m.content as string)}\n`).join("\n");
166
-
167
- // Disinterest handling is delegated to the LLM via prompt guidelines
167
+
168
+ //
169
+ // Formater les messages pour la synthèse
170
+ const parsedMessages = messages
171
+ .filter((m) => typeof m.content === 'string')
172
+ .map((m) => `**${m.role.toUpperCase()}**: ${$t(m.content as string)}`)
173
+ .join("\n\n");
168
174
 
169
- const [systemPrompt, userPrompt] = getFactRetrievalMessages(parsedMessages, customFacts||this.customPrompt);
175
+ //
176
+ // Générer la synthèse via LLM
177
+ const [systemPrompt, userPrompt] = getDiscussionSynthesisMessages(
178
+ parsedMessages,
179
+ capturePrompt || this.customPrompt
180
+ );
170
181
 
171
182
  const response = await this.llm.generateResponse(
172
183
  [
173
184
  { role: "system", content: systemPrompt },
174
185
  { role: "user", content: userPrompt },
175
186
  ],
176
- {...zodResponseFormat(FactRetrievalSchema_extended,"FactRetrieval")},[],false
187
+ { ...zodResponseFormat(DiscussionSynthesisSchema, "DiscussionSynthesis") },
188
+ [],
189
+ false
177
190
  );
178
- const parsedResponse = (response:any) => {
191
+
192
+ //
193
+ // Parser la réponse
194
+ const parsedResponse = (res: any) => {
179
195
  try {
180
- // structured output
181
- if(typeof response === 'object') {
182
- return response;
196
+ if (typeof res === 'object') {
197
+ return res;
183
198
  }
184
- const cleanResponse = removeCodeBlocks(response as string);
199
+ const cleanResponse = removeCodeBlocks(res as string);
185
200
  return JSON.parse(cleanResponse);
186
201
  } catch (e) {
187
- console.error(
188
- "Failed to parse facts from LLM response:",
189
- response,
190
- e,
191
- response
192
- );
193
- return [];
202
+ console.error("Failed to parse synthesis from LLM response:", res, e);
203
+ return { title: "Sans titre", summary: "" };
194
204
  }
195
- }
196
- //
197
- // can use native structured output
198
- // Drop factual facts at capture level (do not store factual memories)
199
- // FIXME Drop factual should be done at prompt level
200
- const facts = parsedResponse(response).facts?.filter((f:any) => !f.existing && f.type !== 'factual')||[];
205
+ };
201
206
 
202
- // console.log("-- DBG extract:", userPrompt);
203
- // console.log("-- DBG facts:", facts);
207
+ const { title, summary } = parsedResponse(response);
204
208
 
205
- // Get embeddings for new facts
206
- const newMessageEmbeddings: Record<string, number[]> = {};
207
- const retrievedOldMemory: Array<{ id: string; text: string; type: string }> = [];
209
+ if (!summary) {
210
+ console.warn("-- ⚠️ Empty summary from LLM, skipping memory creation");
211
+ return [];
212
+ }
208
213
 
209
214
  //
210
- // add the userId to the filters
211
- filters.userId = userId;
212
- // Create embeddings and search for similar memories
213
- for (const elem of facts) {
214
- const fact = elem.fact;
215
- const embedding = await this.embedder.embed(fact);
216
- newMessageEmbeddings[fact] = embedding;
217
-
218
- const existingMemories = await vectorStore.search(
219
- embedding,
220
- 5,
221
- filters,
222
- );
223
- for (const mem of existingMemories) {
224
- retrievedOldMemory.push({ id: mem.id, text: mem.payload.data,type: mem.payload.type });
225
- }
226
- }
215
+ // Créer l'embedding sur le summary (pour recherche sémantique)
216
+ const embedding = await this.embedder.embed(summary);
227
217
 
228
- // console.log("-- DBG old memories:", retrievedOldMemory);
229
- // Remove duplicates from old memories
230
- const uniqueOldMemories = retrievedOldMemory.filter(
231
- (mem, index) =>
232
- retrievedOldMemory.findIndex((m) => m.id === mem.id) === index,
233
- );
218
+ //
219
+ // Préparer les métadonnées
220
+ const memoryType: MemoryType = metadata.type || 'discussion';
221
+ const memoryMetadata = {
222
+ ...metadata,
223
+ title,
224
+ type: memoryType,
225
+ userId,
226
+ };
234
227
 
235
- // Create UUID mapping for handling UUID hallucinations
236
- const tempUuidMapping: Record<string, string> = {};
237
- uniqueOldMemories.forEach((item, idx) => {
238
- tempUuidMapping[String(idx)] = item.id;
239
- uniqueOldMemories[idx].id = String(idx);
240
- });
241
-
242
- // Get memory update decisions
243
- const lastUserMessage = [...messages].reverse().find(m => m.role === 'user');
244
- const userInstruction = typeof lastUserMessage?.content === 'string' ? lastUserMessage?.content as string : '';
245
- const updatePrompt = getUpdateMemoryMessages(uniqueOldMemories, facts, 'French', userInstruction);
246
-
247
- const updateResponse = await this.llm.generateResponse(
248
- [{ role: "user", content: updatePrompt }],
249
- {...zodResponseFormat(MemoryUpdateSchema,"Memory")},[],false,
228
+ //
229
+ // Stocker la mémoire
230
+ const memoryId = await this.createMemory(
231
+ summary,
232
+ { [summary]: embedding },
233
+ memoryMetadata,
234
+ userId,
250
235
  );
251
- // console.log("-- DBG merge:", updatePrompt);
252
236
 
253
- const memoryActions: any[] = parsedResponse(updateResponse).memory || [];
237
+ console.log(`-- 🧠 Memory created: "${title}" (${memoryType})`);
254
238
 
255
- // Process memory actions
256
- const results: MemoryItem[] = [];
257
- for (const action of memoryActions) {
258
- // Ignore any factual memory actions (ADD/UPDATE/DELETE) → void
259
- if(action.type === 'factual') {
260
- continue;
261
- }
262
- if(action.reason === "undefined") {
263
- console.log(`-- ⛔ LLM Error: ${action.event}, ${action.type}, "${action.text}"`);
264
- continue;
265
- }
266
- console.log(`-- DBG memory "${userId}": ${action.event}, ${action.type}, "${action.text}", why: "${action.reason}"`);
267
- try {
268
- switch (action.event) {
269
- case "ADD": {
270
- if(!action.type) {
271
- // log error
272
- console.error("Type is mandatory to manage memories:", action);
273
- continue;
274
- }
275
- metadata.type = action.type;
276
- const memoryId = await this.createMemory(
277
- action.text,
278
- newMessageEmbeddings,
279
- metadata,
280
- userId,
281
- );
282
- results.push({
283
- id: memoryId,
284
- memory: action.text,
285
- type: action.type,
286
- metadata: { event: action.event },
287
- });
288
- break;
289
- }
290
- case "UPDATE": {
291
- const realMemoryId = tempUuidMapping[action.id];
292
- const type = metadata.type = uniqueOldMemories[action.id].type || action.type;
293
- await this.updateMemory(
294
- realMemoryId,
295
- action.text,
296
- newMessageEmbeddings,
297
- metadata,
298
- userId,
299
- );
300
- results.push({
301
- id: realMemoryId,
302
- memory: action.text,
303
- type,
304
- metadata: {
305
- event: action.event,
306
- previousMemory: action.old_memory,
307
- },
308
- });
309
- break;
310
- }
311
- case "DELETE": {
312
- const realMemoryId = tempUuidMapping[action.id];
313
- await this.deleteMemory(realMemoryId, userId);
314
- results.push({
315
- id: realMemoryId,
316
- memory: action.text,
317
- type: action.type,
318
- metadata: { event: action.event },
319
- });
320
- break;
321
- }
322
- }
323
- } catch (error) {
324
- console.error(`Error processing memory action: ${error}`);
325
- }
326
- }
327
- return results;
239
+ return [{
240
+ id: memoryId,
241
+ memory: summary,
242
+ type: memoryType,
243
+ metadata: { title, event: "ADD" },
244
+ }];
328
245
  }
329
246
 
330
247
  static fromConfig(configDict: Record<string, any>): MemoriesLite {
@@ -341,24 +258,24 @@ export class MemoriesLite {
341
258
  return LiteVectorStore.from(userId, this.vectorStoreConfig);
342
259
  }
343
260
 
261
+ /**
262
+ * Capture une discussion et génère une mémoire (title + summary)
263
+ *
264
+ * @param messages - Messages de la discussion ou texte brut
265
+ * @param userId - ID utilisateur
266
+ * @param config - Options incluant capturePrompt pour personnaliser la synthèse
267
+ */
344
268
  async capture(
345
269
  messages: string | Message[],
346
270
  userId: string,
347
271
  config: AddMemoryOptions,
348
272
  ): Promise<SearchResult> {
349
- // await this._captureEvent("add", {
350
- // message_count: Array.isArray(messages) ? messages.length : 1,
351
- // has_metadata: !!config.metadata,
352
- // has_filters: !!config.filters,
353
- // infer: config.infer,
354
- // });
355
273
  const {
356
274
  agentId,
357
275
  runId,
358
276
  metadata = {},
359
277
  filters = {},
360
- infer = true,
361
- customFacts
278
+ capturePrompt
362
279
  } = config;
363
280
 
364
281
  if (agentId) filters.agentId = metadata.agentId = agentId;
@@ -376,16 +293,18 @@ export class MemoriesLite {
376
293
 
377
294
  const final_parsedMessages = await parse_vision_messages(parsedMessages);
378
295
 
379
- // Add to vector store
296
+ //
297
+ // Générer synthèse et stocker
380
298
  const vectorStoreResult = await this.addToVectorStore(
381
299
  final_parsedMessages,
382
300
  metadata,
383
301
  userId,
384
302
  filters,
385
- customFacts,
303
+ capturePrompt,
386
304
  );
387
305
 
388
- // Add to graph store if available
306
+ //
307
+ // Graph store (si configuré)
389
308
  let graphResult;
390
309
  if (this.graphMemory) {
391
310
  try {
@@ -10,8 +10,7 @@ export interface Entity {
10
10
  export interface AddMemoryOptions extends Entity {
11
11
  metadata?: Record<string, any>;
12
12
  filters?: SearchFilters;
13
- customFacts?: string;
14
- infer?: boolean;
13
+ capturePrompt?: string;
15
14
  }
16
15
 
17
16
  export interface SearchMemoryOptions extends Entity {
@@ -1,38 +1,83 @@
1
1
  import { z } from "zod";
2
2
  import { MemoryItem } from "../types";
3
3
 
4
- // Define Zod schema for fact retrieval output
4
+ // ══════════════════════════════════════════════════════════════════════════════
5
+ // DISCUSSION SYNTHESIS - Nouveau système de mémoire
6
+ // ══════════════════════════════════════════════════════════════════════════════
7
+
8
+ /**
9
+ * Schema pour la synthèse de discussion
10
+ * Produit un titre court et une synthèse opérationnelle
11
+ */
12
+ export const DiscussionSynthesisSchema = z.object({
13
+ title: z.string().describe("Titre court et descriptif (6-10 mots)"),
14
+ summary: z.string().describe("Synthèse opérationnelle des points clés (50-100 mots)")
15
+ });
16
+
17
+ /**
18
+ * Prompt par défaut pour la synthèse de discussion
19
+ * Peut être remplacé via capturePrompt dans AddMemoryOptions
20
+ */
21
+ export const DEFAULT_DISCUSSION_PROMPT = `Tu es un expert en synthèse opérationnelle.
22
+
23
+ À partir de cette discussion, génère :
24
+ 1. TITRE: Un titre court et descriptif (6-10 mots) qui capture l'essence de la demande
25
+ 2. SUMMARY: Les points clés du chemin de résolution (50-100 mots)
26
+
27
+ Cette synthèse servira à retrouver et réappliquer ce pattern de résolution similaire.
28
+ Utilise la même langue que la discussion.
29
+
30
+ Discussion à synthétiser:
31
+ `;
32
+
33
+ /**
34
+ * Génère les messages pour la synthèse de discussion
35
+ * @param discussion - Contenu de la discussion formatée
36
+ * @param capturePrompt - Prompt custom optionnel (remplace DEFAULT_DISCUSSION_PROMPT)
37
+ * @returns [systemPrompt, userPrompt]
38
+ */
39
+ export function getDiscussionSynthesisMessages(
40
+ discussion: string,
41
+ capturePrompt?: string
42
+ ): [string, string] {
43
+ const systemPrompt = capturePrompt || DEFAULT_DISCUSSION_PROMPT;
44
+ return [systemPrompt, discussion];
45
+ }
46
+
47
+ // ══════════════════════════════════════════════════════════════════════════════
48
+ // @deprecated - Ancien système de capture (todo, factual)
49
+ // Ces exports sont conservés pour compatibilité mais ne doivent plus être utilisés
50
+ // ══════════════════════════════════════════════════════════════════════════════
51
+
52
+ /**
53
+ * @deprecated Use DiscussionSynthesisSchema instead
54
+ */
5
55
  export const FactRetrievalSchema_simple = z.object({
6
56
  facts: z
7
57
  .array(z.string())
8
58
  .describe("An array of distinct facts extracted from the conversation."),
9
59
  });
10
60
 
11
-
12
- //1. **Factual memory** stable facts & preferences
13
- //2. **Episodic memory** time‑stamped events / interactions
14
- //3. **Procedural memory** – step‑by‑step know‑how
15
- //4. **Todo memory** – explicit user tasks to remember
16
- //
61
+ /**
62
+ * @deprecated Use DiscussionSynthesisSchema instead
63
+ * Types todo et factual supprimés - seul assistant_preference reste pour compatibilité
64
+ */
17
65
  export const FactRetrievalSchema_extended = z.object({
18
66
  facts: z
19
67
  .array(
20
68
  z.object({
21
69
  fact: z.string().describe("The fact extracted from the conversation."),
22
70
  existing: z.boolean().describe("Whether the fact is already present"),
23
- type: z.enum(["assistant_preference","factual","episodic","procedural","todo"])
24
- .describe(`The type of the fact.
25
- Use 'assistant_preference' for Assistant behavior preferences (style/language/constraints/commands).
26
- Use 'episodic' always for time-based events.
27
- Use 'procedural' for how-to/business questions (e.g., « je veux résilier un bail, comment faire ? »).
28
- Use 'todo' ONLY if the user explicitly asks to save/keep as a todo (e.g., « garde/enregistre en todo », « ajoute un todo »). Do not infer todos.
29
- `),
71
+ type: z.enum(["assistant_preference"])
72
+ .describe(`The type of the fact. Only 'assistant_preference' is supported.`),
30
73
  })
31
74
  )
32
75
  });
33
76
 
34
77
 
35
- // Define Zod schema for memory update output
78
+ /**
79
+ * @deprecated Memory updates are disabled - use capture() for new memories
80
+ */
36
81
  export const MemoryUpdateSchema = z.object({
37
82
  memory: z
38
83
  .array(
@@ -56,21 +101,14 @@ export const MemoryUpdateSchema = z.object({
56
101
  "The reason why you selected this event.",
57
102
  ),
58
103
  type: z
59
- .enum(["factual", "episodic", "todo", "procedural","assistant_preference"])
60
- .describe("Type of the memory. Use 'assistant_preference' for Assistant behavior preferences, 'procedural' for all business processes."),
104
+ .enum(["assistant_preference"])
105
+ .describe("Type of the memory. Only 'assistant_preference' is supported."),
61
106
  }),
62
107
  )
63
108
  .describe(
64
109
  "An array representing the state of memory items after processing new facts.",
65
110
  ),
66
111
  });
67
- /**
68
- * Practical Application:
69
- *
70
- * If the task is "factual" (e.g., "Where do I live?") → retrieve factual memory.
71
- * If the task is temporal or event-based ("What was I doing yesterday?") → retrieve episodic memory.
72
- * If the task is a user task/reminder (e.g., "Add a reminder to call the bank tomorrow") → retrieve todo memory.
73
- */
74
112
  export const MEMORY_STRING_SYSTEM = `# DIRECTIVES FOR MEMORIES
75
113
  - Information stored in memory is always enclosed within the <memories> tag.
76
114
  - Prioritize the latest user message over memories (the user's current question is authoritative).
@@ -82,15 +120,10 @@ export const MEMORY_STRING_SYSTEM = `# DIRECTIVES FOR MEMORIES
82
120
 
83
121
  export const MEMORY_STRING_PREFIX = "Use these contextual memories to guide your response. Prioritize the user's question. Ignore irrelevant memories."
84
122
 
85
- export const MEMORY_STRING_SYSTEM_OLD = `# USER AND MEMORIES PREFERENCES:
86
- - Utilize the provided memories to guide your responses.
87
- - Disregard any memories that are not relevant.
88
- - By default, do not reference this section or the memories in your response.
89
- `;
90
-
91
- // Deprecated: getFactRetrievalMessages_O removed in favor of getFactRetrievalMessages
92
-
93
-
123
+ /**
124
+ * @deprecated Use getDiscussionSynthesisMessages instead
125
+ * Cette fonction est conservée pour compatibilité avec l'ancien système
126
+ */
94
127
  export function getFactRetrievalMessages(
95
128
  parsedMessages: string,
96
129
  customRules: string = "",
@@ -98,36 +131,31 @@ export function getFactRetrievalMessages(
98
131
  ): [string, string] {
99
132
 
100
133
  const injectCustomRules = (customRules:string) => customRules ?`\n# PRE-EXISTING FACTS\n${customRules}` : "";
101
- const systemPrompt = `You are a Personal Information Organizer, specialized in accurately storing facts, user memories, and preferences. You are also an expert in job tasks extraction.
134
+ const systemPrompt = `You are a Personal Information Organizer, specialized in extracting and structuring user facts and preferences for AI personalization. You also handle explicit task extraction (todos only).
102
135
 
103
136
  Filter content before extracting triplets:
104
- - Relevance: keep only statements directly about the user (preferences, identity, actions, experiences) or explicit tasks; drop weather/small talk.
137
+ - Relevance: keep only statements directly about the user (preferences with the AI, identity relevant to personalization, actions/experiences that affect responses) or explicit todos; drop weather/small talk.
105
138
  - Disinterest: if the user rejects the topic (e.g., "cette information ne m'intéresse pas", "not interested"), return {"facts":[]}.
106
- - Business/procedures: if the user asks about processes, regulations, or third-party policies (company workflows, public steps, legal actions), classify as "procedural". This applies even if personal pronouns are used.
107
- - Action requests to the assistant (find/search/locate/call/email/book/reserve) are NOT preferences. Unless the user explicitly asks to save as a todo, do not create a memory for such requests (return {"facts":[]}).
108
-
109
- You must strictly extract {Subject, Predicate, Object} triplets by following these rules (max 12 triplets):
139
+ - Ignore business/process/regulation/company-policy content entirely (no extraction, no memory).
140
+ - Action requests to the assistant (find/search/locate/call/email/book/reserve) are NOT preferences. Unless the user explicitly asks to save as a todo, return {"facts":[]}.
141
+
142
+ You must strictly extract {Subject, Predicate, Object} triplets (max 12):
110
143
  1. Identify named entities, preferences, and meaningful user-related concepts:
111
- - 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").
112
- - Apply explicit, precise, and unambiguous predicates (e.g., "owns", "is located at", "is a", "has function", "causes", etc.).
113
- - Determine the triplet type ("assistant_preference", "procedural", "episodic", "factual", "todo"):
114
- - "assistant_preference": ONLY when the user specifies response style/language/format or interaction constraints.
115
- - "procedural": for how-to/business questions (e.g., « je veux résilier un bail, comment faire ? »).
116
- - "todo": ONLY if the user explicitly requests to save/keep as todo (e.g., « garde/enregistre en todo », « ajoute un todo »). Never infer todo from intent alone.
117
- - If multiple types apply (excluding assistant_preference and todo rules above), priority: procedural > episodic > factual.
118
- - "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.
119
- - "procedural" for business processes (e.g., "Looking for customer John Doe address", "How to create a new contract").
120
- - "factual" for stable user data (except procedural that prevails).
121
-
122
- - Eliminate introductions, sub-facts, detailed repetitive elements, stylistic fillers, or vague statements. General facts always takes precedence over multiple sub-facts (signal vs noise).
123
- - The query intention can include specific preferences about how the Assistant should respond (e.g., "answer concisely", "explain in detail").
124
- - Compress each OUTPUT (fact and reason) 10 words.
125
- - Do not include type labels or annotations inside the fact text (e.g., avoid "(todo)", "(procedural)"). Use the separate 'type' field only.
126
- - DO NOT infer personal facts from third-party informations.
127
- - Treat "**ASSISTANT**:" as responses to enrich context of your reasoning process about the USER query.
128
- 2. Use pronoun "I" instead of "The user" in the subject of the triplet.
129
- 3. Do not output any facts already present in section # PRE-EXISTING FACTS.
130
- - If you find facts already present in section # PRE-EXISTING FACTS, use field "existing" to store them.
144
+ - Extract triplets *about the user* that help AI personalization (preferences, stable facts, explicit todos).
145
+ - Use explicit, precise, unambiguous predicates (e.g., "prefers", "speaks", "is a", "uses").
146
+ - Triplet type {"assistant_preference","factual","todo"} only:
147
+ - "assistant_preference": response style/language/format or interaction constraints.
148
+ - "factual": stable user data relevant to personalization (e.g., language, timezone, tools used).
149
+ - "todo": ONLY if the user explicitly asks to save/keep as todo. Never infer from intent alone.
150
+ - Remove introductions, sub-facts, repetitions, fillers, vague statements; prefer the general fact over details.
151
+ - Each triplet (S,P,O) 10 words total.
152
+ - Do not include type labels inside fact text; use the 'type' field only.
153
+ - Do not infer personal facts from third-party information.
154
+ - Treat "**ASSISTANT**:" as context only; never as a fact source.
155
+
156
+ 2. Use pronoun "I" as the Subject (not "The user").
157
+ 3. Do not output facts already in # PRE-EXISTING FACTS.
158
+ - If found, put them in "existing" (list of matched facts or IDs).
131
159
 
132
160
  ${injectCustomRules(customRules)}
133
161
 
@@ -144,6 +172,9 @@ Remember the following:
144
172
  return [systemPrompt, userPrompt];
145
173
  }
146
174
 
175
+ /**
176
+ * @deprecated Memory updates are disabled by config
177
+ */
147
178
  export function getUpdateMemoryMessages(
148
179
  retrievedOldMemory: Array<{ id: string; text: string }>,
149
180
  newRetrievedFacts: any[],
@@ -153,46 +184,66 @@ export function getUpdateMemoryMessages(
153
184
  const serializeFacts = (facts: any[]) => {
154
185
  if(facts.length === 0) return "";
155
186
  if(facts[0].fact) {
156
- return facts.map((elem) => `* ${elem.fact} (${elem.type})`).join("\n");
187
+ return facts.map((elem) => `- "${elem.fact}" (type:${elem.type})`).join("\n");
157
188
  } else {
158
189
  return facts.join("\n");
159
190
  }
160
191
  }
161
192
  const serializeMemory = (memory: any[]) => {
162
- return memory.map((elem) => `* ${elem.id} - ${elem.text}`).join("\n");
193
+ return memory.map((elem) => `- "${elem.text}" (id:${elem.id})`).join("\n");
163
194
  }
164
195
  return `ROLE:
165
- You are a smart memory manager dedicated on users. You are expert in semantic comparison, RDF inference and boolean logic.
196
+ You are the Memory Manager module of an AI assistant. You are specialized in semantic reasoning, fact consistency, and memory lifecycle operations.
197
+ Your job is to maintain a coherent, contradiction-free knowledge base (long-term memory) of user facts.
166
198
 
167
199
  MISSION:
168
- For each new user fact from "# New Retrieved Facts", you MUSTmerge it into "# Current Memory" by assigning exactly ONE of: ADD, DELETE, UPDATE, or NONE:
169
-
170
- 1. Semantic compare each new facts to memory, for each fact:
171
- - If the new fact **contradicts**, **negates**, **reverses**, **retracts**, or **cancels** the meaning of a memory entry THEN DELETE.
172
- ⛔ You MUST NOT treat this as an UPDATE. Contradictions invalidate the original fact.
173
- - Else If the new fact **specializes** the previous fact (adds precision, extends the detail without changing the core meaning) THEN UPDATE.
174
- - Else If it is **equivalent** → NONE.
175
- - Else If it is **completely new** → ADD.
176
- - Else (default) NONE.
177
- 2. Event mapping from user intent (imperatives prevail):
178
- - DELETE if user asks to remove.
179
- - UPDATE if user asks to change: "mets à jour", "modifie", "corrige", "update", "change", "replace".
180
- - ADD if user asks to add: "ajoute", "add" (including explicit todo adds).
181
- 3. If no match is found:
182
- - Generate a new ID for ADD
183
- 5. Assign the action (IF you can't find a match, restart the process)
200
+ Given:
201
+ 1. A set of **Current Memory** facts (each with unique ID and textual content).
202
+ 2. A set of **New Retrieved Facts** from the user or external sources.
203
+ 3. (Optional) A **User Instruction** indicating explicit intent (add, modify, delete).
204
+
205
+ You must process each new fact individually and decide **exactly one** action: **ADD**, **DELETE**, **UPDATE**, or **NONE**, following these rules, in this order:
206
+
207
+ 1. **User intent override**
208
+ If the User Instruction clearly requests adding, updating, or removal (e.g. “ajoute X”, “mets à jour Y”, “supprime Z”), you **must** respect that and assign the corresponding action for the matching fact, superseding semantic rules.
209
+
210
+ 2. **Semantic consistency check**
211
+ For each new fact:
212
+ - If it **contradicts**, **negates**, or **cancels** an existing memory item, you **DELETE** the memory item.
213
+ - Else, if the new fact is a **specialization** (i.e. same core meaning + additional detail) of an existing one, **UPDATE** that memory (keeping the same ID).
214
+ - Else, if it is **semantically equivalent** (i.e. redundant or paraphrased), assign **NONE** (no change).
215
+ - Else, if it is entirely **new** (no overlap or relation), **ADD** it (generate a new ID).
216
+ - Otherwise (if ambiguous or borderline), assign **NONE** (do not delete).
217
+
218
+ 3. **ID reuse and consistency**
219
+ - For **UPDATE**, reuse the existing memory item’s ID.
220
+ - For **DELETE**, simply remove the item from the final memory output.
221
+ - For **ADD**, generate a new unique ID (e.g. UUID).
222
+ - If memory is initially empty, treat all new facts as **ADD**.
223
+
224
+ 4. **Output formatting**
225
+ Return the updated memory state in strict JSON format. Each memory entry must include:
226
+ - \`id\` (string)
227
+ - \`text\` (string, the pure factual content)
228
+ - Optionally for updates: \`old_text\` (the prior version)
229
+ - *(No extra annotation or type markup in \`text\`)*
230
+
231
+ If there are no facts at all, return \`{"memory": []}\`.
232
+
233
+ *You must not output any other text besides the valid JSON result.*
184
234
 
185
235
  # Output Instructions
186
- - Default user language is ${defaultLanguage}.
236
+ - Default user language is "${defaultLanguage}".
187
237
  - Each memory item must follow this strict format:
188
- - UPDATE also include the previous text: \`old_memory\`.
189
- - ⚠️ Reuse correct IDs for UPDATE and DELETE.
190
- - Generate random IDs for ADDs.
238
+ - UPDATE must also include the previous text: \`old_text\`.
239
+ - Reuse correct IDs for UPDATE.
240
+ - For DELETE, exclude the removed item from the final memory list.
241
+ - Generate random IDs for ADDs (format: UUID).
191
242
  - If memory is empty, treat all facts as ADD.
192
243
  - Without facts, return an empty memory: \`{"memory": []}\`
193
244
  - Memory must strictly reflect valid facts.
194
- - Contradictions, cancellations, negations, or ambiguities must be handled by DELETE.
195
- - The field 'text' must be the pure memory content only: do not add any type markers or parentheses like "(todo)".
245
+ - Contradictions, cancellations, or negations must be handled by DELETE. Ambiguities must be handled by NONE.
246
+ - The field 'text' must be the pure memory content only: do not add any type markers or parentheses.
196
247
 
197
248
  # Current Memory (extract and reuse their IDs for UPDATE or DELETE events):
198
249
  ${serializeMemory(retrievedOldMemory)}
@@ -200,8 +251,7 @@ ${serializeMemory(retrievedOldMemory)}
200
251
  # New Retrieved Facts:
201
252
  ${serializeFacts(newRetrievedFacts)}
202
253
 
203
- # User Instruction:
204
- ${userInstruction || ""}
254
+ # User Instruction: "${userInstruction || ''}"
205
255
 
206
256
  Return the updated memory in JSON format only. Do not output anything else.`;
207
257
  }
@@ -229,8 +279,9 @@ export const getMemoriesAsPrefix = (memories: MemoryItem[]) => {
229
279
  };
230
280
 
231
281
  export const getMemoriesAsSystem = (memories: MemoryItem[], facts?: string[]) => {
282
+ if(!memories || memories.length === 0) return "";
232
283
  const memoryString = memories.map((mem) => `- ${mem.memory}`).concat(facts||[]).join("\n");
233
- return `${MEMORY_STRING_SYSTEM}\n<memories>${memoryString}\n</memories>`;
284
+ return `${MEMORY_STRING_SYSTEM}\n<memories>\n${memoryString}\n</memories>`;
234
285
  }
235
286
 
236
287
  export function parseMessages(messages: string[]): string {