Skip to content

Commit ceebaf4

Browse files
committed
Treat already-compacted CLI compaction as no-op
1 parent e7e98f6 commit ceebaf4

2 files changed

Lines changed: 82 additions & 8 deletions

File tree

src/agents/command/cli-compaction.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,74 @@ describe("runCliTurnCompactionLifecycle", () => {
273273
);
274274
});
275275

276+
it("treats already-compacted CLI transcript compaction as a no-op", async () => {
277+
const sessionKey = "agent:main:qwen-already-compacted";
278+
const sessionId = "session-qwen-already-compacted";
279+
const sessionFile = path.join(tmpDir, "session-qwen-already-compacted.jsonl");
280+
const storePath = path.join(tmpDir, "sessions-qwen-already-compacted.json");
281+
await writeSessionFile({ sessionFile, sessionId });
282+
283+
const sessionEntry: SessionEntry = {
284+
sessionId,
285+
updatedAt: Date.now(),
286+
sessionFile,
287+
contextTokens: 1_000,
288+
totalTokens: 950,
289+
totalTokensFresh: true,
290+
};
291+
const sessionStore: Record<string, SessionEntry> = { [sessionKey]: sessionEntry };
292+
await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2), "utf-8");
293+
294+
const compactCalls: Array<Parameters<ContextEngine["compact"]>[0]> = [];
295+
const maintenance = vi.fn(async () => ({ changed: false, bytesFreed: 0, rewrittenEntries: 0 }));
296+
const recordCliCompactionInStore = vi.fn();
297+
setCliCompactionTestDeps({
298+
resolveContextEngine: async () => ({
299+
...buildContextEngine({ compactCalls }),
300+
async compact(compactParams) {
301+
compactCalls.push(compactParams);
302+
throw new Error("Already compacted");
303+
},
304+
}),
305+
createPreparedEmbeddedAgentSettingsManager: async () => ({
306+
getCompactionReserveTokens: () => 200,
307+
getCompactionKeepRecentTokens: () => 0,
308+
applyOverrides: () => {},
309+
}),
310+
shouldPreemptivelyCompactBeforePrompt: () => ({
311+
route: "fits",
312+
shouldCompact: false,
313+
estimatedPromptTokens: 600,
314+
promptBudgetBeforeReserve: 800,
315+
overflowTokens: 0,
316+
toolResultReducibleChars: 0,
317+
effectiveReserveTokens: 200,
318+
}),
319+
resolveLiveToolResultMaxChars: () => 20_000,
320+
runContextEngineMaintenance: maintenance,
321+
recordCliCompactionInStore,
322+
});
323+
324+
const updatedEntry = await runCliTurnCompactionLifecycle({
325+
cfg: {} as OpenClawConfig,
326+
sessionId,
327+
sessionKey,
328+
sessionEntry,
329+
sessionStore,
330+
storePath,
331+
sessionAgentId: "main",
332+
workspaceDir: tmpDir,
333+
agentDir: tmpDir,
334+
provider: "ollama",
335+
model: "qwen3:14b",
336+
});
337+
338+
expect(compactCalls).toHaveLength(1);
339+
expect(maintenance).not.toHaveBeenCalled();
340+
expect(recordCliCompactionInStore).not.toHaveBeenCalled();
341+
expect(updatedEntry).toBe(sessionEntry);
342+
});
343+
276344
it("routes OpenAI Codex harness CLI compaction through native harness compaction", async () => {
277345
const sessionKey = "agent:main:codex";
278346
const sessionId = "session-codex";

src/agents/command/cli-compaction.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,9 @@ function isUnsupportedNativeHarnessCompaction(
200200
return result?.ok === false && result.failure?.reason === "unsupported_harness_compaction";
201201
}
202202

203-
function isBelowCompactionTargetReason(reason: string | undefined): boolean {
204-
return classifyCompactionReason(reason) === "below_threshold";
203+
function isBenignCliCompactionNoopReason(reason: string | undefined): boolean {
204+
const classification = classifyCompactionReason(reason);
205+
return classification === "below_threshold" || classification === "already_compacted_recently";
205206
}
206207

207208
function isIntentionalNativeAutoCompactionSkip(
@@ -317,18 +318,23 @@ async function compactCliTranscript(params: {
317318
resolveCompactionTimeoutMs(params.cfg),
318319
);
319320
} catch (error) {
320-
log.warn(
321-
`CLI transcript compaction failed for ${params.provider}/${params.model}: ${error instanceof Error ? error.message : String(error)}`,
322-
);
321+
const reason = error instanceof Error ? error.message : String(error);
322+
if (isBenignCliCompactionNoopReason(reason)) {
323+
log.info(
324+
`CLI transcript compaction skipped for ${params.provider}/${params.model}: ${reason}`,
325+
);
326+
return { compacted: false };
327+
}
328+
log.warn(`CLI transcript compaction failed for ${params.provider}/${params.model}: ${reason}`);
323329
return {
324330
compacted: false,
325-
failureReason: error instanceof Error ? error.message : String(error),
331+
failureReason: reason,
326332
};
327333
}
328334

329335
if (!compactResult.compacted) {
330336
const reason = compactResult.reason ?? "nothing to compact";
331-
if (isBelowCompactionTargetReason(reason)) {
337+
if (isBenignCliCompactionNoopReason(reason)) {
332338
log.info(
333339
`CLI transcript compaction skipped for ${params.provider}/${params.model}: ${reason}`,
334340
);
@@ -466,7 +472,7 @@ async function compactNativeHarnessCliTranscript(params: {
466472

467473
if (!result?.compacted) {
468474
const reason = result?.reason ?? "nothing to compact";
469-
if (isBelowCompactionTargetReason(reason)) {
475+
if (isBenignCliCompactionNoopReason(reason)) {
470476
log.info(
471477
`CLI native harness compaction skipped for ${params.provider}/${params.model}: ${reason}`,
472478
);

0 commit comments

Comments
 (0)