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
166 .thresholds
167 .token_threshold
168 .map(|t| token_count >= t / 2)
169 .unwrap_or(false);
170 let near_turn = self
171 .thresholds
172 .turn_threshold
173 .map(|t| turn_count >= t / 2)
174 .unwrap_or(false);
175
176 if near_token || near_turn {
177 return true;
178 }
179 }
180 }
181
182 false
183 }
184
185 pub fn compaction_reason(
187 &self,
188 token_count: usize,
189 turn_count: usize,
190 message_count: usize,
191 ) -> Option<String> {
192 if let Some(threshold) = self.thresholds.token_threshold {
193 if token_count >= threshold {
194 return Some(format!(
195 "token count ({}) >= threshold ({})",
196 token_count, threshold
197 ));
198 }
199 }
200
201 if let Some(threshold) = self.thresholds.turn_threshold {
202 if turn_count >= threshold {
203 return Some(format!(
204 "turn count ({}) >= threshold ({})",
205 turn_count, threshold
206 ));
207 }
208 }
209
210 if let Some(threshold) = self.thresholds.message_threshold {
211 if message_count >= threshold {
212 return Some(format!(
213 "message count ({}) >= threshold ({})",
214 message_count, threshold
215 ));
216 }
217 }
218
219 None
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_default_config() {
229 let config = CompactConfig::default();
230 assert_eq!(config.retention_window, defaults::RETENTION_WINDOW);
231 assert!((config.eviction_window - defaults::EVICTION_WINDOW).abs() < f64::EPSILON);
232 }
233
234 #[test]
235 fn test_should_compact_tokens() {
236 let config = CompactConfig::default();
237 assert!(!config.should_compact(50_000, 5, 10, false));
238 assert!(config.should_compact(100_000, 5, 10, false));
239 }
240
241 #[test]
242 fn test_should_compact_turns() {
243 let config = CompactConfig::default();
244 assert!(!config.should_compact(10_000, 10, 20, false));
245 assert!(config.should_compact(10_000, 25, 50, false));
246 }
247
248 #[test]
249 fn test_should_compact_messages() {
250 let config = CompactConfig::default();
251 assert!(!config.should_compact(10_000, 10, 30, false));
252 assert!(config.should_compact(10_000, 10, 60, false));
253 }
254
255 #[test]
256 fn test_aggressive_thresholds() {
257 let thresholds = CompactThresholds::aggressive();
258 assert_eq!(thresholds.token_threshold, Some(40_000));
259 assert_eq!(thresholds.turn_threshold, Some(10));
260 }
261
262 #[test]
263 fn test_disabled_thresholds() {
264 let config = CompactConfig::with_thresholds(CompactThresholds::disabled());
265 assert!(!config.should_compact(1_000_000, 1000, 10000, true));
266 }
267}