Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/sprout-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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). |

Expand Down
30 changes: 21 additions & 9 deletions crates/sprout-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)) => {
Expand All @@ -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");
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<u64>, turn: Option<u64>) -> 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
Expand All @@ -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]
Expand All @@ -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"),
Expand Down
2 changes: 1 addition & 1 deletion crates/sprout-acp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading