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
59 changes: 57 additions & 2 deletions crates/goose/src/gateway/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ use crate::session::{EnabledExtensionsState, ExtensionState, Session};
use super::pairing::PairingStore;
use super::{Gateway, GatewayConfig, IncomingMessage, OutgoingMessage, PairingState, PlatformUser};

/// Conservative default cap on tool-calling loops for gateway sessions.
///
/// Chat platforms like Telegram favor short, snappy replies, so the gateway
/// keeps a stricter default than the global `GOOSE_MAX_TURNS` ceiling. Users
/// can override this through `GOOSE_GATEWAY_MAX_TURNS` (gateway-specific) or
/// `GOOSE_MAX_TURNS` (applies globally).
const DEFAULT_GATEWAY_MAX_TURNS: u32 = 5;

/// Resolve the max turns to use for a gateway session.
///
/// Precedence: `GOOSE_GATEWAY_MAX_TURNS` -> `GOOSE_MAX_TURNS` ->
/// `DEFAULT_GATEWAY_MAX_TURNS`. Extracted as a pure function so the
/// precedence rules can be unit-tested without touching the global config.
fn resolve_gateway_max_turns(gateway_override: Option<u32>, global_max_turns: Option<u32>) -> u32 {
gateway_override
.or(global_max_turns)
.unwrap_or(DEFAULT_GATEWAY_MAX_TURNS)
}

#[derive(Clone)]
pub struct GatewayHandler {
agent_manager: Arc<AgentManager>,
Expand Down Expand Up @@ -316,12 +335,20 @@ impl GatewayHandler {
// dozens of tool calls before responding. After this many
// LLM→tool round-trips the agent will stop and reply with
// whatever it has.
const GATEWAY_MAX_TURNS: u32 = 5;
//
// Honors `GOOSE_GATEWAY_MAX_TURNS` (gateway-specific override) and
// `GOOSE_MAX_TURNS` (global), falling back to a conservative default
// so the limit is configurable without editing the source.
let config = Config::global();
let max_turns = resolve_gateway_max_turns(
config.get_param::<u32>("GOOSE_GATEWAY_MAX_TURNS").ok(),
config.get_param::<u32>("GOOSE_MAX_TURNS").ok(),
);

let session_config = SessionConfig {
id: session_id.to_string(),
schedule_id: None,
max_turns: Some(GATEWAY_MAX_TURNS),
max_turns: Some(max_turns),
retry_config: None,
};

Expand Down Expand Up @@ -509,3 +536,31 @@ fn gateway_working_dir(platform: &str, user_id: &str) -> PathBuf {
.join(platform)
.join(user_id)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn defaults_when_no_overrides() {
assert_eq!(
resolve_gateway_max_turns(None, None),
DEFAULT_GATEWAY_MAX_TURNS
);
}

#[test]
fn uses_global_max_turns_when_gateway_unset() {
assert_eq!(resolve_gateway_max_turns(None, Some(42)), 42);
}

#[test]
fn gateway_override_wins_over_global() {
assert_eq!(resolve_gateway_max_turns(Some(10), Some(42)), 10);
}

#[test]
fn gateway_override_used_when_global_unset() {
assert_eq!(resolve_gateway_max_turns(Some(25), None), 25);
}
}
5 changes: 5 additions & 0 deletions documentation/docs/guides/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ These variables control how goose manages conversation sessions and context.
|----------|---------|---------|---------|
| `GOOSE_CONTEXT_STRATEGY` | Controls how goose handles context limit exceeded situations | "summarize", "truncate", "clear", "prompt" | "prompt" (interactive), "summarize" (headless) |
| `GOOSE_MAX_TURNS` | [Maximum number of turns](/docs/guides/sessions/smart-context-management#maximum-turns) allowed without user input | Integer (e.g., 10, 50, 100) | 1000 |
| `GOOSE_GATEWAY_MAX_TURNS` | Maximum number of turns for gateway sessions (e.g., Telegram). Overrides `GOOSE_MAX_TURNS` for gateway traffic only, so chat platforms can keep a stricter cap than CLI/desktop sessions. | Integer (e.g., 5, 10, 25) | Falls back to `GOOSE_MAX_TURNS`, then 5 |
| `GOOSE_SUBAGENT_MAX_TURNS` | Sets the maximum turns allowed for a [subagent](/docs/guides/context-engineering/subagents) to complete before timeout. Can be overridden by [`settings.max_turns`](/docs/guides/recipes/recipe-reference#settings) in recipes or subagent tool calls. | Integer (e.g., 25) | 25 |
| `GOOSE_MAX_BACKGROUND_TASKS` | Sets the maximum number of concurrent background [subagent](/docs/guides/context-engineering/subagents) tasks goose can run at once | Integer (e.g., 1, 5, 10) | 5 |
| `CONTEXT_FILE_NAMES` | Specifies custom filenames for [hint/context files](/docs/guides/context-engineering/using-goosehints#custom-context-files) | JSON array of strings (e.g., `["CLAUDE.md", ".goosehints"]`) | `[".goosehints"]` |
Expand Down Expand Up @@ -264,6 +265,10 @@ export GOOSE_MAX_TURNS=25
# Set a reasonable limit for production
export GOOSE_MAX_TURNS=100

# Raise the per-gateway cap without changing CLI/desktop limits
# (applies to Telegram and other gateway sessions only)
export GOOSE_GATEWAY_MAX_TURNS=15

# Customize the default subagent turn limit
# Note: This can be overridden per-recipe or per-subagent using the max_turns setting
export GOOSE_SUBAGENT_MAX_TURNS=50
Expand Down