syncable_cli/agent/compact/
config.rs1use serde::{Deserialize, Serialize};
6
7pub mod defaults {
9 pub const RETENTION_WINDOW: usize = 10;
11
12 pub const EVICTION_WINDOW: f64 = 0.6;
14
15 pub const TOKEN_THRESHOLD: usize = 80_000;
17
18 pub const TURN_THRESHOLD: usize = 20;
20
21 pub const MESSAGE_THRESHOLD: usize = 50;
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct CompactThresholds {
28 pub token_threshold: Option<usize>,
30
31 pub turn_threshold: Option<usize>,
33
34 pub message_threshold: Option<usize>,
36
37 pub on_turn_end: Option<bool>,
40}
41
42impl Default for CompactThresholds {
43 fn default() -> Self {
44 Self {
45 token_threshold: Some(defaults::TOKEN_THRESHOLD),
46 turn_threshold: Some(defaults::TURN_THRESHOLD),
47 message_threshold: Some(defaults::MESSAGE_THRESHOLD),
48 on_turn_end: None,
49 }
50 }
51}
52
53impl CompactThresholds {
54 pub fn aggressive() -> Self {
56 Self {
57 token_threshold: Some(40_000),
58 turn_threshold: Some(10),
59 message_threshold: Some(25),
60 on_turn_end: Some(true),
61 }
62 }
63
64 pub fn relaxed() -> Self {
66 Self {
67 token_threshold: Some(150_000),
68 turn_threshold: Some(50),
69 message_threshold: Some(100),
70 on_turn_end: None,
71 }
72 }
73
74 pub fn disabled() -> Self {
76 Self {
77 token_threshold: None,
78 turn_threshold: None,
79 message_threshold: None,
80 on_turn_end: None,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct CompactConfig {
88 pub retention_window: usize,
90
91 pub eviction_window: f64,
94
95 pub thresholds: CompactThresholds,
97}
98
99impl Default for CompactConfig {
100 fn default() -> Self {
101 Self {
102 retention_window: defaults::RETENTION_WINDOW,
103 eviction_window: defaults::EVICTION_WINDOW,
104 thresholds: CompactThresholds::default(),
105 }
106 }
107}
108
109impl CompactConfig {
110 pub fn with_retention(retention: usize) -> Self {
112 Self {
113 retention_window: retention,
114 ..Default::default()
115 }
116 }
117
118 pub fn with_thresholds(thresholds: CompactThresholds) -> Self {
120 Self {
121 thresholds,
122 ..Default::default()
123 }
124 }
125
126 pub fn should_compact(
134 &self,
135 token_count: usize,
136 turn_count: usize,
137 message_count: usize,
138 last_is_user: bool,
139 ) -> bool {
140 if let Some(threshold) = self.thresholds.token_threshold {
142 if token_count >= threshold {
143 return true;
144 }
145 }
146
147 if let Some(threshold) = self.thresholds.turn_threshold {
149 if turn_count >= threshold {
150 return true;
151 }
152 }
153
154 if let Some(threshold) = self.thresholds.message_threshold {
156 if message_count >= threshold {
157 return true;
158 }
159 }
160
161 if let Some(true) = self.thresholds.on_turn_end {
163 if last_is_user {
164 let near_token = self.thresholds.token_threshold
166 .map(|t| token_count >= t / 2)
167 .unwrap_or(false);
168 let near_turn = self.thresholds.turn_threshold
169 .map(|t| turn_count >= t / 2)
170 .unwrap_or(false);
171
172 if near_token || near_turn {
173 return true;
174 }
175 }
176 }
177
178 false
179 }
180
181 pub fn compaction_reason(
183 &self,
184 token_count: usize,
185 turn_count: usize,
186 message_count: usize,
187 ) -> Option<String> {
188 if let Some(threshold) = self.thresholds.token_threshold {
189 if token_count >= threshold {
190 return Some(format!("token count ({}) >= threshold ({})", token_count, threshold));
191 }
192 }
193
194 if let Some(threshold) = self.thresholds.turn_threshold {
195 if turn_count >= threshold {
196 return Some(format!("turn count ({}) >= threshold ({})", turn_count, threshold));
197 }
198 }
199
200 if let Some(threshold) = self.thresholds.message_threshold {
201 if message_count >= threshold {
202 return Some(format!("message count ({}) >= threshold ({})", message_count, threshold));
203 }
204 }
205
206 None
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_default_config() {
216 let config = CompactConfig::default();
217 assert_eq!(config.retention_window, defaults::RETENTION_WINDOW);
218 assert!((config.eviction_window - defaults::EVICTION_WINDOW).abs() < f64::EPSILON);
219 }
220
221 #[test]
222 fn test_should_compact_tokens() {
223 let config = CompactConfig::default();
224 assert!(!config.should_compact(50_000, 5, 10, false));
225 assert!(config.should_compact(100_000, 5, 10, false));
226 }
227
228 #[test]
229 fn test_should_compact_turns() {
230 let config = CompactConfig::default();
231 assert!(!config.should_compact(10_000, 10, 20, false));
232 assert!(config.should_compact(10_000, 25, 50, false));
233 }
234
235 #[test]
236 fn test_should_compact_messages() {
237 let config = CompactConfig::default();
238 assert!(!config.should_compact(10_000, 10, 30, false));
239 assert!(config.should_compact(10_000, 10, 60, false));
240 }
241
242 #[test]
243 fn test_aggressive_thresholds() {
244 let thresholds = CompactThresholds::aggressive();
245 assert_eq!(thresholds.token_threshold, Some(40_000));
246 assert_eq!(thresholds.turn_threshold, Some(10));
247 }
248
249 #[test]
250 fn test_disabled_thresholds() {
251 let config = CompactConfig::with_thresholds(CompactThresholds::disabled());
252 assert!(!config.should_compact(1_000_000, 1000, 10000, true));
253 }
254}