@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.
@@ -4273,6 +4273,11 @@ function applyMMR(candidates, k, lambda = 0.5) {
4273
4273
  return selected;
4274
4274
  }
4275
4275
 
4276
+ // src/lib/memory/recallConstants.ts
4277
+ var RECALL_TOOL_NAME = "recall_memory";
4278
+ var RECALL_MAX_LIMIT = 50;
4279
+ 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.";
4280
+
4276
4281
  // src/lib/memory/recency.ts
4277
4282
  var DAY_MS = 1e3 * 60 * 60 * 24;
4278
4283
  var DEFAULT_PER_YEAR_DECAY = 0.2;
@@ -5255,6 +5260,24 @@ async function eagerEmbedContent(content, embeddingOptions, cache, vaultCtx, mem
5255
5260
  );
5256
5261
  }
5257
5262
  }
5263
+ async function embedQueryOrDegrade(query, embeddingOptions, onDegraded) {
5264
+ try {
5265
+ const embedding = await generateEmbedding(query, embeddingOptions);
5266
+ if (embedding.length === 0) {
5267
+ getLogger().warn(
5268
+ "memoryVault: query embedding came back empty \u2014 falling back to BM25-only ranking for this search"
5269
+ );
5270
+ onDegraded?.();
5271
+ }
5272
+ return embedding;
5273
+ } catch (err) {
5274
+ getLogger().warn(
5275
+ "memoryVault: query embedding failed \u2014 falling back to BM25-only ranking for this search: " + (err instanceof Error ? err.message : String(err))
5276
+ );
5277
+ onDegraded?.();
5278
+ return [];
5279
+ }
5280
+ }
5258
5281
  function admitVaultProjections(queryEmbedding, vectored, k) {
5259
5282
  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);
5260
5283
  }
@@ -5266,23 +5289,36 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
5266
5289
  const hydrateOpts = queryOpts.includeArchived ? { includeArchived: true } : void 0;
5267
5290
  const vaultSize = keys.length;
5268
5291
  if (keys.length === 0) {
5269
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize: 0 };
5292
+ return {
5293
+ memories: [],
5294
+ embeddedItems: [],
5295
+ queryEmbedding: [],
5296
+ vaultSize: 0,
5297
+ laneEmbedFailed: false
5298
+ };
5270
5299
  }
5271
- const queryEmbedding = await generateEmbedding(query, embeddingOptions);
5300
+ const queryEmbedding = await embedQueryOrDegrade(
5301
+ query,
5302
+ embeddingOptions,
5303
+ opts.onEmbeddingDegraded
5304
+ );
5305
+ const embeddingsDegraded = queryEmbedding.length === 0;
5272
5306
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
5273
5307
  const vectored = [];
5274
5308
  const missIds = [];
5275
5309
  const noVectorIds = [];
5276
5310
  const keyById = new Map(keys.map((k2) => [k2.uniqueId, k2]));
5277
- for (const k2 of keys) {
5278
- const cached = cache.get(k2.uniqueId);
5279
- if (cached && cached.length === queryEmbedding.length) {
5280
- vectored.push({ uniqueId: k2.uniqueId, embedding: cached, updatedAt: k2.updatedAt });
5281
- continue;
5311
+ if (!embeddingsDegraded) {
5312
+ for (const k2 of keys) {
5313
+ const cached = cache.get(k2.uniqueId);
5314
+ if (cached && cached.length === queryEmbedding.length) {
5315
+ vectored.push({ uniqueId: k2.uniqueId, embedding: cached, updatedAt: k2.updatedAt });
5316
+ continue;
5317
+ }
5318
+ const modelCompatible = (k2.embeddingModel ?? currentModel) === currentModel;
5319
+ if (modelCompatible) missIds.push(k2.uniqueId);
5320
+ else noVectorIds.push(k2.uniqueId);
5282
5321
  }
5283
- const modelCompatible = (k2.embeddingModel ?? currentModel) === currentModel;
5284
- if (modelCompatible) missIds.push(k2.uniqueId);
5285
- else noVectorIds.push(k2.uniqueId);
5286
5322
  }
5287
5323
  if (missIds.length > 0) {
5288
5324
  const rows = await getVaultEmbeddingsByIdsOp(vaultCtx, missIds, hydrateOpts);
@@ -5303,6 +5339,7 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
5303
5339
  }
5304
5340
  for (const id of missIds) if (!gotVector.has(id)) noVectorIds.push(id);
5305
5341
  }
5342
+ let laneEmbedFailed = false;
5306
5343
  if (noVectorIds.length > 0) {
5307
5344
  const laneIds = noVectorIds.slice(0, opts.unembeddedCap);
5308
5345
  if (noVectorIds.length > laneIds.length) {
@@ -5314,26 +5351,44 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
5314
5351
  (m) => !isEncrypted(m.content)
5315
5352
  );
5316
5353
  if (laneRows.length > 0) {
5317
- const laneVecs = await generateEmbeddings(
5318
- laneRows.map((m) => m.content),
5319
- embeddingOptions
5320
- );
5321
- laneRows.forEach((m, i) => {
5322
- const vec = Float32Array.from(laneVecs[i]);
5323
- cache.set(m.uniqueId, vec);
5324
- vectored.push({ uniqueId: m.uniqueId, embedding: vec, updatedAt: m.updatedAt });
5325
- updateVaultMemoryEmbeddingOp(
5326
- vaultCtx,
5327
- m.uniqueId,
5328
- JSON.stringify(laneVecs[i]),
5329
- currentModel
5330
- ).catch(() => {
5354
+ let laneVecs;
5355
+ try {
5356
+ laneVecs = await generateEmbeddings(
5357
+ laneRows.map((m) => m.content),
5358
+ embeddingOptions
5359
+ );
5360
+ } catch (err) {
5361
+ laneEmbedFailed = true;
5362
+ getLogger().warn(
5363
+ `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))
5364
+ );
5365
+ }
5366
+ if (laneVecs) {
5367
+ const vecs = laneVecs;
5368
+ laneRows.forEach((m, i) => {
5369
+ const vec = Float32Array.from(vecs[i]);
5370
+ cache.set(m.uniqueId, vec);
5371
+ vectored.push({ uniqueId: m.uniqueId, embedding: vec, updatedAt: m.updatedAt });
5372
+ updateVaultMemoryEmbeddingOp(
5373
+ vaultCtx,
5374
+ m.uniqueId,
5375
+ JSON.stringify(vecs[i]),
5376
+ currentModel
5377
+ ).catch(() => {
5378
+ });
5331
5379
  });
5332
- });
5380
+ }
5333
5381
  }
5334
5382
  }
5335
5383
  const k = Math.max(opts.limit * opts.admitFactor, opts.admitFloor);
5336
5384
  const admittedIds = admitVaultProjections(queryEmbedding, vectored, k);
5385
+ if ((embeddingsDegraded || laneEmbedFailed) && admittedIds.length < k) {
5386
+ const already = new Set(admittedIds);
5387
+ for (const key of [...keys].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())) {
5388
+ if (admittedIds.length >= k) break;
5389
+ if (!already.has(key.uniqueId)) admittedIds.push(key.uniqueId);
5390
+ }
5391
+ }
5337
5392
  const admissionSet = new Set(admittedIds);
5338
5393
  if (opts.forceIncludeIds) {
5339
5394
  for (const id of opts.forceIncludeIds) {
@@ -5361,11 +5416,18 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
5361
5416
  eventTimeKind: normalizeEventTimeKind(m.eventTimeKind),
5362
5417
  factType: m.factType
5363
5418
  }));
5364
- return { memories, embeddedItems, queryEmbedding, vaultSize };
5419
+ return { memories, embeddedItems, queryEmbedding, vaultSize, laneEmbedFailed };
5365
5420
  }
5366
5421
  async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache, searchOptions) {
5367
5422
  if (!query || typeof query !== "string") {
5368
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize: 0 };
5423
+ return {
5424
+ memories: [],
5425
+ embeddedItems: [],
5426
+ queryEmbedding: [],
5427
+ vaultSize: 0,
5428
+ embeddingsUnavailable: false,
5429
+ embeddingFailure: false
5430
+ };
5369
5431
  }
5370
5432
  const limit = searchOptions?.limit ?? 5;
5371
5433
  const scopes = searchOptions?.scopes;
@@ -5382,6 +5444,12 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5382
5444
  let embeddedItems;
5383
5445
  let queryEmbedding;
5384
5446
  let vaultSize;
5447
+ let embeddingsUnavailable = false;
5448
+ let embeddingFailure = false;
5449
+ const onEmbeddingDegraded = () => {
5450
+ embeddingsUnavailable = true;
5451
+ embeddingFailure = true;
5452
+ };
5385
5453
  if (searchOptions?.decryptLast) {
5386
5454
  const forceIncludeIds = [
5387
5455
  ...searchOptions.entityRanking ?? [],
@@ -5392,14 +5460,30 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5392
5460
  admitFactor: ADMIT_FACTOR,
5393
5461
  admitFloor: ADMIT_FLOOR,
5394
5462
  unembeddedCap: UNEMBEDDED_CAP,
5395
- ...forceIncludeIds.length > 0 && { forceIncludeIds }
5463
+ ...forceIncludeIds.length > 0 && { forceIncludeIds },
5464
+ onEmbeddingDegraded
5396
5465
  });
5397
5466
  if (corpus.vaultSize === 0) {
5398
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize: 0 };
5467
+ return {
5468
+ memories: [],
5469
+ embeddedItems: [],
5470
+ queryEmbedding: [],
5471
+ vaultSize: 0,
5472
+ embeddingsUnavailable: false,
5473
+ embeddingFailure: false
5474
+ };
5399
5475
  }
5400
5476
  ({ memories, embeddedItems, queryEmbedding, vaultSize } = corpus);
5477
+ if (corpus.laneEmbedFailed) embeddingFailure = true;
5401
5478
  if (memories.length === 0) {
5402
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize };
5479
+ return {
5480
+ memories: [],
5481
+ embeddedItems: [],
5482
+ queryEmbedding: [],
5483
+ vaultSize,
5484
+ embeddingsUnavailable,
5485
+ embeddingFailure
5486
+ };
5403
5487
  }
5404
5488
  } else {
5405
5489
  const loaded = await getAllVaultMemoriesOp(
@@ -5414,14 +5498,21 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5414
5498
  );
5415
5499
  }
5416
5500
  if (memories.length === 0) {
5417
- return { memories: [], embeddedItems: [], queryEmbedding: [], vaultSize: loaded.length };
5501
+ return {
5502
+ memories: [],
5503
+ embeddedItems: [],
5504
+ queryEmbedding: [],
5505
+ vaultSize: loaded.length,
5506
+ embeddingsUnavailable: false,
5507
+ embeddingFailure: false
5508
+ };
5418
5509
  }
5419
- queryEmbedding = await generateEmbedding(query, embeddingOptions);
5510
+ queryEmbedding = await embedQueryOrDegrade(query, embeddingOptions, onEmbeddingDegraded);
5420
5511
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
5421
5512
  const uncachedTexts = [];
5422
5513
  const uncachedIndices = [];
5423
5514
  let staleReembedCount = 0;
5424
- for (let i = 0; i < memories.length; i++) {
5515
+ for (let i = 0; queryEmbedding.length > 0 && i < memories.length; i++) {
5425
5516
  const content = memories[i].content;
5426
5517
  const memoryId = memories[i].uniqueId;
5427
5518
  const cached = cache.get(memoryId);
@@ -5449,20 +5540,30 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5449
5540
  );
5450
5541
  }
5451
5542
  if (uncachedTexts.length > 0) {
5452
- const newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
5453
- for (let j = 0; j < uncachedTexts.length; j++) {
5454
- cache.set(memories[uncachedIndices[j]].uniqueId, Float32Array.from(newEmbeddings[j]));
5455
- updateVaultMemoryEmbeddingOp(
5456
- vaultCtx,
5457
- memories[uncachedIndices[j]].uniqueId,
5458
- JSON.stringify(newEmbeddings[j]),
5459
- currentModel
5460
- ).catch(
5461
- // Silently swallow – SDK must not use console.*; embedding will be retried on next search
5462
- () => {
5463
- }
5543
+ let newEmbeddings;
5544
+ try {
5545
+ newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
5546
+ } catch (err) {
5547
+ embeddingFailure = true;
5548
+ getLogger().warn(
5549
+ `memoryVault: (re)embedding ${uncachedTexts.length} vault rows failed \u2014 they rank on BM25 alone for this search: ` + (err instanceof Error ? err.message : String(err))
5464
5550
  );
5465
5551
  }
5552
+ if (newEmbeddings) {
5553
+ for (let j = 0; j < uncachedTexts.length; j++) {
5554
+ cache.set(memories[uncachedIndices[j]].uniqueId, Float32Array.from(newEmbeddings[j]));
5555
+ updateVaultMemoryEmbeddingOp(
5556
+ vaultCtx,
5557
+ memories[uncachedIndices[j]].uniqueId,
5558
+ JSON.stringify(newEmbeddings[j]),
5559
+ currentModel
5560
+ ).catch(
5561
+ // Silently swallow – SDK must not use console.*; embedding will be retried on next search
5562
+ () => {
5563
+ }
5564
+ );
5565
+ }
5566
+ }
5466
5567
  }
5467
5568
  embeddedItems = memories.map((m) => ({
5468
5569
  id: m.uniqueId,
@@ -5479,12 +5580,22 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
5479
5580
  factType: m.factType
5480
5581
  }));
5481
5582
  }
5482
- return { memories, embeddedItems, queryEmbedding, vaultSize };
5583
+ if (!embeddingsUnavailable && !embeddedItems.some((it) => it.embedding.length > 0)) {
5584
+ embeddingsUnavailable = true;
5585
+ }
5586
+ return {
5587
+ memories,
5588
+ embeddedItems,
5589
+ queryEmbedding,
5590
+ vaultSize,
5591
+ embeddingsUnavailable,
5592
+ embeddingFailure
5593
+ };
5483
5594
  }
5484
5595
  async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, searchOptions) {
5485
5596
  const limit = searchOptions?.limit ?? 5;
5486
5597
  const minSimilarity = searchOptions?.minSimilarity ?? 0.1;
5487
- const { memories, embeddedItems, queryEmbedding, vaultSize } = prepared;
5598
+ const { memories, embeddedItems, queryEmbedding, vaultSize, embeddingsUnavailable } = prepared;
5488
5599
  if (queryEmbedding.length > 0) {
5489
5600
  const mismatched = embeddedItems.filter(
5490
5601
  (it) => it.embedding.length > 0 && it.embedding.length !== queryEmbedding.length
@@ -5521,7 +5632,8 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
5521
5632
  results: results2,
5522
5633
  vaultSize: out.vaultSize,
5523
5634
  reranked: out.reranked ?? false,
5524
- hadV2Head: out.hadV2Head ?? false
5635
+ hadV2Head: out.hadV2Head ?? false,
5636
+ embeddingsUnavailable
5525
5637
  };
5526
5638
  };
5527
5639
  const rerankStats = { applied: false };
@@ -5544,36 +5656,46 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
5544
5656
  ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
5545
5657
  ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
5546
5658
  };
5547
- if (useFusion && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
5659
+ if (useFusion && !embeddingsUnavailable && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
5548
5660
  const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
5549
5661
  if (decomp.mode === "composite") {
5550
- const subEmbeddings = await generateEmbeddings(decomp.subQueries, embeddingOptions);
5551
- const subQueries = decomp.subQueries.map((sq, i) => ({
5552
- query: sq,
5553
- embedding: subEmbeddings[i]
5554
- }));
5555
- const v2HeadStats = { hadResults: false };
5556
- const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
5557
- limit,
5558
- minSimilarity,
5559
- rerank: !!searchOptions.rerank,
5560
- ...searchOptions.rerankTopN !== void 0 && {
5561
- rerankTopN: searchOptions.rerankTopN
5562
- },
5563
- ...searchOptions.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
5564
- ...searchOptions.mmr !== void 0 && { mmr: searchOptions.mmr },
5565
- ...tuning,
5566
- ...searchOptions.entityRanking && { entityRanking: searchOptions.entityRanking },
5567
- ...searchOptions.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
5568
- rerankStats,
5569
- v2HeadStats
5570
- });
5571
- return stampTimestamps({
5572
- results: composite,
5573
- vaultSize,
5574
- reranked: rerankStats.applied,
5575
- hadV2Head: v2HeadStats.hadResults
5576
- });
5662
+ let subEmbeddings;
5663
+ try {
5664
+ subEmbeddings = await generateEmbeddings(decomp.subQueries, embeddingOptions);
5665
+ } catch (err) {
5666
+ getLogger().warn(
5667
+ "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
5668
+ );
5669
+ subEmbeddings = [];
5670
+ }
5671
+ if (subEmbeddings.length > 0) {
5672
+ const subQueries = decomp.subQueries.map((sq, i) => ({
5673
+ query: sq,
5674
+ embedding: subEmbeddings[i]
5675
+ }));
5676
+ const v2HeadStats = { hadResults: false };
5677
+ const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
5678
+ limit,
5679
+ minSimilarity,
5680
+ rerank: !!searchOptions.rerank,
5681
+ ...searchOptions.rerankTopN !== void 0 && {
5682
+ rerankTopN: searchOptions.rerankTopN
5683
+ },
5684
+ ...searchOptions.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
5685
+ ...searchOptions.mmr !== void 0 && { mmr: searchOptions.mmr },
5686
+ ...tuning,
5687
+ ...searchOptions.entityRanking && { entityRanking: searchOptions.entityRanking },
5688
+ ...searchOptions.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
5689
+ rerankStats,
5690
+ v2HeadStats
5691
+ });
5692
+ return stampTimestamps({
5693
+ results: composite,
5694
+ vaultSize,
5695
+ reranked: rerankStats.applied,
5696
+ hadV2Head: v2HeadStats.hadResults
5697
+ });
5698
+ }
5577
5699
  }
5578
5700
  }
5579
5701
  if (useFusion && searchOptions?.rerank) {
@@ -5624,7 +5746,14 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
5624
5746
  }
5625
5747
  async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, cache, searchOptions) {
5626
5748
  if (!query || typeof query !== "string") {
5627
- return { results: [], vaultSize: 0, reranked: false, hadV2Head: false };
5749
+ return {
5750
+ results: [],
5751
+ vaultSize: 0,
5752
+ reranked: false,
5753
+ hadV2Head: false,
5754
+ embeddingsUnavailable: false,
5755
+ rankedOnCosine: false
5756
+ };
5628
5757
  }
5629
5758
  const prepared = await prepareVaultCandidates(
5630
5759
  query,
@@ -5634,12 +5763,32 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
5634
5763
  searchOptions
5635
5764
  );
5636
5765
  if (prepared.vaultSize === 0) {
5637
- return { results: [], vaultSize: 0, reranked: false, hadV2Head: false };
5766
+ return {
5767
+ results: [],
5768
+ vaultSize: 0,
5769
+ reranked: false,
5770
+ hadV2Head: false,
5771
+ embeddingsUnavailable: prepared.embeddingsUnavailable,
5772
+ rankedOnCosine: false
5773
+ };
5638
5774
  }
5639
5775
  if (prepared.memories.length === 0) {
5640
- return { results: [], vaultSize: prepared.vaultSize, reranked: false, hadV2Head: false };
5776
+ return {
5777
+ results: [],
5778
+ vaultSize: prepared.vaultSize,
5779
+ reranked: false,
5780
+ hadV2Head: false,
5781
+ embeddingsUnavailable: prepared.embeddingsUnavailable,
5782
+ rankedOnCosine: false
5783
+ };
5641
5784
  }
5642
- return rankPreparedVaultCandidates(query, prepared, embeddingOptions, searchOptions);
5785
+ const ranked = await rankPreparedVaultCandidates(
5786
+ query,
5787
+ prepared,
5788
+ embeddingOptions,
5789
+ searchOptions
5790
+ );
5791
+ return { ...ranked, rankedOnCosine: !ranked.embeddingsUnavailable };
5643
5792
  }
5644
5793
  async function searchVaultMemories(query, vaultCtx, embeddingOptions, cache, searchOptions) {
5645
5794
  const { results } = await searchVaultMemoriesWithSize(
@@ -5651,6 +5800,7 @@ async function searchVaultMemories(query, vaultCtx, embeddingOptions, cache, sea
5651
5800
  );
5652
5801
  return results;
5653
5802
  }
5803
+ 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.";
5654
5804
  function formatVaultHits(hits) {
5655
5805
  return hits.map((h, i) => `[${i + 1}] (id: ${h.id}, similarity: ${h.score.toFixed(2)})
5656
5806
  ${h.content}`).join("\n\n");
@@ -5713,26 +5863,36 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
5713
5863
  const budget = searchOptions?.decompose === "llm" && searchOptions.decomposeOptions ? "high" : searchOptions?.rerank ? "mid" : "low";
5714
5864
  const folderId = searchOptions?.folderId ?? argFolderId;
5715
5865
  if (searchOptions?.useFusion === false) {
5716
- const legacy = await searchVaultMemories(query, vaultCtx, embeddingOptions, cache, {
5717
- limit: requestLimit,
5718
- minSimilarity,
5719
- useFusion: false,
5720
- ...tuningForward,
5721
- ...folderId !== void 0 && { folderId },
5722
- ...searchOptions?.scopes && { scopes: searchOptions.scopes }
5723
- });
5866
+ const { results: legacy, embeddingsUnavailable } = await searchVaultMemoriesWithSize(
5867
+ query,
5868
+ vaultCtx,
5869
+ embeddingOptions,
5870
+ cache,
5871
+ {
5872
+ limit: requestLimit,
5873
+ minSimilarity,
5874
+ useFusion: false,
5875
+ ...tuningForward,
5876
+ ...folderId !== void 0 && { folderId },
5877
+ ...searchOptions?.scopes && { scopes: searchOptions.scopes }
5878
+ }
5879
+ );
5724
5880
  if (legacy.length === 0) {
5725
- return "No relevant memories found in the vault.";
5881
+ return embeddingsUnavailable ? EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL : "No relevant memories found in the vault.";
5726
5882
  }
5727
5883
  return formatVaultHits(
5728
5884
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
5729
5885
  );
5730
5886
  }
5731
- const { recall: recall2 } = await import("./recall-IBGUFY4P.mjs");
5887
+ const { recall: recall2 } = await import("./recall-JWQ4IJX6.mjs");
5888
+ let recallDegraded = [];
5732
5889
  const result = await recall2(
5733
5890
  query,
5734
5891
  { vaultCtx, embeddingOptions, vaultCache: cache },
5735
5892
  {
5893
+ onDiagnostics: (d) => {
5894
+ recallDegraded = d.degraded;
5895
+ },
5736
5896
  types: ["fact"],
5737
5897
  limit: requestLimit,
5738
5898
  minScore: minSimilarity,
@@ -5753,7 +5913,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
5753
5913
  return "The memory vault is empty. No memories have been saved yet.";
5754
5914
  }
5755
5915
  if (result.memories.length === 0) {
5756
- return "No relevant memories found in the vault.";
5916
+ return recallDegraded.includes("embeddings-unavailable") ? EMBEDDINGS_DEGRADED_EMPTY : "No relevant memories found in the vault.";
5757
5917
  }
5758
5918
  const formatted = formatVaultHits(
5759
5919
  result.memories.map((m) => ({
@@ -6308,6 +6468,9 @@ async function recall(query, ctx, options = {}) {
6308
6468
  let vaultSize;
6309
6469
  let didRerank = false;
6310
6470
  let hadV2Head = false;
6471
+ let embeddingsUnavailable = false;
6472
+ let chunkEmbedFailed = false;
6473
+ let factLaneRankedOnCosine = false;
6311
6474
  const emitDiagnostics = (candidateCount) => {
6312
6475
  const cb = options.onDiagnostics;
6313
6476
  if (!cb) return;
@@ -6316,6 +6479,7 @@ async function recall(query, ctx, options = {}) {
6316
6479
  degraded.push("rerank-unavailable");
6317
6480
  }
6318
6481
  if (flags.decompose && !decomposeAvailable) degraded.push("decompose-unavailable");
6482
+ if (embeddingsUnavailable) degraded.push("embeddings-unavailable");
6319
6483
  const diagnostics = {
6320
6484
  usedBudget,
6321
6485
  reranked: didRerank,
@@ -6346,7 +6510,26 @@ async function recall(query, ctx, options = {}) {
6346
6510
  const graphRefiner = flags.traverse && options.graphRefine && options.decomposeOptions ? createLlmNeighborRefiner(options.decomposeOptions) : void 0;
6347
6511
  const prepStart = nowMs();
6348
6512
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
6349
- needsChunkEmbedding ? generateEmbedding(query, ctx.embeddingOptions) : Promise.resolve(void 0),
6513
+ // The chunk lane is cosine-only — `searchChunksOp` needs a real vector, and
6514
+ // there is no lexical fallback for it — so an embeddings outage must SKIP the
6515
+ // lane, not reject this shared Promise.all and take the primary fact lane
6516
+ // (which BM25 can still serve) down with it. Mirrors safeLane's posture.
6517
+ needsChunkEmbedding ? generateEmbedding(query, ctx.embeddingOptions).then((vec) => {
6518
+ if (vec.length === 0) {
6519
+ getLogger().warn(
6520
+ "[memory/recall] chunk-lane query embedding came back empty; skipping the chunk lane"
6521
+ );
6522
+ chunkEmbedFailed = true;
6523
+ return void 0;
6524
+ }
6525
+ return vec;
6526
+ }).catch((err) => {
6527
+ getLogger().warn(
6528
+ `[memory/recall] chunk-lane query embedding failed; skipping the chunk lane: ${err instanceof Error ? err.message : String(err)}`
6529
+ );
6530
+ chunkEmbedFailed = true;
6531
+ return void 0;
6532
+ }) : Promise.resolve(void 0),
6350
6533
  // The graph + temporal lanes are AUXILIARY (RRF side-signals). A transient
6351
6534
  // WatermelonDB throw in either must NOT reject this Promise.all and take
6352
6535
  // PRIMARY cosine/BM25 recall down with it — degrade the failing lane to an
@@ -6371,7 +6554,9 @@ async function recall(query, ctx, options = {}) {
6371
6554
  results,
6372
6555
  vaultSize: size,
6373
6556
  reranked,
6374
- hadV2Head: v2Head
6557
+ hadV2Head: v2Head,
6558
+ embeddingsUnavailable: factEmbeddingsUnavailable,
6559
+ rankedOnCosine: factRankedOnCosine
6375
6560
  } = await searchVaultMemoriesWithSize(
6376
6561
  query,
6377
6562
  ctx.vaultCtx,
@@ -6428,8 +6613,11 @@ async function recall(query, ctx, options = {}) {
6428
6613
  vaultSize = size;
6429
6614
  didRerank = reranked;
6430
6615
  hadV2Head = v2Head;
6616
+ if (factEmbeddingsUnavailable) embeddingsUnavailable = true;
6617
+ factLaneRankedOnCosine = factRankedOnCosine;
6431
6618
  factLaneMs = nowMs() - factStart;
6432
6619
  }
6620
+ if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
6433
6621
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
6434
6622
  const chunkStart = nowMs();
6435
6623
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
@@ -6724,6 +6912,9 @@ export {
6724
6912
  restoreVaultMemoryOp,
6725
6913
  hardDeleteDecayedOp,
6726
6914
  updateVaultMemoryEmbeddingOp,
6915
+ RECALL_TOOL_NAME,
6916
+ RECALL_MAX_LIMIT,
6917
+ EMBEDDINGS_DEGRADED_EMPTY,
6727
6918
  RerankerUnavailableError,
6728
6919
  isRerankerAvailable,
6729
6920
  DEFAULT_VAULT_CACHE_SIZE,