@anuma/sdk 6.10.0 → 6.12.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.
@@ -6791,9 +6791,9 @@ function recencyMultiplier(updatedAt, opts = {}) {
6791
6791
  const updatedMs = updatedAt.getTime();
6792
6792
  if (!Number.isFinite(updatedMs)) return noDate;
6793
6793
  const now = opts.now ?? /* @__PURE__ */ new Date();
6794
- const nowMs2 = now.getTime();
6795
- if (!Number.isFinite(nowMs2)) return noDate;
6796
- const ageDays2 = Math.max(0, (nowMs2 - updatedMs) / DAY_MS2);
6794
+ const nowMs3 = now.getTime();
6795
+ if (!Number.isFinite(nowMs3)) return noDate;
6796
+ const ageDays2 = Math.max(0, (nowMs3 - updatedMs) / DAY_MS2);
6797
6797
  const perYearDecay = opts.perYearDecay ?? DEFAULT_PER_YEAR_DECAY;
6798
6798
  const floor = opts.floor ?? DEFAULT_FLOOR;
6799
6799
  const decay = perYearDecay * (ageDays2 / 365);
@@ -7148,6 +7148,9 @@ async function decomposeQuery(query, options) {
7148
7148
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
7149
7149
  model: options.model ?? DEFAULT_MODEL2,
7150
7150
  systemPrompt: SYSTEM_PROMPT2,
7151
+ // The query itself rides the user turn below, so SYSTEM_PROMPT is the whole
7152
+ // fixed instruction — which is what lets the portal own it outright.
7153
+ taskType: "memory_decompose",
7151
7154
  // Wrap the bare query in a task framing so the model treats it as
7152
7155
  // input to classify, not as a question to answer conversationally.
7153
7156
  // Bare-query inputs caused Anthropic models to respond with prose
@@ -7491,24 +7494,19 @@ function createLlmNeighborRefiner(options) {
7491
7494
  async refine(query, candidates, limit) {
7492
7495
  if (candidates.length === 0) return [];
7493
7496
  const numbered = candidates.map((name, i) => `[${i + 1}] ${name}`).join("\n");
7494
- const systemPrompt = `You help a memory-retrieval system decide which related topics to explore.
7495
-
7496
- Given a user's question and a numbered list of candidate topics/entities linked to memories found so far, pick the ones most likely to lead to memories that help ANSWER the question. Choose at most ${limit}. Prefer topics semantically related to the question; ignore incidental ones.
7497
-
7498
- Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
7499
7497
  const parsed = await callPortalJsonCompletion({
7500
7498
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
7501
7499
  ...options.getToken !== void 0 && { getToken: options.getToken },
7502
7500
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
7503
7501
  taskType: "memory_graph",
7504
7502
  model: options.model ?? DEFAULT_REFINER_MODEL,
7505
- systemPrompt,
7503
+ systemPrompt: REFINER_SYSTEM_PROMPT,
7506
7504
  userMessage: `Question: ${query}
7507
7505
 
7508
7506
  Candidate topics:
7509
7507
  ${numbered}
7510
7508
 
7511
- Which should be expanded?`,
7509
+ Which should be expanded? Choose at most ${limit}.`,
7512
7510
  tag: "memory/graph-refine",
7513
7511
  maxAttempts: options.maxAttempts ?? DEFAULT_REFINER_ATTEMPTS,
7514
7512
  totalTimeoutMs: options.totalTimeoutMs ?? DEFAULT_REFINER_TOTAL_TIMEOUT_MS,
@@ -7529,7 +7527,7 @@ Which should be expanded?`,
7529
7527
  }
7530
7528
  };
7531
7529
  }
7532
- var MAX_HOPS, ENTITY_FANOUT, MIN_REFINER_CANDIDATES, MAX_REFINER_CANDIDATES, NODE_BUDGET, VAULT_SIZE_HOP_CAP, DEFAULT_REFINER_MODEL, DEFAULT_REFINER_ATTEMPTS, DEFAULT_REFINER_TOTAL_TIMEOUT_MS;
7530
+ var MAX_HOPS, ENTITY_FANOUT, MIN_REFINER_CANDIDATES, MAX_REFINER_CANDIDATES, NODE_BUDGET, VAULT_SIZE_HOP_CAP, DEFAULT_REFINER_MODEL, DEFAULT_REFINER_ATTEMPTS, DEFAULT_REFINER_TOTAL_TIMEOUT_MS, REFINER_SYSTEM_PROMPT;
7533
7531
  var init_graphTraversal = __esm({
7534
7532
  "src/lib/memory/graphTraversal.ts"() {
7535
7533
  "use strict";
@@ -7548,6 +7546,11 @@ var init_graphTraversal = __esm({
7548
7546
  DEFAULT_REFINER_MODEL = "inclusionai/ling-2.6-flash";
7549
7547
  DEFAULT_REFINER_ATTEMPTS = 1;
7550
7548
  DEFAULT_REFINER_TOTAL_TIMEOUT_MS = 8e3;
7549
+ REFINER_SYSTEM_PROMPT = `You help a memory-retrieval system decide which related topics to explore.
7550
+
7551
+ Given a user's question and a numbered list of candidate topics/entities linked to memories found so far, pick the ones most likely to lead to memories that help ANSWER the question. Never return more than the maximum the user turn asks for. Prefer topics semantically related to the question; ignore incidental ones.
7552
+
7553
+ Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
7551
7554
  }
7552
7555
  });
7553
7556
 
@@ -7847,6 +7850,9 @@ async function recall(query, ctx, options = {}) {
7847
7850
  const chunkResults = [];
7848
7851
  let vaultSize;
7849
7852
  let didRerank = false;
7853
+ let rerankMs = 0;
7854
+ let queryEmbedMs = 0;
7855
+ let vaultRowsEmbedded;
7850
7856
  let hadV2Head = false;
7851
7857
  let embeddingsUnavailable = false;
7852
7858
  let chunkEmbedFailed = false;
@@ -7871,12 +7877,15 @@ async function recall(query, ctx, options = {}) {
7871
7877
  ...vaultSize !== void 0 && { vaultSize },
7872
7878
  ...decryptLastRan !== void 0 && { decryptLast: decryptLastRan },
7873
7879
  ...vaultRowsDecrypted !== void 0 && { vaultRowsDecrypted },
7880
+ ...vaultRowsEmbedded !== void 0 && { vaultRowsEmbedded },
7874
7881
  factCount: factResults.length,
7875
7882
  chunkCount: chunkResults.length,
7876
7883
  timings: {
7877
7884
  total: nowMs() - t0,
7878
7885
  prep: prepMs,
7879
7886
  factLane: factLaneMs,
7887
+ rerank: rerankMs,
7888
+ queryEmbed: queryEmbedMs,
7880
7889
  chunkLane: chunkLaneMs,
7881
7890
  fuse: fuseMs
7882
7891
  },
@@ -7940,6 +7949,9 @@ async function recall(query, ctx, options = {}) {
7940
7949
  results,
7941
7950
  vaultSize: size,
7942
7951
  reranked,
7952
+ rerankMs: factRerankMs,
7953
+ queryEmbedMs: factQueryEmbedMs,
7954
+ rowsEmbedded: factRowsEmbedded,
7943
7955
  hadV2Head: v2Head,
7944
7956
  embeddingsUnavailable: factEmbeddingsUnavailable,
7945
7957
  rankedOnCosine: factRankedOnCosine,
@@ -7998,6 +8010,9 @@ async function recall(query, ctx, options = {}) {
7998
8010
  );
7999
8011
  vaultSize = size;
8000
8012
  didRerank = reranked;
8013
+ rerankMs = factRerankMs;
8014
+ queryEmbedMs = factQueryEmbedMs;
8015
+ vaultRowsEmbedded = factRowsEmbedded;
8001
8016
  hadV2Head = v2Head;
8002
8017
  if (factEmbeddingsUnavailable) embeddingsUnavailable = true;
8003
8018
  factLaneRankedOnCosine = factRankedOnCosine;
@@ -8462,6 +8477,7 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
8462
8477
  const rerankTopN = options.rerankTopN ?? 30;
8463
8478
  const ceWeight = options.ceWeight ?? 0.1;
8464
8479
  const headSlice = v2Ranked.slice(0, rerankTopN);
8480
+ const ceStart = nowMs2();
8465
8481
  try {
8466
8482
  const reranked = await rerankPairs(
8467
8483
  query,
@@ -8489,6 +8505,8 @@ async function rankFusedVaultMemoriesAsync(query, queryEmbedding, items, options
8489
8505
  getLogger().warn("[memory/search] cross-encoder rerank failed; using V2 ranking", err);
8490
8506
  }
8491
8507
  combined = v2Ranked;
8508
+ } finally {
8509
+ if (options.rerankStats) options.rerankStats.ms += nowMs2() - ceStart;
8492
8510
  }
8493
8511
  } else {
8494
8512
  combined = v2Ranked;
@@ -8655,6 +8673,7 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
8655
8673
  const ceWeight = options.ceWeight ?? 0.1;
8656
8674
  const headSlice = combined.slice(0, rerankTopN);
8657
8675
  const tailSlice = combined.slice(rerankTopN);
8676
+ const ceStart = nowMs2();
8658
8677
  try {
8659
8678
  const reranked = await rerankPairs(
8660
8679
  originalQuery,
@@ -8682,6 +8701,8 @@ async function rankComposite(originalQuery, originalQueryEmbedding, subQueries,
8682
8701
  err
8683
8702
  );
8684
8703
  }
8704
+ } finally {
8705
+ if (options.rerankStats) options.rerankStats.ms += nowMs2() - ceStart;
8685
8706
  }
8686
8707
  }
8687
8708
  if (options?.mmr) {
@@ -8808,11 +8829,13 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
8808
8829
  rowsDecrypted: 0
8809
8830
  };
8810
8831
  }
8832
+ const queryEmbedStart = nowMs2();
8811
8833
  const queryEmbedding = await embedQueryOrDegrade(
8812
8834
  query,
8813
8835
  embeddingOptions,
8814
8836
  opts.onEmbeddingDegraded
8815
8837
  );
8838
+ if (opts.embedStats) opts.embedStats.queryMs += nowMs2() - queryEmbedStart;
8816
8839
  const embeddingsDegraded = queryEmbedding.length === 0;
8817
8840
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
8818
8841
  const vectored = [];
@@ -8865,6 +8888,7 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
8865
8888
  const laneRows = laneFetched.filter((m) => !isEncrypted(m.content));
8866
8889
  if (laneRows.length > 0) {
8867
8890
  let laneVecs;
8891
+ if (opts.embedStats) opts.embedStats.rowsEmbedded += laneRows.length;
8868
8892
  try {
8869
8893
  laneVecs = await generateEmbeddings(
8870
8894
  laneRows.map((m) => m.content),
@@ -8943,6 +8967,10 @@ async function buildProjectedCorpus(query, vaultCtx, embeddingOptions, cache, qu
8943
8967
  async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache, searchOptions) {
8944
8968
  if (!query || typeof query !== "string") {
8945
8969
  return {
8970
+ // Nothing was read and nothing was embedded — the short-circuit fires
8971
+ // before either.
8972
+ queryEmbedMs: 0,
8973
+ rowsEmbedded: 0,
8946
8974
  memories: [],
8947
8975
  embeddedItems: [],
8948
8976
  queryEmbedding: [],
@@ -8975,6 +9003,7 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
8975
9003
  embeddingsUnavailable = true;
8976
9004
  embeddingFailure = true;
8977
9005
  };
9006
+ const embedStats = { queryMs: 0, rowsEmbedded: 0 };
8978
9007
  if (searchOptions?.decryptLast) {
8979
9008
  const forceIncludeIds = [
8980
9009
  ...searchOptions.entityRanking ?? [],
@@ -8986,7 +9015,8 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
8986
9015
  admitFloor: ADMIT_FLOOR,
8987
9016
  unembeddedCap: UNEMBEDDED_CAP,
8988
9017
  ...forceIncludeIds.length > 0 && { forceIncludeIds },
8989
- onEmbeddingDegraded
9018
+ onEmbeddingDegraded,
9019
+ embedStats
8990
9020
  });
8991
9021
  if (corpus.vaultSize === 0) {
8992
9022
  return {
@@ -8997,7 +9027,9 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
8997
9027
  embeddingsUnavailable: false,
8998
9028
  embeddingFailure: false,
8999
9029
  decryptLast: true,
9000
- rowsDecrypted: corpus.rowsDecrypted
9030
+ rowsDecrypted: corpus.rowsDecrypted,
9031
+ queryEmbedMs: embedStats.queryMs,
9032
+ rowsEmbedded: embedStats.rowsEmbedded
9001
9033
  };
9002
9034
  }
9003
9035
  ({ memories, embeddedItems, queryEmbedding, vaultSize } = corpus);
@@ -9014,7 +9046,9 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
9014
9046
  decryptLast: true,
9015
9047
  // Rows WERE decrypt-attempted even though none came back readable — that
9016
9048
  // is the whole point of reporting attempts rather than successes.
9017
- rowsDecrypted: corpus.rowsDecrypted
9049
+ rowsDecrypted: corpus.rowsDecrypted,
9050
+ queryEmbedMs: embedStats.queryMs,
9051
+ rowsEmbedded: embedStats.rowsEmbedded
9018
9052
  };
9019
9053
  }
9020
9054
  } else {
@@ -9039,10 +9073,14 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
9039
9073
  embeddingsUnavailable: false,
9040
9074
  embeddingFailure: false,
9041
9075
  decryptLast: false,
9042
- rowsDecrypted: loaded.length
9076
+ rowsDecrypted: loaded.length,
9077
+ queryEmbedMs: embedStats.queryMs,
9078
+ rowsEmbedded: embedStats.rowsEmbedded
9043
9079
  };
9044
9080
  }
9081
+ const legacyEmbedStart = nowMs2();
9045
9082
  queryEmbedding = await embedQueryOrDegrade(query, embeddingOptions, onEmbeddingDegraded);
9083
+ embedStats.queryMs += nowMs2() - legacyEmbedStart;
9046
9084
  const currentModel = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
9047
9085
  const uncachedTexts = [];
9048
9086
  const uncachedIndices = [];
@@ -9076,6 +9114,7 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
9076
9114
  }
9077
9115
  if (uncachedTexts.length > 0) {
9078
9116
  let newEmbeddings;
9117
+ embedStats.rowsEmbedded += uncachedTexts.length;
9079
9118
  try {
9080
9119
  newEmbeddings = await generateEmbeddings(uncachedTexts, embeddingOptions);
9081
9120
  } catch (err) {
@@ -9119,6 +9158,8 @@ async function prepareVaultCandidates(query, vaultCtx, embeddingOptions, cache,
9119
9158
  embeddingsUnavailable = true;
9120
9159
  }
9121
9160
  return {
9161
+ queryEmbedMs: embedStats.queryMs,
9162
+ rowsEmbedded: embedStats.rowsEmbedded,
9122
9163
  memories,
9123
9164
  embeddedItems,
9124
9165
  queryEmbedding,
@@ -9169,11 +9210,15 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
9169
9210
  results: results2,
9170
9211
  vaultSize: out.vaultSize,
9171
9212
  reranked: out.reranked ?? false,
9213
+ // Read straight off the shared stats object rather than being passed in at
9214
+ // each call site: every return in this function funnels through here, so a
9215
+ // path added later reports the CE bill without having to remember to.
9216
+ rerankMs: rerankStats.ms,
9172
9217
  hadV2Head: out.hadV2Head ?? false,
9173
9218
  embeddingsUnavailable
9174
9219
  };
9175
9220
  };
9176
- const rerankStats = { applied: false };
9221
+ const rerankStats = { applied: false, ms: 0 };
9177
9222
  const useFusion = searchOptions?.useFusion ?? true;
9178
9223
  const tuning = {
9179
9224
  ...searchOptions?.recencyAlpha !== void 0 && { recencyAlpha: searchOptions.recencyAlpha },
@@ -9292,13 +9337,16 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
9292
9337
  results: [],
9293
9338
  vaultSize: 0,
9294
9339
  reranked: false,
9340
+ rerankMs: 0,
9295
9341
  hadV2Head: false,
9296
9342
  embeddingsUnavailable: false,
9297
9343
  rankedOnCosine: false,
9298
9344
  // No storage read happened, so nothing was decrypted; report the path the
9299
9345
  // caller asked for so this turn is still attributable.
9300
9346
  decryptLast: !!searchOptions?.decryptLast,
9301
- rowsDecrypted: 0
9347
+ rowsDecrypted: 0,
9348
+ queryEmbedMs: 0,
9349
+ rowsEmbedded: 0
9302
9350
  };
9303
9351
  }
9304
9352
  if (searchOptions?.decompose === "llm" && normalizeSubQueries(searchOptions.subQueries).length < 2 && !warnedProgrammaticDecomposeIgnored) {
@@ -9319,11 +9367,14 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
9319
9367
  results: [],
9320
9368
  vaultSize: 0,
9321
9369
  reranked: false,
9370
+ rerankMs: 0,
9322
9371
  hadV2Head: false,
9323
9372
  embeddingsUnavailable: prepared.embeddingsUnavailable,
9324
9373
  rankedOnCosine: false,
9325
9374
  decryptLast: prepared.decryptLast,
9326
- rowsDecrypted: prepared.rowsDecrypted
9375
+ rowsDecrypted: prepared.rowsDecrypted,
9376
+ queryEmbedMs: prepared.queryEmbedMs,
9377
+ rowsEmbedded: prepared.rowsEmbedded
9327
9378
  };
9328
9379
  }
9329
9380
  if (prepared.memories.length === 0) {
@@ -9331,11 +9382,14 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
9331
9382
  results: [],
9332
9383
  vaultSize: prepared.vaultSize,
9333
9384
  reranked: false,
9385
+ rerankMs: 0,
9334
9386
  hadV2Head: false,
9335
9387
  embeddingsUnavailable: prepared.embeddingsUnavailable,
9336
9388
  rankedOnCosine: false,
9337
9389
  decryptLast: prepared.decryptLast,
9338
- rowsDecrypted: prepared.rowsDecrypted
9390
+ rowsDecrypted: prepared.rowsDecrypted,
9391
+ queryEmbedMs: prepared.queryEmbedMs,
9392
+ rowsEmbedded: prepared.rowsEmbedded
9339
9393
  };
9340
9394
  }
9341
9395
  const ranked = await rankPreparedVaultCandidates(
@@ -9348,7 +9402,9 @@ async function searchVaultMemoriesWithSize(query, vaultCtx, embeddingOptions, ca
9348
9402
  ...ranked,
9349
9403
  rankedOnCosine: !ranked.embeddingsUnavailable,
9350
9404
  decryptLast: prepared.decryptLast,
9351
- rowsDecrypted: prepared.rowsDecrypted
9405
+ rowsDecrypted: prepared.rowsDecrypted,
9406
+ queryEmbedMs: prepared.queryEmbedMs,
9407
+ rowsEmbedded: prepared.rowsEmbedded
9352
9408
  };
9353
9409
  }
9354
9410
  async function searchVaultMemories(query, vaultCtx, embeddingOptions, cache, searchOptions) {
@@ -9498,7 +9554,7 @@ ${formatted}`;
9498
9554
  }
9499
9555
  };
9500
9556
  }
9501
- var warnedProgrammaticDecomposeIgnored, SUPERSESSION_SIMILARITY_THRESHOLD, SUPERSESSION_MIN_AGE_GAP_MS, SUPERSESSION_BOOST_FACTOR, SUPERSESSION_MAX_WINDOW, EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL;
9557
+ var nowMs2, warnedProgrammaticDecomposeIgnored, SUPERSESSION_SIMILARITY_THRESHOLD, SUPERSESSION_MIN_AGE_GAP_MS, SUPERSESSION_BOOST_FACTOR, SUPERSESSION_MAX_WINDOW, EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL;
9502
9558
  var init_searchTool = __esm({
9503
9559
  "src/lib/memoryVault/searchTool.ts"() {
9504
9560
  "use strict";
@@ -9516,6 +9572,7 @@ var init_searchTool = __esm({
9516
9572
  init_bm25();
9517
9573
  init_decomposeQuery();
9518
9574
  init_lruCache();
9575
+ nowMs2 = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
9519
9576
  warnedProgrammaticDecomposeIgnored = false;
9520
9577
  SUPERSESSION_SIMILARITY_THRESHOLD = 0.7;
9521
9578
  SUPERSESSION_MIN_AGE_GAP_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -18984,6 +19041,7 @@ ${TRUNCATION_NOTICE}` : formatted;
18984
19041
  // src/lib/memory/reflect.ts
18985
19042
  init_clientConfig();
18986
19043
  init_logger();
19044
+ init_taskType();
18987
19045
  init_portalLlm();
18988
19046
  init_recall();
18989
19047
  var DEFAULT_BASE_URL2 = BASE_URL;
@@ -19029,11 +19087,13 @@ Respond with ONLY a single JSON object conforming to this JSON Schema, with no p
19029
19087
  ${JSON.stringify(
19030
19088
  options.responseSchema
19031
19089
  )}` : basePrompt;
19032
- const userMessage = `Question:
19033
- ${trimmed}
19034
-
19035
- Memories (use only these as evidence):
19036
- ${evidence}`;
19090
+ const userMessage = [
19091
+ `Question:
19092
+ ${trimmed}`,
19093
+ ...options.userInstructions ? [options.userInstructions] : [],
19094
+ `Memories (use only these as evidence):
19095
+ ${evidence}`
19096
+ ].join("\n\n");
19037
19097
  const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
19038
19098
  const fetchImpl = options.fetchFn ?? fetch;
19039
19099
  const log = getLogger();
@@ -19047,7 +19107,13 @@ ${evidence}`;
19047
19107
  try {
19048
19108
  response = await fetchImpl(`${baseUrl}/api/v1/chat/completions`, {
19049
19109
  method: "POST",
19050
- headers: { ...authHeaders, "Content-Type": "application/json" },
19110
+ // No task type unless the caller named one — an undeclared task adds no
19111
+ // header at all, which is what keeps user-facing reflect() unlabelled.
19112
+ headers: {
19113
+ ...authHeaders,
19114
+ ...taskTypeHeader(options.taskType),
19115
+ "Content-Type": "application/json"
19116
+ },
19051
19117
  body: JSON.stringify({
19052
19118
  model,
19053
19119
  // Modern OpenAI field; the portal reads only `max_completion_tokens`
@@ -19499,10 +19565,15 @@ async function synthesizeFacet(facet, ctx, options, prior) {
19499
19565
  limit,
19500
19566
  types: ["fact"],
19501
19567
  maxTokens: DEFAULT_FACET_MAX_TOKENS,
19502
- // Profile-facet synthesis is a background op, so it marks its own prompt.
19503
- // reflect() itself must NOT mark — it also serves the user's own questions,
19504
- // and that traffic is chat, not an internal flow. See ../internalFlowMarker.
19505
- systemPrompt: withInternalFlowMarker(buildFacetSystemPrompt(facet)),
19568
+ // Profile-facet synthesis is a background op, so it marks its own prompt AND
19569
+ // names its own task. reflect() itself must do neither — it also serves the
19570
+ // user's own questions, and that traffic is chat, not an internal flow. See
19571
+ // ../internalFlowMarker and ReflectOptions.taskType.
19572
+ taskType: "memory_profile_synth",
19573
+ systemPrompt: withInternalFlowMarker(FACET_SYSTEM_PROMPT),
19574
+ // The facet's label/guidance/response-fields, on the user turn — what keeps
19575
+ // the system half fixed and therefore server-ownable.
19576
+ userInstructions: buildFacetUserInstructions(facet),
19506
19577
  responseSchema: facetResponseSchema(facet.key),
19507
19578
  memories
19508
19579
  });
@@ -19560,19 +19631,22 @@ function fallbackSection(facet, prior) {
19560
19631
  stale: true
19561
19632
  };
19562
19633
  }
19563
- function buildFacetSystemPrompt(facet) {
19564
- const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
19565
- return `You are writing the "${facet.label}" section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth.
19566
-
19567
- Task for this section:
19568
- ${facet.guidance}
19634
+ var FACET_SYSTEM_PROMPT = `You are writing one section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth. The user turn names the section, states the task for it, and gives the exact JSON shape to respond in.
19569
19635
 
19570
19636
  Rules:
19571
19637
  - Ground every claim in the supplied memories \u2014 never invent, infer beyond, or embellish what they support.
19572
19638
  - If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
19573
19639
  - Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
19574
19640
  - Be concise and specific; no preamble, hedging, or meta-commentary.
19575
- - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
19641
+ - Respond as JSON in exactly the shape the user turn states, with no extra fields.`;
19642
+ function buildFacetUserInstructions(facet) {
19643
+ const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
19644
+ return `Section: "${facet.label}"
19645
+
19646
+ Task for this section:
19647
+ ${facet.guidance}
19648
+
19649
+ Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
19576
19650
  }
19577
19651
  function extractFacetText(structured) {
19578
19652
  if (structured && typeof structured === "object") {
@@ -6140,6 +6140,51 @@ declare function getMemoriesByEntityNamesOp(ctx: EntityOperationsContext, entity
6140
6140
  */
6141
6141
  declare function getEntitiesByMemoryIdsOp(ctx: EntityOperationsContext, memoryIds: readonly string[]): Promise<Map<string, Set<string>>>;
6142
6142
 
6143
+ /**
6144
+ * Class-B task types — the vocabulary of the `X-Anuma-Task-Type` header.
6145
+ *
6146
+ * A Class-B flow has ONE fixed purpose and no conversation: extract memories
6147
+ * from a snippet, pick a topic, consolidate duplicates, classify a query. Today
6148
+ * each of those builds its own system prompt on the client and sends it as a
6149
+ * `role:system` message, which makes the prompt a suggestion rather than a
6150
+ * contract — a caller can reword it into "you are a general assistant" and get
6151
+ * general chat at internal-endpoint prices.
6152
+ *
6153
+ * The fix is that the SERVER owns the text and the client only names the task.
6154
+ * This header is the name. The portal allowlists it against its own copy of this
6155
+ * enum (ai-portal `internal/systemprompt/tasks.go`) and DROPS anything it does
6156
+ * not recognise, so an unknown value degrades to "no task declared" rather than
6157
+ * failing the request.
6158
+ *
6159
+ * ⚠ These string values are a wire contract with ai-portal. Adding one here
6160
+ * before the portal knows it is harmless (it is dropped); renaming or removing
6161
+ * one silently stops that flow being counted. Keep them in step with
6162
+ * `AllTaskTypes` on the portal side, and see ai-portal
6163
+ * `docs/BACKEND_OWNED_TASK_PROMPTS.md` for the rollout.
6164
+ *
6165
+ * ⚠ RELEASE ORDER. This union is a PUBLIC type, so a split is breaking in both
6166
+ * directions and has to be sequenced:
6167
+ *
6168
+ * 1. merge + publish this package;
6169
+ * 2. bump `@anuma/sdk` in the consumer and switch its call sites to the new
6170
+ * names in the same commit.
6171
+ *
6172
+ * A consumer on the OLD version cannot compile the new names, and a consumer on
6173
+ * the NEW version cannot compile the retired ones (`app_inspiration` is gone
6174
+ * here, replaced by the three `app_inspiration_*` names). Both halves of that
6175
+ * are type-only — the wire itself always degrades safely, because the portal
6176
+ * drops any value it does not recognise. Concretely: ai-memoryless-client#5782
6177
+ * is blocked on this publish for exactly that reason.
6178
+ *
6179
+ * Deliberately ABSENT: agent personas, `reflect()` when it answers the user, and
6180
+ * the app-idea chat. Those carry an open conversation with the user, so a fixed
6181
+ * server-owned prompt would be wrong rather than merely premature — the same
6182
+ * boundary {@link INTERNAL_FLOW_MARKER} draws.
6183
+ *
6184
+ * @public
6185
+ */
6186
+ type TaskType = "title" | "commit_message" | "memory_extract" | "memory_dedup" | "memory_decay" | "memory_injection_check" | "memory_verify_support" | "memory_autosort" | "memory_topic" | "memory_consolidate" | "memory_decompose" | "memory_profile_synth" | "memory_graph" | "folder_context" | "selection_memory" | "summarize" | "style_analysis" | "classify_search" | "app_inspiration_generate" | "app_inspiration_trends" | "app_inspiration_remix" | "spotlight_image" | "slide_image" | "slide_video" | "slide_music" | "slide_sfx" | "media_video" | "media_audio" | "media_bg_removal" | "media_image_preset" | "tool_app_builder" | "tool_document_builder" | "tool_slides" | "tool_slides_image_gen" | "connector_guidance" | "cf_task" | "xbot_intent" | "xbot_council" | "scheduled_task";
6187
+
6143
6188
  /**
6144
6189
  * Shared portal LLM call for the memory pipeline.
6145
6190
  *
@@ -6992,6 +7037,17 @@ interface RecallDiagnostics {
6992
7037
  * never the cost.
6993
7038
  */
6994
7039
  vaultRowsDecrypted?: number;
7040
+ /**
7041
+ * Rows the fact lane had to re-embed through the portal because their stored
7042
+ * vector was unusable — stale `embedding_model`, wrong dimension, or
7043
+ * unparseable. Absent when the fact lane didn't run.
7044
+ *
7045
+ * Expected to be 0 on a healthy vault: rows are embedded at write time and the
7046
+ * re-embed writes the current model back, so a persistently non-zero value
7047
+ * means the writeback is not sticking and every turn is paying for it. On the
7048
+ * LEGACY read path this batch is uncapped, so it can be the whole vault.
7049
+ */
7050
+ vaultRowsEmbedded?: number;
6995
7051
  /** Facts the fact lane returned (post-dedupe, pre-fusion). */
6996
7052
  factCount: number;
6997
7053
  /** Chunks the chunk lane returned (post-dedupe, pre-fusion). */
@@ -7004,6 +7060,37 @@ interface RecallDiagnostics {
7004
7060
  prep: number;
7005
7061
  /** Vault fact-lane search (`searchVaultMemoriesWithSize`). */
7006
7062
  factLane: number;
7063
+ /**
7064
+ * The cross-encoder's share of {@link factLane} — wall-clock spent inside
7065
+ * `rerankPairs`. Billed even when the CE threw partway through, because a
7066
+ * rerank that burned three seconds and then failed still cost them.
7067
+ *
7068
+ * A SUBSET of `factLane`, not a sibling: read `factLane - rerank` for
7069
+ * everything else the lane did (query embed, vault read, fused ranking).
7070
+ * 0 when the CE did not run; `reranked` is what distinguishes that from a
7071
+ * rerank that cost nothing.
7072
+ *
7073
+ * Exists because #845 spent three rounds arguing about which stage inside
7074
+ * the fact lane dominated — whole-vault read, then admission window, then
7075
+ * the CE — with one aggregate number for all of them. Every hypothesis was
7076
+ * an inference; this makes the question a query.
7077
+ */
7078
+ rerank: number;
7079
+ /**
7080
+ * The query embed's share of {@link factLane} — one portal round trip.
7081
+ *
7082
+ * Also a SUBSET of `factLane`. Together with `rerank` this makes the lane
7083
+ * decomposable: `factLane - queryEmbed - rerank` is the local work (vault
7084
+ * read, decrypt, BM25/cosine fusion), and each of the three implies a
7085
+ * completely different fix.
7086
+ *
7087
+ * 0 when the lane returned before embedding, which includes every empty
7088
+ * vault — `prepareVaultCandidates` short-circuits first. That is why the
7089
+ * fast `vault_size = 0` population never established a baseline for this
7090
+ * cost, and why the ~850ms floor on the smallest NON-empty vaults had no
7091
+ * attributable owner.
7092
+ */
7093
+ queryEmbed: number;
7007
7094
  /** Chunk-lane search (`searchChunksOp`). */
7008
7095
  chunkLane: number;
7009
7096
  /** Cross-lane RRF fusion + provenance dedup after both lanes. */
@@ -9348,6 +9435,28 @@ interface ReflectOptions extends RecallOptions, PortalLlmAuth {
9348
9435
  maxTokens?: number;
9349
9436
  /** Override the grounding system prompt. */
9350
9437
  systemPrompt?: string;
9438
+ /**
9439
+ * Extra caller instruction to carry on the USER turn, between the question and
9440
+ * the evidence block (see the `userMessage` assembly below). This is the slot a
9441
+ * background caller uses to keep its per-request data OUT of the system message
9442
+ * without colliding with the numbered evidence list — profile-facet synthesis
9443
+ * puts its section label, guidance and response-field hint here so its system
9444
+ * half can stay fixed and server-ownable.
9445
+ */
9446
+ userInstructions?: string;
9447
+ /**
9448
+ * Class-B task name for the `X-Anuma-Task-Type` header, or nothing.
9449
+ *
9450
+ * Deliberately OPTIONAL and unset by default. reflect() also answers the user's
9451
+ * OWN question, and that traffic is chat, not an internal flow — declaring a
9452
+ * task type unconditionally here would put an internal-flow name on real
9453
+ * conversation, which is the same boundary `INTERNAL_FLOW_MARKER` draws
9454
+ * (reflect is deliberately unmarked; its background caller marks its own
9455
+ * prompt — see ../internalFlowMarker.ts). So the name is per call: only a
9456
+ * caller with ONE fixed purpose passes
9457
+ * one, and today that is profile-facet synthesis (`memory_profile_synth`).
9458
+ */
9459
+ taskType?: TaskType;
9351
9460
  /** Endpoint for the answer LLM. */
9352
9461
  baseUrl?: string;
9353
9462
  /** Override fetch (for tests). */