diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index 2a3d8a01..a667b271 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -67,6 +67,11 @@ pub struct StartAgentRequest { recipe_deeplink: Option, } +#[derive(Deserialize, utoipa::ToSchema)] +pub struct StopAgentRequest { + session_id: String, +} + #[derive(Deserialize, utoipa::ToSchema)] pub struct ResumeAgentRequest { session_id: String, @@ -599,6 +604,34 @@ async fn agent_remove_extension( Ok(StatusCode::OK) } +#[utoipa::path( + post, + path = "/agent/stop", + request_body = StopAgentRequest, + responses( + (status = 200, description = "Agent stopped successfully", body = String), + (status = 401, description = "Unauthorized - invalid secret key"), + (status = 404, description = "Session not found"), + (status = 500, description = "Internal server error") + ) +)] +async fn stop_agent( + State(state): State>, + Json(payload): Json, +) -> Result { + let session_id = payload.session_id; + state + .agent_manager + .remove_session(&session_id) + .await + .map_err(|e| ErrorResponse { + message: format!("Failed to stop agent for session {}: {}", session_id, e), + status: StatusCode::NOT_FOUND, + })?; + + Ok(StatusCode::OK) +} + pub fn routes(state: Arc) -> Router { Router::new() .route("/agent/start", post(start_agent)) @@ -612,5 +645,6 @@ pub fn routes(state: Arc) -> Router { .route("/agent/update_from_session", post(update_from_session)) .route("/agent/add_extension", post(agent_add_extension)) .route("/agent/remove_extension", post(agent_remove_extension)) + .route("/agent/stop", post(stop_agent)) .with_state(state) } diff --git a/crates/goose/src/config/base.rs b/crates/goose/src/config/base.rs index 0f6c14d0..ddae0fd2 100644 --- a/crates/goose/src/config/base.rs +++ b/crates/goose/src/config/base.rs @@ -807,6 +807,7 @@ config_value!(GOOSE_SEARCH_PATHS, Vec); config_value!(GOOSE_MODE, GooseMode); config_value!(GOOSE_PROVIDER, String); config_value!(GOOSE_MODEL, String); +config_value!(GOOSE_MAX_ACTIVE_AGENTS, usize); /// Load init-config.yaml from workspace root if it exists. /// This function is shared between the config recovery and the init_config endpoint. diff --git a/crates/goose/src/execution/manager.rs b/crates/goose/src/execution/manager.rs index 10456618..cef71c82 100644 --- a/crates/goose/src/execution/manager.rs +++ b/crates/goose/src/execution/manager.rs @@ -1,6 +1,7 @@ use crate::agents::extension::PlatformExtensionContext; use crate::agents::Agent; use crate::config::paths::Paths; +use crate::config::Config; use crate::scheduler::Scheduler; use crate::scheduler_trait::SchedulerTrait; use anyhow::Result; @@ -52,7 +53,10 @@ impl AgentManager { pub async fn instance() -> Result> { AGENT_MANAGER .get_or_try_init(|| async { - let manager = Self::new(Some(DEFAULT_MAX_SESSION)).await?; + let max_sessions = Config::global() + .get_goose_max_active_agents() + .unwrap_or(DEFAULT_MAX_SESSION); + let manager = Self::new(Some(max_sessions)).await?; Ok(Arc::new(manager)) }) .await