Skip to content

Commit e4f9e93

Browse files
committed
fix(memory): close stale scoped managers
1 parent 480f6c4 commit e4f9e93

2 files changed

Lines changed: 67 additions & 26 deletions

File tree

extensions/memory-core/src/memory/index.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,25 @@ describe("memory index", () => {
976976
expect(third).toBe(second);
977977
});
978978

979+
it("closes stale default managers when provider requirement changes", async () => {
980+
const storePath = path.join(workspaceDir, "index-provider-requirement-cache.sqlite");
981+
const implicitCfg = createCfg({ storePath });
982+
const implicit = requireManager(
983+
await getMemorySearchManager({ cfg: implicitCfg, agentId: "main" }),
984+
);
985+
managersForCleanup.add(implicit);
986+
await implicit.probeEmbeddingAvailability();
987+
988+
const explicitCfg = createCfg({ storePath, provider: "openai" });
989+
const explicit = requireManager(
990+
await getMemorySearchManager({ cfg: explicitCfg, agentId: "main" }),
991+
);
992+
managersForCleanup.add(explicit);
993+
994+
expect(explicit === implicit).toBe(false);
995+
expect(providerCloseCalls).toBe(1);
996+
});
997+
979998
it("retries embedding provider close before releasing the manager", async () => {
980999
providerCloseFailuresRemaining = 1;
9811000
const cfg = createCfg({

extensions/memory-core/src/memory/manager.ts

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -105,37 +105,12 @@ export async function closeMemoryIndexManagersForAgent(params: {
105105
cfg: OpenClawConfig;
106106
agentId: string;
107107
}): Promise<void> {
108-
const settings = resolveMemorySearchConfig(params.cfg, params.agentId);
109-
if (!settings) {
110-
return;
111-
}
112108
const workspaceDir = resolveAgentWorkspaceDir(params.cfg, params.agentId);
113-
const providerRequirement = resolveMemoryEmbeddingProviderRequirement({
114-
cfg: params.cfg,
115-
agentId: params.agentId,
116-
settings,
117-
});
118-
const key = resolveMemoryIndexManagerCacheKey({
109+
await closeMemoryIndexManagersForScope({
119110
agentId: params.agentId,
120111
workspaceDir,
121-
settings,
122-
providerRequirement,
123112
purpose: "default",
124113
});
125-
const pending = INDEX_CACHE_PENDING.get(key);
126-
if (pending) {
127-
await Promise.allSettled([pending]);
128-
}
129-
const manager = INDEX_CACHE.get(key);
130-
if (!manager) {
131-
return;
132-
}
133-
INDEX_CACHE.delete(key);
134-
try {
135-
await manager.close();
136-
} catch (err) {
137-
log.warn(`failed to close memory index manager for agent ${params.agentId}: ${String(err)}`);
138-
}
139114
}
140115

141116
function resolveEffectiveMemorySearchSettings(
@@ -206,6 +181,45 @@ function resolveMemoryIndexManagerCacheKey(params: {
206181
].join(":");
207182
}
208183

184+
function isMemoryIndexManagerCacheKeyInScope(
185+
key: string,
186+
params: {
187+
agentId: string;
188+
workspaceDir: string;
189+
purpose: MemoryIndexManagerPurpose;
190+
},
191+
): boolean {
192+
return (
193+
key.startsWith(`${params.agentId}:${params.workspaceDir}:`) &&
194+
key.endsWith(`:${params.purpose}`)
195+
);
196+
}
197+
198+
async function closeMemoryIndexManagersForScope(params: {
199+
agentId: string;
200+
workspaceDir: string;
201+
purpose: MemoryIndexManagerPurpose;
202+
exceptKey?: string;
203+
}): Promise<void> {
204+
const isScopedKey = (key: string) =>
205+
key !== params.exceptKey && isMemoryIndexManagerCacheKeyInScope(key, params);
206+
const pending = Array.from(INDEX_CACHE_PENDING.entries())
207+
.filter(([key]) => isScopedKey(key))
208+
.map(([, value]) => value);
209+
if (pending.length > 0) {
210+
await Promise.allSettled(pending);
211+
}
212+
const entries = Array.from(INDEX_CACHE.entries()).filter(([key]) => isScopedKey(key));
213+
for (const [key, manager] of entries) {
214+
INDEX_CACHE.delete(key);
215+
try {
216+
await manager.close();
217+
} catch (err) {
218+
log.warn(`failed to close memory index manager for agent ${params.agentId}: ${String(err)}`);
219+
}
220+
}
221+
}
222+
209223
export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements MemorySearchManager {
210224
private readonly cacheKey: string;
211225
private readonly purpose: MemoryIndexManagerPurpose;
@@ -319,6 +333,14 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
319333
purpose,
320334
});
321335
const transient = purpose === "status" || purpose === "cli";
336+
if (!transient) {
337+
await closeMemoryIndexManagersForScope({
338+
agentId,
339+
workspaceDir,
340+
purpose,
341+
exceptKey: key,
342+
});
343+
}
322344
return await getOrCreateManagedCacheEntry({
323345
cache: INDEX_CACHE,
324346
pending: INDEX_CACHE_PENDING,

0 commit comments

Comments
 (0)