Skip to main content

harness_core/
compactor.rs

1use crate::{Context, error::CompactError};
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4
5/// The 5 progressive compaction stages (DESIGN.md §9 — borrowed from Claude Code).
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "kebab-case")]
8#[non_exhaustive]
9pub enum CompactionStage {
10    /// > 60% — trim redundant tool results, keep recent full turns.
11    BudgetReduce,
12    /// > 70% — drop stale file reads, keep path + hash.
13    Snip,
14    /// > 80% — summarise older conversation segments with a cheaper model.
15    Microcompact,
16    /// > 90% — collapse all file reads into a single inventory + key excerpts.
17    ContextCollapse,
18    /// > 95% — rewrite the entire conversation in compressed form with the main model.
19    AutoCompact,
20}
21
22impl CompactionStage {
23    /// Stages, in order. Higher stages imply running all lower stages first.
24    pub const ALL: [CompactionStage; 5] = [
25        CompactionStage::BudgetReduce,
26        CompactionStage::Snip,
27        CompactionStage::Microcompact,
28        CompactionStage::ContextCollapse,
29        CompactionStage::AutoCompact,
30    ];
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct Budget {
35    pub used: u32,
36    pub window: u32,
37}
38
39impl Budget {
40    pub fn ratio(&self) -> f32 {
41        if self.window == 0 {
42            0.0
43        } else {
44            self.used as f32 / self.window as f32
45        }
46    }
47
48    /// Which stages must run, given current usage. Returns lowest → highest.
49    pub fn required_stages(&self) -> Vec<CompactionStage> {
50        let r = self.ratio();
51        let mut out = Vec::new();
52        if r > 0.60 {
53            out.push(CompactionStage::BudgetReduce);
54        }
55        if r > 0.70 {
56            out.push(CompactionStage::Snip);
57        }
58        if r > 0.80 {
59            out.push(CompactionStage::Microcompact);
60        }
61        if r > 0.90 {
62            out.push(CompactionStage::ContextCollapse);
63        }
64        if r > 0.95 {
65            out.push(CompactionStage::AutoCompact);
66        }
67        out
68    }
69}
70
71#[async_trait]
72pub trait Compactor: Send + Sync + 'static {
73    fn budget(&self, ctx: &Context) -> Budget;
74    async fn compact(&self, stage: CompactionStage, ctx: &mut Context) -> Result<(), CompactError>;
75}