@yamo/memory-mesh 3.1.1 → 3.1.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
@@ -1,4 +1,4 @@
1
- # MemoryMesh (v3.1.0 Singularity Edition)
1
+ # MemoryMesh (Singularity Edition)
2
2
 
3
3
  Portable, semantic memory system for AI agents with automatic Layer 0 sanitization and **Autonomous Kernel Integration**.
4
4
 
@@ -7,7 +7,7 @@ Built on the [YAMO Protocol](https://github.com/yamo-protocol) for transparent a
7
7
  ## 🌌 Singularity Edition Highlights
8
8
 
9
9
  - **Intelligent Installer**: Automatically detects OpenClaw workspaces and performs a full kernel upgrade.
10
- - **YAMO Unified OS (v3.0)**: Natively includes the complete Macro (Specification) and Micro (Execution) workflow suite.
10
+ - **YAMO Unified OS**: Natively includes the complete Macro (Specification) and Micro (Execution) workflow suite.
11
11
  - **Ghost Protection**: Self-healing `AGENTS.md` injection to prevent narrative drift and maintain cognitive alignment.
12
12
  - **Autonomous Bootstrap**: Deploys `BOOTSTRAP.yamo` as the primary agent entry point for protocol-native execution.
13
13
  - **Surgical Deployment**: Intelligently skips global CLI clutter when working in project-specific modes.
@@ -33,7 +33,7 @@ npm install @yamo/memory-mesh
33
33
 
34
34
  ### 🚀 Singularity Setup (OpenClaw)
35
35
 
36
- To upgrade your workspace to v3.1 fidelity:
36
+ To upgrade your workspace to full fidelity:
37
37
 
38
38
  ```bash
39
39
  npx memory-mesh-setup
@@ -56,11 +56,12 @@ program
56
56
  // 2. Directory Ingest Command
57
57
  program
58
58
  .command('ingest-dir')
59
- .description('Recursively ingest a directory of files')
59
+ .alias('pull')
60
+ .description('Recursively ingest a directory of files (Smart Ingest)')
60
61
  .argument('<path>', 'Directory path to ingest')
61
- .option('-e, --extension <ext>', 'Filter by file extension (e.g., .yamo, .md)', '')
62
- .option('-t, --type <type>', 'Memory type for all files', 'documentation')
63
- .option('-r, --recursive', 'Ingest subdirectories', false)
62
+ .option('-e, --extension <ext>', 'Filter by file extension', '')
63
+ .option('-t, --type <type>', 'Memory type', 'documentation')
64
+ .option('-r, --recursive', 'Ingest subdirectories', true)
64
65
  .action(async (dirPath, options) => {
65
66
  const mesh = new MemoryMesh();
66
67
  try {
@@ -155,4 +156,121 @@ program
155
156
  }
156
157
  });
157
158
 
159
+ // 4. Get Command
160
+ program
161
+ .command('get')
162
+ .description('Retrieve a single memory by ID')
163
+ .requiredOption('--id <id>', 'Memory record ID')
164
+ .action(async (options) => {
165
+ const mesh = new MemoryMesh();
166
+ try {
167
+ await mesh.init();
168
+ const record = await mesh.get(options.id);
169
+ if (!record) {
170
+ process.stdout.write(`[MemoryMesh] No record found with id: ${options.id}\n`);
171
+ process.exit(1);
172
+ }
173
+ const meta = typeof record.metadata === 'string' ? JSON.parse(record.metadata) : record.metadata;
174
+ process.stdout.write(`[MemoryMesh] id: ${record.id}\n`);
175
+ process.stdout.write(`[MemoryMesh] content: ${record.content}\n`);
176
+ process.stdout.write(`[MemoryMesh] type: ${meta?.type ?? 'unknown'}\n`);
177
+ process.stdout.write(`[MemoryMesh] created_at: ${record.created_at}\n`);
178
+ process.stdout.write(`[MemoryMesh] metadata: ${JSON.stringify(meta, null, 2)}\n`);
179
+ } catch (err) {
180
+ console.error(`❌ Error: ${err.message}`);
181
+ process.exit(1);
182
+ } finally {
183
+ await mesh.close();
184
+ }
185
+ });
186
+
187
+ // 5. Delete Command
188
+ program
189
+ .command('delete')
190
+ .description('Delete a memory by ID')
191
+ .requiredOption('--id <id>', 'Memory record ID to delete')
192
+ .action(async (options) => {
193
+ const mesh = new MemoryMesh();
194
+ try {
195
+ await mesh.init();
196
+ await mesh.delete(options.id);
197
+ process.stdout.write(`[MemoryMesh] Deleted record ${options.id}\n`);
198
+ } catch (err) {
199
+ console.error(`❌ Error: ${err.message}`);
200
+ process.exit(1);
201
+ } finally {
202
+ await mesh.close();
203
+ }
204
+ });
205
+
206
+ // 6. Reflect Command
207
+ program
208
+ .command('reflect')
209
+ .description('Query distilled lessons from memory (wisdom distillation)')
210
+ .option('--topic <text>', 'Topic or query to reflect on', '')
211
+ .option('--lookback <n>', 'Limit results to this many lessons', '10')
212
+ .action(async (options) => {
213
+ const mesh = new MemoryMesh();
214
+ try {
215
+ await mesh.init();
216
+ const query = options.topic || 'lessons learned patterns errors fixes';
217
+ const limit = parseInt(options.lookback) || 10;
218
+ const lessons = await mesh.queryLessons(query, { limit });
219
+ if (lessons.length === 0) {
220
+ process.stdout.write(`[MemoryMesh] No lessons found${options.topic ? ` for topic: ${options.topic}` : ''}.\n`);
221
+ } else {
222
+ process.stdout.write(`[MemoryMesh] Reflecting on ${lessons.length} lesson(s):\n\n`);
223
+ for (const lesson of lessons) {
224
+ process.stdout.write(` scope: ${lesson.applicableScope}\n`);
225
+ process.stdout.write(` rule: ${lesson.preventativeRule}\n`);
226
+ process.stdout.write(` confidence: ${lesson.ruleConfidence}\n`);
227
+ process.stdout.write('\n');
228
+ }
229
+ }
230
+ } catch (err) {
231
+ console.error(`❌ Error: ${err.message}`);
232
+ process.exit(1);
233
+ } finally {
234
+ await mesh.close();
235
+ }
236
+ });
237
+
238
+ // S-MORA command (RFC-0012)
239
+ program
240
+ .command('smora')
241
+ .description('S-MORA enhanced retrieval: HyDE-Lite + multi-channel + heritage-aware reranking (RFC-0012)')
242
+ .argument('<query>', 'The retrieval query')
243
+ .option('-l, --limit <n>', 'Max results to return', '10')
244
+ .option('--no-hyde', 'Disable HyDE-Lite query expansion (Layer 1)')
245
+ .option('--intent <items>', 'Session intent chain for heritage bonus (comma-separated)', '')
246
+ .option('--json', 'Output raw JSON response')
247
+ .action(async (query, options) => {
248
+ const mesh = new MemoryMesh();
249
+ try {
250
+ await mesh.init();
251
+ const sessionIntent = options.intent
252
+ ? options.intent.split(',').map((s) => s.trim()).filter(Boolean)
253
+ : [];
254
+ const resp = await mesh.smora(query, {
255
+ limit: parseInt(options.limit) || 10,
256
+ enableHyDE: options.hyde !== false,
257
+ sessionIntent,
258
+ });
259
+ if (options.json) {
260
+ process.stdout.write(JSON.stringify(resp, null, 2) + '\n');
261
+ } else {
262
+ const p = resp.pipeline;
263
+ process.stdout.write(`[S-MORA] ${resp.results.length} result(s) | HyDE:${p.queryExpanded} heritage:${p.heritageAware} latency:${p.latencyMs}ms\n\n`);
264
+ for (const r of resp.results) {
265
+ process.stdout.write(` [${r.rrfRank}] score:${r.score.toFixed(3)} | ${r.content.slice(0, 100)}${r.content.length > 100 ? '...' : ''}\n`);
266
+ }
267
+ }
268
+ } catch (err) {
269
+ console.error(`❌ Error: ${err.message}`);
270
+ process.exit(1);
271
+ } finally {
272
+ await mesh.close();
273
+ }
274
+ });
275
+
158
276
  program.parse();
package/bin/setup.js CHANGED
@@ -262,6 +262,7 @@ async function installTools(env) {
262
262
  }
263
263
 
264
264
  function showUsage(env) {
265
+ const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf-8'));
265
266
  log('\n✨ Setup Complete!', 'bright');
266
267
 
267
268
  if (env.isOpenClaw) {
@@ -269,11 +270,13 @@ function showUsage(env) {
269
270
  log(' • Ghost Protection active in AGENTS.md', 'cyan');
270
271
  log(' • Kernel entry point: BOOTSTRAP.yamo', 'cyan');
271
272
  log(' • Modules deployed to: yamo-native-agent/', 'cyan');
273
+ log(` • Version: ${pkg.version}`, 'cyan');
272
274
  log(' • REFRESH SESSION to activate v3.0 fidelity.', 'cyan');
273
275
  } else {
274
276
  log('\n📚 STANDARD MODE:', 'blue');
275
277
  log(' • Skills installed to Claude/Gemini', 'blue');
276
278
  log(' • Use /yamo-super to start workflows', 'blue');
279
+ log(` • Version: ${pkg.version}`, 'blue');
277
280
  }
278
281
 
279
282
  log('\n🔧 Tools:', 'blue');
@@ -1,3 +1,32 @@
1
+ /** RFC-0012 S-MORA types */
2
+ export interface SMORAOptions {
3
+ limit?: number;
4
+ retrievalLimit?: number;
5
+ sessionIntent?: string[];
6
+ enableSynthesis?: boolean;
7
+ enableHyDE?: boolean;
8
+ useCache?: boolean;
9
+ }
10
+ export interface SMORAResult {
11
+ id: string;
12
+ content: string;
13
+ metadata: Record<string, unknown>;
14
+ score: number;
15
+ semanticScore: number;
16
+ heritageBonus: number;
17
+ recencyDecay: number;
18
+ rrfRank: number;
19
+ }
20
+ export interface SMORAResponse {
21
+ results: SMORAResult[];
22
+ synthesis?: string;
23
+ pipeline: {
24
+ queryExpanded: boolean;
25
+ heritageAware: boolean;
26
+ synthesized: boolean;
27
+ latencyMs: number;
28
+ };
29
+ }
1
30
  /**
2
31
  * MemoryMesh class for managing vector memory storage
3
32
  */
@@ -228,7 +257,11 @@ export declare class MemoryMesh {
228
257
  * Note: YAMO emission is non-critical - failures are logged but don't throw
229
258
  * to prevent disrupting the main operation.
230
259
  */
231
- _emitYamoBlock(operationType: any, memoryId: any, yamoText: any): Promise<void>;
260
+ _emitYamoBlock(operationType: any, memoryId: any, yamoText: any, heritage?: {
261
+ intentChain: string[];
262
+ hypotheses: string[];
263
+ rationales: string[];
264
+ }): Promise<void>;
232
265
  /**
233
266
  * Search memory using hybrid vector + keyword search with Reciprocal Rank Fusion (RRF).
234
267
  *
@@ -284,6 +317,82 @@ export declare class MemoryMesh {
284
317
  created_at: any;
285
318
  updated_at: any;
286
319
  }>;
320
+ /**
321
+ * Delete a memory entry by ID.
322
+ */
323
+ delete(id: string): Promise<void>;
324
+ /**
325
+ * Distill a LessonLearned block (RFC-0011 §3.5).
326
+ * Idempotent: same patternId + equal/higher confidence returns existing.
327
+ */
328
+ distillLesson(context: {
329
+ situation: string;
330
+ errorPattern: string;
331
+ oversight: string;
332
+ fix: string;
333
+ preventativeRule: string;
334
+ severity?: string;
335
+ applicableScope: string;
336
+ inverseLesson?: string;
337
+ confidence?: number;
338
+ }): Promise<{
339
+ lessonId: string;
340
+ patternId: string;
341
+ severity: string;
342
+ preventativeRule: string;
343
+ ruleConfidence: number;
344
+ applicableScope: string;
345
+ wireFormat: string;
346
+ memoryId: string;
347
+ }>;
348
+ /**
349
+ * Query lessons from memory (RFC-0011 §4.1).
350
+ */
351
+ queryLessons(query?: string, options?: {
352
+ limit?: number;
353
+ }): Promise<any[]>;
354
+ /**
355
+ * Update a memory entry's heritage_chain (RFC-0011 §8).
356
+ */
357
+ insertHeritage(memoryId: string, heritage: {
358
+ intentChain: string[];
359
+ hypotheses: string[];
360
+ rationales: string[];
361
+ }): Promise<void>;
362
+ /**
363
+ * Return all memories whose lesson_pattern_id matches patternId (RFC-0011 §4.1).
364
+ */
365
+ getMemoriesByPattern(patternId: string): Promise<any[]>;
366
+ /**
367
+ * S-MORA: Singularity Memory-Oriented Retrieval Augmentation (RFC-0012)
368
+ * 5-layer pipeline: Scrubbing → HyDE-Lite → Multi-channel retrieval → RRF → Heritage-aware reranking
369
+ */
370
+ smora(query: string, options?: {
371
+ limit?: number;
372
+ retrievalLimit?: number;
373
+ sessionIntent?: string[];
374
+ enableSynthesis?: boolean;
375
+ enableHyDE?: boolean;
376
+ useCache?: boolean;
377
+ }): Promise<{
378
+ results: Array<{
379
+ id: string;
380
+ content: string;
381
+ metadata: Record<string, unknown>;
382
+ score: number;
383
+ semanticScore: number;
384
+ heritageBonus: number;
385
+ recencyDecay: number;
386
+ rrfRank: number;
387
+ }>;
388
+ synthesis?: string;
389
+ pipeline: {
390
+ queryExpanded: boolean;
391
+ heritageAware: boolean;
392
+ synthesized: boolean;
393
+ latencyMs: number;
394
+ };
395
+ }>;
287
396
  getAll(options?: {}): Promise<any>;
288
397
  stats(): Promise<{
289
398
  count: number;
@@ -1063,7 +1063,7 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1063
1063
  * Note: YAMO emission is non-critical - failures are logged but don't throw
1064
1064
  * to prevent disrupting the main operation.
1065
1065
  */
1066
- async _emitYamoBlock(operationType, memoryId, yamoText) {
1066
+ async _emitYamoBlock(operationType, memoryId, yamoText, heritage) {
1067
1067
  if (!this.yamoTable) {
1068
1068
  return;
1069
1069
  }
@@ -1081,6 +1081,7 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1081
1081
  metadata: JSON.stringify({
1082
1082
  memory_id: memoryId || null,
1083
1083
  timestamp: new Date().toISOString(),
1084
+ ...(heritage ? { heritage_chain: heritage } : {}),
1084
1085
  }),
1085
1086
  },
1086
1087
  ]);
@@ -1310,6 +1311,310 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1310
1311
  }
1311
1312
  : null;
1312
1313
  }
1314
+ /**
1315
+ * Delete a memory entry by ID.
1316
+ */
1317
+ async delete(id) {
1318
+ await this.init();
1319
+ if (!this.client) {
1320
+ throw new Error("Database client not initialized");
1321
+ }
1322
+ try {
1323
+ await this.client.delete(id);
1324
+ this.keywordSearch?.remove?.(id);
1325
+ }
1326
+ catch (error) {
1327
+ if (error instanceof Error && error.message.includes("not found"))
1328
+ return;
1329
+ throw error;
1330
+ }
1331
+ }
1332
+ /**
1333
+ * Distill a LessonLearned block (RFC-0011 §3.5).
1334
+ * Idempotent: same patternId + equal/higher confidence returns existing.
1335
+ */
1336
+ async distillLesson(context) {
1337
+ await this.init();
1338
+ const { situation, errorPattern, oversight, fix, preventativeRule, severity = "medium", applicableScope, inverseLesson = "", confidence = 0.7, } = context;
1339
+ const patternId = crypto.createHash("sha256")
1340
+ .update(errorPattern + applicableScope).digest("hex").slice(0, 16);
1341
+ // Idempotency check
1342
+ const existing = await this.getMemoriesByPattern(patternId);
1343
+ if (existing.length > 0) {
1344
+ const meta = typeof existing[0].metadata === "string"
1345
+ ? JSON.parse(existing[0].metadata) : existing[0].metadata;
1346
+ if ((meta.rule_confidence ?? 0) >= confidence) {
1347
+ return {
1348
+ lessonId: meta.lesson_id, patternId, severity: meta.severity || severity,
1349
+ preventativeRule: meta.preventative_rule || preventativeRule,
1350
+ ruleConfidence: meta.rule_confidence, applicableScope: meta.applicable_scope || applicableScope,
1351
+ wireFormat: meta.yamo_wire_format || "", memoryId: existing[0].id,
1352
+ };
1353
+ }
1354
+ }
1355
+ const lessonId = `lesson_${Date.now()}_${crypto.randomBytes(3).toString("hex")}`;
1356
+ const timestamp = new Date().toISOString();
1357
+ const wireFormat = [
1358
+ `agent: MemoryMesh_${this.agentId};`,
1359
+ `intent: distill_wisdom_from_execution;`,
1360
+ `context:`,
1361
+ ` original_context;${situation.replace(/;/g, ",")};`,
1362
+ ` error_pattern;${patternId};`,
1363
+ ` severity;${severity};`,
1364
+ ` timestamp;${timestamp};`,
1365
+ `constraints:`,
1366
+ ` hypothesis;This lesson prevents recurrence of similar failures;`,
1367
+ ` hypothesis_confidence;${confidence};`,
1368
+ `priority: high;`,
1369
+ `output:`,
1370
+ ` lesson_id;${lessonId};`,
1371
+ ` oversight_description;${oversight.replace(/;/g, ",")};`,
1372
+ ` preventative_rule;${preventativeRule.replace(/;/g, ",")};`,
1373
+ ` rule_confidence;${confidence};`,
1374
+ `meta:`,
1375
+ ` rationale;${fix.replace(/;/g, ",")};`,
1376
+ ` applicability_scope;${applicableScope.replace(/;/g, ",")};`,
1377
+ ` inverse_lesson;${inverseLesson.replace(/;/g, ",")};`,
1378
+ ` confidence;${confidence};`,
1379
+ `log: lesson_learned;timestamp;${timestamp};pattern;${patternId};severity;${severity};id;${lessonId};`,
1380
+ `handoff: SubconsciousReflector;`,
1381
+ ].join("\n");
1382
+ const lessonContent = `[LESSON:${patternId}] ${oversight} | Rule: ${preventativeRule} | Scope: ${applicableScope}`;
1383
+ const lessonMetadata = {
1384
+ type: "lesson", tags: ["#lesson_learned"], lesson_id: lessonId,
1385
+ lesson_pattern_id: patternId, severity, oversight, preventative_rule: preventativeRule,
1386
+ rule_confidence: confidence, applicable_scope: applicableScope, inverse_lesson: inverseLesson,
1387
+ yamo_wire_format: wireFormat, source: "distillLesson",
1388
+ };
1389
+ const mem = await this.add(lessonContent, lessonMetadata);
1390
+ if (this.enableYamo) {
1391
+ this._emitYamoBlock("lesson", mem.id, wireFormat).catch(() => { });
1392
+ }
1393
+ return { lessonId, patternId, severity, preventativeRule, ruleConfidence: confidence, applicableScope, wireFormat, memoryId: mem.id };
1394
+ }
1395
+ /**
1396
+ * Query lessons from memory (RFC-0011 §4.1).
1397
+ */
1398
+ async queryLessons(query = "", options = {}) {
1399
+ await this.init();
1400
+ const limit = options.limit || 10;
1401
+ const all = await this.getAll({ limit: 1000 });
1402
+ const lessons = all.filter((r) => {
1403
+ try {
1404
+ const meta = typeof r.metadata === "string" ? JSON.parse(r.metadata) : r.metadata;
1405
+ return meta.type === "lesson" || (Array.isArray(meta.tags) && meta.tags.includes("#lesson_learned"));
1406
+ }
1407
+ catch {
1408
+ return false;
1409
+ }
1410
+ });
1411
+ let scored = lessons;
1412
+ if (query) {
1413
+ const q = query.toLowerCase();
1414
+ scored = lessons.map((r) => ({
1415
+ ...r,
1416
+ _score: (r.content?.toLowerCase().includes(q) ? 2 : 0) +
1417
+ (JSON.stringify(r.metadata).toLowerCase().includes(q) ? 1 : 0),
1418
+ })).sort((a, b) => b._score - a._score);
1419
+ }
1420
+ return scored.slice(0, limit).map((r) => {
1421
+ const meta = typeof r.metadata === "string" ? JSON.parse(r.metadata) : r.metadata;
1422
+ return {
1423
+ lessonId: meta.lesson_id || r.id, patternId: meta.lesson_pattern_id || "",
1424
+ severity: meta.severity || "medium", preventativeRule: meta.preventative_rule || "",
1425
+ ruleConfidence: meta.rule_confidence ?? 0, applicableScope: meta.applicable_scope || "",
1426
+ wireFormat: meta.yamo_wire_format || "", memoryId: r.id,
1427
+ };
1428
+ });
1429
+ }
1430
+ /**
1431
+ * Update a memory entry's heritage_chain (RFC-0011 §8).
1432
+ */
1433
+ async insertHeritage(memoryId, heritage) {
1434
+ await this.init();
1435
+ if (!this.client)
1436
+ throw new Error("Database client not initialized");
1437
+ try {
1438
+ const record = await this.client.getById(memoryId);
1439
+ if (!record)
1440
+ return;
1441
+ const existingMeta = typeof record.metadata === "string"
1442
+ ? JSON.parse(record.metadata) : (record.metadata || {});
1443
+ await this.client.update(memoryId, {
1444
+ metadata: JSON.stringify({ ...existingMeta, heritage_chain: JSON.stringify(heritage) }),
1445
+ });
1446
+ // Emit RFC-0007 §5 heritage block
1447
+ if (this.enableYamo) {
1448
+ const ts = new Date().toISOString();
1449
+ const heritageBlock = [
1450
+ `agent: MemoryMesh_${this.agentId};`,
1451
+ `intent: record_heritage_chain;`,
1452
+ `context:`,
1453
+ ` memory_id;${memoryId};`,
1454
+ ` intent_chain;${heritage.intentChain.join(",")};`,
1455
+ ` timestamp;${ts};`,
1456
+ `output:`,
1457
+ ` heritage_recorded;true;`,
1458
+ `log: heritage_inserted;memory;${memoryId};timestamp;${ts};`,
1459
+ `handoff: End;`,
1460
+ ].join("\n");
1461
+ this._emitYamoBlock("heritage", memoryId, heritageBlock, heritage).catch(() => { });
1462
+ }
1463
+ }
1464
+ catch (error) {
1465
+ if (error instanceof Error && error.message.includes("not found"))
1466
+ return;
1467
+ throw error;
1468
+ }
1469
+ }
1470
+ /**
1471
+ * Return all memories whose lesson_pattern_id matches patternId (RFC-0011 §4.1).
1472
+ */
1473
+ async getMemoriesByPattern(patternId) {
1474
+ await this.init();
1475
+ const all = await this.getAll({ limit: 1000 });
1476
+ return all.filter((r) => {
1477
+ try {
1478
+ const meta = typeof r.metadata === "string" ? JSON.parse(r.metadata) : r.metadata;
1479
+ return meta.lesson_pattern_id === patternId;
1480
+ }
1481
+ catch {
1482
+ return false;
1483
+ }
1484
+ });
1485
+ }
1486
+ /**
1487
+ * S-MORA: Singularity Memory-Oriented Retrieval Augmentation (RFC-0012)
1488
+ * 5-layer pipeline: Scrubbing → HyDE-Lite → Multi-channel retrieval → RRF → Heritage-aware reranking
1489
+ */
1490
+ async smora(query, options = {}) {
1491
+ await this.init();
1492
+ const t0 = Date.now();
1493
+ const { limit = 10, retrievalLimit = 30, sessionIntent = [], enableSynthesis = false, enableHyDE = true, } = options;
1494
+ // Layer 0: scrub query
1495
+ let scrubbed = query;
1496
+ try {
1497
+ if (this.scrubber) {
1498
+ const s = await this.scrubber.process({ content: query });
1499
+ scrubbed = s.content || query;
1500
+ }
1501
+ }
1502
+ catch { /* non-fatal */ }
1503
+ if (!this.client) {
1504
+ return { results: [], pipeline: { queryExpanded: false, heritageAware: false, synthesized: false, latencyMs: Date.now() - t0 } };
1505
+ }
1506
+ // Layer 1: HyDE-Lite — template-based query expansion (no LLM required)
1507
+ let hydeQuery = null;
1508
+ if (enableHyDE) {
1509
+ hydeQuery = `A document about ${scrubbed}. This covers concepts related to ${scrubbed} including patterns, insights, and lessons learned.`;
1510
+ }
1511
+ // Layer 2: Multi-channel retrieval (semantic original, semantic HyDE, keyword BM25)
1512
+ const queryVec = await this.embeddingFactory.embed(scrubbed);
1513
+ const hydeVec = hydeQuery ? await this.embeddingFactory.embed(hydeQuery) : null;
1514
+ const [semanticOrig, semanticHyde, keywordResults] = await Promise.all([
1515
+ this.client.search(queryVec, { limit: retrievalLimit, metric: 'cosine' }),
1516
+ hydeVec ? this.client.search(hydeVec, { limit: retrievalLimit, metric: 'cosine' }) : Promise.resolve([]),
1517
+ Promise.resolve(this.keywordSearch.search(scrubbed, { limit: retrievalLimit })),
1518
+ ]);
1519
+ // Layer 3: Reciprocal Rank Fusion (k=60, channel weights: 1.0 / 0.8 / 0.6)
1520
+ const k = 60;
1521
+ const weights = { orig: 1.0, hyde: 0.8, keyword: 0.6 };
1522
+ const rrfScores = new Map();
1523
+ const docMap = new Map();
1524
+ function applyRRF(list, weight) {
1525
+ for (let rank = 0; rank < list.length; rank++) {
1526
+ const doc = list[rank];
1527
+ if (!doc?.id)
1528
+ continue;
1529
+ rrfScores.set(doc.id, (rrfScores.get(doc.id) || 0) + weight / (k + rank + 1));
1530
+ if (!docMap.has(doc.id))
1531
+ docMap.set(doc.id, doc);
1532
+ }
1533
+ }
1534
+ applyRRF(semanticOrig, weights.orig);
1535
+ applyRRF(semanticHyde, weights.hyde);
1536
+ applyRRF(keywordResults.map((r) => ({ id: r.id, content: r.content, metadata: r.metadata, created_at: r.created_at || new Date().toISOString() })), weights.keyword);
1537
+ // Take top pre_rerank_limit candidates
1538
+ const preRerankLimit = 20;
1539
+ const candidates = Array.from(rrfScores.entries())
1540
+ .sort((a, b) => b[1] - a[1])
1541
+ .slice(0, preRerankLimit)
1542
+ .map(([id]) => docMap.get(id))
1543
+ .filter(Boolean);
1544
+ // Layer 4: Heritage-aware reranking
1545
+ // final_score = 0.6×semantic_sim + 0.25×heritage_bonus + 0.15×recency_decay
1546
+ // When no sessionIntent: weights renormalize → α=0.71, γ=0.29
1547
+ const hasHeritage = sessionIntent.length > 0;
1548
+ const α = hasHeritage ? 0.6 : 0.71;
1549
+ const β = hasHeritage ? 0.25 : 0.0;
1550
+ const γ = hasHeritage ? 0.15 : 0.29;
1551
+ const λ = 0.05; // recency decay rate (half-life ≈ 14 days)
1552
+ const now = Date.now();
1553
+ const reranked = candidates.map((doc) => {
1554
+ // Semantic score: normalize rrfScore to [0,1] approximation
1555
+ const rrfScore = rrfScores.get(doc.id) || 0;
1556
+ const semanticScore = Math.min(1.0, rrfScore * 20); // scale to ~[0,1]
1557
+ // Heritage bonus
1558
+ let heritageBonus = 0;
1559
+ if (hasHeritage) {
1560
+ try {
1561
+ const meta = typeof doc.metadata === 'string' ? JSON.parse(doc.metadata) : doc.metadata;
1562
+ const chain = meta?.heritage_chain;
1563
+ const parsedChain = typeof chain === 'string' ? JSON.parse(chain) : chain;
1564
+ const intentChain = parsedChain?.intentChain ?? [];
1565
+ const overlap = intentChain.filter((i) => sessionIntent.includes(i)).length;
1566
+ if (overlap > 0)
1567
+ heritageBonus = Math.min(1.0, overlap / sessionIntent.length);
1568
+ }
1569
+ catch { /* no heritage */ }
1570
+ }
1571
+ // Recency decay: exp(-λ × age_days)
1572
+ let recencyDecay = 1.0;
1573
+ try {
1574
+ const createdAt = doc.created_at || doc.metadata?.created_at;
1575
+ if (createdAt) {
1576
+ const ageDays = (now - new Date(createdAt).getTime()) / 86400000;
1577
+ recencyDecay = Math.exp(-λ * ageDays);
1578
+ }
1579
+ }
1580
+ catch { /* use 1.0 */ }
1581
+ const score = α * semanticScore + β * heritageBonus + γ * recencyDecay;
1582
+ const meta = typeof doc.metadata === 'string' ? JSON.parse(doc.metadata) : doc.metadata;
1583
+ return { doc, score, semanticScore, heritageBonus, recencyDecay, meta };
1584
+ });
1585
+ reranked.sort((a, b) => b.score - a.score);
1586
+ const results = reranked.slice(0, limit).map(({ doc, score, semanticScore, heritageBonus, recencyDecay, meta }, idx) => ({
1587
+ id: doc.id,
1588
+ content: doc.content,
1589
+ metadata: meta,
1590
+ score,
1591
+ semanticScore,
1592
+ heritageBonus,
1593
+ recencyDecay,
1594
+ rrfRank: idx + 1,
1595
+ }));
1596
+ // Layer 5: Synthesis (skip if LLM unavailable)
1597
+ let synthesis;
1598
+ let synthesized = false;
1599
+ if (enableSynthesis && this.llmClient) {
1600
+ try {
1601
+ const excerpts = results.slice(0, 5).map((r, i) => `[${i + 1}] ${r.content}`).join('\n');
1602
+ synthesis = await this.llmClient.complete(`You are a retrieval synthesis agent. Given the following memory excerpts, produce a coherent summary that directly answers the query.\nQuery: ${scrubbed}\nExcerpts:\n${excerpts}`);
1603
+ synthesized = true;
1604
+ }
1605
+ catch { /* non-fatal, skip synthesis */ }
1606
+ }
1607
+ return {
1608
+ results,
1609
+ ...(synthesis !== undefined ? { synthesis } : {}),
1610
+ pipeline: {
1611
+ queryExpanded: enableHyDE,
1612
+ heritageAware: hasHeritage,
1613
+ synthesized,
1614
+ latencyMs: Date.now() - t0,
1615
+ },
1616
+ };
1617
+ }
1313
1618
  async getAll(options = {}) {
1314
1619
  await this.init();
1315
1620
  if (!this.client) {
@@ -30,6 +30,38 @@ import { LLMClient } from "../llm/client.js";
30
30
  import * as lancedb from "@lancedb/lancedb";
31
31
  import { createLogger } from "../utils/logger.js";
32
32
  const logger = createLogger("brain");
33
+ /** RFC-0012 S-MORA types */
34
+ export interface SMORAOptions {
35
+ limit?: number;
36
+ retrievalLimit?: number;
37
+ sessionIntent?: string[];
38
+ enableSynthesis?: boolean;
39
+ enableHyDE?: boolean;
40
+ useCache?: boolean;
41
+ }
42
+
43
+ export interface SMORAResult {
44
+ id: string;
45
+ content: string;
46
+ metadata: Record<string, unknown>;
47
+ score: number;
48
+ semanticScore: number;
49
+ heritageBonus: number;
50
+ recencyDecay: number;
51
+ rrfRank: number;
52
+ }
53
+
54
+ export interface SMORAResponse {
55
+ results: SMORAResult[];
56
+ synthesis?: string;
57
+ pipeline: {
58
+ queryExpanded: boolean;
59
+ heritageAware: boolean;
60
+ synthesized: boolean;
61
+ latencyMs: number;
62
+ };
63
+ }
64
+
33
65
  /**
34
66
  * MemoryMesh class for managing vector memory storage
35
67
  */
@@ -1063,7 +1095,7 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1063
1095
  * Note: YAMO emission is non-critical - failures are logged but don't throw
1064
1096
  * to prevent disrupting the main operation.
1065
1097
  */
1066
- async _emitYamoBlock(operationType, memoryId, yamoText) {
1098
+ async _emitYamoBlock(operationType, memoryId, yamoText, heritage?: { intentChain: string[]; hypotheses: string[]; rationales: string[] }) {
1067
1099
  if (!this.yamoTable) {
1068
1100
  return;
1069
1101
  }
@@ -1081,6 +1113,7 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1081
1113
  metadata: JSON.stringify({
1082
1114
  memory_id: memoryId || null,
1083
1115
  timestamp: new Date().toISOString(),
1116
+ ...(heritage ? { heritage_chain: heritage } : {}),
1084
1117
  }),
1085
1118
  },
1086
1119
  ]);
@@ -1310,6 +1343,365 @@ description: Auto-generated skill to handle: ${enrichedPrompt || topic}
1310
1343
  }
1311
1344
  : null;
1312
1345
  }
1346
+ /**
1347
+ * Delete a memory entry by ID.
1348
+ */
1349
+ async delete(id: string): Promise<void> {
1350
+ await this.init();
1351
+ if (!this.client) {
1352
+ throw new Error("Database client not initialized");
1353
+ }
1354
+ try {
1355
+ await this.client.delete(id);
1356
+ this.keywordSearch?.remove?.(id);
1357
+ } catch (error: any) {
1358
+ if (error instanceof Error && error.message.includes("not found")) return;
1359
+ throw error;
1360
+ }
1361
+ }
1362
+ /**
1363
+ * Distill a LessonLearned block (RFC-0011 §3.5).
1364
+ * Idempotent: same patternId + equal/higher confidence returns existing.
1365
+ */
1366
+ async distillLesson(context: {
1367
+ situation: string;
1368
+ errorPattern: string;
1369
+ oversight: string;
1370
+ fix: string;
1371
+ preventativeRule: string;
1372
+ severity?: string;
1373
+ applicableScope: string;
1374
+ inverseLesson?: string;
1375
+ confidence?: number;
1376
+ }): Promise<{
1377
+ lessonId: string;
1378
+ patternId: string;
1379
+ severity: string;
1380
+ preventativeRule: string;
1381
+ ruleConfidence: number;
1382
+ applicableScope: string;
1383
+ wireFormat: string;
1384
+ memoryId: string;
1385
+ }> {
1386
+ await this.init();
1387
+ const {
1388
+ situation, errorPattern, oversight, fix, preventativeRule,
1389
+ severity = "medium", applicableScope, inverseLesson = "", confidence = 0.7,
1390
+ } = context;
1391
+ const patternId = crypto.createHash("sha256")
1392
+ .update(errorPattern + applicableScope).digest("hex").slice(0, 16);
1393
+ // Idempotency check
1394
+ const existing = await this.getMemoriesByPattern(patternId);
1395
+ if (existing.length > 0) {
1396
+ const meta = typeof existing[0].metadata === "string"
1397
+ ? JSON.parse(existing[0].metadata) : existing[0].metadata;
1398
+ if ((meta.rule_confidence ?? 0) >= confidence) {
1399
+ return {
1400
+ lessonId: meta.lesson_id, patternId, severity: meta.severity || severity,
1401
+ preventativeRule: meta.preventative_rule || preventativeRule,
1402
+ ruleConfidence: meta.rule_confidence, applicableScope: meta.applicable_scope || applicableScope,
1403
+ wireFormat: meta.yamo_wire_format || "", memoryId: existing[0].id,
1404
+ };
1405
+ }
1406
+ }
1407
+ const lessonId = `lesson_${Date.now()}_${crypto.randomBytes(3).toString("hex")}`;
1408
+ const timestamp = new Date().toISOString();
1409
+ const wireFormat = [
1410
+ `agent: MemoryMesh_${this.agentId};`,
1411
+ `intent: distill_wisdom_from_execution;`,
1412
+ `context:`,
1413
+ ` original_context;${situation.replace(/;/g, ",")};`,
1414
+ ` error_pattern;${patternId};`,
1415
+ ` severity;${severity};`,
1416
+ ` timestamp;${timestamp};`,
1417
+ `constraints:`,
1418
+ ` hypothesis;This lesson prevents recurrence of similar failures;`,
1419
+ ` hypothesis_confidence;${confidence};`,
1420
+ `priority: high;`,
1421
+ `output:`,
1422
+ ` lesson_id;${lessonId};`,
1423
+ ` oversight_description;${oversight.replace(/;/g, ",")};`,
1424
+ ` preventative_rule;${preventativeRule.replace(/;/g, ",")};`,
1425
+ ` rule_confidence;${confidence};`,
1426
+ `meta:`,
1427
+ ` rationale;${fix.replace(/;/g, ",")};`,
1428
+ ` applicability_scope;${applicableScope.replace(/;/g, ",")};`,
1429
+ ` inverse_lesson;${inverseLesson.replace(/;/g, ",")};`,
1430
+ ` confidence;${confidence};`,
1431
+ `log: lesson_learned;timestamp;${timestamp};pattern;${patternId};severity;${severity};id;${lessonId};`,
1432
+ `handoff: SubconsciousReflector;`,
1433
+ ].join("\n");
1434
+ const lessonContent = `[LESSON:${patternId}] ${oversight} | Rule: ${preventativeRule} | Scope: ${applicableScope}`;
1435
+ const lessonMetadata = {
1436
+ type: "lesson", tags: ["#lesson_learned"], lesson_id: lessonId,
1437
+ lesson_pattern_id: patternId, severity, oversight, preventative_rule: preventativeRule,
1438
+ rule_confidence: confidence, applicable_scope: applicableScope, inverse_lesson: inverseLesson,
1439
+ yamo_wire_format: wireFormat, source: "distillLesson",
1440
+ };
1441
+ const mem = await this.add(lessonContent, lessonMetadata);
1442
+ if (this.enableYamo) {
1443
+ this._emitYamoBlock("lesson", mem.id, wireFormat).catch(() => {});
1444
+ }
1445
+ return { lessonId, patternId, severity, preventativeRule, ruleConfidence: confidence, applicableScope, wireFormat, memoryId: mem.id };
1446
+ }
1447
+ /**
1448
+ * Query lessons from memory (RFC-0011 §4.1).
1449
+ */
1450
+ async queryLessons(query = "", options: { limit?: number } = {}): Promise<any[]> {
1451
+ await this.init();
1452
+ const limit = options.limit || 10;
1453
+ const all = await this.getAll({ limit: 1000 });
1454
+ const lessons = all.filter((r: any) => {
1455
+ try {
1456
+ const meta = typeof r.metadata === "string" ? JSON.parse(r.metadata) : r.metadata;
1457
+ return meta.type === "lesson" || (Array.isArray(meta.tags) && meta.tags.includes("#lesson_learned"));
1458
+ } catch { return false; }
1459
+ });
1460
+ let scored = lessons as any[];
1461
+ if (query) {
1462
+ const q = query.toLowerCase();
1463
+ scored = lessons.map((r: any) => ({
1464
+ ...r,
1465
+ _score: (r.content?.toLowerCase().includes(q) ? 2 : 0) +
1466
+ (JSON.stringify(r.metadata).toLowerCase().includes(q) ? 1 : 0),
1467
+ })).sort((a: any, b: any) => b._score - a._score);
1468
+ }
1469
+ return scored.slice(0, limit).map((r: any) => {
1470
+ const meta = typeof r.metadata === "string" ? JSON.parse(r.metadata) : r.metadata;
1471
+ return {
1472
+ lessonId: meta.lesson_id || r.id, patternId: meta.lesson_pattern_id || "",
1473
+ severity: meta.severity || "medium", preventativeRule: meta.preventative_rule || "",
1474
+ ruleConfidence: meta.rule_confidence ?? 0, applicableScope: meta.applicable_scope || "",
1475
+ wireFormat: meta.yamo_wire_format || "", memoryId: r.id,
1476
+ };
1477
+ });
1478
+ }
1479
+ /**
1480
+ * Update a memory entry's heritage_chain (RFC-0011 §8).
1481
+ */
1482
+ async insertHeritage(memoryId: string, heritage: { intentChain: string[]; hypotheses: string[]; rationales: string[] }): Promise<void> {
1483
+ await this.init();
1484
+ if (!this.client) throw new Error("Database client not initialized");
1485
+ try {
1486
+ const record = await this.client.getById(memoryId);
1487
+ if (!record) return;
1488
+ const existingMeta = typeof record.metadata === "string"
1489
+ ? JSON.parse(record.metadata) : (record.metadata || {});
1490
+ await this.client.update(memoryId, {
1491
+ metadata: JSON.stringify({ ...existingMeta, heritage_chain: JSON.stringify(heritage) }),
1492
+ });
1493
+ // Emit RFC-0007 §5 heritage block
1494
+ if (this.enableYamo) {
1495
+ const ts = new Date().toISOString();
1496
+ const heritageBlock = [
1497
+ `agent: MemoryMesh_${this.agentId};`,
1498
+ `intent: record_heritage_chain;`,
1499
+ `context:`,
1500
+ ` memory_id;${memoryId};`,
1501
+ ` intent_chain;${heritage.intentChain.join(",")};`,
1502
+ ` timestamp;${ts};`,
1503
+ `output:`,
1504
+ ` heritage_recorded;true;`,
1505
+ `log: heritage_inserted;memory;${memoryId};timestamp;${ts};`,
1506
+ `handoff: End;`,
1507
+ ].join("\n");
1508
+ this._emitYamoBlock("heritage", memoryId, heritageBlock, heritage).catch(() => {});
1509
+ }
1510
+ } catch (error: any) {
1511
+ if (error instanceof Error && error.message.includes("not found")) return;
1512
+ throw error;
1513
+ }
1514
+ }
1515
+ /**
1516
+ * Return all memories whose lesson_pattern_id matches patternId (RFC-0011 §4.1).
1517
+ */
1518
+ async getMemoriesByPattern(patternId: string): Promise<any[]> {
1519
+ await this.init();
1520
+ const all = await this.getAll({ limit: 1000 });
1521
+ return (all as any[]).filter((r) => {
1522
+ try {
1523
+ const meta = typeof r.metadata === "string" ? JSON.parse(r.metadata) : r.metadata;
1524
+ return meta.lesson_pattern_id === patternId;
1525
+ } catch { return false; }
1526
+ });
1527
+ }
1528
+
1529
+ /**
1530
+ * S-MORA: Singularity Memory-Oriented Retrieval Augmentation (RFC-0012)
1531
+ * 5-layer pipeline: Scrubbing → HyDE-Lite → Multi-channel retrieval → RRF → Heritage-aware reranking
1532
+ */
1533
+ async smora(query: string, options: {
1534
+ limit?: number;
1535
+ retrievalLimit?: number;
1536
+ sessionIntent?: string[];
1537
+ enableSynthesis?: boolean;
1538
+ enableHyDE?: boolean;
1539
+ useCache?: boolean;
1540
+ } = {}): Promise<{
1541
+ results: Array<{
1542
+ id: string;
1543
+ content: string;
1544
+ metadata: Record<string, unknown>;
1545
+ score: number;
1546
+ semanticScore: number;
1547
+ heritageBonus: number;
1548
+ recencyDecay: number;
1549
+ rrfRank: number;
1550
+ }>;
1551
+ synthesis?: string;
1552
+ pipeline: {
1553
+ queryExpanded: boolean;
1554
+ heritageAware: boolean;
1555
+ synthesized: boolean;
1556
+ latencyMs: number;
1557
+ };
1558
+ }> {
1559
+ await this.init();
1560
+ const t0 = Date.now();
1561
+ const {
1562
+ limit = 10,
1563
+ retrievalLimit = 30,
1564
+ sessionIntent = [],
1565
+ enableSynthesis = false,
1566
+ enableHyDE = true,
1567
+ } = options;
1568
+
1569
+ // Layer 0: scrub query
1570
+ let scrubbed = query;
1571
+ try {
1572
+ if (this.scrubber) {
1573
+ const s = await this.scrubber.process({ content: query });
1574
+ scrubbed = s.content || query;
1575
+ }
1576
+ } catch { /* non-fatal */ }
1577
+
1578
+ if (!this.client) {
1579
+ return { results: [], pipeline: { queryExpanded: false, heritageAware: false, synthesized: false, latencyMs: Date.now() - t0 } };
1580
+ }
1581
+
1582
+ // Layer 1: HyDE-Lite — template-based query expansion (no LLM required)
1583
+ let hydeQuery: string | null = null;
1584
+ if (enableHyDE) {
1585
+ hydeQuery = `A document about ${scrubbed}. This covers concepts related to ${scrubbed} including patterns, insights, and lessons learned.`;
1586
+ }
1587
+
1588
+ // Layer 2: Multi-channel retrieval (semantic original, semantic HyDE, keyword BM25)
1589
+ const queryVec = await this.embeddingFactory.embed(scrubbed);
1590
+ const hydeVec = hydeQuery ? await this.embeddingFactory.embed(hydeQuery) : null;
1591
+
1592
+ const [semanticOrig, semanticHyde, keywordResults] = await Promise.all([
1593
+ this.client.search(queryVec, { limit: retrievalLimit, metric: 'cosine' }),
1594
+ hydeVec ? this.client.search(hydeVec, { limit: retrievalLimit, metric: 'cosine' }) : Promise.resolve([]),
1595
+ Promise.resolve(this.keywordSearch.search(scrubbed, { limit: retrievalLimit })),
1596
+ ]);
1597
+
1598
+ // Layer 3: Reciprocal Rank Fusion (k=60, channel weights: 1.0 / 0.8 / 0.6)
1599
+ const k = 60;
1600
+ const weights = { orig: 1.0, hyde: 0.8, keyword: 0.6 };
1601
+ const rrfScores = new Map<string, number>();
1602
+ const docMap = new Map<string, any>();
1603
+
1604
+ function applyRRF(list: any[], weight: number) {
1605
+ for (let rank = 0; rank < list.length; rank++) {
1606
+ const doc = list[rank];
1607
+ if (!doc?.id) continue;
1608
+ rrfScores.set(doc.id, (rrfScores.get(doc.id) || 0) + weight / (k + rank + 1));
1609
+ if (!docMap.has(doc.id)) docMap.set(doc.id, doc);
1610
+ }
1611
+ }
1612
+ applyRRF(semanticOrig, weights.orig);
1613
+ applyRRF(semanticHyde, weights.hyde);
1614
+ applyRRF(keywordResults.map((r: any) => ({ id: r.id, content: r.content, metadata: r.metadata, created_at: r.created_at || new Date().toISOString() })), weights.keyword);
1615
+
1616
+ // Take top pre_rerank_limit candidates
1617
+ const preRerankLimit = 20;
1618
+ const candidates = Array.from(rrfScores.entries())
1619
+ .sort((a, b) => b[1] - a[1])
1620
+ .slice(0, preRerankLimit)
1621
+ .map(([id]) => docMap.get(id))
1622
+ .filter(Boolean);
1623
+
1624
+ // Layer 4: Heritage-aware reranking
1625
+ // final_score = 0.6×semantic_sim + 0.25×heritage_bonus + 0.15×recency_decay
1626
+ // When no sessionIntent: weights renormalize → α=0.71, γ=0.29
1627
+ const hasHeritage = sessionIntent.length > 0;
1628
+ const α = hasHeritage ? 0.6 : 0.71;
1629
+ const β = hasHeritage ? 0.25 : 0.0;
1630
+ const γ = hasHeritage ? 0.15 : 0.29;
1631
+ const λ = 0.05; // recency decay rate (half-life ≈ 14 days)
1632
+ const now = Date.now();
1633
+
1634
+ const reranked = candidates.map((doc: any) => {
1635
+ // Semantic score: normalize rrfScore to [0,1] approximation
1636
+ const rrfScore = rrfScores.get(doc.id) || 0;
1637
+ const semanticScore = Math.min(1.0, rrfScore * 20); // scale to ~[0,1]
1638
+
1639
+ // Heritage bonus
1640
+ let heritageBonus = 0;
1641
+ if (hasHeritage) {
1642
+ try {
1643
+ const meta = typeof doc.metadata === 'string' ? JSON.parse(doc.metadata) : doc.metadata;
1644
+ const chain = meta?.heritage_chain;
1645
+ const parsedChain = typeof chain === 'string' ? JSON.parse(chain) : chain;
1646
+ const intentChain: string[] = parsedChain?.intentChain ?? [];
1647
+ const overlap = intentChain.filter((i: string) => sessionIntent.includes(i)).length;
1648
+ if (overlap > 0) heritageBonus = Math.min(1.0, overlap / sessionIntent.length);
1649
+ } catch { /* no heritage */ }
1650
+ }
1651
+
1652
+ // Recency decay: exp(-λ × age_days)
1653
+ let recencyDecay = 1.0;
1654
+ try {
1655
+ const createdAt = doc.created_at || doc.metadata?.created_at;
1656
+ if (createdAt) {
1657
+ const ageDays = (now - new Date(createdAt).getTime()) / 86400000;
1658
+ recencyDecay = Math.exp(-λ * ageDays);
1659
+ }
1660
+ } catch { /* use 1.0 */ }
1661
+
1662
+ const score = α * semanticScore + β * heritageBonus + γ * recencyDecay;
1663
+ const meta = typeof doc.metadata === 'string' ? JSON.parse(doc.metadata) : doc.metadata;
1664
+ return { doc, score, semanticScore, heritageBonus, recencyDecay, meta };
1665
+ });
1666
+
1667
+ reranked.sort((a, b) => b.score - a.score);
1668
+
1669
+ const results = reranked.slice(0, limit).map(({ doc, score, semanticScore, heritageBonus, recencyDecay, meta }, idx) => ({
1670
+ id: doc.id,
1671
+ content: doc.content,
1672
+ metadata: meta,
1673
+ score,
1674
+ semanticScore,
1675
+ heritageBonus,
1676
+ recencyDecay,
1677
+ rrfRank: idx + 1,
1678
+ }));
1679
+
1680
+ // Layer 5: Synthesis (skip if LLM unavailable)
1681
+ let synthesis: string | undefined;
1682
+ let synthesized = false;
1683
+ if (enableSynthesis && this.llmClient) {
1684
+ try {
1685
+ const excerpts = results.slice(0, 5).map((r, i) => `[${i + 1}] ${r.content}`).join('\n');
1686
+ synthesis = await this.llmClient.complete(
1687
+ `You are a retrieval synthesis agent. Given the following memory excerpts, produce a coherent summary that directly answers the query.\nQuery: ${scrubbed}\nExcerpts:\n${excerpts}`
1688
+ );
1689
+ synthesized = true;
1690
+ } catch { /* non-fatal, skip synthesis */ }
1691
+ }
1692
+
1693
+ return {
1694
+ results,
1695
+ ...(synthesis !== undefined ? { synthesis } : {}),
1696
+ pipeline: {
1697
+ queryExpanded: enableHyDE,
1698
+ heritageAware: hasHeritage,
1699
+ synthesized,
1700
+ latencyMs: Date.now() - t0,
1701
+ },
1702
+ };
1703
+ }
1704
+
1313
1705
  async getAll(options = {}) {
1314
1706
  await this.init();
1315
1707
  if (!this.client) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yamo/memory-mesh",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "Portable semantic memory system with Layer 0 Scrubber for YAMO agents (v3 Singularity Edition)",
5
5
  "type": "module",
6
6
  "main": "lib/memory/index.js",