Skip to content

Commit 3d130f4

Browse files
Merge pull request #1446 from corbitsdev/fix/compaction-failure-notifies
fix(compaction): notify and stop when compaction fails
2 parents 4dd0ee0 + 8c0f8d6 commit 3d130f4

9 files changed

Lines changed: 314 additions & 109 deletions

File tree

‎src/agent/compaction.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,21 @@ describe("handoff arming (/handoff)", () => {
12021202
).toBeNull();
12031203
});
12041204

1205+
test("a failed handoff compact does not schedule the post-compact infer", () => {
1206+
const governor = createCompactionGovernor(undefined);
1207+
governor.syncFromTurns(tenTurns);
1208+
expect(governor.requestHandoff("now do the UI audit")).toBe("armed");
1209+
const actions = governor.interceptIdleContinuation(
1210+
pivot("now do the UI audit"),
1211+
capabilities,
1212+
);
1213+
expect(actions?.some((a) => a.type === "compact")).toBe(true);
1214+
expect(governor.hasOutstandingContinuation()).toBe(true);
1215+
governor.noteKeptPrior();
1216+
expect(governor.hasOutstandingContinuation()).toBe(false);
1217+
expect(governor.resumeAfterCompact(emptyMessage())).toBeNull();
1218+
});
1219+
12051220
test("instructions stay sticky for the summary after the fold fires", () => {
12061221
const governor = createCompactionGovernor(undefined);
12071222
governor.syncFromTurns(tenTurns);

‎src/agent/compaction.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,14 @@ export function createCompactionGovernor(
414414
];
415415
}
416416

417+
// A keep-prior compact (verify abort, summarizer failure) must not resume
418+
// into inference: the continuation is already queued, and inferring on the
419+
// full transcript is what 401/429s the handoff.
420+
function noteKeptPrior(): void {
421+
postCompactInfer = false;
422+
postCompactMeter = false;
423+
}
424+
417425
// After compact, a content-less continuation re-enters decide. "infer"
418426
// resumes the interrupted loop; "meter" adopts the shrunk turns for the
419427
// Ctx display and stays idle (idle empty compact has nothing to answer).
@@ -559,6 +567,7 @@ export function createCompactionGovernor(
559567
interceptIdleContinuation,
560568
interceptOverflow,
561569
resumeAfterCompact,
570+
noteKeptPrior,
562571
hasOutstandingContinuation,
563572
};
564573
}

‎src/agent/director-compaction.test.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,36 @@ test("compaction is self-regulating: a cycle back under threshold does not re-co
211211
expect(hasContinuationEmit(arr)).toBe(false);
212212
});
213213

214+
test("a failed handoff compact does not schedule the post-compact infer", async () => {
215+
const director = makeChatDirector();
216+
director.restoreCacheWrite({
217+
at: 0,
218+
source,
219+
turns: manyTurnsState.turns,
220+
});
221+
expect(director.requestHandoff("now do the UI audit")).toBe("armed");
222+
const armed = await director.decide(
223+
{
224+
type: "message.received",
225+
message: { content: "now do the UI audit" },
226+
} as ReactorInboundEvent,
227+
manyTurnsState,
228+
makeCapabilities(),
229+
);
230+
const armedArr = Array.isArray(armed) ? armed : [armed];
231+
expect(armedArr.some((a) => a.type === "compact")).toBe(true);
232+
expect(hasContinuationEmit(armedArr)).toBe(true);
233+
director.noteCompactionKeptPrior();
234+
const resume = await director.decide(
235+
emptyMessageReceived(),
236+
manyTurnsState,
237+
makeCapabilities(),
238+
);
239+
const resumeArr = Array.isArray(resume) ? resume : [resume];
240+
expect(resumeArr.some((a) => a.type === "infer")).toBe(false);
241+
expect(resumeArr.some((a) => a.type === "compact")).toBe(false);
242+
});
243+
214244
test("a fresh director answers an unsolicited empty continuation without inferring", async () => {
215245
const director = makeChatDirector();
216246

‎src/agent/director.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,11 @@ class ChatDirectorImpl extends DefaultDirector {
762762
return this.compaction.requestHandoff(instructions);
763763
}
764764

765+
/** A compact that kept prior context: do not infer on the queued continuation. */
766+
noteCompactionKeptPrior(): void {
767+
this.compaction.noteKeptPrior();
768+
}
769+
765770
// Drop a handoff arming whose pivot never delivered, so the next operator
766771
// message does not fold unexpectedly.
767772
cancelManualCompact(): void {
@@ -888,6 +893,9 @@ class ChatDirectorImpl extends DefaultDirector {
888893
capabilities: ReactorCapabilities,
889894
): Promise<ReactorAction | ReactorAction[]> {
890895
const afterCompact = this.compaction.resumeAfterCompact(event);
896+
// noteKeptPrior clears the resume flags during apply, before this
897+
// continuation is decided. A keep-prior handoff must wait, not infer
898+
// on the unchanged transcript.
891899
if (afterCompact !== null) {
892900
// Compacted history is the live occupancy until the next
893901
// inference.done; paint from the estimate meanwhile.
@@ -1358,4 +1366,6 @@ export interface ChatDirector extends ReactorDirector {
13581366
requestHandoff(instructions: string): HandoffArming;
13591367
/** Drop a handoff arming whose pivot never delivered. */
13601368
cancelManualCompact(): void;
1369+
/** Compact kept the prior transcript; the queued continuation must not infer. */
1370+
noteCompactionKeptPrior(): void;
13611371
}

‎src/exec/runner.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,9 @@ export async function runExec(config: Config): Promise<ExecResult> {
944944
onFailure: (text) => {
945945
stderr.write(`${text}\n`);
946946
},
947+
onKeptPrior: () => {
948+
directorHolder.instance?.noteCompactionKeptPrior();
949+
},
947950
onFolded: () => {
948951
// Fold restarts the cached prefix: drop idle execute-promoted
949952
// schemas and persist, so a crash resume cannot re-flush them.

0 commit comments

Comments
 (0)