@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
@@ -0,0 +1,119 @@
1
+ import path from "node:path";
2
+ import type { QmdSearchResult } from "../types.js";
3
+ import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
4
+ import {
5
+ isActivityDigestPath,
6
+ isArtifactMemoryPath,
7
+ isMeetingRecordPath,
8
+ } from "./orchestrator-helpers.js";
9
+
10
+ export interface GenericRecallPathPolicy {
11
+ readonly memoryDir?: string;
12
+ readonly qmdCollection?: string;
13
+ readonly qmdColdCollection?: string;
14
+ }
15
+
16
+ type GenericRecallPathSource = "filesystem" | "qmd";
17
+
18
+ const CATEGORY_MEMORY_ROOTS: Record<string, true> = Object.fromEntries(
19
+ ALL_CATEGORY_DIRS.map((directory) => [directory, true]),
20
+ );
21
+ const RESERVED_ARCHIVE_ROOTS: Record<string, true> = { archive: true, namespaces: true };
22
+
23
+ function isQmdCollectionPrefix(
24
+ prefix: string,
25
+ collection: string | undefined,
26
+ source: GenericRecallPathSource,
27
+ ): boolean {
28
+ if (typeof collection !== "string" || collection.length === 0) return false;
29
+ if (prefix.startsWith(`${collection}--`)) return true;
30
+ if (prefix !== collection) return false;
31
+ if (source === "qmd") return true;
32
+ return (
33
+ !Object.hasOwn(CATEGORY_MEMORY_ROOTS, prefix) &&
34
+ !Object.hasOwn(RESERVED_ARCHIVE_ROOTS, prefix)
35
+ );
36
+ }
37
+
38
+ function normalizeQmdUriPath(relativePath: string, source: GenericRecallPathSource): string {
39
+ if (source !== "qmd" || !relativePath.startsWith("qmd://")) return relativePath;
40
+ try {
41
+ const parsed = new URL(relativePath);
42
+ if (parsed.protocol !== "qmd:" || !parsed.hostname) return relativePath;
43
+ const collection = decodeURIComponent(parsed.hostname);
44
+ const pathname = decodeURIComponent(parsed.pathname.replace(/^\/+/, ""));
45
+ return pathname ? `${collection}/${pathname}` : collection;
46
+ } catch {
47
+ return relativePath;
48
+ }
49
+ }
50
+
51
+ function stripQmdCollectionPrefix(
52
+ relativePath: string,
53
+ policy: GenericRecallPathPolicy,
54
+ source: GenericRecallPathSource,
55
+ ): string {
56
+ const normalized = path.posix.normalize(normalizeQmdUriPath(relativePath, source).replace(/\\/g, "/"));
57
+ const slashIndex = normalized.indexOf("/");
58
+ if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return normalized;
59
+
60
+ const prefix = normalized.slice(0, slashIndex);
61
+ const matchesHotCollection = isQmdCollectionPrefix(prefix, policy.qmdCollection, source);
62
+ const matchesColdCollection = isQmdCollectionPrefix(prefix, policy.qmdColdCollection, source);
63
+ return matchesHotCollection || matchesColdCollection
64
+ ? normalized.slice(slashIndex + 1)
65
+ : normalized;
66
+ }
67
+
68
+ export function isTopLevelArchivePath(
69
+ filePath: string,
70
+ policy: GenericRecallPathPolicy = {},
71
+ source: GenericRecallPathSource = "filesystem",
72
+ ): boolean {
73
+ const isAbsolutePath = path.isAbsolute(filePath);
74
+ const relative =
75
+ policy.memoryDir && isAbsolutePath
76
+ ? path.relative(policy.memoryDir, path.resolve(filePath))
77
+ : filePath;
78
+ const normalized = !isAbsolutePath
79
+ ? stripQmdCollectionPrefix(relative, policy, source)
80
+ : relative;
81
+ return /^(?:archive|namespaces[\\/][^\\/]+[\\/]archive)(?:[\\/]|$)/i.test(normalized);
82
+ }
83
+
84
+ export function isGenericRecallExcludedPath(
85
+ filePath: string,
86
+ policy: GenericRecallPathPolicy = {},
87
+ source: GenericRecallPathSource = "filesystem",
88
+ ): boolean {
89
+ return (
90
+ isArtifactMemoryPath(filePath) ||
91
+ isActivityDigestPath(filePath, policy.memoryDir) ||
92
+ isTopLevelArchivePath(filePath, policy, source) ||
93
+ isMeetingRecordPath(filePath)
94
+ );
95
+ }
96
+
97
+ export function filterRecallCandidates(
98
+ candidates: QmdSearchResult[],
99
+ options: {
100
+ namespacesEnabled: boolean;
101
+ recallNamespaces: string[];
102
+ resolveNamespace: (path: string) => string;
103
+ limit: number;
104
+ pathPolicy?: GenericRecallPathPolicy;
105
+ memoryRoot?: string;
106
+ },
107
+ ): QmdSearchResult[] {
108
+ const pathPolicy =
109
+ options.pathPolicy ??
110
+ (options.memoryRoot === undefined ? undefined : { memoryDir: options.memoryRoot });
111
+ const scopedByNamespace = options.namespacesEnabled
112
+ ? candidates.filter((r) =>
113
+ options.recallNamespaces.includes(r.namespace ?? options.resolveNamespace(r.path)),
114
+ )
115
+ : candidates;
116
+ return scopedByNamespace
117
+ .filter((r) => !isGenericRecallExcludedPath(r.path, pathPolicy, "qmd"))
118
+ .slice(0, Math.max(0, options.limit));
119
+ }
@@ -475,16 +475,4 @@ export class NamespaceReadFanoutCoordinator {
475
475
  return lists.flat();
476
476
  }
477
477
 
478
- async readArchivedMemoriesForNamespaces(
479
- namespaces: string[],
480
- ): Promise<MemoryFile[]> {
481
- const uniq = Array.from(new Set(namespaces.filter(Boolean)));
482
- const lists = await Promise.all(
483
- uniq.map(async (ns) => {
484
- const sm = await this.deps.storageRouter.storageFor(ns);
485
- return sm.readArchivedMemories();
486
- }),
487
- );
488
- return lists.flat();
489
- }
490
478
  }
@@ -641,21 +641,6 @@ export function isActivityDigestPath(filePath: string, memoryRoot?: string): boo
641
641
  return ACTIVITY_DIGEST_ANYWHERE.test(filePath);
642
642
  }
643
643
 
644
- /**
645
- * Paths that dedicated surfaces own and generic recall must never inject:
646
- * artifacts, activity digests (issue #1899), and meeting records (issue #1900).
647
- * One predicate shared by every recall filter site so the exclusion cannot drift.
648
- * Explicit search paths (memory_search, activity search) do not apply this
649
- * filter, so those surfaces still read them.
650
- */
651
- export function isGenericRecallExcludedPath(filePath: string, memoryRoot?: string): boolean {
652
- return (
653
- isArtifactMemoryPath(filePath) ||
654
- isActivityDigestPath(filePath, memoryRoot) ||
655
- isMeetingRecordPath(filePath)
656
- );
657
- }
658
-
659
644
  export function buildCompressionGuidelinesMarkdown(
660
645
  events: MemoryActionEvent[],
661
646
  generatedAtIso: string = new Date().toISOString(),
@@ -663,26 +648,6 @@ export function buildCompressionGuidelinesMarkdown(
663
648
  return buildCompressionGuidelinesMarkdownV2(events, generatedAtIso);
664
649
  }
665
650
 
666
- export function filterRecallCandidates(
667
- candidates: QmdSearchResult[],
668
- options: {
669
- namespacesEnabled: boolean;
670
- recallNamespaces: string[];
671
- resolveNamespace: (path: string) => string;
672
- limit: number;
673
- /** Memory root for top-level activity-digest detection. */
674
- memoryRoot?: string;
675
- },
676
- ): QmdSearchResult[] {
677
- const scopedByNamespace = options.namespacesEnabled
678
- ? candidates.filter((r) =>
679
- options.recallNamespaces.includes(r.namespace ?? options.resolveNamespace(r.path)),
680
- )
681
- : candidates;
682
- return scopedByNamespace
683
- .filter((r) => !isGenericRecallExcludedPath(r.path, options.memoryRoot))
684
- .slice(0, Math.max(0, options.limit));
685
- }
686
651
 
687
652
  export function applyQueryAwareCandidateFilter(
688
653
  candidates: QmdSearchResult[],
@@ -100,7 +100,7 @@ import {
100
100
  type QueryAwarePrefilter,
101
101
  type RecallInvocationOptions,
102
102
  } from "../orchestrator.js";
103
- import { isGenericRecallExcludedPath } from "./orchestrator-helpers.js";
103
+ import { isGenericRecallExcludedPath } from "./generic-recall-paths.js";
104
104
 
105
105
  import type {
106
106
  RecallSectionAppendOptions,
@@ -4165,8 +4165,7 @@ export class RecallInternalCoordinator {
4165
4165
  memoryResults = memoryResults.filter((r) =>
4166
4166
  recallNamespaces.includes(r.namespace ?? this.deps.namespaceFromPath(r.path)));
4167
4167
  }
4168
- // Artifacts + activity digests are dedicated surfaces, never generic recall.
4169
- memoryResults = memoryResults.filter((r) => !isGenericRecallExcludedPath(r.path, this.deps.config.memoryDir));
4168
+ memoryResults = memoryResults.filter((r) => !isGenericRecallExcludedPath(r.path, this.deps.config, "qmd"));
4170
4169
 
4171
4170
  const isFullModeGraphAssist =
4172
4171
  graphCaps.multiGraphMemory &&
@@ -4297,6 +4296,7 @@ export class RecallInternalCoordinator {
4297
4296
  }
4298
4297
  }
4299
4298
  }
4299
+ memoryResults = memoryResults.filter((r) => !isGenericRecallExcludedPath(r.path, this.deps.config, "qmd"));
4300
4300
 
4301
4301
  // Apply mandatory recall safety filters before deadline-bound scoring
4302
4302
  // enrichment. If scoring times out, we must fall back to this filtered
@@ -4550,7 +4550,7 @@ export class RecallInternalCoordinator {
4550
4550
  recallNamespaces,
4551
4551
  resolveNamespace: (p) => this.deps.namespaceFromPath(p),
4552
4552
  limit: embeddingFetchLimit,
4553
- memoryRoot: this.deps.config.memoryDir,
4553
+ pathPolicy: this.deps.config,
4554
4554
  },
4555
4555
  );
4556
4556
  const boostedScoped = await this.deps.boostSearchResults(
@@ -4745,7 +4745,7 @@ export class RecallInternalCoordinator {
4745
4745
  recallNamespaces,
4746
4746
  resolveNamespace: (p) => this.deps.namespaceFromPath(p),
4747
4747
  limit: embeddingFetchLimit,
4748
- memoryRoot: this.deps.config.memoryDir,
4748
+ pathPolicy: this.deps.config,
4749
4749
  },
4750
4750
  );
4751
4751
  const boostedScoped = await this.deps.boostSearchResults(
@@ -4868,7 +4868,7 @@ export class RecallInternalCoordinator {
4868
4868
  typeof asOfMs === "number" && Number.isFinite(asOfMs);
4869
4869
  const activeMemories = memories.filter(
4870
4870
  (m) => {
4871
- if (isGenericRecallExcludedPath(m.path, this.deps.config.memoryDir)) return false;
4871
+ if (isGenericRecallExcludedPath(m.path, this.deps.config)) return false;
4872
4872
  const status = m.frontmatter.status;
4873
4873
  if (!status || status === "active") return true;
4874
4874
  if (status === "superseded") {
@@ -78,13 +78,12 @@ function scopedCandidatesStub(
78
78
  seed.filter((r) => candidatePaths.has(r.path)).slice(0, Math.max(0, limit));
79
79
  }
80
80
 
81
- function fallbackCoordinator(seed: QmdSearchResult[], archived: MemoryFile[]): RecallSearchPipelineCoordinator {
82
- const readArchivedMemoriesForNamespaces: RecallSearchPipelineDeps["readArchivedMemoriesForNamespaces"] =
83
- async () => archived;
81
+ function queryAwareFallbackCoordinator(
82
+ seed: QmdSearchResult[],
83
+ ): RecallSearchPipelineCoordinator {
84
84
  const namespaceFromPath: RecallSearchPipelineDeps["namespaceFromPath"] = () => "default";
85
85
  const deps = {
86
86
  searchScopedMemoryCandidates: scopedCandidatesStub(seed),
87
- readArchivedMemoriesForNamespaces,
88
87
  namespaceFromPath,
89
88
  config: { memoryDir: "/mem" },
90
89
  } as unknown as RecallSearchPipelineDeps;
@@ -107,48 +106,85 @@ const SEED_PATHS = [MEETING_RECORD, "facts/a.md", "artifacts/y.md"];
107
106
  const SEED_WITH_NON_RECALLABLE = SEED_PATHS.map((p) => result("default", p));
108
107
  const PREFILTER = prefilter(SEED_PATHS);
109
108
 
110
- test("archive fallback excludes non-recallable seeds on the archived-empty early return", async () => {
111
- const coordinator = fallbackCoordinator(SEED_WITH_NON_RECALLABLE, []);
112
- // limit 10 > filtered seed count, tokens non-empty, archived empty → the
113
- // `return scopedSeedResults` early path at the archived-empty guard.
114
- const out = await coordinator.searchLongTermArchiveFallback("quarterly report", ["default"], 10, PREFILTER);
109
+ test("query-aware fallback filters non-recallable scoped candidates", async () => {
110
+ const coordinator = queryAwareFallbackCoordinator(SEED_WITH_NON_RECALLABLE);
111
+ const out = await coordinator.searchQueryAwareFallback("quarterly report", 10, PREFILTER);
115
112
  assert.deepEqual(
116
113
  out.map((r) => r.path),
117
114
  ["facts/a.md"],
118
- "meeting record + artifact must not leak through the direct-caller early return",
115
+ "meeting records and artifacts must not leak through query-aware fallback",
119
116
  );
120
117
  });
121
118
 
122
- test("archive fallback excludes non-recallable seeds on the empty-tokens early return", async () => {
123
- const coordinator = fallbackCoordinator(SEED_WITH_NON_RECALLABLE, [memory("facts/z.md", "archived")]);
124
- // Empty prompt zero tokens → the `return scopedSeedResults` early path
125
- // before archive scoring is even attempted.
126
- const out = await coordinator.searchLongTermArchiveFallback("", ["default"], 10, PREFILTER);
119
+ test("query-aware fallback filters non-recallable scoped candidates for an empty query", async () => {
120
+ const coordinator = queryAwareFallbackCoordinator(SEED_WITH_NON_RECALLABLE);
121
+ const out = await coordinator.searchQueryAwareFallback("", 10, PREFILTER);
127
122
  assert.deepEqual(out.map((r) => r.path), ["facts/a.md"]);
128
123
  });
129
124
 
130
- test("archive fallback applies the cap to FILTERED seeds, not raw non-recallable hits", async () => {
131
- // The scoped search returns a single non-recallable hit that would fill a cap
132
- // of 1 if counted raw; the method must count only recallable seeds, so it does
133
- // not early-return that hit and (archive empty) yields nothing.
134
- const coordinator = fallbackCoordinator([result("default", MEETING_RECORD)], []);
135
- const out = await coordinator.searchLongTermArchiveFallback("quarterly", ["default"], 1, prefilter([MEETING_RECORD]));
125
+ test("query-aware fallback applies the cap after filtering", async () => {
126
+ const coordinator = queryAwareFallbackCoordinator([result("default", MEETING_RECORD)]);
127
+ const out = await coordinator.searchQueryAwareFallback("quarterly", 1, prefilter([MEETING_RECORD]));
136
128
  assert.deepEqual(out.map((r) => r.path), []);
137
129
  });
138
130
 
139
- test("finding 1 overfetches scoped seeds before exclusion so a capped fetch still yields recallable hits", async () => {
131
+ test("query-aware fallback excludes root archive candidates before its cap", async () => {
132
+ const archivePath = "archive/2026-02-23/fact-archived.md";
133
+ const coordinator = queryAwareFallbackCoordinator([
134
+ result("default", archivePath),
135
+ result("default", "facts/a.md"),
136
+ ]);
137
+
138
+ const out = await coordinator.searchQueryAwareFallback(
139
+ "quarterly",
140
+ 1,
141
+ prefilter([archivePath, "facts/a.md"]),
142
+ );
143
+
144
+ assert.deepEqual(out.map((r) => r.path), ["facts/a.md"]);
145
+ });
146
+
147
+ test("query-aware fallback retains non-active candidates for historical filtering", async () => {
148
+ let optionsSeen: { allowArchived?: boolean } | undefined;
149
+ const searchScopedMemoryCandidates: RecallSearchPipelineDeps["searchScopedMemoryCandidates"] = async (
150
+ candidatePaths,
151
+ _query,
152
+ limit,
153
+ options,
154
+ ) => {
155
+ optionsSeen = options;
156
+ return [result("default", "facts/superseded.md")]
157
+ .filter((candidate) => candidatePaths.has(candidate.path))
158
+ .slice(0, limit);
159
+ };
160
+ const coordinator = new RecallSearchPipelineCoordinator({
161
+ config: { memoryDir: "/mem" },
162
+ namespaceFromPath: () => "default",
163
+ searchScopedMemoryCandidates,
164
+ } as unknown as RecallSearchPipelineDeps);
165
+
166
+ const out = await coordinator.searchQueryAwareFallback(
167
+ "historical incident",
168
+ 1,
169
+ prefilter(["facts/superseded.md"]),
170
+ );
171
+
172
+ assert.deepEqual(out.map((candidate) => candidate.path), ["facts/superseded.md"]);
173
+ assert.deepEqual(optionsSeen, { allowArchived: true });
174
+ });
175
+
176
+ test("query-aware fallback overfetches scoped candidates before exclusion", async () => {
140
177
  // The scoped candidate order puts the NON-recallable meeting record FIRST;
141
178
  // capping the scoped fetch to the caller's limit (1) BEFORE the generic-recall
142
179
  // exclusion would return only the meeting record, which is then filtered out —
143
180
  // dropping facts/a.md with no refill. Overfetching the full candidate set first
144
181
  // and excluding afterward must still surface the recallable hit at the cap.
145
- const coordinator = fallbackCoordinator(
146
- [result("default", MEETING_RECORD), result("default", "facts/a.md")],
147
- [],
148
- );
149
- const out = await coordinator.searchLongTermArchiveFallback(
182
+ const coordinator = queryAwareFallbackCoordinator([
183
+ result("default", MEETING_RECORD),
184
+ result("default", "facts/a.md"),
185
+ ]);
186
+ const out = await coordinator.searchQueryAwareFallback(
150
187
  "quarterly",
151
- ["default"],
152
188
  1,
153
189
  prefilter([MEETING_RECORD, "facts/a.md"]),
154
190
  );
@@ -159,6 +195,7 @@ test("finding 1 — overfetches scoped seeds before exclusion so a capped fetch
159
195
  );
160
196
  });
161
197
 
198
+
162
199
  function hotSeedCoordinator(seed: QmdSearchResult[]): RecallSearchPipelineCoordinator {
163
200
  const deps = {
164
201
  config: { memoryDir: "/mem", qmdSearchStrategy: "lex", searchBackend: "qmd" },
@@ -250,3 +287,132 @@ test("finding 1 (round-5) — excluded seeds must not fill the merge cap and sta
250
287
  "excluded seeds ranked ahead must not fill the fetchLimit cap and starve the recallable primary hit",
251
288
  );
252
289
  });
290
+
291
+ test("QMD widening continues past archive-only pages", async () => {
292
+ const archived = Array.from({ length: 45 }, (_, index) =>
293
+ resultScored("default", `archive/2026-07-25/archive-${index}.md`, 2),
294
+ );
295
+ const live = resultScored("default", "facts/live.md", 1);
296
+ const ranked = [...archived, live];
297
+ const coordinator = new RecallSearchPipelineCoordinator({
298
+ config: { memoryDir: "/mem", qmdSearchStrategy: "lex", searchBackend: "qmd" },
299
+ qmd: {},
300
+ namespaceFromPath: () => "default",
301
+ searchAcrossNamespaces: async ({ maxResults }: { maxResults: number }) =>
302
+ ranked.slice(0, maxResults),
303
+ } as unknown as RecallSearchPipelineDeps);
304
+
305
+ const out = await coordinator.fetchQmdMemoryResultsWithArtifactTopUp(
306
+ "archive-starved",
307
+ 20,
308
+ 20,
309
+ {
310
+ namespacesEnabled: false,
311
+ recallNamespaces: ["default"],
312
+ resolveNamespace: () => "default",
313
+ queryAwarePrefilter: {
314
+ candidatePaths: null,
315
+ temporalFromDate: null,
316
+ matchedTags: [],
317
+ expandedTags: [],
318
+ combination: "none",
319
+ filteredToFullSearch: false,
320
+ },
321
+ },
322
+ );
323
+
324
+ assert.deepEqual(out.map((candidate) => candidate.path), ["facts/live.md"]);
325
+ });
326
+
327
+ test("QMD hybrid top-up runs when primary results are archive-only", async () => {
328
+ const archived = Array.from({ length: 20 }, (_, index) =>
329
+ resultScored("default", `archive/2026-07-25/archive-${index}.md`, 2),
330
+ );
331
+ const live = resultScored("default", "facts/hybrid-live.md", 1);
332
+ let hybridCalls = 0;
333
+ const coordinator = new RecallSearchPipelineCoordinator({
334
+ config: {
335
+ memoryDir: "/mem",
336
+ qmdSearchStrategy: "hybrid",
337
+ searchBackend: "qmd",
338
+ },
339
+ qmd: {
340
+ search: async (
341
+ _query: string,
342
+ _collection: string,
343
+ maxResults: number,
344
+ ) => archived.slice(0, maxResults),
345
+ hybridSearch: async () => {
346
+ hybridCalls += 1;
347
+ return [live];
348
+ },
349
+ },
350
+ namespaceFromPath: () => "default",
351
+ } as unknown as RecallSearchPipelineDeps);
352
+
353
+ const out = await coordinator.fetchQmdMemoryResultsWithArtifactTopUp(
354
+ "archive-starved-hybrid",
355
+ 2,
356
+ 2,
357
+ {
358
+ namespacesEnabled: false,
359
+ recallNamespaces: ["default"],
360
+ resolveNamespace: () => "default",
361
+ collection: "openclaw-engram",
362
+ queryAwarePrefilter: {
363
+ candidatePaths: null,
364
+ temporalFromDate: null,
365
+ matchedTags: [],
366
+ expandedTags: [],
367
+ combination: "none",
368
+ filteredToFullSearch: false,
369
+ },
370
+ },
371
+ );
372
+
373
+ assert.ok(hybridCalls > 0, "archive-only primary pages must trigger hybrid top-up");
374
+ assert.deepEqual(out.map((candidate) => candidate.path), ["facts/hybrid-live.md"]);
375
+ });
376
+
377
+ test("QMD stops after an underfilled archive-only page", async () => {
378
+ const archived = [resultScored("default", "archive/2026-07-25/only.md", 2)];
379
+ let searchCalls = 0;
380
+ const coordinator = new RecallSearchPipelineCoordinator({
381
+ config: {
382
+ memoryDir: "/mem",
383
+ qmdSearchStrategy: "hybrid",
384
+ searchBackend: "qmd",
385
+ },
386
+ qmd: {
387
+ search: async () => {
388
+ searchCalls += 1;
389
+ return archived;
390
+ },
391
+ hybridSearch: async () => [],
392
+ },
393
+ namespaceFromPath: () => "default",
394
+ } as unknown as RecallSearchPipelineDeps);
395
+
396
+ const out = await coordinator.fetchQmdMemoryResultsWithArtifactTopUp(
397
+ "archive-underfilled",
398
+ 20,
399
+ 20,
400
+ {
401
+ namespacesEnabled: false,
402
+ recallNamespaces: ["default"],
403
+ resolveNamespace: () => "default",
404
+ collection: "openclaw-engram",
405
+ queryAwarePrefilter: {
406
+ candidatePaths: null,
407
+ temporalFromDate: null,
408
+ matchedTags: [],
409
+ expandedTags: [],
410
+ combination: "none",
411
+ filteredToFullSearch: false,
412
+ },
413
+ },
414
+ );
415
+
416
+ assert.deepEqual(out, []);
417
+ assert.equal(searchCalls, 1, "an underfilled raw page must not trigger widening");
418
+ });