@anuma/sdk 6.10.0 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5732,6 +5732,17 @@ interface RecallDiagnostics {
5732
5732
  * never the cost.
5733
5733
  */
5734
5734
  vaultRowsDecrypted?: number;
5735
+ /**
5736
+ * Rows the fact lane had to re-embed through the portal because their stored
5737
+ * vector was unusable — stale `embedding_model`, wrong dimension, or
5738
+ * unparseable. Absent when the fact lane didn't run.
5739
+ *
5740
+ * Expected to be 0 on a healthy vault: rows are embedded at write time and the
5741
+ * re-embed writes the current model back, so a persistently non-zero value
5742
+ * means the writeback is not sticking and every turn is paying for it. On the
5743
+ * LEGACY read path this batch is uncapped, so it can be the whole vault.
5744
+ */
5745
+ vaultRowsEmbedded?: number;
5735
5746
  /** Facts the fact lane returned (post-dedupe, pre-fusion). */
5736
5747
  factCount: number;
5737
5748
  /** Chunks the chunk lane returned (post-dedupe, pre-fusion). */
@@ -5744,6 +5755,37 @@ interface RecallDiagnostics {
5744
5755
  prep: number;
5745
5756
  /** Vault fact-lane search (`searchVaultMemoriesWithSize`). */
5746
5757
  factLane: number;
5758
+ /**
5759
+ * The cross-encoder's share of {@link factLane} — wall-clock spent inside
5760
+ * `rerankPairs`. Billed even when the CE threw partway through, because a
5761
+ * rerank that burned three seconds and then failed still cost them.
5762
+ *
5763
+ * A SUBSET of `factLane`, not a sibling: read `factLane - rerank` for
5764
+ * everything else the lane did (query embed, vault read, fused ranking).
5765
+ * 0 when the CE did not run; `reranked` is what distinguishes that from a
5766
+ * rerank that cost nothing.
5767
+ *
5768
+ * Exists because #845 spent three rounds arguing about which stage inside
5769
+ * the fact lane dominated — whole-vault read, then admission window, then
5770
+ * the CE — with one aggregate number for all of them. Every hypothesis was
5771
+ * an inference; this makes the question a query.
5772
+ */
5773
+ rerank: number;
5774
+ /**
5775
+ * The query embed's share of {@link factLane} — one portal round trip.
5776
+ *
5777
+ * Also a SUBSET of `factLane`. Together with `rerank` this makes the lane
5778
+ * decomposable: `factLane - queryEmbed - rerank` is the local work (vault
5779
+ * read, decrypt, BM25/cosine fusion), and each of the three implies a
5780
+ * completely different fix.
5781
+ *
5782
+ * 0 when the lane returned before embedding, which includes every empty
5783
+ * vault — `prepareVaultCandidates` short-circuits first. That is why the
5784
+ * fast `vault_size = 0` population never established a baseline for this
5785
+ * cost, and why the ~850ms floor on the smallest NON-empty vaults had no
5786
+ * attributable owner.
5787
+ */
5788
+ queryEmbed: number;
5747
5789
  /** Chunk-lane search (`searchChunksOp`). */
5748
5790
  chunkLane: number;
5749
5791
  /** Cross-lane RRF fusion + provenance dedup after both lanes. */
@@ -5732,6 +5732,17 @@ interface RecallDiagnostics {
5732
5732
  * never the cost.
5733
5733
  */
5734
5734
  vaultRowsDecrypted?: number;
5735
+ /**
5736
+ * Rows the fact lane had to re-embed through the portal because their stored
5737
+ * vector was unusable — stale `embedding_model`, wrong dimension, or
5738
+ * unparseable. Absent when the fact lane didn't run.
5739
+ *
5740
+ * Expected to be 0 on a healthy vault: rows are embedded at write time and the
5741
+ * re-embed writes the current model back, so a persistently non-zero value
5742
+ * means the writeback is not sticking and every turn is paying for it. On the
5743
+ * LEGACY read path this batch is uncapped, so it can be the whole vault.
5744
+ */
5745
+ vaultRowsEmbedded?: number;
5735
5746
  /** Facts the fact lane returned (post-dedupe, pre-fusion). */
5736
5747
  factCount: number;
5737
5748
  /** Chunks the chunk lane returned (post-dedupe, pre-fusion). */
@@ -5744,6 +5755,37 @@ interface RecallDiagnostics {
5744
5755
  prep: number;
5745
5756
  /** Vault fact-lane search (`searchVaultMemoriesWithSize`). */
5746
5757
  factLane: number;
5758
+ /**
5759
+ * The cross-encoder's share of {@link factLane} — wall-clock spent inside
5760
+ * `rerankPairs`. Billed even when the CE threw partway through, because a
5761
+ * rerank that burned three seconds and then failed still cost them.
5762
+ *
5763
+ * A SUBSET of `factLane`, not a sibling: read `factLane - rerank` for
5764
+ * everything else the lane did (query embed, vault read, fused ranking).
5765
+ * 0 when the CE did not run; `reranked` is what distinguishes that from a
5766
+ * rerank that cost nothing.
5767
+ *
5768
+ * Exists because #845 spent three rounds arguing about which stage inside
5769
+ * the fact lane dominated — whole-vault read, then admission window, then
5770
+ * the CE — with one aggregate number for all of them. Every hypothesis was
5771
+ * an inference; this makes the question a query.
5772
+ */
5773
+ rerank: number;
5774
+ /**
5775
+ * The query embed's share of {@link factLane} — one portal round trip.
5776
+ *
5777
+ * Also a SUBSET of `factLane`. Together with `rerank` this makes the lane
5778
+ * decomposable: `factLane - queryEmbed - rerank` is the local work (vault
5779
+ * read, decrypt, BM25/cosine fusion), and each of the three implies a
5780
+ * completely different fix.
5781
+ *
5782
+ * 0 when the lane returned before embedding, which includes every empty
5783
+ * vault — `prepareVaultCandidates` short-circuits first. That is why the
5784
+ * fast `vault_size = 0` population never established a baseline for this
5785
+ * cost, and why the ~850ms floor on the smallest NON-empty vaults had no
5786
+ * attributable owner.
5787
+ */
5788
+ queryEmbed: number;
5747
5789
  /** Chunk-lane search (`searchChunksOp`). */
5748
5790
  chunkLane: number;
5749
5791
  /** Cross-lane RRF fusion + provenance dedup after both lanes. */
@@ -132,7 +132,7 @@ import {
132
132
  upsertMessageOp,
133
133
  useEncryption,
134
134
  vaultMemoryToStored
135
- } from "./chunk-4M56MKBV.mjs";
135
+ } from "./chunk-ZO7S3TBT.mjs";
136
136
  import {
137
137
  PII_PATTERNS,
138
138
  PiiRedactor,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-4M56MKBV.mjs";
3
+ } from "./chunk-ZO7S3TBT.mjs";
4
4
  import "./chunk-CJ32HO6Q.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {
@@ -5553,9 +5553,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
5553
5553
  const updatedMs = updatedAt.getTime();
5554
5554
  if (!Number.isFinite(updatedMs)) return noDate;
5555
5555
  const now = opts.now ?? /* @__PURE__ */ new Date();
5556
- const nowMs2 = now.getTime();
5557
- if (!Number.isFinite(nowMs2)) return noDate;
5558
- const ageDays = Math.max(0, (nowMs2 - updatedMs) / DAY_MS);
5556
+ const nowMs3 = now.getTime();
5557
+ if (!Number.isFinite(nowMs3)) return noDate;
5558
+ const ageDays = Math.max(0, (nowMs3 - updatedMs) / DAY_MS);
5559
5559
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
5560
5560
  const floor = opts.floor ?? DEFAULT_FLOOR;
5561
5561
  const decay = perYearDecay * (ageDays / 365);
@@ -5901,6 +5901,7 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
5901
5901
  }
5902
5902
 
5903
5903
  // src/lib/memoryVault/searchTool.ts
5904
+ var nowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
5904
5905
  var warnedProgrammaticDecomposeIgnored = false;
5905
5906
  function normalizeEventTimeKind(kind) {
5906
5907
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
@@ -6133,6 +6134,7 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
6133
6134
  const rerankTopN = options.rerankTopN ?? 30;
6134
6135
  const ceWeight = options.ceWeight ?? 0.1;
6135
6136
  const headSlice = v2Ranked.slice(0, rerankTopN);
6137
+ const ceStart = nowMs();
6136
6138
  try {
6137
6139
  const reranked = await rerankPairs(
6138
6140
  query,
@@ -6160,6 +6162,8 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
6160
6162
  getLogger().warn("[memory/search] cross-encoder rerank failed; using V2 ranking", err);
6161
6163
  }
6162
6164
  combined = v2Ranked;
6165
+ } finally {
6166
+ if (options.rerankStats) options.rerankStats.ms += nowMs() - ceStart;
6163
6167
  }
6164
6168
  } else {
6165
6169
  combined = v2Ranked;
@@ -6326,6 +6330,7 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
6326
6330
  const ceWeight = options.ceWeight ?? 0.1;
6327
6331
  const headSlice = combined.slice(0, rerankTopN);
6328
6332
  const tailSlice = combined.slice(rerankTopN);
6333
+ const ceStart = nowMs();
6329
6334
  try {
6330
6335
  const reranked = await rerankPairs(
6331
6336
  originalQuery,
@@ -6353,6 +6358,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
6353
6358
  err
6354
6359
  );
6355
6360
  }
6361
+ } finally {
6362
+ if (options.rerankStats) options.rerankStats.ms += nowMs() - ceStart;
6356
6363
  }
6357
6364
  }
6358
6365
  if (options?.mmr) {
@@ -6479,11 +6486,13 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
6479
6486
  rowsDecrypted: 0
6480
6487
  };
6481
6488
  }
6489
+ const queryEmbedStart = nowMs();
6482
6490
  const queryEmbedding = await embedQueryOrDegrade(
6483
6491
  query,
6484
6492
  embeddingOptions,
6485
6493
  opts.onEmbeddingDegraded
6486
6494
  );
6495
+ if (opts.embedStats) opts.embedStats.queryMs += nowMs() - queryEmbedStart;
6487
6496
  const embeddingsDegraded = queryEmbedding.length === 0;
6488
6497
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
6489
6498
  const vectored = [];
@@ -6536,6 +6545,7 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
6536
6545
  const laneRows = laneFetched.filter((m) => !isEncrypted(m.content));
6537
6546
  if (laneRows.length > 0) {
6538
6547
  let laneVecs;
6548
+ if (opts.embedStats) opts.embedStats.rowsEmbedded += laneRows.length;
6539
6549
  try {
6540
6550
  laneVecs = await generateEmbeddings(
6541
6551
  laneRows.map((m) => m.content),
@@ -6614,6 +6624,10 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
6614
6624
  async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache, searchOptions) {
6615
6625
  if (!query || typeof query !== "string") {
6616
6626
  return {
6627
+ // Nothing was read and nothing was embedded — the short-circuit fires
6628
+ // before either.
6629
+ queryEmbedMs: 0,
6630
+ rowsEmbedded: 0,
6617
6631
  memories: [],
6618
6632
  embeddedItems: [],
6619
6633
  queryEmbedding: [],
@@ -6646,6 +6660,7 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
6646
6660
  embeddingsUnavailable = true;
6647
6661
  embeddingFailure = true;
6648
6662
  };
6663
+ const embedStats = { queryMs: 0, rowsEmbedded: 0 };
6649
6664
  if (searchOptions?.decryptLast) {
6650
6665
  const forceIncludeIds = [
6651
6666
  ...searchOptions.entityRanking ?? [],
@@ -6657,7 +6672,8 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
6657
6672
  admitFloor: ADMIT_FLOOR,
6658
6673
  unembeddedCap: UNEMBEDDED_CAP,
6659
6674
  ...forceIncludeIds.length > 0 && { forceIncludeIds },
6660
- onEmbeddingDegraded
6675
+ onEmbeddingDegraded,
6676
+ embedStats
6661
6677
  });
6662
6678
  if (corpus.vaultSize === 0) {
6663
6679
  return {
@@ -6668,7 +6684,9 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
6668
6684
  embeddingsUnavailable: false,
6669
6685
  embeddingFailure: false,
6670
6686
  decryptLast: true,
6671
- rowsDecrypted: corpus.rowsDecrypted
6687
+ rowsDecrypted: corpus.rowsDecrypted,
6688
+ queryEmbedMs: embedStats.queryMs,
6689
+ rowsEmbedded: embedStats.rowsEmbedded
6672
6690
  };
6673
6691
  }
6674
6692
  ({ memories, embeddedItems, queryEmbedding, vaultSize } = corpus);
@@ -6685,7 +6703,9 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
6685
6703
  decryptLast: true,
6686
6704
  // Rows WERE decrypt-attempted even though none came back readable — that
6687
6705
  // is the whole point of reporting attempts rather than successes.
6688
- rowsDecrypted: corpus.rowsDecrypted
6706
+ rowsDecrypted: corpus.rowsDecrypted,
6707
+ queryEmbedMs: embedStats.queryMs,
6708
+ rowsEmbedded: embedStats.rowsEmbedded
6689
6709
  };
6690
6710
  }
6691
6711
  } else {
@@ -6710,10 +6730,14 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
6710
6730
  embeddingsUnavailable: false,
6711
6731
  embeddingFailure: false,
6712
6732
  decryptLast: false,
6713
- rowsDecrypted: loaded.length
6733
+ rowsDecrypted: loaded.length,
6734
+ queryEmbedMs: embedStats.queryMs,
6735
+ rowsEmbedded: embedStats.rowsEmbedded
6714
6736
  };
6715
6737
  }
6738
+ const legacyEmbedStart = nowMs();
6716
6739
  queryEmbedding = await embedQueryOrDegrade(query, embeddingOptions, onEmbeddingDegraded);
6740
+ embedStats.queryMs += nowMs() - legacyEmbedStart;
6717
6741
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
6718
6742
  const uncachedTexts = [];
6719
6743
  const uncachedIndices = [];
@@ -6747,6 +6771,7 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
6747
6771
  }
6748
6772
  if (uncachedTexts.length > 0) {
6749
6773
  let newEmbeddings;
6774
+ embedStats.rowsEmbedded += uncachedTexts.length;
6750
6775
  try {
6751
6776
  newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
6752
6777
  } catch (err) {
@@ -6790,6 +6815,8 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
6790
6815
  embeddingsUnavailable = true;
6791
6816
  }
6792
6817
  return {
6818
+ queryEmbedMs: embedStats.queryMs,
6819
+ rowsEmbedded: embedStats.rowsEmbedded,
6793
6820
  memories,
6794
6821
  embeddedItems,
6795
6822
  queryEmbedding,
@@ -6840,11 +6867,15 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
6840
6867
  results: results2,
6841
6868
  vaultSize: out.vaultSize,
6842
6869
  reranked: out.reranked ?? false,
6870
+ // Read straight off the shared stats object rather than being passed in at
6871
+ // each call site: every return in this function funnels through here, so a
6872
+ // path added later reports the CE bill without having to remember to.
6873
+ rerankMs: rerankStats.ms,
6843
6874
  hadV2Head: out.hadV2Head ?? false,
6844
6875
  embeddingsUnavailable
6845
6876
  };
6846
6877
  };
6847
- const rerankStats = { applied: false };
6878
+ const rerankStats = { applied: false, ms: 0 };
6848
6879
  const useFusion = searchOptions?.useFusion ?? true;
6849
6880
  const tuning = {
6850
6881
  ...searchOptions?.recencyAlpha !== void 0 && { recencyAlpha: searchOptions.recencyAlpha },
@@ -6963,13 +6994,16 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
6963
6994
  results: [],
6964
6995
  vaultSize: 0,
6965
6996
  reranked: false,
6997
+ rerankMs: 0,
6966
6998
  hadV2Head: false,
6967
6999
  embeddingsUnavailable: false,
6968
7000
  rankedOnCosine: false,
6969
7001
  // No storage read happened, so nothing was decrypted; report the path the
6970
7002
  // caller asked for so this turn is still attributable.
6971
7003
  decryptLast: !!searchOptions?.decryptLast,
6972
- rowsDecrypted: 0
7004
+ rowsDecrypted: 0,
7005
+ queryEmbedMs: 0,
7006
+ rowsEmbedded: 0
6973
7007
  };
6974
7008
  }
6975
7009
  if (searchOptions?.decompose === "llm" && normalizeSubQueries(searchOptions.subQueries).length < 2 && !warnedProgrammaticDecomposeIgnored) {
@@ -6990,11 +7024,14 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
6990
7024
  results: [],
6991
7025
  vaultSize: 0,
6992
7026
  reranked: false,
7027
+ rerankMs: 0,
6993
7028
  hadV2Head: false,
6994
7029
  embeddingsUnavailable: prepared.embeddingsUnavailable,
6995
7030
  rankedOnCosine: false,
6996
7031
  decryptLast: prepared.decryptLast,
6997
- rowsDecrypted: prepared.rowsDecrypted
7032
+ rowsDecrypted: prepared.rowsDecrypted,
7033
+ queryEmbedMs: prepared.queryEmbedMs,
7034
+ rowsEmbedded: prepared.rowsEmbedded
6998
7035
  };
6999
7036
  }
7000
7037
  if (prepared.memories.length === 0) {
@@ -7002,11 +7039,14 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
7002
7039
  results: [],
7003
7040
  vaultSize: prepared.vaultSize,
7004
7041
  reranked: false,
7042
+ rerankMs: 0,
7005
7043
  hadV2Head: false,
7006
7044
  embeddingsUnavailable: prepared.embeddingsUnavailable,
7007
7045
  rankedOnCosine: false,
7008
7046
  decryptLast: prepared.decryptLast,
7009
- rowsDecrypted: prepared.rowsDecrypted
7047
+ rowsDecrypted: prepared.rowsDecrypted,
7048
+ queryEmbedMs: prepared.queryEmbedMs,
7049
+ rowsEmbedded: prepared.rowsEmbedded
7010
7050
  };
7011
7051
  }
7012
7052
  const ranked = await rankPreparedVaultCandidates(
@@ -7019,7 +7059,9 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
7019
7059
  ...ranked,
7020
7060
  rankedOnCosine: !ranked.embeddingsUnavailable,
7021
7061
  decryptLast: prepared.decryptLast,
7022
- rowsDecrypted: prepared.rowsDecrypted
7062
+ rowsDecrypted: prepared.rowsDecrypted,
7063
+ queryEmbedMs: prepared.queryEmbedMs,
7064
+ rowsEmbedded: prepared.rowsEmbedded
7023
7065
  };
7024
7066
  }
7025
7067
  async function searchVaultMemories(query, vaultCtx, embeddingOptions, cache, searchOptions) {
@@ -7124,7 +7166,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
7124
7166
  subQueries = decomp.subQueries;
7125
7167
  }
7126
7168
  }
7127
- const { recall: recall2 } = await import("./recall-FWILZSFH.mjs");
7169
+ const { recall: recall2 } = await import("./recall-F5DBVCU2.mjs");
7128
7170
  let recallDegraded = [];
7129
7171
  const result = await recall2(
7130
7172
  query,
@@ -7664,7 +7706,7 @@ function scoreEventTimeOverlap(memoryStart, memoryEnd, memoryKind, window2) {
7664
7706
  var DEFAULT_LIMIT = 8;
7665
7707
  var DEFAULT_BUDGET = "low";
7666
7708
  var DEFAULT_FACT_MIN_SCORE = 0.1;
7667
- var nowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
7709
+ var nowMs2 = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
7668
7710
  var DEFAULT_CHUNK_MIN_SCORE = 0.5;
7669
7711
  function flagsForBudget(budget) {
7670
7712
  switch (budget) {
@@ -7697,7 +7739,7 @@ async function recall(query, ctx, options = {}) {
7697
7739
  const flags = flagsForBudget(usedBudget);
7698
7740
  const normalizedFacets = normalizeSubQueries(options.subQueries);
7699
7741
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
7700
- const t0 = nowMs();
7742
+ const t0 = nowMs2();
7701
7743
  let prepMs = 0;
7702
7744
  let factLaneMs = 0;
7703
7745
  let chunkLaneMs = 0;
@@ -7706,6 +7748,9 @@ async function recall(query, ctx, options = {}) {
7706
7748
  const chunkResults = [];
7707
7749
  let vaultSize;
7708
7750
  let didRerank = false;
7751
+ let rerankMs = 0;
7752
+ let queryEmbedMs = 0;
7753
+ let vaultRowsEmbedded;
7709
7754
  let hadV2Head = false;
7710
7755
  let embeddingsUnavailable = false;
7711
7756
  let chunkEmbedFailed = false;
@@ -7730,12 +7775,15 @@ async function recall(query, ctx, options = {}) {
7730
7775
  ...vaultSize !== void 0 && { vaultSize },
7731
7776
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
7732
7777
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
7778
+ ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
7733
7779
  factCount: factResults.length,
7734
7780
  chunkCount: chunkResults.length,
7735
7781
  timings: {
7736
- total: nowMs() - t0,
7782
+ total: nowMs2() - t0,
7737
7783
  prep: prepMs,
7738
7784
  factLane: factLaneMs,
7785
+ rerank: rerankMs,
7786
+ queryEmbed: queryEmbedMs,
7739
7787
  chunkLane: chunkLaneMs,
7740
7788
  fuse: fuseMs
7741
7789
  },
@@ -7753,7 +7801,7 @@ async function recall(query, ctx, options = {}) {
7753
7801
  const needsChunkEmbedding = types.includes("chunk") && ctx.storageCtx;
7754
7802
  const wantsTemporal = types.includes("fact") && ctx.vaultCtx;
7755
7803
  const graphRefiner = flags.traverse && options.graphRefine && options.decomposeOptions ? createLlmNeighborRefiner(options.decomposeOptions) : void 0;
7756
- const prepStart = nowMs();
7804
+ const prepStart = nowMs2();
7757
7805
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
7758
7806
  // The chunk lane is cosine-only — `searchChunksOp` needs a real vector, and
7759
7807
  // there is no lexical fallback for it — so an embeddings outage must SKIP the
@@ -7791,14 +7839,17 @@ async function recall(query, ctx, options = {}) {
7791
7839
  ),
7792
7840
  wantsTemporal ? safeLane("temporal", () => buildTemporalLaneRanking(query, ctx.vaultCtx, options.now)) : Promise.resolve([])
7793
7841
  ]);
7794
- prepMs = nowMs() - prepStart;
7842
+ prepMs = nowMs2() - prepStart;
7795
7843
  if (types.includes("fact") && ctx.vaultCtx && ctx.vaultCache) {
7796
- const factStart = nowMs();
7844
+ const factStart = nowMs2();
7797
7845
  const vaultMinScore = options.minScore ?? DEFAULT_FACT_MIN_SCORE;
7798
7846
  const {
7799
7847
  results,
7800
7848
  vaultSize: size,
7801
7849
  reranked,
7850
+ rerankMs: factRerankMs,
7851
+ queryEmbedMs: factQueryEmbedMs,
7852
+ rowsEmbedded: factRowsEmbedded,
7802
7853
  hadV2Head: v2Head,
7803
7854
  embeddingsUnavailable: factEmbeddingsUnavailable,
7804
7855
  rankedOnCosine: factRankedOnCosine,
@@ -7857,16 +7908,19 @@ async function recall(query, ctx, options = {}) {
7857
7908
  );
7858
7909
  vaultSize = size;
7859
7910
  didRerank = reranked;
7911
+ rerankMs = factRerankMs;
7912
+ queryEmbedMs = factQueryEmbedMs;
7913
+ vaultRowsEmbedded = factRowsEmbedded;
7860
7914
  hadV2Head = v2Head;
7861
7915
  if (factEmbeddingsUnavailable) embeddingsUnavailable = true;
7862
7916
  factLaneRankedOnCosine = factRankedOnCosine;
7863
7917
  decryptLastRan = factDecryptLast;
7864
7918
  vaultRowsDecrypted = factRowsDecrypted;
7865
- factLaneMs = nowMs() - factStart;
7919
+ factLaneMs = nowMs2() - factStart;
7866
7920
  }
7867
7921
  if (chunkEmbedFailed && !factLaneRankedOnCosine) embeddingsUnavailable = true;
7868
7922
  if (types.includes("chunk") && ctx.storageCtx && queryEmbedding) {
7869
- const chunkStart = nowMs();
7923
+ const chunkStart = nowMs2();
7870
7924
  const chunkMinScore = options.minScore ?? DEFAULT_CHUNK_MIN_SCORE;
7871
7925
  const results = await searchChunksOp(ctx.storageCtx, queryEmbedding, {
7872
7926
  limit: types.includes("fact") ? Math.max(limit * 2, 16) : limit,
@@ -7883,9 +7937,9 @@ async function recall(query, ctx, options = {}) {
7883
7937
  (r) => r.chunkText.trim()
7884
7938
  )
7885
7939
  );
7886
- chunkLaneMs = nowMs() - chunkStart;
7940
+ chunkLaneMs = nowMs2() - chunkStart;
7887
7941
  }
7888
- const fuseStart = nowMs();
7942
+ const fuseStart = nowMs2();
7889
7943
  if (types.length === 1 || factResults.length === 0 || chunkResults.length === 0) {
7890
7944
  const memories2 = [
7891
7945
  ...factResults.map((r) => toFactMemory(r, options.now)),
@@ -7893,7 +7947,7 @@ async function recall(query, ctx, options = {}) {
7893
7947
  ];
7894
7948
  memories2.sort((a, b) => b.score - a.score);
7895
7949
  const candidateCount = factResults.length + chunkResults.length;
7896
- fuseMs = nowMs() - fuseStart;
7950
+ fuseMs = nowMs2() - fuseStart;
7897
7951
  emitDiagnostics(candidateCount);
7898
7952
  return {
7899
7953
  memories: memories2.slice(0, limit),
@@ -7948,7 +8002,7 @@ async function recall(query, ctx, options = {}) {
7948
8002
  suppressed = next;
7949
8003
  memories = selectWith(suppressed);
7950
8004
  }
7951
- fuseMs = nowMs() - fuseStart;
8005
+ fuseMs = nowMs2() - fuseStart;
7952
8006
  emitDiagnostics(byId.size);
7953
8007
  return {
7954
8008
  memories,