@melandlabs/memory-store 1.3.8 → 1.4.1

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.
@@ -320,6 +320,46 @@ function resolveSearchRuntimeContext(input, runtimeContext, now = Date.now) {
320
320
  };
321
321
  }
322
322
 
323
+ // src/search/execution-budget.ts
324
+ function boundedSearchOperation(operation, budget) {
325
+ const timeout = budget.sourceTimeoutMs ?? 5e3;
326
+ if (!Number.isFinite(timeout) || timeout <= 0)
327
+ throw new TypeError("sourceTimeoutMs must be positive and finite");
328
+ return (...args) => new Promise((resolve, reject) => {
329
+ const signal = budget.signal;
330
+ if (signal?.aborted) {
331
+ reject(signal.reason);
332
+ return;
333
+ }
334
+ const cleanup2 = () => {
335
+ clearTimeout(timer);
336
+ signal?.removeEventListener("abort", onAbort);
337
+ };
338
+ const onAbort = () => {
339
+ cleanup2();
340
+ reject(signal?.reason);
341
+ };
342
+ const timer = setTimeout(() => {
343
+ cleanup2();
344
+ reject(new Error(`Search operation exceeded ${timeout}ms waiting budget`));
345
+ }, timeout);
346
+ signal?.addEventListener("abort", onAbort, { once: true });
347
+ Promise.resolve().then(() => {
348
+ signal?.throwIfAborted();
349
+ return operation(...args);
350
+ }).then(
351
+ (value) => {
352
+ cleanup2();
353
+ resolve(value);
354
+ },
355
+ (error) => {
356
+ cleanup2();
357
+ reject(error);
358
+ }
359
+ );
360
+ });
361
+ }
362
+
323
363
  // src/search/reranker.ts
324
364
  async function applyReranker(reranker, query, results) {
325
365
  if (!reranker || results.length === 0) {
@@ -466,16 +506,21 @@ function deriveLexicalKeywords(query) {
466
506
  }
467
507
  var DEFAULT_LIMIT = 10;
468
508
  var DEFAULT_THRESHOLD = 0.7;
469
- var DEFAULT_SOURCES = ["memory", "insights", "knowledge"];
509
+ var DEFAULT_SOURCES = ["memory", "insights", "knowledge", "workspace"];
470
510
  var SOURCE_SET = new Set(DEFAULT_SOURCES);
471
511
  function normalizeUnifiedMemorySearchSources(sources) {
472
- if (!Array.isArray(sources) || sources.length === 0) {
512
+ if (sources === void 0) {
473
513
  return [...DEFAULT_SOURCES];
474
514
  }
515
+ if (!Array.isArray(sources) || sources.length === 0) {
516
+ throw new TypeError("Search sources must be a non-empty array when specified");
517
+ }
475
518
  const normalized = sources.filter((source) => typeof source === "string").map((source) => source.trim().toLowerCase()).filter(
476
519
  (source) => SOURCE_SET.has(source)
477
520
  );
478
- return normalized.length > 0 ? Array.from(new Set(normalized)) : [...DEFAULT_SOURCES];
521
+ if (normalized.length !== sources.length)
522
+ throw new TypeError("Search sources contain an unsupported source");
523
+ return Array.from(new Set(normalized));
479
524
  }
480
525
  function clampUnifiedMemorySearchLimit(limit) {
481
526
  const parsed = typeof limit === "number" ? limit : Number(limit ?? DEFAULT_LIMIT);
@@ -517,6 +562,20 @@ function normalizeUnifiedMemoryReasoningStrategy(value, defaultStrategy = "none"
517
562
  return defaultStrategy;
518
563
  }
519
564
  var DEFAULT_RRF_K = 60;
565
+ function mergeSearchResultProvenance(preferred, other) {
566
+ const first = preferred.provenance;
567
+ const second = other.provenance;
568
+ const firstVersion = first?.version ?? first?.citation?.version_id;
569
+ const secondVersion = second?.version ?? second?.citation?.version_id;
570
+ const firstHash = first?.contentHash ?? first?.citation?.content_hash;
571
+ const secondHash = second?.contentHash ?? second?.citation?.content_hash;
572
+ if (firstVersion != null && secondVersion != null && firstVersion !== secondVersion || firstHash != null && secondHash != null && firstHash !== secondHash || first?.citation && second?.citation && first.citation.id !== second.citation.id || preferred.content !== other.content && !(firstHash && firstHash === secondHash))
573
+ return preferred;
574
+ const metadata = { ...other.metadata, ...preferred.metadata };
575
+ if (!second) return { ...preferred, metadata };
576
+ const knownFirst = Object.fromEntries(Object.entries(first ?? {}).filter(([, value]) => value != null));
577
+ return { ...preferred, metadata, provenance: { ...second, ...knownFirst } };
578
+ }
520
579
  function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, weights = {}) {
521
580
  if (!Array.isArray(lists) || lists.length === 0) {
522
581
  return [];
@@ -530,6 +589,7 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
530
589
  const scores = /* @__PURE__ */ new Map();
531
590
  const order = [];
532
591
  for (const list of lists) {
592
+ const seenInList = /* @__PURE__ */ new Set();
533
593
  const channel = listNameToChannel(list.name);
534
594
  const configuredWeight = channel ? effectiveWeights[channel] : void 0;
535
595
  const weight = typeof configuredWeight === "number" && Number.isFinite(configuredWeight) && configuredWeight >= 0 ? configuredWeight : 1;
@@ -537,7 +597,8 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
537
597
  for (let index = 0; index < list.hits.length; index += 1) {
538
598
  const hit = list.hits[index];
539
599
  const key = `${hit.type}::${hit.id}`;
540
- const contribution = weight / (safeK + index + 1);
600
+ const contribution = seenInList.has(key) ? 0 : weight / (safeK + seenInList.size + 1);
601
+ seenInList.add(key);
541
602
  const existing = scores.get(key);
542
603
  const rankedHit = hit.type === "memory" ? withMatchedEvidence(hit, list.name, index + 1) : hit;
543
604
  if (existing) {
@@ -545,6 +606,7 @@ function mergeUnifiedMemorySearchResultsRrf(lists, limit, k = DEFAULT_RRF_K, wei
545
606
  if (hit.type === "memory" && existing.hit.type === "memory") {
546
607
  existing.hit = mergeMatchedEvidence(existing.hit, rankedHit);
547
608
  }
609
+ existing.hit = mergeSearchResultProvenance(existing.hit, rankedHit);
548
610
  } else {
549
611
  scores.set(key, { hit: rankedHit, rrf: contribution });
550
612
  order.push(key);
@@ -751,14 +813,15 @@ function buildSynthesisPrompt(input) {
751
813
  grouped.set(item.source, bucket);
752
814
  }
753
815
  const sections = [];
754
- for (const tier of DEFAULT_TIERS) {
816
+ for (const tier of [...DEFAULT_TIERS, "workspace"]) {
755
817
  const items = grouped.get(tier) ?? [];
756
818
  if (items.length === 0) {
757
819
  continue;
758
820
  }
759
821
  const lines = items.map((item, index) => {
760
822
  const factSource = typeof item.metadata?.source === "string" && item.metadata.source.length > 0 ? item.metadata.source : item.source ?? "unknown";
761
- return ` [${index + 1}] (${item.id}, source=${factSource}, score=${item.score.toFixed(4)}) ${item.snippet}`;
823
+ const provenance = item.provenance ? ` provenance=${JSON.stringify(item.provenance)}` : "";
824
+ return ` [${index + 1}] (${item.id}, source=${factSource}, score=${item.score.toFixed(4)}) ${item.snippet}${provenance}`;
762
825
  });
763
826
  sections.push(`## ${tier}
764
827
  ${lines.join("\n")}`);
@@ -849,7 +912,8 @@ async function synthesizeAnswer(input) {
849
912
  score: item.score,
850
913
  // Forward per-fact provenance (e.g. `metadata.source` from
851
914
  // `opencontext add --source`) so the synthesis prompt can cite it.
852
- metadata: item.metadata
915
+ metadata: item.metadata,
916
+ provenance: item.provenance
853
917
  }));
854
918
  const warnings = [];
855
919
  const complete = deps.reasoning?.complete;
@@ -1070,7 +1134,7 @@ async function embedQueryVariant(embedQuery, input, query) {
1070
1134
  authToken: input.authToken
1071
1135
  });
1072
1136
  }
1073
- async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext) {
1137
+ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit, threshold, logger, peerPeers, warnings, runtimeContext, execution) {
1074
1138
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds.map((botId) => ({ botId })) : [{}];
1075
1139
  let semantic = [];
1076
1140
  if (typeof deps.searchRawMessagesAnn === "function") {
@@ -1098,7 +1162,10 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
1098
1162
  semantic = (await Promise.all(
1099
1163
  filters.map((filter) => {
1100
1164
  const botId = "botId" in filter ? filter.botId : void 0;
1101
- return searchRawMessagesWithChroma({
1165
+ return boundedSearchOperation(
1166
+ searchRawMessagesWithChroma,
1167
+ deps
1168
+ )({
1102
1169
  userId: input.userId,
1103
1170
  queryEmbedding,
1104
1171
  limit,
@@ -1107,19 +1174,21 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
1107
1174
  });
1108
1175
  })
1109
1176
  )).flat().map(toMemoryResult);
1177
+ if (execution) execution.completed++;
1110
1178
  logger.log?.("[memory-store] Raw message semantic search completed", {
1111
1179
  backend: "chroma",
1112
1180
  dimensions: queryEmbedding.length,
1113
1181
  count: semantic.length
1114
1182
  });
1115
1183
  } catch (error) {
1184
+ if (execution) execution.failed++;
1116
1185
  logger.warn?.(
1117
1186
  "[memory-store] Chroma raw message search failed; falling back to database search:",
1118
1187
  error
1119
1188
  );
1120
1189
  }
1121
1190
  }
1122
- if (semantic.length === 0 && runtimeContext !== void 0) {
1191
+ if (semantic.length === 0 && runtimeContext !== void 0 && !deps.searchRawMessagesAnn) {
1123
1192
  warnApplicabilityNotEnforced(warnings);
1124
1193
  return [];
1125
1194
  }
@@ -1129,7 +1198,10 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
1129
1198
  const manager2 = await getRawMessageManager2();
1130
1199
  if (typeof manager2.searchMessagesSemantically === "function") {
1131
1200
  const factTypes = input.factTypes?.length ? input.factTypes : void 0;
1132
- const results = await manager2.searchMessagesSemantically({
1201
+ const results = await boundedSearchOperation(
1202
+ manager2.searchMessagesSemantically.bind(manager2),
1203
+ deps
1204
+ )({
1133
1205
  userId: input.userId,
1134
1206
  queryEmbedding,
1135
1207
  limit,
@@ -1139,6 +1211,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
1139
1211
  ...peerPeers.length > 0 ? { peers: peerPeers } : {},
1140
1212
  ...factTypes ? { factTypes } : {}
1141
1213
  });
1214
+ if (execution) execution.completed++;
1142
1215
  semantic = results.map((r) => ({
1143
1216
  type: "memory",
1144
1217
  id: r.id,
@@ -1148,6 +1221,7 @@ async function runSemanticSearchForEmbedding(deps, input, queryEmbedding, limit,
1148
1221
  }));
1149
1222
  }
1150
1223
  } catch (error) {
1224
+ if (execution) execution.failed++;
1151
1225
  logger.warn?.("[memory-store] SQLite semantic search failed:", error);
1152
1226
  }
1153
1227
  }
@@ -1198,7 +1272,7 @@ function toIterativeRecallCandidate(hit) {
1198
1272
  metadata: hit.metadata
1199
1273
  };
1200
1274
  }
1201
- async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext) {
1275
+ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger, peerPeers, warnings, runtimeContext, execution) {
1202
1276
  if (keywords.length === 0) {
1203
1277
  return [];
1204
1278
  }
@@ -1248,9 +1322,12 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
1248
1322
  const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
1249
1323
  const factTypes = input.factTypes?.length ? input.factTypes : void 0;
1250
1324
  const includeDeprecated = input.includeDeprecated === true;
1251
- return (await Promise.all(
1325
+ const results = (await Promise.all(
1252
1326
  filters.map(
1253
- (botId) => lexicalSearchRawMessages2({
1327
+ (botId) => boundedSearchOperation(
1328
+ lexicalSearchRawMessages2,
1329
+ deps
1330
+ )({
1254
1331
  userId: input.userId,
1255
1332
  keywords,
1256
1333
  limit: Math.ceil(limit / filters.length),
@@ -1265,7 +1342,10 @@ async function runLexicalSearchForKeywords(deps, input, keywords, limit, logger,
1265
1342
  r
1266
1343
  )
1267
1344
  );
1345
+ if (execution) execution.completed++;
1346
+ return results;
1268
1347
  } catch (error) {
1348
+ if (execution) execution.failed++;
1269
1349
  logger.warn?.("[memory-store] SQLite lexical search failed:", error);
1270
1350
  }
1271
1351
  return [];
@@ -1312,7 +1392,21 @@ function mapHitToEvidenceSource(hit) {
1312
1392
  }
1313
1393
  return hit.type;
1314
1394
  }
1315
- function createUnifiedSearch(deps = {}) {
1395
+ function createUnifiedSearch(inputDeps = {}) {
1396
+ const deps = {
1397
+ ...inputDeps,
1398
+ embedQuery: inputDeps.embedQuery && boundedSearchOperation(inputDeps.embedQuery, inputDeps),
1399
+ searchRawMessagesAnn: inputDeps.searchRawMessagesAnn && boundedSearchOperation(inputDeps.searchRawMessagesAnn, inputDeps),
1400
+ searchRawMessagesHybrid: inputDeps.searchRawMessagesHybrid && boundedSearchOperation(inputDeps.searchRawMessagesHybrid, inputDeps),
1401
+ searchRawMessagesLexical: inputDeps.searchRawMessagesLexical && boundedSearchOperation(inputDeps.searchRawMessagesLexical, inputDeps),
1402
+ searchWorkspace: inputDeps.searchWorkspace && boundedSearchOperation(inputDeps.searchWorkspace, inputDeps),
1403
+ searchInsights: inputDeps.searchInsights && boundedSearchOperation(inputDeps.searchInsights, inputDeps),
1404
+ searchKnowledge: inputDeps.searchKnowledge && boundedSearchOperation(inputDeps.searchKnowledge, inputDeps),
1405
+ searchSummaries: inputDeps.searchSummaries && boundedSearchOperation(inputDeps.searchSummaries, inputDeps),
1406
+ getRawMessageRetrievalStatus: inputDeps.getRawMessageRetrievalStatus && boundedSearchOperation(inputDeps.getRawMessageRetrievalStatus, inputDeps),
1407
+ entitySearch: inputDeps.entitySearch && boundedSearchOperation(inputDeps.entitySearch, inputDeps),
1408
+ searchRawMessageNeighbors: inputDeps.searchRawMessageNeighbors && boundedSearchOperation(inputDeps.searchRawMessageNeighbors, inputDeps)
1409
+ };
1316
1410
  const logger = deps.logger ?? console;
1317
1411
  function resolveReasoningStrategy(input) {
1318
1412
  const requested = normalizeUnifiedMemoryReasoningStrategy(input.reasoningStrategy);
@@ -1322,6 +1416,25 @@ function createUnifiedSearch(deps = {}) {
1322
1416
  return normalizeUnifiedMemoryReasoningStrategy(deps.reasoning?.defaultStrategy, "none");
1323
1417
  }
1324
1418
  async function runMemorySource(input, limit, threshold, warnings, reasoningStrategy = "none", reasoningInfo, peerPeers = [], runtimeContext) {
1419
+ const execution = { completed: 0, failed: 0 };
1420
+ const observe = (operation, preparation = false) => operation && (async (...args) => {
1421
+ try {
1422
+ const result = await operation(...args);
1423
+ if (!preparation) execution.completed++;
1424
+ return result;
1425
+ } catch (error) {
1426
+ execution.failed++;
1427
+ throw error;
1428
+ }
1429
+ });
1430
+ const sourceDeps = {
1431
+ ...deps,
1432
+ embedQuery: observe(deps.embedQuery, true),
1433
+ searchRawMessagesAnn: observe(deps.searchRawMessagesAnn),
1434
+ searchRawMessagesHybrid: observe(deps.searchRawMessagesHybrid),
1435
+ searchRawMessagesLexical: observe(deps.searchRawMessagesLexical),
1436
+ entitySearch: observe(deps.entitySearch)
1437
+ };
1325
1438
  const sourceStartedAt = monotonicNow();
1326
1439
  const timings = {
1327
1440
  semanticMs: 0,
@@ -1339,9 +1452,10 @@ function createUnifiedSearch(deps = {}) {
1339
1452
  };
1340
1453
  const finish = (results) => ({
1341
1454
  results,
1342
- timings: { ...timings, memorySourceMs: monotonicNow() - sourceStartedAt }
1455
+ timings: { ...timings, memorySourceMs: monotonicNow() - sourceStartedAt },
1456
+ availability: execution.failed > 0 ? execution.completed > 0 ? "partial" : "failed" : execution.completed > 0 || Object.values(results).some((hits) => hits.length > 0) ? "complete" : "not_ready"
1343
1457
  });
1344
- if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !deps.reasoning?.iterativePlanner) {
1458
+ if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && !sourceDeps.reasoning?.iterativePlanner) {
1345
1459
  warnings.push({
1346
1460
  source: "memory",
1347
1461
  code: "memory_iterative_planner_not_configured",
@@ -1349,7 +1463,7 @@ function createUnifiedSearch(deps = {}) {
1349
1463
  });
1350
1464
  if (reasoningInfo) reasoningInfo.degraded = true;
1351
1465
  }
1352
- if (reasoningStrategy === "rewrite" && !deps.reasoning?.queryRewriter) {
1466
+ if (reasoningStrategy === "rewrite" && !sourceDeps.reasoning?.queryRewriter) {
1353
1467
  warnings.push({
1354
1468
  source: "memory",
1355
1469
  code: "memory_query_rewrite_not_configured",
@@ -1358,8 +1472,8 @@ function createUnifiedSearch(deps = {}) {
1358
1472
  if (reasoningInfo) reasoningInfo.degraded = true;
1359
1473
  }
1360
1474
  let unionEvidence = null;
1361
- if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && deps.reasoning?.iterativePlanner) {
1362
- const planner = deps.reasoning.iterativePlanner;
1475
+ if ((reasoningStrategy === "iterative" || reasoningStrategy === "union") && sourceDeps.reasoning?.iterativePlanner) {
1476
+ const planner = sourceDeps.reasoning.iterativePlanner;
1363
1477
  const result = await planner.plan({
1364
1478
  query: input.query,
1365
1479
  ...input.includeRetrievalDiagnostics ? { options: { collectDiagnostics: true } } : {},
@@ -1371,26 +1485,27 @@ function createUnifiedSearch(deps = {}) {
1371
1485
  const lexicalHits = await measure(
1372
1486
  "lexicalMs",
1373
1487
  () => runLexicalSearchForKeywords(
1374
- deps,
1488
+ sourceDeps,
1375
1489
  input,
1376
1490
  keywords2,
1377
1491
  limit,
1378
1492
  logger,
1379
1493
  peerPeers,
1380
1494
  warnings,
1381
- runtimeContext
1495
+ runtimeContext,
1496
+ execution
1382
1497
  )
1383
1498
  );
1384
1499
  let semanticHits = [];
1385
- if (typeof deps.embedQuery === "function") {
1500
+ if (typeof sourceDeps.embedQuery === "function") {
1386
1501
  try {
1387
1502
  semanticHits = await measure("semanticMs", async () => {
1388
1503
  const semanticQuery = keywords2.join(" ");
1389
- const queryEmbedder = deps.embedQuery;
1504
+ const queryEmbedder = sourceDeps.embedQuery;
1390
1505
  if (typeof queryEmbedder !== "function") return [];
1391
1506
  const queryEmbedding = await embedQueryVariant(queryEmbedder, input, semanticQuery);
1392
1507
  return runSemanticSearchForEmbedding(
1393
- deps,
1508
+ sourceDeps,
1394
1509
  input,
1395
1510
  queryEmbedding,
1396
1511
  limit,
@@ -1398,7 +1513,8 @@ function createUnifiedSearch(deps = {}) {
1398
1513
  logger,
1399
1514
  peerPeers,
1400
1515
  warnings,
1401
- runtimeContext
1516
+ runtimeContext,
1517
+ execution
1402
1518
  );
1403
1519
  });
1404
1520
  } catch (error) {
@@ -1451,66 +1567,74 @@ function createUnifiedSearch(deps = {}) {
1451
1567
  }
1452
1568
  unionEvidence = evidence;
1453
1569
  }
1454
- if (typeof deps.embedQuery !== "function") {
1570
+ const lexicalFallback = async () => {
1571
+ const lexical2 = await runLexicalSearchForKeywords(
1572
+ sourceDeps,
1573
+ input,
1574
+ deriveLexicalKeywords(input.query),
1575
+ limit,
1576
+ logger,
1577
+ peerPeers,
1578
+ warnings,
1579
+ runtimeContext,
1580
+ execution
1581
+ );
1582
+ return finish({ semantic: [], lexical: filterByDateRange(lexical2, input.dateFrom, input.dateTo) });
1583
+ };
1584
+ if (typeof sourceDeps.embedQuery !== "function") {
1455
1585
  warnings.push({
1456
1586
  source: "memory",
1457
1587
  code: "memory_lexical_search_fallback",
1458
1588
  message: "No embedding provider is configured; using lexical (BM25) search as fallback. Pass an `embedQuery` function via `unified.embedQuery` to enable semantic retrieval."
1459
1589
  });
1460
- const keywords2 = deriveLexicalKeywords(input.query);
1461
- const lexical2 = filterByDateRange(
1462
- await runLexicalSearchForKeywords(
1463
- deps,
1464
- input,
1465
- keywords2,
1466
- limit,
1467
- logger,
1468
- peerPeers,
1469
- warnings,
1470
- runtimeContext
1471
- ),
1472
- input.dateFrom,
1473
- input.dateTo
1474
- );
1475
- return finish({ semantic: [], lexical: lexical2 });
1590
+ return lexicalFallback();
1476
1591
  }
1477
1592
  let semantic = [];
1478
1593
  let lexicalQueries = [input.query];
1479
- const embedQuery = deps.embedQuery;
1594
+ const embedQuery = sourceDeps.embedQuery;
1480
1595
  const activeMergeStrategy = normalizeUnifiedMemoryMergeStrategy(
1481
- input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
1596
+ input.mergeStrategy ?? sourceDeps.reasoning?.defaultMergeStrategy ?? "rrf"
1482
1597
  );
1483
- if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof deps.searchRawMessagesHybrid === "function") {
1484
- const hybrid = await measure("hybridMs", async () => {
1485
- const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
1486
- const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
1487
- return (await Promise.all(
1488
- filters.map(
1489
- (botId) => deps.searchRawMessagesHybrid?.({
1490
- userId: input.userId,
1491
- query: input.query,
1492
- queryEmbedding,
1493
- limit: Math.ceil(limit / filters.length),
1494
- botId,
1495
- ...runtimeContext ?? {}
1496
- })
1497
- )
1498
- )).flatMap((items) => items ?? []).filter(isRawMemorySemanticResult).map(toMemoryResult);
1499
- });
1500
- return finish({
1501
- semantic: [],
1502
- lexical: [],
1503
- hybrid: filterByDateRange(hybrid, input.dateFrom, input.dateTo)
1504
- });
1598
+ if (reasoningStrategy === "none" && activeMergeStrategy === "rrf" && input.threshold === void 0 && typeof sourceDeps.searchRawMessagesHybrid === "function") {
1599
+ try {
1600
+ const hybrid = await measure("hybridMs", async () => {
1601
+ const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
1602
+ const filters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
1603
+ return (await Promise.all(
1604
+ filters.map(
1605
+ (botId) => sourceDeps.searchRawMessagesHybrid?.({
1606
+ userId: input.userId,
1607
+ query: input.query,
1608
+ queryEmbedding,
1609
+ limit: Math.ceil(limit / filters.length),
1610
+ botId,
1611
+ ...runtimeContext ?? {}
1612
+ })
1613
+ )
1614
+ )).flatMap((items) => items ?? []).filter(isRawMemorySemanticResult).map(toMemoryResult);
1615
+ });
1616
+ return finish({
1617
+ semantic: [],
1618
+ lexical: [],
1619
+ hybrid: filterByDateRange(hybrid, input.dateFrom, input.dateTo)
1620
+ });
1621
+ } catch (error) {
1622
+ warnings.push({
1623
+ source: "memory",
1624
+ code: "memory_hybrid_search_failed",
1625
+ message: error instanceof Error ? error.message : String(error)
1626
+ });
1627
+ return lexicalFallback();
1628
+ }
1505
1629
  }
1506
- if (reasoningStrategy === "rewrite" && deps.reasoning?.queryRewriter) {
1630
+ if (reasoningStrategy === "rewrite" && sourceDeps.reasoning?.queryRewriter) {
1507
1631
  try {
1508
- const variants = await deps.reasoning.queryRewriter.rewrite({
1632
+ const variants = await sourceDeps.reasoning.queryRewriter.rewrite({
1509
1633
  query: input.query,
1510
1634
  userId: input.userId,
1511
1635
  authToken: input.authToken
1512
1636
  });
1513
- if (deps.reasoning.rewriteLexical === true) {
1637
+ if (sourceDeps.reasoning.rewriteLexical === true) {
1514
1638
  const seen = /* @__PURE__ */ new Set();
1515
1639
  lexicalQueries = [input.query, ...variants].map((query) => query.trim()).filter((query) => {
1516
1640
  const key = query.toLowerCase();
@@ -1521,7 +1645,7 @@ function createUnifiedSearch(deps = {}) {
1521
1645
  }
1522
1646
  if (reasoningInfo) {
1523
1647
  reasoningInfo.rewrittenQueries = variants;
1524
- if (deps.reasoning.queryRewriter.lastDegraded?.()) {
1648
+ if (sourceDeps.reasoning.queryRewriter.lastDegraded?.()) {
1525
1649
  reasoningInfo.degraded = true;
1526
1650
  }
1527
1651
  }
@@ -1532,7 +1656,7 @@ function createUnifiedSearch(deps = {}) {
1532
1656
  return Promise.all(
1533
1657
  embeddings.map(
1534
1658
  (embedding) => runSemanticSearchForEmbedding(
1535
- deps,
1659
+ sourceDeps,
1536
1660
  input,
1537
1661
  embedding,
1538
1662
  limit,
@@ -1540,12 +1664,13 @@ function createUnifiedSearch(deps = {}) {
1540
1664
  logger,
1541
1665
  peerPeers,
1542
1666
  warnings,
1543
- runtimeContext
1667
+ runtimeContext,
1668
+ execution
1544
1669
  )
1545
1670
  )
1546
1671
  );
1547
1672
  });
1548
- if (activeMergeStrategy === "rrf" && deps.reasoning.rewriteSemanticMerge === "rrf" && lists.length > 1 && lists.some((hits) => hits.length > 0)) {
1673
+ if (activeMergeStrategy === "rrf" && sourceDeps.reasoning.rewriteSemanticMerge === "rrf" && lists.length > 1 && lists.some((hits) => hits.length > 0)) {
1549
1674
  semantic = mergeUnifiedMemorySearchResultsRrf(
1550
1675
  lists.map((hits, index) => ({
1551
1676
  name: `memory-semantic-query:${index}`,
@@ -1568,24 +1693,33 @@ function createUnifiedSearch(deps = {}) {
1568
1693
  }
1569
1694
  }
1570
1695
  if (semantic.length === 0) {
1571
- semantic = await measure("semanticMs", async () => {
1572
- const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
1573
- return runSemanticSearchForEmbedding(
1574
- deps,
1575
- input,
1576
- queryEmbedding,
1577
- limit,
1578
- threshold,
1579
- logger,
1580
- peerPeers,
1581
- warnings,
1582
- runtimeContext
1583
- );
1584
- });
1696
+ try {
1697
+ semantic = await measure("semanticMs", async () => {
1698
+ const queryEmbedding = await embedQueryVariant(embedQuery, input, input.query);
1699
+ return runSemanticSearchForEmbedding(
1700
+ sourceDeps,
1701
+ input,
1702
+ queryEmbedding,
1703
+ limit,
1704
+ threshold,
1705
+ logger,
1706
+ peerPeers,
1707
+ warnings,
1708
+ runtimeContext,
1709
+ execution
1710
+ );
1711
+ });
1712
+ } catch (error) {
1713
+ warnings.push({
1714
+ source: "memory",
1715
+ code: "memory_semantic_search_failed",
1716
+ message: error instanceof Error ? error.message : String(error)
1717
+ });
1718
+ }
1585
1719
  }
1586
1720
  let lexical = [];
1587
1721
  const keywords = deriveLexicalKeywords(input.query);
1588
- if (activeMergeStrategy === "rrf" && lexicalQueries.length > 1 && typeof deps.searchRawMessagesLexical === "function") {
1722
+ if (activeMergeStrategy === "rrf" && lexicalQueries.length > 1 && typeof sourceDeps.searchRawMessagesLexical === "function") {
1589
1723
  const lists = await Promise.all(
1590
1724
  lexicalQueries.map(async (query) => ({
1591
1725
  name: "memory-bm25",
@@ -1593,14 +1727,15 @@ function createUnifiedSearch(deps = {}) {
1593
1727
  await measure(
1594
1728
  "lexicalMs",
1595
1729
  () => runLexicalSearchForKeywords(
1596
- deps,
1730
+ sourceDeps,
1597
1731
  input,
1598
1732
  deriveLexicalKeywords(query),
1599
1733
  limit,
1600
1734
  logger,
1601
1735
  peerPeers,
1602
1736
  warnings,
1603
- runtimeContext
1737
+ runtimeContext,
1738
+ execution
1604
1739
  )
1605
1740
  ),
1606
1741
  "memory-bm25"
@@ -1613,11 +1748,11 @@ function createUnifiedSearch(deps = {}) {
1613
1748
  }));
1614
1749
  if (reasoningInfo) reasoningInfo.lexicalRewrittenQueries = lexicalQueries;
1615
1750
  } else if (keywords.length > 0) {
1616
- if (typeof deps.searchRawMessagesLexical === "function") {
1751
+ if (typeof sourceDeps.searchRawMessagesLexical === "function") {
1617
1752
  const lexicalStartedAt = monotonicNow();
1618
1753
  try {
1619
1754
  const lexFilters = input.botIds && input.botIds.length > 0 ? input.botIds : [void 0];
1620
- const searchRawMessagesLexical = deps.searchRawMessagesLexical;
1755
+ const searchRawMessagesLexical = sourceDeps.searchRawMessagesLexical;
1621
1756
  const factTypes = input.factTypes?.length ? input.factTypes : void 0;
1622
1757
  const includeDeprecated = input.includeDeprecated === true;
1623
1758
  lexical = (await Promise.all(
@@ -1657,16 +1792,16 @@ function createUnifiedSearch(deps = {}) {
1657
1792
  }
1658
1793
  }
1659
1794
  let entity;
1660
- if (typeof deps.entitySearch === "function") {
1795
+ if (typeof sourceDeps.entitySearch === "function") {
1661
1796
  entity = filterByDateRange(
1662
- await runEntitySearchForQuery(deps, input, limit, logger, peerPeers, runtimeContext),
1797
+ await runEntitySearchForQuery(sourceDeps, input, limit, logger, peerPeers, runtimeContext),
1663
1798
  input.dateFrom,
1664
1799
  input.dateTo
1665
1800
  );
1666
1801
  if (entity.length === 0) {
1667
1802
  entity = void 0;
1668
1803
  } else if (normalizeUnifiedMemoryMergeStrategy(
1669
- input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
1804
+ input.mergeStrategy ?? sourceDeps.reasoning?.defaultMergeStrategy ?? "rrf"
1670
1805
  ) !== "rrf") {
1671
1806
  warnings.push({
1672
1807
  source: "memory",
@@ -1697,6 +1832,7 @@ function createUnifiedSearch(deps = {}) {
1697
1832
  const searchStartedAt = monotonicNow();
1698
1833
  const query = input.query.trim();
1699
1834
  const sources = normalizeUnifiedMemorySearchSources(input.sources);
1835
+ const sourceStates = [];
1700
1836
  const limit = clampUnifiedMemorySearchLimit(input.limit);
1701
1837
  const mergeStrategy = normalizeUnifiedMemoryMergeStrategy(
1702
1838
  input.mergeStrategy ?? deps.reasoning?.defaultMergeStrategy ?? "rrf"
@@ -1722,7 +1858,8 @@ function createUnifiedSearch(deps = {}) {
1722
1858
  sources,
1723
1859
  results: [],
1724
1860
  count: 0,
1725
- warnings
1861
+ warnings,
1862
+ sourceStates
1726
1863
  };
1727
1864
  }
1728
1865
  let memorySubs;
@@ -1733,6 +1870,7 @@ function createUnifiedSearch(deps = {}) {
1733
1870
  reasoningInfo.dateRange = { from: input.dateFrom, to: input.dateTo };
1734
1871
  }
1735
1872
  if (sources.includes("memory")) {
1873
+ let availability = "not_ready";
1736
1874
  const hasRawProviders = typeof deps.searchRawMessagesAnn === "function" || typeof deps.searchRawMessagesHybrid === "function" || typeof deps.searchRawMessagesLexical === "function";
1737
1875
  if (isRawMessageStorageAvailable() || hasRawProviders) {
1738
1876
  try {
@@ -1747,6 +1885,7 @@ function createUnifiedSearch(deps = {}) {
1747
1885
  runtimeContext
1748
1886
  );
1749
1887
  memorySubs = memoryRun.results;
1888
+ availability = memoryRun.availability;
1750
1889
  memoryTimings = memoryRun.timings;
1751
1890
  memorySubs = {
1752
1891
  semantic: dedupeChannelByParent(memorySubs.semantic, "memory-semantic"),
@@ -1771,11 +1910,12 @@ function createUnifiedSearch(deps = {}) {
1771
1910
  warnings.push({ source: "memory", code, message });
1772
1911
  }
1773
1912
  } catch (error) {
1913
+ availability = availability === "complete" || availability === "partial" ? "partial" : "failed";
1774
1914
  logger.warn?.("[memory-store] memory source failed:", error);
1775
1915
  warnings.push({
1776
1916
  source: "memory",
1777
1917
  code: "memory_search_failed",
1778
- message: `Memory search failed: ${error.message ?? "Unknown error"}. Using keyword search fallback.`
1918
+ message: `Memory search failed: ${error.message ?? "Unknown error"}.`
1779
1919
  });
1780
1920
  }
1781
1921
  } else {
@@ -1785,6 +1925,51 @@ function createUnifiedSearch(deps = {}) {
1785
1925
  message: "Raw memory storage is not available in this environment."
1786
1926
  });
1787
1927
  }
1928
+ const count = new Set(
1929
+ [
1930
+ ...memorySubs?.semantic ?? [],
1931
+ ...memorySubs?.lexical ?? [],
1932
+ ...memorySubs?.hybrid ?? [],
1933
+ ...memorySubs?.planner ?? [],
1934
+ ...memorySubs?.entity ?? []
1935
+ ].map((hit) => hit.id)
1936
+ ).size;
1937
+ if (availability === "complete" && count === 0 && retrievalStatus?.lexicalReady === false && (!retrievalStatus.semanticReady || !deps.embedQuery)) {
1938
+ availability = "not_ready";
1939
+ }
1940
+ const memoryWarnings = warnings.filter((warning) => warning.source === "memory");
1941
+ sourceStates.push({
1942
+ source: "memory",
1943
+ status: availability,
1944
+ count,
1945
+ ...memoryWarnings.length ? { message: memoryWarnings.map((warning) => warning.message).join("; ") } : {}
1946
+ });
1947
+ }
1948
+ const workspaceHits = [];
1949
+ if (sources.includes("workspace")) {
1950
+ if (!deps.searchWorkspace) {
1951
+ sourceStates.push({ source: "workspace", status: "not_configured", count: 0 });
1952
+ } else {
1953
+ try {
1954
+ const result = await deps.searchWorkspace({
1955
+ userId: input.userId,
1956
+ query,
1957
+ limit: candidateLimit,
1958
+ ...runtimeContext ?? {}
1959
+ });
1960
+ workspaceHits.push(...result.hits.map((hit) => ({ ...hit, type: "workspace" })));
1961
+ sourceStates.push({
1962
+ source: "workspace",
1963
+ status: result.status,
1964
+ count: result.hits.length,
1965
+ ...result.message ? { message: result.message } : {}
1966
+ });
1967
+ } catch (error) {
1968
+ const message = error instanceof Error ? error.message : String(error);
1969
+ sourceStates.push({ source: "workspace", status: "failed", count: 0, message });
1970
+ warnings.push({ source: "workspace", code: "workspace_search_failed", message });
1971
+ }
1972
+ }
1788
1973
  }
1789
1974
  const insightHits = [];
1790
1975
  if (sources.includes("insights") && typeof deps.searchInsights === "function") {
@@ -1806,10 +1991,18 @@ function createUnifiedSearch(deps = {}) {
1806
1991
  id: hit.id,
1807
1992
  content: hit.content,
1808
1993
  similarity: hit.similarity,
1809
- metadata: hit.metadata
1994
+ metadata: hit.metadata,
1995
+ provenance: hit.provenance
1810
1996
  });
1811
1997
  }
1998
+ sourceStates.push({ source: "insights", status: "complete", count: insightHits.length });
1812
1999
  } catch (error) {
2000
+ sourceStates.push({
2001
+ source: "insights",
2002
+ status: "failed",
2003
+ count: 0,
2004
+ message: error instanceof Error ? error.message : String(error)
2005
+ });
1813
2006
  logger.warn?.("[memory-store] insights source failed:", error);
1814
2007
  warnings.push({
1815
2008
  source: "insights",
@@ -1817,7 +2010,9 @@ function createUnifiedSearch(deps = {}) {
1817
2010
  message: error.message ?? "insights_search_failed"
1818
2011
  });
1819
2012
  }
1820
- } else if (sources.includes("insights")) ;
2013
+ } else if (sources.includes("insights")) {
2014
+ sourceStates.push({ source: "insights", status: "not_configured", count: 0 });
2015
+ }
1821
2016
  const knowledgeHits = [];
1822
2017
  if (sources.includes("knowledge") && typeof deps.searchKnowledge === "function") {
1823
2018
  try {
@@ -1836,7 +2031,14 @@ function createUnifiedSearch(deps = {}) {
1836
2031
  for (const hit of hits) {
1837
2032
  knowledgeHits.push(toKnowledgeResult(hit));
1838
2033
  }
2034
+ sourceStates.push({ source: "knowledge", status: "complete", count: knowledgeHits.length });
1839
2035
  } catch (error) {
2036
+ sourceStates.push({
2037
+ source: "knowledge",
2038
+ status: "failed",
2039
+ count: 0,
2040
+ message: error instanceof Error ? error.message : String(error)
2041
+ });
1840
2042
  logger.warn?.("[memory-store] knowledge source failed:", error);
1841
2043
  warnings.push({
1842
2044
  source: "knowledge",
@@ -1844,12 +2046,15 @@ function createUnifiedSearch(deps = {}) {
1844
2046
  message: error.message ?? "knowledge_search_failed"
1845
2047
  });
1846
2048
  }
1847
- } else if (sources.includes("knowledge")) ;
2049
+ } else if (sources.includes("knowledge")) {
2050
+ sourceStates.push({ source: "knowledge", status: "not_configured", count: 0 });
2051
+ }
1848
2052
  const fusionStartedAt = monotonicNow();
1849
2053
  const merged = mergeAcrossSources({
1850
2054
  memorySubs,
1851
2055
  insightHits,
1852
2056
  knowledgeHits,
2057
+ workspaceHits,
1853
2058
  limit: candidateLimit,
1854
2059
  strategy: mergeStrategy,
1855
2060
  rrfK,
@@ -1876,7 +2081,8 @@ function createUnifiedSearch(deps = {}) {
1876
2081
  sources,
1877
2082
  results: ranked,
1878
2083
  count: ranked.length,
1879
- warnings
2084
+ warnings,
2085
+ sourceStates
1880
2086
  };
1881
2087
  if (input.includeRetrievalDiagnostics) {
1882
2088
  output.retrievalDiagnostics = {
@@ -1986,15 +2192,17 @@ function createUnifiedSearch(deps = {}) {
1986
2192
  evidence: [],
1987
2193
  count: 0,
1988
2194
  warnings: peerScope.warnings,
2195
+ sourceStates: [],
1989
2196
  answer: ""
1990
2197
  };
1991
2198
  return empty;
1992
2199
  }
1993
- const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge"] : ["raw", "insight", "knowledge"];
2200
+ const tiers = input.tiers ? [...input.tiers] : wantsSynthesis ? ["summary", "raw", "insight", "knowledge", "workspace"] : ["raw", "insight", "knowledge", "workspace"];
1994
2201
  const wantsSummaryTier = tiers.includes("summary");
1995
2202
  const wantsRawTier = tiers.includes("raw");
1996
2203
  const wantsInsightTier = tiers.includes("insight");
1997
2204
  const wantsKnowledgeTier = tiers.includes("knowledge");
2205
+ const wantsWorkspaceTier = tiers.includes("workspace");
1998
2206
  const warnings = [...peerScope.warnings];
1999
2207
  const limit = clampUnifiedMemorySearchLimit(input.limit);
2000
2208
  const threshold = clampUnifiedMemorySearchThreshold(input.threshold);
@@ -2002,11 +2210,13 @@ function createUnifiedSearch(deps = {}) {
2002
2210
  let sources = input.sources ? [...input.sources] : [];
2003
2211
  let unifiedReasoning;
2004
2212
  let retrievalDiagnostics;
2005
- if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier) {
2213
+ let sourceStates = [];
2214
+ if (wantsRawTier || wantsInsightTier || wantsKnowledgeTier || wantsWorkspaceTier) {
2006
2215
  const inferredSources = sources.length > 0 ? sources : [
2007
2216
  ...wantsRawTier ? ["memory"] : [],
2008
2217
  ...wantsInsightTier ? ["insights"] : [],
2009
- ...wantsKnowledgeTier ? ["knowledge"] : []
2218
+ ...wantsKnowledgeTier ? ["knowledge"] : [],
2219
+ ...wantsWorkspaceTier ? ["workspace"] : []
2010
2220
  ];
2011
2221
  const unified = await searchUnifiedMemoryWithRuntime(
2012
2222
  {
@@ -2019,6 +2229,7 @@ function createUnifiedSearch(deps = {}) {
2019
2229
  sources = unified.sources;
2020
2230
  unifiedReasoning = unified.reasoning;
2021
2231
  retrievalDiagnostics = unified.retrievalDiagnostics;
2232
+ sourceStates = unified.sourceStates;
2022
2233
  warnings.push(...unified.warnings);
2023
2234
  }
2024
2235
  let summaryHits = [];
@@ -2057,6 +2268,17 @@ function createUnifiedSearch(deps = {}) {
2057
2268
  source: mapHitToEvidenceSource(hit),
2058
2269
  snippet: hit.content,
2059
2270
  score: hit.similarity,
2271
+ provenance: {
2272
+ citation: hit.provenance?.citation ?? null,
2273
+ origin: hit.provenance?.origin ?? null,
2274
+ businessReference: hit.provenance?.businessReference ?? null,
2275
+ version: hit.provenance?.version ?? hit.provenance?.citation?.version_id ?? null,
2276
+ contentHash: hit.provenance?.contentHash ?? hit.provenance?.citation?.content_hash ?? null,
2277
+ scope: hit.provenance?.scope ?? null,
2278
+ derivedFrom: hit.provenance?.derivedFrom ?? null,
2279
+ confirmation: hit.provenance?.confirmation ?? null,
2280
+ freshness: hit.provenance?.freshness ?? null
2281
+ },
2060
2282
  timestamp: getCandidateTimestamp(hit.metadata),
2061
2283
  metadata: hit.metadata
2062
2284
  }));
@@ -2066,7 +2288,8 @@ function createUnifiedSearch(deps = {}) {
2066
2288
  results: hits,
2067
2289
  evidence,
2068
2290
  count: hits.length,
2069
- warnings
2291
+ warnings,
2292
+ sourceStates
2070
2293
  };
2071
2294
  if (unifiedReasoning) {
2072
2295
  base.reasoning = unifiedReasoning;
@@ -2089,8 +2312,16 @@ function createUnifiedSearch(deps = {}) {
2089
2312
  return base;
2090
2313
  }
2091
2314
  async function search(input, runtimeContext) {
2315
+ deps.signal?.throwIfAborted();
2316
+ if (input.sources !== void 0) normalizeUnifiedMemorySearchSources(input.sources);
2092
2317
  const resolvedRuntime = resolveSearchRuntimeContext(input, runtimeContext);
2093
- return searchWithResolvedRuntime(input, resolvedRuntime);
2318
+ const runSearch = boundedSearchOperation(() => searchWithResolvedRuntime(input, resolvedRuntime), {
2319
+ signal: deps.signal,
2320
+ sourceTimeoutMs: deps.searchTimeoutMs ?? 3e4
2321
+ });
2322
+ const result = await runSearch();
2323
+ deps.signal?.throwIfAborted();
2324
+ return result;
2094
2325
  }
2095
2326
  return {
2096
2327
  search,
@@ -2205,6 +2436,7 @@ function selectSessionNeighborResults(results, options, limit) {
2205
2436
  function mergeAcrossSources(input) {
2206
2437
  const memorySubs = input.memorySubs ?? { semantic: [], lexical: [] };
2207
2438
  const lists = buildChannelLists(memorySubs, input.insightHits, input.knowledgeHits);
2439
+ if (input.workspaceHits.length) lists.push({ name: "workspace", hits: input.workspaceHits });
2208
2440
  if (input.strategy !== "rrf") {
2209
2441
  const seen = /* @__PURE__ */ new Map();
2210
2442
  for (const hit of [
@@ -2213,7 +2445,8 @@ function mergeAcrossSources(input) {
2213
2445
  ...memorySubs.planner ?? [],
2214
2446
  ...memorySubs.hybrid ?? [],
2215
2447
  ...input.insightHits,
2216
- ...input.knowledgeHits
2448
+ ...input.knowledgeHits,
2449
+ ...input.workspaceHits
2217
2450
  ]) {
2218
2451
  const key = `${hit.type}::${hit.id}`;
2219
2452
  const existing = seen.get(key);
@@ -2224,7 +2457,10 @@ function mergeAcrossSources(input) {
2224
2457
  const other = preferred === hit ? existing : hit;
2225
2458
  seen.set(
2226
2459
  key,
2227
- preferred.type === "memory" && other.type === "memory" ? mergeMatchedEvidence(preferred, other) : preferred
2460
+ mergeSearchResultProvenance(
2461
+ preferred.type === "memory" && other.type === "memory" ? mergeMatchedEvidence(preferred, other) : preferred,
2462
+ other
2463
+ )
2228
2464
  );
2229
2465
  }
2230
2466
  }
@@ -2240,7 +2476,8 @@ function mergeAcrossSources(input) {
2240
2476
  ...memorySubs.hybrid ?? [],
2241
2477
  ...memorySubs.entity ?? [],
2242
2478
  ...input.insightHits,
2243
- ...input.knowledgeHits
2479
+ ...input.knowledgeHits,
2480
+ ...input.workspaceHits
2244
2481
  ];
2245
2482
  const merged = mergeUnifiedMemorySearchResultsRrf(
2246
2483
  lists.length > 0 ? lists : [{ name: "memory-semantic", hits: all }],
@@ -2335,7 +2572,8 @@ async function applyReflectedPlan(deps, store, input, _logger) {
2335
2572
  source: item.source ?? "raw",
2336
2573
  snippet: item.snippet,
2337
2574
  score: item.score,
2338
- timestamp: item.timestamp
2575
+ timestamp: item.timestamp,
2576
+ provenance: item.provenance
2339
2577
  }));
2340
2578
  const records = evidence.map((item) => toMemoryEvidenceRecord(item, input.userId));
2341
2579
  const applyInput = {
@@ -3084,10 +3322,16 @@ async function startHttpServer(options = {}) {
3084
3322
  }
3085
3323
  const wantsSynthesis = typeof body.synthesize === "boolean" ? body.synthesize : typeof body.synthesize === "object" && body.synthesize !== null;
3086
3324
  const responseSchema = typeof body.synthesize === "object" && body.synthesize !== null ? body.synthesize.responseSchema ?? body.responseSchema : body.responseSchema;
3325
+ let sources;
3326
+ try {
3327
+ if (body.sources !== void 0) sources = normalizeUnifiedMemorySearchSources(body.sources);
3328
+ } catch (error) {
3329
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
3330
+ }
3087
3331
  const input = {
3088
3332
  userId,
3089
3333
  query,
3090
- sources: Array.isArray(body.sources) ? body.sources : void 0,
3334
+ sources,
3091
3335
  tiers: Array.isArray(body.tiers) ? body.tiers : void 0,
3092
3336
  limit: typeof body.limit === "number" ? body.limit : void 0,
3093
3337
  candidateLimit: typeof body.candidateLimit === "number" ? body.candidateLimit : void 0,