From 12f4ab4c1f4e1241b268fa1f87de9dd7a88d27db Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Thu, 22 Jan 2026 15:23:32 -0500 Subject: [PATCH 1/2] Strip the audience for compacting --- crates/goose-server/src/routes/session.rs | 7 ++- crates/goose/src/context_mgmt/mod.rs | 16 +++--- crates/goose/src/conversation/message.rs | 61 +++++++++++++++++++++++ crates/goose/src/providers/api_client.rs | 6 --- 4 files changed, 75 insertions(+), 15 deletions(-) diff --git a/crates/goose-server/src/routes/session.rs b/crates/goose-server/src/routes/session.rs index c214c86f4c60..148c132bf730 100644 --- a/crates/goose-server/src/routes/session.rs +++ b/crates/goose-server/src/routes/session.rs @@ -1,7 +1,7 @@ use crate::routes::errors::ErrorResponse; use crate::routes::recipe_utils::{apply_recipe_to_agent, build_recipe_with_parameter_values}; use crate::state::AppState; -use axum::extract::State; +use axum::extract::{DefaultBodyLimit, State}; use axum::routing::post; use axum::{ extract::Path, @@ -493,7 +493,10 @@ pub fn routes(state: Arc) -> Router { .route("/sessions/{session_id}", get(get_session)) .route("/sessions/{session_id}", delete(delete_session)) .route("/sessions/{session_id}/export", get(export_session)) - .route("/sessions/import", post(import_session)) + .route( + "/sessions/import", + post(import_session).layer(DefaultBodyLimit::max(25 * 1024 * 1024)), + ) .route("/sessions/insights", get(get_session_insights)) .route("/sessions/{session_id}/name", put(update_session_name)) .route( diff --git a/crates/goose/src/context_mgmt/mod.rs b/crates/goose/src/context_mgmt/mod.rs index b6f6e095ec80..5e347073d023 100644 --- a/crates/goose/src/context_mgmt/mod.rs +++ b/crates/goose/src/context_mgmt/mod.rs @@ -222,7 +222,7 @@ pub async fn check_if_compaction_needed( Ok(needs_compaction) } -fn filter_tool_responses<'a>(messages: &[&'a Message], remove_percent: u32) -> Vec<&'a Message> { +fn filter_tool_responses(messages: &[Message], remove_percent: u32) -> Vec<&Message> { fn has_tool_response(msg: &Message) -> bool { msg.content .iter() @@ -230,7 +230,7 @@ fn filter_tool_responses<'a>(messages: &[&'a Message], remove_percent: u32) -> V } if remove_percent == 0 { - return messages.to_vec(); + return messages.iter().collect(); } let tool_indices: Vec = messages @@ -241,7 +241,7 @@ fn filter_tool_responses<'a>(messages: &[&'a Message], remove_percent: u32) -> V .collect(); if tool_indices.is_empty() { - return messages.to_vec(); + return messages.iter().collect(); } let num_to_remove = ((tool_indices.len() * remove_percent as usize) / 100).max(1); @@ -268,7 +268,7 @@ fn filter_tool_responses<'a>(messages: &[&'a Message], remove_percent: u32) -> V .iter() .enumerate() .filter(|(i, _)| !indices_to_remove.contains(i)) - .map(|(_, msg)| *msg) + .map(|(_, msg)| msg) .collect() } @@ -277,11 +277,13 @@ async fn do_compact( session_id: &str, messages: &[Message], ) -> Result<(Message, ProviderUsage), anyhow::Error> { - let agent_visible_messages: Vec<&Message> = messages + let agent_visible_messages: Vec = messages .iter() .filter(|msg| msg.is_agent_visible()) + .map(|msg| msg.agent_visible_content()) .collect(); + // Try progressively removing more tool response messages from the middle to reduce context length let removal_percentages = [0, 10, 20, 50, 100]; @@ -350,7 +352,7 @@ fn format_message_for_compacting(msg: &Message) -> String { format!( "tool_request({}): {}", call.name, - serde_json::to_string_pretty(&call.arguments) + serde_json::to_string(&call.arguments) .unwrap_or_else(|_| "<>".to_string()) ) } else { @@ -397,7 +399,7 @@ fn format_message_for_compacting(msg: &Message) -> String { "frontend_tool_request: [error]".to_string() } } - MessageContent::Thinking(thinking) => format!("thinking: {}", thinking.thinking), + MessageContent::Thinking(_) => "thinking".to_string(), MessageContent::RedactedThinking(_) => "redacted_thinking".to_string(), MessageContent::SystemNotification(notification) => { format!("system_notification: {}", notification.msg) diff --git a/crates/goose/src/conversation/message.rs b/crates/goose/src/conversation/message.rs index 5e9de889834e..0531bca53a06 100644 --- a/crates/goose/src/conversation/message.rs +++ b/crates/goose/src/conversation/message.rs @@ -245,6 +245,56 @@ impl MessageContent { ) } + pub fn filter_for_audience(&self, audience: Role) -> Option { + match self { + MessageContent::Text(text) => { + if text.audience().map(|roles| roles.contains(&audience)).unwrap_or(true) { + Some(self.clone()) + } else { + None + } + } + MessageContent::Image(img) => { + if img.audience().map(|roles| roles.contains(&audience)).unwrap_or(true) { + Some(self.clone()) + } else { + None + } + } + MessageContent::ToolResponse(res) => { + let Ok(result) = &res.tool_result else { + return Some(self.clone()); + }; + + let filtered_content: Vec = result + .content + .iter() + .filter(|c| { + c.audience() + .map(|roles| roles.contains(&audience)) + .unwrap_or(true) + }) + .cloned() + .collect(); + + if filtered_content.is_empty() { + return None; + } + + Some(MessageContent::ToolResponse(ToolResponse { + id: res.id.clone(), + tool_result: Ok(CallToolResult { + content: filtered_content, + ..result.clone() + }), + metadata: res.metadata.clone(), + })) + } + MessageContent::Thinking(_) | MessageContent::RedactedThinking(_) => None, + _ => Some(self.clone()), + } + } + pub fn image, T: Into>(data: S, mime_type: T) -> Self { MessageContent::Image( RawImageContent { @@ -621,6 +671,17 @@ impl Message { format!("{:?}", self) } + pub fn agent_visible_content(&self) -> Message { + let filtered_content = self.content.iter() + .filter_map(|c| c.filter_for_audience(Role::Assistant)) + .collect(); + + Message { + content: filtered_content, + ..self.clone() + } + } + /// Create a new user message with the current timestamp pub fn user() -> Self { Message { diff --git a/crates/goose/src/providers/api_client.rs b/crates/goose/src/providers/api_client.rs index 541275285440..163257f5b6c8 100644 --- a/crates/goose/src/providers/api_client.rs +++ b/crates/goose/src/providers/api_client.rs @@ -354,12 +354,6 @@ impl<'a> ApiRequestBuilder<'a> { } pub async fn response_post(self, payload: &Value) -> Result { - // Log the JSON payload being sent to the LLM - tracing::debug!( - "LLM_REQUEST: {}", - serde_json::to_string(payload).unwrap_or_else(|_| "{}".to_string()) - ); - let request = self.send_request(|url, client| client.post(url)).await?; Ok(request.json(payload).send().await?) } From d3454aa24c4b5e0817ece0cf9d66ebea0f9ffc62 Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Thu, 22 Jan 2026 15:41:49 -0500 Subject: [PATCH 2/2] that's a wrap folks --- crates/goose/src/context_mgmt/mod.rs | 1 - crates/goose/src/conversation/message.rs | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/goose/src/context_mgmt/mod.rs b/crates/goose/src/context_mgmt/mod.rs index 5e347073d023..588086ee94a6 100644 --- a/crates/goose/src/context_mgmt/mod.rs +++ b/crates/goose/src/context_mgmt/mod.rs @@ -283,7 +283,6 @@ async fn do_compact( .map(|msg| msg.agent_visible_content()) .collect(); - // Try progressively removing more tool response messages from the middle to reduce context length let removal_percentages = [0, 10, 20, 50, 100]; diff --git a/crates/goose/src/conversation/message.rs b/crates/goose/src/conversation/message.rs index 0531bca53a06..0434e1a3c597 100644 --- a/crates/goose/src/conversation/message.rs +++ b/crates/goose/src/conversation/message.rs @@ -248,14 +248,22 @@ impl MessageContent { pub fn filter_for_audience(&self, audience: Role) -> Option { match self { MessageContent::Text(text) => { - if text.audience().map(|roles| roles.contains(&audience)).unwrap_or(true) { + if text + .audience() + .map(|roles| roles.contains(&audience)) + .unwrap_or(true) + { Some(self.clone()) } else { None } } MessageContent::Image(img) => { - if img.audience().map(|roles| roles.contains(&audience)).unwrap_or(true) { + if img + .audience() + .map(|roles| roles.contains(&audience)) + .unwrap_or(true) + { Some(self.clone()) } else { None @@ -672,7 +680,9 @@ impl Message { } pub fn agent_visible_content(&self) -> Message { - let filtered_content = self.content.iter() + let filtered_content = self + .content + .iter() .filter_map(|c| c.filter_for_audience(Role::Assistant)) .collect();