memorix 1.1.11 → 1.1.13

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.
@@ -4773,6 +4773,45 @@ function dimsCacheKey(config) {
4773
4773
  config.requestedDimensions ?? "native"
4774
4774
  ].join("|");
4775
4775
  }
4776
+ function isValidDimension(value) {
4777
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
4778
+ }
4779
+ async function loadCachedVectorDimensions(config) {
4780
+ try {
4781
+ const raw = await readFile2(CACHE_META_FILE, "utf-8");
4782
+ const data = JSON.parse(raw);
4783
+ const namespace = cacheNamespace(config);
4784
+ if (!Array.isArray(data?.entries)) return null;
4785
+ const entry = data.entries.find(
4786
+ (candidate) => typeof candidate === "object" && candidate !== null && candidate.namespace === namespace && isValidDimension(candidate.dimensions)
4787
+ );
4788
+ return entry?.dimensions ?? null;
4789
+ } catch {
4790
+ return null;
4791
+ }
4792
+ }
4793
+ async function saveCachedVectorDimensions(config, dimensions) {
4794
+ if (!isValidDimension(dimensions)) return;
4795
+ try {
4796
+ await mkdir2(CACHE_DIR2, { recursive: true });
4797
+ let entries = [];
4798
+ try {
4799
+ const raw = await readFile2(CACHE_META_FILE, "utf-8");
4800
+ const data = JSON.parse(raw);
4801
+ if (Array.isArray(data?.entries)) {
4802
+ entries = data.entries.filter(
4803
+ (candidate) => typeof candidate === "object" && candidate !== null && typeof candidate.namespace === "string" && isValidDimension(candidate.dimensions) && typeof candidate.ts === "number"
4804
+ );
4805
+ }
4806
+ } catch {
4807
+ }
4808
+ const namespace = cacheNamespace(config);
4809
+ entries = entries.filter((entry) => entry.namespace !== namespace);
4810
+ entries.push({ namespace, dimensions, ts: Date.now() });
4811
+ await writeFile2(CACHE_META_FILE, JSON.stringify({ version: 1, entries }));
4812
+ } catch {
4813
+ }
4814
+ }
4776
4815
  async function loadCachedDims(config) {
4777
4816
  try {
4778
4817
  const raw = await readFile2(DIMS_CACHE_FILE, "utf-8");
@@ -4835,6 +4874,7 @@ async function saveCachedDims(config, dimensions) {
4835
4874
  await writeFile2(DIMS_CACHE_FILE, JSON.stringify({ entries }));
4836
4875
  } catch {
4837
4876
  }
4877
+ await saveCachedVectorDimensions(config, dimensions);
4838
4878
  }
4839
4879
  async function saveDiskCacheNow() {
4840
4880
  if (!diskCacheDirty2) return;
@@ -4917,7 +4957,7 @@ async function fetchWithRetry(url, apiKey, body, attempt = 0) {
4917
4957
  const errorText2 = await response.text().catch(() => "unknown error");
4918
4958
  throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
4919
4959
  }
4920
- var CACHE_DIR2, CACHE_FILE2, DIMS_CACHE_FILE, cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES, BASE_DELAY_MS, APIEmbeddingProvider;
4960
+ var CACHE_DIR2, CACHE_FILE2, DIMS_CACHE_FILE, CACHE_META_FILE, cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES, BASE_DELAY_MS, APIEmbeddingProvider;
4921
4961
  var init_api_provider = __esm({
4922
4962
  "src/embedding/api-provider.ts"() {
4923
4963
  "use strict";
@@ -4925,6 +4965,7 @@ var init_api_provider = __esm({
4925
4965
  CACHE_DIR2 = process.env.MEMORIX_DATA_DIR || join4(homedir7(), ".memorix", "data");
4926
4966
  CACHE_FILE2 = join4(CACHE_DIR2, ".embedding-api-cache.json");
4927
4967
  DIMS_CACHE_FILE = join4(CACHE_DIR2, ".embedding-dims-cache.json");
4968
+ CACHE_META_FILE = join4(CACHE_DIR2, ".embedding-api-cache-meta.json");
4928
4969
  cache3 = /* @__PURE__ */ new Map();
4929
4970
  MAX_CACHE_SIZE3 = 1e4;
4930
4971
  diskCacheDirty2 = false;
@@ -4951,13 +4992,22 @@ var init_api_provider = __esm({
4951
4992
  this.dimensions = detectedDimensions;
4952
4993
  this.name = `api-${config.model.replace(/\//g, "-")}`;
4953
4994
  }
4954
- static async create() {
4995
+ static async create(options = {}) {
4955
4996
  const config = _APIEmbeddingProvider.resolveConfig();
4956
- startDiskCacheLoad();
4997
+ const allowNetworkProbe = options.allowNetworkProbe !== false;
4957
4998
  let probeDimensions = await loadCachedDims(config);
4999
+ let dimensionSource = "dims-cache";
5000
+ if (probeDimensions === null) {
5001
+ probeDimensions = await loadCachedVectorDimensions(config);
5002
+ if (probeDimensions !== null) dimensionSource = "vector-cache";
5003
+ }
4958
5004
  if (probeDimensions !== null) {
4959
- console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d) [cached dims]`);
5005
+ console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d) [${dimensionSource}]`);
5006
+ if (dimensionSource === "dims-cache") {
5007
+ void saveCachedVectorDimensions(config, probeDimensions);
5008
+ }
4960
5009
  } else {
5010
+ if (!allowNetworkProbe) return null;
4961
5011
  probeDimensions = await _APIEmbeddingProvider.probeAPI(config);
4962
5012
  console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d)`);
4963
5013
  saveCachedDims(config, probeDimensions).catch(() => {
@@ -4966,6 +5016,7 @@ var init_api_provider = __esm({
4966
5016
  if (config.requestedDimensions) {
4967
5017
  console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
4968
5018
  }
5019
+ startDiskCacheLoad();
4969
5020
  return new _APIEmbeddingProvider(config, probeDimensions);
4970
5021
  }
4971
5022
  static resolveConfig() {
@@ -5144,6 +5195,13 @@ var init_api_provider = __esm({
5144
5195
  scheduleDiskSave();
5145
5196
  return results;
5146
5197
  }
5198
+ async getCachedEmbeddings(texts) {
5199
+ await ensureDiskCacheLoaded();
5200
+ return texts.map((text) => {
5201
+ const hash = textHash2(normalizeText(text), this.cacheKeyNamespace);
5202
+ return cache3.get(hash) ?? null;
5203
+ });
5204
+ }
5147
5205
  getStats() {
5148
5206
  return {
5149
5207
  totalTokens: this.totalTokensUsed,
@@ -5252,10 +5310,10 @@ async function createTransformersProvider() {
5252
5310
  return null;
5253
5311
  }
5254
5312
  }
5255
- async function createAPIProvider() {
5313
+ async function createAPIProvider(options) {
5256
5314
  try {
5257
5315
  const { APIEmbeddingProvider: APIEmbeddingProvider2 } = await Promise.resolve().then(() => (init_api_provider(), api_provider_exports));
5258
- return await APIEmbeddingProvider2.create();
5316
+ return await APIEmbeddingProvider2.create(options ?? {});
5259
5317
  } catch (e) {
5260
5318
  warnOnce(`[memorix] Failed to init API embedding: ${e instanceof Error ? e.message : e}`);
5261
5319
  return null;
@@ -5310,11 +5368,24 @@ function wrapProvider(candidate) {
5310
5368
  }
5311
5369
  throw error;
5312
5370
  }
5313
- }
5371
+ },
5372
+ ...candidate.getCachedEmbeddings ? {
5373
+ async getCachedEmbeddings(texts) {
5374
+ return candidate.getCachedEmbeddings(texts);
5375
+ }
5376
+ } : {}
5314
5377
  };
5315
5378
  }
5316
- async function getEmbeddingProvider() {
5379
+ async function getEmbeddingProvider(options = {}) {
5317
5380
  if (provider) return provider;
5381
+ if (options.allowNetworkProbe === false) {
5382
+ const mode = getEmbeddingMode2();
5383
+ if (mode !== "api" && !(mode === "auto" && hasAPIEmbeddingConfig())) {
5384
+ return null;
5385
+ }
5386
+ const cached = await createAPIProvider({ allowNetworkProbe: false });
5387
+ return cached ? wrapProvider(cached) : null;
5388
+ }
5318
5389
  if (lastInitWasTemporaryFailure) {
5319
5390
  const elapsed = Date.now() - lastFailureTimestamp;
5320
5391
  if (elapsed < RETRY_COOLDOWN_MS) {
@@ -6315,12 +6386,15 @@ __export(orama_store_exports, {
6315
6386
  batchGenerateEmbeddings: () => batchGenerateEmbeddings,
6316
6387
  generateEmbedding: () => generateEmbedding,
6317
6388
  getDb: () => getDb,
6389
+ getDeferredCachedVectorHydration: () => getDeferredCachedVectorHydration,
6318
6390
  getLastSearchMode: () => getLastSearchMode,
6319
6391
  getObservationCount: () => getObservationCount,
6320
6392
  getObservationsByIds: () => getObservationsByIds,
6321
6393
  getTimeline: () => getTimeline,
6322
6394
  getVectorDimensions: () => getVectorDimensions,
6395
+ hasObservationVector: () => hasObservationVector,
6323
6396
  hydrateIndex: () => hydrateIndex,
6397
+ hydrateIndexForStartup: () => hydrateIndexForStartup,
6324
6398
  insertObservation: () => insertObservation,
6325
6399
  isEmbeddingEnabled: () => isEmbeddingEnabled,
6326
6400
  makeOramaObservationId: () => makeOramaObservationId,
@@ -6381,11 +6455,10 @@ function stripVectorSearchParams(params) {
6381
6455
  const { mode, vector, similarity, hybridWeights, ...rest } = params;
6382
6456
  return rest;
6383
6457
  }
6384
- async function getDb() {
6385
- if (db) return db;
6386
- const provider2 = await getEmbeddingProvider();
6387
- embeddingEnabled = provider2 !== null;
6388
- embeddingDimensions = provider2?.dimensions ?? null;
6458
+ async function initializeDb(options, generation) {
6459
+ const provider2 = await getEmbeddingProvider({ allowNetworkProbe: options.allowNetworkProbe });
6460
+ const nextEmbeddingEnabled = provider2 !== null;
6461
+ const nextEmbeddingDimensions = provider2?.dimensions ?? null;
6389
6462
  const baseSchema = {
6390
6463
  id: "string",
6391
6464
  observationId: "number",
@@ -6408,15 +6481,39 @@ async function getDb() {
6408
6481
  documentType: "string",
6409
6482
  knowledgeLayer: "string"
6410
6483
  };
6411
- const dims = embeddingDimensions ?? 384;
6412
- const schema = embeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
6413
- db = await create({ schema });
6414
- return db;
6484
+ const dims = nextEmbeddingDimensions ?? 384;
6485
+ const schema = nextEmbeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
6486
+ const nextDb = await create({ schema });
6487
+ if (generation !== dbGeneration) {
6488
+ return getDb(options);
6489
+ }
6490
+ indexEmbeddingProvider = provider2;
6491
+ embeddingEnabled = nextEmbeddingEnabled;
6492
+ embeddingDimensions = nextEmbeddingDimensions;
6493
+ db = nextDb;
6494
+ return nextDb;
6495
+ }
6496
+ async function getDb(options = {}) {
6497
+ if (db) return db;
6498
+ if (dbInitPromise) return dbInitPromise;
6499
+ const initPromise2 = initializeDb(options, dbGeneration);
6500
+ dbInitPromise = initPromise2;
6501
+ try {
6502
+ return await initPromise2;
6503
+ } finally {
6504
+ if (dbInitPromise === initPromise2) {
6505
+ dbInitPromise = null;
6506
+ }
6507
+ }
6415
6508
  }
6416
6509
  async function resetDb() {
6510
+ dbGeneration++;
6417
6511
  db = null;
6512
+ dbInitPromise = null;
6418
6513
  embeddingEnabled = false;
6419
6514
  embeddingDimensions = null;
6515
+ indexEmbeddingProvider = null;
6516
+ deferredCachedVectorHydration = null;
6420
6517
  lastSearchModeByProject.clear();
6421
6518
  docByObservationKey.clear();
6422
6519
  }
@@ -6444,14 +6541,64 @@ async function batchGenerateEmbeddings(texts) {
6444
6541
  return texts.map(() => null);
6445
6542
  }
6446
6543
  }
6447
- async function hydrateIndex(observations2) {
6448
- const database = await getDb();
6544
+ async function getCachedEmbeddings(texts) {
6545
+ if (!embeddingEnabled || texts.length === 0) return texts.map(() => null);
6546
+ const provider2 = indexEmbeddingProvider;
6547
+ if (!provider2?.getCachedEmbeddings) return texts.map(() => null);
6548
+ try {
6549
+ return await provider2.getCachedEmbeddings(texts);
6550
+ } catch {
6551
+ return texts.map(() => null);
6552
+ }
6553
+ }
6554
+ function observationEmbeddingText(observation) {
6555
+ return [
6556
+ observation.title ?? "",
6557
+ observation.narrative ?? "",
6558
+ ...Array.isArray(observation.facts) ? observation.facts : []
6559
+ ].join(" ");
6560
+ }
6561
+ function documentEmbeddingText(document) {
6562
+ return [document.title ?? "", document.narrative ?? "", document.facts ?? ""].join(" ");
6563
+ }
6564
+ function isCompatibleCachedVector(vector) {
6565
+ return Boolean(
6566
+ vector && embeddingDimensions !== null && vector.length === embeddingDimensions && vector.every(Number.isFinite)
6567
+ );
6568
+ }
6569
+ async function attachCachedVectors(database, candidates) {
6570
+ const cachedVectors = await getCachedEmbeddings(
6571
+ candidates.map(({ observation }) => observationEmbeddingText(observation))
6572
+ );
6573
+ if (db !== database) return;
6574
+ for (let index = 0; index < candidates.length; index++) {
6575
+ const vector = cachedVectors[index];
6576
+ if (!isCompatibleCachedVector(vector)) continue;
6577
+ const existing = getByID(database, candidates[index].id);
6578
+ if (!existing || documentEmbeddingText(existing) !== observationEmbeddingText(candidates[index].observation)) continue;
6579
+ try {
6580
+ await update(database, candidates[index].id, { ...existing, embedding: vector });
6581
+ } catch {
6582
+ }
6583
+ }
6584
+ }
6585
+ async function hydrateIndex(observations2, options = {}) {
6586
+ const database = await getDb({ allowNetworkProbe: options.allowNetworkProbe });
6587
+ const candidates = [];
6588
+ for (const observation of observations2) {
6589
+ if (!observation || !observation.id || !observation.projectId) continue;
6590
+ const id = makeOramaObservationId(observation.projectId, observation.id);
6591
+ if (getByID(database, id)) continue;
6592
+ candidates.push({ observation, id });
6593
+ }
6594
+ const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
6595
+ const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
6449
6596
  let inserted = 0;
6450
- for (const obs of observations2) {
6451
- if (!obs || !obs.id || !obs.projectId) continue;
6597
+ for (let index = 0; index < candidates.length; index++) {
6598
+ const { observation: obs, id } = candidates[index];
6452
6599
  try {
6453
- const id = makeOramaObservationId(obs.projectId, obs.id);
6454
- if (getByID(database, id)) continue;
6600
+ const vector = cachedVectors[index];
6601
+ const compatibleVector = isCompatibleCachedVector(vector) ? vector : null;
6455
6602
  const doc = {
6456
6603
  id,
6457
6604
  observationId: obs.id,
@@ -6470,7 +6617,8 @@ async function hydrateIndex(observations2) {
6470
6617
  status: obs.status ?? "active",
6471
6618
  source: obs.source || "agent",
6472
6619
  documentType: "observation",
6473
- knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source)
6620
+ knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
6621
+ ...compatibleVector ? { embedding: compatibleVector } : {}
6474
6622
  };
6475
6623
  await insert2(database, doc);
6476
6624
  rememberObservationDoc(doc);
@@ -6478,8 +6626,24 @@ async function hydrateIndex(observations2) {
6478
6626
  } catch {
6479
6627
  }
6480
6628
  }
6629
+ deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6630
+ }) : null;
6481
6631
  return inserted;
6482
6632
  }
6633
+ async function hydrateIndexForStartup(observations2) {
6634
+ return hydrateIndex(observations2, {
6635
+ allowNetworkProbe: false,
6636
+ deferCachedVectors: true
6637
+ });
6638
+ }
6639
+ function getDeferredCachedVectorHydration() {
6640
+ return deferredCachedVectorHydration;
6641
+ }
6642
+ function hasObservationVector(projectId, observationId) {
6643
+ if (!db || !embeddingEnabled || embeddingDimensions === null) return false;
6644
+ const document = getByID(db, makeOramaObservationId(projectId, observationId));
6645
+ return Array.isArray(document?.embedding) && document.embedding.length === embeddingDimensions && document.embedding.every(Number.isFinite);
6646
+ }
6483
6647
  async function insertObservation(doc) {
6484
6648
  const database = await getDb();
6485
6649
  await insert2(database, doc);
@@ -7013,7 +7177,7 @@ function formatTime(isoDate) {
7013
7177
  return isoDate;
7014
7178
  }
7015
7179
  }
7016
- var db, embeddingEnabled, embeddingDimensions, docByObservationKey, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, COMMAND_LOG_TITLE, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
7180
+ var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration, docByObservationKey, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, COMMAND_LOG_TITLE, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
7017
7181
  var init_orama_store = __esm({
7018
7182
  "src/store/orama-store.ts"() {
7019
7183
  "use strict";
@@ -7025,8 +7189,12 @@ var init_orama_store = __esm({
7025
7189
  init_intent_detector();
7026
7190
  init_query_expansion();
7027
7191
  db = null;
7192
+ dbInitPromise = null;
7193
+ dbGeneration = 0;
7028
7194
  embeddingEnabled = false;
7029
7195
  embeddingDimensions = null;
7196
+ indexEmbeddingProvider = null;
7197
+ deferredCachedVectorHydration = null;
7030
7198
  docByObservationKey = /* @__PURE__ */ new Map();
7031
7199
  NON_CJK_HYBRID_SIMILARITY = 0.45;
7032
7200
  lastSearchModeByProject = /* @__PURE__ */ new Map();
@@ -7217,7 +7385,18 @@ async function bindObservationCodeRefsBestEffort(observation) {
7217
7385
  function isVectorCompatibleWithCurrentIndex(embedding) {
7218
7386
  if (!embedding) return false;
7219
7387
  const vectorDimensions = getVectorDimensions();
7220
- return vectorDimensions === null || embedding.length === vectorDimensions;
7388
+ return vectorDimensions !== null && embedding.length === vectorDimensions && embedding.every(Number.isFinite);
7389
+ }
7390
+ async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
7391
+ if (isVectorCompatibleWithCurrentIndex(embedding)) return true;
7392
+ if (getVectorDimensions() !== null || !embedding.every(Number.isFinite)) return false;
7393
+ const targetProjectDir = projectDir;
7394
+ if (!vectorSchemaUpgradePromise) {
7395
+ vectorSchemaUpgradePromise = reindexObservations().then(() => projectDir === targetProjectDir && isVectorCompatibleWithCurrentIndex(embedding)).catch(() => false).finally(() => {
7396
+ vectorSchemaUpgradePromise = null;
7397
+ });
7398
+ }
7399
+ return vectorSchemaUpgradePromise;
7221
7400
  }
7222
7401
  async function initObservations(dir) {
7223
7402
  if (projectDir === dir) return;
@@ -7227,6 +7406,7 @@ async function initObservations(dir) {
7227
7406
  nextId = await store.loadIdCounter();
7228
7407
  projectDir = dir;
7229
7408
  searchIndexPrepared = false;
7409
+ vectorSchemaUpgradePromise = null;
7230
7410
  }
7231
7411
  async function ensureFreshObservations() {
7232
7412
  if (!projectDir) return false;
@@ -7406,6 +7586,9 @@ async function storeObservation(input) {
7406
7586
  const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
7407
7587
  generateEmbedding(searchableText).then(async (embedding) => {
7408
7588
  if (embedding) {
7589
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7590
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
7591
+ }
7409
7592
  if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7410
7593
  const vectorDimensions = getVectorDimensions();
7411
7594
  console.error(
@@ -7516,6 +7699,13 @@ async function upsertObservation(existing, input, now) {
7516
7699
  vectorMissingIds.add(obsId);
7517
7700
  generateEmbedding(searchableText).then(async (embedding) => {
7518
7701
  if (embedding) {
7702
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7703
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
7704
+ }
7705
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7706
+ queueVectorBackfill(existing.projectId);
7707
+ return;
7708
+ }
7519
7709
  try {
7520
7710
  const { removeObservation: removeObs } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
7521
7711
  await removeObs(makeOramaObservationId(existing.projectId, obsId));
@@ -7660,14 +7850,21 @@ async function reindexObservations() {
7660
7850
  let embeddings = observations.map(() => null);
7661
7851
  const provider2 = await getEmbeddingProvider();
7662
7852
  const canBatchEmbedAtStartup = provider2 !== null && !provider2.name.startsWith("api-");
7853
+ await getDb();
7854
+ const texts = observations.map(
7855
+ (obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
7856
+ );
7857
+ if (provider2?.getCachedEmbeddings) {
7858
+ try {
7859
+ embeddings = await provider2.getCachedEmbeddings(texts);
7860
+ } catch {
7861
+ }
7862
+ }
7663
7863
  if (provider2 && !canBatchEmbedAtStartup) {
7664
- console.error("[memorix] Startup reindex: skipping synchronous API embeddings; background backfill will hydrate vectors");
7864
+ console.error("[memorix] Startup reindex: restored cached API embeddings; uncached vectors stay in background recovery");
7665
7865
  }
7666
7866
  if (canBatchEmbedAtStartup) {
7667
7867
  try {
7668
- const texts = observations.map(
7669
- (obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
7670
- );
7671
7868
  embeddings = await batchGenerateEmbeddings(texts);
7672
7869
  } catch {
7673
7870
  }
@@ -7720,16 +7917,31 @@ async function reindexObservations() {
7720
7917
  }
7721
7918
  async function prepareSearchIndex() {
7722
7919
  if (searchIndexPrepared) return 0;
7723
- const count2 = await hydrateIndex(observations);
7920
+ const count2 = await hydrateIndexForStartup(observations);
7724
7921
  if (count2 === 0) {
7725
7922
  searchIndexPrepared = true;
7726
7923
  return 0;
7727
7924
  }
7728
- vectorMissingIds.clear();
7729
- if (isEmbeddingEnabled()) {
7730
- for (const obs of observations) {
7731
- vectorMissingIds.add(obs.id);
7925
+ const hydratedProjectDir = projectDir;
7926
+ const queueMissingVectors = () => {
7927
+ if (projectDir !== hydratedProjectDir) return;
7928
+ vectorMissingIds.clear();
7929
+ if (!isEmbeddingExplicitlyDisabled()) {
7930
+ const projectsWithMissingVectors = /* @__PURE__ */ new Set();
7931
+ for (const obs of observations) {
7932
+ if (!hasObservationVector(obs.projectId, obs.id)) {
7933
+ vectorMissingIds.add(obs.id);
7934
+ projectsWithMissingVectors.add(obs.projectId);
7935
+ }
7936
+ }
7937
+ for (const projectId of projectsWithMissingVectors) queueVectorBackfill(projectId);
7732
7938
  }
7939
+ };
7940
+ const cacheHydration = getDeferredCachedVectorHydration();
7941
+ if (cacheHydration) {
7942
+ void cacheHydration.finally(queueMissingVectors);
7943
+ } else {
7944
+ queueMissingVectors();
7733
7945
  }
7734
7946
  searchIndexPrepared = true;
7735
7947
  return count2;
@@ -7789,6 +8001,9 @@ async function backfillVectorEmbeddings(options = {}) {
7789
8001
  try {
7790
8002
  const embedding = await generateEmbedding(text);
7791
8003
  if (embedding) {
8004
+ if (!isVectorCompatibleWithCurrentIndex(embedding)) {
8005
+ await upgradeVectorSchemaAfterFirstEmbedding(embedding);
8006
+ }
7792
8007
  if (!isVectorCompatibleWithCurrentIndex(embedding)) {
7793
8008
  const vectorDimensions = getVectorDimensions();
7794
8009
  console.error(
@@ -7851,7 +8066,7 @@ async function backfillVectorEmbeddings(options = {}) {
7851
8066
  }
7852
8067
  return { attempted: ids.length, succeeded, failed };
7853
8068
  }
7854
- var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
8069
+ var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
7855
8070
  var init_observations = __esm({
7856
8071
  "src/memory/observations.ts"() {
7857
8072
  "use strict";
@@ -7869,6 +8084,7 @@ var init_observations = __esm({
7869
8084
  searchIndexPrepared = false;
7870
8085
  vectorMissingIds = /* @__PURE__ */ new Set();
7871
8086
  vectorBackfillRunning = false;
8087
+ vectorSchemaUpgradePromise = null;
7872
8088
  lastVectorBackfill = null;
7873
8089
  embeddingFailureLogTimestamps = /* @__PURE__ */ new Map();
7874
8090
  EMBEDDING_FAILURE_LOG_COOLDOWN_MS = 3e4;