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