fix(agent): seeded startup learned memory snapshot

Use the pre-session memory prompt snapshot as the learned.md baseline when startup consolidation refreshes before a session-scoped cache exists. This keeps active-session learn writes out of the refreshed prompt while still surfacing the new consolidated summary.

Refs #3743
This commit is contained in:
roboomp
2026-06-28 18:35:01 +00:00
parent d883e50c06
commit 45f60657d5
2 changed files with 18 additions and 7 deletions
+10 -2
View File
@@ -166,6 +166,11 @@ const memoryToolDeveloperInstructionsBySession = new WeakMap<
MemoryInstructionSession,
CachedMemoryToolDeveloperInstructions
>();
const memoryToolDeveloperInstructionsByRoot = new Map<string, MemoryToolDeveloperInstructionsSnapshot | undefined>();
function getMemoryInstructionRoot(agentDir: string, settings: Settings): string {
return getMemoryRoot(agentDir, settings.getCwd());
}
function getMemoryInstructionSessionFile(session: MemoryInstructionSession): string | undefined {
return session.sessionManager.getSessionFile() ?? undefined;
@@ -177,7 +182,7 @@ async function readMemoryToolDeveloperInstructionsSnapshot(
): Promise<MemoryToolDeveloperInstructionsSnapshot | undefined> {
const cfg = loadMemoryConfig(settings);
if (!cfg.enabled) return undefined;
const memoryRoot = getMemoryRoot(agentDir, settings.getCwd());
const memoryRoot = getMemoryInstructionRoot(agentDir, settings);
let summary = "";
try {
@@ -255,8 +260,10 @@ export async function refreshMemoryToolDeveloperInstructionsCacheAfterStartup(
const sessionFile = getMemoryInstructionSessionFile(session);
const cached = memoryToolDeveloperInstructionsBySession.get(session);
const current = await readMemoryToolDeveloperInstructionsSnapshot(agentDir, settings);
const root = getMemoryInstructionRoot(agentDir, settings);
const baseline = memoryToolDeveloperInstructionsByRoot.get(root);
const cachedLearned = cached && cached.sessionFile === sessionFile ? cached.snapshot?.learned : undefined;
const learned = cachedLearned ?? current?.learned ?? "";
const learned = cachedLearned ?? baseline?.learned ?? "";
const snapshot = current ? { summary: current.summary, learned } : undefined;
cacheMemoryToolDeveloperInstructions(session, sessionFile, snapshot, settings);
}
@@ -271,6 +278,7 @@ export async function buildMemoryToolDeveloperInstructions(
): Promise<string | undefined> {
if (!session) {
const snapshot = await readMemoryToolDeveloperInstructionsSnapshot(agentDir, settings);
memoryToolDeveloperInstructionsByRoot.set(getMemoryInstructionRoot(agentDir, settings), snapshot);
return renderMemoryToolDeveloperInstructionsSnapshot(snapshot, settings);
}
@@ -198,13 +198,15 @@ describe("learned-lesson read-back", () => {
expect(await buildMemoryToolDeveloperInstructions(agentDir, settings, session)).toBeUndefined();
});
it("refreshes consolidated summaries without rereading learned lessons from the active session", async () => {
it("uses the pre-session learned snapshot when startup refresh has no session cache yet", async () => {
const settings = Settings.isolated({ "memory.backend": "local" });
const session = sessionWithFile("session-consolidate.jsonl");
// First build (before background consolidation finishes) snapshots an empty learned.md.
expect(await buildMemoryToolDeveloperInstructions(agentDir, settings, session)).toBeUndefined();
await saveLearnedLesson(agentDir, settings.getCwd(), { content: "Prior-session lesson" });
const initial = await buildMemoryToolDeveloperInstructions(agentDir, settings);
expect(initial).toContain("Prior-session lesson");
// The active session learns while the background pipeline is still running.
const session = sessionWithFile("session-consolidate.jsonl");
// The active session learns while the background pipeline is still running,
// before any session-scoped prompt rebuild has populated the WeakMap.
await saveLearnedLesson(agentDir, settings.getCwd(), { content: "Active-session lesson" });
// Background pipeline writes the consolidated summary later.
const root = getMemoryRoot(agentDir, settings.getCwd());
@@ -213,6 +215,7 @@ describe("learned-lesson read-back", () => {
await refreshMemoryToolDeveloperInstructionsCacheAfterStartup(session, agentDir, settings);
const out = await buildMemoryToolDeveloperInstructions(agentDir, settings, session);
expect(out).toContain("Consolidated guidance here.");
expect(out).toContain("Prior-session lesson");
expect(out).not.toContain("Active-session lesson");
const nextSession = sessionWithFile("session-after-consolidate.jsonl");