@remnic/core 9.3.636 → 9.3.638

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 (83) hide show
  1. package/dist/access-cli.js +12 -12
  2. package/dist/access-http.d.ts +2 -2
  3. package/dist/access-http.js +8 -6
  4. package/dist/access-mcp.d.ts +2 -2
  5. package/dist/access-mcp.js +7 -5
  6. package/dist/{access-service-GVOE5U3C.d.ts → access-service-DvEvBv9_.d.ts} +2 -1
  7. package/dist/access-service.d.ts +2 -2
  8. package/dist/access-service.js +6 -4
  9. package/dist/active-recall.js +1 -1
  10. package/dist/bootstrap.d.ts +1 -1
  11. package/dist/causal-behavior.js +2 -2
  12. package/dist/causal-chain.js +2 -2
  13. package/dist/causal-consolidation.js +2 -2
  14. package/dist/causal-retrieval.js +2 -2
  15. package/dist/causal-trajectory-graph.js +1 -1
  16. package/dist/causal-trajectory.js +1 -1
  17. package/dist/{chunk-VTH5SKQW.js → chunk-2JIYTTQJ.js} +4 -4
  18. package/dist/{chunk-HZVIYZYN.js → chunk-7KRYJUTB.js} +6 -6
  19. package/dist/{chunk-7H5WCPBS.js → chunk-A3HKTPPV.js} +21 -6
  20. package/dist/chunk-A3HKTPPV.js.map +1 -0
  21. package/dist/{chunk-2IT7WFYE.js → chunk-FWIROLS6.js} +2 -1
  22. package/dist/chunk-FWIROLS6.js.map +1 -0
  23. package/dist/{chunk-JYFO32V4.js → chunk-I3YAA4BI.js} +9 -9
  24. package/dist/{chunk-A6D7A2FW.js → chunk-IHG27THY.js} +4 -4
  25. package/dist/{chunk-W25I7G6U.js → chunk-KXTLEB4L.js} +3 -3
  26. package/dist/{chunk-XM6M6DHG.js → chunk-MNDFVT4F.js} +195 -6
  27. package/dist/chunk-MNDFVT4F.js.map +1 -0
  28. package/dist/{chunk-7V2SGZ3H.js → chunk-NE566K4E.js} +2 -2
  29. package/dist/{chunk-NFNQ7TWF.js → chunk-OSPVAP56.js} +3 -3
  30. package/dist/{chunk-UXFOGILU.js → chunk-QDAWVW75.js} +1 -1
  31. package/dist/{chunk-TQOU3VAT.js → chunk-SSOMTUCA.js} +1 -1
  32. package/dist/{chunk-TFICNXXU.js → chunk-TJRV4MWX.js} +990 -252
  33. package/dist/chunk-TJRV4MWX.js.map +1 -0
  34. package/dist/{chunk-BK35NOJL.js → chunk-U2YPUWRB.js} +3 -3
  35. package/dist/{chunk-EWC6W6AB.js → chunk-UXA5L2DZ.js} +2 -2
  36. package/dist/{chunk-XL7FK7PJ.js → chunk-Y56J7CXW.js} +33 -16
  37. package/dist/{chunk-XL7FK7PJ.js.map → chunk-Y56J7CXW.js.map} +1 -1
  38. package/dist/{cli-B-fHk59D.d.ts → cli-BJBKNWIS.d.ts} +2 -2
  39. package/dist/cli.d.ts +3 -3
  40. package/dist/cli.js +13 -12
  41. package/dist/compounding/engine.js +1 -1
  42. package/dist/config.js +1 -1
  43. package/dist/explicit-capture.d.ts +1 -1
  44. package/dist/{graph-edge-decay-5ZOK7ZNO.js → graph-edge-decay-PUFNHOBS.js} +2 -2
  45. package/dist/graph-snapshot.js +2 -2
  46. package/dist/graph.d.ts +3 -0
  47. package/dist/graph.js +1 -1
  48. package/dist/index.d.ts +4 -4
  49. package/dist/index.js +17 -17
  50. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  51. package/dist/operator-toolkit.js +3 -3
  52. package/dist/{orchestrator-CoqytbK_.d.ts → orchestrator-ibTTeoUa.d.ts} +9 -0
  53. package/dist/orchestrator.d.ts +1 -1
  54. package/dist/orchestrator.js +9 -9
  55. package/dist/recall-state.d.ts +2 -0
  56. package/dist/recall-state.js +1 -1
  57. package/dist/resume-bundles.js +4 -4
  58. package/dist/summarizer.js +2 -2
  59. package/dist/transcript.js +2 -2
  60. package/package.json +1 -1
  61. package/src/access-service-namespace.test.ts +740 -0
  62. package/src/access-service.ts +222 -3
  63. package/src/config.test.ts +118 -0
  64. package/src/config.ts +45 -10
  65. package/src/graph.ts +39 -17
  66. package/src/orchestrator.ts +1286 -331
  67. package/src/recall-state.ts +3 -0
  68. package/dist/chunk-2IT7WFYE.js.map +0 -1
  69. package/dist/chunk-7H5WCPBS.js.map +0 -1
  70. package/dist/chunk-TFICNXXU.js.map +0 -1
  71. package/dist/chunk-XM6M6DHG.js.map +0 -1
  72. /package/dist/{chunk-VTH5SKQW.js.map → chunk-2JIYTTQJ.js.map} +0 -0
  73. /package/dist/{chunk-HZVIYZYN.js.map → chunk-7KRYJUTB.js.map} +0 -0
  74. /package/dist/{chunk-JYFO32V4.js.map → chunk-I3YAA4BI.js.map} +0 -0
  75. /package/dist/{chunk-A6D7A2FW.js.map → chunk-IHG27THY.js.map} +0 -0
  76. /package/dist/{chunk-W25I7G6U.js.map → chunk-KXTLEB4L.js.map} +0 -0
  77. /package/dist/{chunk-7V2SGZ3H.js.map → chunk-NE566K4E.js.map} +0 -0
  78. /package/dist/{chunk-NFNQ7TWF.js.map → chunk-OSPVAP56.js.map} +0 -0
  79. /package/dist/{chunk-UXFOGILU.js.map → chunk-QDAWVW75.js.map} +0 -0
  80. /package/dist/{chunk-TQOU3VAT.js.map → chunk-SSOMTUCA.js.map} +0 -0
  81. /package/dist/{chunk-BK35NOJL.js.map → chunk-U2YPUWRB.js.map} +0 -0
  82. /package/dist/{chunk-EWC6W6AB.js.map → chunk-UXA5L2DZ.js.map} +0 -0
  83. /package/dist/{graph-edge-decay-5ZOK7ZNO.js.map → graph-edge-decay-PUFNHOBS.js.map} +0 -0
@@ -29,7 +29,7 @@ import {
29
29
  import {
30
30
  CompoundingEngine,
31
31
  defaultTierMigrationCycleBudget
32
- } from "./chunk-UXFOGILU.js";
32
+ } from "./chunk-QDAWVW75.js";
33
33
  import {
34
34
  SharedContextManager
35
35
  } from "./chunk-DRD2Q7HQ.js";
@@ -64,7 +64,7 @@ import {
64
64
  } from "./chunk-VOUOLGIP.js";
65
65
  import {
66
66
  HourlySummarizer
67
- } from "./chunk-HZVIYZYN.js";
67
+ } from "./chunk-7KRYJUTB.js";
68
68
  import {
69
69
  SessionObserverState
70
70
  } from "./chunk-MHQC2WU2.js";
@@ -113,7 +113,7 @@ import {
113
113
  LastRecallStore,
114
114
  TierMigrationStatusStore,
115
115
  clampGraphRecallExpandedEntries
116
- } from "./chunk-2IT7WFYE.js";
116
+ } from "./chunk-FWIROLS6.js";
117
117
  import {
118
118
  buildQmdRecallCacheKey,
119
119
  getCachedQmdRecall,
@@ -231,7 +231,7 @@ import {
231
231
  } from "./chunk-ZRWB5D4H.js";
232
232
  import {
233
233
  TranscriptManager
234
- } from "./chunk-A6D7A2FW.js";
234
+ } from "./chunk-IHG27THY.js";
235
235
  import {
236
236
  NamespaceStorageRouter
237
237
  } from "./chunk-LEG7XWS2.js";
@@ -308,6 +308,9 @@ import {
308
308
  listTrustZoneRecords,
309
309
  searchTrustZoneRecords
310
310
  } from "./chunk-JGSKJHF7.js";
311
+ import {
312
+ isPathInsideStorageRoot
313
+ } from "./chunk-FF4KLI5W.js";
311
314
  import {
312
315
  buildXraySnapshot
313
316
  } from "./chunk-YQNADJCT.js";
@@ -345,7 +348,7 @@ import {
345
348
  } from "./chunk-H3PHZLMF.js";
346
349
  import {
347
350
  GraphIndex
348
- } from "./chunk-XL7FK7PJ.js";
351
+ } from "./chunk-Y56J7CXW.js";
349
352
  import {
350
353
  buildChainFollowupGenerator
351
354
  } from "./chunk-CMN5AWAZ.js";
@@ -387,7 +390,7 @@ import {
387
390
  } from "./chunk-FVQJYWH7.js";
388
391
  import {
389
392
  searchCausalTrajectories
390
- } from "./chunk-TQOU3VAT.js";
393
+ } from "./chunk-SSOMTUCA.js";
391
394
  import {
392
395
  canReadNamespace,
393
396
  defaultNamespaceForPrincipal,
@@ -416,6 +419,9 @@ import {
416
419
  keyring_exports,
417
420
  secureStoreDir
418
421
  } from "./chunk-BJMBJZ2Y.js";
422
+ import {
423
+ SecureStoreLockedError
424
+ } from "./chunk-ILXTATKK.js";
419
425
  import {
420
426
  parseFlexibleIsoTimestamp
421
427
  } from "./chunk-P7FMDTKL.js";
@@ -1633,6 +1639,42 @@ async function raceRecallAbort(promise, signal, message = "recall aborted") {
1633
1639
  }
1634
1640
  }
1635
1641
  }
1642
+ function qmdCollectionPathParts(resultPath) {
1643
+ if (!resultPath || path2.isAbsolute(resultPath)) return null;
1644
+ const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
1645
+ const slashIndex = normalized.indexOf("/");
1646
+ if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
1647
+ const collection = normalized.slice(0, slashIndex);
1648
+ if (/^\d{4}-\d{2}-\d{2}$/.test(collection)) return null;
1649
+ return {
1650
+ collection,
1651
+ relativePath: normalized.slice(slashIndex + 1)
1652
+ };
1653
+ }
1654
+ function qmdResultPathCandidates(storageDir, resultPath) {
1655
+ const candidates = /* @__PURE__ */ new Set();
1656
+ const storageRoot = path2.resolve(storageDir);
1657
+ const addCandidate = (candidate) => {
1658
+ const resolved = path2.resolve(candidate);
1659
+ if (isPathInsideStorageRoot(storageRoot, resolved)) {
1660
+ candidates.add(resolved);
1661
+ }
1662
+ };
1663
+ const addRelativeCandidates = (relativePath) => {
1664
+ const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
1665
+ if (!normalized) return;
1666
+ addCandidate(path2.join(storageRoot, normalized));
1667
+ if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
1668
+ addCandidate(path2.join(storageRoot, "facts", normalized));
1669
+ }
1670
+ };
1671
+ if (path2.isAbsolute(resultPath)) {
1672
+ addCandidate(resultPath);
1673
+ } else {
1674
+ addRelativeCandidates(resultPath);
1675
+ }
1676
+ return [...candidates];
1677
+ }
1636
1678
  var COMPACTION_SIGNAL_MAX_AGE_MS = 60 * 60 * 1e3;
1637
1679
  var DEFAULT_QMD_STARTUP_COLLECTION_CHECK_TIMEOUT_MS = 1e4;
1638
1680
  function qmdStartupCollectionCheckTimeoutMs() {
@@ -5334,16 +5376,74 @@ ${r.snippet.trim()}
5334
5376
  return capped;
5335
5377
  }
5336
5378
  async expandResultsViaGraph(options) {
5379
+ const deadlineExpired = () => typeof options.deadlineAtMs === "number" && Date.now() >= options.deadlineAtMs;
5337
5380
  const byNamespace = /* @__PURE__ */ new Map();
5338
- for (const result of options.memoryResults) {
5339
- const ns = this.namespaceFromPath(result.path);
5340
- if (!options.recallNamespaces.includes(ns)) continue;
5341
- const existing = byNamespace.get(ns);
5381
+ const addResultForNamespace = (namespace, result) => {
5382
+ const existing = byNamespace.get(namespace);
5342
5383
  if (existing) {
5343
5384
  existing.push(result);
5344
5385
  } else {
5345
- byNamespace.set(ns, [result]);
5386
+ byNamespace.set(namespace, [result]);
5346
5387
  }
5388
+ };
5389
+ const resolvedAmbiguousSeeds = /* @__PURE__ */ new Map();
5390
+ const resolveAmbiguousSeedOwner = async (result, parts) => {
5391
+ const cached = resolvedAmbiguousSeeds.get(result.path);
5392
+ if (cached !== void 0) return cached;
5393
+ if (deadlineExpired()) {
5394
+ resolvedAmbiguousSeeds.set(result.path, null);
5395
+ return null;
5396
+ }
5397
+ let resolvedPath = result.path;
5398
+ let resolvedResult = result;
5399
+ if (parts) {
5400
+ const resolvedCold = await this.resolveColdQmdResultForRecall(
5401
+ result,
5402
+ this.storage,
5403
+ options.recallNamespaces
5404
+ );
5405
+ if (!resolvedCold || deadlineExpired()) {
5406
+ resolvedAmbiguousSeeds.set(result.path, null);
5407
+ return null;
5408
+ }
5409
+ resolvedPath = resolvedCold.result.path;
5410
+ resolvedResult = resolvedCold.result;
5411
+ }
5412
+ if (!path2.isAbsolute(resolvedPath)) {
5413
+ resolvedAmbiguousSeeds.set(result.path, null);
5414
+ return null;
5415
+ }
5416
+ const ownerStorage = await this.storageForAbsoluteQmdResultPath(
5417
+ resolvedPath,
5418
+ this.storage,
5419
+ options.recallNamespaces
5420
+ );
5421
+ const ownerNamespace = ownerStorage?.namespace ?? null;
5422
+ const resolved = ownerNamespace && options.recallNamespaces.includes(ownerNamespace) ? { namespace: ownerNamespace, result: resolvedResult } : null;
5423
+ resolvedAmbiguousSeeds.set(result.path, resolved);
5424
+ return resolved;
5425
+ };
5426
+ const coldCollection = this.config.qmdColdCollection ?? "openclaw-engram-cold";
5427
+ for (const result of options.memoryResults) {
5428
+ if (deadlineExpired()) break;
5429
+ const parts = qmdCollectionPathParts(result.path);
5430
+ if (parts?.collection === coldCollection) {
5431
+ const resolved = await resolveAmbiguousSeedOwner(result, parts);
5432
+ if (resolved) {
5433
+ addResultForNamespace(resolved.namespace, resolved.result);
5434
+ }
5435
+ continue;
5436
+ }
5437
+ if (path2.isAbsolute(result.path)) {
5438
+ const resolved = await resolveAmbiguousSeedOwner(result, null);
5439
+ if (resolved) {
5440
+ addResultForNamespace(resolved.namespace, resolved.result);
5441
+ }
5442
+ continue;
5443
+ }
5444
+ const ns = this.namespaceFromPath(result.path);
5445
+ if (!options.recallNamespaces.includes(ns)) continue;
5446
+ addResultForNamespace(ns, result);
5347
5447
  }
5348
5448
  const perNamespaceSeedCap = Math.max(3, options.recallResultLimit);
5349
5449
  const perNamespaceExpandedCap = Math.max(8, options.recallResultLimit * 2);
@@ -5352,12 +5452,25 @@ ${r.snippet.trim()}
5352
5452
  const expandedPaths = [];
5353
5453
  const expandedResults = [];
5354
5454
  for (const [namespace, nsResults] of byNamespace.entries()) {
5455
+ if (deadlineExpired()) break;
5355
5456
  const storage = await this.storageRouter.storageFor(namespace);
5356
5457
  const seedCandidates = nsResults.slice(0, perNamespaceSeedCap);
5357
5458
  seedResults.push(...seedCandidates);
5358
- const seedRelativePaths = seedCandidates.map((result) => graphPathRelativeToStorage(storage.dir, result.path)).filter(
5459
+ const seedRelativePaths = typeof options.deadlineAtMs === "number" ? await this.graphSeedPathsWithinDeadline(
5460
+ storage,
5461
+ seedCandidates,
5462
+ options.deadlineAtMs,
5463
+ [namespace]
5464
+ ) : (await Promise.all(
5465
+ seedCandidates.map(
5466
+ (result) => this.graphSeedPathRelativeToStorage(storage, result, [
5467
+ namespace
5468
+ ])
5469
+ )
5470
+ )).filter(
5359
5471
  (value) => typeof value === "string" && value.length > 0
5360
5472
  );
5473
+ if (deadlineExpired()) break;
5361
5474
  if (seedRelativePaths.length === 0) continue;
5362
5475
  const seedRecallScore = seedCandidates.reduce(
5363
5476
  (max, item) => Math.max(max, item.score),
@@ -5370,13 +5483,19 @@ ${r.snippet.trim()}
5370
5483
  const expanded = await this.graphIndexFor(storage).spreadingActivation(
5371
5484
  seedRelativePaths,
5372
5485
  this.config.maxGraphTraversalSteps,
5373
- options.includeLowConfidence === true ? { includeLowConfidence: true } : void 0
5486
+ {
5487
+ ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {},
5488
+ ...typeof options.deadlineAtMs === "number" ? { deadlineAtMs: options.deadlineAtMs } : {}
5489
+ }
5374
5490
  );
5375
5491
  if (expanded.length === 0) continue;
5492
+ if (deadlineExpired()) break;
5376
5493
  for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
5494
+ if (deadlineExpired()) break;
5377
5495
  if (seedSet.has(candidate.path)) continue;
5378
5496
  const memoryPath = path2.resolve(storage.dir, candidate.path);
5379
5497
  const memory = await storage.readMemoryByPath(memoryPath);
5498
+ if (deadlineExpired()) break;
5380
5499
  if (!memory) continue;
5381
5500
  if (isArtifactMemoryPath(memory.path)) continue;
5382
5501
  if (memory.frontmatter.status && memory.frontmatter.status !== "active")
@@ -7505,10 +7624,12 @@ ${formatted}`;
7505
7624
  return section;
7506
7625
  })()
7507
7626
  );
7508
- const recentBoxesPromise = this.isRecallSectionEnabled(
7509
- "memory-boxes",
7510
- this.config.memoryBoxesEnabled === true
7511
- ) && this.config.memoryBoxesEnabled && this.config.boxRecallDays > 0 ? this.boxBuilderFor(profileStorage).readRecentBoxes(this.config.boxRecallDays).catch(() => []) : Promise.resolve([]);
7627
+ const recentBoxesPromise = observeEnrichmentPromise(
7628
+ this.isRecallSectionEnabled(
7629
+ "memory-boxes",
7630
+ this.config.memoryBoxesEnabled === true
7631
+ ) && this.config.memoryBoxesEnabled && this.config.boxRecallDays > 0 ? this.boxBuilderFor(profileStorage).readRecentBoxes(this.config.boxRecallDays).catch(() => []) : Promise.resolve([])
7632
+ );
7512
7633
  this.profiler.startSpan("phase-1-parallel", profileTraceId);
7513
7634
  const phase1Start = Date.now();
7514
7635
  log.info(
@@ -7637,6 +7758,48 @@ ${formatted}`;
7637
7758
  }
7638
7759
  return finalizeEnrichmentOutcome(outcome);
7639
7760
  };
7761
+ const remainingEnrichmentAssemblyMs = () => enrichmentAssemblyDeadlineAtMs === null ? null : Math.max(0, enrichmentAssemblyDeadlineAtMs - Date.now());
7762
+ const awaitAssemblyStep = async (name, task, fallback) => {
7763
+ if (options.abortSignal?.aborted) {
7764
+ log.debug(
7765
+ `recall phase-1 assembly [${name}]: skipped after abort at +${Date.now() - phase1Start}ms`
7766
+ );
7767
+ return fallback;
7768
+ }
7769
+ const timeoutMs = remainingEnrichmentAssemblyMs();
7770
+ if (timeoutMs === 0) {
7771
+ log.debug(
7772
+ `recall phase-1 assembly [${name}]: skipped after shared ${enrichmentSectionDeadlineMs}ms budget expired at +${Date.now() - phase1Start}ms`
7773
+ );
7774
+ return fallback;
7775
+ }
7776
+ let timeoutHandle;
7777
+ try {
7778
+ const result = await (timeoutMs !== null ? Promise.race([
7779
+ task(),
7780
+ new Promise((resolve) => {
7781
+ timeoutHandle = setTimeout(
7782
+ () => resolve({ status: "timed_out" }),
7783
+ timeoutMs
7784
+ );
7785
+ })
7786
+ ]) : task());
7787
+ if (timeoutHandle) clearTimeout(timeoutHandle);
7788
+ if (typeof result === "object" && result !== null && "status" in result && result.status === "timed_out") {
7789
+ log.debug(
7790
+ `recall phase-1 assembly [${name}]: timed out within shared ${enrichmentSectionDeadlineMs}ms budget at +${Date.now() - phase1Start}ms`
7791
+ );
7792
+ return fallback;
7793
+ }
7794
+ return result;
7795
+ } catch (err) {
7796
+ if (timeoutHandle) clearTimeout(timeoutHandle);
7797
+ log.warn(
7798
+ `recall phase-1 assembly [${name}] failed open: ${err instanceof Error ? err.message : String(err)}`
7799
+ );
7800
+ return fallback;
7801
+ }
7802
+ };
7640
7803
  this.profiler.startSpan("assembly", profileTraceId);
7641
7804
  if (sharedCtx)
7642
7805
  this.appendRecallSection(sectionBuckets, "shared-context", sharedCtx);
@@ -7855,8 +8018,11 @@ ${lines.join("\n")}`
7855
8018
  );
7856
8019
  }
7857
8020
  {
7858
- const recentBoxes = await recentBoxesPromise;
7859
- if (recentBoxes.length > 0) {
8021
+ const recentBoxes = await awaitEnrichmentSection(
8022
+ "memory-boxes",
8023
+ recentBoxesPromise
8024
+ );
8025
+ if (recentBoxes && recentBoxes.length > 0) {
7860
8026
  const boxLines = recentBoxes.slice(0, 5).map((b) => {
7861
8027
  const sealedDate = b.sealedAt ? b.sealedAt.slice(0, 16).replace("T", " ") : "?";
7862
8028
  const traceNote = b.traceId ? ` [trace: ${b.traceId.slice(0, 12)}]` : "";
@@ -7986,6 +8152,7 @@ ${tmtNode.summary}`
7986
8152
  workProductsSection
7987
8153
  );
7988
8154
  }
8155
+ const qmdWasSettledBeforeAssemblyWait = qmdPromise.getSettledOutcome() !== void 0;
7989
8156
  const qmdResult = await awaitEnrichmentSection("qmd", qmdPromise);
7990
8157
  if (qmdResult) {
7991
8158
  const t0 = Date.now();
@@ -8032,48 +8199,69 @@ ${tmtNode.summary}`
8032
8199
  graphExpandedResultPaths.clear();
8033
8200
  } else {
8034
8201
  try {
8035
- const {
8036
- merged,
8037
- seedPaths,
8038
- expandedPaths,
8039
- seedResults = baselineMemoryResults
8040
- } = await this.expandResultsViaGraph({
8041
- memoryResults,
8042
- recallNamespaces,
8043
- recallResultLimit,
8044
- ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {}
8045
- });
8046
- graphSnapshotStatus = "completed";
8047
- graphDecisionStatus = "completed";
8048
- graphDecisionReason = graphShadowEvalEnabled ? "graph shadow evaluation completed without altering injected context" : "graph expansion merged into recall ranking";
8049
- graphSnapshotReason = graphDecisionReason;
8050
- graphSnapshotSeedPaths = seedPaths;
8051
- graphSnapshotExpandedPaths = expandedPaths;
8052
- graphSnapshotSeedResults = this.buildGraphRecallRankedResults(
8053
- seedResults,
8054
- () => ["baseline"]
8055
- );
8056
- graphExpandedResultPaths.clear();
8057
- expandedPaths.forEach(
8058
- (entry) => graphExpandedResultPaths.add(entry.path)
8202
+ const graphExpansion = await awaitAssemblyStep(
8203
+ "graph-expansion",
8204
+ () => this.expandResultsViaGraph({
8205
+ memoryResults,
8206
+ recallNamespaces,
8207
+ recallResultLimit,
8208
+ deadlineAtMs: enrichmentAssemblyDeadlineAtMs,
8209
+ ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {}
8210
+ }),
8211
+ null
8059
8212
  );
8060
- memoryResults = graphShadowEvalEnabled ? baselineMemoryResults : merged;
8061
- if (graphShadowEvalEnabled) {
8062
- const comparison = summarizeGraphShadowComparison(
8213
+ if (!graphExpansion) {
8214
+ graphSnapshotStatus = "aborted";
8215
+ graphDecisionStatus = "aborted";
8216
+ graphDecisionReason = options.abortSignal?.aborted ? "graph expansion skipped because recall assembly was aborted" : "graph expansion skipped because shared post-retrieval assembly budget expired";
8217
+ graphSnapshotReason = graphDecisionReason;
8218
+ graphSnapshotSeedPaths = baselineMemoryResults.slice(0, Math.max(1, recallResultLimit)).map((result) => result.path);
8219
+ graphSnapshotSeedResults = this.buildGraphRecallRankedResults(
8063
8220
  baselineMemoryResults,
8221
+ () => ["baseline"]
8222
+ );
8223
+ graphSnapshotExpandedPaths = [];
8224
+ graphExpandedResultPaths.clear();
8225
+ memoryResults = baselineMemoryResults;
8226
+ } else {
8227
+ const {
8064
8228
  merged,
8065
- recallResultLimit
8229
+ seedPaths,
8230
+ expandedPaths,
8231
+ seedResults = baselineMemoryResults
8232
+ } = graphExpansion;
8233
+ graphSnapshotStatus = "completed";
8234
+ graphDecisionStatus = "completed";
8235
+ graphDecisionReason = graphShadowEvalEnabled ? "graph shadow evaluation completed without altering injected context" : "graph expansion merged into recall ranking";
8236
+ graphSnapshotReason = graphDecisionReason;
8237
+ graphSnapshotSeedPaths = seedPaths;
8238
+ graphSnapshotExpandedPaths = expandedPaths;
8239
+ graphSnapshotSeedResults = this.buildGraphRecallRankedResults(
8240
+ seedResults,
8241
+ () => ["baseline"]
8066
8242
  );
8067
- graphSnapshotShadowComparison = comparison;
8068
- recordRecallSectionMetric({
8069
- section: "graphShadow",
8070
- priority: "enrichment",
8071
- durationMs: Date.now() - t0,
8072
- deadlineMs: enrichmentSectionDeadlineMs,
8073
- source: "fresh",
8074
- success: true,
8075
- timing: `on b=${comparison.baselineCount} g=${comparison.graphCount} ov=${comparison.overlapCount} (${comparison.overlapRatio.toFixed(2)}) avgDelta=${comparison.averageOverlapDelta.toFixed(3)}`
8076
- });
8243
+ graphExpandedResultPaths.clear();
8244
+ expandedPaths.forEach(
8245
+ (entry) => graphExpandedResultPaths.add(entry.path)
8246
+ );
8247
+ memoryResults = graphShadowEvalEnabled ? baselineMemoryResults : merged;
8248
+ if (graphShadowEvalEnabled) {
8249
+ const comparison = summarizeGraphShadowComparison(
8250
+ baselineMemoryResults,
8251
+ merged,
8252
+ recallResultLimit
8253
+ );
8254
+ graphSnapshotShadowComparison = comparison;
8255
+ recordRecallSectionMetric({
8256
+ section: "graphShadow",
8257
+ priority: "enrichment",
8258
+ durationMs: Date.now() - t0,
8259
+ deadlineMs: enrichmentSectionDeadlineMs,
8260
+ source: "fresh",
8261
+ success: true,
8262
+ timing: `on b=${comparison.baselineCount} g=${comparison.graphCount} ov=${comparison.overlapCount} (${comparison.overlapRatio.toFixed(2)}) avgDelta=${comparison.averageOverlapDelta.toFixed(3)}`
8263
+ });
8264
+ }
8077
8265
  }
8078
8266
  } catch (err) {
8079
8267
  graphSnapshotStatus = "aborted";
@@ -8092,12 +8280,31 @@ ${tmtNode.summary}`
8092
8280
  }
8093
8281
  }
8094
8282
  }
8095
- memoryResults = await this.boostSearchResults(
8283
+ const qmdBoostInput = await this.filterSearchResultsForRecall(
8096
8284
  memoryResults,
8097
- recallNamespaces,
8098
- retrievalQuery,
8099
8285
  void 0,
8100
- { asOfMs }
8286
+ {
8287
+ asOfMs,
8288
+ // If QMD had already settled before the ordered assembly reached it,
8289
+ // do not let unrelated slow enrichment turn those known results into
8290
+ // unchecked misses. QMD that only settles during its own wait remains
8291
+ // bounded by the shared post-retrieval assembly deadline.
8292
+ deadlineAtMs: qmdWasSettledBeforeAssemblyWait ? null : enrichmentAssemblyDeadlineAtMs,
8293
+ abortSignal: options.abortSignal,
8294
+ dropUnresolved: true,
8295
+ recallNamespaces
8296
+ }
8297
+ );
8298
+ memoryResults = await awaitAssemblyStep(
8299
+ "qmd-boost",
8300
+ () => this.boostSearchResults(
8301
+ qmdBoostInput.results,
8302
+ recallNamespaces,
8303
+ retrievalQuery,
8304
+ qmdBoostInput.memoryByPath,
8305
+ { asOfMs }
8306
+ ),
8307
+ qmdBoostInput.results
8101
8308
  );
8102
8309
  if (this.config.rerankEnabled && this.config.rerankProvider === "local") {
8103
8310
  const ranked = await rerankLocalOrNoop({
@@ -8224,39 +8431,45 @@ ${tmtNode.summary}`
8224
8431
  } else if (!confidenceGateRejected) {
8225
8432
  const queryAwarePrefilter = await queryAwarePrefilterPromise;
8226
8433
  if (queryAwarePrefilter.candidatePaths?.size !== 0) {
8227
- const embeddingResults = await this.searchEmbeddingFallback(
8228
- retrievalQuery,
8229
- embeddingFetchLimit
8230
- );
8231
- const prefilteredEmbeddingResults = applyQueryAwareCandidateFilter(
8232
- embeddingResults,
8233
- queryAwarePrefilter.candidatePaths
8234
- );
8235
- const scopedCandidates = filterRecallCandidates(
8236
- prefilteredEmbeddingResults,
8237
- {
8238
- namespacesEnabled: this.config.namespacesEnabled,
8239
- recallNamespaces,
8240
- resolveNamespace: (p) => this.namespaceFromPath(p),
8241
- limit: embeddingFetchLimit
8242
- }
8243
- );
8244
- const boostedScoped = await this.boostSearchResults(
8245
- scopedCandidates,
8246
- recallNamespaces,
8247
- retrievalQuery,
8248
- void 0,
8249
- { asOfMs }
8250
- );
8251
- xrayBranchPoolSize.hot_embedding = Math.max(
8252
- xrayBranchPoolSize.hot_embedding,
8253
- boostedScoped.length
8254
- );
8255
- const scoped = this.diversifyAndLimitRecallResults(
8256
- "memories",
8257
- boostedScoped,
8258
- recallResultLimit,
8259
- retrievalQuery
8434
+ const scoped = await awaitAssemblyStep(
8435
+ "embedding-fallback",
8436
+ async () => {
8437
+ const embeddingResults = await this.searchEmbeddingFallback(
8438
+ retrievalQuery,
8439
+ embeddingFetchLimit
8440
+ );
8441
+ const prefilteredEmbeddingResults = applyQueryAwareCandidateFilter(
8442
+ embeddingResults,
8443
+ queryAwarePrefilter.candidatePaths
8444
+ );
8445
+ const scopedCandidates = filterRecallCandidates(
8446
+ prefilteredEmbeddingResults,
8447
+ {
8448
+ namespacesEnabled: this.config.namespacesEnabled,
8449
+ recallNamespaces,
8450
+ resolveNamespace: (p) => this.namespaceFromPath(p),
8451
+ limit: embeddingFetchLimit
8452
+ }
8453
+ );
8454
+ const boostedScoped = await this.boostSearchResults(
8455
+ scopedCandidates,
8456
+ recallNamespaces,
8457
+ retrievalQuery,
8458
+ void 0,
8459
+ { asOfMs }
8460
+ );
8461
+ xrayBranchPoolSize.hot_embedding = Math.max(
8462
+ xrayBranchPoolSize.hot_embedding,
8463
+ boostedScoped.length
8464
+ );
8465
+ return this.diversifyAndLimitRecallResults(
8466
+ "memories",
8467
+ boostedScoped,
8468
+ recallResultLimit,
8469
+ retrievalQuery
8470
+ );
8471
+ },
8472
+ []
8260
8473
  );
8261
8474
  if (scoped.length > 0) {
8262
8475
  if (shouldPersistGraphSnapshot) {
@@ -8292,6 +8505,7 @@ ${tmtNode.summary}`
8292
8505
  queryAwarePrefilter,
8293
8506
  abortSignal: options.abortSignal,
8294
8507
  xrayPoolSizeSink: xrayColdPoolSink,
8508
+ deadlineAtMs: enrichmentAssemblyDeadlineAtMs,
8295
8509
  asOfMs,
8296
8510
  ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {}
8297
8511
  });
@@ -8358,39 +8572,45 @@ ${tmtNode.summary}`
8358
8572
  } else if (recallResultLimit > 0 && !this.qmd.isAvailable()) {
8359
8573
  const queryAwarePrefilter = await queryAwarePrefilterPromise;
8360
8574
  if (queryAwarePrefilter.candidatePaths?.size !== 0) {
8361
- const embeddingResults = await this.searchEmbeddingFallback(
8362
- retrievalQuery,
8363
- embeddingFetchLimit
8364
- );
8365
- const prefilteredEmbeddingResults = applyQueryAwareCandidateFilter(
8366
- embeddingResults,
8367
- queryAwarePrefilter.candidatePaths
8368
- );
8369
- const scopedCandidates = filterRecallCandidates(
8370
- prefilteredEmbeddingResults,
8371
- {
8372
- namespacesEnabled: this.config.namespacesEnabled,
8373
- recallNamespaces,
8374
- resolveNamespace: (p) => this.namespaceFromPath(p),
8375
- limit: embeddingFetchLimit
8376
- }
8377
- );
8378
- const boostedScoped = await this.boostSearchResults(
8379
- scopedCandidates,
8380
- recallNamespaces,
8381
- retrievalQuery,
8382
- void 0,
8383
- { asOfMs }
8384
- );
8385
- xrayBranchPoolSize.hot_embedding = Math.max(
8386
- xrayBranchPoolSize.hot_embedding,
8387
- boostedScoped.length
8388
- );
8389
- const scoped = this.diversifyAndLimitRecallResults(
8390
- "memories",
8391
- boostedScoped,
8392
- recallResultLimit,
8393
- retrievalQuery
8575
+ const scoped = await awaitAssemblyStep(
8576
+ "embedding-fallback",
8577
+ async () => {
8578
+ const embeddingResults = await this.searchEmbeddingFallback(
8579
+ retrievalQuery,
8580
+ embeddingFetchLimit
8581
+ );
8582
+ const prefilteredEmbeddingResults = applyQueryAwareCandidateFilter(
8583
+ embeddingResults,
8584
+ queryAwarePrefilter.candidatePaths
8585
+ );
8586
+ const scopedCandidates = filterRecallCandidates(
8587
+ prefilteredEmbeddingResults,
8588
+ {
8589
+ namespacesEnabled: this.config.namespacesEnabled,
8590
+ recallNamespaces,
8591
+ resolveNamespace: (p) => this.namespaceFromPath(p),
8592
+ limit: embeddingFetchLimit
8593
+ }
8594
+ );
8595
+ const boostedScoped = await this.boostSearchResults(
8596
+ scopedCandidates,
8597
+ recallNamespaces,
8598
+ retrievalQuery,
8599
+ void 0,
8600
+ { asOfMs }
8601
+ );
8602
+ xrayBranchPoolSize.hot_embedding = Math.max(
8603
+ xrayBranchPoolSize.hot_embedding,
8604
+ boostedScoped.length
8605
+ );
8606
+ return this.diversifyAndLimitRecallResults(
8607
+ "memories",
8608
+ boostedScoped,
8609
+ recallResultLimit,
8610
+ retrievalQuery
8611
+ );
8612
+ },
8613
+ []
8394
8614
  );
8395
8615
  if (scoped.length > 0) {
8396
8616
  if (shouldPersistGraphSnapshot) {
@@ -8418,7 +8638,11 @@ ${tmtNode.summary}`
8418
8638
  xrayRecalledResults = scoped;
8419
8639
  impressionRecorded = true;
8420
8640
  } else {
8421
- const memories = await this.readAllMemoriesForNamespaces(recallNamespaces);
8641
+ const memories = await awaitAssemblyStep(
8642
+ "recent-memory-read",
8643
+ () => this.readAllMemoriesForNamespaces(recallNamespaces),
8644
+ []
8645
+ );
8422
8646
  if (memories.length > 0) {
8423
8647
  const supersessionOptions = {
8424
8648
  enabled: this.config.temporalSupersessionEnabled,
@@ -8449,6 +8673,7 @@ ${tmtNode.summary}`
8449
8673
  queryAwarePrefilter,
8450
8674
  abortSignal: options.abortSignal,
8451
8675
  xrayPoolSizeSink: xrayColdPoolSink,
8676
+ deadlineAtMs: enrichmentAssemblyDeadlineAtMs,
8452
8677
  asOfMs,
8453
8678
  ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {}
8454
8679
  });
@@ -8473,36 +8698,42 @@ ${tmtNode.summary}`
8473
8698
  impressionRecorded = true;
8474
8699
  }
8475
8700
  } else {
8476
- const recentSorted = queryAwareScopedMemories.sort(
8477
- (a, b) => new Date(b.frontmatter.updated).getTime() - new Date(a.frontmatter.updated).getTime()
8478
- );
8479
- const preloadedMap = new Map(
8480
- queryAwareScopedMemories.filter((m) => m.path).map((m) => [m.path, m])
8481
- );
8482
- const recentAsResults = recentSorted.map(
8483
- (m, i) => ({
8484
- docid: m.frontmatter.id,
8485
- path: m.path,
8486
- snippet: m.content,
8487
- score: 1 - i / Math.max(recentSorted.length, 1)
8488
- })
8489
- );
8490
- const boostedRecent = (await this.boostSearchResults(
8491
- recentAsResults,
8492
- recallNamespaces,
8493
- retrievalQuery,
8494
- preloadedMap,
8495
- { asOfMs }
8496
- )).sort((a, b) => b.score - a.score);
8497
- xrayBranchPoolSize.recent_scan = Math.max(
8498
- xrayBranchPoolSize.recent_scan,
8499
- boostedRecent.length
8500
- );
8501
- const recent = this.diversifyAndLimitRecallResults(
8502
- "memories",
8503
- boostedRecent,
8504
- recallResultLimit,
8505
- retrievalQuery
8701
+ const recent = await awaitAssemblyStep(
8702
+ "recent-memory-scan",
8703
+ async () => {
8704
+ const recentSorted = queryAwareScopedMemories.sort(
8705
+ (a, b) => new Date(b.frontmatter.updated).getTime() - new Date(a.frontmatter.updated).getTime()
8706
+ );
8707
+ const preloadedMap = new Map(
8708
+ queryAwareScopedMemories.filter((m) => m.path).map((m) => [m.path, m])
8709
+ );
8710
+ const recentAsResults = recentSorted.map(
8711
+ (m, i) => ({
8712
+ docid: m.frontmatter.id,
8713
+ path: m.path,
8714
+ snippet: m.content,
8715
+ score: 1 - i / Math.max(recentSorted.length, 1)
8716
+ })
8717
+ );
8718
+ const boostedRecent = (await this.boostSearchResults(
8719
+ recentAsResults,
8720
+ recallNamespaces,
8721
+ retrievalQuery,
8722
+ preloadedMap,
8723
+ { asOfMs }
8724
+ )).sort((a, b) => b.score - a.score);
8725
+ xrayBranchPoolSize.recent_scan = Math.max(
8726
+ xrayBranchPoolSize.recent_scan,
8727
+ boostedRecent.length
8728
+ );
8729
+ return this.diversifyAndLimitRecallResults(
8730
+ "memories",
8731
+ boostedRecent,
8732
+ recallResultLimit,
8733
+ retrievalQuery
8734
+ );
8735
+ },
8736
+ []
8506
8737
  );
8507
8738
  if (recent.length > 0) {
8508
8739
  if (shouldPersistGraphSnapshot) {
@@ -8538,6 +8769,7 @@ ${tmtNode.summary}`
8538
8769
  queryAwarePrefilter,
8539
8770
  abortSignal: options.abortSignal,
8540
8771
  xrayPoolSizeSink: xrayColdPoolSink,
8772
+ deadlineAtMs: enrichmentAssemblyDeadlineAtMs,
8541
8773
  asOfMs,
8542
8774
  ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {}
8543
8775
  });
@@ -8578,6 +8810,7 @@ ${tmtNode.summary}`
8578
8810
  queryAwarePrefilter,
8579
8811
  abortSignal: options.abortSignal,
8580
8812
  xrayPoolSizeSink: xrayColdPoolSink,
8813
+ deadlineAtMs: enrichmentAssemblyDeadlineAtMs,
8581
8814
  asOfMs,
8582
8815
  ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {}
8583
8816
  });
@@ -8872,6 +9105,7 @@ _Context: ${topQuestion.context}_`
8872
9105
  query: retrievalQuery,
8873
9106
  memoryIds: recalledMemoryIds,
8874
9107
  namespace: selfNamespace,
9108
+ recallNamespaces,
8875
9109
  traceId,
8876
9110
  plannerMode: recallMode,
8877
9111
  requestedMode,
@@ -12268,6 +12502,212 @@ ${lines.join("\n\n")}`;
12268
12502
  *
12269
12503
  * Callers must pass the full candidate pool (post-rerank, pre-slice).
12270
12504
  */
12505
+ qmdCollectionNamespaceFromPrefix(collectionPrefix) {
12506
+ const baseCollection = this.config.qmdCollection;
12507
+ if (collectionPrefix === baseCollection) return this.config.defaultNamespace;
12508
+ const namespaceSuffix = collectionPrefix.startsWith(`${baseCollection}--`) ? collectionPrefix.slice(baseCollection.length + 2) : "";
12509
+ if (!namespaceSuffix) return null;
12510
+ const decoded = namespaceIdentityFromToken(namespaceSuffix);
12511
+ if (decoded !== null) return decoded || this.config.defaultNamespace;
12512
+ if (namespaceSuffix.startsWith("ns--")) {
12513
+ const legacyNamespace = namespaceSuffix.slice("ns--".length).trim();
12514
+ return legacyNamespace || null;
12515
+ }
12516
+ return null;
12517
+ }
12518
+ async readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces = []) {
12519
+ const parts = qmdCollectionPathParts(resultPath);
12520
+ const storageDirFor = (storage) => typeof storage.dir === "string" && storage.dir ? storage.dir : null;
12521
+ const fallbackStorageDir = storageDirFor(fallbackStorage);
12522
+ const coldCollection = this.config.qmdColdCollection ?? "openclaw-engram-cold";
12523
+ if (parts && parts.collection === coldCollection) {
12524
+ const storages = [];
12525
+ const seenStorageDirs = /* @__PURE__ */ new Set();
12526
+ const addStorage = (storage) => {
12527
+ const storageDir = storageDirFor(storage);
12528
+ const storageKey = storageDir ? path2.resolve(storageDir) : `storage-without-dir-${storages.length}`;
12529
+ if (seenStorageDirs.has(storageKey)) return;
12530
+ seenStorageDirs.add(storageKey);
12531
+ storages.push(storage);
12532
+ };
12533
+ const fallbackNamespace = fallbackStorageDir !== null ? this.namespaceFromStorageDir(fallbackStorageDir) : this.config.defaultNamespace;
12534
+ if (recallNamespaces.length === 0 || !this.config.namespacesEnabled || recallNamespaces.includes(fallbackNamespace)) {
12535
+ addStorage(fallbackStorage);
12536
+ }
12537
+ if (recallNamespaces.length > 0) {
12538
+ for (const namespace of recallNamespaces) {
12539
+ try {
12540
+ addStorage(await this.storageRouter.storageFor(namespace));
12541
+ } catch (err) {
12542
+ log.debug("qmd cold result namespace storage lookup skipped", {
12543
+ path: resultPath,
12544
+ namespace,
12545
+ error: err.message
12546
+ });
12547
+ }
12548
+ }
12549
+ }
12550
+ for (const storage of storages) {
12551
+ const storageDir = storageDirFor(storage);
12552
+ if (!storageDir) {
12553
+ const memory = await storage.readMemoryByPath(resultPath);
12554
+ if (memory) return memory;
12555
+ continue;
12556
+ }
12557
+ try {
12558
+ const coldRoot = path2.join(storageDir, "cold");
12559
+ for (const candidate of qmdResultPathCandidates(
12560
+ coldRoot,
12561
+ parts.relativePath
12562
+ )) {
12563
+ const memory = await storage.readMemoryByPath(candidate);
12564
+ if (memory) return memory;
12565
+ }
12566
+ } catch (err) {
12567
+ if (err instanceof SecureStoreLockedError) throw err;
12568
+ log.debug("qmd cold result path lookup failed open", {
12569
+ path: resultPath,
12570
+ collection: coldCollection,
12571
+ error: err.message
12572
+ });
12573
+ }
12574
+ }
12575
+ return null;
12576
+ }
12577
+ const collectionNamespace = parts ? this.qmdCollectionNamespaceFromPrefix(parts.collection) : null;
12578
+ if (parts && collectionNamespace) {
12579
+ try {
12580
+ const collectionStorage = await this.storageRouter.storageFor(collectionNamespace);
12581
+ for (const candidate of qmdResultPathCandidates(
12582
+ collectionStorage.dir,
12583
+ parts.relativePath
12584
+ )) {
12585
+ const memory = await collectionStorage.readMemoryByPath(candidate);
12586
+ if (memory) return memory;
12587
+ }
12588
+ return null;
12589
+ } catch (err) {
12590
+ if (err instanceof SecureStoreLockedError) throw err;
12591
+ log.debug("qmd result namespace path lookup failed open", {
12592
+ path: resultPath,
12593
+ namespace: collectionNamespace,
12594
+ error: err.message
12595
+ });
12596
+ return null;
12597
+ }
12598
+ }
12599
+ if (path2.isAbsolute(resultPath)) {
12600
+ if (!fallbackStorageDir) {
12601
+ return await fallbackStorage.readMemoryByPath(resultPath);
12602
+ }
12603
+ const ownerStorage = await this.storageForAbsoluteQmdResultPath(
12604
+ resultPath,
12605
+ fallbackStorage,
12606
+ recallNamespaces
12607
+ );
12608
+ if (!ownerStorage) return null;
12609
+ for (const candidate of qmdResultPathCandidates(
12610
+ ownerStorage.dir,
12611
+ resultPath
12612
+ )) {
12613
+ const memory = await ownerStorage.storage.readMemoryByPath(candidate);
12614
+ if (memory) return memory;
12615
+ }
12616
+ return null;
12617
+ }
12618
+ if (!fallbackStorageDir) {
12619
+ return await fallbackStorage.readMemoryByPath(resultPath);
12620
+ }
12621
+ for (const candidate of qmdResultPathCandidates(
12622
+ fallbackStorageDir,
12623
+ resultPath
12624
+ )) {
12625
+ const memory = await fallbackStorage.readMemoryByPath(candidate);
12626
+ if (memory) return memory;
12627
+ }
12628
+ return null;
12629
+ }
12630
+ async resolveColdQmdResultForRecall(result, fallbackStorage, recallNamespaces = []) {
12631
+ const memory = await this.readQmdResultMemory(
12632
+ result.path,
12633
+ fallbackStorage,
12634
+ recallNamespaces
12635
+ );
12636
+ if (!memory) return null;
12637
+ let ownerNamespace = null;
12638
+ if (path2.isAbsolute(memory.path)) {
12639
+ const ownerStorage = await this.storageForAbsoluteQmdResultPath(
12640
+ memory.path,
12641
+ fallbackStorage,
12642
+ recallNamespaces
12643
+ );
12644
+ ownerNamespace = ownerStorage?.namespace ?? null;
12645
+ if (!ownerNamespace && this.config.namespacesEnabled) return null;
12646
+ }
12647
+ ownerNamespace ??= this.namespaceFromPath(memory.path);
12648
+ if (recallNamespaces.length > 0 && !recallNamespaces.includes(ownerNamespace)) {
12649
+ return null;
12650
+ }
12651
+ return {
12652
+ namespace: ownerNamespace,
12653
+ result: {
12654
+ ...result,
12655
+ docid: result.docid || memory.frontmatter.id,
12656
+ path: memory.path,
12657
+ snippet: result.snippet || memory.content.slice(0, 400)
12658
+ }
12659
+ };
12660
+ }
12661
+ async storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces = []) {
12662
+ const resolvedPath = path2.resolve(resultPath);
12663
+ const memoryRoot = path2.resolve(this.config.memoryDir);
12664
+ const namespacesRoot = path2.join(memoryRoot, "namespaces");
12665
+ const fallbackStorageDir = typeof fallbackStorage.dir === "string" && fallbackStorage.dir ? fallbackStorage.dir : null;
12666
+ const matches = [];
12667
+ const seenDirs = /* @__PURE__ */ new Set();
12668
+ const maybeAddStorage = (storage, namespace) => {
12669
+ const storageDir = typeof storage.dir === "string" && storage.dir ? storage.dir : null;
12670
+ if (!storageDir) return;
12671
+ const candidateRoot = path2.resolve(storageDir);
12672
+ if (seenDirs.has(candidateRoot)) return;
12673
+ if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
12674
+ if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
12675
+ return;
12676
+ }
12677
+ seenDirs.add(candidateRoot);
12678
+ matches.push({ storage, dir: candidateRoot, namespace });
12679
+ };
12680
+ const fallbackNamespace = fallbackStorageDir !== null ? this.namespaceFromStorageDir(fallbackStorageDir) : this.config.defaultNamespace;
12681
+ maybeAddStorage(fallbackStorage, fallbackNamespace);
12682
+ const candidateNamespaces = /* @__PURE__ */ new Set();
12683
+ candidateNamespaces.add(this.config.defaultNamespace);
12684
+ candidateNamespaces.add(this.config.sharedNamespace);
12685
+ for (const ns of recallNamespaces) {
12686
+ candidateNamespaces.add(ns);
12687
+ }
12688
+ if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
12689
+ const relativeToNamespaces = path2.relative(namespacesRoot, resolvedPath);
12690
+ const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
12691
+ if (namespaceSegment) {
12692
+ candidateNamespaces.add(
12693
+ namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
12694
+ );
12695
+ }
12696
+ }
12697
+ for (const policy of this.config.namespacePolicies ?? []) {
12698
+ candidateNamespaces.add(policy.name);
12699
+ }
12700
+ for (const ns of candidateNamespaces) {
12701
+ if (!ns) continue;
12702
+ try {
12703
+ maybeAddStorage(await this.storageRouter.storageFor(ns), ns);
12704
+ } catch {
12705
+ continue;
12706
+ }
12707
+ }
12708
+ matches.sort((a, b) => b.dir.length - a.dir.length);
12709
+ return matches[0] ?? null;
12710
+ }
12271
12711
  async applyMemoryWorthRerank(results, namespaces) {
12272
12712
  const counters = /* @__PURE__ */ new Map();
12273
12713
  const seenNamespaces = /* @__PURE__ */ new Set();
@@ -12312,7 +12752,7 @@ ${lines.join("\n\n")}`;
12312
12752
  if (reader) {
12313
12753
  for (const r of missing) {
12314
12754
  try {
12315
- const memory = await reader.readMemoryByPath(r.path);
12755
+ const memory = await this.readQmdResultMemory(r.path, reader, namespaces);
12316
12756
  if (!memory) continue;
12317
12757
  const fm = memory.frontmatter;
12318
12758
  if (fm.mw_success === void 0 && fm.mw_fail === void 0) continue;
@@ -12560,6 +13000,41 @@ ${lines.join("\n\n")}`;
12560
13000
  if (options.xrayPoolSizeSink) options.xrayPoolSizeSink.size = 0;
12561
13001
  return [];
12562
13002
  }
13003
+ const deadlineRemainingMs = () => typeof options.deadlineAtMs === "number" ? Math.max(0, options.deadlineAtMs - Date.now()) : null;
13004
+ const runColdStepWithinDeadline = async (label, fallback, task) => {
13005
+ throwIfRecallAborted(options.abortSignal);
13006
+ const remainingMs = deadlineRemainingMs();
13007
+ if (remainingMs === 0) {
13008
+ log.debug(`cold-tier recall ${label} skipped: shared assembly deadline expired`);
13009
+ return fallback;
13010
+ }
13011
+ if (remainingMs === null) return task();
13012
+ let timeoutHandle;
13013
+ let timedOut = false;
13014
+ const taskPromise = task().catch((err) => {
13015
+ if (timedOut) {
13016
+ log.debug(`cold-tier recall ${label} failed after deadline: ${err}`);
13017
+ return fallback;
13018
+ }
13019
+ throw err;
13020
+ });
13021
+ try {
13022
+ return await Promise.race([
13023
+ taskPromise,
13024
+ new Promise((resolve) => {
13025
+ timeoutHandle = setTimeout(() => {
13026
+ timedOut = true;
13027
+ log.debug(
13028
+ `cold-tier recall ${label} skipped: shared assembly deadline expired`
13029
+ );
13030
+ resolve(fallback);
13031
+ }, remainingMs);
13032
+ })
13033
+ ]);
13034
+ } finally {
13035
+ if (timeoutHandle) clearTimeout(timeoutHandle);
13036
+ }
13037
+ };
12563
13038
  const coldQmdEnabled = this.config.qmdColdTierEnabled === true;
12564
13039
  const coldCollection = this.config.qmdColdCollection ?? "openclaw-engram-cold";
12565
13040
  const coldMaxResults = this.config.qmdColdMaxResults ?? this.config.qmdMaxResults;
@@ -12575,19 +13050,23 @@ ${lines.join("\n\n")}`;
12575
13050
  false,
12576
13051
  0
12577
13052
  );
12578
- longTerm = await this.fetchQmdMemoryResultsWithArtifactTopUp(
12579
- options.prompt,
12580
- coldFetchLimit,
12581
- coldHybridLimit,
12582
- {
12583
- namespacesEnabled: this.config.namespacesEnabled,
12584
- recallNamespaces: options.recallNamespaces,
12585
- resolveNamespace: (p) => this.namespaceFromPath(p),
12586
- collection: coldCollection,
12587
- queryAwarePrefilter: options.queryAwarePrefilter,
12588
- searchOptions: this.buildConfiguredQmdSearchOptions(options.prompt),
12589
- abortSignal: options.abortSignal
12590
- }
13053
+ longTerm = await runColdStepWithinDeadline(
13054
+ "qmd lookup",
13055
+ [],
13056
+ () => this.fetchQmdMemoryResultsWithArtifactTopUp(
13057
+ options.prompt,
13058
+ coldFetchLimit,
13059
+ coldHybridLimit,
13060
+ {
13061
+ namespacesEnabled: this.config.namespacesEnabled,
13062
+ recallNamespaces: options.recallNamespaces,
13063
+ resolveNamespace: (p) => this.namespaceFromPath(p),
13064
+ collection: coldCollection,
13065
+ queryAwarePrefilter: options.queryAwarePrefilter,
13066
+ searchOptions: this.buildConfiguredQmdSearchOptions(options.prompt),
13067
+ abortSignal: options.abortSignal
13068
+ }
13069
+ )
12591
13070
  );
12592
13071
  if (longTerm.length > 0) {
12593
13072
  log.debug(
@@ -12597,12 +13076,16 @@ ${lines.join("\n\n")}`;
12597
13076
  }
12598
13077
  }
12599
13078
  if (longTerm.length === 0) {
12600
- longTerm = await this.searchLongTermArchiveFallback(
12601
- options.prompt,
12602
- options.recallNamespaces,
12603
- options.recallResultLimit,
12604
- options.queryAwarePrefilter,
12605
- options.abortSignal
13079
+ longTerm = await runColdStepWithinDeadline(
13080
+ "archive scan",
13081
+ [],
13082
+ () => this.searchLongTermArchiveFallback(
13083
+ options.prompt,
13084
+ options.recallNamespaces,
13085
+ options.recallResultLimit,
13086
+ options.queryAwarePrefilter,
13087
+ options.abortSignal
13088
+ )
12606
13089
  );
12607
13090
  if (longTerm.length > 0) {
12608
13091
  log.debug("cold-tier recall source=archive-scan");
@@ -12611,9 +13094,51 @@ ${lines.join("\n\n")}`;
12611
13094
  if (longTerm.length === 0) return [];
12612
13095
  let results = longTerm;
12613
13096
  if (this.config.namespacesEnabled) {
12614
- results = results.filter(
12615
- (r) => options.recallNamespaces.includes(this.namespaceFromPath(r.path))
12616
- );
13097
+ const recallRoots = [];
13098
+ const seenRecallRoots = /* @__PURE__ */ new Set();
13099
+ for (const namespace of options.recallNamespaces) {
13100
+ try {
13101
+ const storage = await this.storageRouter.storageFor(namespace);
13102
+ const storageDir = typeof storage.dir === "string" && storage.dir ? storage.dir : null;
13103
+ if (!storageDir) continue;
13104
+ const recallRoot = path2.resolve(storageDir);
13105
+ if (seenRecallRoots.has(recallRoot)) continue;
13106
+ seenRecallRoots.add(recallRoot);
13107
+ recallRoots.push(recallRoot);
13108
+ } catch (err) {
13109
+ log.debug("cold-tier recall namespace root lookup skipped", {
13110
+ namespace,
13111
+ error: err.message
13112
+ });
13113
+ }
13114
+ }
13115
+ const scopedResults = [];
13116
+ for (const result of results) {
13117
+ if (options.abortSignal?.aborted || deadlineRemainingMs() === 0) break;
13118
+ const parts = qmdCollectionPathParts(result.path);
13119
+ if (parts?.collection === coldCollection) {
13120
+ const resolvedCold = await this.resolveColdQmdResultForRecall(
13121
+ result,
13122
+ this.storage,
13123
+ options.recallNamespaces
13124
+ );
13125
+ if (resolvedCold) scopedResults.push(resolvedCold.result);
13126
+ continue;
13127
+ }
13128
+ if (path2.isAbsolute(result.path)) {
13129
+ const resolvedPath = path2.resolve(result.path);
13130
+ if (recallRoots.some(
13131
+ (recallRoot) => isPathInsideStorageRoot(recallRoot, resolvedPath)
13132
+ )) {
13133
+ scopedResults.push(result);
13134
+ }
13135
+ continue;
13136
+ }
13137
+ if (options.recallNamespaces.includes(this.namespaceFromPath(result.path))) {
13138
+ scopedResults.push(result);
13139
+ }
13140
+ }
13141
+ results = scopedResults;
12617
13142
  }
12618
13143
  results = results.filter((r) => !isArtifactMemoryPath(r.path));
12619
13144
  if (results.length === 0) return [];
@@ -12624,17 +13149,64 @@ ${lines.join("\n\n")}`;
12624
13149
  memoryResults: results,
12625
13150
  recallNamespaces: options.recallNamespaces,
12626
13151
  recallResultLimit: options.recallResultLimit,
13152
+ deadlineAtMs: options.deadlineAtMs,
12627
13153
  ...options.includeLowConfidence === true ? { includeLowConfidence: true } : {}
12628
13154
  });
12629
13155
  results = merged;
12630
13156
  }
12631
- results = await this.boostSearchResults(
13157
+ const boostInput = await this.filterSearchResultsForRecall(
12632
13158
  results,
12633
- options.recallNamespaces,
12634
- options.prompt,
12635
13159
  void 0,
12636
- { allowLifecycleFiltered: true, asOfMs: options.asOfMs }
13160
+ {
13161
+ allowLifecycleFiltered: true,
13162
+ asOfMs: options.asOfMs,
13163
+ deadlineAtMs: options.deadlineAtMs,
13164
+ abortSignal: options.abortSignal,
13165
+ dropUnresolved: true,
13166
+ recallNamespaces: options.recallNamespaces
13167
+ }
12637
13168
  );
13169
+ results = boostInput.results;
13170
+ const boostTimeoutMs = typeof options.deadlineAtMs === "number" ? Math.max(0, options.deadlineAtMs - Date.now()) : null;
13171
+ if (boostTimeoutMs !== 0) {
13172
+ let timeoutHandle;
13173
+ try {
13174
+ const boosted = await (boostTimeoutMs !== null ? Promise.race([
13175
+ this.boostSearchResults(
13176
+ boostInput.results,
13177
+ options.recallNamespaces,
13178
+ options.prompt,
13179
+ boostInput.memoryByPath,
13180
+ { allowLifecycleFiltered: true, asOfMs: options.asOfMs }
13181
+ ),
13182
+ new Promise((resolve) => {
13183
+ timeoutHandle = setTimeout(
13184
+ () => resolve({ status: "timed_out" }),
13185
+ boostTimeoutMs
13186
+ );
13187
+ })
13188
+ ]) : this.boostSearchResults(
13189
+ boostInput.results,
13190
+ options.recallNamespaces,
13191
+ options.prompt,
13192
+ boostInput.memoryByPath,
13193
+ { allowLifecycleFiltered: true, asOfMs: options.asOfMs }
13194
+ ));
13195
+ if (typeof boosted === "object" && boosted !== null && "status" in boosted && boosted.status === "timed_out") {
13196
+ log.debug("cold-tier recall boost skipped: shared assembly deadline expired");
13197
+ } else if (Array.isArray(boosted)) {
13198
+ results = boosted;
13199
+ } else {
13200
+ results = boostInput.results;
13201
+ }
13202
+ } catch (err) {
13203
+ log.debug(`cold-tier recall boost failed open: ${err}`);
13204
+ } finally {
13205
+ if (timeoutHandle) clearTimeout(timeoutHandle);
13206
+ }
13207
+ } else {
13208
+ log.debug("cold-tier recall boost skipped: shared assembly deadline already expired");
13209
+ }
12638
13210
  if (this.config.rerankEnabled && this.config.rerankProvider === "local") {
12639
13211
  const ranked = await rerankLocalOrNoop({
12640
13212
  query: options.prompt,
@@ -12755,64 +13327,101 @@ ${lines.join("\n\n")}`;
12755
13327
  this.accessTrackingBuffer.clear();
12756
13328
  log.debug(`flushed ${entries.length} access tracking entries`);
12757
13329
  }
12758
- /**
12759
- * Apply recency, access count, and importance boosting to QMD search results.
12760
- * Returns re-ranked results.
12761
- */
12762
- async boostSearchResults(results, _recallNamespaces, prompt, preloadedMemoryMap, options) {
12763
- if (results.length === 0) return results;
12764
- const now = Date.now();
13330
+ async loadSearchResultMemoryMap(results, preloadedMemoryMap, options) {
12765
13331
  const memoryByPath = preloadedMemoryMap ? new Map(preloadedMemoryMap) : /* @__PURE__ */ new Map();
12766
- const resultPaths = new Set(
12767
- results.map((r) => r.path).filter(Boolean)
12768
- );
12769
- let temporalFromDate = null;
12770
- let promptTags = [];
12771
- if (this.config.queryAwareIndexingEnabled && prompt) {
12772
- if (isTemporalQuery(prompt)) {
12773
- temporalFromDate = recencyWindowFromPrompt(prompt, now);
13332
+ const checkedPaths = /* @__PURE__ */ new Set();
13333
+ const unreadablePaths = /* @__PURE__ */ new Set();
13334
+ const markChecked = (result) => {
13335
+ if (result.path) checkedPaths.add(result.path);
13336
+ };
13337
+ const markUnreadable = (result, err) => {
13338
+ if (!result.path) return;
13339
+ checkedPaths.add(result.path);
13340
+ unreadablePaths.add(result.path);
13341
+ log.warn("recall safety filter dropped unreadable secure-store candidate", {
13342
+ path: result.path,
13343
+ error: err.message
13344
+ });
13345
+ };
13346
+ const deadlineExpired = () => typeof options?.deadlineAtMs === "number" && Date.now() >= options.deadlineAtMs;
13347
+ if (options?.deadlineAtMs == null) {
13348
+ const batchSize = options?.abortSignal ? 16 : results.length;
13349
+ for (let offset = 0; offset < results.length; offset += batchSize) {
13350
+ if (options?.abortSignal?.aborted) {
13351
+ return {
13352
+ memoryByPath,
13353
+ checkedPaths,
13354
+ unreadablePaths,
13355
+ completed: false
13356
+ };
13357
+ }
13358
+ await Promise.all(
13359
+ results.slice(offset, offset + batchSize).map(async (r) => {
13360
+ if (!r.path) return;
13361
+ if (memoryByPath.has(r.path)) {
13362
+ markChecked(r);
13363
+ return;
13364
+ }
13365
+ try {
13366
+ const mem = await this.readQmdResultMemory(
13367
+ r.path,
13368
+ this.storage,
13369
+ options?.recallNamespaces
13370
+ );
13371
+ markChecked(r);
13372
+ if (mem) memoryByPath.set(r.path, mem);
13373
+ } catch (err) {
13374
+ if (err instanceof SecureStoreLockedError) {
13375
+ markUnreadable(r, err);
13376
+ return;
13377
+ }
13378
+ throw err;
13379
+ }
13380
+ })
13381
+ );
12774
13382
  }
12775
- promptTags = extractTagsFromPrompt(prompt);
13383
+ return { memoryByPath, checkedPaths, unreadablePaths, completed: true };
12776
13384
  }
12777
- const [, rawTemporal, rawTags] = await Promise.all([
12778
- Promise.all(
12779
- results.map(async (r) => {
12780
- if (!r.path || memoryByPath.has(r.path)) return;
12781
- const mem = await this.storage.readMemoryByPath(r.path);
12782
- if (mem) memoryByPath.set(r.path, mem);
12783
- })
12784
- ),
12785
- temporalFromDate !== null ? queryByDateRangeAsync(this.config.memoryDir, temporalFromDate) : Promise.resolve(null),
12786
- promptTags.length > 0 ? queryByTagsAsync(this.config.memoryDir, promptTags) : Promise.resolve(null)
12787
- ]);
12788
- const queryIntent = this.config.intentRoutingEnabled && prompt ? inferIntentFromText(prompt) : null;
12789
- let temporalCandidates = null;
12790
- let tagCandidates = null;
12791
- if (this.config.queryAwareIndexingEnabled && prompt) {
12792
- const maxCandidates = this.config.queryAwareIndexingMaxCandidates;
12793
- const capSet = (s) => {
12794
- if (!s) return null;
12795
- const scoped = new Set(Array.from(s).filter((p) => resultPaths.has(p)));
12796
- if (maxCandidates === 0 || scoped.size <= maxCandidates)
12797
- return scoped;
12798
- return new Set(Array.from(scoped).slice(0, maxCandidates));
12799
- };
12800
- if (temporalFromDate !== null) {
12801
- temporalCandidates = capSet(rawTemporal);
13385
+ for (const result of results) {
13386
+ if (!result.path) continue;
13387
+ if (memoryByPath.has(result.path)) {
13388
+ markChecked(result);
13389
+ continue;
12802
13390
  }
12803
- if (promptTags.length > 0) {
12804
- tagCandidates = capSet(rawTags);
13391
+ if (options?.abortSignal?.aborted || deadlineExpired()) {
13392
+ return { memoryByPath, checkedPaths, unreadablePaths, completed: false };
13393
+ }
13394
+ try {
13395
+ const mem = await this.readQmdResultMemory(
13396
+ result.path,
13397
+ this.storage,
13398
+ options?.recallNamespaces
13399
+ );
13400
+ markChecked(result);
13401
+ if (mem) memoryByPath.set(result.path, mem);
13402
+ } catch (err) {
13403
+ if (err instanceof SecureStoreLockedError) {
13404
+ markUnreadable(result, err);
13405
+ continue;
13406
+ }
13407
+ throw err;
12805
13408
  }
12806
13409
  }
13410
+ return { memoryByPath, checkedPaths, unreadablePaths, completed: true };
13411
+ }
13412
+ filterSearchResultsByRecallSafety(results, memoryByPath, options) {
12807
13413
  let lifecycleFilteredCount = 0;
12808
13414
  let temporalSupersededFilteredCount = 0;
12809
13415
  let dedicatedSurfaceFilteredCount = 0;
12810
13416
  let forgottenFilteredCount = 0;
12811
- const boosted = [];
12812
- const recencyWeight = this.effectiveRecencyWeight();
13417
+ let blockedPathFilteredCount = 0;
13418
+ const filtered = [];
12813
13419
  for (const r of results) {
13420
+ if (r.path && options?.blockedPaths?.has(r.path)) {
13421
+ blockedPathFilteredCount += 1;
13422
+ continue;
13423
+ }
12814
13424
  const memory = memoryByPath.get(r.path);
12815
- let score = r.score;
12816
13425
  if (memory) {
12817
13426
  if (memory.frontmatter.status === "forgotten") {
12818
13427
  forgottenFilteredCount += 1;
@@ -12854,6 +13463,126 @@ ${lines.join("\n\n")}`;
12854
13463
  dedicatedSurfaceFilteredCount += 1;
12855
13464
  continue;
12856
13465
  }
13466
+ }
13467
+ filtered.push(r);
13468
+ }
13469
+ if (lifecycleFilteredCount > 0) {
13470
+ log.debug(
13471
+ `lifecycle retrieval filter removed ${lifecycleFilteredCount} stale/archived candidates`
13472
+ );
13473
+ }
13474
+ if (temporalSupersededFilteredCount > 0) {
13475
+ log.debug(
13476
+ `temporal supersession filter removed ${temporalSupersededFilteredCount} superseded candidates`
13477
+ );
13478
+ }
13479
+ if (dedicatedSurfaceFilteredCount > 0) {
13480
+ log.debug(
13481
+ `dedicated surface filter removed ${dedicatedSurfaceFilteredCount} dream/procedural candidates from generic recall`
13482
+ );
13483
+ }
13484
+ if (forgottenFilteredCount > 0) {
13485
+ log.debug(
13486
+ `forgotten status filter removed ${forgottenFilteredCount} candidates from recall`
13487
+ );
13488
+ }
13489
+ if (blockedPathFilteredCount > 0) {
13490
+ log.debug(
13491
+ `unreadable-path filter removed ${blockedPathFilteredCount} candidates from recall`
13492
+ );
13493
+ }
13494
+ return filtered;
13495
+ }
13496
+ async filterSearchResultsForRecall(results, preloadedMemoryMap, options) {
13497
+ if (results.length === 0) {
13498
+ return {
13499
+ results,
13500
+ memoryByPath: preloadedMemoryMap ? new Map(preloadedMemoryMap) : /* @__PURE__ */ new Map()
13501
+ };
13502
+ }
13503
+ const loaded = await this.loadSearchResultMemoryMap(
13504
+ results,
13505
+ preloadedMemoryMap,
13506
+ options
13507
+ );
13508
+ const candidateResults = loaded.completed ? results : results.filter((result) => !result.path || loaded.checkedPaths.has(result.path));
13509
+ if (!loaded.completed) {
13510
+ log.debug(
13511
+ `recall safety filter stopped before validating all candidates (${candidateResults.length}/${results.length} eligible)`
13512
+ );
13513
+ }
13514
+ const blockedPaths = new Set(loaded.unreadablePaths);
13515
+ if (options?.dropUnresolved === true) {
13516
+ for (const resultPath of loaded.checkedPaths) {
13517
+ if (!loaded.memoryByPath.has(resultPath)) {
13518
+ blockedPaths.add(resultPath);
13519
+ }
13520
+ }
13521
+ }
13522
+ return {
13523
+ results: this.filterSearchResultsByRecallSafety(
13524
+ candidateResults,
13525
+ loaded.memoryByPath,
13526
+ { ...options, blockedPaths }
13527
+ ),
13528
+ memoryByPath: loaded.memoryByPath
13529
+ };
13530
+ }
13531
+ /**
13532
+ * Apply recency, access count, and importance boosting to QMD search results.
13533
+ * Returns re-ranked results.
13534
+ */
13535
+ async boostSearchResults(results, _recallNamespaces, prompt, preloadedMemoryMap, options) {
13536
+ if (results.length === 0) return results;
13537
+ const safety = await this.filterSearchResultsForRecall(
13538
+ results,
13539
+ preloadedMemoryMap,
13540
+ { ...options, recallNamespaces: _recallNamespaces }
13541
+ );
13542
+ const safeResults = safety.results;
13543
+ const memoryByPath = safety.memoryByPath;
13544
+ if (safeResults.length === 0) return safeResults;
13545
+ const now = Date.now();
13546
+ const resultPaths = new Set(
13547
+ safeResults.map((r) => r.path).filter(Boolean)
13548
+ );
13549
+ let temporalFromDate = null;
13550
+ let promptTags = [];
13551
+ if (this.config.queryAwareIndexingEnabled && prompt) {
13552
+ if (isTemporalQuery(prompt)) {
13553
+ temporalFromDate = recencyWindowFromPrompt(prompt, now);
13554
+ }
13555
+ promptTags = extractTagsFromPrompt(prompt);
13556
+ }
13557
+ const [rawTemporal, rawTags] = await Promise.all([
13558
+ temporalFromDate !== null ? queryByDateRangeAsync(this.config.memoryDir, temporalFromDate) : Promise.resolve(null),
13559
+ promptTags.length > 0 ? queryByTagsAsync(this.config.memoryDir, promptTags) : Promise.resolve(null)
13560
+ ]);
13561
+ const queryIntent = this.config.intentRoutingEnabled && prompt ? inferIntentFromText(prompt) : null;
13562
+ let temporalCandidates = null;
13563
+ let tagCandidates = null;
13564
+ if (this.config.queryAwareIndexingEnabled && prompt) {
13565
+ const maxCandidates = this.config.queryAwareIndexingMaxCandidates;
13566
+ const capSet = (s) => {
13567
+ if (!s) return null;
13568
+ const scoped = new Set(Array.from(s).filter((p) => resultPaths.has(p)));
13569
+ if (maxCandidates === 0 || scoped.size <= maxCandidates)
13570
+ return scoped;
13571
+ return new Set(Array.from(scoped).slice(0, maxCandidates));
13572
+ };
13573
+ if (temporalFromDate !== null) {
13574
+ temporalCandidates = capSet(rawTemporal);
13575
+ }
13576
+ if (promptTags.length > 0) {
13577
+ tagCandidates = capSet(rawTags);
13578
+ }
13579
+ }
13580
+ const boosted = [];
13581
+ const recencyWeight = this.effectiveRecencyWeight();
13582
+ for (const r of safeResults) {
13583
+ const memory = memoryByPath.get(r.path);
13584
+ let score = r.score;
13585
+ if (memory) {
12857
13586
  if (recencyWeight > 0) {
12858
13587
  const createdAt = new Date(memory.frontmatter.created).getTime();
12859
13588
  const ageMs = now - createdAt;
@@ -12964,26 +13693,6 @@ ${lines.join("\n\n")}`;
12964
13693
  }
12965
13694
  boosted.push({ ...r, score });
12966
13695
  }
12967
- if (lifecycleFilteredCount > 0) {
12968
- log.debug(
12969
- `lifecycle retrieval filter removed ${lifecycleFilteredCount} stale/archived candidates`
12970
- );
12971
- }
12972
- if (temporalSupersededFilteredCount > 0) {
12973
- log.debug(
12974
- `temporal supersession filter removed ${temporalSupersededFilteredCount} superseded candidates`
12975
- );
12976
- }
12977
- if (dedicatedSurfaceFilteredCount > 0) {
12978
- log.debug(
12979
- `dedicated surface filter removed ${dedicatedSurfaceFilteredCount} dream/procedural candidates from generic recall`
12980
- );
12981
- }
12982
- if (forgottenFilteredCount > 0) {
12983
- log.debug(
12984
- `forgotten status filter removed ${forgottenFilteredCount} candidates from recall`
12985
- );
12986
- }
12987
13696
  return boosted.sort((a, b) => b.score - a.score);
12988
13697
  }
12989
13698
  /**
@@ -13121,8 +13830,37 @@ ${lines.join("\n\n")}`;
13121
13830
  reason: link.reason || void 0
13122
13831
  }));
13123
13832
  }
13833
+ async graphSeedPathRelativeToStorage(storage, result, recallNamespaces = []) {
13834
+ const parts = qmdCollectionPathParts(result.path);
13835
+ if (parts) {
13836
+ const memory = await this.readQmdResultMemory(
13837
+ result.path,
13838
+ storage,
13839
+ recallNamespaces
13840
+ );
13841
+ return memory ? graphPathRelativeToStorage(storage.dir, memory.path) : null;
13842
+ }
13843
+ return graphPathRelativeToStorage(storage.dir, result.path);
13844
+ }
13845
+ async graphSeedPathsWithinDeadline(storage, results, deadlineAtMs, recallNamespaces = []) {
13846
+ const resolved = [];
13847
+ for (const result of results) {
13848
+ if (Date.now() >= deadlineAtMs) break;
13849
+ const seedPath = await this.graphSeedPathRelativeToStorage(
13850
+ storage,
13851
+ result,
13852
+ recallNamespaces
13853
+ );
13854
+ if (Date.now() >= deadlineAtMs) break;
13855
+ if (seedPath) resolved.push(seedPath);
13856
+ }
13857
+ return resolved;
13858
+ }
13124
13859
  namespaceFromPath(p) {
13125
13860
  if (!this.config.namespacesEnabled) return this.config.defaultNamespace;
13861
+ const parts = qmdCollectionPathParts(p);
13862
+ const collectionNamespace = parts ? this.qmdCollectionNamespaceFromPrefix(parts.collection) : null;
13863
+ if (collectionNamespace) return collectionNamespace;
13126
13864
  const m = p.match(/[\\/]+namespaces[\\/]+([^\\/]+)(?:[\\/]|$)/);
13127
13865
  if (!m?.[1]) return this.config.defaultNamespace;
13128
13866
  return namespaceIdentityFromToken(m[1]) ?? m[1];
@@ -13206,4 +13944,4 @@ export {
13206
13944
  resolvePersistedMemoryRelativePath,
13207
13945
  Orchestrator
13208
13946
  };
13209
- //# sourceMappingURL=chunk-TFICNXXU.js.map
13947
+ //# sourceMappingURL=chunk-TJRV4MWX.js.map