Skip to content

Commit 062ca1a

Browse files
committed
fix(compaction): do not inherit extras after a failed /clear
Stamp last workdir and session id only after a successful rebuild so a failed factory cannot make the retry look like the same session.
1 parent 8c109d2 commit 062ca1a

2 files changed

Lines changed: 68 additions & 9 deletions

File tree

‎src/session/assemble-runtime.test.ts‎

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -292,24 +292,31 @@ async function withAssembledDirector(
292292
getRecords: () => readonly ManifestRecord[];
293293
directorHolder: { instance?: ChatDirector };
294294
wiring?: Partial<ChatAgentWiring>;
295+
beforeCreateSessionStores?: () => void;
296+
onReadManifestHistory?: (limit: number) => void;
295297
},
296298
run: (buildAgent: () => Promise<unknown>) => Promise<void>,
297299
): Promise<void> {
298300
const fakeAgent = { close: async () => undefined } as unknown as Agent;
299301
const fakeStorage = {
300302
readBlob: async () => new Uint8Array(),
301-
readManifestHistory: async (limit: number) =>
302-
input.getRecords().slice(0, limit),
303+
readManifestHistory: async (limit: number) => {
304+
input.onReadManifestHistory?.(limit);
305+
return input.getRecords().slice(0, limit);
306+
},
303307
} as unknown as ContextStore;
304308

305309
await withMockedModuleDuring(
306310
import.meta.resolve("./optimized-context-store.js"),
307311
(real: typeof import("./optimized-context-store.js")) => ({
308312
...real,
309-
createSessionStores: async () => ({
310-
storage: fakeStorage,
311-
audit: stubAuditStore(),
312-
}),
313+
createSessionStores: async () => {
314+
input.beforeCreateSessionStores?.();
315+
return {
316+
storage: fakeStorage,
317+
audit: stubAuditStore(),
318+
};
319+
},
313320
}),
314321
async () => {
315322
await withMockedModuleDuring(
@@ -534,20 +541,68 @@ describe("assembleChatAgent", () => {
534541
);
535542
});
536543

544+
test("failed /clear rebuild then retry on a new identity does not inherit extraInstructions", async () => {
545+
let records: ManifestRecord[] = [
546+
pruningExtrasRecord("keep the auth discussion"),
547+
];
548+
let workdir = "/session-a";
549+
let sessionId = "session-a";
550+
let failStores = false;
551+
const directorHolder: { instance?: ChatDirector } = {};
552+
553+
await withAssembledDirector(
554+
{
555+
getRecords: () => records,
556+
directorHolder,
557+
wiring: {
558+
getWorkdir: () => workdir,
559+
getSessionId: () => sessionId,
560+
},
561+
beforeCreateSessionStores: () => {
562+
if (failStores) throw new Error("store rebuild failed");
563+
},
564+
},
565+
async (buildAgent) => {
566+
await buildAgent();
567+
expect(directorHolder.instance?.getCompactInstructions()).toBe(
568+
"keep the auth discussion",
569+
);
570+
records = [];
571+
workdir = "/session-b";
572+
sessionId = "session-b";
573+
failStores = true;
574+
await expect(buildAgent()).rejects.toThrow("store rebuild failed");
575+
failStores = false;
576+
await buildAgent();
577+
expect(
578+
directorHolder.instance?.getCompactInstructions(),
579+
).toBeUndefined();
580+
},
581+
);
582+
});
583+
537584
test("cold resume restores extraInstructions beyond 32 later cycles", async () => {
538585
const records: ManifestRecord[] = [
539586
...Array.from({ length: 32 }, () => laterManifestRecord()),
540587
pruningExtrasRecord("keep the auth discussion"),
541588
];
542589
const directorHolder: { instance?: ChatDirector } = {};
590+
const limits: number[] = [];
543591

544592
await withAssembledDirector(
545-
{ getRecords: () => records, directorHolder },
593+
{
594+
getRecords: () => records,
595+
directorHolder,
596+
onReadManifestHistory: (limit) => {
597+
limits.push(limit);
598+
},
599+
},
546600
async (buildAgent) => {
547601
await buildAgent();
548602
expect(directorHolder.instance?.getCompactInstructions()).toBe(
549603
"keep the auth discussion",
550604
);
605+
expect(limits).toEqual([32, 64]);
551606
},
552607
);
553608
});

‎src/session/assemble-runtime.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,8 +610,10 @@ export function assembleChatAgent(wiring: ChatAgentWiring): AssembledChatAgent {
610610
inheritFromPrev =
611611
lastWorkdir === undefined ||
612612
(lastWorkdir === workdir && lastSessionId === sessionId);
613-
lastWorkdir = workdir;
614-
lastSessionId = sessionId;
613+
// Do not stamp last* until this build succeeds. A failed /clear factory
614+
// would otherwise make the retry look like the same session and inherit
615+
// extras from the previous director / sticky store snapshot.
616+
if (!inheritFromPrev) stickyFromStore = undefined;
615617
const { storage, audit } = await createSessionStores(workdir);
616618
// Primary-only evidence archive. Workers never pass evidenceArchiveHolder, so
617619
// they keep plain storage and omit admission / authorize recording wraps.
@@ -721,6 +723,8 @@ export function assembleChatAgent(wiring: ChatAgentWiring): AssembledChatAgent {
721723
? agent
722724
: createPrimaryDeliveryAdmission(agent, primaryArchive);
723725
wiring.onBuilt(admittedAgent, storageForAgent);
726+
lastWorkdir = workdir;
727+
lastSessionId = sessionId;
724728
return admittedAgent;
725729
};
726730

0 commit comments

Comments
 (0)