@anuma/sdk 6.24.1 → 6.25.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.
Files changed (40) hide show
  1. package/dist/expo/{chunk-XDBX4FZY.mjs → chunk-EDABJLYN.mjs} +441 -2
  2. package/dist/{react/consolidate-XKS6IZFC.mjs → expo/consolidate-5WM3CFKK.mjs} +2 -4
  3. package/dist/expo/index.cjs +16259 -16469
  4. package/dist/expo/index.d.mts +106 -18
  5. package/dist/expo/index.d.ts +106 -18
  6. package/dist/expo/index.mjs +14022 -5927
  7. package/dist/index.cjs +88 -46
  8. package/dist/index.mjs +88 -46
  9. package/dist/memory/context.d.mts +20 -1
  10. package/dist/memory/context.d.ts +20 -1
  11. package/dist/{server/chunk-RKQ4MRS2.mjs → react/chunk-BPMINZPO.mjs} +443 -2
  12. package/dist/{expo/consolidate-XBYKAFKT.mjs → react/consolidate-A2X3M47N.mjs} +2 -4
  13. package/dist/react/index.cjs +17892 -18131
  14. package/dist/react/index.d.mts +113 -18
  15. package/dist/react/index.d.ts +113 -18
  16. package/dist/react/index.mjs +11492 -3153
  17. package/dist/{react/chunk-GPLMMMZN.mjs → server/chunk-DWNEDXA5.mjs} +425 -4
  18. package/dist/server/{consolidate-54P6QZKU.mjs → consolidate-53I4CTD7.mjs} +2 -4
  19. package/dist/server/index.cjs +11333 -11520
  20. package/dist/server/index.d.mts +113 -18
  21. package/dist/server/index.d.ts +113 -18
  22. package/dist/server/index.mjs +26288 -4192
  23. package/dist/telemetry/index.d.mts +4 -0
  24. package/dist/telemetry/index.d.ts +4 -0
  25. package/dist/tools/index.cjs +112 -2
  26. package/dist/tools/index.d.mts +106 -1
  27. package/dist/tools/index.d.ts +106 -1
  28. package/dist/tools/index.mjs +110 -2
  29. package/dist/tools/selection/index.cjs +88 -46
  30. package/dist/tools/selection/index.mjs +88 -46
  31. package/package.json +1 -1
  32. package/dist/expo/chunk-5LKHK5LN.mjs +0 -8142
  33. package/dist/expo/chunk-WUG6GTHO.mjs +0 -445
  34. package/dist/expo/recall-4FSY2MC4.mjs +0 -8
  35. package/dist/react/chunk-DOYXFYB3.mjs +0 -8472
  36. package/dist/react/chunk-MHZB6KIX.mjs +0 -445
  37. package/dist/react/recall-6FVSDGWU.mjs +0 -8
  38. package/dist/server/chunk-FEDRFVHD.mjs +0 -22161
  39. package/dist/server/chunk-YXL5VKCO.mjs +0 -429
  40. package/dist/server/recall-Z2WJGXOL.mjs +0 -8
package/dist/index.cjs CHANGED
@@ -1336,11 +1336,54 @@ var DEFAULT_API_EMBEDDING_MODEL = "qwen/qwen3-embedding-8b";
1336
1336
 
1337
1337
  // src/lib/memoryEngine/generate.ts
1338
1338
  var EMBED_MAX_ATTEMPTS = 4;
1339
- async function withEmbeddingRetry(call) {
1339
+ var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
1340
+ var DEFAULT_EMBEDDING_TOKEN_TIMEOUT_MS = 1e4;
1341
+ async function withDeadline(run, ms, what, parent) {
1342
+ if (!(ms > 0) || !Number.isFinite(ms)) return Promise.resolve().then(() => run(parent));
1343
+ const controller = new AbortController();
1344
+ const onParentAbort = () => controller.abort(parent?.reason);
1345
+ let timer;
1346
+ try {
1347
+ if (parent?.aborted) onParentAbort();
1348
+ else parent?.addEventListener("abort", onParentAbort, { once: true });
1349
+ const work = Promise.resolve().then(() => run(controller.signal));
1350
+ work.catch(() => {
1351
+ });
1352
+ const deadline = new Promise((_, reject) => {
1353
+ timer = setTimeout(() => {
1354
+ const err = new Error(`${what} timed out after ${ms}ms`);
1355
+ err.name = "TimeoutError";
1356
+ controller.abort(err);
1357
+ reject(err);
1358
+ }, ms);
1359
+ });
1360
+ return await Promise.race([work, deadline]);
1361
+ } finally {
1362
+ clearTimeout(timer);
1363
+ parent?.removeEventListener("abort", onParentAbort);
1364
+ }
1365
+ }
1366
+ async function resolveAuthHeaders(options, parent) {
1367
+ const { getToken, apiKey } = options;
1368
+ if (apiKey) return { "X-API-Key": apiKey };
1369
+ if (!getToken) throw new Error("Either apiKey or getToken must be provided");
1370
+ const token = await withDeadline(
1371
+ () => getToken(),
1372
+ options.tokenTimeoutMs ?? DEFAULT_EMBEDDING_TOKEN_TIMEOUT_MS,
1373
+ "embedding auth token read",
1374
+ parent
1375
+ );
1376
+ if (!token) {
1377
+ throw new Error("No token available for embedding generation");
1378
+ }
1379
+ return { Authorization: `Bearer ${token}` };
1380
+ }
1381
+ async function withEmbeddingRetry(call, stop) {
1340
1382
  let last;
1341
1383
  let lastThrown;
1342
1384
  let threw = false;
1343
1385
  for (let attempt = 1; attempt <= EMBED_MAX_ATTEMPTS; attempt++) {
1386
+ if (stop?.aborted) throw stop.reason;
1344
1387
  try {
1345
1388
  threw = false;
1346
1389
  last = await call();
@@ -1375,34 +1418,36 @@ function embeddingErrorFrom(response) {
1375
1418
  return new EmbeddingHttpError(message, response.response?.status);
1376
1419
  }
1377
1420
  async function generateEmbedding(text, options) {
1378
- const { baseUrl = BASE_URL, getToken, apiKey, model, cache } = options;
1421
+ const total = options.totalTimeoutMs;
1422
+ if (total === void 0) return embedOne(text, options, void 0);
1423
+ return withDeadline((signal) => embedOne(text, options, signal), total, "embedding");
1424
+ }
1425
+ async function embedOne(text, options, outer) {
1426
+ const { baseUrl = BASE_URL, model, cache } = options;
1427
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
1379
1428
  if (cache) {
1380
1429
  const cached = cache.get(text);
1381
1430
  if (cached) return Array.from(cached);
1382
1431
  }
1383
- let headers;
1384
- if (apiKey) {
1385
- headers = { "X-API-Key": apiKey };
1386
- } else if (getToken) {
1387
- const token = await getToken();
1388
- if (!token) {
1389
- throw new Error("No token available for embedding generation");
1390
- }
1391
- headers = { Authorization: `Bearer ${token}` };
1392
- } else {
1393
- throw new Error("Either apiKey or getToken must be provided");
1394
- }
1432
+ const headers = await resolveAuthHeaders(options, outer);
1395
1433
  const response = await withEmbeddingRetry(
1396
- () => postApiV1Embeddings({
1397
- baseUrl,
1398
- body: {
1399
- // Mask PII from the request body only — the cache above still keys on
1400
- // the original `text`, so callers keep their original values.
1401
- input: options.maskInput ? options.maskInput(text) : text,
1402
- model: model ?? DEFAULT_API_EMBEDDING_MODEL
1403
- },
1404
- headers
1405
- })
1434
+ () => withDeadline(
1435
+ (signal) => postApiV1Embeddings({
1436
+ baseUrl,
1437
+ body: {
1438
+ // Mask PII from the request body only — the cache above still keys on
1439
+ // the original `text`, so callers keep their original values.
1440
+ input: options.maskInput ? options.maskInput(text) : text,
1441
+ model: model ?? DEFAULT_API_EMBEDDING_MODEL
1442
+ },
1443
+ headers,
1444
+ ...signal && { signal }
1445
+ }),
1446
+ timeoutMs,
1447
+ "embedding request",
1448
+ outer
1449
+ ),
1450
+ outer
1406
1451
  );
1407
1452
  if (response.error) {
1408
1453
  throw embeddingErrorFrom(response);
@@ -1425,14 +1470,19 @@ async function generateEmbedding(text, options) {
1425
1470
  }
1426
1471
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
1427
1472
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
1428
- async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage, maskInput) {
1473
+ async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage, maskInput, timeoutMs) {
1429
1474
  const response = await withEmbeddingRetry(
1430
- () => postApiV1Embeddings({
1431
- baseUrl,
1432
- // Mask PII from the request body only; cache/order still key on originals.
1433
- body: { input: maskInput ? texts.map(maskInput) : texts, model },
1434
- headers
1435
- })
1475
+ () => withDeadline(
1476
+ (signal) => postApiV1Embeddings({
1477
+ baseUrl,
1478
+ // Mask PII from the request body only; cache/order still key on originals.
1479
+ body: { input: maskInput ? texts.map(maskInput) : texts, model },
1480
+ headers,
1481
+ ...signal && { signal }
1482
+ }),
1483
+ timeoutMs,
1484
+ "embedding batch request"
1485
+ )
1436
1486
  );
1437
1487
  if (response.error) {
1438
1488
  throw embeddingErrorFrom(response);
@@ -1450,7 +1500,8 @@ async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage,
1450
1500
  }
1451
1501
  async function generateEmbeddings(texts, options) {
1452
1502
  if (texts.length === 0) return [];
1453
- const { baseUrl = BASE_URL, getToken, apiKey, model, batchSize, cache } = options;
1503
+ const { baseUrl = BASE_URL, model, batchSize, cache } = options;
1504
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
1454
1505
  const chunkSize = batchSize ?? DEFAULT_EMBEDDING_BATCH_SIZE;
1455
1506
  const results = new Array(texts.length).fill(null);
1456
1507
  const uncachedIndices = [];
@@ -1469,18 +1520,7 @@ async function generateEmbeddings(texts, options) {
1469
1520
  if (uncachedTexts.length === 0) {
1470
1521
  return results;
1471
1522
  }
1472
- let headers;
1473
- if (apiKey) {
1474
- headers = { "X-API-Key": apiKey };
1475
- } else if (getToken) {
1476
- const token = await getToken();
1477
- if (!token) {
1478
- throw new Error("No token available for embedding generation");
1479
- }
1480
- headers = { Authorization: `Bearer ${token}` };
1481
- } else {
1482
- throw new Error("Either apiKey or getToken must be provided");
1483
- }
1523
+ const headers = await resolveAuthHeaders(options);
1484
1524
  const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
1485
1525
  let newEmbeddings;
1486
1526
  if (uncachedTexts.length <= chunkSize) {
@@ -1490,7 +1530,8 @@ async function generateEmbeddings(texts, options) {
1490
1530
  baseUrl,
1491
1531
  embeddingModel,
1492
1532
  options.onUsage,
1493
- options.maskInput
1533
+ options.maskInput,
1534
+ timeoutMs
1494
1535
  );
1495
1536
  } else {
1496
1537
  const chunks = [];
@@ -1508,7 +1549,8 @@ async function generateEmbeddings(texts, options) {
1508
1549
  baseUrl,
1509
1550
  embeddingModel,
1510
1551
  options.onUsage,
1511
- options.maskInput
1552
+ options.maskInput,
1553
+ timeoutMs
1512
1554
  );
1513
1555
  }
1514
1556
  };
package/dist/index.mjs CHANGED
@@ -1134,11 +1134,54 @@ var DEFAULT_API_EMBEDDING_MODEL = "qwen/qwen3-embedding-8b";
1134
1134
 
1135
1135
  // src/lib/memoryEngine/generate.ts
1136
1136
  var EMBED_MAX_ATTEMPTS = 4;
1137
- async function withEmbeddingRetry(call) {
1137
+ var DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS = 15e3;
1138
+ var DEFAULT_EMBEDDING_TOKEN_TIMEOUT_MS = 1e4;
1139
+ async function withDeadline(run, ms, what, parent) {
1140
+ if (!(ms > 0) || !Number.isFinite(ms)) return Promise.resolve().then(() => run(parent));
1141
+ const controller = new AbortController();
1142
+ const onParentAbort = () => controller.abort(parent?.reason);
1143
+ let timer;
1144
+ try {
1145
+ if (parent?.aborted) onParentAbort();
1146
+ else parent?.addEventListener("abort", onParentAbort, { once: true });
1147
+ const work = Promise.resolve().then(() => run(controller.signal));
1148
+ work.catch(() => {
1149
+ });
1150
+ const deadline = new Promise((_, reject) => {
1151
+ timer = setTimeout(() => {
1152
+ const err = new Error(`${what} timed out after ${ms}ms`);
1153
+ err.name = "TimeoutError";
1154
+ controller.abort(err);
1155
+ reject(err);
1156
+ }, ms);
1157
+ });
1158
+ return await Promise.race([work, deadline]);
1159
+ } finally {
1160
+ clearTimeout(timer);
1161
+ parent?.removeEventListener("abort", onParentAbort);
1162
+ }
1163
+ }
1164
+ async function resolveAuthHeaders(options, parent) {
1165
+ const { getToken, apiKey } = options;
1166
+ if (apiKey) return { "X-API-Key": apiKey };
1167
+ if (!getToken) throw new Error("Either apiKey or getToken must be provided");
1168
+ const token = await withDeadline(
1169
+ () => getToken(),
1170
+ options.tokenTimeoutMs ?? DEFAULT_EMBEDDING_TOKEN_TIMEOUT_MS,
1171
+ "embedding auth token read",
1172
+ parent
1173
+ );
1174
+ if (!token) {
1175
+ throw new Error("No token available for embedding generation");
1176
+ }
1177
+ return { Authorization: `Bearer ${token}` };
1178
+ }
1179
+ async function withEmbeddingRetry(call, stop) {
1138
1180
  let last;
1139
1181
  let lastThrown;
1140
1182
  let threw = false;
1141
1183
  for (let attempt = 1; attempt <= EMBED_MAX_ATTEMPTS; attempt++) {
1184
+ if (stop?.aborted) throw stop.reason;
1142
1185
  try {
1143
1186
  threw = false;
1144
1187
  last = await call();
@@ -1173,34 +1216,36 @@ function embeddingErrorFrom(response) {
1173
1216
  return new EmbeddingHttpError(message, response.response?.status);
1174
1217
  }
1175
1218
  async function generateEmbedding(text, options) {
1176
- const { baseUrl = BASE_URL, getToken, apiKey, model, cache } = options;
1219
+ const total = options.totalTimeoutMs;
1220
+ if (total === void 0) return embedOne(text, options, void 0);
1221
+ return withDeadline((signal) => embedOne(text, options, signal), total, "embedding");
1222
+ }
1223
+ async function embedOne(text, options, outer) {
1224
+ const { baseUrl = BASE_URL, model, cache } = options;
1225
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
1177
1226
  if (cache) {
1178
1227
  const cached = cache.get(text);
1179
1228
  if (cached) return Array.from(cached);
1180
1229
  }
1181
- let headers;
1182
- if (apiKey) {
1183
- headers = { "X-API-Key": apiKey };
1184
- } else if (getToken) {
1185
- const token = await getToken();
1186
- if (!token) {
1187
- throw new Error("No token available for embedding generation");
1188
- }
1189
- headers = { Authorization: `Bearer ${token}` };
1190
- } else {
1191
- throw new Error("Either apiKey or getToken must be provided");
1192
- }
1230
+ const headers = await resolveAuthHeaders(options, outer);
1193
1231
  const response = await withEmbeddingRetry(
1194
- () => postApiV1Embeddings({
1195
- baseUrl,
1196
- body: {
1197
- // Mask PII from the request body only — the cache above still keys on
1198
- // the original `text`, so callers keep their original values.
1199
- input: options.maskInput ? options.maskInput(text) : text,
1200
- model: model ?? DEFAULT_API_EMBEDDING_MODEL
1201
- },
1202
- headers
1203
- })
1232
+ () => withDeadline(
1233
+ (signal) => postApiV1Embeddings({
1234
+ baseUrl,
1235
+ body: {
1236
+ // Mask PII from the request body only — the cache above still keys on
1237
+ // the original `text`, so callers keep their original values.
1238
+ input: options.maskInput ? options.maskInput(text) : text,
1239
+ model: model ?? DEFAULT_API_EMBEDDING_MODEL
1240
+ },
1241
+ headers,
1242
+ ...signal && { signal }
1243
+ }),
1244
+ timeoutMs,
1245
+ "embedding request",
1246
+ outer
1247
+ ),
1248
+ outer
1204
1249
  );
1205
1250
  if (response.error) {
1206
1251
  throw embeddingErrorFrom(response);
@@ -1223,14 +1268,19 @@ async function generateEmbedding(text, options) {
1223
1268
  }
1224
1269
  var DEFAULT_EMBEDDING_BATCH_SIZE = 100;
1225
1270
  var DEFAULT_EMBEDDING_BATCH_CONCURRENCY = 3;
1226
- async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage, maskInput) {
1271
+ async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage, maskInput, timeoutMs) {
1227
1272
  const response = await withEmbeddingRetry(
1228
- () => postApiV1Embeddings({
1229
- baseUrl,
1230
- // Mask PII from the request body only; cache/order still key on originals.
1231
- body: { input: maskInput ? texts.map(maskInput) : texts, model },
1232
- headers
1233
- })
1273
+ () => withDeadline(
1274
+ (signal) => postApiV1Embeddings({
1275
+ baseUrl,
1276
+ // Mask PII from the request body only; cache/order still key on originals.
1277
+ body: { input: maskInput ? texts.map(maskInput) : texts, model },
1278
+ headers,
1279
+ ...signal && { signal }
1280
+ }),
1281
+ timeoutMs,
1282
+ "embedding batch request"
1283
+ )
1234
1284
  );
1235
1285
  if (response.error) {
1236
1286
  throw embeddingErrorFrom(response);
@@ -1248,7 +1298,8 @@ async function generateEmbeddingsBatch(texts, headers, baseUrl, model, onUsage,
1248
1298
  }
1249
1299
  async function generateEmbeddings(texts, options) {
1250
1300
  if (texts.length === 0) return [];
1251
- const { baseUrl = BASE_URL, getToken, apiKey, model, batchSize, cache } = options;
1301
+ const { baseUrl = BASE_URL, model, batchSize, cache } = options;
1302
+ const timeoutMs = options.timeoutMs ?? DEFAULT_EMBEDDING_REQUEST_TIMEOUT_MS;
1252
1303
  const chunkSize = batchSize ?? DEFAULT_EMBEDDING_BATCH_SIZE;
1253
1304
  const results = new Array(texts.length).fill(null);
1254
1305
  const uncachedIndices = [];
@@ -1267,18 +1318,7 @@ async function generateEmbeddings(texts, options) {
1267
1318
  if (uncachedTexts.length === 0) {
1268
1319
  return results;
1269
1320
  }
1270
- let headers;
1271
- if (apiKey) {
1272
- headers = { "X-API-Key": apiKey };
1273
- } else if (getToken) {
1274
- const token = await getToken();
1275
- if (!token) {
1276
- throw new Error("No token available for embedding generation");
1277
- }
1278
- headers = { Authorization: `Bearer ${token}` };
1279
- } else {
1280
- throw new Error("Either apiKey or getToken must be provided");
1281
- }
1321
+ const headers = await resolveAuthHeaders(options);
1282
1322
  const embeddingModel = model ?? DEFAULT_API_EMBEDDING_MODEL;
1283
1323
  let newEmbeddings;
1284
1324
  if (uncachedTexts.length <= chunkSize) {
@@ -1288,7 +1328,8 @@ async function generateEmbeddings(texts, options) {
1288
1328
  baseUrl,
1289
1329
  embeddingModel,
1290
1330
  options.onUsage,
1291
- options.maskInput
1331
+ options.maskInput,
1332
+ timeoutMs
1292
1333
  );
1293
1334
  } else {
1294
1335
  const chunks = [];
@@ -1306,7 +1347,8 @@ async function generateEmbeddings(texts, options) {
1306
1347
  baseUrl,
1307
1348
  embeddingModel,
1308
1349
  options.onUsage,
1309
- options.maskInput
1350
+ options.maskInput,
1351
+ timeoutMs
1310
1352
  );
1311
1353
  }
1312
1354
  };
@@ -430,6 +430,20 @@ interface RecallOptions {
430
430
  rerankTopN?: number;
431
431
  /** Multiplicative cross-encoder blend weight. Default: 0.1. */
432
432
  ceWeight?: number;
433
+ /**
434
+ * Max ms a `mid`/`high` recall waits for the cross-encoder's FIRST model load
435
+ * before degrading to the fused ranking (reported as `rerank-unavailable`).
436
+ * Default: 10000. The load keeps going in the background for later calls.
437
+ */
438
+ rerankLoadTimeoutMs?: number;
439
+ /**
440
+ * Overall deadline, in ms, for embedding the query — token read, every retry
441
+ * attempt and the backoff between them. Default: 8000. On expiry the fact lane
442
+ * degrades to BM25 and the (cosine-only) chunk lane is skipped, reported as
443
+ * `embeddings-unavailable`, so an embeddings outage costs a turn at most this
444
+ * long rather than ~4 x the per-attempt timeout. `0` disables it.
445
+ */
446
+ queryEmbedTotalTimeoutMs?: number;
433
447
  /** Recency boost slope in the fused ranker. Default: 1.0. */
434
448
  recencyAlpha?: number;
435
449
  /** Recency decay curve overrides (per-year decay slope, floor, no-date multiplier). */
@@ -457,7 +471,8 @@ interface RecallOptions {
457
471
  maxHops?: number;
458
472
  /** Max neighbor entities expanded per hop. Default: 8. */
459
473
  entityFanout?: number;
460
- /** Hard cap on accumulated memory IDs across all hops. Default: 64. */
474
+ /** Hard cap on graph-lane memory IDs — across all hops, and on the single-hop
475
+ * lane `low`/`mid` run. Default: 64. */
461
476
  nodeBudget?: number;
462
477
  /**
463
478
  * PR5 — enable LLM graph path-refinement: at each traversal hop a model picks
@@ -669,6 +684,10 @@ interface RecallDiagnostics {
669
684
  * fast `vault_size = 0` population never established a baseline for this
670
685
  * cost, and why the ~850ms floor on the smallest NON-empty vaults had no
671
686
  * attributable owner.
687
+ *
688
+ * EXCEPTION — a mixed fact + chunk recall embeds the query ONCE, during
689
+ * {@link prep}, and hands the vector to both lanes. There `queryEmbed` is
690
+ * that shared embed and sits inside `prep`, not `factLane`.
672
691
  */
673
692
  queryEmbed: number;
674
693
  /** Chunk-lane search (`searchChunksOp`). */
@@ -430,6 +430,20 @@ interface RecallOptions {
430
430
  rerankTopN?: number;
431
431
  /** Multiplicative cross-encoder blend weight. Default: 0.1. */
432
432
  ceWeight?: number;
433
+ /**
434
+ * Max ms a `mid`/`high` recall waits for the cross-encoder's FIRST model load
435
+ * before degrading to the fused ranking (reported as `rerank-unavailable`).
436
+ * Default: 10000. The load keeps going in the background for later calls.
437
+ */
438
+ rerankLoadTimeoutMs?: number;
439
+ /**
440
+ * Overall deadline, in ms, for embedding the query — token read, every retry
441
+ * attempt and the backoff between them. Default: 8000. On expiry the fact lane
442
+ * degrades to BM25 and the (cosine-only) chunk lane is skipped, reported as
443
+ * `embeddings-unavailable`, so an embeddings outage costs a turn at most this
444
+ * long rather than ~4 x the per-attempt timeout. `0` disables it.
445
+ */
446
+ queryEmbedTotalTimeoutMs?: number;
433
447
  /** Recency boost slope in the fused ranker. Default: 1.0. */
434
448
  recencyAlpha?: number;
435
449
  /** Recency decay curve overrides (per-year decay slope, floor, no-date multiplier). */
@@ -457,7 +471,8 @@ interface RecallOptions {
457
471
  maxHops?: number;
458
472
  /** Max neighbor entities expanded per hop. Default: 8. */
459
473
  entityFanout?: number;
460
- /** Hard cap on accumulated memory IDs across all hops. Default: 64. */
474
+ /** Hard cap on graph-lane memory IDs — across all hops, and on the single-hop
475
+ * lane `low`/`mid` run. Default: 64. */
461
476
  nodeBudget?: number;
462
477
  /**
463
478
  * PR5 — enable LLM graph path-refinement: at each traversal hop a model picks
@@ -669,6 +684,10 @@ interface RecallDiagnostics {
669
684
  * fast `vault_size = 0` population never established a baseline for this
670
685
  * cost, and why the ~850ms floor on the smallest NON-empty vaults had no
671
686
  * attributable owner.
687
+ *
688
+ * EXCEPTION — a mixed fact + chunk recall embeds the query ONCE, during
689
+ * {@link prep}, and hands the vector to both lanes. There `queryEmbed` is
690
+ * that shared embed and sits inside `prep`, not `factLane`.
672
691
  */
673
692
  queryEmbed: number;
674
693
  /** Chunk-lane search (`searchChunksOp`). */