Skip to content

Commit 5ccec45

Browse files
committed
fix(compaction): cancel failed handoff without biasing later folds
1 parent d7d6f52 commit 5ccec45

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

‎src/agent/compaction.test.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,4 +902,35 @@ describe("handoff arming (/handoff)", () => {
902902
),
903903
).toBeNull();
904904
});
905+
906+
test("cancelManual clears sticky extraInstructions from a failed pivot", () => {
907+
const governor = createCompactionGovernor(undefined);
908+
governor.syncFromTurns(tenTurns);
909+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
910+
governor.cancelManual();
911+
expect(governor.extraInstructions).toBeUndefined();
912+
});
913+
914+
test("threshold pending survives cancelManual so a tool pause still folds", () => {
915+
const governor = createCompactionGovernor(undefined);
916+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
917+
expect(
918+
governor.interceptActions(toolDone(), inferAction, capabilities),
919+
).not.toBeNull();
920+
921+
const cancelled = createCompactionGovernor(undefined);
922+
cancelled.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
923+
expect(cancelled.requestHandoff("now do the UI audit")).toBe("armed");
924+
cancelled.cancelManual();
925+
const actions = cancelled.interceptActions(
926+
toolDone(),
927+
inferAction,
928+
capabilities,
929+
);
930+
expect(actions).not.toBeNull();
931+
expect(actions?.some((a) => a.type === "compact")).toBe(true);
932+
expect(actions?.find((a) => a.type === "compact")).toMatchObject({
933+
reason: "context-threshold",
934+
});
935+
});
905936
});

‎src/agent/compaction.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,14 @@ export function createCompactionGovernor(
397397
}
398398

399399
// Disarm after a pivot send that never delivered: without this the next
400-
// operator message would fold unexpectedly.
400+
// operator message would fold unexpectedly. Threshold `pending` is independent
401+
// of the failed pivot and must still fire at the next tool pause. Sticky
402+
// extraInstructions belong to a successful fold, not a cancelled one.
401403
function cancelManual(): void {
404+
const thresholdPending = pending;
402405
clearManualArming();
406+
pending = thresholdPending;
407+
extraInstructions = undefined;
403408
}
404409

405410
return {

0 commit comments

Comments
 (0)