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

@@ -39,6 +39,7 @@ pub struct Session {
pub last_tokens_per_second: Option<f32>,
/// Raw column value; read it through [`Session::state`].
state: String,
pub compacted_summary: Option<String>,
}
impl Session {
@@ -56,6 +57,7 @@ impl Session {
context_limit: 0,
last_tokens_per_second: None,
state: state.as_id().to_owned(),
compacted_summary: None,
}
}
}
@@ -129,6 +131,14 @@ struct NewMessage<'a> {
content: &'a str,
}
pub struct MessageDraft {
pub user: bool,
pub tool: bool,
pub reasoning: Option<String>,
pub reasoning_complete: bool,
pub content: String,
}
#[derive(Debug)]
pub struct ProjectWithSessions {
pub project: Project,
@@ -391,6 +401,40 @@ impl Database {
.map(|_| ())
.map_err(|error| error.to_string())
}
pub fn replace_with_compacted_transcript(
&mut self,
session_id: i32,
summary: &str,
tail: &[MessageDraft],
) -> Result<Vec<StoredMessage>, String> {
self.connection
.transaction(|connection| {
diesel::delete(messages::table.filter(messages::session_id.eq(session_id)))
.execute(connection)?;
diesel::update(sessions::table.find(session_id))
.set(sessions::compacted_summary.eq(Some(summary)))
.execute(connection)?;
let mut stored = Vec::with_capacity(tail.len());
for message in tail {
stored.push(
diesel::insert_into(messages::table)
.values(NewMessage {
session_id,
user: message.user,
tool: message.tool,
reasoning: message.reasoning.as_deref(),
reasoning_complete: message.reasoning_complete,
content: &message.content,
})
.returning(StoredMessage::as_returning())
.get_result(connection)?,
);
}
Ok(stored)
})
.map_err(|error: diesel::result::Error| error.to_string())
}
}
#[cfg(test)]
@@ -506,6 +550,29 @@ mod tests {
assert_eq!(messages[2].content, "Tool result");
assert!(!messages[3].user);
assert!(!messages[3].tool);
let compacted = reopened
.replace_with_compacted_transcript(
session.id,
"Keep the active task.",
&[MessageDraft {
user: true,
tool: false,
reasoning: None,
reasoning_complete: true,
content: "Recent question".into(),
}],
)
.unwrap();
assert_eq!(compacted.len(), 1);
drop(reopened);
let mut reopened = Database::open(&path).unwrap();
assert_eq!(
reopened.load_projects().unwrap()[0].sessions[0]
.compacted_summary
.as_deref(),
Some("Keep the active task.")
);
assert_eq!(reopened.load_messages(session.id).unwrap().len(), 1);
reopened.delete_session(session.id).unwrap();
assert!(reopened.load_messages(session.id).unwrap().is_empty());
drop(reopened);