@anuma/sdk 6.43.0 → 6.44.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.
@@ -122,6 +122,18 @@ var init_logger = __esm({
122
122
  }
123
123
  });
124
124
 
125
+ // src/lib/taskType.ts
126
+ function taskTypeHeader(taskType) {
127
+ return taskType ? { [TASK_TYPE_HEADER]: taskType } : {};
128
+ }
129
+ var TASK_TYPE_HEADER;
130
+ var init_taskType = __esm({
131
+ "src/lib/taskType.ts"() {
132
+ "use strict";
133
+ TASK_TYPE_HEADER = "X-Anuma-Task-Type";
134
+ }
135
+ });
136
+
125
137
  // src/lib/pii/patterns.ts
126
138
  function luhnCheck(digits) {
127
139
  let sum = 0;
@@ -766,18 +778,6 @@ var init_internalFlowMarker = __esm({
766
778
  }
767
779
  });
768
780
 
769
- // src/lib/taskType.ts
770
- function taskTypeHeader(taskType) {
771
- return taskType ? { [TASK_TYPE_HEADER]: taskType } : {};
772
- }
773
- var TASK_TYPE_HEADER;
774
- var init_taskType = __esm({
775
- "src/lib/taskType.ts"() {
776
- "use strict";
777
- TASK_TYPE_HEADER = "X-Anuma-Task-Type";
778
- }
779
- });
780
-
781
781
  // src/lib/memory/portalLlm.ts
782
782
  function defaultBaseUrl() {
783
783
  return typeof process !== "undefined" && process.env?.ANUMA_PORTAL_BASE_URL || BASE_URL;
@@ -7132,6 +7132,7 @@ var DEFAULT_API_EMBEDDING_MODEL = "qwen/qwen3-embedding-8b";
7132
7132
 
7133
7133
  // src/lib/memoryEngine/generate.ts
7134
7134
  init_clientConfig();
7135
+ init_taskType();
7135
7136
  var EMBED_MAX_ATTEMPTS = 4;
7136
7137
  var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
7137
7138
  var DEFAULT_EMBEDDING_TOKEN_TIMEOUT_MS = 1e4;
@@ -7229,7 +7230,10 @@ async function embedOne(text14, options, outer) {
7229
7230
  const cached = cache.get(text14);
7230
7231
  if (cached) return Array.from(cached);
7231
7232
  }
7232
- const headers = await resolveAuthHeaders(options, outer);
7233
+ const headers = {
7234
+ ...await resolveAuthHeaders(options, outer),
7235
+ ...taskTypeHeader(options.taskType)
7236
+ };
7233
7237
  const response = await withEmbeddingRetry(
7234
7238
  () => withDeadline(
7235
7239
  (signal) => postApiV1Embeddings({
@@ -7324,7 +7328,10 @@ async function embedMany(texts, options, outer) {
7324
7328
  if (uncachedTexts.length === 0) {
7325
7329
  return results;
7326
7330
  }
7327
- const headers = await resolveAuthHeaders(options, outer);
7331
+ const headers = {
7332
+ ...await resolveAuthHeaders(options, outer),
7333
+ ...taskTypeHeader(options.taskType)
7334
+ };
7328
7335
  if (outer?.aborted) throw outer.reason;
7329
7336
  const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
7330
7337
  let newEmbeddings;
@@ -18522,6 +18529,50 @@ function applyMMR(candidates, k, lambda = 0.5) {
18522
18529
  return selected;
18523
18530
  }
18524
18531
 
18532
+ // src/lib/memory/recallTelemetry.ts
18533
+ function notifyMemoryCallback(callback, value) {
18534
+ try {
18535
+ callback?.(value);
18536
+ } catch {
18537
+ return;
18538
+ }
18539
+ }
18540
+ function factRecallDiagnostics(scores, elapsedMs, options = {}) {
18541
+ return {
18542
+ usedBudget: "low",
18543
+ reranked: false,
18544
+ candidateCount: options.candidateCount ?? scores.length,
18545
+ ...options.vaultSize !== void 0 && { vaultSize: options.vaultSize },
18546
+ ...options.decryptLast !== void 0 && { decryptLast: options.decryptLast },
18547
+ ...options.vaultRowsDecrypted !== void 0 && {
18548
+ vaultRowsDecrypted: options.vaultRowsDecrypted
18549
+ },
18550
+ ...options.vaultRowsEmbedded !== void 0 && {
18551
+ vaultRowsEmbedded: options.vaultRowsEmbedded
18552
+ },
18553
+ factCount: scores.length,
18554
+ chunkCount: 0,
18555
+ admittedCount: scores.length,
18556
+ topScore: scores.length ? Math.max(...scores) : -1,
18557
+ lowestAdmittedScore: scores.length ? Math.min(...scores) : -1,
18558
+ minScoreApplied: options.minScore ?? -1,
18559
+ truncated: options.truncated ?? false,
18560
+ graphLaneCount: 0,
18561
+ temporalLaneCount: 0,
18562
+ emptyReason: scores.length ? "" : options.vaultSize === 0 ? "vault-empty" : "no-candidates",
18563
+ timings: {
18564
+ total: elapsedMs,
18565
+ prep: 0,
18566
+ factLane: elapsedMs,
18567
+ rerank: 0,
18568
+ queryEmbed: options.queryEmbedMs ?? 0,
18569
+ chunkLane: 0,
18570
+ fuse: 0
18571
+ },
18572
+ degraded: options.embeddingsUnavailable ? ["embeddings-unavailable"] : []
18573
+ };
18574
+ }
18575
+
18525
18576
  // src/lib/memory/recency.ts
18526
18577
  var DAY_MS2 = 1e3 * 60 * 60 * 24;
18527
18578
  var DEFAULT_PER_YEAR_DECAY = 0.2;
@@ -19214,6 +19265,7 @@ function rankVaultMemories(_query, queryEmbedding, items, options) {
19214
19265
  }
19215
19266
  filtered.sort((a, b) => b.similarity - a.similarity);
19216
19267
  }
19268
+ notifyMemoryCallback(options?.onCandidateCount, filtered.length);
19217
19269
  const itemById = new Map(items.map((i) => [i.id, i]));
19218
19270
  return filtered.slice(0, limit).map((r) => resultFromItem(itemById.get(r.uniqueId), r.similarity));
19219
19271
  }
@@ -20196,6 +20248,7 @@ async function rankPreparedVaultCandidates(query, prepared, embeddingOptions, se
20196
20248
  const results = rankVaultMemories(query, queryEmbedding, embeddedItems, {
20197
20249
  limit,
20198
20250
  minSimilarity,
20251
+ onCandidateCount: searchOptions?.onCandidateCount,
20199
20252
  ...searchOptions?.supersessionBoost !== void 0 && {
20200
20253
  supersessionBoost: searchOptions.supersessionBoost
20201
20254
  },
@@ -20297,16 +20350,20 @@ var DEFAULT_SCOPE = "private";
20297
20350
  var DEFAULT_CONSOLIDATE_THRESHOLD = 0.55;
20298
20351
  var DEFAULT_CONSOLIDATE_TOP_K = 20;
20299
20352
  async function retain(content, ctx, options = {}) {
20353
+ const embeddingOptions = {
20354
+ ...ctx.embeddingOptions,
20355
+ taskType: "memory_retain_embed"
20356
+ };
20300
20357
  return retainWithPersistence(
20301
20358
  content,
20302
20359
  {
20303
- embeddingOptions: ctx.embeddingOptions,
20360
+ embeddingOptions,
20304
20361
  vaultCache: ctx.vaultCache,
20305
20362
  persistence: {
20306
20363
  prepare: (query, searchOptions) => prepareVaultCandidates(
20307
20364
  query,
20308
20365
  ctx.vaultCtx,
20309
- ctx.embeddingOptions,
20366
+ embeddingOptions,
20310
20367
  ctx.vaultCache,
20311
20368
  searchOptions
20312
20369
  ),
@@ -21460,7 +21517,17 @@ function parseVerdict(parsed) {
21460
21517
 
21461
21518
  // src/lib/memory/decayWorker.ts
21462
21519
  init_logger();
21463
- var EMPTY_RESULT = { archived: 0, deleted: 0, scanned: 0 };
21520
+ function emptyResult() {
21521
+ return {
21522
+ archived: 0,
21523
+ deleted: 0,
21524
+ scanned: 0,
21525
+ ruleDriven: { archived: 0, deleted: 0 },
21526
+ classifierDriven: { archived: 0, deleted: 0 },
21527
+ classifierCalls: 0,
21528
+ classifierCapHits: 0
21529
+ };
21530
+ }
21464
21531
  var DEFAULT_MAX_CLASSIFIER_CALLS_PER_SWEEP = 20;
21465
21532
  function resolveNow(now) {
21466
21533
  if (typeof now === "function") return now();
@@ -21499,42 +21566,45 @@ function createDecaySweeper(options) {
21499
21566
  let disposed = false;
21500
21567
  async function verdictFor(input, now, sweep) {
21501
21568
  const ruleVerdict = classifyDecay(input, now, policy);
21502
- if (ruleVerdict !== "keep") return ruleVerdict;
21503
- if (!classifier || !isBorderline(input)) return ruleVerdict;
21569
+ const rule = { verdict: ruleVerdict, source: "ruleDriven" };
21570
+ if (ruleVerdict !== "keep") return rule;
21571
+ if (!classifier || !isBorderline(input)) return rule;
21504
21572
  if (input.id) {
21505
21573
  const cached = classifierCache.get(input.id);
21506
- if (cached && cached.activityAt === lastActivityAt(input)) return cached.verdict;
21574
+ if (cached && cached.activityAt === lastActivityAt(input))
21575
+ return { verdict: cached.verdict, source: "classifierDriven" };
21507
21576
  }
21508
21577
  if (sweep.classifierCalls >= maxClassifierCalls) {
21578
+ sweep.classifierCapHits++;
21509
21579
  if (!sweep.ceilingLogged) {
21510
21580
  sweep.ceilingLogged = true;
21511
21581
  getLogger().warn(
21512
21582
  `[memory/decay] classifier per-sweep ceiling (${maxClassifierCalls}) reached; remaining borderline rows use the rule verdict this sweep`
21513
21583
  );
21514
21584
  }
21515
- return ruleVerdict;
21585
+ return rule;
21516
21586
  }
21517
21587
  sweep.classifierCalls++;
21518
21588
  try {
21519
21589
  const verdict = await classifier.classify(input, ruleVerdict, now);
21520
21590
  if (input.id) classifierCache.set(input.id, { activityAt: lastActivityAt(input), verdict });
21521
- return verdict;
21591
+ return { verdict, source: "classifierDriven" };
21522
21592
  } catch (err) {
21523
21593
  getLogger().warn(
21524
21594
  `[memory/decay] classifier failed; falling back to rule verdict: ${err instanceof Error ? err.message : String(err)}`
21525
21595
  );
21526
- return ruleVerdict;
21596
+ return rule;
21527
21597
  }
21528
21598
  }
21529
21599
  async function runSweep() {
21530
- if (disposed) return { ...EMPTY_RESULT };
21600
+ if (disposed) return emptyResult();
21531
21601
  const now = resolveNow(options.now);
21532
21602
  let candidates;
21533
21603
  try {
21534
21604
  candidates = await getDecayCandidatesRawOp(vaultCtx);
21535
21605
  } catch (err) {
21536
21606
  onError?.(err instanceof Error ? err : new Error(String(err)));
21537
- return { ...EMPTY_RESULT };
21607
+ return emptyResult();
21538
21608
  }
21539
21609
  if (classifierCache.size > 0) {
21540
21610
  const liveIds = new Set(candidates.map((c) => c.uniqueId));
@@ -21542,33 +21612,40 @@ function createDecaySweeper(options) {
21542
21612
  if (!liveIds.has(id)) classifierCache.delete(id);
21543
21613
  }
21544
21614
  }
21545
- const sweep = { classifierCalls: 0, ceilingLogged: false };
21615
+ const sweep = { classifierCalls: 0, classifierCapHits: 0, ceilingLogged: false };
21546
21616
  const toArchive = [];
21547
21617
  const toDelete = [];
21548
21618
  for (const c of candidates) {
21549
- const verdict = await verdictFor(toDecayInput(c), now, sweep);
21550
- if (verdict === "archive") toArchive.push(c);
21551
- else if (verdict === "delete") toDelete.push(c);
21552
- }
21553
- let archived = 0;
21554
- let deleted = 0;
21619
+ const { verdict, source } = await verdictFor(toDecayInput(c), now, sweep);
21620
+ if (verdict === "archive") toArchive.push({ candidate: c, source });
21621
+ else if (verdict === "delete") toDelete.push({ candidate: c, source });
21622
+ }
21623
+ const result = emptyResult();
21624
+ result.scanned = candidates.length;
21625
+ result.classifierCalls = sweep.classifierCalls;
21626
+ result.classifierCapHits = sweep.classifierCapHits;
21555
21627
  try {
21556
- for (const c of toArchive) {
21628
+ for (const { candidate: c, source } of toArchive) {
21557
21629
  const ok = await archiveVaultMemoryOp(vaultCtx, c.uniqueId, {
21558
21630
  now,
21559
21631
  expectedUpdatedAt: c.updatedAt,
21560
21632
  expectedLastObservedAt: c.lastObservedAt ?? null
21561
21633
  });
21562
- if (ok) archived++;
21634
+ if (ok) {
21635
+ result.archived++;
21636
+ result[source].archived++;
21637
+ }
21563
21638
  }
21564
- for (const c of toDelete) {
21639
+ for (const { candidate: c, source } of toDelete) {
21565
21640
  const ok = await hardDeleteDecayedOp(vaultCtx, c.uniqueId, { hardDeleteWindowMs, now });
21566
- if (ok) deleted++;
21641
+ if (ok) {
21642
+ result.deleted++;
21643
+ result[source].deleted++;
21644
+ }
21567
21645
  }
21568
21646
  } catch (err) {
21569
21647
  onError?.(err instanceof Error ? err : new Error(String(err)));
21570
21648
  }
21571
- const result = { archived, deleted, scanned: candidates.length };
21572
21649
  onSwept?.(result);
21573
21650
  return result;
21574
21651
  }
@@ -22173,6 +22250,10 @@ async function recall(query, ctx, options = {}) {
22173
22250
  const flags = flagsForBudget(usedBudget);
22174
22251
  const normalizedFacets = normalizeSubQueries(options.subQueries);
22175
22252
  const subQueries = normalizedFacets.length >= 2 ? normalizedFacets : void 0;
22253
+ const embeddingOptions = {
22254
+ ...ctx.embeddingOptions,
22255
+ taskType: "memory_recall_embed"
22256
+ };
22176
22257
  const t0 = nowMs2();
22177
22258
  let prepMs = 0;
22178
22259
  let factLaneMs = 0;
@@ -22268,7 +22349,7 @@ async function recall(query, ctx, options = {}) {
22268
22349
  let sharedEmbedMs = 0;
22269
22350
  const [queryEmbedding, entityRanking, temporalRanking] = await Promise.all([
22270
22351
  needsChunkEmbedding ? generateEmbedding(query, {
22271
- ...ctx.embeddingOptions,
22352
+ ...embeddingOptions,
22272
22353
  totalTimeoutMs: queryEmbedTotalTimeoutMs
22273
22354
  }).finally(() => {
22274
22355
  sharedEmbedMs = nowMs2() - prepStart;
@@ -22326,7 +22407,7 @@ async function recall(query, ctx, options = {}) {
22326
22407
  } = await (ctx.factSource?.search.bind(ctx.factSource) ?? ((searchQuery, searchOptions) => searchVaultMemoriesWithSize(
22327
22408
  searchQuery,
22328
22409
  ctx.vaultCtx,
22329
- ctx.embeddingOptions,
22410
+ embeddingOptions,
22330
22411
  ctx.vaultCache,
22331
22412
  searchOptions
22332
22413
  )))(query, {
@@ -22692,7 +22773,7 @@ function isDumpQuery(query) {
22692
22773
  async function listRecentFacts(ctx, limit, toolOptions) {
22693
22774
  const store = toolOptions?.memoryStore;
22694
22775
  const vaultCtx = ctx.vaultCtx;
22695
- if (!vaultCtx && !store) return [];
22776
+ if (!vaultCtx && !store) return { memories: [], truncated: false };
22696
22777
  const read = (ids) => store ? store.list({ memoryIds: ids }) : getVaultMemoriesByIdsOp(vaultCtx, ids);
22697
22778
  const filters = {
22698
22779
  ...toolOptions?.scopes && { scopes: toolOptions.scopes },
@@ -22703,34 +22784,47 @@ async function listRecentFacts(ctx, limit, toolOptions) {
22703
22784
  ...toolOptions?.folderId !== void 0 && { folderId: toolOptions.folderId }
22704
22785
  });
22705
22786
  const readable = [];
22787
+ const readLimit = limit + 1;
22788
+ let batchLimit = limit;
22706
22789
  let start = 0;
22707
- for (let size = limit; readable.length < limit; size *= 2) {
22790
+ let projected = 0;
22791
+ let exhausted = false;
22792
+ for (let size = readLimit; readable.length < readLimit; size *= 2) {
22708
22793
  const ordered = await project(size);
22709
- for (let i = start; i < ordered.length && readable.length < limit; i += limit) {
22710
- const batch = ordered.slice(i, i + limit).map((p) => p.uniqueId);
22794
+ projected = ordered.length;
22795
+ exhausted = !store || ordered.length < size;
22796
+ for (let i = start; i < ordered.length && readable.length < readLimit; ) {
22797
+ const batch = ordered.slice(i, i + batchLimit).map((p) => p.uniqueId);
22711
22798
  const byId = new Map((await read(batch)).map((m) => [m.uniqueId, m]));
22799
+ const previousCount = readable.length;
22712
22800
  for (const id of batch) {
22713
22801
  const m = byId.get(id);
22714
22802
  if (m && !isEncrypted(m.content)) readable.push(m);
22715
- if (readable.length >= limit) break;
22803
+ if (readable.length >= readLimit) break;
22716
22804
  }
22805
+ batchLimit = readable.length > previousCount ? Math.min(limit, readLimit - readable.length) : limit;
22806
+ i += batch.length;
22717
22807
  }
22718
- if (!store || ordered.length < size) break;
22808
+ if (exhausted) break;
22719
22809
  start = ordered.length;
22720
22810
  }
22721
- return readable.map((m) => ({
22722
- id: m.uniqueId,
22723
- kind: "fact",
22724
- content: m.content,
22725
- score: 0,
22726
- folderId: m.folderId,
22727
- eventTimeStart: m.eventTimeStart,
22728
- eventTimeEnd: m.eventTimeEnd,
22729
- eventTimeKind: m.eventTimeKind,
22730
- factType: m.factType,
22731
- createdAt: m.createdAt,
22732
- updatedAt: m.updatedAt
22733
- }));
22811
+ return {
22812
+ memories: readable.slice(0, limit).map((m) => ({
22813
+ id: m.uniqueId,
22814
+ kind: "fact",
22815
+ content: m.content,
22816
+ score: 0,
22817
+ folderId: m.folderId,
22818
+ eventTimeStart: m.eventTimeStart,
22819
+ eventTimeEnd: m.eventTimeEnd,
22820
+ eventTimeKind: m.eventTimeKind,
22821
+ factType: m.factType,
22822
+ createdAt: m.createdAt,
22823
+ updatedAt: m.updatedAt
22824
+ })),
22825
+ ...exhausted && { vaultSize: projected },
22826
+ truncated: readable.length > limit
22827
+ };
22734
22828
  }
22735
22829
  function createRecallTool(ctx, toolOptions, callbacks) {
22736
22830
  if (toolOptions?.memoryStore && toolOptions.folderId !== void 0)
@@ -22769,7 +22863,11 @@ function createRecallTool(ctx, toolOptions, callbacks) {
22769
22863
  },
22770
22864
  executor: async (args) => {
22771
22865
  if (typeof args.query !== "string" || args.query.length === 0) {
22772
- throw new Error("recall_memory: `query` is required and must be a non-empty string.");
22866
+ const error = new Error(
22867
+ "recall_memory: `query` is required and must be a non-empty string."
22868
+ );
22869
+ notifyMemoryCallback(callbacks?.onError, error);
22870
+ throw error;
22773
22871
  }
22774
22872
  const query = args.query;
22775
22873
  const now = Date.now();
@@ -22797,10 +22895,17 @@ function createRecallTool(ctx, toolOptions, callbacks) {
22797
22895
  RECALL_MAX_MEMORIES_PER_CONVERSATION - conversationMemories
22798
22896
  );
22799
22897
  const effectiveLimit = Math.min(requestLimit, remainingTurnBudget, remainingConvBudget);
22898
+ const truncatedByBudget = effectiveLimit < requestLimit;
22899
+ if (truncatedByBudget) {
22900
+ notifyMemoryCallback(callbacks?.onTruncated, {
22901
+ requestedLimit: requestLimit,
22902
+ effectiveLimit,
22903
+ truncatedByBudget
22904
+ });
22905
+ }
22800
22906
  if (effectiveLimit <= 0) {
22801
22907
  return VOLUME_LIMIT_NOTICE;
22802
22908
  }
22803
- const truncatedByBudget = effectiveLimit < requestLimit;
22804
22909
  turnMemories += effectiveLimit;
22805
22910
  conversationMemories += effectiveLimit;
22806
22911
  let surfaced = 0;
@@ -22809,7 +22914,27 @@ function createRecallTool(ctx, toolOptions, callbacks) {
22809
22914
  const wantsRecent = args.sort === "recent" && defaultTypes.includes("fact") && (ctx.vaultCtx !== void 0 || toolOptions?.memoryStore !== void 0);
22810
22915
  let result;
22811
22916
  if (wantsRecent) {
22812
- result = { memories: await listRecentFacts(ctx, effectiveLimit, toolOptions) };
22917
+ const start = Date.now();
22918
+ const recent = await listRecentFacts(ctx, effectiveLimit, toolOptions);
22919
+ result = recent;
22920
+ notifyMemoryCallback(callbacks?.onDiagnostics, {
22921
+ ...factRecallDiagnostics(
22922
+ result.memories.map((m) => m.score),
22923
+ Date.now() - start,
22924
+ {
22925
+ truncated: recent.truncated,
22926
+ ...recent.vaultSize !== void 0 && { vaultSize: recent.vaultSize }
22927
+ }
22928
+ ),
22929
+ truncatedByBudget
22930
+ });
22931
+ if (recent.truncated && !truncatedByBudget) {
22932
+ notifyMemoryCallback(callbacks?.onTruncated, {
22933
+ requestedLimit: requestLimit,
22934
+ effectiveLimit,
22935
+ truncatedByBudget
22936
+ });
22937
+ }
22813
22938
  } else {
22814
22939
  let subQueries;
22815
22940
  if (defaultBudget === "high" && toolOptions?.decomposeOptions) {
@@ -22821,6 +22946,14 @@ function createRecallTool(ctx, toolOptions, callbacks) {
22821
22946
  const recallOpts = {
22822
22947
  onDiagnostics: (d) => {
22823
22948
  recallDegraded = d.degraded;
22949
+ notifyMemoryCallback(callbacks?.onDiagnostics, { ...d, truncatedByBudget });
22950
+ if (d.truncated && !truncatedByBudget) {
22951
+ notifyMemoryCallback(callbacks?.onTruncated, {
22952
+ requestedLimit: requestLimit,
22953
+ effectiveLimit,
22954
+ truncatedByBudget
22955
+ });
22956
+ }
22824
22957
  },
22825
22958
  types: defaultTypes,
22826
22959
  limit: effectiveLimit,
@@ -22864,6 +22997,7 @@ ${TRUNCATION_NOTICE}` : formatted;
22864
22997
  const message = error instanceof Error ? error.message : "Unknown error";
22865
22998
  const wrapped = new Error(`recall_memory: search failed \u2014 ${message}`);
22866
22999
  wrapped.cause = error;
23000
+ notifyMemoryCallback(callbacks?.onError, wrapped);
22867
23001
  throw wrapped;
22868
23002
  } finally {
22869
23003
  const unused = effectiveLimit - surfaced;
@@ -23105,6 +23239,18 @@ When a memory refers to one of these, reuse the EXACT existing name (same spelli
23105
23239
  ` : "";
23106
23240
  for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
23107
23241
  const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
23242
+ const start = Date.now();
23243
+ let attempts = 0;
23244
+ let failure;
23245
+ const reportBatch = (unansweredCount) => {
23246
+ notifyMemoryCallback(options.onBatch, {
23247
+ memoryIds: batch.map((m) => m.id),
23248
+ attempts,
23249
+ durationMs: Date.now() - start,
23250
+ unansweredCount,
23251
+ ...failure && { failure }
23252
+ });
23253
+ };
23108
23254
  const rows = [];
23109
23255
  for (const m of batch) {
23110
23256
  const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
@@ -23126,6 +23272,12 @@ ${listing}
23126
23272
 
23127
23273
  List each memory's named entities.`,
23128
23274
  tag: "memory/topics",
23275
+ onAttempt: (attempt) => {
23276
+ attempts = attempt.attempt;
23277
+ },
23278
+ onFailure: (value) => {
23279
+ failure = value;
23280
+ },
23129
23281
  extra: { max_completion_tokens: MAX_COMPLETION_TOKENS },
23130
23282
  ...options.fetchFn && { fetchFn: options.fetchFn },
23131
23283
  ...options.maxAttempts !== void 0 && { maxAttempts: options.maxAttempts },
@@ -23134,6 +23286,7 @@ List each memory's named entities.`,
23134
23286
  ...options.backoffMs && { backoffMs: options.backoffMs }
23135
23287
  });
23136
23288
  if (parsed === null) {
23289
+ reportBatch(batch.length);
23137
23290
  getLogger().warn(
23138
23291
  `[memory/topics] batch of ${batch.length} failed after retries \u2014 will retry next sweep`
23139
23292
  );
@@ -23142,6 +23295,7 @@ List each memory's named entities.`,
23142
23295
  const validIds = new Set(batch.map((m) => m.id));
23143
23296
  const byId = parseTopicResponse(parsed, validIds);
23144
23297
  if (byId === null) {
23298
+ reportBatch(batch.length);
23145
23299
  getLogger().warn(
23146
23300
  `[memory/topics] batch of ${batch.length} returned an unrecognized shape \u2014 will retry next sweep`
23147
23301
  );
@@ -23154,6 +23308,7 @@ List each memory's named entities.`,
23154
23308
  }
23155
23309
  out.set(m.id, redactor ? restoreEntities(entities, redactor) : entities);
23156
23310
  }
23311
+ reportBatch(batch.filter((m) => !byId.has(m.id)).length);
23157
23312
  }
23158
23313
  return out;
23159
23314
  }
@@ -23192,6 +23347,7 @@ function isDegradedTopicSkip(reason) {
23192
23347
  return DEGRADED_TOPIC_SKIP[reason] === true;
23193
23348
  }
23194
23349
  async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
23350
+ const start = Date.now();
23195
23351
  const entityCtx = ctx.entityCtx;
23196
23352
  if (!entityCtx) {
23197
23353
  throw new Error("extractAndLinkEntitiesForMemoriesOp requires ctx.entityCtx");
@@ -23232,7 +23388,18 @@ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
23232
23388
  skip(id, "unreadable");
23233
23389
  }
23234
23390
  }
23235
- const entitiesByMemory = await extractEntitiesForMemories(inputs, options);
23391
+ const batches = [];
23392
+ const llmFailures = /* @__PURE__ */ new Map();
23393
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, {
23394
+ ...options,
23395
+ onBatch: (batch) => {
23396
+ batches.push(batch);
23397
+ if (batch.failure) {
23398
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
23399
+ }
23400
+ notifyMemoryCallback(options.onBatch, batch);
23401
+ }
23402
+ });
23236
23403
  const toStamp = [];
23237
23404
  for (const input of inputs) {
23238
23405
  const entities = entitiesByMemory.get(input.id);
@@ -23263,7 +23430,16 @@ async function extractAndLinkEntitiesForMemoriesOp(ctx, memoryIds, options) {
23263
23430
  entitiesByMemory.delete(id);
23264
23431
  }
23265
23432
  }
23266
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
23433
+ return {
23434
+ entitiesByMemory,
23435
+ stampedIds,
23436
+ skippedIds,
23437
+ skippedReasons,
23438
+ batchCount: batches.length,
23439
+ durationMs: Date.now() - start,
23440
+ batches,
23441
+ llmFailures
23442
+ };
23267
23443
  }
23268
23444
 
23269
23445
  // src/lib/memory/store/local.ts
@@ -23418,7 +23594,7 @@ function createLocalMemoryStore(options) {
23418
23594
  search: (query, searchOptions) => searchVaultMemoriesWithSize(
23419
23595
  query,
23420
23596
  vaultCtx,
23421
- options.embeddingOptions,
23597
+ { ...options.embeddingOptions, taskType: "memory_recall_embed" },
23422
23598
  vaultCache,
23423
23599
  searchOptions
23424
23600
  ),
@@ -24082,6 +24258,7 @@ function createRemoteMemoryPipeline(options) {
24082
24258
  const budgetMs = search.queryEmbedTotalTimeoutMs ?? 8e3;
24083
24259
  const embed = (texts) => generateEmbeddings(texts, {
24084
24260
  ...embeddingOptions,
24261
+ taskType: mode === "retain" ? "memory_retain_embed" : "memory_recall_embed",
24085
24262
  totalTimeoutMs: budgetMs
24086
24263
  });
24087
24264
  const embedFacets = async () => {
@@ -24330,7 +24507,7 @@ function createRemoteMemoryPipeline(options) {
24330
24507
  const ranked = await rankPreparedVaultCandidates(
24331
24508
  text14,
24332
24509
  prepared,
24333
- embeddingOptions,
24510
+ { ...embeddingOptions, taskType: "memory_recall_embed" },
24334
24511
  searchOptions
24335
24512
  );
24336
24513
  return {
@@ -24352,7 +24529,7 @@ function createRemoteMemoryPipeline(options) {
24352
24529
  content,
24353
24530
  {
24354
24531
  persistence: port,
24355
- embeddingOptions,
24532
+ embeddingOptions: { ...embeddingOptions, taskType: "memory_retain_embed" },
24356
24533
  vaultCache: /* @__PURE__ */ new Map(),
24357
24534
  normalizeEmbedding: storedVector
24358
24535
  },
@@ -24729,6 +24906,7 @@ var MAX_BATCH = 50;
24729
24906
  var MAX_QUERY_IDS = 1e3;
24730
24907
  var MAX_QUERY_TOPICS = 100;
24731
24908
  var MAX_QUERY_FILTER = 20;
24909
+ var GRAPH_LANE_ROWS = 256;
24732
24910
  var RemoteMemoryPartialCreateError = class extends Error {
24733
24911
  constructor(created, cause) {
24734
24912
  super(`createMany stored ${created.length} memories before a batch failed`);
@@ -24824,7 +25002,11 @@ function createRemoteMemoryStore(options) {
24824
25002
  const { persistence, embeddingOptions } = options;
24825
25003
  const model = embeddingOptions.model ?? DEFAULT_API_EMBEDDING_MODEL;
24826
25004
  const pollIntervalMs = options.pollIntervalMs ?? 6e4;
24827
- const pipeline = createRemoteMemoryPipeline(options);
25005
+ const pipeline = createRemoteMemoryPipeline({
25006
+ ...options,
25007
+ graphRanking: options.graphRanking ?? ((query, _traverse, recallOptions) => topicGraphRanking(query, recallOptions)),
25008
+ temporalRanking: options.temporalRanking ?? (() => Promise.resolve([]))
25009
+ });
24828
25010
  const subscribers = /* @__PURE__ */ new Set();
24829
25011
  const seen = /* @__PURE__ */ new Map();
24830
25012
  let timer;
@@ -24856,6 +25038,36 @@ function createRemoteMemoryStore(options) {
24856
25038
  const decrypted = (options2) => persistence.query(options2);
24857
25039
  const metadata2 = (options2) => persistence.queryMetadata(options2);
24858
25040
  const enumerate = (filters, max = Infinity) => pages(decrypted, filters, max);
25041
+ const memoriesByTopics = async (names) => {
25042
+ const wanted = new Set(names.map(normalizeEntityName).filter(Boolean));
25043
+ const out = /* @__PURE__ */ new Map();
25044
+ const pages2 = await Promise.all(
25045
+ chunks2([...wanted], MAX_QUERY_TOPICS).map((topics) => enumerate({ topics }))
25046
+ );
25047
+ for (const { memory } of pages2.flat()) {
25048
+ const matched = [...canonicalTopics(memory)].filter((name) => wanted.has(name));
25049
+ if (matched.length) out.set(memory.memory_id, new Set(matched));
25050
+ }
25051
+ return out;
25052
+ };
25053
+ const topicGraphRanking = async (query, recallOptions = {}) => {
25054
+ const wanted = new Set(extractQueryEntities(query).map(normalizeEntityName).filter(Boolean));
25055
+ if (wanted.size === 0 || recallOptions.memoryIds?.length === 0) return [];
25056
+ const rows = await pages(
25057
+ metadata2,
25058
+ {
25059
+ topics: [...wanted].slice(0, MAX_QUERY_TOPICS),
25060
+ ...recallOptions.scopes && { scopes: recallOptions.scopes },
25061
+ ...recallOptions.factTypes && { fact_types: recallOptions.factTypes },
25062
+ ...recallOptions.memoryIds && { memory_ids: recallOptions.memoryIds }
25063
+ },
25064
+ GRAPH_LANE_ROWS
25065
+ );
25066
+ return rows.map(({ memory }) => ({
25067
+ memoryId: memory.memory_id,
25068
+ matched: [...canonicalTopics(memory)].filter((name) => wanted.has(name)).length
25069
+ })).filter((row) => row.matched > 0).sort((a, b) => b.matched - a.matched).slice(0, NODE_BUDGET).map((row) => row.memoryId);
25070
+ };
24859
25071
  const enumerateIds = async (read, ids, filters) => (await Promise.all(
24860
25072
  chunks2([...new Set(ids)], MAX_QUERY_IDS).map(
24861
25073
  (memory_ids) => pages(read, { ...filters, memory_ids })
@@ -25081,7 +25293,8 @@ function createRemoteMemoryStore(options) {
25081
25293
  };
25082
25294
  },
25083
25295
  extractTopics: async (memoryIds, extractOptions) => {
25084
- const extractedAt = extractOptions.now ?? Date.now();
25296
+ const start = Date.now();
25297
+ const extractedAt = extractOptions.now ?? start;
25085
25298
  const skippedIds = [];
25086
25299
  const skippedReasons = /* @__PURE__ */ new Map();
25087
25300
  const skip = (id, reason) => {
@@ -25099,7 +25312,18 @@ function createRemoteMemoryStore(options) {
25099
25312
  else if (memory.is_deleted || memory.topics_user_managed) skip(id, "excluded");
25100
25313
  else inputs.push({ id, content: memory.content });
25101
25314
  }
25102
- const entitiesByMemory = await extractEntitiesForMemories(inputs, extractOptions);
25315
+ const batches = [];
25316
+ const llmFailures = /* @__PURE__ */ new Map();
25317
+ const entitiesByMemory = await extractEntitiesForMemories(inputs, {
25318
+ ...extractOptions,
25319
+ onBatch: (batch) => {
25320
+ batches.push(batch);
25321
+ if (batch.failure) {
25322
+ for (const id of batch.memoryIds) llmFailures.set(id, batch.failure);
25323
+ }
25324
+ notifyMemoryCallback(extractOptions.onBatch, batch);
25325
+ }
25326
+ });
25103
25327
  const answered = /* @__PURE__ */ new Map();
25104
25328
  for (const input of inputs) {
25105
25329
  const entities = entitiesByMemory.get(input.id);
@@ -25148,7 +25372,16 @@ function createRemoteMemoryStore(options) {
25148
25372
  getLogger().warn("[memory/remote-store] Failed to write extracted topics:", error);
25149
25373
  for (const id of answered.keys()) fail(id, "link-failed");
25150
25374
  }
25151
- return { entitiesByMemory, stampedIds, skippedIds, skippedReasons };
25375
+ return {
25376
+ entitiesByMemory,
25377
+ stampedIds,
25378
+ skippedIds,
25379
+ skippedReasons,
25380
+ batchCount: batches.length,
25381
+ durationMs: Date.now() - start,
25382
+ batches,
25383
+ llmFailures
25384
+ };
25152
25385
  },
25153
25386
  stampTopicsExtracted: async (memoryIds, extractedAt, version = TOPICS_EXTRACTION_VERSION) => {
25154
25387
  const ids = [...new Set(memoryIds)];
@@ -25275,18 +25508,7 @@ function createRemoteMemoryStore(options) {
25275
25508
  listArchived: async () => (await enumerate({ archived_only: true, order: "archived", include_embeddings: true })).map(
25276
25509
  (record2) => stored(record2.memory)
25277
25510
  ),
25278
- memoriesByTopics: async (names) => {
25279
- const wanted = new Set(names.map(normalizeEntityName).filter(Boolean));
25280
- const out = /* @__PURE__ */ new Map();
25281
- const pages2 = await Promise.all(
25282
- chunks2([...wanted], MAX_QUERY_TOPICS).map((topics) => enumerate({ topics }))
25283
- );
25284
- for (const { memory } of pages2.flat()) {
25285
- const matched = [...canonicalTopics(memory)].filter((name) => wanted.has(name));
25286
- if (matched.length) out.set(memory.memory_id, new Set(matched));
25287
- }
25288
- return out;
25289
- },
25511
+ memoriesByTopics,
25290
25512
  topicsByMemories: async (memoryIds) => {
25291
25513
  const out = /* @__PURE__ */ new Map();
25292
25514
  for (const { memory } of await enumerateIds(decrypted, memoryIds, {
@@ -27164,9 +27386,13 @@ function formatVaultHits(hits) {
27164
27386
  return hits.map((h, i) => `[${i + 1}] (id: ${h.id}, similarity: ${h.score.toFixed(2)})
27165
27387
  ${h.content}`).join("\n\n");
27166
27388
  }
27167
- function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOptions) {
27389
+ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOptions, callbacks) {
27168
27390
  const limit = searchOptions?.limit ?? 5;
27169
27391
  const minSimilarity = searchOptions?.minSimilarity ?? 0.1;
27392
+ const recallEmbeddingOptions = {
27393
+ ...embeddingOptions,
27394
+ taskType: "memory_recall_embed"
27395
+ };
27170
27396
  const tuningForward = {
27171
27397
  ...searchOptions?.rerankTopN !== void 0 && { rerankTopN: searchOptions.rerankTopN },
27172
27398
  ...searchOptions?.ceWeight !== void 0 && { ceWeight: searchOptions.ceWeight },
@@ -27220,6 +27446,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27220
27446
  const requestLimit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.floor(rawLimit), 1), RECALL_MAX_LIMIT) : limit;
27221
27447
  const argFolderId = args.folder_id;
27222
27448
  if (!query || typeof query !== "string") {
27449
+ notifyMemoryCallback(callbacks?.onError, new Error("A search query is required."));
27223
27450
  return "Error: A search query is required.";
27224
27451
  }
27225
27452
  try {
@@ -27227,7 +27454,11 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27227
27454
  const budget = wantsDecompose ? "high" : searchOptions?.rerank ? "mid" : "low";
27228
27455
  const folderId = searchOptions?.folderId ?? argFolderId;
27229
27456
  if (searchOptions?.useFusion === false) {
27457
+ let candidateCount;
27230
27458
  const legacyOptions = {
27459
+ onCandidateCount: (count2) => {
27460
+ candidateCount = count2;
27461
+ },
27231
27462
  limit: requestLimit,
27232
27463
  minSimilarity,
27233
27464
  useFusion: false,
@@ -27235,13 +27466,38 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27235
27466
  ...folderId !== void 0 && { folderId },
27236
27467
  ...searchOptions?.scopes && { scopes: searchOptions.scopes }
27237
27468
  };
27238
- const { results: legacy, embeddingsUnavailable } = "retain" in vaultCtx ? await vaultCtx.factSource.search(query, legacyOptions) : await searchVaultMemoriesWithSize(
27469
+ const start = Date.now();
27470
+ const outcome2 = "retain" in vaultCtx ? await vaultCtx.factSource.search(query, legacyOptions) : await searchVaultMemoriesWithSize(
27239
27471
  query,
27240
27472
  vaultCtx,
27241
- embeddingOptions,
27473
+ recallEmbeddingOptions,
27242
27474
  cache,
27243
27475
  legacyOptions
27244
27476
  );
27477
+ const { results: legacy, embeddingsUnavailable, vaultSize } = outcome2;
27478
+ const diagnostics = factRecallDiagnostics(
27479
+ legacy.map((m) => m.similarity),
27480
+ Date.now() - start,
27481
+ {
27482
+ vaultSize,
27483
+ minScore: minSimilarity,
27484
+ embeddingsUnavailable,
27485
+ candidateCount,
27486
+ truncated: candidateCount !== void 0 && candidateCount > requestLimit,
27487
+ decryptLast: outcome2.decryptLast,
27488
+ vaultRowsDecrypted: outcome2.rowsDecrypted,
27489
+ vaultRowsEmbedded: outcome2.rowsEmbedded,
27490
+ queryEmbedMs: outcome2.queryEmbedMs
27491
+ }
27492
+ );
27493
+ notifyMemoryCallback(callbacks?.onDiagnostics, diagnostics);
27494
+ if (diagnostics.truncated) {
27495
+ notifyMemoryCallback(callbacks?.onTruncated, {
27496
+ requestedLimit: requestLimit,
27497
+ effectiveLimit: requestLimit,
27498
+ truncatedByBudget: false
27499
+ });
27500
+ }
27245
27501
  if (legacy.length === 0) {
27246
27502
  return embeddingsUnavailable ? EMBEDDINGS_DEGRADED_EMPTY_NO_LEXICAL : "No relevant memories found in the vault.";
27247
27503
  }
@@ -27263,6 +27519,14 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27263
27519
  {
27264
27520
  onDiagnostics: (d) => {
27265
27521
  recallDegraded = d.degraded;
27522
+ notifyMemoryCallback(callbacks?.onDiagnostics, d);
27523
+ if (d.truncated) {
27524
+ notifyMemoryCallback(callbacks?.onTruncated, {
27525
+ requestedLimit: requestLimit,
27526
+ effectiveLimit: requestLimit,
27527
+ truncatedByBudget: false
27528
+ });
27529
+ }
27266
27530
  },
27267
27531
  types: ["fact"],
27268
27532
  limit: requestLimit,
@@ -27295,6 +27559,10 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
27295
27559
 
27296
27560
  ${formatted}`;
27297
27561
  } catch (error) {
27562
+ notifyMemoryCallback(
27563
+ callbacks?.onError,
27564
+ error instanceof Error ? error : new Error(String(error))
27565
+ );
27298
27566
  const message = error instanceof Error ? error.message : "Unknown error";
27299
27567
  return `Error searching vault: ${message}`;
27300
27568
  }