use consistent naming for compaction

This commit is contained in:
Dhanji R. Prasanna
2026-01-08 12:54:03 +11:00
parent 3776ed847e
commit 5bfaee8dd5
13 changed files with 86 additions and 89 deletions

View File

@@ -1666,7 +1666,7 @@ async fn run_interactive<W: UiWriter>(
"/help" => {
output.print("");
output.print("📖 Control Commands:");
output.print(" /compact - Trigger auto-summarization (compacts conversation history)");
output.print(" /compact - Trigger compaction (compacts conversation history)");
output.print(" /thinnify - Trigger context thinning (replaces large tool results with file references)");
output.print(" /skinnify - Trigger full context thinning (like /thinnify but for entire context, not just first third)");
output.print(" /clear - Clear session and start fresh (discards continuation artifacts)");
@@ -1680,17 +1680,17 @@ async fn run_interactive<W: UiWriter>(
continue;
}
"/compact" => {
output.print("🗜️ Triggering manual summarization...");
match agent.force_summarize().await {
output.print("🗜️ Triggering manual compaction...");
match agent.force_compact().await {
Ok(true) => {
output.print("Summarization completed successfully");
output.print("Compaction completed successfully");
}
Ok(false) => {
output.print("⚠️ Summarization failed");
output.print("⚠️ Compaction failed");
}
Err(e) => {
output.print(&format!(
"❌ Error during summarization: {}",
"❌ Error during compaction: {}",
e
));
}
@@ -1909,9 +1909,9 @@ async fn run_interactive_machine(
match input.as_str() {
"/compact" => {
println!("COMMAND: compact");
match agent.force_summarize().await {
Ok(true) => println!("RESULT: Summarization completed"),
Ok(false) => println!("RESULT: Summarization failed"),
match agent.force_compact().await {
Ok(true) => println!("RESULT: Compaction completed"),
Ok(false) => println!("RESULT: Compaction failed"),
Err(e) => println!("ERROR: {}", e),
}
continue;

View File

@@ -321,7 +321,7 @@ impl UiWriter for ConsoleUiWriter {
fn print_final_output(&self, summary: &str) {
// Show spinner while "formatting"
let spinner_frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let message = "summarizing work done...";
let message = "compacting work done...";
// Brief spinner animation (about 0.5 seconds)
for i in 0..5 {

View File

@@ -183,8 +183,8 @@ impl ContextWindow {
self.total_tokens.saturating_sub(self.used_tokens)
}
/// Check if we should trigger summarization (at 80% capacity)
pub fn should_summarize(&self) -> bool {
/// Check if we should trigger compaction (at 80% capacity)
pub fn should_compact(&self) -> bool {
// Trigger at 80% OR if we're getting close to absolute limits
// This prevents issues with models that have large contexts but still hit limits
let percentage_trigger = self.percentage_used() >= 80.0;
@@ -744,19 +744,19 @@ mod tests {
}
#[test]
fn test_should_summarize_at_80_percent() {
fn test_should_compact_at_80_percent() {
let mut cw = ContextWindow::new(100);
cw.used_tokens = 79;
assert!(!cw.should_summarize());
assert!(!cw.should_compact());
cw.used_tokens = 80;
assert!(cw.should_summarize());
assert!(cw.should_compact());
}
#[test]
fn test_should_summarize_at_absolute_limit() {
fn test_should_compact_at_absolute_limit() {
let mut cw = ContextWindow::new(1_000_000);
cw.used_tokens = 150_001;
assert!(cw.should_summarize());
assert!(cw.should_compact());
}
#[test]

View File

@@ -181,7 +181,7 @@ pub enum RecoverableError {
ModelBusy,
/// Timeout
Timeout,
/// Token limit exceeded (might be recoverable with summarization)
/// Token limit exceeded (might be recoverable with compaction)
TokenLimit,
/// Context length exceeded (prompt too long) - should end current turn in autonomous mode
ContextLengthExceeded,
@@ -357,7 +357,7 @@ where
// Special handling for token limit errors
if matches!(recoverable_type, RecoverableError::TokenLimit) {
debug!("Token limit error detected. Consider triggering summarization.");
debug!("Token limit error detected. Consider triggering compaction.");
}
tokio::time::sleep(delay).await;

View File

@@ -92,9 +92,9 @@ pub struct Agent<W: UiWriter> {
providers: ProviderRegistry,
context_window: ContextWindow,
thinning_events: Vec<usize>, // chars saved per thinning event
pending_90_summarization: bool, // flag to trigger summarization at 90%
pending_90_compaction: bool, // flag to trigger compaction at 90%
auto_compact: bool, // whether to auto-compact at 90% before tool calls
summarization_events: Vec<usize>, // chars saved per summarization event
compaction_events: Vec<usize>, // chars saved per compaction event
first_token_times: Vec<Duration>, // time to first token for each completion
config: Config,
session_id: Option<String>,
@@ -267,9 +267,9 @@ impl<W: UiWriter> Agent<W> {
providers,
context_window,
auto_compact: config.agent.auto_compact,
pending_90_summarization: false,
pending_90_compaction: false,
thinning_events: Vec::new(),
summarization_events: Vec::new(),
compaction_events: Vec::new(),
first_token_times: Vec::new(),
config,
session_id: None,
@@ -856,15 +856,15 @@ impl<W: UiWriter> Agent<W> {
self.save_context_window("completed");
// Check if we need to do 90% auto-compaction
if self.pending_90_summarization {
if self.pending_90_compaction {
self.ui_writer
.print_context_status("\n⚡ Context window reached 90% - auto-compacting...\n");
if let Err(e) = self.force_summarize().await {
if let Err(e) = self.force_compact().await {
warn!("Failed to auto-compact at 90%: {}", e);
} else {
self.ui_writer.println("");
}
self.pending_90_summarization = false;
self.pending_90_compaction = false;
}
// Return the task result which already includes timing if needed
@@ -940,13 +940,13 @@ impl<W: UiWriter> Agent<W> {
}
}
/// Manually trigger context summarization regardless of context window size
/// Returns Ok(true) if summarization was successful, Ok(false) if it failed
pub async fn force_summarize(&mut self) -> Result<bool> {
debug!("Manual summarization triggered");
/// Manually trigger context compaction regardless of context window size
/// Returns Ok(true) if compaction was successful, Ok(false) if it failed
pub async fn force_compact(&mut self) -> Result<bool> {
debug!("Manual compaction triggered");
self.ui_writer.print_context_status(&format!(
"\n🗜️ Manual summarization requested (current usage: {}%)...",
"\n🗜️ Manual compaction requested (current usage: {}%)...",
self.context_window.percentage_used() as u32
));
@@ -1048,7 +1048,7 @@ impl<W: UiWriter> Agent<W> {
let chars_saved = self
.context_window
.reset_with_summary(summary_response.content, latest_user_msg);
self.summarization_events.push(chars_saved);
self.compaction_events.push(chars_saved);
Ok(true)
}
@@ -1238,17 +1238,17 @@ impl<W: UiWriter> Agent<W> {
}
stats.push_str(&format!(
"Summarizations: {:>10}\n",
self.summarization_events.len()
"Compactions: {:>10}\n",
self.compaction_events.len()
));
if !self.summarization_events.is_empty() {
let total_summarized: usize = self.summarization_events.iter().sum();
let avg_summarized = total_summarized / self.summarization_events.len();
if !self.compaction_events.is_empty() {
let total_compacted: usize = self.compaction_events.iter().sum();
let avg_compacted = total_compacted / self.compaction_events.len();
stats.push_str(&format!(
" • Total Chars Saved: {:>10}\n",
total_summarized
total_compacted
));
stats.push_str(&format!(" • Avg Chars/Event: {:>10}\n", avg_summarized));
stats.push_str(&format!(" • Avg Chars/Event: {:>10}\n", avg_compacted));
}
stats.push('\n');
@@ -1604,9 +1604,9 @@ impl<W: UiWriter> Agent<W> {
// Note: Session-level duplicate tracking was removed - we only prevent sequential duplicates (DUP IN CHUNK, DUP IN MSG)
let mut turn_accumulated_usage: Option<g3_providers::Usage> = None; // Track token usage for timing footer
// Check if we need to summarize before starting
if self.context_window.should_summarize() {
// First try thinning if we are at capacity, don't call the LLM for a summary (might fail)
// Check if we need to compact before starting
if self.context_window.should_compact() {
// First try thinning if we are at capacity, don't call the LLM for compaction (might fail)
if self.context_window.percentage_used() > 90.0 && self.context_window.should_thin() {
self.ui_writer.print_context_status(&format!(
"\n🥒 Context window at {}%. Trying thinning first...",
@@ -1617,23 +1617,23 @@ impl<W: UiWriter> Agent<W> {
self.ui_writer.print_context_thinning(&thin_summary);
// Check if thinning was sufficient
if !self.context_window.should_summarize() {
if !self.context_window.should_compact() {
self.ui_writer.print_context_status(
"✅ Thinning resolved capacity issue. Continuing...\n",
);
// Continue with the original request without summarization
// Continue with the original request without compaction
} else {
self.ui_writer.print_context_status(
"⚠️ Thinning insufficient. Proceeding with summarization...\n",
"⚠️ Thinning insufficient. Proceeding with compaction...\n",
);
}
}
// Only proceed with summarization if still needed after thinning
if self.context_window.should_summarize() {
// Notify user about summarization
// Only proceed with compaction if still needed after thinning
if self.context_window.should_compact() {
// Notify user about compaction
self.ui_writer.print_context_status(&format!(
"\n🗜️ Context window reaching capacity ({}%). Creating summary...",
"\n🗜️ Context window reaching capacity ({}%). Compacting...",
self.context_window.percentage_used() as u32
));
@@ -1735,17 +1735,17 @@ impl<W: UiWriter> Agent<W> {
let chars_saved = self
.context_window
.reset_with_summary(summary_response.content, latest_user_msg);
self.summarization_events.push(chars_saved);
self.compaction_events.push(chars_saved);
// Update the request with new context
request.messages = self.context_window.conversation_history.clone();
}
Err(e) => {
error!("Failed to create summary: {}", e);
self.ui_writer.print_context_status("⚠️ Unable to create summary. Consider starting a new session if you continue to see errors.\n");
// Don't continue with the original request if summarization failed
self.ui_writer.print_context_status("⚠️ Unable to compact context. Consider starting a new session if you continue to see errors.\n");
// Don't continue with the original request if compaction failed
// as we're likely at token limit
return Err(anyhow::anyhow!("Context window at capacity and summarization failed. Please start a new session."));
return Err(anyhow::anyhow!("Context window at capacity and compaction failed. Please start a new session."));
}
}
}
@@ -1963,9 +1963,9 @@ impl<W: UiWriter> Agent<W> {
// Check if we should auto-compact at 90% BEFORE executing the tool
// We need to do this before any borrows of self
if self.auto_compact && self.context_window.percentage_used() >= 90.0 {
// Set flag to trigger summarization after this turn completes
// Set flag to trigger compaction after this turn completes
// We can't do it now due to borrow checker constraints
self.pending_90_summarization = true;
self.pending_90_compaction = true;
}
// Check if we should thin the context BEFORE executing the tool

View File

@@ -2,7 +2,7 @@ use g3_core::ContextWindow;
use g3_providers::{Message, MessageRole, Usage};
/// Test that used_tokens is tracked via add_message, not update_usage_from_response.
/// This is critical for the 80% summarization threshold to work correctly.
/// This is critical for the 80% compaction threshold to work correctly.
#[test]
fn test_used_tokens_tracked_via_messages() {
let mut window = ContextWindow::new(10000);
@@ -106,10 +106,10 @@ fn test_percentage_based_on_used_tokens() {
assert!(window.remaining_tokens() < 1000, "remaining tokens should decrease");
}
/// Test that the 80% summarization threshold works correctly.
/// Test that the 80% compaction threshold works correctly.
/// This was the original bug - used_tokens was being double/triple counted.
#[test]
fn test_should_summarize_threshold() {
fn test_should_compact_threshold() {
let mut window = ContextWindow::new(1000);
// Add messages until we approach 80%
@@ -131,9 +131,9 @@ fn test_should_summarize_threshold() {
let percentage_after = window.percentage_used();
println!("After 10 messages: {}% used ({} tokens)", percentage_after, window.used_tokens);
// Now should_summarize should return true if we're at 80%+
// Now should_compact should return true if we're at 80%+
if percentage_after >= 80.0 {
assert!(window.should_summarize(), "should_summarize should be true at 80%+");
assert!(window.should_compact(), "should_compact should be true at 80%+");
}
}