Skip to content

Conversation

@DOsinga
Copy link
Collaborator

@DOsinga DOsinga commented Sep 15, 2025

Use SQLite for Session manager

Don't implement your own database

@jamadeo jamadeo requested a review from Copilot September 22, 2025 13:21
Copy link
Contributor

Copilot AI left a 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 pull request implements a comprehensive migration from a file-based session storage system to an SQLite database for session management. The primary purpose is to replace the custom JSONL file-based session storage with a more robust, standardized database approach.

Key changes:

  • Replace custom JSONL session storage with SQLite database using SessionManager
  • Migrate all session-related APIs to use the new database schema
  • Remove deprecated file-based session functions and replace with database operations

Reviewed Changes

Copilot reviewed 40 out of 42 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
ui/desktop/src/sessions.ts Removes custom session management functions (updateSessionMetadata, deleteSession)
ui/desktop/src/goosed.ts Adds error logging and removes server status check for external backend
ui/desktop/src/components/sessions/SessionsInsights.tsx Updates to use new API session insights instead of custom fetch
ui/desktop/src/components/sessions/SessionListView.tsx Migrates to use API functions for session operations
ui/desktop/src/api/types.gen.ts Adds new API types for session insights and metadata operations
ui/desktop/src/api/sdk.gen.ts Adds generated API functions for session management
crates/goose/src/session/session_manager.rs New SQLite-based session manager implementation
crates/goose/src/session/legacy.rs Legacy session file reader for migration purposes
crates/goose/src/agents/agent.rs Updates agent code to use SessionManager instead of file operations
crates/goose-server/src/routes/session.rs Updates server routes to use SessionManager
crates/goose-cli/src/session/mod.rs Updates CLI session handling to use SessionManager

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

let storage = Arc::new(SessionStorage::new().await?);
match SESSION_STORAGE.set(storage.clone()) {
Ok(_) => Ok(storage),
Err(_) => Ok(SESSION_STORAGE.get().unwrap().clone()),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you do want to check the variants: https://docs.rs/tokio/latest/tokio/sync/enum.SetError.html#variants

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 LLMs said:

async fn instance() -> Result<Arc> {
let storage = Arc::new(SessionStorage::new().await?);
match SESSION_STORAGE.set(storage) {
Ok() => Ok(SESSION_STORAGE.get().unwrap().clone()),
Err(
) => Ok(SESSION_STORAGE.get().unwrap().clone()),
}
}

should do the trick, since failure means we already have the thing

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that's true for a regular OnceCell, but this is a tokio::OnceCell, which can fail to set if it's being concurrently set

If the OnceCell is empty, but some other task is currently trying to set the value, this call will fail with SetError::InitializingError.

{
error!("Failed to persist compacted messages: {}", e);
}
tracing::info!("History replaced, compacting happened in reply");
Copy link
Collaborator

Choose a reason for hiding this comment

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

not in scope here, but have we considered how compaction should work with session history? seems like we should store the both the old and compacted state of things

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think the idea is that we keep all messages around but annotate them with visibility. Hmm, is this already in /cc @katzdave

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes we have message metadata tagging messages appropriately as agent vs user visible.

I could see benefits of even more metadata tags like 'summary' being relevant.

I'm pushing this domain actively both in the UI for making the post/pre-summary messages more clear, and I have some other ideas around improving the summaries with with keeping around more user messages + messages at the end of tool call chains.


session = SessionManager::get_session(&session.id, false)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we do something other than throw away the error here? maybe at minimum a log

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

why particularly here? this should never fail I think - we're just refreshing the session after updating it

Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe we leave it for another error handling PR. I just think returning 500s without error messages isn't great because there's always an extra step when debugging

.0
.iter()
.zip(&other.0)
.all(|(a, b)| a.role == b.role && a.created == b.created && a.content == b.content)
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we not defer to equivalence of the Messages ?

@DOsinga DOsinga requested a review from Copilot September 23, 2025 21:20
Copy link
Contributor

Copilot AI left a 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 41 out of 43 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

crates/goose/src/agents/agent.rs:1

  • This TODO indicates that database updates are missing when handling interruptions. This could lead to session state inconsistencies and should be addressed.
use std::collections::HashMap;

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

match SessionManager::create_session(
std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")),
"Web session".to_string(),
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggest something like

let session = SessionManager::create_session(..).await.map_err(..)?;
Redirect::permanent(..)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

hmm, also shouldn't be a permanent redirect I think

)
.await
.unwrap();
Some(session.id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this function should probably not panic or exit(1) but that's too much yak shaving

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is the cli though so it is not the end of the world (well, for the cli it is) - but I agree since who knows who might thing it is a good idea to call this builder. pre-existing ...

.await
.map(|session| session.conversation.unwrap_or_default())
.unwrap_or_else(|e| {
eprintln!("Warning: Failed to load message history: {}", e);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this isn't new but seems weird that this would just warn and continue

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

so now you do want to panic? :)

let storage = Arc::new(SessionStorage::new().await?);
match SESSION_STORAGE.set(storage.clone()) {
Ok(_) => Ok(storage),
Err(_) => Ok(SESSION_STORAGE.get().unwrap().clone()),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that's true for a regular OnceCell, but this is a tokio::OnceCell, which can fail to set if it's being concurrently set

If the OnceCell is empty, but some other task is currently trying to set the value, this call will fail with SetError::InitializingError.

let current_version = self.get_schema_version().await?;

if current_version < CURRENT_SCHEMA_VERSION {
println!(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps we configure the logging utility so all output from these modules goes somewhere like:

/Users/alexhancock/.local/state/goose/logs/session-db

alongside the others?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

replaced the println!

println!("Creating new session database...");
let storage = Self::create(&db_path).await?;

if let Err(e) = storage.import_legacy(&session_dir).await {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would be good to test this with a few real session dirs from the wild to ensure no issues before launch

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I imported 8000 from my folder...

.await?
.unwrap_or(0);

let session_id = format!("{}_{}", today, max_idx + 1);
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 ever use the fact that the ids have these dates in them? If not, could just be an autoincrementing int in sqlite.

let modified_time = file_metadata.modified().unwrap_or(SystemTime::now());
let created_time = file_metadata
.created()
.unwrap_or_else(|_| parse_session_timestamp(session_name).unwrap_or(modified_time));
Copy link
Collaborator

Choose a reason for hiding this comment

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

were the old session names human readable title strings? or is this a different field from the metadata and called session_name in this code

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nah, we just called it that. they were date strings. description is the human readable version

s.id === sessionId
? { ...s, metadata: { ...s.metadata, description: newDescription } }
: s
s.id === sessionId ? { ...s, metadata: { ...s, description: newDescription } } : s
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 need all the props at both root and metadata of this object? it seems like how we've flattened out the data model a lot this object could become flattened as well

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oops. there is no more metadata, this is just a remnant that will set this useless property that we then don't display until we get confirmation from the server. nice catch!

@alexhancock alexhancock self-requested a review September 26, 2025 15:25
Copy link
Collaborator

@alexhancock alexhancock left a comment

Choose a reason for hiding this comment

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

Looks like some conflicts but once resolved 🚢

@DOsinga
Copy link
Collaborator Author

DOsinga commented Sep 26, 2025

Looks like some conflicts but once resolved 🚢

yeah, with this type of thing, I'd rather let the conflicts vester than keep updating them

@DOsinga DOsinga merged commit dc29288 into main Sep 26, 2025
10 checks passed
@DOsinga DOsinga deleted the session-manager branch September 26, 2025 18:56
matt-wirth added a commit to LiquidMetal-AI/goose that referenced this pull request Sep 30, 2025
* remove only-pr-labels (block#4842)

Signed-off-by: Angela Ning <[email protected]>

* Docs: Add link to Plug & Play video for Reddit MCP (block#4852)

* Fix: Token count UI doesn't re-render if it's open. (block#4822)

* Update databricks flash model (block#4836)

* Session manager (block#4648)

Co-authored-by: Douwe Osinga <[email protected]>

* Add Hacktoberfest Guides (block#4830)

Co-authored-by: taniashiba <[email protected]>

* docs: goose x Hacktoberfest 2025 Blog (block#4855)

Co-authored-by: Tania Chakraborty <[email protected]>
Co-authored-by: Angie Jones <[email protected]>

* fix: delete some flaky and not-useful tests (block#4861)

* can tell the system what shell it is using (block#4807)

* new subrecipe blog post banner (block#4862)

* docs: remove recipe generator link from next to extension search (block#4858)

* lowercase g in goose (block#4832)

* doc: file parameter recipe update (block#4594)

* Fiie input recipe ref doc (block#4869)

* Add .goosehints file to enforce lowercase branding in documentation (block#4870)

Co-authored-by: Angie Jones <[email protected]>

* fix: restoring test data and correcting name (block#4875)

* Dead code cleanup (block#4873)

Co-authored-by: Douwe Osinga <[email protected]>
Co-authored-by: Michael Neale <[email protected]>

* Fix block#4612: Return non-zero exit code when CLI session ends with error (block#4621)

Signed-off-by: jalateras <[email protected]>

* Internal MCP Crate Cleanup (block#4800)

* remove 2 redundant comments and one that lies (block#4866)

Co-authored-by: Douwe Osinga <[email protected]>

* Revert "Internal MCP Crate Cleanup (block#4800)" (block#4883)

* fix(token_counter): fix panic with GitHub Copilot (block#4632)

Signed-off-by: sings-to-bees-on-wednesdays <[email protected]>

main was broken. this seems important

* Cli web auth token (block#4456)

Signed-off-by: Evanfeenstra <[email protected]>

* make agent manager singleton (block#4880)

* docs: new multi-model section with autopilot topic (block#4864)

* docs: rename sub-recipe to subrecipe (block#4886)

* alexhancock/mcp-crate-cleanup (block#4885)

* Temporary workaround for mcp server

* Add filtering for agentVisible: false messages on streaming providers (block#4847)

* add new prompt to get all available tutorials (block#4802)

Signed-off-by: AdemolaAri <[email protected]>

* Fix for auth in extension, fix for stale keychain

* fix: path is duplicated on tool calls causing them to fail (block#4658) (block#4859)

Signed-off-by: demetrio108 <[email protected]>

* Fix: LiteLLM API key field not showing in UI configuration (block#4105)

Signed-off-by: jalateras <[email protected]>
Co-authored-by: Ebony Louis <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Jack Amadeo <[email protected]>

* fix: Windows prompt cursor positioning issue with ANSI escape sequences (block#4464)

Signed-off-by: Matt Donovan <[email protected]>
Co-authored-by: Matt Donovan <[email protected]>

* Allow better concurrent access (block#4896)

Co-authored-by: Douwe Osinga <[email protected]>

* feat(cli): add `path` & `limit` to `session list` command (block#4878)

* Added CMD+T keyboard shortcut that takes you to the Home tab (block#4541)

Signed-off-by: Guillaume Simard <[email protected]>

---------

Signed-off-by: Angela Ning <[email protected]>
Signed-off-by: jalateras <[email protected]>
Signed-off-by: Evanfeenstra <[email protected]>
Signed-off-by: AdemolaAri <[email protected]>
Signed-off-by: demetrio108 <[email protected]>
Signed-off-by: Matt Donovan <[email protected]>
Signed-off-by: Guillaume Simard <[email protected]>
Co-authored-by: Angela Ning <[email protected]>
Co-authored-by: Emma Youndtsmith <[email protected]>
Co-authored-by: David Katz <[email protected]>
Co-authored-by: Douwe Osinga <[email protected]>
Co-authored-by: Douwe Osinga <[email protected]>
Co-authored-by: Ebony Louis <[email protected]>
Co-authored-by: taniashiba <[email protected]>
Co-authored-by: taniandjerry <[email protected]>
Co-authored-by: Tania Chakraborty <[email protected]>
Co-authored-by: Angie Jones <[email protected]>
Co-authored-by: Jack Amadeo <[email protected]>
Co-authored-by: Michael Neale <[email protected]>
Co-authored-by: w. ian douglas <[email protected]>
Co-authored-by: Alex Hancock <[email protected]>
Co-authored-by: Jarrod Sibbison <[email protected]>
Co-authored-by: Rizel Scarlett <[email protected]>
Co-authored-by: Jim Alateras <[email protected]>
Co-authored-by: sings-to-bees-on-wednesdays <[email protected]>
Co-authored-by: Evan Feenstra <[email protected]>
Co-authored-by: Yingjie He <[email protected]>
Co-authored-by: dianed-square <[email protected]>
Co-authored-by: Ademola Arigbabuwo <[email protected]>
Co-authored-by: Demetrio ⚡️ <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Jack Amadeo <[email protected]>
Co-authored-by: Matt Donovan <[email protected]>
Co-authored-by: Matt Donovan <[email protected]>
Co-authored-by: Robert Mcgregor <[email protected]>
Co-authored-by: Guillaume Simard <[email protected]>
HikaruEgashira pushed a commit to HikaruEgashira/goose that referenced this pull request Oct 3, 2025
Co-authored-by: Douwe Osinga <[email protected]>
Signed-off-by: HikaruEgashira <[email protected]>
Comment on lines +131 to +138
let messages = if let Some(session_id) = &session_id {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
SessionManager::get_session(session_id, true)
.await
.map(|session| session.conversation.unwrap_or_default())
.unwrap()
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Hi, @DOsinga! Since this got merged, I started seeing this error:

thread 'main' panicked at crates/goose-cli/src/session/mod.rs:136:26:
called `Result::unwrap()` on an `Err` value: Session not found
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if a good patch, but this fixed the issue for me locally: #5055

r0x0d added a commit to r0x0d/goose that referenced this pull request Oct 7, 2025
This bug got introduced in when merging  dc29288 (block#4648).  The problem was that the code was trying to load a session id from the database that does not exist.
r0x0d added a commit to r0x0d/goose that referenced this pull request Oct 7, 2025
This bug got introduced in when merging  dc29288 (block#4648).  The problem was that the code was trying to load a session id from the database that does not exist.

Signed-off-by: Rodolfo Olivieri <[email protected]>
r0x0d added a commit to r0x0d/goose that referenced this pull request Oct 14, 2025
This bug got introduced in when merging  dc29288 (block#4648).  The problem was that the code was trying to load a session id from the database that does not exist.

Signed-off-by: Rodolfo Olivieri <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants