feat: freeze rendered workspace memory snapshot

This commit is contained in:
Ralph Chang
2026-04-27 01:57:41 +08:00
parent eb74a9f03e
commit 026c75a5e4
2 changed files with 55 additions and 19 deletions
+21 -18
View File
@@ -162,6 +162,7 @@ export const MemoryV2Plugin: Plugin = async (input) => {
string,
{
store: Awaited<ReturnType<typeof loadWorkspaceMemory>>;
renderedPrompt: string;
loadedAt: number;
}
>();
@@ -232,24 +233,28 @@ export const MemoryV2Plugin: Plugin = async (input) => {
}
/**
* Get frozen workspace memory for a session.
* Loads from disk once per session, then caches in memory.
* Get frozen workspace memory snapshot for a session.
* Loads and renders from disk once per session, then reuses the exact rendered string.
*/
async function getFrozenWorkspaceMemory(
async function getFrozenWorkspaceMemorySnapshot(
root: string,
sessionID: string
): Promise<Awaited<ReturnType<typeof loadWorkspaceMemory>>> {
): Promise<{
store: Awaited<ReturnType<typeof loadWorkspaceMemory>>;
renderedPrompt: string;
}> {
const now = Date.now();
const cached = frozenWorkspaceMemoryCache.get(sessionID);
// Cache is valid for the session lifetime
if (cached) {
return cached.store;
return { store: cached.store, renderedPrompt: cached.renderedPrompt };
}
const store = await loadWorkspaceMemory(root);
frozenWorkspaceMemoryCache.set(sessionID, { store, loadedAt: now });
return store;
const renderedPrompt = renderWorkspaceMemory(store);
frozenWorkspaceMemoryCache.set(sessionID, { store, renderedPrompt, loadedAt: now });
return { store, renderedPrompt };
}
/**
@@ -271,16 +276,15 @@ export const MemoryV2Plugin: Plugin = async (input) => {
// Process explicit user memory even on no-tool turns.
await processLatestUserMessage(sessionID);
// Get frozen workspace memory (loaded once per session)
const workspaceMemory = await getFrozenWorkspaceMemory(directory, sessionID);
// Get frozen workspace memory snapshot (loaded and rendered once per session)
const workspaceSnapshot = await getFrozenWorkspaceMemorySnapshot(directory, sessionID);
// Get current hot session state
const sessionState = await loadSessionState(directory, sessionID);
// Render and inject workspace memory
const workspacePrompt = renderWorkspaceMemory(workspaceMemory);
if (workspacePrompt) {
output.system.push(workspacePrompt);
// Inject frozen workspace memory snapshot
if (workspaceSnapshot.renderedPrompt) {
output.system.push(workspaceSnapshot.renderedPrompt);
}
// Render and inject hot session state
@@ -371,11 +375,10 @@ export const MemoryV2Plugin: Plugin = async (input) => {
// Build our private context (workspace memory, hot state, todos)
const contextParts: string[] = [];
// 1. Frozen workspace memory
const workspaceMemory = await getFrozenWorkspaceMemory(directory, sessionID);
const workspacePrompt = renderWorkspaceMemory(workspaceMemory);
if (workspacePrompt) {
contextParts.push(workspacePrompt);
// 1. Frozen workspace memory snapshot
const workspaceSnapshot = await getFrozenWorkspaceMemorySnapshot(directory, sessionID);
if (workspaceSnapshot.renderedPrompt) {
contextParts.push(workspaceSnapshot.renderedPrompt);
}
// 2. Hot session state
+34 -1
View File
@@ -345,4 +345,37 @@ Next steps: continue development.
const candidates = parseWorkspaceMemoryCandidates(summary);
assert.equal(candidates.length, 1, "Should parse legacy XML format");
assert.equal(candidates[0].type, "feedback");
});
});
test("chat system transform reuses frozen rendered workspace snapshot", async () => {
const tmpDir = await mkdtemp(join(tmpdir(), "memory-plugin-test-"));
try {
const client = mockRootClient();
const plugin = await MemoryV2Plugin({ directory: tmpDir, client });
const output1 = { system: ["base header"] };
await (plugin as Record<string, Function>)["experimental.chat.system.transform"](
{ sessionID: "snapshot-session", model: {} },
output1,
);
const firstWorkspacePrompt = output1.system.find((part: string) =>
part.startsWith("Workspace memory")
);
assert.equal(firstWorkspacePrompt, undefined,
"empty workspace memory should not render a prompt before any memories exist");
const output2 = { system: ["base header"] };
await (plugin as Record<string, Function>)["experimental.chat.system.transform"](
{ sessionID: "snapshot-session", model: {} },
output2,
);
assert.deepEqual(output2.system, ["base header"],
"no compaction summary means no workspace memory prompt is added");
} finally {
await rm(tmpDir, { recursive: true, force: true });
}
});