Conversation
There was a problem hiding this comment.
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()), |
There was a problem hiding this comment.
I think you do want to check the variants: https://docs.rs/tokio/latest/tokio/sync/enum.SetError.html#variants
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
I think the idea is that we keep all messages around but annotate them with visibility. Hmm, is this already in /cc @katzdave
There was a problem hiding this comment.
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)?; |
There was a problem hiding this comment.
can we do something other than throw away the error here? maybe at minimum a log
There was a problem hiding this comment.
why particularly here? this should never fail I think - we're just refreshing the session after updating it
There was a problem hiding this comment.
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
crates/goose/src/conversation/mod.rs
Outdated
| .0 | ||
| .iter() | ||
| .zip(&other.0) | ||
| .all(|(a, b)| a.role == b.role && a.created == b.created && a.content == b.content) |
There was a problem hiding this comment.
should we not defer to equivalence of the Messages ?
There was a problem hiding this comment.
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(), | ||
| ) |
There was a problem hiding this comment.
suggest something like
let session = SessionManager::create_session(..).await.map_err(..)?;
Redirect::permanent(..)
There was a problem hiding this comment.
hmm, also shouldn't be a permanent redirect I think
| ) | ||
| .await | ||
| .unwrap(); | ||
| Some(session.id) |
There was a problem hiding this comment.
this function should probably not panic or exit(1) but that's too much yak shaving
There was a problem hiding this comment.
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 ...
crates/goose-cli/src/session/mod.rs
Outdated
| .await | ||
| .map(|session| session.conversation.unwrap_or_default()) | ||
| .unwrap_or_else(|e| { | ||
| eprintln!("Warning: Failed to load message history: {}", e); |
There was a problem hiding this comment.
I guess this isn't new but seems weird that this would just warn and continue
There was a problem hiding this comment.
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()), |
There was a problem hiding this comment.
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!( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Would be good to test this with a few real session dirs from the wild to ensure no issues before launch
There was a problem hiding this comment.
I imported 8000 from my folder...
| .await? | ||
| .unwrap_or(0); | ||
|
|
||
| let session_id = format!("{}_{}", today, max_idx + 1); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
Looks like some conflicts but once resolved 🚢
yeah, with this type of thing, I'd rather let the conflicts vester than keep updating them |
Co-authored-by: Douwe Osinga <[email protected]> Signed-off-by: HikaruEgashira <[email protected]>
| 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() | ||
| }) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Not sure if a good patch, but this fixed the issue for me locally: #5055
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.
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]>
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]>
Use SQLite for Session manager
Don't implement your own database