Skip to main content

agent_works/compression/
config.rs

1//! Compression configuration.
2
3use tracing;
4
5/// Tuning knobs for context compression.
6///
7/// Controls when and how the conversation history is compressed to fit within
8/// the model's context window. The compressor keeps the most recent N messages
9/// intact (including tool results) and summarises older history via LLM.
10///
11/// # Defaults
12///
13/// | Field | Default | Purpose |
14/// |-------|---------|---------|
15/// | `enabled` | `true` | Master switch |
16/// | `trigger_tokens` | 256 000 | Skip compression when estimated tokens are below this |
17/// | `keep_recent_messages` | 16 | Number of most-recent messages kept verbatim |
18/// | `max_summary_chars` | 10 240 (10 KB) | Hard cap on the generated summary length |
19/// | `max_transcript_chars` | 20 480 (20 KB) | Max chars of old messages sent to the summarizer |
20#[derive(Clone, Debug)]
21pub struct CompressionConfig {
22    /// Master switch. When `false`, compression is a no-op.
23    pub enabled: bool,
24    /// Compress when the estimated token count of the message list exceeds this.
25    /// Set lower than `context_window` so compression fires before the window
26    /// manager's blunt trim kicks in.
27    pub trigger_tokens: usize,
28    /// Always keep the most recent N messages intact (the agent's working
29    /// context, including tool results). Older messages are summarised.
30    pub keep_recent_messages: usize,
31    /// Hard cap (in chars) on the summary the summarizer LLM may produce.
32    /// Output exceeding this is truncated (front 80 % + rear 20 %).
33    pub max_summary_chars: usize,
34    /// Max chars of the old-message transcript handed to the summarizer.
35    pub max_transcript_chars: usize,
36}
37
38impl Default for CompressionConfig {
39    fn default() -> Self {
40        Self {
41            enabled: true,
42            trigger_tokens: 256_000,
43            keep_recent_messages: 16,
44            max_summary_chars: 10 * 1024,    // 10 KB
45            max_transcript_chars: 20 * 1024, // 20 KB
46        }
47    }
48}
49
50// ── Builder methods ──────────────────────────────────────────────────────────
51
52impl CompressionConfig {
53    /// Create a config with everything at default, then customise via builder.
54    pub fn builder() -> Self {
55        Self::default()
56    }
57
58    pub fn with_enabled(mut self, enabled: bool) -> Self {
59        self.enabled = enabled;
60        self
61    }
62
63    pub fn with_trigger_tokens(mut self, tokens: usize) -> Self {
64        self.trigger_tokens = tokens;
65        self
66    }
67
68    pub fn with_keep_recent_messages(mut self, n: usize) -> Self {
69        self.keep_recent_messages = n;
70        self
71    }
72
73    pub fn with_max_summary_chars(mut self, chars: usize) -> Self {
74        self.max_summary_chars = chars;
75        self
76    }
77
78    pub fn with_max_transcript_chars(mut self, chars: usize) -> Self {
79        self.max_transcript_chars = chars;
80        self
81    }
82
83    /// Validate that `trigger_tokens` is less than the model's context window.
84    /// Logs a warning and clamps if violated. Returns the (possibly clamped) config.
85    pub fn validate_context_window(mut self, context_window: usize) -> Self {
86        if self.trigger_tokens >= context_window {
87            tracing::warn!(
88                trigger_tokens = self.trigger_tokens,
89                context_window,
90                "trigger_tokens >= context_window, clamping to 50% of context_window"
91            );
92            self.trigger_tokens = context_window / 2;
93        }
94        self
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_default_values() {
104        let cfg = CompressionConfig::default();
105        assert!(cfg.enabled);
106        assert_eq!(cfg.trigger_tokens, 256_000);
107        assert_eq!(cfg.keep_recent_messages, 16);
108        assert_eq!(cfg.max_summary_chars, 10 * 1024);
109        assert_eq!(cfg.max_transcript_chars, 20 * 1024);
110    }
111
112    #[test]
113    fn test_builder_chaining() {
114        let cfg = CompressionConfig::builder()
115            .with_enabled(false)
116            .with_trigger_tokens(50_000)
117            .with_keep_recent_messages(20)
118            .with_max_summary_chars(8_000)
119            .with_max_transcript_chars(16_000);
120
121        assert!(!cfg.enabled);
122        assert_eq!(cfg.trigger_tokens, 50_000);
123        assert_eq!(cfg.keep_recent_messages, 20);
124        assert_eq!(cfg.max_summary_chars, 8_000);
125        assert_eq!(cfg.max_transcript_chars, 16_000);
126    }
127}