Skip to content

Commit ab31dae

Browse files
committed
test(compaction): red latch tests for post-compact above-threshold re-arm
1 parent 6d07b36 commit ab31dae

1 file changed

Lines changed: 160 additions & 0 deletions

File tree

‎src/agent/compaction.test.ts‎

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,166 @@ describe("compaction governor", () => {
936936
});
937937
});
938938

939+
describe("post-compact above-threshold latch (CL-9006)", () => {
940+
// Wide resume gap: twice the old resume delta — a full warning band of
941+
// growth past the post-compact measurement before a still-over session may
942+
// fold again (danger-anchored; see COMPACTION_WIDE_RESUME_FRACTION).
943+
const wideDelta = resumeDelta * 2;
944+
945+
test("resume-delta-scale growth while still over threshold does not re-arm", () => {
946+
const governor = createCompactionGovernor(() => undefined);
947+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
948+
expect(
949+
governor.interceptActions(toolDone(), inferAction, capabilities),
950+
).not.toBeNull();
951+
952+
// Post-compact measurement stays over the high watermark: the latch sets.
953+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
954+
// Growth by the old resume delta must NOT re-arm on its own.
955+
governor.noteInferenceDone(
956+
inferenceDone(overThreshold + resumeDelta),
957+
tenTurns,
958+
);
959+
expect(
960+
governor.interceptActions(toolDone(), inferAction, capabilities),
961+
).toBeNull();
962+
// The idle path shares the same latch.
963+
governor.noteIdleTurn(inferenceDone(overThreshold + resumeDelta), [
964+
{ type: "reply", content: "done" },
965+
]);
966+
expect(
967+
governor.interceptIdleContinuation(emptyMessage(), capabilities),
968+
).toBeNull();
969+
});
970+
971+
test("a wide resume gap while still over threshold re-arms", () => {
972+
const governor = createCompactionGovernor(() => undefined);
973+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
974+
expect(
975+
governor.interceptActions(toolDone(), inferAction, capabilities),
976+
).not.toBeNull();
977+
978+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
979+
governor.noteInferenceDone(
980+
inferenceDone(overThreshold + wideDelta),
981+
tenTurns,
982+
);
983+
const actions = governor.interceptActions(
984+
toolDone(),
985+
inferAction,
986+
capabilities,
987+
);
988+
expect(actions).not.toBeNull();
989+
expect(actions?.some((a) => a.type === "compact")).toBe(true);
990+
});
991+
992+
test("the consecutive-compact cap holds across tool-call occupancy", () => {
993+
const governor = createCompactionGovernor(() => undefined);
994+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
995+
expect(
996+
governor.interceptActions(toolDone(), inferAction, capabilities),
997+
).not.toBeNull();
998+
999+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1000+
governor.noteInferenceDone(
1001+
inferenceDone(overThreshold + wideDelta),
1002+
tenTurns,
1003+
);
1004+
expect(
1005+
governor.interceptActions(toolDone(), inferAction, capabilities),
1006+
).not.toBeNull();
1007+
1008+
// Post-compact measurement still over: tool-call occupancy must not reset
1009+
// the cap, even past a wide gap.
1010+
governor.noteInferenceDone(
1011+
inferenceDone(overThreshold + wideDelta),
1012+
tenTurns,
1013+
);
1014+
governor.noteInferenceDone(
1015+
inferenceDoneWithTools(overThreshold + 2 * wideDelta),
1016+
tenTurns,
1017+
);
1018+
expect(
1019+
governor.interceptActions(toolDone(), inferAction, capabilities),
1020+
).toBeNull();
1021+
// The idle path shares the same cap.
1022+
governor.noteIdleTurn(
1023+
inferenceDoneWithTools(overThreshold + 2 * wideDelta),
1024+
[{ type: "reply", content: "done" }],
1025+
);
1026+
expect(
1027+
governor.interceptIdleContinuation(emptyMessage(), capabilities),
1028+
).toBeNull();
1029+
});
1030+
1031+
test("overflow recoveries are bounded across still-over measurements", () => {
1032+
const governor = createCompactionGovernor(() => undefined);
1033+
expect(
1034+
governor.interceptOverflow(overflowError(), capabilities),
1035+
).not.toBeNull();
1036+
expect(governor.resumeAfterCompact(emptyMessage())).toBe("infer");
1037+
1038+
// Still-over post-compact measurement: no relief, budget stays spent.
1039+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1040+
expect(
1041+
governor.interceptOverflow(overflowError(), capabilities),
1042+
).not.toBeNull();
1043+
expect(governor.resumeAfterCompact(emptyMessage())).toBe("infer");
1044+
1045+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1046+
expect(governor.interceptOverflow(overflowError(), capabilities)).toBeNull();
1047+
});
1048+
1049+
test("an under-threshold fold restores the overflow budget", () => {
1050+
const governor = createCompactionGovernor(() => undefined);
1051+
expect(
1052+
governor.interceptOverflow(overflowError(), capabilities),
1053+
).not.toBeNull();
1054+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1055+
expect(
1056+
governor.interceptOverflow(overflowError(), capabilities),
1057+
).not.toBeNull();
1058+
// Fold evidence: usage back under the watermark restores the budget.
1059+
governor.noteInferenceDone(inferenceDone(1000), tenTurns);
1060+
expect(
1061+
governor.interceptOverflow(overflowError(), capabilities),
1062+
).not.toBeNull();
1063+
});
1064+
1065+
test("auto re-arm after an operator compact uses the identical latch", () => {
1066+
const governor = createCompactionGovernor(() => undefined);
1067+
governor.noteInferenceDone(inferenceDone(1000), tenTurns);
1068+
expect(governor.requestManual("", { inFlight: true })).toBe("armed");
1069+
expect(
1070+
governor.interceptActions(toolDone(), inferAction, capabilities),
1071+
).not.toBeNull();
1072+
1073+
// Post-operator-compact measurement stays over: small growth must not
1074+
// re-arm the automatic path.
1075+
governor.noteInferenceDone(inferenceDone(overThreshold), tenTurns);
1076+
governor.noteInferenceDone(
1077+
inferenceDone(overThreshold + resumeDelta),
1078+
tenTurns,
1079+
);
1080+
expect(
1081+
governor.interceptActions(toolDone(), inferAction, capabilities),
1082+
).toBeNull();
1083+
1084+
// Wide gap re-arms identically to the automatic path.
1085+
governor.noteInferenceDone(
1086+
inferenceDone(overThreshold + wideDelta),
1087+
tenTurns,
1088+
);
1089+
const actions = governor.interceptActions(
1090+
toolDone(),
1091+
inferAction,
1092+
capabilities,
1093+
);
1094+
expect(actions).not.toBeNull();
1095+
expect(actions?.some((a) => a.type === "compact")).toBe(true);
1096+
});
1097+
});
1098+
9391099
describe("cache expiry never folds (CL-8914)", () => {
9401100
const MINUTE_MS = 60_000;
9411101

0 commit comments

Comments
 (0)