rustyclaw_core/
memory_flush.rs1use chrono::{Local, Utc};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct MemoryFlushConfig {
13 #[serde(default = "default_true")]
15 pub enabled: bool,
16
17 #[serde(default = "default_soft_threshold")]
20 pub soft_threshold_tokens: usize,
21
22 #[serde(default = "default_flush_system_prompt")]
24 pub system_prompt: String,
25
26 #[serde(default = "default_flush_user_prompt")]
28 pub user_prompt: String,
29}
30
31fn default_true() -> bool {
32 true
33}
34
35fn default_soft_threshold() -> usize {
36 4000
37}
38
39fn default_flush_system_prompt() -> String {
40 "Pre-compaction memory flush. Session context is approaching limits. \
41 Store any durable memories now (use memory/YYYY-MM-DD.md; create memory/ if needed). \
42 IMPORTANT: If the file already exists, APPEND new content only — do not overwrite existing entries. \
43 If nothing important needs to be stored, reply with NO_REPLY."
44 .to_string()
45}
46
47fn default_flush_user_prompt() -> String {
48 "Write any lasting notes or context to memory files before compaction. \
49 Reply with NO_REPLY if nothing needs to be stored."
50 .to_string()
51}
52
53impl Default for MemoryFlushConfig {
54 fn default() -> Self {
55 Self {
56 enabled: true,
57 soft_threshold_tokens: default_soft_threshold(),
58 system_prompt: default_flush_system_prompt(),
59 user_prompt: default_flush_user_prompt(),
60 }
61 }
62}
63
64pub struct MemoryFlush {
69 config: MemoryFlushConfig,
70 flushed_this_cycle: bool,
72}
73
74impl MemoryFlush {
75 pub fn new(config: MemoryFlushConfig) -> Self {
77 Self {
78 config,
79 flushed_this_cycle: false,
80 }
81 }
82
83 pub fn should_flush(
90 &self,
91 current_tokens: usize,
92 max_tokens: usize,
93 compaction_threshold: f64,
94 ) -> bool {
95 if !self.config.enabled || self.flushed_this_cycle {
96 return false;
97 }
98
99 let compaction_point = (max_tokens as f64 * compaction_threshold) as usize;
102 let flush_point = compaction_point.saturating_sub(self.config.soft_threshold_tokens);
103
104 current_tokens >= flush_point
105 }
106
107 pub fn build_flush_messages(&self) -> (String, String) {
111 let date = Local::now().format("%Y-%m-%d").to_string();
112 let time = Utc::now().format("%H:%M UTC").to_string();
113
114 let system = format!(
115 "{}\nCurrent time: {}. Today's date: {}.",
116 self.config.system_prompt, time, date
117 );
118
119 let user = self.config.user_prompt.replace("YYYY-MM-DD", &date);
120
121 (system, user)
122 }
123
124 pub fn mark_flushed(&mut self) {
126 self.flushed_this_cycle = true;
127 }
128
129 pub fn reset_cycle(&mut self) {
131 self.flushed_this_cycle = false;
132 }
133
134 pub fn is_enabled(&self) -> bool {
136 self.config.enabled
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_should_flush_at_threshold() {
146 let config = MemoryFlushConfig::default();
147 let flush = MemoryFlush::new(config);
148
149 assert!(!flush.should_flush(70000, 100000, 0.75));
152 assert!(flush.should_flush(71000, 100000, 0.75));
153 assert!(flush.should_flush(75000, 100000, 0.75));
154 }
155
156 #[test]
157 fn test_flush_only_once_per_cycle() {
158 let config = MemoryFlushConfig::default();
159 let mut flush = MemoryFlush::new(config);
160
161 assert!(flush.should_flush(75000, 100000, 0.75));
162 flush.mark_flushed();
163 assert!(!flush.should_flush(75000, 100000, 0.75));
164
165 flush.reset_cycle();
166 assert!(flush.should_flush(75000, 100000, 0.75));
167 }
168
169 #[test]
170 fn test_disabled_flush() {
171 let config = MemoryFlushConfig {
172 enabled: false,
173 ..Default::default()
174 };
175 let flush = MemoryFlush::new(config);
176
177 assert!(!flush.should_flush(100000, 100000, 0.75));
178 }
179
180 #[test]
181 fn test_build_flush_messages() {
182 let config = MemoryFlushConfig::default();
183 let flush = MemoryFlush::new(config);
184
185 let (system, user) = flush.build_flush_messages();
186
187 assert!(system.contains("Pre-compaction memory flush"));
188 assert!(system.contains("UTC"));
189 assert!(!user.contains("YYYY-MM-DD")); }
191}