perf(memory): avoid eager provider init on empty search

This commit is contained in:
Vincent Koc
2026-03-24 13:01:04 -07:00
parent db0f957aba
commit 7330e2ce23
4 changed files with 68 additions and 20 deletions

View File

@@ -957,6 +957,7 @@ describe("memory index", () => {
const result = await getMemorySearchManager({ cfg, agentId: "main" });
const manager = requireManager(result);
await manager.probeEmbeddingAvailability();
expect(
providerCalls.some(

View File

@@ -93,17 +93,15 @@ describe("memory manager cache hydration", () => {
expect(managers).toHaveLength(12);
expect(new Set(managers).size).toBe(1);
expect(hoisted.providerCreateCalls).toBe(1);
expect(hoisted.providerCreateCalls).toBe(0);
await managers[0].close();
});
it("drains in-flight manager creation during global teardown", async () => {
it("evicts cached managers during global teardown", async () => {
const indexPath = path.join(workspaceDir, "index.sqlite");
const cfg = createMemoryConcurrencyConfig(indexPath);
hoisted.providerDelayMs = 100;
const pendingResult = RawMemoryIndexManager.get({ cfg, agentId: "main" });
await closeAllMemoryIndexManagers();
const firstManager = await pendingResult;
@@ -113,7 +111,7 @@ describe("memory manager cache hydration", () => {
expect(firstManager).toBeTruthy();
expect(secondManager).toBeTruthy();
expect(Object.is(secondManager, firstManager)).toBe(false);
expect(hoisted.providerCreateCalls).toBe(2);
expect(hoisted.providerCreateCalls).toBe(0);
await secondManager?.close?.();
});

View File

@@ -193,11 +193,6 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
return pending;
}
const createPromise = (async () => {
const providerResult = await MemoryIndexManager.loadProviderResult({
cfg,
agentId,
settings,
});
const refreshed = INDEX_CACHE.get(key);
if (refreshed) {
return refreshed;
@@ -208,7 +203,6 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
agentId,
workspaceDir,
settings,
providerResult,
purpose: params.purpose,
});
INDEX_CACHE.set(key, manager);
@@ -334,17 +328,21 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
sessionKey?: string;
},
): Promise<MemorySearchResult[]> {
await this.ensureProviderInitialized();
const cleaned = query.trim();
if (!cleaned) {
return [];
}
void this.warmSession(opts?.sessionKey);
if (this.settings.sync.onSearch && (this.dirty || this.sessionsDirty)) {
void this.sync({ reason: "search" }).catch((err) => {
log.warn(`memory sync failed (search): ${String(err)}`);
});
}
const cleaned = query.trim();
if (!cleaned) {
const hasIndexedContent = this.hasIndexedContent();
if (!hasIndexedContent) {
return [];
}
await this.ensureProviderInitialized();
const minScore = opts?.minScore ?? this.settings.query.minScore;
const maxResults = opts?.maxResults ?? this.settings.query.maxResults;
const hybrid = this.settings.query.hybrid;
@@ -437,6 +435,32 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
.slice(0, maxResults);
}
private hasIndexedContent(): boolean {
const chunks =
(
this.db.prepare(`SELECT COUNT(*) as c FROM chunks`).get() as
| {
c: number;
}
| undefined
)?.c ?? 0;
if (chunks > 0) {
return true;
}
if (!this.fts.enabled || !this.fts.available) {
return false;
}
const ftsRows =
(
this.db.prepare(`SELECT COUNT(*) as c FROM ${FTS_TABLE}`).get() as
| {
c: number;
}
| undefined
)?.c ?? 0;
return ftsRows > 0;
}
private async searchVector(
queryVec: number[],
limit: number,