@anuma/sdk 3.11.1 → 3.11.3

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.
@@ -4574,6 +4574,11 @@ function applyMMR(candidates, k, lambda = 0.5) {
4574
4574
  return selected;
4575
4575
  }
4576
4576
 
4577
+ // src/lib/memory/recallConstants.ts
4578
+ var RECALL_TOOL_NAME = "recall_memory";
4579
+ var RECALL_MAX_LIMIT = 50;
4580
+ var EMBEDDINGS_DEGRADED_EMPTY = "Semantic memory search is temporarily unavailable, so only keyword matching ran and it found no matches. Do not conclude the user has no such memory \u2014 say the memory lookup was degraded, or retry with different keywords.";
4581
+
4577
4582
  // src/lib/memory/recency.ts
4578
4583
  var DAY_MS = 1e3 * 60 * 60 * 24;
4579
4584
  var DEFAULT_PER_YEAR_DECAY = 0.2;
@@ -5556,6 +5561,24 @@ async function eagerEmbedContent(content, embeddingOptions, cache, vaultCtx, mem
5556
5561
  );
5557
5562
  }
5558
5563
  }
5564
+ async function embedQueryOrDegrade(query, embeddingOptions, onDegraded) {
5565
+ try {
5566
+ const embedding = await generateEmbedding(query, embeddingOptions);
5567
+ if (embedding.length === 0) {
5568
+ getLogger().warn(
5569
+ "memoryVault: query embedding came back empty \u2014 falling back to BM25-only ranking for this search"
5570
+ );
5571
+ onDegraded?.();
5572
+ }
5573
+ return embedding;
5574
+ } catch (err) {
5575
+ getLogger().warn(
5576
+ "memoryVault: query embedding failed \u2014 falling back to BM25-only ranking for this search: " + (err instanceof Error ? err.message : String(err))
5577
+ );
5578
+ onDegraded?.();
5579
+ return [];
5580
+ }
5581
+ }
5559
5582
  function admitVaultProjections(queryEmbedding, vectored, k) {
5560
5583
  return vectored.map((it) => ({ it, score: cosineSimilarity(queryEmbedding, it.embedding) })).sort((a, b) => b.score - a.score || b.it.updatedAt.getTime() - a.it.updatedAt.getTime()).slice(0, k).map((s) => s.it.uniqueId);
5561
5584
  }
@@ -5567,23 +5590,36 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
5567
5590
  const hydrateOpts = queryOpts.includeArchived ? { includeArchived: true } : void 0;
5568
5591
  const vaultSize = keys.length;
5569
5592
  if (keys.length === 0) {
5570
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize: 0 };
5593
+ return {
5594
+ memories: [],
5595
+ embeddedItems: [],
5596
+ queryEmbedding: [],
5597
+ vaultSize: 0,
5598
+ laneEmbedFailed: false
5599
+ };
5571
5600
  }
5572
- const queryEmbedding = await generateEmbedding(query, embeddingOptions);
5601
+ const queryEmbedding = await embedQueryOrDegrade(
5602
+ query,
5603
+ embeddingOptions,
5604
+ opts.onEmbeddingDegraded
5605
+ );
5606
+ const embeddingsDegraded = queryEmbedding.length === 0;
5573
5607
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
5574
5608
  const vectored = [];
5575
5609
  const missIds = [];
5576
5610
  const noVectorIds = [];
5577
5611
  const keyById = new Map(keys.map((k2) => [k2.uniqueId, k2]));
5578
- for (const k2 of keys) {
5579
- const cached = cache.get(k2.uniqueId);
5580
- if (cached && cached.length === queryEmbedding.length) {
5581
- vectored.push({ uniqueId: k2.uniqueId, embedding: cached, updatedAt: k2.updatedAt });
5582
- continue;
5612
+ if (!embeddingsDegraded) {
5613
+ for (const k2 of keys) {
5614
+ const cached = cache.get(k2.uniqueId);
5615
+ if (cached && cached.length === queryEmbedding.length) {
5616
+ vectored.push({ uniqueId: k2.uniqueId, embedding: cached, updatedAt: k2.updatedAt });
5617
+ continue;
5618
+ }
5619
+ const modelCompatible = (k2.embeddingModel ?? currentModel) === currentModel;
5620
+ if (modelCompatible) missIds.push(k2.uniqueId);
5621
+ else noVectorIds.push(k2.uniqueId);
5583
5622
  }
5584
- const modelCompatible = (k2.embeddingModel ?? currentModel) === currentModel;
5585
- if (modelCompatible) missIds.push(k2.uniqueId);
5586
- else noVectorIds.push(k2.uniqueId);
5587
5623
  }
5588
5624
  if (missIds.length > 0) {
5589
5625
  const rows = await getVaultEmbeddingsByIdsOp(vaultCtx, missIds, hydrateOpts);
@@ -5604,6 +5640,7 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
5604
5640
  }
5605
5641
  for (const id of missIds) if (!gotVector.has(id)) noVectorIds.push(id);
5606
5642
  }
5643
+ let laneEmbedFailed = false;
5607
5644
  if (noVectorIds.length > 0) {
5608
5645
  const laneIds = noVectorIds.slice(0, opts.unembeddedCap);
5609
5646
  if (noVectorIds.length > laneIds.length) {
@@ -5615,26 +5652,44 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
5615
5652
  (m) => !isEncrypted(m.content)
5616
5653
  );
5617
5654
  if (laneRows.length > 0) {
5618
- const laneVecs = await generateEmbeddings(
5619
- laneRows.map((m) => m.content),
5620
- embeddingOptions
5621
- );
5622
- laneRows.forEach((m, i) => {
5623
- const vec = Float32Array.from(laneVecs[i]);
5624
- cache.set(m.uniqueId, vec);
5625
- vectored.push({ uniqueId: m.uniqueId, embedding: vec, updatedAt: m.updatedAt });
5626
- updateVaultMemoryEmbeddingOp(
5627
- vaultCtx,
5628
- m.uniqueId,
5629
- JSON.stringify(laneVecs[i]),
5630
- currentModel
5631
- ).catch(() => {
5655
+ let laneVecs;
5656
+ try {
5657
+ laneVecs = await generateEmbeddings(
5658
+ laneRows.map((m) => m.content),
5659
+ embeddingOptions
5660
+ );
5661
+ } catch (err) {
5662
+ laneEmbedFailed = true;
5663
+ getLogger().warn(
5664
+ `memoryVault: un-embedded lane embedding failed for ${laneRows.length} rows \u2014 they rank on BM25 alone for this search: ` + (err instanceof Error ? err.message : String(err))
5665
+ );
5666
+ }
5667
+ if (laneVecs) {
5668
+ const vecs = laneVecs;
5669
+ laneRows.forEach((m, i) => {
5670
+ const vec = Float32Array.from(vecs[i]);
5671
+ cache.set(m.uniqueId, vec);
5672
+ vectored.push({ uniqueId: m.uniqueId, embedding: vec, updatedAt: m.updatedAt });
5673
+ updateVaultMemoryEmbeddingOp(
5674
+ vaultCtx,
5675
+ m.uniqueId,
5676
+ JSON.stringify(vecs[i]),
5677
+ currentModel
5678
+ ).catch(() => {
5679
+ });
5632
5680
  });
5633
- });
5681
+ }
5634
5682
  }
5635
5683
  }
5636
5684
  const k = Math.max(opts.limit * opts.admitFactor, opts.admitFloor);
5637
5685
  const admittedIds = admitVaultProjections(queryEmbedding, vectored, k);
5686
+ if ((embeddingsDegraded || laneEmbedFailed) && admittedIds.length < k) {
5687
+ const already = new Set(admittedIds);
5688
+ for (const key of [...keys].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())) {
5689
+ if (admittedIds.length >= k) break;
5690
+ if (!already.has(key.uniqueId)) admittedIds.push(key.uniqueId);
5691
+ }
5692
+ }
5638
5693
  const admissionSet = new Set(admittedIds);
5639
5694
  if (opts.forceIncludeIds) {
5640
5695
  for (const id of opts.forceIncludeIds) {
@@ -5662,11 +5717,18 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
5662
5717
  eventTimeKind: normalizeEventTimeKind(m.eventTimeKind),
5663
5718
  factType: m.factType
5664
5719
  }));
5665
- return { memories, embeddedItems, queryEmbedding, vaultSize };
5720
+ return { memories, embeddedItems, queryEmbedding, vaultSize, laneEmbedFailed };
5666
5721
  }
5667
5722
  async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache, searchOptions) {
5668
5723
  if (!query || typeof query !== "string") {
5669
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize: 0 };
5724
+ return {
5725
+ memories: [],
5726
+ embeddedItems: [],
5727
+ queryEmbedding: [],
5728
+ vaultSize: 0,
5729
+ embeddingsUnavailable: false,
5730
+ embeddingFailure: false
5731
+ };
5670
5732
  }
5671
5733
  const limit = searchOptions?.limit ?? 5;
5672
5734
  const scopes = searchOptions?.scopes;
@@ -5683,6 +5745,12 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5683
5745
  let embeddedItems;
5684
5746
  let queryEmbedding;
5685
5747
  let vaultSize;
5748
+ let embeddingsUnavailable = false;
5749
+ let embeddingFailure = false;
5750
+ const onEmbeddingDegraded = () => {
5751
+ embeddingsUnavailable = true;
5752
+ embeddingFailure = true;
5753
+ };
5686
5754
  if (searchOptions?.decryptLast) {
5687
5755
  const forceIncludeIds = [
5688
5756
  ...searchOptions.entityRanking ?? [],
@@ -5693,14 +5761,30 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5693
5761
  admitFactor: ADMIT_FACTOR,
5694
5762
  admitFloor: ADMIT_FLOOR,
5695
5763
  unembeddedCap: UNEMBEDDED_CAP,
5696
- ...forceIncludeIds.length > 0 && { forceIncludeIds }
5764
+ ...forceIncludeIds.length > 0 && { forceIncludeIds },
5765
+ onEmbeddingDegraded
5697
5766
  });
5698
5767
  if (corpus.vaultSize === 0) {
5699
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize: 0 };
5768
+ return {
5769
+ memories: [],
5770
+ embeddedItems: [],
5771
+ queryEmbedding: [],
5772
+ vaultSize: 0,
5773
+ embeddingsUnavailable: false,
5774
+ embeddingFailure: false
5775
+ };
5700
5776
  }
5701
5777
  ({ memories, embeddedItems, queryEmbedding, vaultSize } = corpus);
5778
+ if (corpus.laneEmbedFailed) embeddingFailure = true;
5702
5779
  if (memories.length === 0) {
5703
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize };
5780
+ return {
5781
+ memories: [],
5782
+ embeddedItems: [],
5783
+ queryEmbedding: [],
5784
+ vaultSize,
5785
+ embeddingsUnavailable,
5786
+ embeddingFailure
5787
+ };
5704
5788
  }
5705
5789
  } else {
5706
5790
  const loaded = await getAllVaultMemoriesOp(
@@ -5715,14 +5799,21 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5715
5799
  );
5716
5800
  }
5717
5801
  if (memories.length === 0) {
5718
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize: loaded.length };
5802
+ return {
5803
+ memories: [],
5804
+ embeddedItems: [],
5805
+ queryEmbedding: [],
5806
+ vaultSize: loaded.length,
5807
+ embeddingsUnavailable: false,
5808
+ embeddingFailure: false
5809
+ };
5719
5810
  }
5720
- queryEmbedding = await generateEmbedding(query, embeddingOptions);
5811
+ queryEmbedding = await embedQueryOrDegrade(query, embeddingOptions, onEmbeddingDegraded);
5721
5812
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
5722
5813
  const uncachedTexts = [];
5723
5814
  const uncachedIndices = [];
5724
5815
  let staleReembedCount = 0;
5725
- for (let i = 0; i < memories.length; i++) {
5816
+ for (let i = 0; queryEmbedding.length > 0 && i < memories.length; i++) {
5726
5817
  const content = memories[i].content;
5727
5818
  const memoryId = memories[i].uniqueId;
5728
5819
  const cached = cache.get(memoryId);
@@ -5750,20 +5841,30 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5750
5841
  );
5751
5842
  }
5752
5843
  if (uncachedTexts.length > 0) {
5753
- const newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
5754
- for (let j = 0; j < uncachedTexts.length; j++) {
5755
- cache.set(memories[uncachedIndices[j]].uniqueId, Float32Array.from(newEmbeddings[j]));
5756
- updateVaultMemoryEmbeddingOp(
5757
- vaultCtx,
5758
- memories[uncachedIndices[j]].uniqueId,
5759
- JSON.stringify(newEmbeddings[j]),
5760
- currentModel
5761
- ).catch(
5762
- // Silently swallow – SDK must not use console.*; embedding will be retried on next search
5763
- () => {
5764
- }
5844
+ let newEmbeddings;
5845
+ try {
5846
+ newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
5847
+ } catch (err) {
5848
+ embeddingFailure = true;
5849
+ getLogger().warn(
5850
+ `memoryVault: (re)embedding ${uncachedTexts.length} vault rows failed \u2014 they rank on BM25 alone for this search: ` + (err instanceof Error ? err.message : String(err))
5765
5851
  );
5766
5852
  }
5853
+ if (newEmbeddings) {
5854
+ for (let j = 0; j < uncachedTexts.length; j++) {
5855
+ cache.set(memories[uncachedIndices[j]].uniqueId, Float32Array.from(newEmbeddings[j]));
5856
+ updateVaultMemoryEmbeddingOp(
5857
+ vaultCtx,
5858
+ memories[uncachedIndices[j]].uniqueId,
5859
+ JSON.stringify(newEmbeddings[j]),
5860
+ currentModel
5861
+ ).catch(
5862
+ // Silently swallow – SDK must not use console.*; embedding will be retried on next search
5863
+ () => {
5864
+ }
5865
+ );
5866
+ }
5867
+ }
5767
5868
  }
5768
5869
  embeddedItems = memories.map((m) => ({
5769
5870
  id: m.uniqueId,
@@ -5780,12 +5881,22 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5780
5881
  factType: m.factType
5781
5882
  }));
5782
5883
  }
5783
- return { memories, embeddedItems, queryEmbedding, vaultSize };
5884
+ if (!embeddingsUnavailable && !embeddedItems.some((it) => it.embedding.length > 0)) {
5885
+ embeddingsUnavailable = true;
5886
+ }
5887
+ return {
5888
+ memories,
5889
+ embeddedItems,
5890
+ queryEmbedding,
5891
+ vaultSize,
5892
+ embeddingsUnavailable,
5893
+ embeddingFailure
5894
+ };
5784
5895
  }
5785
5896
  async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, searchOptions) {
5786
5897
  const limit = searchOptions?.limit ?? 5;
5787
5898
  const minSimilarity = searchOptions?.minSimilarity ?? 0.1;
5788
- const { memories, embeddedItems, queryEmbedding, vaultSize } = prepared;
5899
+ const { memories, embeddedItems, queryEmbedding, vaultSize, embeddingsUnavailable } = prepared;
5789
5900
  if (queryEmbedding.length > 0) {
5790
5901
  const mismatched = embeddedItems.filter(
5791
5902
  (it) => it.embedding.length > 0 && it.embedding.length !== queryEmbedding.length
@@ -5822,7 +5933,8 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
5822
5933
  results: results2,
5823
5934
  vaultSize: out.vaultSize,
5824
5935
  reranked: out.reranked ?? false,
5825
- hadV2Head: out.hadV2Head ?? false
5936
+ hadV2Head: out.hadV2Head ?? false,
5937
+ embeddingsUnavailable
5826
5938
  };
5827
5939
  };
5828
5940
  const rerankStats = { applied: false };
@@ -5845,36 +5957,46 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
5845
5957
  ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
5846
5958
  ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
5847
5959
  };
5848
- if (useFusion && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
5960
+ if (useFusion && !embeddingsUnavailable && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
5849
5961
  const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
5850
5962
  if (decomp.mode === "composite") {
5851
- const subEmbeddings = await generateEmbeddings(decomp.subQueries, embeddingOptions);
5852
- const subQueries = decomp.subQueries.map((sq, i) => ({
5853
- query: sq,
5854
- embedding: subEmbeddings[i]
5855
- }));
5856
- const v2HeadStats = { hadResults: false };
5857
- const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
5858
- limit,
5859
- minSimilarity,
5860
- rerank: !!searchOptions.rerank,
5861
- ...searchOptions.rerankTopN !== void 0 && {
5862
- rerankTopN: searchOptions.rerankTopN
5863
- },
5864
- ...searchOptions.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
5865
- ...searchOptions.mmr !== void 0 && { mmr: searchOptions.mmr },
5866
- ...tuning,
5867
- ...searchOptions.entityRanking && { entityRanking: searchOptions.entityRanking },
5868
- ...searchOptions.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
5869
- rerankStats,
5870
- v2HeadStats
5871
- });
5872
- return stampTimestamps({
5873
- results: composite,
5874
- vaultSize,
5875
- reranked: rerankStats.applied,
5876
- hadV2Head: v2HeadStats.hadResults
5877
- });
5963
+ let subEmbeddings;
5964
+ try {
5965
+ subEmbeddings = await generateEmbeddings(decomp.subQueries, embeddingOptions);
5966
+ } catch (err) {
5967
+ getLogger().warn(
5968
+ "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
5969
+ );
5970
+ subEmbeddings = [];
5971
+ }
5972
+ if (subEmbeddings.length > 0) {
5973
+ const subQueries = decomp.subQueries.map((sq, i) => ({
5974
+ query: sq,
5975
+ embedding: subEmbeddings[i]
5976
+ }));
5977
+ const v2HeadStats = { hadResults: false };
5978
+ const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
5979
+ limit,
5980
+ minSimilarity,
5981
+ rerank: !!searchOptions.rerank,
5982
+ ...searchOptions.rerankTopN !== void 0 && {
5983
+ rerankTopN: searchOptions.rerankTopN
5984
+ },
5985
+ ...searchOptions.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
5986
+ ...searchOptions.mmr !== void 0 && { mmr: searchOptions.mmr },
5987
+ ...tuning,
5988
+ ...searchOptions.entityRanking && { entityRanking: searchOptions.entityRanking },
5989
+ ...searchOptions.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
5990
+ rerankStats,
5991
+ v2HeadStats
5992
+ });
5993
+ return stampTimestamps({
5994
+ results: composite,
5995
+ vaultSize,
5996
+ reranked: rerankStats.applied,
5997
+ hadV2Head: v2HeadStats.hadResults
5998
+ });
5999
+ }
5878
6000
  }
5879
6001
  }
5880
6002
  if (useFusion && searchOptions?.rerank) {
@@ -5925,7 +6047,14 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
5925
6047
  }
5926
6048
  async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, cache, searchOptions) {
5927
6049
  if (!query || typeof query !== "string") {
5928
- return { results: [], vaultSize: 0, reranked: false, hadV2Head: false };
6050
+ return {
6051
+ results: [],
6052
+ vaultSize: 0,
6053
+ reranked: false,
6054
+ hadV2Head: false,
6055
+ embeddingsUnavailable: false,
6056
+ rankedOnCosine: false
6057
+ };
5929
6058
  }
5930
6059
  const prepared = await prepareVaultCandidates(
5931
6060
  query,
@@ -5935,12 +6064,32 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
5935
6064
  searchOptions
5936
6065
  );
5937
6066
  if (prepared.vaultSize === 0) {
5938
- return { results: [], vaultSize: 0, reranked: false, hadV2Head: false };
6067
+ return {
6068
+ results: [],
6069
+ vaultSize: 0,
6070
+ reranked: false,
6071
+ hadV2Head: false,
6072
+ embeddingsUnavailable: prepared.embeddingsUnavailable,
6073
+ rankedOnCosine: false
6074
+ };
5939
6075
  }
5940
6076
  if (prepared.memories.length === 0) {
5941
- return { results: [], vaultSize: prepared.vaultSize, reranked: false, hadV2Head: false };
6077
+ return {
6078
+ results: [],
6079
+ vaultSize: prepared.vaultSize,
6080
+ reranked: false,
6081
+ hadV2Head: false,
6082
+ embeddingsUnavailable: prepared.embeddingsUnavailable,
6083
+ rankedOnCosine: false
6084
+ };
5942
6085
  }
5943
- return rankPreparedVaultCandidates(query, prepared, embeddingOptions, searchOptions);
6086
+ const ranked = await rankPreparedVaultCandidates(
6087
+ query,
6088
+ prepared,
6089
+ embeddingOptions,
6090
+ searchOptions
6091
+ );
6092
+ return { ...ranked, rankedOnCosine: !ranked.embeddingsUnavailable };
5944
6093
  }
5945
6094
  async function searchVaultMemories(query, vaultCtx, embeddingOptions, cache, searchOptions) {
5946
6095
  const { results } = await searchVaultMemoriesWithSize(
@@ -5952,6 +6101,7 @@ async function searchVaultMemories(query, vaultCtx, embeddingOptions, cache, sea
5952
6101
  );
5953
6102
  return results;
5954
6103
  }
6104
+ var EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL = "Memory search is temporarily unavailable \u2014 the semantic lookup failed and this search mode has no keyword fallback, so no memory search ran at all. Do not conclude the user has no such memory; say the memory lookup was unavailable.";
5955
6105
  function formatVaultHits(hits) {
5956
6106
  return hits.map((h, i) => `[${i + 1}] (id: ${h.id}, similarity: ${h.score.toFixed(2)})
5957
6107
  ${h.content}`).join("\n\n");
@@ -6014,26 +6164,36 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6014
6164
  const budget = searchOptions?.decompose === "llm" && searchOptions.decomposeOptions ? "high" : searchOptions?.rerank ? "mid" : "low";
6015
6165
  const folderId = searchOptions?.folderId ?? argFolderId;
6016
6166
  if (searchOptions?.useFusion === false) {
6017
- const legacy = await searchVaultMemories(query, vaultCtx, embeddingOptions, cache, {
6018
- limit: requestLimit,
6019
- minSimilarity,
6020
- useFusion: false,
6021
- ...tuningForward,
6022
- ...folderId !== void 0 && { folderId },
6023
- ...searchOptions?.scopes && { scopes: searchOptions.scopes }
6024
- });
6167
+ const { results: legacy, embeddingsUnavailable } = await searchVaultMemoriesWithSize(
6168
+ query,
6169
+ vaultCtx,
6170
+ embeddingOptions,
6171
+ cache,
6172
+ {
6173
+ limit: requestLimit,
6174
+ minSimilarity,
6175
+ useFusion: false,
6176
+ ...tuningForward,
6177
+ ...folderId !== void 0 && { folderId },
6178
+ ...searchOptions?.scopes && { scopes: searchOptions.scopes }
6179
+ }
6180
+ );
6025
6181
  if (legacy.length === 0) {
6026
- return "No relevant memories found in the vault.";
6182
+ return embeddingsUnavailable ? EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL : "No relevant memories found in the vault.";
6027
6183
  }
6028
6184
  return formatVaultHits(
6029
6185
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
6030
6186
  );
6031
6187
  }
6032
- const { recall: recall2 } = await import("./recall-Q5V5DN4L.mjs");
6188
+ const { recall: recall2 } = await import("./recall-DOFCEH2I.mjs");
6189
+ let recallDegraded = [];
6033
6190
  const result = await recall2(
6034
6191
  query,
6035
6192
  { vaultCtx, embeddingOptions, vaultCache: cache },
6036
6193
  {
6194
+ onDiagnostics: (d) => {
6195
+ recallDegraded = d.degraded;
6196
+ },
6037
6197
  types: ["fact"],
6038
6198
  limit: requestLimit,
6039
6199
  minScore: minSimilarity,
@@ -6054,7 +6214,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6054
6214
  return "The memory vault is empty. No memories have been saved yet.";
6055
6215
  }
6056
6216
  if (result.memories.length === 0) {
6057
- return "No relevant memories found in the vault.";
6217
+ return recallDegraded.includes("embeddings-unavailable") ? EMBEDDINGS_DEGRADED_EMPTY : "No relevant memories found in the vault.";
6058
6218
  }
6059
6219
  const formatted = formatVaultHits(
6060
6220
  result.memories.map((m) => ({
@@ -6609,6 +6769,9 @@ async function recall(query, ctx, options = {}) {
6609
6769
  let vaultSize;
6610
6770
  let didRerank = false;
6611
6771
  let hadV2Head = false;
6772
+ let embeddingsUnavailable = false;
6773
+ let chunkEmbedFailed = false;
6774
+ let factLaneRankedOnCosine = false;
6612
6775
  const emitDiagnostics = (candidateCount) => {
6613
6776
  const cb = options.onDiagnostics;
6614
6777
  if (!cb) return;
@@ -6617,6 +6780,7 @@ async function recall(query, ctx, options = {}) {
6617
6780
  degraded.push("rerank-unavailable");
6618
6781
  }
6619
6782
  if (flags.decompose && !decomposeAvailable) degraded.push("decompose-unavailable");
6783
+ if (embeddingsUnavailable) degraded.push("embeddings-unavailable");
6620
6784
  const diagnostics = {
6621
6785
  usedBudget,
6622
6786
  reranked: didRerank,
@@ -6647,7 +6811,26 @@ async function recall(query, ctx, options = {}) {
6647
6811
  const graphRefiner = flags.traverse && options.graphRefine && options.decomposeOptions ? createLlmNeighborRefiner(options.decomposeOptions) : void 0;
6648
6812
  const prepStart = nowMs();
6649
6813
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
6650
- needsChunkEmbedding ? generateEmbedding(query, ctx.embeddingOptions) : Promise.resolve(void 0),
6814
+ // The chunk lane is cosine-only — `searchChunksOp` needs a real vector, and
6815
+ // there is no lexical fallback for it — so an embeddings outage must SKIP the
6816
+ // lane, not reject this shared Promise.all and take the primary fact lane
6817
+ // (which BM25 can still serve) down with it. Mirrors safeLane's posture.
6818
+ needsChunkEmbedding ? generateEmbedding(query, ctx.embeddingOptions).then((vec) => {
6819
+ if (vec.length === 0) {
6820
+ getLogger().warn(
6821
+ "[memory/recall] chunk-lane query embedding came back empty; skipping the chunk lane"
6822
+ );
6823
+ chunkEmbedFailed = true;
6824
+ return void 0;
6825
+ }
6826
+ return vec;
6827
+ }).catch((err) => {
6828
+ getLogger().warn(
6829
+ `[memory/recall] chunk-lane query embedding failed; skipping the chunk lane: ${err instanceof Error ? err.message : String(err)}`
6830
+ );
6831
+ chunkEmbedFailed = true;
6832
+ return void 0;
6833
+ }) : Promise.resolve(void 0),
6651
6834
  // The graph + temporal lanes are AUXILIARY (RRF side-signals). A transient
6652
6835
  // WatermelonDB throw in either must NOT reject this Promise.all and take
6653
6836
  // PRIMARY cosine/BM25 recall down with it — degrade the failing lane to an
@@ -6672,7 +6855,9 @@ async function recall(query, ctx, options = {}) {
6672
6855
  results,
6673
6856
  vaultSize: size,
6674
6857
  reranked,
6675
- hadV2Head: v2Head
6858
+ hadV2Head: v2Head,
6859
+ embeddingsUnavailable: factEmbeddingsUnavailable,
6860
+ rankedOnCosine: factRankedOnCosine
6676
6861
  } = await searchVaultMemoriesWithSize(
6677
6862
  query,
6678
6863
  ctx.vaultCtx,
@@ -6729,8 +6914,11 @@ async function recall(query, ctx, options = {}) {
6729
6914
  vaultSize = size;
6730
6915
  didRerank = reranked;
6731
6916
  hadV2Head = v2Head;
6917
+ if (factEmbeddingsUnavailable) embeddingsUnavailable = true;
6918
+ factLaneRankedOnCosine = factRankedOnCosine;
6732
6919
  factLaneMs = nowMs() - factStart;
6733
6920
  }
6921
+ if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
6734
6922
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
6735
6923
  const chunkStart = nowMs();
6736
6924
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
@@ -7070,6 +7258,9 @@ export {
7070
7258
  restoreVaultMemoryOp,
7071
7259
  hardDeleteDecayedOp,
7072
7260
  updateVaultMemoryEmbeddingOp,
7261
+ RECALL_TOOL_NAME,
7262
+ RECALL_MAX_LIMIT,
7263
+ EMBEDDINGS_DEGRADED_EMPTY,
7073
7264
  RerankerUnavailableError,
7074
7265
  isRerankerAvailable,
7075
7266
  DEFAULT_VAULT_CACHE_SIZE,