fix(memory): avoid caching qmd status managers

This commit is contained in:
Vincent Koc 2026-03-24 10:24:33 -07:00
parent a37ed72829
commit 435e2c5967
2 changed files with 9 additions and 63 deletions

View File

@ -195,7 +195,7 @@ describe("getMemorySearchManager caching", () => {
expect(createQmdManagerMock).toHaveBeenCalledTimes(2);
});
it("reuses cached qmd managers for status-only requests", async () => {
it("does not cache qmd managers for status-only requests", async () => {
const agentId = "status-agent";
const cfg = createQmdCfg(agentId);
@ -211,9 +211,12 @@ describe("getMemorySearchManager caching", () => {
requestedProvider: "qmd",
});
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(1);
expect(createQmdManagerMock).toHaveBeenCalledTimes(2);
expect(mockMemoryIndexGet).not.toHaveBeenCalled();
expect(second.manager).toBe(first.manager);
await first.manager?.close?.();
await second.manager?.close?.();
expect(mockPrimary.close).toHaveBeenCalledTimes(2);
});
it("reuses cached full qmd manager for status-only requests", async () => {
@ -235,7 +238,7 @@ describe("getMemorySearchManager caching", () => {
expect(fullAgain.manager).toBe(full.manager);
});
it("evicts closed cached status managers so later status requests get a fresh manager", async () => {
it("gets a fresh qmd manager for later status requests after close", async () => {
const agentId = "status-eviction-agent";
const cfg = createQmdCfg(agentId);
@ -246,9 +249,9 @@ describe("getMemorySearchManager caching", () => {
const second = await getMemorySearchManager({ cfg, agentId, purpose: "status" });
requireManager(second);
expect(second.manager).not.toBe(firstManager);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(createQmdManagerMock).toHaveBeenCalledTimes(2);
expect(mockPrimary.close).toHaveBeenCalledTimes(1);
});
it("does not evict a newer cached wrapper when closing an older failed wrapper", async () => {

View File

@ -71,11 +71,7 @@ export async function getMemorySearchManager(params: {
});
if (primary) {
if (statusOnly) {
const wrapper = new CachedStatusMemoryManager(primary, () => {
QMD_MANAGER_CACHE.delete(cacheKey);
});
QMD_MANAGER_CACHE.set(cacheKey, wrapper);
return { manager: wrapper };
return { manager: primary };
}
const wrapper = new FallbackMemoryManager(
{
@ -146,59 +142,6 @@ class BorrowedMemoryManager implements MemorySearchManager {
async close() {}
}
class CachedStatusMemoryManager implements MemorySearchManager {
private closed = false;
constructor(
private readonly inner: MemorySearchManager,
private readonly onClose: () => void,
) {}
async search(
query: string,
opts?: { maxResults?: number; minScore?: number; sessionKey?: string },
) {
return await this.inner.search(query, opts);
}
async readFile(params: { relPath: string; from?: number; lines?: number }) {
return await this.inner.readFile(params);
}
status() {
return this.inner.status();
}
async sync(params?: {
reason?: string;
force?: boolean;
sessionFiles?: string[];
progress?: (update: MemorySyncProgressUpdate) => void;
}) {
await this.inner.sync?.(params);
}
async probeEmbeddingAvailability(): Promise<MemoryEmbeddingProbeResult> {
return await this.inner.probeEmbeddingAvailability();
}
async probeVectorAvailability() {
return await this.inner.probeVectorAvailability();
}
async close() {
if (this.closed) {
return;
}
this.closed = true;
try {
await this.inner.close?.();
} finally {
this.onClose();
}
}
}
export async function closeAllMemorySearchManagers(): Promise<void> {
const managers = Array.from(QMD_MANAGER_CACHE.values());
QMD_MANAGER_CACHE.clear();