Skip to main content

vtcode_core/core/agent/
compaction_checkpoint.rs

1//! Compaction checkpoint: writes compaction summaries to persistent artifacts.
2//!
3//! Following the context engineering principle: "the summary should be written
4//! into a persistent artifact, such as progress.md, so that later sessions can
5//! read it." This module ensures that when compaction happens, the summary is
6//! written to `memories/progress.md` and `memories/compaction_summary.md` so
7//! that later sessions can orient from these artifacts.
8//!
9//! This bridges the gap between compaction (which compresses conversation history)
10//! and the orient phase (which reads external artifacts at session start).
11
12use std::path::Path;
13
14use anyhow::Context;
15use tracing::warn;
16
17use crate::compaction::memory_envelope::SessionMemoryEnvelope;
18
19/// Write compaction summary to persistent artifacts for later sessions.
20///
21/// This is called after compaction completes to ensure the summary survives
22/// across sessions. It writes to:
23/// - `memories/progress.md` - human-readable progress summary
24/// - `memories/compaction_summary.md` - detailed compaction summary
25pub fn write_compaction_checkpoint(workspace_root: &Path, envelope: &SessionMemoryEnvelope) {
26    let memories_dir = workspace_root.join("memories");
27    if let Err(e) = std::fs::create_dir_all(&memories_dir) {
28        warn!(path = %memories_dir.display(), error = %e, "failed to create memories dir");
29        return;
30    }
31
32    // Write progress.md with the compaction summary
33    let progress_path = memories_dir.join("progress.md");
34    let progress_content = format!(
35        "# Session Progress\n\n\
36         **Session:** {}\n\
37         **Generated:** {}\n\n\
38         ## Summary\n{}\n\n\
39         ## Objective\n{}\n\n\
40         ## Key Facts\n{}\n\n\
41         ## Touched Files\n{}\n",
42        envelope.session_id,
43        envelope.generated_at,
44        envelope.summary.trim(),
45        envelope.objective.as_deref().unwrap_or("(not set)"),
46        if envelope.grounded_facts.is_empty() {
47            "(none)".to_string()
48        } else {
49            envelope
50                .grounded_facts
51                .iter()
52                .map(|f| format!("- {}", f.fact))
53                .collect::<Vec<_>>()
54                .join("\n")
55        },
56        if envelope.touched_files.is_empty() {
57            "(none)".to_string()
58        } else {
59            envelope
60                .touched_files
61                .iter()
62                .map(|f| format!("- {f}"))
63                .collect::<Vec<_>>()
64                .join("\n")
65        },
66    );
67    if let Err(e) = std::fs::write(&progress_path, progress_content) {
68        warn!(path = %progress_path.display(), error = %e, "failed to write progress checkpoint");
69    }
70
71    // Write compaction_summary.md with the full summary
72    let summary_path = memories_dir.join("compaction_summary.md");
73    let summary_content = format!(
74        "# Compaction Summary\n\n\
75         **Session:** {}\n\
76         **Generated:** {}\n\n\
77         ## Full Summary\n{}\n\n\
78         ## Constraints\n{}\n\n\
79         ## Open Questions\n{}\n\n\
80         ## Verification TODO\n{}\n",
81        envelope.session_id,
82        envelope.generated_at,
83        envelope.summary.trim(),
84        if envelope.constraints.is_empty() {
85            "(none)".to_string()
86        } else {
87            envelope
88                .constraints
89                .iter()
90                .map(|c| format!("- {c}"))
91                .collect::<Vec<_>>()
92                .join("\n")
93        },
94        if envelope.open_questions.is_empty() {
95            "(none)".to_string()
96        } else {
97            envelope
98                .open_questions
99                .iter()
100                .map(|q| format!("- {q}"))
101                .collect::<Vec<_>>()
102                .join("\n")
103        },
104        if envelope.verification_todo.is_empty() {
105            "(none)".to_string()
106        } else {
107            envelope
108                .verification_todo
109                .iter()
110                .map(|t| format!("- {t}"))
111                .collect::<Vec<_>>()
112                .join("\n")
113        },
114    );
115    if let Err(e) = std::fs::write(&summary_path, summary_content) {
116        warn!(path = %summary_path.display(), error = %e, "failed to write compaction summary");
117    }
118}
119
120/// Async adapter for compaction callers running on Tokio workers.
121pub async fn write_compaction_checkpoint_async(workspace_root: &Path, envelope: &SessionMemoryEnvelope) {
122    let workspace_root = workspace_root.to_path_buf();
123    let envelope = envelope.clone();
124    if let Err(error) = tokio::task::spawn_blocking(move || write_compaction_checkpoint(&workspace_root, &envelope))
125        .await
126        .context("Compaction checkpoint task panicked")
127    {
128        warn!(error = %error, "failed to persist compaction checkpoint");
129    }
130}