@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.
@@ -5031,6 +5031,107 @@ async function chunkAndEmbedAllMessages(ctx, options, filter) {
5031
5031
  return embeddedCount;
5032
5032
  }
5033
5033
 
5034
+ // src/lib/memoryVault/decomposeQuery.ts
5035
+ var DEFAULT_MODEL = "inclusionai/ling-2.6-flash";
5036
+ var MAX_SUB_QUERIES = 5;
5037
+ var SYSTEM_PROMPT = `You classify a memory query and, if needed, decompose it into concrete sub-queries.
5038
+
5039
+ CLASSIFICATION RULES \u2014 be strict:
5040
+ - "specific": asks for one fact OR a list of items in ONE category (one dimension).
5041
+ Examples: a name, a date, a preference, a location, an ID, a value, "what hobbies",
5042
+ "which conferences", "what open source projects", "what does X do for exercise".
5043
+ Even when the answer is a list, if all items belong to one dimension, it is specific.
5044
+ - "composite": asks about a person, system, or setup MULTI-DIMENSIONALLY \u2014 a summary,
5045
+ an overview, a profile, "tell me about X", "what is my X setup", "describe the team's
5046
+ X". The answer must span multiple distinct fact dimensions (e.g. for a "tech stack":
5047
+ language + database + framework + tools \u2014 four different dimensions).
5048
+
5049
+ When in doubt, prefer "specific". Mis-classifying a single-dimension list as composite
5050
+ hurts retrieval more than the reverse.
5051
+
5052
+ SUB-QUERY RULES (composite only, 3\u20135 questions):
5053
+ - Each sub-query targets a DIFFERENT fact dimension.
5054
+ - Use SHORT parenthetical examples \u2014 2\u20134 concrete options that anchor the
5055
+ embedding to likely memory content. Examples must be terms a user would
5056
+ actually have in their notes (e.g., "Postgres" not "PostgreSQL 15.2 with
5057
+ read replicas"). No verbose qualifiers like "and versions" or "and any
5058
+ relevant configuration details".
5059
+ - 8\u201314 words including the parenthetical. Like a human asking a follow-up.
5060
+
5061
+ Examples:
5062
+ "What is my dog's name?" \u2192 {"mode":"specific","subQueries":["What is my dog's name?"]}
5063
+ "What does the user do for exercise?" \u2192 {"mode":"specific","subQueries":["What does the user do for exercise?"]}
5064
+ "What conferences has the user attended?" \u2192 {"mode":"specific","subQueries":["What conferences has the user attended?"]}
5065
+ "What did I do last Tuesday?" \u2192 {"mode":"specific","subQueries":["What did I do last Tuesday?"]}
5066
+ "Tell me about the user as a person" \u2192 {"mode":"composite","subQueries":["What is the user's name?","Where does the user live?","What does the user do for work?","Any health conditions or allergies?","What are the user's hobbies (sports, music, games)?"]}
5067
+ "What is the user's tech stack?" \u2192 {"mode":"composite","subQueries":["What programming languages does the user use (Python, Go, TypeScript)?","What database does the user use (Postgres, MySQL, Mongo)?","What framework or ORM (React, Django, Prisma)?","What infrastructure (AWS, Docker, K8s)?","What testing tools (Jest, pytest, Cypress)?"]}
5068
+ "Describe the user's development environment" \u2192 {"mode":"composite","subQueries":["What operating system does the user use (macOS, Linux, Windows)?","What editor or IDE (VS Code, Vim, IntelliJ)?","What shell or terminal setup (zsh, bash, tmux)?","What keyboard or display setup (mechanical, vertical monitor)?","Any UI preferences (dark mode, font, vim keybindings)?"]}
5069
+
5070
+ Output strict JSON. No prose.`;
5071
+ async function decomposeQuery(query, options) {
5072
+ const fallback = { mode: "specific", subQueries: [query] };
5073
+ const trimmed = query.trim();
5074
+ if (trimmed.length === 0) return fallback;
5075
+ let parsed;
5076
+ try {
5077
+ parsed = await callPortalJsonCompletion({
5078
+ ...options.apiKey !== void 0 && { apiKey: options.apiKey },
5079
+ ...options.getToken !== void 0 && { getToken: options.getToken },
5080
+ ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
5081
+ model: options.model ?? DEFAULT_MODEL,
5082
+ systemPrompt: SYSTEM_PROMPT,
5083
+ // Wrap the bare query in a task framing so the model treats it as
5084
+ // input to classify, not as a question to answer conversationally.
5085
+ // Bare-query inputs caused Anthropic models to respond with prose
5086
+ // like "Do you mean...?" before this wrap.
5087
+ 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.
5088
+
5089
+ Query: ${trimmed}`,
5090
+ // Decompose runs ahead of recall in the tool layer — tighter than the
5091
+ // portalLlm default (consolidate/extract can wait longer).
5092
+ timeoutMs: 2e4,
5093
+ // No internal retry: a failure degrades to the safe `{specific,[query]}`
5094
+ // fallback below. The agent/tool loop is the retry surface (719/B4).
5095
+ maxAttempts: 1,
5096
+ tag: "memory/decompose",
5097
+ ...options.fetchFn && { fetchFn: options.fetchFn }
5098
+ });
5099
+ } catch (err) {
5100
+ getLogger().warn("memoryVault/decompose: portal call failed, falling back to specific", err);
5101
+ return fallback;
5102
+ }
5103
+ if (parsed === null) return fallback;
5104
+ return validate(parsed, trimmed) ?? fallback;
5105
+ }
5106
+ function normalizeSubQueries(raw) {
5107
+ if (!raw || raw.length === 0) return [];
5108
+ const seen = /* @__PURE__ */ new Set();
5109
+ const out = [];
5110
+ for (const s of raw) {
5111
+ if (typeof s !== "string") continue;
5112
+ const t = s.trim();
5113
+ if (t.length === 0) continue;
5114
+ const key = t.toLowerCase();
5115
+ if (seen.has(key)) continue;
5116
+ seen.add(key);
5117
+ out.push(t);
5118
+ if (out.length >= MAX_SUB_QUERIES) break;
5119
+ }
5120
+ return out;
5121
+ }
5122
+ function validate(parsed, originalQuery) {
5123
+ if (typeof parsed !== "object" || parsed === null) return null;
5124
+ const obj = parsed;
5125
+ const mode = obj.mode === "composite" ? "composite" : "specific";
5126
+ if (!Array.isArray(obj.subQueries)) return null;
5127
+ const subQueries = normalizeSubQueries(obj.subQueries);
5128
+ if (subQueries.length === 0) return null;
5129
+ if (mode === "specific") {
5130
+ return { mode: "specific", subQueries: [originalQuery] };
5131
+ }
5132
+ return { mode, subQueries };
5133
+ }
5134
+
5034
5135
  // src/lib/memory/mmr.ts
5035
5136
  function applyMMR(candidates, k, lambda = 0.5) {
5036
5137
  if (candidates.length === 0 || k <= 0) return [];
@@ -5408,91 +5509,6 @@ function scoreBM25(query, items) {
5408
5509
  return scoreBM25Prepared(query, prepareBM25Corpus(items));
5409
5510
  }
5410
5511
 
5411
- // src/lib/memoryVault/decomposeQuery.ts
5412
- var DEFAULT_MODEL = "inclusionai/ling-2.6-flash";
5413
- var MAX_SUB_QUERIES = 5;
5414
- var SYSTEM_PROMPT = `You classify a memory query and, if needed, decompose it into concrete sub-queries.
5415
-
5416
- CLASSIFICATION RULES \u2014 be strict:
5417
- - "specific": asks for one fact OR a list of items in ONE category (one dimension).
5418
- Examples: a name, a date, a preference, a location, an ID, a value, "what hobbies",
5419
- "which conferences", "what open source projects", "what does X do for exercise".
5420
- Even when the answer is a list, if all items belong to one dimension, it is specific.
5421
- - "composite": asks about a person, system, or setup MULTI-DIMENSIONALLY \u2014 a summary,
5422
- an overview, a profile, "tell me about X", "what is my X setup", "describe the team's
5423
- X". The answer must span multiple distinct fact dimensions (e.g. for a "tech stack":
5424
- language + database + framework + tools \u2014 four different dimensions).
5425
-
5426
- When in doubt, prefer "specific". Mis-classifying a single-dimension list as composite
5427
- hurts retrieval more than the reverse.
5428
-
5429
- SUB-QUERY RULES (composite only, 3\u20135 questions):
5430
- - Each sub-query targets a DIFFERENT fact dimension.
5431
- - Use SHORT parenthetical examples \u2014 2\u20134 concrete options that anchor the
5432
- embedding to likely memory content. Examples must be terms a user would
5433
- actually have in their notes (e.g., "Postgres" not "PostgreSQL 15.2 with
5434
- read replicas"). No verbose qualifiers like "and versions" or "and any
5435
- relevant configuration details".
5436
- - 8\u201314 words including the parenthetical. Like a human asking a follow-up.
5437
-
5438
- Examples:
5439
- "What is my dog's name?" \u2192 {"mode":"specific","subQueries":["What is my dog's name?"]}
5440
- "What does the user do for exercise?" \u2192 {"mode":"specific","subQueries":["What does the user do for exercise?"]}
5441
- "What conferences has the user attended?" \u2192 {"mode":"specific","subQueries":["What conferences has the user attended?"]}
5442
- "What did I do last Tuesday?" \u2192 {"mode":"specific","subQueries":["What did I do last Tuesday?"]}
5443
- "Tell me about the user as a person" \u2192 {"mode":"composite","subQueries":["What is the user's name?","Where does the user live?","What does the user do for work?","Any health conditions or allergies?","What are the user's hobbies (sports, music, games)?"]}
5444
- "What is the user's tech stack?" \u2192 {"mode":"composite","subQueries":["What programming languages does the user use (Python, Go, TypeScript)?","What database does the user use (Postgres, MySQL, Mongo)?","What framework or ORM (React, Django, Prisma)?","What infrastructure (AWS, Docker, K8s)?","What testing tools (Jest, pytest, Cypress)?"]}
5445
- "Describe the user's development environment" \u2192 {"mode":"composite","subQueries":["What operating system does the user use (macOS, Linux, Windows)?","What editor or IDE (VS Code, Vim, IntelliJ)?","What shell or terminal setup (zsh, bash, tmux)?","What keyboard or display setup (mechanical, vertical monitor)?","Any UI preferences (dark mode, font, vim keybindings)?"]}
5446
-
5447
- Output strict JSON. No prose.`;
5448
- async function decomposeQuery(query, options) {
5449
- const fallback = { mode: "specific", subQueries: [query] };
5450
- const trimmed = query.trim();
5451
- if (trimmed.length === 0) return fallback;
5452
- let parsed;
5453
- try {
5454
- parsed = await callPortalJsonCompletion({
5455
- ...options.apiKey !== void 0 && { apiKey: options.apiKey },
5456
- ...options.getToken !== void 0 && { getToken: options.getToken },
5457
- ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
5458
- model: options.model ?? DEFAULT_MODEL,
5459
- systemPrompt: SYSTEM_PROMPT,
5460
- // Wrap the bare query in a task framing so the model treats it as
5461
- // input to classify, not as a question to answer conversationally.
5462
- // Bare-query inputs caused Anthropic models to respond with prose
5463
- // like "Do you mean...?" before this wrap.
5464
- 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.
5465
-
5466
- Query: ${trimmed}`,
5467
- // Decompose runs on the recall hot path — tighter than the
5468
- // portalLlm default (consolidate/extract can wait longer).
5469
- timeoutMs: 2e4,
5470
- // No internal retry: a failure degrades to the safe `{specific,[query]}`
5471
- // fallback below, so retrying would only add latency on the hot path.
5472
- maxAttempts: 1,
5473
- tag: "memory/decompose",
5474
- ...options.fetchFn && { fetchFn: options.fetchFn }
5475
- });
5476
- } catch (err) {
5477
- getLogger().warn("memoryVault/decompose: portal call failed, falling back to specific", err);
5478
- return fallback;
5479
- }
5480
- if (parsed === null) return fallback;
5481
- return validate(parsed, trimmed) ?? fallback;
5482
- }
5483
- function validate(parsed, originalQuery) {
5484
- if (typeof parsed !== "object" || parsed === null) return null;
5485
- const obj = parsed;
5486
- const mode = obj.mode === "composite" ? "composite" : "specific";
5487
- if (!Array.isArray(obj.subQueries)) return null;
5488
- const subQueries = obj.subQueries.filter((s) => typeof s === "string").map((s) => s.trim()).filter((s) => s.length > 0).slice(0, MAX_SUB_QUERIES);
5489
- if (subQueries.length === 0) return null;
5490
- if (mode === "specific") {
5491
- return { mode: "specific", subQueries: [originalQuery] };
5492
- }
5493
- return { mode, subQueries };
5494
- }
5495
-
5496
5512
  // src/lib/memoryVault/lruCache.ts
5497
5513
  var DEFAULT_VAULT_CACHE_SIZE = 5e3;
5498
5514
  var LRUCache = class extends Map {
@@ -5526,6 +5542,7 @@ function createVaultEmbeddingCache(maxSize = DEFAULT_VAULT_CACHE_SIZE) {
5526
5542
  }
5527
5543
 
5528
5544
  // src/lib/memoryVault/searchTool.ts
5545
+ var warnedProgrammaticDecomposeIgnored = false;
5529
5546
  function normalizeEventTimeKind(kind) {
5530
5547
  return kind === "point" || kind === "range" || kind === "ongoing" ? kind : null;
5531
5548
  }
@@ -6460,52 +6477,51 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
6460
6477
  ...searchOptions?.rrfK !== void 0 && { rrfK: searchOptions.rrfK },
6461
6478
  ...searchOptions?.factTypeWeights && { factTypeWeights: searchOptions.factTypeWeights }
6462
6479
  };
6463
- if (useFusion && !embeddingsUnavailable && searchOptions?.decompose === "llm" && searchOptions.decomposeOptions) {
6464
- const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
6465
- if (decomp.mode === "composite") {
6466
- let subEmbeddings;
6467
- try {
6468
- subEmbeddings = await generateEmbeddings(decomp.subQueries, embeddingOptions);
6469
- } catch (err) {
6470
- getLogger().warn(
6471
- "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
6472
- );
6473
- subEmbeddings = [];
6474
- }
6475
- const subEmbeddingsUsable = decomp.subQueries.length > 0 && subEmbeddings.length === decomp.subQueries.length && subEmbeddings.every((v) => v.length > 0 && v.length === queryEmbedding.length);
6476
- if (subEmbeddings.length > 0 && !subEmbeddingsUsable) {
6477
- getLogger().warn(
6478
- `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`
6479
- );
6480
- }
6481
- if (subEmbeddingsUsable) {
6482
- const subQueries = decomp.subQueries.map((sq, i) => ({
6483
- query: sq,
6484
- embedding: subEmbeddings[i]
6485
- }));
6486
- const v2HeadStats = { hadResults: false };
6487
- const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
6488
- limit,
6489
- minSimilarity,
6490
- rerank: !!searchOptions.rerank,
6491
- ...searchOptions.rerankTopN !== void 0 && {
6492
- rerankTopN: searchOptions.rerankTopN
6493
- },
6494
- ...searchOptions.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
6495
- ...searchOptions.mmr !== void 0 && { mmr: searchOptions.mmr },
6496
- ...tuning,
6497
- ...searchOptions.entityRanking && { entityRanking: searchOptions.entityRanking },
6498
- ...searchOptions.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
6499
- rerankStats,
6500
- v2HeadStats
6501
- });
6502
- return stampTimestamps({
6503
- results: composite,
6504
- vaultSize,
6505
- reranked: rerankStats.applied,
6506
- hadV2Head: v2HeadStats.hadResults
6507
- });
6508
- }
6480
+ const normalizedFacets = normalizeSubQueries(searchOptions?.subQueries);
6481
+ const facetQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
6482
+ if (useFusion && !embeddingsUnavailable && facetQueries) {
6483
+ let subEmbeddings;
6484
+ try {
6485
+ subEmbeddings = await generateEmbeddings(facetQueries, embeddingOptions);
6486
+ } catch (err) {
6487
+ getLogger().warn(
6488
+ "memoryVault: sub-query embedding failed \u2014 falling back to single-query ranking: " + (err instanceof Error ? err.message : String(err))
6489
+ );
6490
+ subEmbeddings = [];
6491
+ }
6492
+ const subEmbeddingsUsable = facetQueries.length > 0 && subEmbeddings.length === facetQueries.length && subEmbeddings.every((v) => v.length > 0 && v.length === queryEmbedding.length);
6493
+ if (subEmbeddings.length > 0 && !subEmbeddingsUsable) {
6494
+ getLogger().warn(
6495
+ `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`
6496
+ );
6497
+ }
6498
+ if (subEmbeddingsUsable) {
6499
+ const subQueries = facetQueries.map((sq, i) => ({
6500
+ query: sq,
6501
+ embedding: subEmbeddings[i]
6502
+ }));
6503
+ const v2HeadStats = { hadResults: false };
6504
+ const composite = await rankComposite(query, queryEmbedding, subQueries, embeddedItems, {
6505
+ limit,
6506
+ minSimilarity,
6507
+ rerank: !!searchOptions?.rerank,
6508
+ ...searchOptions?.rerankTopN !== void 0 && {
6509
+ rerankTopN: searchOptions.rerankTopN
6510
+ },
6511
+ ...searchOptions?.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
6512
+ ...searchOptions?.mmr !== void 0 && { mmr: searchOptions.mmr },
6513
+ ...tuning,
6514
+ ...searchOptions?.entityRanking && { entityRanking: searchOptions.entityRanking },
6515
+ ...searchOptions?.temporalRanking && { temporalRanking: searchOptions.temporalRanking },
6516
+ rerankStats,
6517
+ v2HeadStats
6518
+ });
6519
+ return stampTimestamps({
6520
+ results: composite,
6521
+ vaultSize,
6522
+ reranked: rerankStats.applied,
6523
+ hadV2Head: v2HeadStats.hadResults
6524
+ });
6509
6525
  }
6510
6526
  }
6511
6527
  if (useFusion && searchOptions?.rerank) {
@@ -6565,6 +6581,12 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
6565
6581
  rankedOnCosine: false
6566
6582
  };
6567
6583
  }
6584
+ if (searchOptions?.decompose === "llm" && normalizeSubQueries(searchOptions.subQueries).length < 2 && !warnedProgrammaticDecomposeIgnored) {
6585
+ warnedProgrammaticDecomposeIgnored = true;
6586
+ getLogger().warn(
6587
+ 'memoryVault: decompose:"llm" is ignored by searchVaultMemories \u2014 pass subQueries or use createMemoryVaultSearchTool / createRecallTool (719/B4)'
6588
+ );
6589
+ }
6568
6590
  const prepared = await prepareVaultCandidates(
6569
6591
  query,
6570
6592
  vaultCtx,
@@ -6670,7 +6692,8 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6670
6692
  return "Error: A search query is required.";
6671
6693
  }
6672
6694
  try {
6673
- const budget = searchOptions?.decompose === "llm" && searchOptions.decomposeOptions ? "high" : searchOptions?.rerank ? "mid" : "low";
6695
+ const wantsDecompose = searchOptions?.decompose === "llm" && !!searchOptions.decomposeOptions;
6696
+ const budget = wantsDecompose ? "high" : searchOptions?.rerank ? "mid" : "low";
6674
6697
  const folderId = searchOptions?.folderId ?? argFolderId;
6675
6698
  if (searchOptions?.useFusion === false) {
6676
6699
  const { results: legacy, embeddingsUnavailable } = await searchVaultMemoriesWithSize(
@@ -6694,7 +6717,14 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6694
6717
  legacy.map((r) => ({ id: r.uniqueId, content: r.content, score: r.similarity }))
6695
6718
  );
6696
6719
  }
6697
- const { recall: recall2 } = await import("./recall-FPPCKYYH.mjs");
6720
+ let subQueries;
6721
+ if (wantsDecompose && searchOptions?.decomposeOptions) {
6722
+ const decomp = await decomposeQuery(query, searchOptions.decomposeOptions);
6723
+ if (decomp.mode === "composite" && decomp.subQueries.length >= 2) {
6724
+ subQueries = decomp.subQueries;
6725
+ }
6726
+ }
6727
+ const { recall: recall2 } = await import("./recall-DVX4W2UD.mjs");
6698
6728
  let recallDegraded = [];
6699
6729
  const result = await recall2(
6700
6730
  query,
@@ -6710,9 +6740,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6710
6740
  ...tuningForward,
6711
6741
  ...folderId !== void 0 && { folderId },
6712
6742
  ...searchOptions?.scopes && { scopes: searchOptions.scopes },
6713
- ...searchOptions?.decompose === "llm" && searchOptions.decomposeOptions && {
6714
- decomposeOptions: searchOptions.decomposeOptions
6715
- }
6743
+ ...subQueries && { subQueries }
6716
6744
  }
6717
6745
  );
6718
6746
  if (result.vaultSize === 0) {
@@ -7240,12 +7268,12 @@ var DEFAULT_CHUNK_MIN_SCORE = 0.5;
7240
7268
  function flagsForBudget(budget) {
7241
7269
  switch (budget) {
7242
7270
  case "high":
7243
- return { rerank: true, decompose: true, traverse: true };
7271
+ return { rerank: true, traverse: true };
7244
7272
  case "mid":
7245
- return { rerank: true, decompose: false, traverse: false };
7273
+ return { rerank: true, traverse: false };
7246
7274
  case "low":
7247
7275
  default:
7248
- return { rerank: false, decompose: false, traverse: false };
7276
+ return { rerank: false, traverse: false };
7249
7277
  }
7250
7278
  }
7251
7279
  function dedupeBy(items, ...keys) {
@@ -7264,10 +7292,10 @@ function dedupeBy(items, ...keys) {
7264
7292
  async function recall(query, ctx, options = {}) {
7265
7293
  const types = options.types ?? ["fact"];
7266
7294
  const limit = options.limit ?? DEFAULT_LIMIT;
7267
- const requestedBudget = options.budget ?? DEFAULT_BUDGET;
7268
- const flags = flagsForBudget(requestedBudget);
7269
- const decomposeAvailable = flags.decompose && !!options.decomposeOptions;
7270
- const usedBudget = requestedBudget === "high" && !decomposeAvailable ? "mid" : requestedBudget;
7295
+ const usedBudget = options.budget ?? DEFAULT_BUDGET;
7296
+ const flags = flagsForBudget(usedBudget);
7297
+ const normalizedFacets = normalizeSubQueries(options.subQueries);
7298
+ const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
7271
7299
  const t0 = nowMs();
7272
7300
  let prepMs = 0;
7273
7301
  let factLaneMs = 0;
@@ -7288,7 +7316,9 @@ async function recall(query, ctx, options = {}) {
7288
7316
  if (flags.rerank && factResults.length > 0 && hadV2Head && !didRerank) {
7289
7317
  degraded.push("rerank-unavailable");
7290
7318
  }
7291
- if (flags.decompose && !decomposeAvailable) degraded.push("decompose-unavailable");
7319
+ if (usedBudget === "high" && options.decomposeOptions && !subQueries && !options.graphRefine) {
7320
+ degraded.push("decompose-moved");
7321
+ }
7292
7322
  if (embeddingsUnavailable) degraded.push("embeddings-unavailable");
7293
7323
  const diagnostics = {
7294
7324
  usedBudget,
@@ -7401,10 +7431,8 @@ async function recall(query, ctx, options = {}) {
7401
7431
  },
7402
7432
  ...options.rrfK !== void 0 && { rrfK: options.rrfK },
7403
7433
  ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
7404
- ...decomposeAvailable && {
7405
- decompose: "llm",
7406
- decomposeOptions: options.decomposeOptions
7407
- },
7434
+ // 719/B4 — composite facets arrive pre-built; no LLM call here.
7435
+ ...subQueries && { subQueries },
7408
7436
  ...options.scopes && { scopes: options.scopes },
7409
7437
  ...options.folderId !== void 0 && { folderId: options.folderId },
7410
7438
  ...options.factTypes?.length && { factTypes: options.factTypes },
@@ -7777,6 +7805,7 @@ export {
7777
7805
  EMBEDDINGS_DEGRADED_EMPTY,
7778
7806
  RerankerUnavailableError,
7779
7807
  isRerankerAvailable,
7808
+ decomposeQuery,
7780
7809
  DEFAULT_VAULT_CACHE_SIZE,
7781
7810
  LRUCache,
7782
7811
  createVaultEmbeddingCache,