Fix continuation messaging

This commit is contained in:
Jack Amadeo
2025-10-27 12:57:47 -04:00
parent db58b0c176
commit 096830a7cc
3 changed files with 63 additions and 36 deletions
-1
View File
@@ -659,7 +659,6 @@ impl CliSession {
goose::context_mgmt::compact_messages(
self.agent.provider().await?.as_ref(),
&self.messages,
false,
)
.await?;
+2 -2
View File
@@ -812,7 +812,7 @@ impl Agent {
);
let provider = self.provider().await?;
match crate::context_mgmt::compact_messages(provider.as_ref(), &conversation_to_compact, false).await {
match crate::context_mgmt::compact_messages(provider.as_ref(), &conversation_to_compact).await {
Ok((compacted_conversation, _token_counts, _summarization_usage)) => {
if let Some(session_to_store) = &session {
SessionManager::replace_conversation(&session_to_store.id, &compacted_conversation).await?;
@@ -1175,7 +1175,7 @@ impl Agent {
);
let provider = self.provider().await?;
match crate::context_mgmt::compact_messages(provider.as_ref(), &conversation, true).await {
match crate::context_mgmt::compact_messages(provider.as_ref(), &conversation).await {
Ok((compacted_conversation, _token_counts, _usage)) => {
if let Some(session_to_store) = &session {
SessionManager::replace_conversation(&session_to_store.id, &compacted_conversation).await?
+61 -33
View File
@@ -16,6 +16,52 @@ struct SummarizeContext {
messages: String,
}
fn is_user_submitted_text(message: &Message) -> bool {
match message.role {
Role::User => !is_tool_response_message(message),
Role::Assistant => false,
}
}
fn is_tool_response_message(message: &Message) -> bool {
message
.content
.iter()
.any(|c| c.as_tool_response().is_some())
}
const CONVSERATION_CONTINUATION_TEXT: &str =
"The previous message contains a summary that was prepared because a context limit was reached.
Do not mention that you read a summary or that conversation summarization occurred
Just continue the conversation naturally based on the summarized context";
const TOOL_LOOP_CONTINUATION_TEXT: &str =
"The previous message contains a summary that was prepared because a context limit was reached.
Do not mention that you read a summary or that conversation summarization occurred
Continue calling tools as necessary to complete the task.";
fn conversation_continuation_message() -> Message {
Message::user()
.with_text(CONVSERATION_CONTINUATION_TEXT)
.with_metadata(MessageMetadata::agent_only())
}
fn tool_loop_continuation_message(last_user_message: Option<&Message>) -> Message {
let prompt = if let Some(user_message) = last_user_message {
format!(
"{}\nThe last user message was:\n\n{}",
TOOL_LOOP_CONTINUATION_TEXT,
user_message.as_concat_text()
)
} else {
TOOL_LOOP_CONTINUATION_TEXT.to_string()
};
Message::user()
.with_text(prompt)
.with_metadata(MessageMetadata::agent_only())
}
/// Compact messages by summarizing them
///
/// This function performs the actual compaction by summarizing messages and updating
@@ -24,32 +70,28 @@ struct SummarizeContext {
pub async fn compact_messages(
provider: &dyn Provider,
conversation: &Conversation,
preserve_last_user_message: bool,
) -> Result<(Conversation, Vec<usize>, Option<ProviderUsage>)> {
info!("Performing message compaction");
let messages = conversation.messages();
// Check if the most recent message is a user message
let (messages_to_compact, preserved_user_message) = if let Some(last_message) = messages.last()
{
if matches!(last_message.role, rmcp::model::Role::User) {
// Remove the last user message before compaction
(&messages[..messages.len() - 1], Some(last_message.clone()))
} else if preserve_last_user_message {
// Last message is not a user message, but we want to preserve the most recent user message
// Find the most recent user message and copy it (don't remove from history)
// split the conversation into messages to be compacted, and continuation messages
let (messages_to_compact, continuation_messages) = match messages.last() {
Some(message) if is_user_submitted_text(message) => (
&messages[..messages.len() - 1],
vec![conversation_continuation_message(), message.clone()],
),
Some(message) if is_tool_response_message(message) => {
let most_recent_user_message = messages
.iter()
.rev()
.find(|msg| matches!(msg.role, rmcp::model::Role::User))
.cloned();
(messages.as_slice(), most_recent_user_message)
} else {
(messages.as_slice(), None)
.find(|msg| is_user_submitted_text(msg));
(
&messages[..messages.len() - 1],
vec![tool_loop_continuation_message(most_recent_user_message)],
)
}
} else {
(messages.as_slice(), None)
_ => (messages.as_ref(), vec![]), // we are compacting an empty conversation
};
let summary = do_compact(provider, messages_to_compact).await?;
@@ -91,22 +133,10 @@ pub async fn compact_messages(
final_messages.push(summary_msg);
final_token_counts.push(summary_tokens);
// Add an assistant message to continue the conversation (agent_visible=true, user_visible=false)
let assistant_message = Message::assistant()
.with_text(
"The previous message contains a summary that was prepared because a context limit was reached.
Do not mention that you read a summary or that conversation summarization occurred
Just continue the conversation naturally based on the summarized context"
)
.with_metadata(MessageMetadata::agent_only());
let assistant_message_tokens: usize = 0; // Not counted since it's for agent context only
final_messages.push(assistant_message);
final_token_counts.push(assistant_message_tokens);
// Add back the preserved user message if it exists
if let Some(user_message) = preserved_user_message {
final_messages.push(user_message);
}
final_messages.extend(continuation_messages.into_iter());
Ok((
Conversation::new_unvalidated(final_messages),
@@ -357,9 +387,7 @@ mod tests {
let conversation = Conversation::new_unvalidated(basic_conversation);
let (compacted_conversation, _token_counts, _usage) =
compact_messages(&provider, &conversation, true)
.await
.unwrap();
compact_messages(&provider, &conversation).await.unwrap();
let agent_conversation = compacted_conversation.agent_visible_messages();