From e2c44aab2af4fd8bc5dabe1404c338c06432752b Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Wed, 20 May 2026 16:42:03 +0100 Subject: [PATCH] fix(boot): make vector-dim mismatch recovery self-explanatory and persistent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recovery from a persisted vector-index dimension mismatch (after the user changes EMBEDDING_PROVIDER) was unnecessarily hard: 1. The error pointed users at `AGENTMEMORY_DROP_STALE_INDEX=true` but never told them which `.env` file the running process actually reads, which matters under LaunchAgent / systemd / Docker contexts where HOME can differ from the expected user directory. 2. Even after setting the flag, the dropStale branch only skipped restoring the bad vectors in memory — the stale payload stayed in KV. Removing the flag on the next boot would crash-loop the server again because the persisted index was still bad. Two minimal changes: - Export the resolved data dir + env-file paths from config and surface them in the error message, with a ready-to-paste `echo … >> $envFile` recovery line and an explicit HOME-resolution note for service-managed deployments. - In the dropStale branch, persist the now-cleared index back via indexPersistence.save() so the recovery is one-shot: the flag drops the stale payload AND clears the on-disk KV, so the next boot is clean even after the flag is removed. Tests (1081) + build pass. --- src/config.ts | 12 ++++++++++++ src/index.ts | 33 ++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/config.ts b/src/config.ts index eed5725e..ec9e27b1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -19,6 +19,18 @@ function safeParseInt(value: string | undefined, fallback: number): number { const DATA_DIR = join(homedir(), ".agentmemory"); const ENV_FILE = join(DATA_DIR, ".env"); +/** + * Resolved paths that the runtime actually reads. Exported so error + * messages can show users the exact location where their flags belong, + * which matters when HOME differs from the expected user dir (LaunchAgent + * plists, systemd units, Docker, etc.). + */ +export const RESOLVED_PATHS = { + dataDir: DATA_DIR, + envFile: ENV_FILE, + envFileExists: (): boolean => existsSync(ENV_FILE), +}; + function loadEnvFile(): Record { if (!existsSync(ENV_FILE)) return {}; const content = readFileSync(ENV_FILE, "utf-8"); diff --git a/src/index.ts b/src/index.ts index 704d4809..43941c3b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import { isConsolidationEnabled, isContextInjectionEnabled, isDropStaleIndexEnabled, + RESOLVED_PATHS, } from "./config.js"; import { createProvider, @@ -387,18 +388,40 @@ async function main() { `AGENTMEMORY_DROP_STALE_INDEX=true is set — discarding the persisted ` + `vectors. Live observations will rebuild the index over time.`, ); + // Persist the empty vector index back so subsequent starts don't + // trip the same guard. Without this, the KV still holds the stale + // payload, so removing AGENTMEMORY_DROP_STALE_INDEX from .env would + // crash-loop the server again on the next boot. + await indexPersistence.save().catch((err) => { + console.warn( + `[agentmemory] Failed to persist cleared vector index:`, + err, + ); + }); } else { + const envExists = RESOLVED_PATHS.envFileExists(); throw new Error( `[agentmemory] Refusing to start: persisted vector index has ` + `${mismatches.length} of ${loaded.vector.size} vectors with the ` + `wrong dimension. Active provider (${embeddingProvider?.name}) ` + `declares ${activeDim}; dimensions seen on disk: ${distinct}. ` + `First mismatched obsIds: ${sample}. Loading would silently corrupt ` + - `search (cross-dimension cosine returns 0). Choose one:\n` + - ` - Re-embed the existing index against the new provider, then start.\n` + - ` - Set AGENTMEMORY_DROP_STALE_INDEX=true to discard the persisted ` + - `vectors and rebuild from live observations.\n` + - ` - Switch the embedding provider back to the one that wrote the index.`, + `search (cross-dimension cosine returns 0).\n` + + `\n` + + `Resolved paths:\n` + + ` data dir: ${RESOLVED_PATHS.dataDir}\n` + + ` env file: ${RESOLVED_PATHS.envFile} (exists: ${envExists})\n` + + `\n` + + `Recovery — pick ONE:\n` + + ` 1. One-shot drop + rebuild (recommended):\n` + + ` echo 'AGENTMEMORY_DROP_STALE_INDEX=true' >> ${RESOLVED_PATHS.envFile}\n` + + ` # restart agentmemory; the flag can be removed after the next clean boot.\n` + + ` 2. Re-embed the existing index against the new provider, then start.\n` + + ` 3. Switch the embedding provider back to the one that wrote the index.\n` + + `\n` + + `If running under a service manager (LaunchAgent, systemd, Docker), confirm\n` + + `HOME points at the user account that owns ${RESOLVED_PATHS.dataDir} —\n` + + `the .env file above is what the running process actually reads.`, ); } } else {