diff --git a/crates/sprout-acp/README.md b/crates/sprout-acp/README.md index a869bbe33f..ecd66bd66e 100644 --- a/crates/sprout-acp/README.md +++ b/crates/sprout-acp/README.md @@ -105,7 +105,7 @@ All configuration is via environment variables (or CLI flags — every env var h | `SPROUT_ACP_AGENT_COMMAND` | no | `goose` | Agent binary to spawn. | | `SPROUT_ACP_AGENT_ARGS` | no | `acp` | Agent arguments (comma-separated). | | `SPROUT_ACP_MCP_COMMAND` | no | `sprout-mcp-server` | Path to the Sprout MCP server binary. | -| `SPROUT_ACP_IDLE_TIMEOUT` | no | `300` | Idle timeout: max seconds of silence before cancelling a turn. Resets on any agent stdout activity. | +| `SPROUT_ACP_IDLE_TIMEOUT` | no | `620` | Idle timeout: max seconds of silence before cancelling a turn. Resets on any agent stdout activity. | | `SPROUT_ACP_MAX_TURN_DURATION` | no | `3600` | Absolute wall-clock cap per turn (safety valve). | | `SPROUT_API_TOKEN` | no | — | API token (required if relay enforces token auth). | diff --git a/crates/sprout-acp/src/config.rs b/crates/sprout-acp/src/config.rs index ed354526dc..f7cf492324 100644 --- a/crates/sprout-acp/src/config.rs +++ b/crates/sprout-acp/src/config.rs @@ -13,6 +13,17 @@ use uuid::Uuid; use crate::filter::SubscriptionRule; +// ── Constants ───────────────────────────────────────────────────────────────── + +/// Default idle timeout (seconds) when neither `--idle-timeout` nor the +/// deprecated `--turn-timeout` is set. +/// +/// Sized for slow turns where the agent may go silent on its outer ACP channel +/// while running long sub-tools (e.g. a sprout-agent running another agent, or +/// codex/claude doing multi-minute single tool calls). 600s working budget + +/// 20s buffer. Override via `--idle-timeout` / `SPROUT_ACP_IDLE_TIMEOUT`. +pub(crate) const DEFAULT_IDLE_TIMEOUT_SECS: u64 = 620; + // ── Errors ──────────────────────────────────────────────────────────────────── #[derive(Debug, Error)] @@ -597,7 +608,7 @@ impl Config { }; // Resolve idle_timeout_secs with deprecation handling. - // Precedence: explicit --idle-timeout > --turn-timeout (deprecated) > default 320. + // Precedence: explicit --idle-timeout > --turn-timeout (deprecated) > `DEFAULT_IDLE_TIMEOUT_SECS`. let idle_timeout_secs = { let raw = match (args.idle_timeout, args.turn_timeout) { (Some(idle), Some(_turn)) => { @@ -615,7 +626,7 @@ impl Config { ); turn } - (None, None) => 320, // default: 20s buffer over goose's 300s turn timeout + (None, None) => DEFAULT_IDLE_TIMEOUT_SECS, }; if raw == 0 { tracing::warn!("idle timeout of 0 is invalid — using 1s minimum"); @@ -1092,7 +1103,7 @@ mod tests { agent_command: "goose".into(), agent_args: vec!["acp".into()], mcp_command: "sprout-mcp-server".into(), - idle_timeout_secs: 320, + idle_timeout_secs: DEFAULT_IDLE_TIMEOUT_SECS, max_turn_duration_secs: 3600, agents: 1, heartbeat_interval_secs: 0, @@ -1800,13 +1811,13 @@ channels = "ALL" // ── Idle timeout config precedence ───────────────────────────────────── /// Helper: resolve idle_timeout_secs using the same precedence logic as Config::from_args. - /// Precedence: explicit --idle-timeout > --turn-timeout (deprecated) > default 320. + /// Precedence: explicit --idle-timeout > --turn-timeout (deprecated) > `DEFAULT_IDLE_TIMEOUT_SECS`. fn resolve_idle_timeout(idle: Option, turn: Option) -> u64 { let raw = match (idle, turn) { (Some(idle), Some(_)) => idle, (Some(idle), None) => idle, (None, Some(turn)) => turn, - (None, None) => 320, + (None, None) => DEFAULT_IDLE_TIMEOUT_SECS, }; if raw == 0 { 1 @@ -1826,8 +1837,8 @@ channels = "ALL" } #[test] - fn idle_timeout_defaults_to_320_when_neither_set() { - assert_eq!(resolve_idle_timeout(None, None), 320); + fn idle_timeout_defaults_to_constant_when_neither_set() { + assert_eq!(resolve_idle_timeout(None, None), DEFAULT_IDLE_TIMEOUT_SECS); } #[test] @@ -1844,9 +1855,10 @@ channels = "ALL" fn test_config_summary_includes_idle_and_max_turn() { let config = test_config(SubscribeMode::Mentions); let summary = config.summary(); + let expected_idle = format!("idle_timeout={DEFAULT_IDLE_TIMEOUT_SECS}s"); assert!( - summary.contains("idle_timeout=320s"), - "summary should include idle_timeout: {summary}" + summary.contains(&expected_idle), + "summary should include {expected_idle}: {summary}" ); assert!( summary.contains("max_turn=3600s"), diff --git a/crates/sprout-acp/src/main.rs b/crates/sprout-acp/src/main.rs index 33e1944961..6ad750d672 100644 --- a/crates/sprout-acp/src/main.rs +++ b/crates/sprout-acp/src/main.rs @@ -2729,7 +2729,7 @@ mod build_mcp_servers_tests { agent_command: "goose".into(), agent_args: vec!["acp".into()], mcp_command: "sprout-mcp-server".into(), - idle_timeout_secs: 320, + idle_timeout_secs: config::DEFAULT_IDLE_TIMEOUT_SECS, max_turn_duration_secs: 3600, agents: 1, heartbeat_interval_secs: 0,