Files
deer-flow/backend/packages/harness/deerflow/subagents/config.py
T
DanielWalnut 05be7ea688 fix(subagents): raise general-purpose max_turns to 150 and default timeout to 30min (#3610)
* fix(subagents): raise general-purpose max_turns to 150 and default timeout to 30min

Deep-research subtasks failed out of the box with GraphRecursionError (Recursion limit of 100 reached): the built-in general-purpose subagent caps at max_turns=100. Raise it to 150 and bump the default subagent timeout from 900s (15min) to 1800s (30min) so the extra turns have time to run instead of shifting the failure to a timeout. The lead agent recursion_limit (100) is unchanged; the failures are subagent-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(subagents): clarify lead recursion_limit is independent of subagent max_turns

Add comments at both lead recursion_limit=100 sites (gateway services + channel manager) explaining the lead's LangGraph super-step budget is separate from subagent depth, so the two 100s are not conflated. Comment-only, no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(subagents): clarify built-in vs custom timeout scope; pin bash max_turns in test

Review follow-ups: (1) clarify SubagentConfig docstring + global timeout field/comment that the 1800 default applies to built-in subagents (custom agents keep their own timeout_seconds); (2) pin bash.max_turns==60 in the defaults regression test so the config.example.yaml doc cannot drift; (3) rename test_default_timeout_preserved_when_no_config -> test_explicit_global_timeout_propagates_to_general_purpose since it intentionally exercises an explicit non-default 900. No runtime behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 19:55:04 +08:00

62 lines
2.3 KiB
Python

"""Subagent configuration definitions."""
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from deerflow.config.app_config import AppConfig
@dataclass
class SubagentConfig:
"""Configuration for a subagent.
Attributes:
name: Unique identifier for the subagent.
description: When Claude should delegate to this subagent.
system_prompt: The system prompt that guides the subagent's behavior.
tools: Optional list of tool names to allow. If None, inherits all tools.
disallowed_tools: Optional list of tool names to deny.
skills: Optional list of skill names to load. If None, inherits all enabled skills.
If an empty list, no skills are loaded.
model: Model to use - 'inherit' uses parent's model.
max_turns: Maximum agent turns before stopping. Built-in agents use the
value set here (general-purpose=150, bash=60) unless the global
``subagents.max_turns`` is set.
timeout_seconds: Bare fallback execution-time cap. For built-in agents the
effective limit is the global ``subagents.timeout_seconds`` (default
1800 = 30 min), layered on by the registry; this 900 only applies
when no differing global value exists.
"""
name: str
description: str
system_prompt: str | None = None
tools: list[str] | None = None
disallowed_tools: list[str] | None = field(default_factory=lambda: ["task"])
skills: list[str] | None = None
model: str = "inherit"
max_turns: int = 50
timeout_seconds: int = 900
def _default_model_name(app_config: "AppConfig") -> str:
if not app_config.models:
raise ValueError("No chat models are configured. Please configure at least one model in config.yaml.")
return app_config.models[0].name
def resolve_subagent_model_name(config: SubagentConfig, parent_model: str | None, *, app_config: "AppConfig | None" = None) -> str:
"""Resolve the effective model name a subagent should use."""
if config.model != "inherit":
return config.model
if parent_model is not None:
return parent_model
if app_config is None:
from deerflow.config import get_app_config
app_config = get_app_config()
return _default_model_name(app_config)