fix(memory): warn cleanly on degraded vector recall

This commit is contained in:
Peter Steinberger
2026-04-06 15:20:18 +01:00
parent a224f59fe3
commit 0337a0d7f8
3 changed files with 30 additions and 3 deletions

View File

@@ -856,13 +856,14 @@ export async function runMemoryIndex(opts: MemoryCommandOptions) {
if (qmdIndexSummary) {
defaultRuntime.log(qmdIndexSummary);
}
// HELM-0251: surface warning if vec0/chunks_vec was not updated
const postIndexStatus = manager.status();
const vectorEnabled = postIndexStatus.vector?.enabled ?? false;
const vectorAvailable = postIndexStatus.vector?.available;
const vectorLoadErr = postIndexStatus.vector?.loadError;
if (vectorEnabled && vectorAvailable === false) {
const errDetail = vectorLoadErr ? `: ${vectorLoadErr}` : "";
// Indexing still persisted chunks/FTS state; keep the command successful but
// emit a stderr warning so operators and scripts can detect degraded recall.
defaultRuntime.error(
`Memory index WARNING (${agentId}): chunks_vec not updated — sqlite-vec unavailable${errDetail}. Vector recall degraded.`,
);

View File

@@ -529,6 +529,33 @@ describe("memory cli", () => {
expect(log).toHaveBeenCalledWith("Memory index updated (main).");
});
it("warns on stderr when index completes without sqlite-vec embeddings", async () => {
const close = vi.fn(async () => {});
const sync = vi.fn(async () => {});
mockManager({
sync,
status: () =>
makeMemoryStatus({
vector: {
enabled: true,
available: false,
loadError: "load failed",
},
}),
close,
});
const error = spyRuntimeErrors(defaultRuntime);
await runMemoryCli(["index"]);
expectCliSync(sync);
expect(error).toHaveBeenCalledWith(
"Memory index WARNING (main): chunks_vec not updated — sqlite-vec unavailable: load failed. Vector recall degraded.",
);
expect(close).toHaveBeenCalled();
expect(process.exitCode).toBeUndefined();
});
it("logs qmd index file path and size after index", async () => {
const close = vi.fn(async () => {});
const sync = vi.fn(async () => {});

View File

@@ -655,11 +655,10 @@ export abstract class MemoryManagerEmbeddingOps extends MemoryManagerSyncOps {
.run(chunk.text, id, entry.path, source, model, chunk.startLine, chunk.endLine);
}
}
// HELM-0251: warn if chunks were written but chunks_vec was not updated
if (this.vector.enabled && !vectorReady && chunks.length > 0) {
const errDetail = this.vector.loadError ? `: ${this.vector.loadError}` : "";
log.warn(
`HELM-0251: chunks written for ${entry.path} without vector embeddings — chunks_vec not updated (sqlite-vec unavailable${errDetail}). Vector recall degraded for this file.`,
`chunks written for ${entry.path} without vector embeddings — chunks_vec not updated (sqlite-vec unavailable${errDetail}). Vector recall degraded for this file.`,
);
}
this.upsertFileRecord(entry, source);