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