diff --git a/crates/goose/src/execution/manager.rs b/crates/goose/src/execution/manager.rs index 2b2d45a3091b..bac39df5ef09 100644 --- a/crates/goose/src/execution/manager.rs +++ b/crates/goose/src/execution/manager.rs @@ -223,6 +223,11 @@ impl AgentManager { } } extension_results = agent.load_extensions_from_session(&session).await; + if let Some(recipe) = &session.recipe { + agent + .apply_recipe_components(recipe.response.clone(), true) + .await; + } } if agent.provider().await.is_err() { @@ -763,6 +768,100 @@ mod tests { assert_eq!(agent.goose_mode().await, mode); } + #[tokio::test] + async fn test_final_output_tool_restored_after_lru_eviction() { + use crate::agents::final_output_tool::FINAL_OUTPUT_TOOL_NAME; + use crate::recipe::{Recipe, Response}; + use serde_json::json; + + let temp_dir = TempDir::new().unwrap(); + let session_manager = Arc::new(SessionManager::new(temp_dir.path().to_path_buf())); + let agent_config = AgentConfig::new( + Arc::clone(&session_manager), + PermissionManager::instance(), + None, + GooseMode::default(), + false, + GoosePlatform::GooseDesktop, + ); + let manager = AgentManager::new(agent_config, Some(1)).await.unwrap(); + + let session = session_manager + .create_session( + temp_dir.path().to_path_buf(), + "recipe-session".into(), + crate::session::SessionType::User, + GooseMode::default(), + ) + .await + .unwrap(); + + let recipe = Recipe { + version: "1.0.0".into(), + title: "Test".into(), + description: "Test recipe".into(), + response: Some(Response { + json_schema: Some(json!({ + "type": "object", + "properties": { "result": { "type": "string" } }, + "required": ["result"] + })), + }), + instructions: None, + prompt: None, + extensions: None, + settings: None, + activities: None, + author: None, + parameters: None, + sub_recipes: None, + retry: None, + }; + + session_manager + .update(&session.id) + .recipe(Some(recipe)) + .apply() + .await + .unwrap(); + + // Fill the cache (capacity 1) then evict it + let agent = manager + .get_or_create_agent(session.id.clone()) + .await + .unwrap(); + let tools = agent.list_tools(&session.id, None).await; + assert!( + tools + .iter() + .any(|t| t.name.as_ref() == FINAL_OUTPUT_TOOL_NAME), + "final_output_tool must be present on first creation" + ); + + // Evict by adding a second session + manager + .get_or_create_agent("evict-trigger".into()) + .await + .unwrap(); + assert!( + !manager.has_session(&session.id).await, + "session should be evicted" + ); + + // Recreate agent via slow path (create_agent_locked) + let restored_agent = manager + .get_or_create_agent(session.id.clone()) + .await + .unwrap(); + let tools = restored_agent.list_tools(&session.id, None).await; + assert!( + tools + .iter() + .any(|t| t.name.as_ref() == FINAL_OUTPUT_TOOL_NAME), + "final_output_tool must be restored after LRU eviction" + ); + } + #[tokio::test] async fn test_session_mode_isolation() { let temp_dir = TempDir::new().unwrap();