Add agent context compaction

This commit is contained in:
Georg Bauer
2026-07-26 10:48:29 +02:00
parent 1dcadeb882
commit 6aa45b2cf0
12 changed files with 625 additions and 2 deletions

View File

@@ -341,6 +341,13 @@ pub(crate) struct GenerationOutput {
pub(crate) checkpoint_bytes: u64,
}
#[cfg(target_os = "macos")]
pub(crate) struct CompactionOutput {
pub(crate) summary: String,
pub(crate) tail: Vec<ChatTurn>,
pub(crate) context_tokens: u32,
}
#[cfg(target_os = "macos")]
impl Generator {
pub(crate) fn open(settings: &EngineSettings, metrics: Arc<Metrics>) -> Result<Self, String> {
@@ -496,6 +503,95 @@ impl Generator {
Ok(output)
}
pub(crate) fn compact(
&mut self,
messages: &[ChatTurn],
settings: &TurnSettings,
reason: &str,
cancelled: &AtomicBool,
mut progress: impl FnMut(u32, u32, Option<f32>),
) -> Result<CompactionOutput, String> {
self.executor.reset()?;
self.checkpoint = None;
let result = (|| {
let mut private_messages = messages.to_vec();
private_messages.push(ChatTurn {
user: true,
tool: false,
skip_previous_eos: false,
reasoning: None,
reasoning_complete: true,
content: crate::compaction::summary_prompt(reason),
});
let mut private_settings = settings.clone();
private_settings.reasoning_mode = ReasoningMode::Direct;
private_settings.max_generated_tokens = crate::compaction::SUMMARY_MAX_TOKENS;
private_settings.temperature = 0.0;
private_settings.stops.extend([
"<DSML".into(),
"<DSML".into(),
"<tool_call>".into(),
"<think>".into(),
"</think>".into(),
]);
let (output, prompt_complete) = self.generate_inner(
&private_messages,
&private_settings,
cancelled,
&mut |_, _| {},
&mut progress,
)?;
if cancelled.load(Ordering::Relaxed) || !prompt_complete {
return Err("context compaction interrupted".into());
}
let summary = crate::compaction::sanitize_summary(&output.message.content);
if summary.is_empty() {
return Err("context compaction produced an empty summary".into());
}
let full = self.executor.model().render_conversation(
&settings.system_prompt,
messages,
settings.reasoning_mode,
);
let mut starts = Vec::with_capacity(messages.len());
for index in 0..messages.len() {
starts.push(
self.executor
.model()
.render_conversation(
&settings.system_prompt,
&messages[..index],
settings.reasoning_mode,
)
.len() as u32,
);
}
let start = crate::compaction::tail_start(
messages,
&starts,
full.len() as u32,
crate::compaction::tail_budget(self.executor.context()),
);
let tail = messages[start..].to_vec();
let rebuilt_system =
crate::compaction::summary_system_prompt(&settings.system_prompt, Some(&summary));
let context_tokens = self
.executor
.model()
.render_conversation(&rebuilt_system, &tail, settings.reasoning_mode)
.len() as u32;
Ok(CompactionOutput {
summary,
tail,
context_tokens,
})
})();
self.executor.reset()?;
self.checkpoint = None;
result
}
fn select_checkpoint(
&mut self,
checkpoint: &Path,