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 && token_count >= threshold
143 {
144 return true;
145 }
146
147 if let Some(threshold) = self.thresholds.turn_threshold
149 && turn_count >= threshold
150 {
151 return true;
152 }
153
154 if let Some(threshold) = self.thresholds.message_threshold
156 && message_count >= threshold
157 {
158 return true;
159 }
160
161 if let Some(true) = self.thresholds.on_turn_end
163 && last_is_user
164 {
165 let near_token = self
167 .thresholds
168 .token_threshold
169 .map(|t| token_count >= t / 2)
170 .unwrap_or(false);
171 let near_turn = self
172 .thresholds
173 .turn_threshold
174 .map(|t| turn_count >= t / 2)
175 .unwrap_or(false);
176
177 if near_token || near_turn {
178 return true;
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 && token_count >= threshold
194 {
195 return Some(format!(
196 "token count ({}) >= threshold ({})",
197 token_count, threshold
198 ));
199 }
200
201 if let Some(threshold) = self.thresholds.turn_threshold
202 && turn_count >= threshold
203 {
204 return Some(format!(
205 "turn count ({}) >= threshold ({})",
206 turn_count, threshold
207 ));
208 }
209
210 if let Some(threshold) = self.thresholds.message_threshold
211 && message_count >= threshold
212 {
213 return Some(format!(
214 "message count ({}) >= threshold ({})",
215 message_count, threshold
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}