@remnic/core 9.25.6 → 9.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/access-admin-ops-surface.d.ts +2 -2
  2. package/dist/access-admin-ops-surface.js +41 -41
  3. package/dist/access-audit.js +2 -2
  4. package/dist/access-authorization-probe.d.ts +2 -2
  5. package/dist/access-authorization-probe.js +42 -42
  6. package/dist/access-boundary.d.ts +2 -2
  7. package/dist/access-boundary.js +41 -41
  8. package/dist/access-cli.js +98 -97
  9. package/dist/access-cli.js.map +1 -1
  10. package/dist/access-http-lcm-compaction.d.ts +130 -0
  11. package/dist/access-http-lcm-compaction.js +10 -0
  12. package/dist/access-http.d.ts +2 -2
  13. package/dist/access-http.js +53 -52
  14. package/dist/access-identity-continuity-surface.d.ts +1 -1
  15. package/dist/access-identity-continuity-surface.js +41 -41
  16. package/dist/access-lcm-surface.d.ts +2 -2
  17. package/dist/access-lcm-surface.js +41 -41
  18. package/dist/access-mcp.d.ts +2 -2
  19. package/dist/access-mcp.js +47 -47
  20. package/dist/access-namespace-preflight.js +41 -41
  21. package/dist/access-observe-write-surface.d.ts +2 -2
  22. package/dist/access-observe-write-surface.js +41 -41
  23. package/dist/access-operations-batch.js +43 -43
  24. package/dist/access-operations.d.ts +2 -2
  25. package/dist/access-operations.js +45 -45
  26. package/dist/access-recall-concurrency.d.ts +2 -2
  27. package/dist/access-recall-concurrency.js +41 -41
  28. package/dist/access-recall-response.d.ts +2 -2
  29. package/dist/access-recall-response.js +41 -41
  30. package/dist/access-recall-surface.d.ts +2 -2
  31. package/dist/access-recall-surface.js +41 -41
  32. package/dist/access-schema.d.ts +16 -1
  33. package/dist/access-schema.js +8 -8
  34. package/dist/{access-service-BJQ22URi.d.ts → access-service-DL8FJnTj.d.ts} +2 -2
  35. package/dist/access-service.d.ts +2 -2
  36. package/dist/access-service.js +41 -41
  37. package/dist/access-surface-catalog.d.ts +2 -2
  38. package/dist/adapters/index.js +7 -7
  39. package/dist/adapters/registry.js +3 -3
  40. package/dist/{auto-sync-5OE5QOZB.js → auto-sync-UPTUVE4X.js} +3 -3
  41. package/dist/bootstrap.d.ts +1 -1
  42. package/dist/briefing.js +5 -5
  43. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  44. package/dist/capsule-crypto-YO5QJ6L3.js.map +1 -0
  45. package/dist/causal-behavior.js +2 -2
  46. package/dist/causal-chain.js +2 -2
  47. package/dist/causal-consolidation.js +11 -11
  48. package/dist/causal-retrieval.js +2 -2
  49. package/dist/causal-trajectory.js +1 -1
  50. package/dist/{chunk-JBPKEARU.js → chunk-2QSZNTDO.js} +7 -7
  51. package/dist/{chunk-BL4JHZSO.js → chunk-2XP3PWL3.js} +40 -40
  52. package/dist/{chunk-AQ2ZQYDI.js → chunk-4KKV7W62.js} +6 -6
  53. package/dist/chunk-4Z4XNEWZ.js +86 -0
  54. package/dist/chunk-4Z4XNEWZ.js.map +1 -0
  55. package/dist/{chunk-32XTXKIA.js → chunk-67DSDRUB.js} +22 -14
  56. package/dist/chunk-67DSDRUB.js.map +1 -0
  57. package/dist/{chunk-2PHX3I5C.js → chunk-6NNNURLK.js} +8 -8
  58. package/dist/{chunk-BLAKS3CP.js → chunk-77CNV7DY.js} +4 -4
  59. package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
  60. package/dist/{chunk-227UFSEQ.js → chunk-7RZJWZ46.js} +5 -5
  61. package/dist/{chunk-WIGBTWXA.js → chunk-7XXCIO6X.js} +2 -2
  62. package/dist/{chunk-7CPOZH6Y.js → chunk-AJ7QCSIS.js} +2 -2
  63. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  64. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  65. package/dist/{chunk-UMFLAACL.js → chunk-E6EAB4J6.js} +9 -9
  66. package/dist/{chunk-WSEA52RI.js → chunk-EXM2Q555.js} +2 -2
  67. package/dist/{chunk-G6MGAE36.js → chunk-F6LSNPPB.js} +5 -5
  68. package/dist/{chunk-7KXQ44PA.js → chunk-FE5FKTCI.js} +5 -5
  69. package/dist/{chunk-5J7HJ7RT.js → chunk-H2PBM562.js} +2 -2
  70. package/dist/{chunk-OIE242WM.js → chunk-HBVOJNV4.js} +3 -3
  71. package/dist/{chunk-PS5OG75R.js → chunk-IHMXPG4P.js} +2 -2
  72. package/dist/{chunk-3UOSVTFR.js → chunk-JCVQV6QX.js} +11 -11
  73. package/dist/{chunk-P25IPRXP.js → chunk-KGTD2KZJ.js} +28 -8
  74. package/dist/chunk-KGTD2KZJ.js.map +1 -0
  75. package/dist/{chunk-NY6W6CJW.js → chunk-KPWLZBXZ.js} +2 -2
  76. package/dist/{chunk-W33DEUKR.js → chunk-LIIQPDFN.js} +2 -2
  77. package/dist/{chunk-D7MAWMMK.js → chunk-M2KGXXTL.js} +2 -2
  78. package/dist/{chunk-UOURUMBN.js → chunk-MIFF37NN.js} +2 -2
  79. package/dist/{chunk-37VPMR4E.js → chunk-NKDVXEFU.js} +6 -6
  80. package/dist/{chunk-ZPUFQGUF.js → chunk-PQPSZICJ.js} +5 -5
  81. package/dist/{chunk-ES36HTSC.js → chunk-PTN6UAUA.js} +4 -4
  82. package/dist/{chunk-FR75B4UU.js → chunk-PX7VDW7R.js} +408 -721
  83. package/dist/chunk-PX7VDW7R.js.map +1 -0
  84. package/dist/{chunk-S2BBISHU.js → chunk-RYHWZ52A.js} +4 -4
  85. package/dist/{chunk-ZN4UZWMT.js → chunk-SK3EYPKC.js} +2 -2
  86. package/dist/{chunk-TPYZ727G.js → chunk-SN3ZESWQ.js} +6 -6
  87. package/dist/{chunk-BUK2FXOL.js → chunk-TKBDJTXG.js} +4 -4
  88. package/dist/{chunk-4B77XSG5.js → chunk-UV72DEZD.js} +5 -5
  89. package/dist/{chunk-K4DP5KZA.js → chunk-UZK7VVPD.js} +14 -14
  90. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  91. package/dist/{chunk-SSYBI43M.js → chunk-VGEH3STZ.js} +6 -6
  92. package/dist/{chunk-NRV4Y3PM.js → chunk-W4FBGYGK.js} +1 -1
  93. package/dist/{chunk-IR4LGIH6.js → chunk-WMDRLTNA.js} +2 -2
  94. package/dist/{chunk-MU7ZHSZE.js → chunk-WZFAPHIK.js} +2 -2
  95. package/dist/{chunk-JA4HEXV7.js → chunk-XAAIUXJO.js} +2 -2
  96. package/dist/{chunk-VPSAVXHM.js → chunk-XNUBF6RF.js} +7 -7
  97. package/dist/{chunk-3CHQZVAL.js → chunk-Y6E37WRG.js} +5 -5
  98. package/dist/{chunk-E5A6C4A5.js → chunk-YN4AUGNF.js} +48 -48
  99. package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
  100. package/dist/{chunk-7IGWPXVC.js → chunk-ZHP4SUPB.js} +2 -2
  101. package/dist/{cli-1W7cNjJJ.d.ts → cli-BoouX6LX.d.ts} +2 -2
  102. package/dist/cli.d.ts +3 -3
  103. package/dist/cli.js +75 -74
  104. package/dist/compounding/engine.js +5 -5
  105. package/dist/connectors/codex-materialize-runner.js +5 -5
  106. package/dist/connectors/index.js +5 -5
  107. package/dist/conversation-index/backend.js +2 -2
  108. package/dist/corpus-watermark.js +16 -16
  109. package/dist/dashboard-runtime.js +2 -2
  110. package/dist/entity-retrieval.js +5 -5
  111. package/dist/explicit-capture.d.ts +1 -1
  112. package/dist/extraction.js +3 -3
  113. package/dist/{first-start-migration-BKNGOFED.js → first-start-migration-LEXL3XYL.js} +4 -4
  114. package/dist/index.d.ts +4 -4
  115. package/dist/index.js +177 -176
  116. package/dist/lcm/engine.js +3 -3
  117. package/dist/lcm/index.js +9 -9
  118. package/dist/maintenance/memory-governance.js +6 -6
  119. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
  120. package/dist/maintenance/rebuild-memory-projection.js +7 -7
  121. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  122. package/dist/namespaces/migrate.js +15 -15
  123. package/dist/namespaces/search.js +9 -9
  124. package/dist/namespaces/storage.js +5 -5
  125. package/dist/operator-doctor-corpus.js +17 -17
  126. package/dist/operator-toolkit.js +28 -28
  127. package/dist/orchestration/maintenance.js +11 -11
  128. package/dist/{orchestrator-BtA1wC5K.d.ts → orchestrator-CFJ_TG6D.d.ts} +15 -10
  129. package/dist/orchestrator.d.ts +1 -1
  130. package/dist/orchestrator.js +98 -97
  131. package/dist/resume-bundles.js +1 -1
  132. package/dist/search/factory.js +8 -8
  133. package/dist/search/index.js +13 -13
  134. package/dist/search/lancedb-backend.js +2 -2
  135. package/dist/search/meilisearch-backend.js +2 -2
  136. package/dist/search/orama-backend.js +2 -2
  137. package/dist/semantic-consolidation.js +7 -7
  138. package/dist/semantic-rule-promotion.js +5 -5
  139. package/dist/semantic-rule-verifier.js +6 -6
  140. package/dist/session-namespace-bindings.d.ts +27 -0
  141. package/dist/session-namespace-bindings.js +243 -0
  142. package/dist/session-namespace-bindings.js.map +1 -0
  143. package/dist/storage.js +4 -4
  144. package/dist/testing/lifecycle-matrix.d.ts +100 -0
  145. package/dist/testing/lifecycle-matrix.js +149 -0
  146. package/dist/testing/lifecycle-matrix.js.map +1 -0
  147. package/dist/transfer/backup.js +4 -4
  148. package/dist/transfer/capsule-export.js +4 -4
  149. package/dist/transfer/capsule-import.js +3 -3
  150. package/dist/verified-recall.js +6 -6
  151. package/package.json +22 -2
  152. package/src/access-http-lcm-compaction.ts +116 -0
  153. package/src/access-http.test.ts +218 -0
  154. package/src/access-http.ts +8 -8
  155. package/src/access-schema-pi.test.ts +9 -0
  156. package/src/access-schema.ts +29 -4
  157. package/src/access-surface-catalog.test.ts +1 -0
  158. package/src/meetings/recall-exclusion.test.ts +4 -2
  159. package/src/orchestration/generic-recall-paths.ts +119 -0
  160. package/src/orchestration/namespace-read-fanout.ts +0 -12
  161. package/src/orchestration/orchestrator-helpers.ts +0 -35
  162. package/src/orchestration/recall-internal.ts +6 -6
  163. package/src/orchestration/recall-search-pipeline.test.ts +194 -28
  164. package/src/orchestration/recall-search-pipeline.ts +39 -89
  165. package/src/orchestrator.ts +5 -17
  166. package/src/recall-cold-deadline.test.ts +11 -13
  167. package/src/session-namespace-bindings.test.ts +358 -0
  168. package/src/session-namespace-bindings.ts +301 -0
  169. package/src/testing/lifecycle-matrix.ts +17 -7
  170. package/src/testing/subjects/generic-recall-paths.test.ts +157 -0
  171. package/dist/chunk-32XTXKIA.js.map +0 -1
  172. package/dist/chunk-FR75B4UU.js.map +0 -1
  173. package/dist/chunk-P25IPRXP.js.map +0 -1
  174. package/src/recall/archive-scoring.ts +0 -544
  175. /package/dist/{capsule-crypto-7FJQINUR.js.map → access-http-lcm-compaction.js.map} +0 -0
  176. /package/dist/{auto-sync-5OE5QOZB.js.map → auto-sync-UPTUVE4X.js.map} +0 -0
  177. /package/dist/{chunk-JBPKEARU.js.map → chunk-2QSZNTDO.js.map} +0 -0
  178. /package/dist/{chunk-BL4JHZSO.js.map → chunk-2XP3PWL3.js.map} +0 -0
  179. /package/dist/{chunk-AQ2ZQYDI.js.map → chunk-4KKV7W62.js.map} +0 -0
  180. /package/dist/{chunk-2PHX3I5C.js.map → chunk-6NNNURLK.js.map} +0 -0
  181. /package/dist/{chunk-BLAKS3CP.js.map → chunk-77CNV7DY.js.map} +0 -0
  182. /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
  183. /package/dist/{chunk-227UFSEQ.js.map → chunk-7RZJWZ46.js.map} +0 -0
  184. /package/dist/{chunk-WIGBTWXA.js.map → chunk-7XXCIO6X.js.map} +0 -0
  185. /package/dist/{chunk-7CPOZH6Y.js.map → chunk-AJ7QCSIS.js.map} +0 -0
  186. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  187. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  188. /package/dist/{chunk-UMFLAACL.js.map → chunk-E6EAB4J6.js.map} +0 -0
  189. /package/dist/{chunk-WSEA52RI.js.map → chunk-EXM2Q555.js.map} +0 -0
  190. /package/dist/{chunk-G6MGAE36.js.map → chunk-F6LSNPPB.js.map} +0 -0
  191. /package/dist/{chunk-7KXQ44PA.js.map → chunk-FE5FKTCI.js.map} +0 -0
  192. /package/dist/{chunk-5J7HJ7RT.js.map → chunk-H2PBM562.js.map} +0 -0
  193. /package/dist/{chunk-OIE242WM.js.map → chunk-HBVOJNV4.js.map} +0 -0
  194. /package/dist/{chunk-PS5OG75R.js.map → chunk-IHMXPG4P.js.map} +0 -0
  195. /package/dist/{chunk-3UOSVTFR.js.map → chunk-JCVQV6QX.js.map} +0 -0
  196. /package/dist/{chunk-NY6W6CJW.js.map → chunk-KPWLZBXZ.js.map} +0 -0
  197. /package/dist/{chunk-W33DEUKR.js.map → chunk-LIIQPDFN.js.map} +0 -0
  198. /package/dist/{chunk-D7MAWMMK.js.map → chunk-M2KGXXTL.js.map} +0 -0
  199. /package/dist/{chunk-UOURUMBN.js.map → chunk-MIFF37NN.js.map} +0 -0
  200. /package/dist/{chunk-37VPMR4E.js.map → chunk-NKDVXEFU.js.map} +0 -0
  201. /package/dist/{chunk-ZPUFQGUF.js.map → chunk-PQPSZICJ.js.map} +0 -0
  202. /package/dist/{chunk-ES36HTSC.js.map → chunk-PTN6UAUA.js.map} +0 -0
  203. /package/dist/{chunk-S2BBISHU.js.map → chunk-RYHWZ52A.js.map} +0 -0
  204. /package/dist/{chunk-ZN4UZWMT.js.map → chunk-SK3EYPKC.js.map} +0 -0
  205. /package/dist/{chunk-TPYZ727G.js.map → chunk-SN3ZESWQ.js.map} +0 -0
  206. /package/dist/{chunk-BUK2FXOL.js.map → chunk-TKBDJTXG.js.map} +0 -0
  207. /package/dist/{chunk-4B77XSG5.js.map → chunk-UV72DEZD.js.map} +0 -0
  208. /package/dist/{chunk-K4DP5KZA.js.map → chunk-UZK7VVPD.js.map} +0 -0
  209. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  210. /package/dist/{chunk-SSYBI43M.js.map → chunk-VGEH3STZ.js.map} +0 -0
  211. /package/dist/{chunk-NRV4Y3PM.js.map → chunk-W4FBGYGK.js.map} +0 -0
  212. /package/dist/{chunk-IR4LGIH6.js.map → chunk-WMDRLTNA.js.map} +0 -0
  213. /package/dist/{chunk-MU7ZHSZE.js.map → chunk-WZFAPHIK.js.map} +0 -0
  214. /package/dist/{chunk-JA4HEXV7.js.map → chunk-XAAIUXJO.js.map} +0 -0
  215. /package/dist/{chunk-VPSAVXHM.js.map → chunk-XNUBF6RF.js.map} +0 -0
  216. /package/dist/{chunk-3CHQZVAL.js.map → chunk-Y6E37WRG.js.map} +0 -0
  217. /package/dist/{chunk-E5A6C4A5.js.map → chunk-YN4AUGNF.js.map} +0 -0
  218. /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
  219. /package/dist/{chunk-7IGWPXVC.js.map → chunk-ZHP4SUPB.js.map} +0 -0
  220. /package/dist/{first-start-migration-BKNGOFED.js.map → first-start-migration-LEXL3XYL.js.map} +0 -0
@@ -28,7 +28,6 @@ import { NamespaceStorageRouter } from "../namespaces/storage.js";
28
28
  import { NegativeExampleStore } from "../negative.js";
29
29
  import { qmdCollectionPathParts } from "./qmd-result-resolver.js";
30
30
  import type { GraphRecallExpandedEntry } from "../recall-state.js";
31
- import { getDefaultArchiveScoring, memoryFileToScoreItem } from "../recall/archive-scoring.js";
32
31
  import { RelevanceStore } from "../relevance.js";
33
32
  import { RerankCache, rerankLocalOrNoop, reorderByRankedKeys } from "../rerank.js";
34
33
  import type { SearchBackend, SearchDegradation, SearchExecutionOptions, SearchQueryOptions } from "../search/port.js";
@@ -55,11 +54,11 @@ import {
55
54
  computeQmdHybridFetchLimit,
56
55
  filterRecallCandidates,
57
56
  throwIfRecallAborted,
58
- tokenizeRecallQuery,
59
57
  type QmdRecallSnapshot,
60
58
  type QueryAwarePrefilter,
61
59
  } from "../orchestrator.js";
62
- import { isActivityDigestPath, isGenericRecallExcludedPath } from "./orchestrator-helpers.js";
60
+ import { isActivityDigestPath } from "./orchestrator-helpers.js";
61
+ import { isGenericRecallExcludedPath, isTopLevelArchivePath } from "./generic-recall-paths.js";
63
62
 
64
63
  export interface RecallSearchPipelineDeps {
65
64
  applyMemoryWorthRerank(
@@ -185,7 +184,6 @@ export interface RecallSearchPipelineDeps {
185
184
  namespaceFromPath(p: string): string;
186
185
  readonly negatives: NegativeExampleStore;
187
186
  readonly qmd: SearchBackend;
188
- readArchivedMemoriesForNamespaces(namespaces: string[]): Promise<MemoryFile[]>;
189
187
  readQmdResultMemory(
190
188
  resultPath: string,
191
189
  fallbackStorage: StorageManager,
@@ -215,9 +213,8 @@ export interface RecallSearchPipelineDeps {
215
213
  searchOptions?: SearchQueryOptions;
216
214
  execution?: SearchExecutionOptions;
217
215
  }): Promise<QmdSearchResult[]>;
218
- searchLongTermArchiveFallback(
216
+ searchQueryAwareFallback(
219
217
  prompt: string,
220
- recallNamespaces: string[],
221
218
  limit: number,
222
219
  queryAwarePrefilter?: QueryAwarePrefilter,
223
220
  abortSignal?: AbortSignal,
@@ -422,26 +419,19 @@ export class RecallSearchPipelineCoordinator {
422
419
  ? await this.deps.searchScopedMemoryCandidates(
423
420
  queryAwarePrefilter.candidatePaths,
424
421
  prompt,
425
- // Overfetch the FULL scoped candidate set BEFORE the generic-recall
426
- // exclusion `filterRecallCandidates` applies below (L464). Capping to
427
- // `qmdFetchLimit` here lets excluded paths (artifacts, activity digests,
428
- // meeting records) consume the caller's budget and drop legitimate hits
429
- // with no refill — the hot-path mirror of the archive-fallback fix. The
430
- // candidate set is an already-bounded prefilter, so its size is the
431
- // natural ceiling; the post-exclusion cap is applied by filterRecallCandidates.
422
+ // Exclude dedicated surfaces before capping the bounded prefilter set.
432
423
  queryAwarePrefilter.candidatePaths.size,
433
424
  { allowArchived: options.collection !== undefined },
434
425
  )
435
426
  : [];
436
427
  // Drop generic-recall-excluded records before the fetchLimit cap so they can't starve valid memories (#1995).
437
- const recallable = (r: QmdSearchResult) => !isGenericRecallExcludedPath(r.path, this.deps.config.memoryDir);
428
+ const recallable = (r: QmdSearchResult) => !isGenericRecallExcludedPath(r.path, this.deps.config, "qmd");
438
429
 
439
430
  let fetchLimit = Math.max(qmdFetchLimit, qmdHybridFetchLimit);
440
431
  const maxFetchLimit = Math.min(
441
432
  320,
442
433
  Math.max(fetchLimit, qmdFetchLimit * 5),
443
434
  );
444
- const MAX_ATTEMPTS = 2;
445
435
  const qmdRecallBudgetMs = this.deps.config.recallEnrichmentDeadlineMs ?? 25_000;
446
436
  const qmdRecallBudgetEnabled = qmdRecallBudgetMs > 0;
447
437
  const startedAtMs = Date.now();
@@ -475,7 +465,7 @@ export class RecallSearchPipelineCoordinator {
475
465
  recallNamespaces: options.recallNamespaces,
476
466
  resolveNamespace: options.resolveNamespace,
477
467
  limit: qmdFetchLimit,
478
- memoryRoot: this.deps.config.memoryDir,
468
+ pathPolicy: this.deps.config,
479
469
  });
480
470
  const emitDebugSnapshot = async (
481
471
  results: QmdSearchResult[],
@@ -508,7 +498,7 @@ export class RecallSearchPipelineCoordinator {
508
498
  return [];
509
499
  }
510
500
 
511
- for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
501
+ for (;;) {
512
502
  throwIfRecallAborted(options.abortSignal);
513
503
  if (
514
504
  qmdRecallBudgetEnabled &&
@@ -554,10 +544,15 @@ export class RecallSearchPipelineCoordinator {
554
544
  lastHybridTopUpUsed = false;
555
545
  lastHybridTopUpSkippedReason = undefined;
556
546
  let mergedResults = primaryResults;
547
+ const primaryRecallableCount = primaryResults.filter(recallable).length;
548
+ const primaryIncludesArchivePath = primaryResults.some((result) =>
549
+ isTopLevelArchivePath(result.path, this.deps.config, "qmd"),
550
+ );
557
551
 
558
- // Backfill with hybrid results only when primary retrieval underfills.
552
+ // Backfill with hybrid results when the recallable primary page underfills.
559
553
  if (
560
- primaryResults.length < qmdFetchLimit &&
554
+ primaryRecallableCount < qmdFetchLimit &&
555
+ (primaryResults.length < qmdFetchLimit || primaryIncludesArchivePath) &&
561
556
  (!qmdRecallBudgetEnabled ||
562
557
  Date.now() - startedAtMs < qmdRecallBudgetMs)
563
558
  ) {
@@ -620,7 +615,7 @@ export class RecallSearchPipelineCoordinator {
620
615
  recallNamespaces: options.recallNamespaces,
621
616
  resolveNamespace: options.resolveNamespace,
622
617
  limit: fetchLimit,
623
- memoryRoot: this.deps.config.memoryDir,
618
+ pathPolicy: this.deps.config,
624
619
  });
625
620
 
626
621
  if (filteredResults.length >= qmdFetchLimit) {
@@ -631,11 +626,9 @@ export class RecallSearchPipelineCoordinator {
631
626
  if (filteredResults.length > bestFiltered.length) {
632
627
  bestFiltered = filteredResults;
633
628
  }
634
- if (mergedResults.length === 0) {
635
- await emitDebugSnapshot(filteredResults, fetchLimit);
636
- return filteredResults;
637
- }
638
- if (mergedResults.length < fetchLimit && filteredResults.length > 0) {
629
+ // A full raw page may filter down to nothing (for example, archive-only
630
+ // hits). Keep widening until the backend itself underfills the page.
631
+ if (primaryResults.length < fetchLimit) {
639
632
  await emitDebugSnapshot(filteredResults, fetchLimit);
640
633
  return filteredResults;
641
634
  }
@@ -679,20 +672,18 @@ export class RecallSearchPipelineCoordinator {
679
672
  }
680
673
 
681
674
  /**
682
- * Long-term fallback retrieval.
683
- * Searches archived memories only, and is invoked only when hot recall returns zero hits.
675
+ * Cold fallback retains qualifying query-aware candidates. Archive records
676
+ * belong to dedicated surfaces and never re-enter generic recall.
684
677
  */
685
- async searchLongTermArchiveFallback(
678
+ async searchQueryAwareFallback(
686
679
  prompt: string,
687
- recallNamespaces: string[],
688
680
  limit: number,
689
681
  queryAwarePrefilter?: QueryAwarePrefilter,
690
682
  abortSignal?: AbortSignal,
691
683
  ): Promise<QmdSearchResult[]> {
692
684
  throwIfRecallAborted(abortSignal);
693
685
  const cappedLimit = Math.max(0, limit);
694
- if (cappedLimit === 0) return [];
695
- if (queryAwarePrefilter?.candidatePaths?.size === 0) return [];
686
+ if (cappedLimit === 0 || queryAwarePrefilter?.candidatePaths?.size === 0) return [];
696
687
 
697
688
  const candidatePaths = queryAwarePrefilter?.candidatePaths;
698
689
  const scopedSeedResults = (
@@ -700,57 +691,12 @@ export class RecallSearchPipelineCoordinator {
700
691
  ? await this.deps.searchScopedMemoryCandidates(
701
692
  candidatePaths,
702
693
  prompt,
703
- // Overfetch the FULL scoped candidate set BEFORE the generic-recall
704
- // exclusion below. Capping to `cappedLimit` first lets excluded paths
705
- // (artifacts, activity digests, meeting records) consume the caller's
706
- // budget and drop legitimate hits with no refill — e.g. a prefilter of
707
- // [meetingRecord, facts/a.md] at limit 1 would return nothing. The
708
- // candidate set is an already-bounded prefilter, so its size is the
709
- // natural ceiling; the post-exclusion cap is applied below.
710
694
  candidatePaths.size,
711
695
  { allowArchived: true },
712
696
  )
713
697
  : []
714
- ).filter((result) => !isGenericRecallExcludedPath(result.path, this.deps.config.memoryDir));
715
- // Non-recallable seed paths (artifacts, activity digests, meeting records) are
716
- // excluded above AFTER an unconstrained candidate fetch, so the cap here counts
717
- // only recallable seeds — and EVERY early-return / direct-caller path below
718
- // returns already-filtered, budget-honoring seeds.
719
- if (scopedSeedResults.length >= cappedLimit) {
720
- return scopedSeedResults.slice(0, cappedLimit);
721
- }
722
-
723
- const tokens = Array.from(new Set(tokenizeRecallQuery(prompt)));
724
- if (tokens.length === 0) return scopedSeedResults;
725
-
726
- throwIfRecallAborted(abortSignal);
727
- const archivedMemories =
728
- await this.deps.readArchivedMemoriesForNamespaces(recallNamespaces);
729
- if (archivedMemories.length === 0) return scopedSeedResults;
730
-
731
- // Issue #1674: off-load the CPU-bound archive-scoring loop to a
732
- // worker_threads pool so concurrent recall requests run on separate
733
- // cores instead of serializing on the main JS thread. The pure scoring
734
- // function is identical to the old inline loop — only the execution
735
- // context changed. Aborts are checked at the boundaries (before submit
736
- // and after result); the worker's work is bounded by the file count.
737
- throwIfRecallAborted(abortSignal);
738
- const scoring = getDefaultArchiveScoring();
739
- const scoredResults = await scoring.score(archivedMemories.map(memoryFileToScoreItem), tokens, abortSignal);
740
- throwIfRecallAborted(abortSignal);
741
- const scored: QmdSearchResult[] = scoredResults.map((r) => ({
742
- docid: r.docid,
743
- path: r.path,
744
- score: r.score,
745
- snippet: r.snippet,
746
- }));
747
-
748
- return dedupeResultsByNamespace(
749
- [...scopedSeedResults, ...scored],
750
- this.deps.namespaceFromPath,
751
- cappedLimit,
752
- { filter: (result) => !isGenericRecallExcludedPath(result.path, this.deps.config.memoryDir) },
753
- );
698
+ ).filter((result) => !isGenericRecallExcludedPath(result.path, this.deps.config, "qmd"));
699
+ return scopedSeedResults.slice(0, cappedLimit);
754
700
  }
755
701
 
756
702
  async applyColdFallbackPipeline(options: {
@@ -935,17 +881,19 @@ export class RecallSearchPipelineCoordinator {
935
881
  }
936
882
  }
937
883
  if (longTerm.length === 0) {
938
- // The archive-scan scoring worker terminates when EITHER the caller
939
- // aborts or this step's own deadline wins — both flow through the
940
- // step signal injected by runColdStepWithinDeadline (#1674, #1907).
941
884
  longTerm = await runColdStepWithinDeadline(
942
- "archive scan", [],
943
- (stepSignal) => this.deps.searchLongTermArchiveFallback(
944
- options.prompt, options.recallNamespaces, options.recallResultLimit,
945
- options.queryAwarePrefilter, stepSignal),
885
+ "query-aware fallback",
886
+ [],
887
+ (stepSignal) =>
888
+ this.deps.searchQueryAwareFallback(
889
+ options.prompt,
890
+ options.recallResultLimit,
891
+ options.queryAwarePrefilter,
892
+ stepSignal,
893
+ ),
946
894
  );
947
895
  if (longTerm.length > 0) {
948
- log.debug("cold-tier recall source=archive-scan");
896
+ log.debug("cold-tier recall source=query-aware-fallback");
949
897
  }
950
898
  }
951
899
  if (longTerm.length === 0) return [];
@@ -997,9 +945,8 @@ export class RecallSearchPipelineCoordinator {
997
945
  }
998
946
  results = scopedResults;
999
947
  }
1000
- // Dedicated-surface isolation: generic recall must exclude artifacts and
1001
- // activity digests (both remain readable via explicit search).
1002
- results = results.filter((r) => !isGenericRecallExcludedPath(r.path, this.deps.config.memoryDir));
948
+ // Dedicated-surface isolation keeps generic recall out of artifacts, activity digests, and archives.
949
+ results = results.filter((r) => !isGenericRecallExcludedPath(r.path, this.deps.config, "qmd"));
1003
950
  if (results.length === 0) return [];
1004
951
 
1005
952
  const isFullModeGraphAssist =
@@ -1022,6 +969,9 @@ export class RecallSearchPipelineCoordinator {
1022
969
  });
1023
970
  results = merged;
1024
971
  }
972
+ results = results.filter(
973
+ (result) => !isGenericRecallExcludedPath(result.path, this.deps.config, "qmd"),
974
+ );
1025
975
 
1026
976
  const boostInput = await this.deps.filterSearchResultsForRecall(
1027
977
  results,
@@ -134,11 +134,11 @@ import {
134
134
  computeSemanticDedupScope,
135
135
  qmdCollectionNamespaceFromPrefix as computeQmdCollectionNamespaceFromPrefix,
136
136
  } from "./orchestration/orchestrator-namespace-scope.js";
137
+ import { isGenericRecallExcludedPath } from "./orchestration/generic-recall-paths.js";
137
138
  import {
138
139
  abortRecallError,
139
140
  buildCompressionGuidelinesMarkdown,
140
141
  buildQmdIntentHint,
141
- isGenericRecallExcludedPath,
142
142
  mergeArtifactRecallCandidates,
143
143
  tokenizeRecallQuery,
144
144
  type BulkImportBatchIngestResult,
@@ -163,7 +163,6 @@ export {
163
163
  computeQmdHybridFetchLimit,
164
164
  defaultWorkspaceDir,
165
165
  filterHourlySummaryMarkdownForLocalDay,
166
- filterRecallCandidates,
167
166
  formatDateInTimeZone,
168
167
  isArtifactMemoryPath,
169
168
  lifecycleRecallScoreAdjustment,
@@ -198,6 +197,7 @@ export {
198
197
  type RecallInvocationOptions,
199
198
  type RecallModeDecision,
200
199
  } from "./orchestration/orchestrator-helpers.js";
200
+ export { filterRecallCandidates } from "./orchestration/generic-recall-paths.js";
201
201
 
202
202
  export { hasIdentityRecoveryIntent, resolveEffectiveIdentityInjectionMode } from "./orchestration/recall-result-formatter.js";
203
203
  import {
@@ -541,7 +541,6 @@ import type {
541
541
  RecallSectionConfig,
542
542
  RecallTierExplain,
543
543
  } from "./types.js";
544
- import { disposeDefaultArchiveScoring, getDefaultArchiveScoring, memoryFileToScoreItem } from "./recall/archive-scoring.js";
545
544
 
546
545
  // Issue #1526 seam 15: ExtractionRunResult moved to orchestration/extraction-run.ts.
547
546
  export type { ExtractionRunResult } from "./orchestration/extraction-run.js";
@@ -997,8 +996,6 @@ export class Orchestrator {
997
996
  if (this.conversationQmd && this.conversationQmd !== this.qmd) {
998
997
  await (this.conversationQmd as { dispose?: () => void | Promise<void> }).dispose?.();
999
998
  }
1000
- // Issue #1674: terminate archive-scoring worker threads on destroy.
1001
- await disposeDefaultArchiveScoring();
1002
999
  } finally {
1003
1000
  if (bufferFlushError !== undefined) {
1004
1001
  log.warn(
@@ -2483,7 +2480,7 @@ export class Orchestrator {
2483
2480
  if (!paths) return null;
2484
2481
  const scoped = new Set<string>();
2485
2482
  for (const memoryPath of paths) {
2486
- if (!memoryPath || isGenericRecallExcludedPath(memoryPath, this.config.memoryDir)) continue;
2483
+ if (!memoryPath || isGenericRecallExcludedPath(memoryPath, this.config, "qmd")) continue;
2487
2484
  if (
2488
2485
  resolveNamespaceCapabilities(this.config).namespaces &&
2489
2486
  !recallNamespaces.includes(this.namespaceFromPath(memoryPath))
@@ -3600,16 +3597,14 @@ export class Orchestrator {
3600
3597
  );
3601
3598
  }
3602
3599
 
3603
- private async searchLongTermArchiveFallback(
3600
+ private async searchQueryAwareFallback(
3604
3601
  prompt: string,
3605
- recallNamespaces: string[],
3606
3602
  limit: number,
3607
3603
  queryAwarePrefilter?: QueryAwarePrefilter,
3608
3604
  abortSignal?: AbortSignal,
3609
3605
  ): Promise<QmdSearchResult[]> {
3610
- return this.recallSearchPipelineCoordinator.searchLongTermArchiveFallback(
3606
+ return this.recallSearchPipelineCoordinator.searchQueryAwareFallback(
3611
3607
  prompt,
3612
- recallNamespaces,
3613
3608
  limit,
3614
3609
  queryAwarePrefilter,
3615
3610
  abortSignal,
@@ -3968,11 +3963,4 @@ export class Orchestrator {
3968
3963
  namespaces,
3969
3964
  );
3970
3965
  }
3971
- private async readArchivedMemoriesForNamespaces(
3972
- namespaces: string[],
3973
- ): Promise<MemoryFile[]> {
3974
- return this.namespaceReadFanoutCoordinator.readArchivedMemoriesForNamespaces(
3975
- namespaces,
3976
- );
3977
- }
3978
3966
  }
@@ -4,7 +4,7 @@
4
4
  * Pins the task-level cancellation contract of runColdStepWithinDeadline
5
5
  * inside RecallSearchPipelineCoordinator.applyColdFallbackPipeline:
6
6
  * - when the per-step deadline wins, the losing cold task's INJECTED signal
7
- * is aborted (so an orphaned archive scan stops cooperatively) and the
7
+ * is aborted so an orphaned query-aware fallback stops cooperatively; the
8
8
  * step returns its fallback (fail-open);
9
9
  * - a task-level deadline must NOT abort the request-level signal;
10
10
  * - a task that completes normally (no deadline) never aborts the injected
@@ -37,24 +37,22 @@ async function coldConfig() {
37
37
  }
38
38
 
39
39
  /**
40
- * Minimal deps for the archive-scan branch. qmd.isAvailable() === false forces
41
- * the cold-qmd block to be skipped, so control reaches the archive-scan step;
42
- * an empty archive result returns [] before any namespace/storage-router work,
43
- * keeping the stub surface tight. The cast is a deliberate test double.
40
+ * Minimal dependencies for the query-aware fallback. QMD is unavailable, so
41
+ * the cold-QMD branch is skipped and the injected fallback step is observed.
44
42
  */
45
43
  function coldDeps(
46
44
  config: PluginConfig,
47
- searchLongTermArchiveFallback: RecallSearchPipelineDeps["searchLongTermArchiveFallback"],
45
+ searchQueryAwareFallback: RecallSearchPipelineDeps["searchQueryAwareFallback"],
48
46
  ): RecallSearchPipelineDeps {
49
47
  return {
50
48
  config,
51
49
  qmd: { isAvailable: () => false },
52
50
  namespaceFromPath: () => "default",
53
- searchLongTermArchiveFallback,
51
+ searchQueryAwareFallback,
54
52
  } as unknown as RecallSearchPipelineDeps;
55
53
  }
56
54
 
57
- test("#1907: cold archive-scan deadline aborts the injected step signal, returns fallback, leaves the request signal intact", async () => {
55
+ test("#1907: query-aware fallback deadline aborts the injected step signal, returns fallback, leaves the request signal intact", async () => {
58
56
  const { memoryDir, config } = await coldConfig();
59
57
  try {
60
58
  const stuck = Promise.withResolvers<QmdSearchResult[]>();
@@ -65,7 +63,7 @@ test("#1907: cold archive-scan deadline aborts the injected step signal, returns
65
63
  // so the late completion leaks "late" here and the assertion below fails.
66
64
  const lateWrites: string[] = [];
67
65
  const abandonedTaskSettled = Promise.withResolvers<void>();
68
- const deps = coldDeps(config, async (_prompt, _ns, _limit, _prefilter, abortSignal) => {
66
+ const deps = coldDeps(config, async (_prompt, _limit, _prefilter, abortSignal) => {
69
67
  capturedStepSignal = abortSignal;
70
68
  const late = await stuck.promise; // does not settle before the deadline wins
71
69
  if (!abortSignal?.aborted) {
@@ -87,7 +85,7 @@ test("#1907: cold archive-scan deadline aborts the injected step signal, returns
87
85
  });
88
86
 
89
87
  assert.deepEqual(result, [], "the step returns its fallback when the deadline wins");
90
- assert.ok(capturedStepSignal, "archive scan received an injected step signal");
88
+ assert.ok(capturedStepSignal, "query-aware fallback received an injected step signal");
91
89
  assert.equal(
92
90
  capturedStepSignal!.aborted,
93
91
  true,
@@ -119,7 +117,7 @@ test("#1907: a cold step that completes before its deadline never aborts the inj
119
117
  const { memoryDir, config } = await coldConfig();
120
118
  try {
121
119
  let capturedStepSignal: AbortSignal | undefined;
122
- const deps = coldDeps(config, async (_prompt, _ns, _limit, _prefilter, abortSignal) => {
120
+ const deps = coldDeps(config, async (_prompt, _limit, _prefilter, abortSignal) => {
123
121
  capturedStepSignal = abortSignal;
124
122
  return []; // resolves immediately, well within the deadline
125
123
  });
@@ -135,8 +133,8 @@ test("#1907: a cold step that completes before its deadline never aborts the inj
135
133
  abortSignal: requestController.signal,
136
134
  });
137
135
 
138
- assert.deepEqual(result, [], "empty archive returns []");
139
- assert.ok(capturedStepSignal, "archive scan received an injected step signal");
136
+ assert.deepEqual(result, [], "empty query-aware fallback returns []");
137
+ assert.ok(capturedStepSignal, "query-aware fallback received an injected step signal");
140
138
  assert.equal(
141
139
  capturedStepSignal!.aborted,
142
140
  false,