-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Sessions required #5548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sessions required #5548
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the session management system to introduce a SessionType enum and simplifies the agent's reply interface. The key changes include:
- Added
SessionTypeenum with variants:User,Scheduled,SubAgent, andHiddento categorize different session types - Refactored
Agent::reply()interface to accept a singleMessageinstead of aConversation, with the conversation now loaded from session storage internally - Removed
execution_modefield fromScheduledJobandSessionConfigstructures, simplifying the scheduled job configuration - Removed
working_dirfield fromSessionConfigas the working directory is now retrieved from the Session object - Updated database schema to version 5, adding a
session_typecolumn with an index for filtering sessions by type
Reviewed Changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/goose/src/session/session_manager.rs | Adds SessionType enum, updates schema to v5, adds session_type column to sessions table with filtering in list_sessions |
| crates/goose/src/agents/agent.rs | Refactors reply() to accept Message instead of Conversation, removes execution_mode logic, simplifies session handling |
| crates/goose/src/agents/types.rs | Removes execution_mode and working_dir from SessionConfig |
| crates/goose/src/scheduler.rs | Removes execution_mode field from ScheduledJob, updates job creation to use SessionType::Scheduled |
| crates/goose/tests/test_support.rs | Removes execution_mode from test job creation, adds session_type to test session metadata |
| crates/goose/tests/private_tests.rs | Entire test file deleted (833 lines removed) |
| crates/goose/tests/agent.rs | Consolidates tests, removes provider tests, simplifies schedule tool tests using new session API |
| crates/goose-cli/src/session/mod.rs | Changes session_id from Option<String> to String, removes all optional session handling |
| crates/goose-server/src/routes/reply.rs | Updates reply call to use single message instead of conversation |
| Multiple CLI/server files | Updates SessionManager::create_session() calls to include SessionType parameter |
Comments suppressed due to low confidence (1)
crates/goose/src/agents/agent.rs:1
- Commented-out code should be removed rather than left in the codebase. If this is intentionally kept for reference during migration, add a TODO comment explaining why.
use std::collections::HashMap;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| provider: Arc<dyn Provider>, | ||
| parent_session_id: Option<String>, | ||
| parent_working_dir: PathBuf, | ||
| parent_session_id: &str, |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parent_session_id parameter changed from Option<String> to &str, which makes it required. However, the change from owned Option<String> to borrowed &str means callers can no longer pass None. Consider whether there are legitimate cases where a subagent might not have a parent session, and if so, this should remain Option<&str> or document why it's always required.
| SessionManager::add_message(&session_config.id, &user_message).await?; | ||
| let session = SessionManager::get_session(&session_config.id, true).await?; | ||
|
|
||
| let needs_auto_compact = crate::context_mgmt::check_if_compaction_needed( | ||
| self, | ||
| &unfixed_conversation, | ||
| None, | ||
| session_metadata.as_ref(), | ||
| ) | ||
| .await?; | ||
| let conversation = session | ||
| .conversation | ||
| .clone() | ||
| .ok_or_else(|| anyhow::anyhow!("Session {} has no conversation", session_config.id))?; |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reply() method now fetches the entire session (including conversation) after adding a message, which involves two database operations. This could be optimized by having SessionManager::add_message() return the updated session, or by maintaining the conversation in memory and only persisting messages incrementally.
| let session = SessionManager::create_session( | ||
| std::env::current_dir().unwrap_or_default(), | ||
| "ACP Session".to_string(), | ||
| SessionType::Hidden, | ||
| ) | ||
| .await?; |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new session is created for every prompt in the ACP handler, but the session is never cleaned up or reused. This will create a new database entry for each prompt, potentially leading to database bloat. Consider reusing the session for the lifetime of the ACP session or cleaning up Hidden sessions periodically.
| } | ||
| } else { | ||
| session_config.session_id | ||
| session_config.session_id.unwrap() |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling unwrap() on session_id will panic if it's None. The logic should either guarantee that session_id is always Some at this point, or handle the None case explicitly. Consider using expect() with a descriptive message or restructuring the code to ensure the value is always present.
| session_config.session_id.unwrap() | |
| match session_config.session_id { | |
| Some(session_id) => session_id, | |
| None => { | |
| output::render_error("No session ID provided and not resuming or creating a new session."); | |
| process::exit(1); | |
| } | |
| } |
| &self, | ||
| unfixed_conversation: Conversation, | ||
| session: Option<SessionConfig>, | ||
| user_message: Message, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic!
crates/goose/src/agents/types.rs
Outdated
| pub id: String, | ||
| /// Working directory for the session | ||
| pub working_dir: PathBuf, | ||
| //pub working_dir: PathBuf, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this supposed to be deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. let me delete it for real. the session config has a working directory and a session id which maps to a session which also has a working dir. they were the same in practice, but we should only have one source of truth
| retry_config: None, | ||
| }; | ||
|
|
||
| let user_message = match messages.last() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like we're still passing in all messages from the client + building/modifying all_messages below. Is that intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're not really
- we're still sending the entire conversation over, but only pick up the last message
- we keep track of all_messages here separately but only for telemetry. this is somewhat misguided, I think it is measuring the wrong thing and it should happen in agent.reply, not here, but this PR is big enough as is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (2)
crates/goose/tests/private_tests.rs:1
- The entire private_tests.rs file (833 lines) has been deleted. These tests covered schedule management functionality including list, create, run_now, pause, unpause, delete, kill, inspect, and sessions actions. Verify that equivalent test coverage exists elsewhere, or these features lack testing. If moving to agent.rs was intentional, ensure all test scenarios are preserved.
crates/goose/src/agents/subagent_handler.rs:1 - The TaskConfig.parent_session_id changed from Option to String, requiring a parent session. This is a breaking change that prevents standalone subagent tasks. Verify this is intentional and all callers can provide a valid parent session ID.
use crate::session::session_manager::SessionType;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| COUNT(m.id) as message_count | ||
| FROM sessions s | ||
| INNER JOIN messages m ON s.id = m.session_id | ||
| WHERE s.session_type = 'user' OR s.session_type = 'scheduled' |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The hardcoded filter excludes SubAgent and Hidden session types from list_sessions(). Consider making this configurable via a parameter to allow listing all sessions when needed for debugging or administrative purposes.
| let user_message = match messages.last() { | ||
| Some(msg) => msg, | ||
| _ => { | ||
| let _ = stream_event( | ||
| MessageEvent::Error { | ||
| error: "Reply started with empty messages".to_string(), | ||
| }, | ||
| &task_tx, | ||
| &task_cancel, | ||
| ) | ||
| .await; | ||
| return; | ||
| } | ||
| }; |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracting the last message assumes it's the user's new message, but the session already contains the full conversation history. If the client passes the entire conversation including the new message, this works. However, if messages is empty or contains only system messages, this will fail silently. The logic should validate that the extracted message is actually a new user message and not already in the session.
| SessionManager::add_message(&session_config.id, &user_message).await?; | ||
| let session = SessionManager::get_session(&session_config.id, true).await?; |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The user_message is added to the session and then immediately the full session (with conversation) is loaded. This results in two database operations where the conversation could potentially be assembled more efficiently. Consider if the message can be appended to an in-memory conversation or if this pattern creates unnecessary round-trips.
| SessionManager::add_message(&session_config.id, &user_message).await?; | |
| let session = SessionManager::get_session(&session_config.id, true).await?; | |
| // Fetch the session once | |
| let mut session = SessionManager::get_session(&session_config.id, true).await?; | |
| // Add the user message to the session's conversation in-memory | |
| if let Some(conversation) = &mut session.conversation { | |
| conversation.push(user_message.clone()); | |
| } else { | |
| return Err(anyhow::anyhow!("Session {} has no conversation", session_config.id)); | |
| } | |
| // Persist the message if needed | |
| SessionManager::add_message(&session_config.id, &user_message).await?; |
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||
| pub struct SessionConfig { | ||
| /// Unique identifier for the session | ||
| /// Identifier of the underlying Session |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The comment update from 'Unique identifier for the session' to 'Identifier of the underlying Session' loses clarity. The original comment was clearer about the field's purpose. Consider reverting to the original or improving further.
| /// Identifier of the underlying Session | |
| /// Unique identifier for the session |
* 'main' of github.com:block/goose: Sessions required (#5548) feat: add grouped extension loading notification (#5529) we should run this on main and also test open models at least via ope… (#5556) info: print location of sessions.db via goose info (#5557) chore: remove yarn usage from documentation (#5555) cli: adjust default theme to address #1905 (#5552)
* main: (85 commits) improve linux tray icon support (#5425) feat: log rotation (#5561) use app.isPackaged instead of checking for node env development (#5465) disable RPM build-ID generation to prevent package conflicts (#5563) Add Diagnostics Info to Q&A and Bug Report Templates (#5565) fix: improve server error messages to include HTTP status code (#5532) improvement: add useful error message when attempting to use unauthenticated cursor-agent (#5300) fix: unblock acp via databricks (#5562) feat: add --output-format json flag to goose run command (#5525) Sessions required (#5548) feat: add grouped extension loading notification (#5529) we should run this on main and also test open models at least via ope… (#5556) info: print location of sessions.db via goose info (#5557) chore: remove yarn usage from documentation (#5555) cli: adjust default theme to address #1905 (#5552) Manual compaction counting fix + cli cleanup (#5480) chore(deps): bump prismjs and react-syntax-highlighter in /ui/desktop (#5549) fix: remove qwen3-coder from provider/mcp smoke tests (#5551) fix: do not build unsigned desktop app bundles on every PR in ci. add manual option. (#5550) fix: update Husky prepare script to v9 format (#5522) ...
* main: (54 commits) add clippy warning for string_slice (#5422) improve linux tray icon support (#5425) feat: log rotation (#5561) use app.isPackaged instead of checking for node env development (#5465) disable RPM build-ID generation to prevent package conflicts (#5563) Add Diagnostics Info to Q&A and Bug Report Templates (#5565) fix: improve server error messages to include HTTP status code (#5532) improvement: add useful error message when attempting to use unauthenticated cursor-agent (#5300) fix: unblock acp via databricks (#5562) feat: add --output-format json flag to goose run command (#5525) Sessions required (#5548) feat: add grouped extension loading notification (#5529) we should run this on main and also test open models at least via ope… (#5556) info: print location of sessions.db via goose info (#5557) chore: remove yarn usage from documentation (#5555) cli: adjust default theme to address #1905 (#5552) Manual compaction counting fix + cli cleanup (#5480) chore(deps): bump prismjs and react-syntax-highlighter in /ui/desktop (#5549) fix: remove qwen3-coder from provider/mcp smoke tests (#5551) fix: do not build unsigned desktop app bundles on every PR in ci. add manual option. (#5550) ...
Co-authored-by: Douwe Osinga <[email protected]> Signed-off-by: fbalicchia <[email protected]>
Co-authored-by: Douwe Osinga <[email protected]> Signed-off-by: Blair Allan <[email protected]>
Summary
Makes session ids required, hides sessions of non user type and sends only the user message to the end point
also gets rid of tests that are pointless or don't actually succeed