feat(compaction): add dynamic context pruning as recovery stage

Implements DCP-style pruning strategies inspired by opencode-dynamic-context-pruning plugin:

- Deduplication: removes duplicate tool calls (same tool + args)
- Supersede writes: prunes write inputs when file subsequently read
- Purge errors: removes old error tool inputs after N turns

Integration:
- Added as Stage 2.5 in compaction pipeline (after truncation, before summarize)
- Configurable via experimental.dynamic_context_pruning
- Opt-in by default (experimental feature)
- Protected tools list prevents pruning critical tools

Configuration:
- Turn protection (default: 3 turns)
- Per-strategy enable/disable
- Aggressive/conservative modes for supersede writes
- Configurable error purge threshold (default: 5 turns)
- Toast notifications (off/minimal/detailed)

Testing:
- Added unit tests for deduplication signature creation
- Type check passes
- Schema regenerated

Closes #271
This commit is contained in:
sisyphus-dev-ai
2025-12-27 09:20:42 +00:00
parent 3ba7e6d46b
commit 1fc7fe7122
11 changed files with 1042 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ import type {
} from "./types";
import type { ExperimentalConfig } from "../../config";
import { FALLBACK_CONFIG, RETRY_CONFIG, TRUNCATE_CONFIG } from "./types";
import { executeDynamicContextPruning } from "./pruning-executor";
import {
findLargestToolResult,
truncateToolResult,
@@ -383,6 +384,40 @@ export async function executeCompact(
}
}
if (experimental?.dynamic_context_pruning?.enabled) {
log("[auto-compact] attempting DCP before truncation", { sessionID });
try {
const pruningResult = await executeDynamicContextPruning(
sessionID,
experimental.dynamic_context_pruning,
client
);
if (pruningResult.itemsPruned > 0) {
log("[auto-compact] DCP successful, resuming", {
itemsPruned: pruningResult.itemsPruned,
tokensSaved: pruningResult.totalTokensSaved,
});
setTimeout(async () => {
try {
await (client as Client).session.prompt_async({
path: { sessionID },
body: { parts: [{ type: "text", text: "Continue" }] },
query: { directory },
});
} catch {}
}, 500);
return;
}
} catch (error) {
log("[auto-compact] DCP failed, continuing to truncation", {
error: String(error),
});
}
}
let skipSummarize = false;
if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {