From 53cbd97387394ca1760e9993efc3b2aa8a5ca23a Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Tue, 14 Apr 2026 11:28:43 +1000 Subject: [PATCH] feat(knowledge_review): query past sessions for cross-session patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Background review now queries the SQLite session history via search_chat_history before running the extraction. Recent sessions (up to 3, excluding current) are summarized and appended to the review prompt as context. This lets the review agent notice cross-session patterns: - 'user corrected me about X in multiple sessions' - 'this tool quirk keeps coming up' - 'user always prefers Y approach' Uses the existing ChatHistorySearch infrastructure — no new dependencies or schema changes needed. Signed-off-by: Michael Neale --- crates/goose/src/agents/agent.rs | 1 + crates/goose/src/agents/knowledge_review.rs | 75 ++++++++++++++++++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index 5da41106c5..8b16ba6936 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -1825,6 +1825,7 @@ impl Agent { super::knowledge_review::spawn_background_review( provider, Arc::clone(&self.extension_manager), + Arc::clone(&self.config.session_manager), conversation.clone(), session_config.id.clone(), working_dir.clone(), diff --git a/crates/goose/src/agents/knowledge_review.rs b/crates/goose/src/agents/knowledge_review.rs index 6db7d35b41..403ab8cadb 100644 --- a/crates/goose/src/agents/knowledge_review.rs +++ b/crates/goose/src/agents/knowledge_review.rs @@ -74,16 +74,18 @@ const FLUSH_PROMPT: &str = "[System: The session context is being compressed. Sa /// Spawn a background task to review the conversation for memory and/or skill saves. /// /// Runs AFTER the reply is delivered. The user never sees this. +#[allow(clippy::too_many_arguments)] pub fn spawn_background_review( provider: Arc, extension_manager: Arc, + session_manager: Arc, conversation: Conversation, session_id: String, working_dir: std::path::PathBuf, review_memory: bool, review_skills: bool, ) { - let prompt = if review_memory && review_skills { + let base_prompt = if review_memory && review_skills { COMBINED_REVIEW_PROMPT } else if review_skills { SKILL_REVIEW_PROMPT @@ -99,14 +101,26 @@ pub fn spawn_background_review( "memory_review" }; + let sid = session_id.clone(); tokio::spawn(async move { + // Query past sessions for cross-session patterns + let session_context = build_session_context(&session_manager, &sid).await; + let prompt = if session_context.is_empty() { + base_prompt.to_string() + } else { + format!( + "{}\n\nContext from past sessions (consider saving recurring patterns):\n{}", + base_prompt, session_context + ) + }; + if let Err(e) = run_knowledge_extraction( provider.as_ref(), &extension_manager, &conversation, - &session_id, + &sid, &working_dir, - prompt, + &prompt, task_name, ReviewScope { include_memory_tools: review_memory, @@ -120,6 +134,61 @@ pub fn spawn_background_review( }); } +/// Query recent past sessions for recurring patterns to feed into the review. +/// +/// Extracts key user messages from recent sessions to help the review agent +/// notice cross-session patterns (e.g. "user always does X", "this tool quirk +/// keeps coming up"). +async fn build_session_context( + session_manager: &crate::session::SessionManager, + exclude_session_id: &str, +) -> String { + use crate::session::session_manager::SessionType; + + // Search recent sessions for user messages (broad query) + let results = session_manager + .search_chat_history( + "*", // broad search + Some(5), + None, + None, + Some(exclude_session_id.to_string()), + vec![SessionType::User], + ) + .await; + + let Ok(results) = results else { + return String::new(); + }; + + if results.results.is_empty() { + return String::new(); + } + + let mut context = String::new(); + for session in results.results.iter().take(3) { + if session.messages.is_empty() { + continue; + } + context.push_str(&format!( + "\n[Session: {} ({})]\n", + session.session_description, session.last_activity + )); + for msg in session.messages.iter().take(5) { + let preview: String = msg.content.chars().take(200).collect(); + context.push_str(&format!(" {}: {}\n", msg.role, preview)); + } + } + + // Cap at 2000 chars to avoid bloating the review prompt + if context.len() > 2000 { + context.truncate(2000); + context.push_str("\n[...truncated]"); + } + + context +} + /// Run the pre-compression flush synchronously before compaction. pub async fn flush_memories_before_compaction( provider: &dyn Provider,