@anuma/sdk 4.2.1 → 5.0.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.
@@ -6684,11 +6684,11 @@ async function decomposeQuery(query, options) {
6684
6684
  userMessage: `Classify the following memory query and decompose if composite. Respond with JSON only \u2014 do not answer the question, do not ask for clarification.
6685
6685
 
6686
6686
  Query: ${trimmed}`,
6687
- // Decompose runs on the recall hot path — tighter than the
6687
+ // Decompose runs ahead of recall in the tool layer — tighter than the
6688
6688
  // portalLlm default (consolidate/extract can wait longer).
6689
6689
  timeoutMs: 2e4,
6690
6690
  // No internal retry: a failure degrades to the safe `{specific,[query]}`
6691
- // fallback below, so retrying would only add latency on the hot path.
6691
+ // fallback below. The agent/tool loop is the retry surface (719/B4).
6692
6692
  maxAttempts: 1,
6693
6693
  tag: "memory/decompose",
6694
6694
  ...options.fetchFn && { fetchFn: options.fetchFn }
@@ -6700,12 +6700,28 @@ Query: ${trimmed}`,
6700
6700
  if (parsed === null) return fallback;
6701
6701
  return validate(parsed, trimmed) ?? fallback;
6702
6702
  }
6703
+ function normalizeSubQueries(raw) {
6704
+ if (!raw || raw.length === 0) return [];
6705
+ const seen = /* @__PURE__ */ new Set();
6706
+ const out = [];
6707
+ for (const s of raw) {
6708
+ if (typeof s !== "string") continue;
6709
+ const t = s.trim();
6710
+ if (t.length === 0) continue;
6711
+ const key = t.toLowerCase();
6712
+ if (seen.has(key)) continue;
6713
+ seen.add(key);
6714
+ out.push(t);
6715
+ if (out.length >= MAX_SUB_QUERIES) break;
6716
+ }
6717
+ return out;
6718
+ }
6703
6719
  function validate(parsed, originalQuery) {
6704
6720
  if (typeof parsed !== "object" || parsed === null) return null;
6705
6721
  const obj = parsed;
6706
6722
  const mode = obj.mode === "composite" ? "composite" : "specific";
6707
6723
  if (!Array.isArray(obj.subQueries)) return null;
6708
- const subQueries = obj.subQueries.filter((s) => typeof s === "string").map((s) => s.trim()).filter((s) => s.length > 0).slice(0, MAX_SUB_QUERIES);
6724
+ const subQueries = normalizeSubQueries(obj.subQueries);
6709
6725
  if (subQueries.length === 0) return null;
6710
6726
  if (mode === "specific") {
6711
6727
  return { mode: "specific", subQueries: [originalQuery] };
@@ -7322,12 +7338,12 @@ __export(recall_exports, {
7322
7338
  function flagsForBudget(budget) {
7323
7339
  switch (budget) {
7324
7340
  case "high":
7325
- return { rerank: true, decompose: true, traverse: true };
7341
+ return { rerank: true, traverse: true };
7326
7342
  case "mid":
7327
- return { rerank: true, decompose: false, traverse: false };
7343
+ return { rerank: true, traverse: false };
7328
7344
  case "low":
7329
7345
  default:
7330
- return { rerank: false, decompose: false, traverse: false };
7346
+ return { rerank: false, traverse: false };
7331
7347
  }
7332
7348
  }
7333
7349
  function dedupeBy(items, ...keys) {
@@ -7346,10 +7362,10 @@ function dedupeBy(items, ...keys) {
7346
7362
  async function recall(query, ctx, options = {}) {
7347
7363
  const types = options.types ?? ["fact"];
7348
7364
  const limit = options.limit ?? DEFAULT_LIMIT;
7349
- const requestedBudget = options.budget ?? DEFAULT_BUDGET;
7350
- const flags = flagsForBudget(requestedBudget);
7351
- const decomposeAvailable = flags.decompose && !!options.decomposeOptions;
7352
- const usedBudget = requestedBudget === "high" && !decomposeAvailable ? "mid" : requestedBudget;
7365
+ const usedBudget = options.budget ?? DEFAULT_BUDGET;
7366
+ const flags = flagsForBudget(usedBudget);
7367
+ const normalizedFacets = normalizeSubQueries(options.subQueries);
7368
+ const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
7353
7369
  const t0 = nowMs();
7354
7370
  let prepMs = 0;
7355
7371
  let factLaneMs = 0;
@@ -7370,7 +7386,9 @@ async function recall(query, ctx, options = {}) {
7370
7386
  if (flags.rerank && factResults.length > 0 && hadV2Head && !didRerank) {
7371
7387
  degraded.push("rerank-unavailable");
7372
7388
  }
7373
- if (flags.decompose && !decomposeAvailable) degraded.push("decompose-unavailable");
7389
+ if (usedBudget === "high" && options.decomposeOptions && !subQueries && !options.graphRefine) {
7390
+ degraded.push("decompose-moved");
7391
+ }
7374
7392
  if (embeddingsUnavailable) degraded.push("embeddings-unavailable");
7375
7393
  const diagnostics = {
7376
7394
  usedBudget,
@@ -7483,10 +7501,8 @@ async function recall(query, ctx, options = {}) {
7483
7501
  },
7484
7502
  ...options.rrfK !== void 0 && { rrfK: options.rrfK },
7485
7503
  ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
7486
- ...decomposeAvailable && {
7487
- decompose: "llm",
7488
- decomposeOptions: options.decomposeOptions
7489
- },
7504
+ // 719/B4 — composite facets arrive pre-built; no LLM call here.
7505
+ ...subQueries && { subQueries },
7490
7506
  ...options.scopes && { scopes: options.scopes },
7491
7507
  ...options.folderId !== void 0 && { folderId: options.folderId },
7492
7508
  ...options.factTypes?.length && { factTypes: options.factTypes },
@@ -7723,6 +7739,7 @@ var init_recall = __esm({
7723
7739
  init_logger();
7724
7740
  init_constants();
7725
7741
  init_embeddings();
7742
+ init_decomposeQuery();
7726
7743
  init_searchTool();
7727
7744
  init_graphTraversal();
7728
7745
  init_observationTrend();
@@ -8668,52 +8685,51 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
8668
8685
  ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
8669
8686
  ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
8670
8687
  };
8671
- if (useFusion && !embeddingsUnavailable && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
8672
- const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
8673
- if (decomp.mode === "composite") {
8674
- let subEmbeddings;
8675
- try {
8676
- subEmbeddings = await generateEmbeddings(decomp.subQueries, embeddingOptions);
8677
- } catch (err) {
8678
- getLogger().warn(
8679
- "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
8680
- );
8681
- subEmbeddings = [];
8682
- }
8683
- const subEmbeddingsUsable = decomp.subQueries.length > 0 && subEmbeddings.length === decomp.subQueries.length && subEmbeddings.every((v) => v.length > 0 && v.length === queryEmbedding.length);
8684
- if (subEmbeddings.length > 0 && !subEmbeddingsUsable) {
8685
- getLogger().warn(
8686
- `memoryVault: sub-query embedding returned ${subEmbeddings.length} vectors (dims ${subEmbeddings.map((v) => v.length).join(",")}) for ${decomp.subQueries.length} sub-queries against a ${queryEmbedding.length}-dim query \u2014 falling back to single-query ranking`
8687
- );
8688
- }
8689
- if (subEmbeddingsUsable) {
8690
- const subQueries = decomp.subQueries.map((sq, i) => ({
8691
- query: sq,
8692
- embedding: subEmbeddings[i]
8693
- }));
8694
- const v2HeadStats = { hadResults: false };
8695
- const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
8696
- limit,
8697
- minSimilarity,
8698
- rerank: !!searchOptions.rerank,
8699
- ...searchOptions.rerankTopN !== void 0 && {
8700
- rerankTopN: searchOptions.rerankTopN
8701
- },
8702
- ...searchOptions.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
8703
- ...searchOptions.mmr !== void 0 && { mmr: searchOptions.mmr },
8704
- ...tuning,
8705
- ...searchOptions.entityRanking && { entityRanking: searchOptions.entityRanking },
8706
- ...searchOptions.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
8707
- rerankStats,
8708
- v2HeadStats
8709
- });
8710
- return stampTimestamps({
8711
- results: composite,
8712
- vaultSize,
8713
- reranked: rerankStats.applied,
8714
- hadV2Head: v2HeadStats.hadResults
8715
- });
8716
- }
8688
+ const normalizedFacets = normalizeSubQueries(searchOptions?.subQueries);
8689
+ const facetQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
8690
+ if (useFusion && !embeddingsUnavailable && facetQueries) {
8691
+ let subEmbeddings;
8692
+ try {
8693
+ subEmbeddings = await generateEmbeddings(facetQueries, embeddingOptions);
8694
+ } catch (err) {
8695
+ getLogger().warn(
8696
+ "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
8697
+ );
8698
+ subEmbeddings = [];
8699
+ }
8700
+ const subEmbeddingsUsable = facetQueries.length > 0 && subEmbeddings.length === facetQueries.length && subEmbeddings.every((v) => v.length > 0 && v.length === queryEmbedding.length);
8701
+ if (subEmbeddings.length > 0 && !subEmbeddingsUsable) {
8702
+ getLogger().warn(
8703
+ `memoryVault: sub-query embedding returned ${subEmbeddings.length} vectors (dims ${subEmbeddings.map((v) => v.length).join(",")}) for ${facetQueries.length} sub-queries against a ${queryEmbedding.length}-dim query \u2014 falling back to single-query ranking`
8704
+ );
8705
+ }
8706
+ if (subEmbeddingsUsable) {
8707
+ const subQueries = facetQueries.map((sq, i) => ({
8708
+ query: sq,
8709
+ embedding: subEmbeddings[i]
8710
+ }));
8711
+ const v2HeadStats = { hadResults: false };
8712
+ const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
8713
+ limit,
8714
+ minSimilarity,
8715
+ rerank: !!searchOptions?.rerank,
8716
+ ...searchOptions?.rerankTopN !== void 0 && {
8717
+ rerankTopN: searchOptions.rerankTopN
8718
+ },
8719
+ ...searchOptions?.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
8720
+ ...searchOptions?.mmr !== void 0 && { mmr: searchOptions.mmr },
8721
+ ...tuning,
8722
+ ...searchOptions?.entityRanking && { entityRanking: searchOptions.entityRanking },
8723
+ ...searchOptions?.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
8724
+ rerankStats,
8725
+ v2HeadStats
8726
+ });
8727
+ return stampTimestamps({
8728
+ results: composite,
8729
+ vaultSize,
8730
+ reranked: rerankStats.applied,
8731
+ hadV2Head: v2HeadStats.hadResults
8732
+ });
8717
8733
  }
8718
8734
  }
8719
8735
  if (useFusion && searchOptions?.rerank) {
@@ -8773,6 +8789,12 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
8773
8789
  rankedOnCosine: false
8774
8790
  };
8775
8791
  }
8792
+ if (searchOptions?.decompose === "llm" && normalizeSubQueries(searchOptions.subQueries).length < 2 && !warnedProgrammaticDecomposeIgnored) {
8793
+ warnedProgrammaticDecomposeIgnored = true;
8794
+ getLogger().warn(
8795
+ 'memoryVault: decompose:"llm" is ignored by searchVaultMemories \u2014 pass subQueries or use createMemoryVaultSearchTool / createRecallTool (719/B4)'
8796
+ );
8797
+ }
8776
8798
  const prepared = await prepareVaultCandidates(
8777
8799
  query,
8778
8800
  vaultCtx,
@@ -8877,7 +8899,8 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
8877
8899
  return "Error: A search query is required.";
8878
8900
  }
8879
8901
  try {
8880
- const budget = searchOptions?.decompose === "llm" && searchOptions.decomposeOptions ? "high" : searchOptions?.rerank ? "mid" : "low";
8902
+ const wantsDecompose = searchOptions?.decompose === "llm" && !!searchOptions.decomposeOptions;
8903
+ const budget = wantsDecompose ? "high" : searchOptions?.rerank ? "mid" : "low";
8881
8904
  const folderId = searchOptions?.folderId ?? argFolderId;
8882
8905
  if (searchOptions?.useFusion === false) {
8883
8906
  const { results: legacy, embeddingsUnavailable } = await searchVaultMemoriesWithSize(
@@ -8901,6 +8924,13 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
8901
8924
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
8902
8925
  );
8903
8926
  }
8927
+ let subQueries;
8928
+ if (wantsDecompose && searchOptions?.decomposeOptions) {
8929
+ const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
8930
+ if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
8931
+ subQueries = decomp.subQueries;
8932
+ }
8933
+ }
8904
8934
  const { recall: recall2 } = await Promise.resolve().then(() => (init_recall(), recall_exports));
8905
8935
  let recallDegraded = [];
8906
8936
  const result = await recall2(
@@ -8917,9 +8947,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
8917
8947
  ...tuningForward,
8918
8948
  ...folderId !== void 0 && { folderId },
8919
8949
  ...searchOptions?.scopes && { scopes: searchOptions.scopes },
8920
- ...searchOptions?.decompose === "llm" && searchOptions.decomposeOptions && {
8921
- decomposeOptions: searchOptions.decomposeOptions
8922
- }
8950
+ ...subQueries && { subQueries }
8923
8951
  }
8924
8952
  );
8925
8953
  if (result.vaultSize === 0) {
@@ -8949,7 +8977,7 @@ ${formatted}`;
8949
8977
  }
8950
8978
  };
8951
8979
  }
8952
- var SUPERSESSION_SIMILARITY_THRESHOLD, SUPERSESSION_MIN_AGE_GAP_MS, SUPERSESSION_BOOST_FACTOR, SUPERSESSION_MAX_WINDOW, EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL;
8980
+ var warnedProgrammaticDecomposeIgnored, SUPERSESSION_SIMILARITY_THRESHOLD, SUPERSESSION_MIN_AGE_GAP_MS, SUPERSESSION_BOOST_FACTOR, SUPERSESSION_MAX_WINDOW, EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL;
8953
8981
  var init_searchTool = __esm({
8954
8982
  "src/lib/memoryVault/searchTool.ts"() {
8955
8983
  "use strict";
@@ -8967,6 +8995,7 @@ var init_searchTool = __esm({
8967
8995
  init_bm25();
8968
8996
  init_decomposeQuery();
8969
8997
  init_lruCache();
8998
+ warnedProgrammaticDecomposeIgnored = false;
8970
8999
  SUPERSESSION_SIMILARITY_THRESHOLD = 0.7;
8971
9000
  SUPERSESSION_MIN_AGE_GAP_MS = 30 * 24 * 60 * 60 * 1e3;
8972
9001
  SUPERSESSION_BOOST_FACTOR = 0.8;
@@ -18014,6 +18043,7 @@ function rankProfileCandidates(inputs, now = Date.now(), options = {}) {
18014
18043
  init_recall();
18015
18044
 
18016
18045
  // src/lib/memory/recallTool.ts
18046
+ init_decomposeQuery();
18017
18047
  init_recall();
18018
18048
  init_recallConstants();
18019
18049
  var DEFAULT_LIMIT2 = 8;
@@ -18111,7 +18141,7 @@ function createRecallTool(ctx, toolOptions, callbacks) {
18111
18141
  type: "function",
18112
18142
  function: {
18113
18143
  name: RECALL_TOOL_NAME,
18114
- description: "Search the user's memory across stored facts/preferences and past conversation excerpts. Returns a unified ranked list \u2014 facts carry an `id` you can reference; conversation excerpts carry a date and role. Use this whenever the user's question may relate to anything previously discussed or saved (preferences, prior decisions, past topics). Phrase the query naturally.",
18144
+ description: `Search the user's memory across stored facts/preferences and past conversation excerpts. Returns a unified ranked list \u2014 facts carry an \`id\` you can reference; conversation excerpts carry a date and role. Use this whenever the user's question may relate to anything previously discussed or saved (preferences, prior decisions, past topics). Phrase the query naturally. For multi-faceted / overview questions ("tell me about the user", "what's my tech stack"), prefer several targeted searches \u2014 one facet each (e.g. name, work, hobbies) \u2014 over a single broad query; fuse the results yourself.`,
18115
18145
  arguments: {
18116
18146
  type: "object",
18117
18147
  properties: {
@@ -18166,6 +18196,13 @@ function createRecallTool(ctx, toolOptions, callbacks) {
18166
18196
  let surfaced = 0;
18167
18197
  try {
18168
18198
  let recallDegraded = [];
18199
+ let subQueries;
18200
+ if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
18201
+ const decomp = await decomposeQuery(query, toolOptions.decomposeOptions);
18202
+ if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
18203
+ subQueries = decomp.subQueries;
18204
+ }
18205
+ }
18169
18206
  const recallOpts = {
18170
18207
  onDiagnostics: (d) => {
18171
18208
  recallDegraded = d.degraded;
@@ -18179,10 +18216,12 @@ function createRecallTool(ctx, toolOptions, callbacks) {
18179
18216
  ...toolOptions?.excludeConversationId && {
18180
18217
  excludeConversationId: toolOptions.excludeConversationId
18181
18218
  },
18182
- ...toolOptions?.decomposeOptions && {
18183
- decomposeOptions: toolOptions.decomposeOptions
18184
- },
18185
- ...toolOptions?.now !== void 0 && { now: toolOptions.now }
18219
+ // Do NOT forward decomposeOptions into recall() — rewrite already
18220
+ // ran above, and forwarding would falsely trip `decompose-moved`
18221
+ // on every specific-mode high-budget call. graphRefine auth can
18222
+ // be threaded later when the tool opts into that path.
18223
+ ...toolOptions?.now !== void 0 && { now: toolOptions.now },
18224
+ ...subQueries && { subQueries }
18186
18225
  };
18187
18226
  const result = await recall(query, ctx, recallOpts);
18188
18227
  if (callbacks?.onChunksRetrieved) {
@@ -23590,7 +23629,7 @@ __decorateClass([
23590
23629
  ], VaultFolder.prototype, "context", 2);
23591
23630
 
23592
23631
  // src/lib/db/schema.ts
23593
- var SDK_SCHEMA_VERSION = 42;
23632
+ var SDK_SCHEMA_VERSION = 43;
23594
23633
  var sdkSchema = (0, import_watermelondb20.appSchema)({
23595
23634
  version: SDK_SCHEMA_VERSION,
23596
23635
  tables: [
@@ -23638,7 +23677,16 @@ var sdkSchema = (0, import_watermelondb20.appSchema)({
23638
23677
  { name: "conversation_id", type: "string", isIndexed: true },
23639
23678
  { name: "title", type: "string" },
23640
23679
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
23641
- { name: "created_at", type: "number" },
23680
+ // Indexed to match every other list-sorted created_at in this schema.
23681
+ // Note this single-column index is NOT what makes the conversation list
23682
+ // reads fast on native SQLite — they all filter is_deleted as well, and
23683
+ // SQLite will not combine the two indexes, so the composite created by
23684
+ // the v42 migration is what its planner actually uses. This declaration
23685
+ // still earns its keep on the other two adapters: LokiJS builds its
23686
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
23687
+ // outright), and Postgres gathers statistics via autovacuum, so its
23688
+ // planner can use a single-column index the SQLite planner skips.
23689
+ { name: "created_at", type: "number", isIndexed: true },
23642
23690
  { name: "updated_at", type: "number" },
23643
23691
  { name: "is_deleted", type: "boolean", isIndexed: true },
23644
23692
  { name: "pinned_at", type: "number", isOptional: true }
@@ -24505,6 +24553,49 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
24505
24553
  ]
24506
24554
  })
24507
24555
  ]
24556
+ },
24557
+ // v42 -> v43: Give the conversation list reads an index they can actually
24558
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
24559
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
24560
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
24561
+ // created_at desc`, so before this they resolved is_deleted through its
24562
+ // index and then built a temp B-tree over every live row just to return
24563
+ // the newest page.
24564
+ //
24565
+ // The index is COMPOSITE, and that is the whole point — a bare index on
24566
+ // created_at does not fix this. SQLite will not combine two single-column
24567
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
24568
+ // separately it still picks conversations_is_deleted and still temp-sorts.
24569
+ // It only prefers a lone created_at index once ANALYZE has populated
24570
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
24571
+ // native SQLite bindings, not this SDK. Every device is permanently in the
24572
+ // no-statistics state, so the index has to satisfy the filter and the sort
24573
+ // in one structure. `(is_deleted, created_at)` does: equality on the
24574
+ // leading column, ordered scan on the second, and the keyset boundary
24575
+ // becomes a range seek on the same index.
24576
+ //
24577
+ // unsafeExecuteSql, not addColumns: both columns already exist and
24578
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
24579
+ // to build one in place (same shape as the v19 -> v20 memory_vault
24580
+ // migration). IF NOT EXISTS keeps the step idempotent.
24581
+ //
24582
+ // Two limits worth knowing before someone re-measures this and finds it
24583
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
24584
+ // so existing browser databases gain nothing — web's only lever is the
24585
+ // `isIndexed` flag on the column itself. Second, this index reaches
24586
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
24587
+ // the encoded schema, and its schema format cannot express a composite
24588
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
24589
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
24590
+ // makes the whole schema fail structuredClone and takes the database down
24591
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
24592
+ {
24593
+ toVersion: 43,
24594
+ steps: [
24595
+ (0, import_migrations2.unsafeExecuteSql)(
24596
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
24597
+ )
24598
+ ]
24508
24599
  }
24509
24600
  ]
24510
24601
  });
@@ -6282,15 +6282,26 @@ interface MemoryVaultSearchOptions {
6282
6282
  /** RRF smoothing constant for lane fusion. Default 60. */
6283
6283
  rrfK?: number;
6284
6284
  /**
6285
- * LLM-based query decomposition for composite/abstract queries. When set,
6286
- * each query is classified + (if composite) decomposed into 3–5 facet
6287
- * sub-queries via gpt-5-mini, then ranked via {@link rankComposite}.
6288
- * Requires `decomposeOptions` (auth) when set to "llm".
6285
+ * Pre-decomposed facet queries for the composite ranker (719/B4). When
6286
+ * ≥2 are supplied (and embeddings are available), runs {@link rankComposite}
6287
+ * over them — no LLM call inside the search path. Callers that want LLM
6288
+ * rewrite (e.g. `createRecallTool`) call `decomposeQuery` first and pass
6289
+ * the facets here.
6290
+ */
6291
+ subQueries?: string[];
6292
+ /**
6293
+ * @deprecated 719/B4 — ignored by {@link searchVaultMemories} /
6294
+ * {@link searchVaultMemoriesWithSize}. Pass {@link MemoryVaultSearchOptions.subQueries}
6295
+ * (or use `createRecallTool`). The legacy {@link createMemoryVaultSearchTool}
6296
+ * executor still honors `decompose: "llm"` + `decomposeOptions` for eval
6297
+ * parity, then forwards facets into the LLM-free search path.
6289
6298
  */
6290
6299
  decompose?: "off" | "llm";
6291
- /** Auth + endpoint for the decomposition LLM call. Required when
6292
- * decompose="llm". Auth is the dual pattern — one of `apiKey` /
6293
- * `getToken`; see {@link PortalLlmAuth}. */
6300
+ /**
6301
+ * @deprecated 719/B4 — see `decompose`. Ignored on the programmatic search
6302
+ * path; tool-layer rewrite still reads this from the search-tool options.
6303
+ * Prefer `RecallToolOptions.decomposeOptions` with `createRecallTool`.
6304
+ */
6294
6305
  decomposeOptions?: PortalLlmAuth & {
6295
6306
  baseUrl?: string;
6296
6307
  model?: string;
@@ -6455,9 +6466,11 @@ type MemoryKind = "fact" | "chunk";
6455
6466
  * Budget controls retrieval depth/cost. Higher budgets enable more
6456
6467
  * candidate sources and the cross-encoder reranker.
6457
6468
  *
6458
- * - `low`: cosine + BM25 fusion only. No reranker. Mobile default.
6459
- * - `mid`: + recency boost. Demo default.
6460
- * - `high`: + cross-encoder rerank stage. Demo `budget: high` path.
6469
+ * - `low`: cosine + BM25 + recency. No reranker. Mobile default.
6470
+ * - `mid`: + cross-encoder rerank.
6471
+ * - `high`: + multi-hop graph traversal. LLM-free — query decomposition
6472
+ * (when wanted) lives in the tool/agent layer ({@link createRecallTool}),
6473
+ * not inside `recall()` itself (719/B4).
6461
6474
  */
6462
6475
  type Budget = "low" | "mid" | "high";
6463
6476
  interface ScoreBreakdown {
@@ -6557,10 +6570,28 @@ interface RecallOptions {
6557
6570
  /** Drop results below this score. Default: 0.1 for facts, 0.5 for chunks (mirrors today's defaults). */
6558
6571
  minScore?: number;
6559
6572
  /**
6560
- * Auth + endpoint for the LLM-based query decomposition pass. Without
6561
- * these, decompose is skipped even at `budget: 'high'`. Mirrors the
6562
- * shape used by `searchVaultMemories`. Auth is the dual pattern — one
6563
- * of `apiKey` / `getToken` is required; see {@link PortalLlmAuth}.
6573
+ * Pre-decomposed facet queries for the composite ranker. When ≥2 are
6574
+ * supplied, the vault lane runs `rankComposite` over them (no LLM call
6575
+ * inside `recall()` — 719/B4). Callers that still want LLM rewrite
6576
+ * (e.g. {@link createRecallTool} at `budget: 'high'`) call
6577
+ * `decomposeQuery` themselves and pass the result here. A single entry
6578
+ * (or omitting this) keeps the single-query path.
6579
+ */
6580
+ subQueries?: string[];
6581
+ /**
6582
+ * Auth + endpoint for optional LLM helpers that reuse portal auth —
6583
+ * currently {@link RecallOptions.graphRefine} neighbor selection.
6584
+ * Query decomposition is **not** driven by this field inside `recall()`
6585
+ * (719/B4); {@link createRecallTool} reads the same shape from
6586
+ * `RecallToolOptions.decomposeOptions` for tool-layer rewrite.
6587
+ *
6588
+ * Callers that still pass `{ budget: 'high', decomposeOptions }` without
6589
+ * {@link RecallOptions.subQueries} keep compiling but no longer rewrite —
6590
+ * `recall()` emits `decompose-moved` on {@link RecallDiagnostics.degraded}
6591
+ * so upgrades without a changelog read still leave a telemetry breadcrumb.
6592
+ *
6593
+ * Auth is the dual pattern — one of `apiKey` / `getToken` is required;
6594
+ * see {@link PortalLlmAuth}.
6564
6595
  */
6565
6596
  decomposeOptions?: PortalLlmAuth & {
6566
6597
  baseUrl?: string;
@@ -6664,9 +6695,22 @@ type RecallDegradation =
6664
6695
  /** Rerank was requested (budget mid/high) but the cross-encoder didn't run
6665
6696
  * this call — unavailable (e.g. React Native) or a transient failure. */
6666
6697
  "rerank-unavailable"
6667
- /** `budget: 'high'` requested but no `decomposeOptions`, so query
6668
- * decomposition was skipped and the budget downgraded to mid. */
6698
+ /**
6699
+ * @deprecated 719/B4 moved query decomposition out of `recall()`. Budget
6700
+ * `high` no longer requires `decomposeOptions` and this signal is never
6701
+ * emitted. Prefer `"decompose-moved"` for the upgrade breadcrumb. Kept in
6702
+ * the union so existing telemetry consumers stay typed.
6703
+ */
6669
6704
  | "decompose-unavailable"
6705
+ /**
6706
+ * `budget: 'high'` with `decomposeOptions` set but no usable
6707
+ * {@link RecallOptions.subQueries}. Pre-B4 this call shape triggered LLM
6708
+ * rewrite inside `recall()`; post-B4 it stays on the single-query high
6709
+ * path (rerank + graph) and callers must pass facets (or use
6710
+ * {@link createRecallTool}). Surfaced so silent quality regressions show
6711
+ * up in diagnostics instead of looking healthier than before.
6712
+ */
6713
+ | "decompose-moved"
6670
6714
  /** There was no usable cosine lane, so results were ranked on BM25 (lexical)
6671
6715
  * alone: either the query embedding failed (or came back empty), or no
6672
6716
  * candidate had a vector to score against it because the row (re)embed failed.
@@ -8706,9 +8750,14 @@ declare function rankProfileCandidates(inputs: readonly ProfileSalienceInput[],
8706
8750
  * Unified Recall API — single retrieval surface above vault + engine.
8707
8751
  *
8708
8752
  * Budget maps to pipeline depth (mirrors Hindsight's tiered defaults):
8709
- * - `low` → V2 cosine + BM25 + recency (no rerank, no decompose)
8753
+ * - `low` → V2 cosine + BM25 + recency (no rerank)
8710
8754
  * - `mid` → V2 + cross-encoder rerank
8711
- * - `high` → V2 + rerank + LLM query decomposition for composite queries
8755
+ * - `high` → V2 + rerank + multi-hop graph traversal
8756
+ *
8757
+ * LLM-free by design (719/B4): query decomposition for composite asks
8758
+ * lives in the tool/agent layer (`createRecallTool`), which may pass
8759
+ * pre-built `subQueries` into this API. `recall()` itself never pays an
8760
+ * LLM RTT.
8712
8761
  *
8713
8762
  * Fact + chunk lanes are fused with RRF (k=60). The previous naive
8714
8763
  * "sort the union by raw score" path is gone — score scales differ
@@ -8763,7 +8812,8 @@ interface RecallToolOptions {
8763
8812
  folderId?: string | null;
8764
8813
  /** Exclude one conversation from chunk results (typically the active one). */
8765
8814
  excludeConversationId?: string;
8766
- /** LLM-decompose options; only used at budget="high". Auth follows the
8815
+ /** LLM-decompose options; only used at budget="high". Runs in THIS tool
8816
+ * executor (719/B4) — `recall()` itself is LLM-free. Auth follows the
8767
8817
  * dual pattern: apiKey (server/CLI) or getToken (browser identity
8768
8818
  * tokens) — at least one required. */
8769
8819
  decomposeOptions?: PortalLlmAuth & {
@@ -10343,8 +10393,12 @@ declare class WatermelonChatStorageAdapter implements ChatStorageAdapter {
10343
10393
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
10344
10394
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
10345
10395
  * key on `updated_at`, so a topic-only change would neither upload nor merge
10396
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
10397
+ * conversation list read filters is_deleted and orders by created_at DESC,
10398
+ * which previously meant a temp B-tree sort of the whole live set on every
10399
+ * read. Structural only — no column added, no data rewritten
10346
10400
  */
10347
- declare const SDK_SCHEMA_VERSION = 42;
10401
+ declare const SDK_SCHEMA_VERSION = 43;
10348
10402
  /**
10349
10403
  * Combined WatermelonDB schema for all SDK storage modules.
10350
10404
  *
@@ -10431,6 +10485,7 @@ declare const sdkSchema: Readonly<{
10431
10485
  * - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
10432
10486
  * - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
10433
10487
  * - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
10488
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
10434
10489
  */
10435
10490
  declare const sdkMigrations: Readonly<{
10436
10491
  validated: true;