Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
15224fb
first wave refactor
katzdave Oct 16, 2025
a795429
rename conversation compacted content
katzdave Oct 16, 2025
814de07
fmt
katzdave Oct 16, 2025
af08051
undo rename
katzdave Oct 16, 2025
69b66f2
rm some comments
katzdave Oct 16, 2025
60aca5e
rm compactioncheckresult
katzdave Oct 16, 2025
d0098b3
rm autocompactresult
katzdave Oct 16, 2025
38f00fe
massive rename fun
katzdave Oct 16, 2025
86d6da7
Merge branch 'main' of github.com:block/goose into dkatz/system-notify
katzdave Oct 16, 2025
97aa4ab
continue renames
katzdave Oct 17, 2025
76252aa
Merge branch 'main' of github.com:block/goose into dkatz/system-notify
katzdave Oct 17, 2025
3689708
massive code delete
katzdave Oct 20, 2025
bcb0b6d
Merge branch 'main' of github.com:block/goose into dkatz/system-notify
katzdave Oct 20, 2025
f9dada8
clean up reply
katzdave Oct 20, 2025
c2139de
force user only on system notifications
katzdave Oct 20, 2025
b0e1dd3
update notificiaiton
katzdave Oct 20, 2025
293a696
client side compact msg
katzdave Oct 20, 2025
85d446e
fix typecheck
katzdave Oct 20, 2025
a3c5278
experimental big thinking msg change
katzdave Oct 20, 2025
8acc16c
simplify slightly
katzdave Oct 20, 2025
46e8c14
Merge branch 'main' of github.com:block/goose into dkatz/system-notify
katzdave Oct 20, 2025
e1c9b62
cli support
katzdave Oct 20, 2025
44f1674
msg string
katzdave Oct 20, 2025
2f623d4
fmt
katzdave Oct 20, 2025
ecb523a
minor cleanup
katzdave Oct 20, 2025
22db6f8
rm a couple more comments
katzdave Oct 20, 2025
9151686
setMessage cleanup in progress
katzdave Oct 20, 2025
4925dba
big change -> client streams compact message
katzdave Oct 20, 2025
dfddf3e
remove messy do_compaction abstraction
katzdave Oct 20, 2025
b82dec7
Change manual compaction command to /compact and remove state setters
katzdave Oct 20, 2025
2cb8636
reply cleanup
katzdave Oct 21, 2025
ab83a4c
minor deletes
katzdave Oct 21, 2025
3c78a6e
Merge branch 'main' of github.com:block/goose into dkatz/system-notify
katzdave Oct 21, 2025
80380bc
clippy and rename
katzdave Oct 21, 2025
b60a69f
delete manual compact routes
katzdave Oct 21, 2025
ea91c6d
Merge branch 'main' of github.com:block/goose into dkatz/system-notify
katzdave Oct 21, 2025
cb1f51c
openapi fix
katzdave Oct 21, 2025
b94efe5
lowercase
katzdave Oct 21, 2025
a8ff2ec
fixes wave 1
katzdave Oct 22, 2025
0841d8c
review pt 2
katzdave Oct 22, 2025
85aeda0
Review pt 2
katzdave Oct 22, 2025
a3130ef
resolved merge conflicts
katzdave Oct 22, 2025
e7c106f
fix clippy diff
katzdave Oct 22, 2025
159d82f
Merge branch 'main' of github.com:block/goose into dkatz/system-notify
katzdave Oct 22, 2025
d1b3f30
Merge main into dkatz/system-notify
katzdave Oct 22, 2025
f32364f
fmt
katzdave Oct 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/goose-cli/src/session/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,8 @@ pub fn message_to_markdown(message: &Message, export_all_content: bool) -> Strin
md.push_str("**Thinking:**\n");
md.push_str("> *Thinking was redacted*\n\n");
}
MessageContent::ConversationCompacted(summarization) => {
md.push_str(&format!("*{}*\n\n", summarization.msg));
MessageContent::SystemNotification(notification) => {
md.push_str(&format!("*{}*\n\n", notification.msg));
}
_ => {
md.push_str(
Expand Down
14 changes: 12 additions & 2 deletions crates/goose-cli/src/session/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,18 @@ pub fn render_message(message: &Message, debug: bool) {
println!("\n{}", style("Thinking:").dim().italic());
print_markdown("Thinking was redacted", theme);
}
MessageContent::ConversationCompacted(summarization) => {
println!("\n{}", style(&summarization.msg).yellow());
MessageContent::SystemNotification(notification) => {
use goose::conversation::message::SystemNotificationType;

match notification.notification_type {
SystemNotificationType::ThinkingMessage => {
show_thinking();
set_thinking_message(&notification.msg);
}
SystemNotificationType::InlineMessage => {
println!("\n{}", style(&notification.msg).yellow());
}
}
}
_ => {
println!("WARNING: Message content type could not be rendered");
Expand Down
11 changes: 5 additions & 6 deletions crates/goose-server/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ use goose::config::declarative_providers::{
DeclarativeProviderConfig, LoadedProvider, ProviderEngine,
};
use goose::conversation::message::{
ConversationCompacted, FrontendToolRequest, Message, MessageContent, MessageMetadata,
RedactedThinkingContent, ThinkingContent, ToolConfirmationRequest, ToolRequest, ToolResponse,
FrontendToolRequest, Message, MessageContent, MessageMetadata, RedactedThinkingContent,
SystemNotificationContent, SystemNotificationType, ThinkingContent, ToolConfirmationRequest,
ToolRequest, ToolResponse,
};

use utoipa::openapi::schema::{
Expand Down Expand Up @@ -350,7 +351,6 @@ derive_utoipa!(Icon as IconSchema);
super::routes::agent::update_router_tool_selector,
super::routes::reply::confirm_permission,
super::routes::reply::reply,
super::routes::context::manage_context,
super::routes::session::list_sessions,
super::routes::session::get_session,
super::routes::session::get_session_insights,
Expand Down Expand Up @@ -393,8 +393,6 @@ derive_utoipa!(Icon as IconSchema);
super::routes::config_management::UpdateCustomProviderRequest,
super::routes::reply::PermissionConfirmationRequest,
super::routes::reply::ChatRequest,
super::routes::context::ContextManageRequest,
super::routes::context::ContextManageResponse,
super::routes::session::ImportSessionRequest,
super::routes::session::SessionListResponse,
super::routes::session::UpdateSessionDescriptionRequest,
Expand All @@ -420,7 +418,8 @@ derive_utoipa!(Icon as IconSchema);
RedactedThinkingContent,
FrontendToolRequest,
ResourceContentsSchema,
ConversationCompacted,
SystemNotificationType,
SystemNotificationContent,
JsonObjectSchema,
RoleSchema,
ProviderMetadata,
Expand Down
68 changes: 0 additions & 68 deletions crates/goose-server/src/routes/context.rs

This file was deleted.

2 changes: 0 additions & 2 deletions crates/goose-server/src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
pub mod agent;
pub mod audio;
pub mod config_management;
pub mod context;
pub mod errors;
pub mod extension;
pub mod recipe;
Expand All @@ -23,7 +22,6 @@ pub fn configure(state: Arc<crate::state::AppState>) -> Router {
.merge(reply::routes(state.clone()))
.merge(agent::routes(state.clone()))
.merge(audio::routes(state.clone()))
.merge(context::routes(state.clone()))
.merge(extension::routes(state.clone()))
.merge(config_management::routes(state.clone()))
.merge(recipe::routes(state.clone()))
Expand Down
176 changes: 103 additions & 73 deletions crates/goose/src/agents/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,13 @@ use super::model_selector::autopilot::AutoPilot;
use super::platform_tools;
use super::tool_execution::{ToolCallResult, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DECLINED_RESPONSE};
use crate::agents::subagent_task_config::TaskConfig;
use crate::conversation::message::{Message, ToolRequest};
use crate::conversation::message::{Message, MessageContent, SystemNotificationType, ToolRequest};
use crate::session::extension_data::{EnabledExtensionsState, ExtensionState};
use crate::session::SessionManager;

const DEFAULT_MAX_TURNS: u32 = 1000;
const COMPACTION_THINKING_TEXT: &str = "goose is compacting the conversation...";
const MANUAL_COMPACT_TRIGGER: &str = "Please compact this conversation";

/// Context needed for the reply function
pub struct ReplyContext {
Expand Down Expand Up @@ -745,77 +747,99 @@ impl Agent {
session: Option<SessionConfig>,
cancel_token: Option<CancellationToken>,
) -> Result<BoxStream<'_, Result<AgentEvent>>> {
// Try to get session metadata for more accurate token counts
let session_metadata = if let Some(session_config) = &session {
SessionManager::get_session(&session_config.id, false)
.await
.ok()
} else {
None
};

let check_result = crate::context_mgmt::check_if_compaction_needed(
self,
&unfixed_conversation,
None,
session_metadata.as_ref(),
)
.await;
let is_manual_compact = unfixed_conversation.messages().last().is_some_and(|msg| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems rather convoluted; I think it is because you want the manual compaction to use the same stream as we do for the agent reply? what was wrong with the old way?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old way triggered from REST vs a stream. It just made it a lot more complicated to keep the client in the right 'thinking' state. It also sent the client's view of the messages and used that which would cause a bit of rendering jitter when we did history replaced.

Could maybe split these more, but I think it's generally fine, main differences here are messaging + whether or not we continue the stream.

msg.content.iter().any(|c| {
if let MessageContent::Text(text) = c {
text.text.trim() == MANUAL_COMPACT_TRIGGER
} else {
false
}
})
});

let (did_compact, compacted_conversation, compaction_error) = match check_result {
// TODO(dkatz): send a notification that we are starting compaction here.
Ok(true) => {
match crate::context_mgmt::compact_messages(self, &unfixed_conversation, false)
if !is_manual_compact {
let session_metadata = if let Some(session_config) = &session {
SessionManager::get_session(&session_config.id, false)
.await
{
Ok((conversation, _token_counts, _summarization_usage)) => {
(true, conversation, None)
}
Err(e) => (false, unfixed_conversation.clone(), Some(e)),
}
.ok()
} else {
None
};

let needs_auto_compact = crate::context_mgmt::check_if_compaction_needed(
self,
&unfixed_conversation,
None,
session_metadata.as_ref(),
)
.await?;

if !needs_auto_compact {
return self
.reply_internal(unfixed_conversation, session, cancel_token)
.await;
}
Ok(false) => (false, unfixed_conversation, None),
Err(e) => (false, unfixed_conversation.clone(), Some(e)),
};
}

if did_compact {
// Get threshold from config to include in message
let config = crate::config::Config::global();
let threshold = config
.get_param::<f64>("GOOSE_AUTO_COMPACT_THRESHOLD")
.unwrap_or(DEFAULT_COMPACTION_THRESHOLD);
let threshold_percentage = (threshold * 100.0) as u32;

let compaction_msg = format!(
"Exceeded auto-compact threshold of {}%. Context has been summarized and reduced.\n\n",
threshold_percentage
);
let conversation_to_compact = unfixed_conversation.clone();

Ok(Box::pin(async_stream::try_stream! {
if !is_manual_compact {
let config = crate::config::Config::global();
let threshold = config
.get_param::<f64>("GOOSE_AUTO_COMPACT_THRESHOLD")
.unwrap_or(DEFAULT_COMPACTION_THRESHOLD);
let threshold_percentage = (threshold * 100.0) as u32;

let inline_msg = format!(
"Exceeded auto-compact threshold of {}%. Performing auto-compaction...",
threshold_percentage
);

Ok(Box::pin(async_stream::try_stream! {
// TODO(Douwe): send this before we actually compact:
yield AgentEvent::Message(
Message::assistant().with_conversation_compacted(compaction_msg)
Message::assistant().with_system_notification(
SystemNotificationType::InlineMessage,
inline_msg,
)
);
yield AgentEvent::HistoryReplaced(compacted_conversation.clone());
if let Some(session_to_store) = &session {
SessionManager::replace_conversation(&session_to_store.id, &compacted_conversation).await?
}
}

yield AgentEvent::Message(
Message::assistant().with_system_notification(
SystemNotificationType::ThinkingMessage,
COMPACTION_THINKING_TEXT,
)
);

match crate::context_mgmt::compact_messages(self, &conversation_to_compact, false).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?;
}

yield AgentEvent::HistoryReplaced(compacted_conversation.clone());

let mut reply_stream = self.reply_internal(compacted_conversation, session, cancel_token).await?;
while let Some(event) = reply_stream.next().await {
yield event?;
yield AgentEvent::Message(
Message::assistant().with_system_notification(
SystemNotificationType::InlineMessage,
"Compaction complete",
)
);

if !is_manual_compact {
let mut reply_stream = self.reply_internal(compacted_conversation, session, cancel_token).await?;
while let Some(event) = reply_stream.next().await {
yield event?;
}
}
}
}))
} else if let Some(error) = compaction_error {
Ok(Box::pin(async_stream::try_stream! {
yield AgentEvent::Message(Message::assistant().with_text(
format!("Ran into this error trying to auto-compact: {error}.\n\nPlease try again or create a new session")
));
}))
} else {
self.reply_internal(compacted_conversation, session, cancel_token)
.await
}
Err(e) => {
yield AgentEvent::Message(Message::assistant().with_text(
format!("Ran into this error trying to compact: {e}.\n\nPlease try again or create a new session")
));
}
}
}))
}

/// Main reply method that handles the actual agent processing
Expand Down Expand Up @@ -1138,23 +1162,29 @@ impl Agent {
}
}
Err(ProviderError::ContextLengthExceeded(_error_msg)) => {
info!("Context length exceeded, attempting compaction");
yield AgentEvent::Message(
Message::assistant().with_system_notification(
SystemNotificationType::InlineMessage,
"Context limit reached. Compacting to continue conversation...",
)
);
yield AgentEvent::Message(
Message::assistant().with_system_notification(
SystemNotificationType::ThinkingMessage,
COMPACTION_THINKING_TEXT,
)
);

// TODO(dkatz): send a notification that we are starting compaction here.
match crate::context_mgmt::compact_messages(self, &conversation, true).await {
Ok((compacted_conversation, _token_counts, _usage)) => {
if let Some(session_to_store) = &session {
SessionManager::replace_conversation(&session_to_store.id, &compacted_conversation).await?
}

conversation = compacted_conversation;
did_recovery_compact_this_iteration = true;

yield AgentEvent::Message(
Message::assistant().with_conversation_compacted(
"Context limit reached. Conversation has been automatically compacted to continue."
)
);
yield AgentEvent::HistoryReplaced(conversation.clone());
if let Some(session_to_store) = &session {
SessionManager::replace_conversation(&session_to_store.id, &conversation).await?
}
continue;
}
Err(e) => {
Expand Down
12 changes: 3 additions & 9 deletions crates/goose/src/context_mgmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,6 @@ pub async fn compact_messages(
final_token_counts.push(0);
}

// Add the compaction marker (user_visible=true, agent_visible=false)
let compaction_marker = Message::assistant()
.with_conversation_compacted("Conversation compacted and summarized")
.with_metadata(MessageMetadata::user_only());
let compaction_marker_tokens: usize = 0; // Not counted since agent_visible=false
final_messages.push(compaction_marker);
final_token_counts.push(compaction_marker_tokens);

// Add the summary message (agent_visible=true, user_visible=false)
let summary_msg = summary_message.with_metadata(MessageMetadata::agent_only());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need this block below? I thought we had already deleted that

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assistant_message? The summary is a user message historically; I think we could make it an agent one and then we don't need this. Just don't want the conversation to end in a user message unless we're trying to continue.

// For token counting purposes, we use the output tokens (the actual summary content)
Expand Down Expand Up @@ -281,7 +273,9 @@ fn format_message_for_compacting(msg: &Message) -> String {
}
MessageContent::Thinking(thinking) => format!("thinking: {}", thinking.thinking),
MessageContent::RedactedThinking(_) => "redacted_thinking".to_string(),
MessageContent::ConversationCompacted(compact) => format!("compacted: {}", compact.msg),
MessageContent::SystemNotification(notification) => {
format!("system_notification: {}", notification.msg)
}
})
.collect();

Expand Down
Loading
Loading