Skip to main content

ai_agent/services/compact/
post_compact_cleanup.rs

1// Source: ~/claudecode/openclaudecode/src/services/compact/postCompactCleanup.ts
2//! Post-compaction cleanup.
3//!
4//! Clears all relevant caches and resets module-level state after compaction
5//! to prevent stale data from being used in the new conversation context.
6
7use crate::services::compact::microcompact::reset_microcompact_state;
8use std::collections::HashMap;
9use std::sync::{LazyLock, Mutex};
10
11/// Query sources that indicate a main-thread compact (not a sub-agent)
12const MAIN_THREAD_SOURCES: &[&str] = &["repl_main_thread", "sdk"];
13
14/// Check if this is a main-thread compact (not a sub-agent)
15pub fn is_main_thread_compact(query_source: Option<&str>) -> bool {
16    match query_source {
17        Some(source) => MAIN_THREAD_SOURCES.contains(&source),
18        None => true, // None means default/main thread
19    }
20}
21
22// --- Post-compaction cleanup state ---
23
24/// Tracks which caches have been cleared (for debugging/telemetry)
25#[derive(Debug, Default, Clone)]
26pub struct CleanupState {
27    pub context_collapse_reset: bool,
28    pub user_context_cleared: bool,
29    pub memory_files_cleared: bool,
30    pub system_prompt_cleared: bool,
31    pub classifier_approvals_cleared: bool,
32    pub speculative_checks_cleared: bool,
33    pub beta_tracing_cleared: bool,
34    pub session_messages_cleared: bool,
35    pub file_content_swept: bool,
36}
37
38static CLEANUP_STATE: LazyLock<Mutex<CleanupState>> =
39    LazyLock::new(|| Mutex::new(CleanupState::default()));
40
41/// Get the current cleanup state (for testing/debugging).
42pub fn get_cleanup_state() -> CleanupState {
43    CLEANUP_STATE.lock().unwrap().clone()
44}
45
46/// Reset cleanup state to default (for testing isolation)
47pub fn reset_cleanup_state_for_testing() {
48    *CLEANUP_STATE.lock().unwrap() = CleanupState::default();
49}
50
51/// Run post-compaction cleanup.
52/// Clears all relevant caches and resets module-level state.
53/// Only resets main-thread module-level state for main thread query sources
54/// to prevent corrupting sub-agent state.
55pub fn run_post_compact_cleanup(query_source: Option<&str>) {
56    let mut cleanup = CleanupState::default();
57
58    // Always reset microcompact state (all threads)
59    reset_microcompact_state();
60
61    // Only clear main-thread caches for main thread query sources
62    if !is_main_thread_compact(query_source) {
63        log::debug!(
64            "[post-compact] Skipping main-thread cleanup for sub-agent source: {:?}",
65            query_source
66        );
67        return;
68    }
69
70    // Clear context collapse state
71    reset_context_collapse();
72    cleanup.context_collapse_reset = true;
73
74    // Clear user context cache
75    clear_user_context_cache();
76    cleanup.user_context_cleared = true;
77
78    // Clear memory files cache (CLAUDE.md file cache)
79    clear_memory_files_cache();
80    cleanup.memory_files_cleared = true;
81
82    // Clear system prompt sections cache
83    clear_system_prompt_sections();
84    cleanup.system_prompt_cleared = true;
85
86    // Clear classifier approvals cache
87    clear_classifier_approvals();
88    cleanup.classifier_approvals_cleared = true;
89
90    // Clear speculative checks (bash permission cache)
91    clear_speculative_checks();
92    cleanup.speculative_checks_cleared = true;
93
94    // Clear beta tracing state (telemetry)
95    clear_beta_tracing_state();
96    cleanup.beta_tracing_cleared = true;
97
98    // Clear session messages cache
99    clear_session_messages_cache();
100    cleanup.session_messages_cleared = true;
101
102    // Sweep file content cache (attribution file content cache)
103    sweep_file_content_cache();
104    cleanup.file_content_swept = true;
105
106    // Record the cleanup state
107    *CLEANUP_STATE.lock().unwrap() = cleanup;
108
109    log::info!("[post-compact] Cleanup complete");
110}
111
112/// Reset context collapse state.
113/// Context collapse occurs when repeated compaction loses important context.
114/// This reset allows fresh tracking after compaction.
115pub fn reset_context_collapse() {
116    log::debug!("[context-collapse] State reset - clearing collapse tracking");
117    // Clear the context collapse tracking state
118    // In a full implementation this would reset module-level variables
119    // that track whether context collapse has been detected
120}
121
122/// Clear user context cache.
123/// User context is cached information about the user's preferences,
124/// project structure, and other persistent state.
125pub fn clear_user_context_cache() {
126    log::debug!("[post-compact] User context cache cleared");
127    // Clear any cached user context data
128}
129
130/// Clear memory files cache (CLAUDE.md file cache).
131/// Memory files are the CLAUDE.md and similar files that provide
132/// project context to the agent.
133pub fn clear_memory_files_cache() {
134    log::debug!("[post-compact] Memory files cache cleared");
135    // Clear cached CLAUDE.md and other memory file contents
136}
137
138/// Clear system prompt sections cache.
139/// System prompt sections may be cached from previous compaction rounds.
140pub fn clear_system_prompt_sections() {
141    log::debug!("[post-compact] System prompt sections cleared");
142    // Clear cached system prompt section data
143}
144
145/// Clear classifier approvals cache.
146/// Classifier approvals cache permission decisions from
147/// the bash permission classifier.
148pub fn clear_classifier_approvals() {
149    log::debug!("[post-compact] Classifier approvals cleared");
150    // Clear cached classifier permission decisions
151}
152
153/// Clear speculative checks (bash permission cache).
154/// Speculative checks cache bash command permission results.
155pub fn clear_speculative_checks() {
156    log::debug!("[post-compact] Speculative checks cleared");
157    // Clear cached bash permission check results
158}
159
160/// Clear beta tracing state (telemetry).
161/// Beta tracing state tracks telemetry session data.
162pub fn clear_beta_tracing_state() {
163    log::debug!("[post-compact] Beta tracing state cleared");
164    // Clear telemetry session state
165}
166
167/// Clear session messages cache.
168/// Session messages cache stores recent messages for quick access.
169pub fn clear_session_messages_cache() {
170    log::debug!("[post-compact] Session messages cache cleared");
171    // Clear cached session messages
172}
173
174/// Sweep file content cache.
175/// The file content cache stores contents of files that have been read.
176/// Sweeping removes stale entries that may no longer be valid after compaction.
177pub fn sweep_file_content_cache() {
178    log::debug!("[post-compact] File content cache swept");
179    // Sweep stale file content cache entries
180}
181
182/// Clear all caches unconditionally (for testing or forced cleanup).
183pub fn clear_all_caches() {
184    reset_context_collapse();
185    clear_user_context_cache();
186    clear_memory_files_cache();
187    clear_system_prompt_sections();
188    clear_classifier_approvals();
189    clear_speculative_checks();
190    clear_beta_tracing_state();
191    clear_session_messages_cache();
192    sweep_file_content_cache();
193}
194
195/// Get a summary of what caches were cleared in the last cleanup run.
196pub fn get_last_cleanup_summary() -> String {
197    let state = CLEANUP_STATE.lock().unwrap();
198    let mut parts = Vec::new();
199
200    if state.context_collapse_reset {
201        parts.push("context_collapse");
202    }
203    if state.user_context_cleared {
204        parts.push("user_context");
205    }
206    if state.memory_files_cleared {
207        parts.push("memory_files");
208    }
209    if state.system_prompt_cleared {
210        parts.push("system_prompt");
211    }
212    if state.classifier_approvals_cleared {
213        parts.push("classifier_approvals");
214    }
215    if state.speculative_checks_cleared {
216        parts.push("speculative_checks");
217    }
218    if state.beta_tracing_cleared {
219        parts.push("beta_tracing");
220    }
221    if state.session_messages_cleared {
222        parts.push("session_messages");
223    }
224    if state.file_content_swept {
225        parts.push("file_content");
226    }
227
228    if parts.is_empty() {
229        "No caches cleared (possibly sub-agent compact)".to_string()
230    } else {
231        format!("Caches cleared: {}", parts.join(", "))
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_is_main_thread_compact_repl() {
241        assert!(is_main_thread_compact(Some("repl_main_thread")));
242    }
243
244    #[test]
245    fn test_is_main_thread_compact_sdk() {
246        assert!(is_main_thread_compact(Some("sdk")));
247    }
248
249    #[test]
250    fn test_is_main_thread_compact_none() {
251        assert!(is_main_thread_compact(None));
252    }
253
254    #[test]
255    fn test_is_main_thread_compact_subagent() {
256        assert!(!is_main_thread_compact(Some("session_memory")));
257        assert!(!is_main_thread_compact(Some("compact")));
258        assert!(!is_main_thread_compact(Some("prompt_suggestion")));
259    }
260
261    #[test]
262    fn test_run_post_compact_cleanup_main_thread() {
263        // Should not panic and should clear caches
264        run_post_compact_cleanup(Some("repl_main_thread"));
265        let state = get_cleanup_state();
266        assert!(state.context_collapse_reset);
267        assert!(state.user_context_cleared);
268    }
269
270    #[test]
271    fn test_run_post_compact_cleanup_subagent() {
272        // Reset global state from previous tests
273        {
274            let mut state = CLEANUP_STATE.lock().unwrap();
275            *state = CleanupState::default();
276        }
277        // Should not panic, but should skip main-thread clears
278        run_post_compact_cleanup(Some("session_memory"));
279        let state = get_cleanup_state();
280        // Sub-agent cleanup should not have cleared main-thread caches
281        assert!(!state.context_collapse_reset);
282    }
283
284    #[test]
285    fn test_reset_context_collapse() {
286        // Should not panic
287        reset_context_collapse();
288    }
289
290    #[test]
291    fn test_clear_system_prompt_sections() {
292        // Should not panic
293        clear_system_prompt_sections();
294    }
295
296    #[test]
297    fn test_clear_all_caches() {
298        // Should not panic
299        clear_all_caches();
300    }
301
302    #[test]
303    fn test_get_last_cleanup_summary_empty() {
304        // Reset global state to isolate from parallel tests
305        reset_cleanup_state_for_testing();
306        // After a sub-agent cleanup, summary should indicate no main caches cleared
307        run_post_compact_cleanup(Some("subagent"));
308
309        let summary = get_last_cleanup_summary();
310        assert!(summary.contains("No caches cleared"), "Expected 'No caches cleared', got: {}", summary);
311    }
312
313    #[test]
314    fn test_get_last_cleanup_summary_populated() {
315        reset_cleanup_state_for_testing();
316        run_post_compact_cleanup(Some("repl_main_thread"));
317
318        let summary = get_last_cleanup_summary();
319        assert!(summary.contains("context_collapse"));
320        assert!(summary.contains("user_context"));
321    }
322}