@mnemoai/core 1.1.0

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 (49) hide show
  1. package/index.ts +3395 -0
  2. package/openclaw.plugin.json +815 -0
  3. package/package.json +59 -0
  4. package/src/access-tracker.ts +341 -0
  5. package/src/adapters/README.md +78 -0
  6. package/src/adapters/chroma.ts +206 -0
  7. package/src/adapters/lancedb.ts +237 -0
  8. package/src/adapters/pgvector.ts +218 -0
  9. package/src/adapters/qdrant.ts +191 -0
  10. package/src/adaptive-retrieval.ts +90 -0
  11. package/src/audit-log.ts +238 -0
  12. package/src/chunker.ts +254 -0
  13. package/src/config.ts +271 -0
  14. package/src/decay-engine.ts +238 -0
  15. package/src/embedder.ts +735 -0
  16. package/src/extraction-prompts.ts +339 -0
  17. package/src/license.ts +258 -0
  18. package/src/llm-client.ts +125 -0
  19. package/src/mcp-server.ts +415 -0
  20. package/src/memory-categories.ts +71 -0
  21. package/src/memory-upgrader.ts +388 -0
  22. package/src/migrate.ts +364 -0
  23. package/src/mnemo.ts +142 -0
  24. package/src/noise-filter.ts +97 -0
  25. package/src/noise-prototypes.ts +164 -0
  26. package/src/observability.ts +81 -0
  27. package/src/query-tracker.ts +57 -0
  28. package/src/reflection-event-store.ts +98 -0
  29. package/src/reflection-item-store.ts +112 -0
  30. package/src/reflection-mapped-metadata.ts +84 -0
  31. package/src/reflection-metadata.ts +23 -0
  32. package/src/reflection-ranking.ts +33 -0
  33. package/src/reflection-retry.ts +181 -0
  34. package/src/reflection-slices.ts +265 -0
  35. package/src/reflection-store.ts +602 -0
  36. package/src/resonance-state.ts +85 -0
  37. package/src/retriever.ts +1510 -0
  38. package/src/scopes.ts +375 -0
  39. package/src/self-improvement-files.ts +143 -0
  40. package/src/semantic-gate.ts +121 -0
  41. package/src/session-recovery.ts +138 -0
  42. package/src/smart-extractor.ts +923 -0
  43. package/src/smart-metadata.ts +561 -0
  44. package/src/storage-adapter.ts +153 -0
  45. package/src/store.ts +1330 -0
  46. package/src/tier-manager.ts +189 -0
  47. package/src/tools.ts +1292 -0
  48. package/src/wal-recovery.ts +172 -0
  49. package/test/core.test.mjs +301 -0
@@ -0,0 +1,388 @@
1
+ // SPDX-License-Identifier: LicenseRef-Mnemo-Pro
2
+ /**
3
+ * Memory Upgrader — Convert legacy memories to new smart memory format
4
+ *
5
+ * Legacy memories lack L0/L1/L2 metadata, memory_category (6-category),
6
+ * tier, access_count, and confidence fields. This module enriches them
7
+ * to enable unified memory lifecycle management (decay, tier promotion,
8
+ * smart dedup).
9
+ *
10
+ * Pipeline per memory:
11
+ * 1. Detect legacy format (missing `memory_category` in metadata)
12
+ * 2. Reverse-map 5-category → 6-category
13
+ * 3. Generate L0/L1/L2 via LLM (or fallback to simple rules)
14
+ * 4. Write enriched metadata back via store.update()
15
+ */
16
+
17
+ import type { MemoryStore, MemoryEntry } from "./store.js";
18
+ import type { LlmClient } from "./llm-client.js";
19
+ import type { MemoryCategory } from "./memory-categories.js";
20
+ import type { MemoryTier } from "./memory-categories.js";
21
+ import { buildSmartMetadata, stringifySmartMetadata } from "./smart-metadata.js";
22
+
23
+ // ============================================================================
24
+ // Types
25
+ // ============================================================================
26
+
27
+ export interface UpgradeOptions {
28
+ /** Only report counts without modifying data (default: false) */
29
+ dryRun?: boolean;
30
+ /** Number of memories to process per batch (default: 10) */
31
+ batchSize?: number;
32
+ /** Skip LLM calls; use simple text truncation for L0/L1 (default: false) */
33
+ noLlm?: boolean;
34
+ /** Maximum number of memories to upgrade (default: unlimited) */
35
+ limit?: number;
36
+ /** Scope filter — only upgrade memories in these scopes */
37
+ scopeFilter?: string[];
38
+ /** Logger function */
39
+ log?: (msg: string) => void;
40
+ }
41
+
42
+ export interface UpgradeResult {
43
+ /** Total legacy memories found */
44
+ totalLegacy: number;
45
+ /** Successfully upgraded count */
46
+ upgraded: number;
47
+ /** Skipped (already new format) */
48
+ skipped: number;
49
+ /** Errors encountered */
50
+ errors: string[];
51
+ }
52
+
53
+ interface EnrichedMetadata {
54
+ l0_abstract: string;
55
+ l1_overview: string;
56
+ l2_content: string;
57
+ memory_category: MemoryCategory;
58
+ tier: MemoryTier;
59
+ access_count: number;
60
+ confidence: number;
61
+ last_accessed_at: number;
62
+ upgraded_from: string; // original 5-category
63
+ upgraded_at: number; // timestamp of upgrade
64
+ }
65
+
66
+ // ============================================================================
67
+ // Reverse Category Mapping
68
+ // ============================================================================
69
+
70
+ /**
71
+ * Reverse-map old 5-category → new 6-category.
72
+ *
73
+ * Ambiguous case: `fact` maps to both `profile` and `cases`.
74
+ * Without LLM, defaults to `cases` (conservative).
75
+ * With LLM, the enrichment prompt will determine the correct category.
76
+ */
77
+ function reverseMapCategory(
78
+ oldCategory: MemoryEntry["category"],
79
+ text: string,
80
+ ): MemoryCategory {
81
+ switch (oldCategory) {
82
+ case "preference":
83
+ return "preferences";
84
+ case "entity":
85
+ return "entities";
86
+ case "decision":
87
+ return "events";
88
+ case "other":
89
+ return "patterns";
90
+ case "fact":
91
+ // Heuristic: if text looks like personal identity info, map to profile
92
+ if (
93
+ /\b(my |i am |i'm |name is |叫我|我的|我是)\b/i.test(text) &&
94
+ text.length < 200
95
+ ) {
96
+ return "profile";
97
+ }
98
+ return "cases";
99
+ default:
100
+ return "patterns";
101
+ }
102
+ }
103
+
104
+ // ============================================================================
105
+ // LLM Upgrade Prompt
106
+ // ============================================================================
107
+
108
+ function buildUpgradePrompt(text: string, category: MemoryCategory): string {
109
+ return `You are a memory librarian. Given a raw memory text and its category, produce a structured 3-layer summary.
110
+
111
+ **Category**: ${category}
112
+
113
+ **Raw memory text**:
114
+ """
115
+ ${text.slice(0, 2000)}
116
+ """
117
+
118
+ Return ONLY valid JSON (no markdown fences):
119
+ {
120
+ "l0_abstract": "One sentence (≤30 words) summarizing the core fact/preference/event",
121
+ "l1_overview": "A structured markdown summary (2-5 bullet points)",
122
+ "l2_content": "The full original text, cleaned up if needed",
123
+ "resolved_category": "${category}"
124
+ }
125
+
126
+ Rules:
127
+ - l0_abstract must be a single concise sentence, suitable as a search index key
128
+ - l1_overview should use markdown bullet points to structure the information
129
+ - l2_content should preserve the original meaning; may clean up formatting
130
+ - resolved_category: if the text is clearly about personal identity/profile info (name, age, role, etc.), set to "profile"; if it's a reusable problem-solution pair, set to "cases"; otherwise keep "${category}"
131
+ - Respond in the SAME language as the raw memory text`;
132
+ }
133
+
134
+ // ============================================================================
135
+ // Simple (No-LLM) Enrichment
136
+ // ============================================================================
137
+
138
+ function simpleEnrich(
139
+ text: string,
140
+ category: MemoryCategory,
141
+ ): Pick<EnrichedMetadata, "l0_abstract" | "l1_overview" | "l2_content"> {
142
+ // L0: first sentence or first 80 chars
143
+ const firstSentence = text.match(/^[^.!?。!?\n]+[.!?。!?]?/)?.[0] || text;
144
+ const l0 = firstSentence.slice(0, 100).trim();
145
+
146
+ // L1: structured as a single bullet
147
+ const l1 = `- ${l0}`;
148
+
149
+ // L2: full text
150
+ return {
151
+ l0_abstract: l0,
152
+ l1_overview: l1,
153
+ l2_content: text,
154
+ };
155
+ }
156
+
157
+ // ============================================================================
158
+ // Memory Upgrader
159
+ // ============================================================================
160
+
161
+ export class MemoryUpgrader {
162
+ private log: (msg: string) => void;
163
+
164
+ constructor(
165
+ private store: MemoryStore,
166
+ private llm: LlmClient | null,
167
+ private options: UpgradeOptions = {},
168
+ ) {
169
+ this.log = options.log ?? console.log;
170
+ }
171
+
172
+ /**
173
+ * Check if a memory entry is in legacy format (needs upgrade).
174
+ * Legacy = no metadata, or metadata lacks `memory_category`.
175
+ */
176
+ isLegacyMemory(entry: MemoryEntry): boolean {
177
+ if (!entry.metadata) return true;
178
+ try {
179
+ const meta = JSON.parse(entry.metadata);
180
+ // If it has memory_category, it was created by SmartExtractor → new format
181
+ return !meta.memory_category;
182
+ } catch {
183
+ return true;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Scan and count legacy memories without modifying them.
189
+ */
190
+ async countLegacy(scopeFilter?: string[]): Promise<{
191
+ total: number;
192
+ legacy: number;
193
+ byCategory: Record<string, number>;
194
+ }> {
195
+ const allMemories = await this.store.list(scopeFilter, undefined, 10000, 0);
196
+ let legacy = 0;
197
+ const byCategory: Record<string, number> = {};
198
+
199
+ for (const entry of allMemories) {
200
+ if (this.isLegacyMemory(entry)) {
201
+ legacy++;
202
+ byCategory[entry.category] = (byCategory[entry.category] || 0) + 1;
203
+ }
204
+ }
205
+
206
+ return { total: allMemories.length, legacy, byCategory };
207
+ }
208
+
209
+ /**
210
+ * Main upgrade entry point.
211
+ * Scans all memories, filters legacy ones, and enriches them.
212
+ */
213
+ async upgrade(options: UpgradeOptions = {}): Promise<UpgradeResult> {
214
+ const batchSize = options.batchSize ?? this.options.batchSize ?? 10;
215
+ const noLlm = options.noLlm ?? this.options.noLlm ?? false;
216
+ const dryRun = options.dryRun ?? this.options.dryRun ?? false;
217
+ const limit = options.limit ?? this.options.limit;
218
+
219
+ const result: UpgradeResult = {
220
+ totalLegacy: 0,
221
+ upgraded: 0,
222
+ skipped: 0,
223
+ errors: [],
224
+ };
225
+
226
+ // Load all memories
227
+ this.log("memory-upgrader: scanning memories...");
228
+ const allMemories = await this.store.list(
229
+ options.scopeFilter ?? this.options.scopeFilter,
230
+ undefined,
231
+ 10000,
232
+ 0,
233
+ );
234
+
235
+ // Filter legacy memories
236
+ const legacyMemories = allMemories.filter((m) => this.isLegacyMemory(m));
237
+ result.totalLegacy = legacyMemories.length;
238
+ result.skipped = allMemories.length - legacyMemories.length;
239
+
240
+ if (legacyMemories.length === 0) {
241
+ this.log("memory-upgrader: no legacy memories found — all memories are already in new format");
242
+ return result;
243
+ }
244
+
245
+ this.log(
246
+ `memory-upgrader: found ${legacyMemories.length} legacy memories out of ${allMemories.length} total`,
247
+ );
248
+
249
+ if (dryRun) {
250
+ const byCategory: Record<string, number> = {};
251
+ for (const m of legacyMemories) {
252
+ byCategory[m.category] = (byCategory[m.category] || 0) + 1;
253
+ }
254
+ this.log(
255
+ `memory-upgrader: [DRY-RUN] would upgrade ${legacyMemories.length} memories`,
256
+ );
257
+ this.log(`memory-upgrader: [DRY-RUN] breakdown: ${JSON.stringify(byCategory)}`);
258
+ return result;
259
+ }
260
+
261
+ // Process in batches
262
+ const toProcess = limit
263
+ ? legacyMemories.slice(0, limit)
264
+ : legacyMemories;
265
+
266
+ for (let i = 0; i < toProcess.length; i += batchSize) {
267
+ const batch = toProcess.slice(i, i + batchSize);
268
+ this.log(
269
+ `memory-upgrader: processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(toProcess.length / batchSize)} (${batch.length} memories)`,
270
+ );
271
+
272
+ for (const entry of batch) {
273
+ try {
274
+ await this.upgradeEntry(entry, noLlm);
275
+ result.upgraded++;
276
+ } catch (err) {
277
+ const errMsg = `Failed to upgrade ${entry.id}: ${String(err)}`;
278
+ result.errors.push(errMsg);
279
+ this.log(`memory-upgrader: ERROR — ${errMsg}`);
280
+ }
281
+ }
282
+
283
+ // Progress report
284
+ this.log(
285
+ `memory-upgrader: progress — ${result.upgraded} upgraded, ${result.errors.length} errors`,
286
+ );
287
+ }
288
+
289
+ this.log(
290
+ `memory-upgrader: upgrade complete — ${result.upgraded} upgraded, ${result.skipped} already new, ${result.errors.length} errors`,
291
+ );
292
+ return result;
293
+ }
294
+
295
+ /**
296
+ * Upgrade a single legacy memory entry.
297
+ */
298
+ private async upgradeEntry(
299
+ entry: MemoryEntry,
300
+ noLlm: boolean,
301
+ ): Promise<void> {
302
+ // Step 1: Reverse-map category
303
+ let newCategory = reverseMapCategory(entry.category, entry.text);
304
+
305
+ // Step 2: Generate L0/L1/L2
306
+ let enriched: Pick<EnrichedMetadata, "l0_abstract" | "l1_overview" | "l2_content">;
307
+
308
+ if (!noLlm && this.llm) {
309
+ try {
310
+ const prompt = buildUpgradePrompt(entry.text, newCategory);
311
+ const llmResult = await this.llm.completeJson<{
312
+ l0_abstract: string;
313
+ l1_overview: string;
314
+ l2_content: string;
315
+ resolved_category?: string;
316
+ }>(prompt);
317
+
318
+ if (!llmResult) {
319
+ throw new Error("LLM returned null");
320
+ }
321
+
322
+ enriched = {
323
+ l0_abstract: llmResult.l0_abstract || simpleEnrich(entry.text, newCategory).l0_abstract,
324
+ l1_overview: llmResult.l1_overview || simpleEnrich(entry.text, newCategory).l1_overview,
325
+ l2_content: llmResult.l2_content || entry.text,
326
+ };
327
+
328
+ // LLM may have resolved the ambiguous fact→profile/cases
329
+ if (llmResult.resolved_category) {
330
+ const validCategories = new Set([
331
+ "profile", "preferences", "entities", "events", "cases", "patterns",
332
+ ]);
333
+ if (validCategories.has(llmResult.resolved_category)) {
334
+ newCategory = llmResult.resolved_category as MemoryCategory;
335
+ }
336
+ }
337
+ } catch (err) {
338
+ this.log(
339
+ `memory-upgrader: LLM enrichment failed for ${entry.id}, falling back to simple — ${String(err)}`,
340
+ );
341
+ enriched = simpleEnrich(entry.text, newCategory);
342
+ }
343
+ } else {
344
+ enriched = simpleEnrich(entry.text, newCategory);
345
+ }
346
+
347
+ // Step 3: Build enriched metadata
348
+ const existingMeta = entry.metadata ? (() => {
349
+ try { return JSON.parse(entry.metadata!); } catch { return {}; }
350
+ })() : {};
351
+
352
+ const newMetadata: EnrichedMetadata = {
353
+ ...buildSmartMetadata(
354
+ { ...entry, metadata: JSON.stringify(existingMeta) },
355
+ {
356
+ l0_abstract: enriched.l0_abstract,
357
+ l1_overview: enriched.l1_overview,
358
+ l2_content: enriched.l2_content,
359
+ memory_category: newCategory,
360
+ tier: "working" as MemoryTier,
361
+ access_count: 0,
362
+ confidence: 0.7,
363
+ },
364
+ ),
365
+ upgraded_from: entry.category,
366
+ upgraded_at: Date.now(),
367
+ };
368
+
369
+ // Step 4: Update the memory entry
370
+ await this.store.update(entry.id, {
371
+ // Update text to L0 abstract for better search indexing
372
+ text: enriched.l0_abstract,
373
+ metadata: stringifySmartMetadata(newMetadata),
374
+ });
375
+ }
376
+ }
377
+
378
+ // ============================================================================
379
+ // Factory
380
+ // ============================================================================
381
+
382
+ export function createMemoryUpgrader(
383
+ store: MemoryStore,
384
+ llm: LlmClient | null,
385
+ options: UpgradeOptions = {},
386
+ ): MemoryUpgrader {
387
+ return new MemoryUpgrader(store, llm, options);
388
+ }