From b7ba237c3656d8ddb2520fa3d7af4b26fca3ad77 Mon Sep 17 00:00:00 2001 From: hetao Date: Thu, 5 Feb 2026 20:49:02 +0800 Subject: [PATCH] feat: add configuration to enable/disable subagents Add subagents.enabled flag in config.yaml to control subagent feature: - When disabled, task/task_status tools are not loaded - When disabled, system prompt excludes subagent documentation - Default is enabled for backward compatibility Co-Authored-By: Claude Opus 4.5 --- backend/CLAUDE.md | 2 + backend/src/agents/lead_agent/prompt.py | 132 +++++++++++++----------- backend/src/config/app_config.py | 2 + backend/src/config/subagents_config.py | 9 ++ backend/src/tools/tools.py | 11 +- config.example.yaml | 12 +++ 6 files changed, 105 insertions(+), 63 deletions(-) create mode 100644 backend/src/config/subagents_config.py diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index d8c56c6..81d4238 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -268,6 +268,8 @@ Models, tools, sandbox providers, skills, and middleware settings are configured - `skills.container_path`: Container mount path (default: `/mnt/skills`) - `title`: Automatic thread title generation configuration - `summarization`: Automatic conversation summarization configuration +- `subagents`: Subagent (task tool) configuration + - `enabled`: Master switch to enable/disable subagents (boolean, default: true) - `memory`: Memory system configuration - `enabled`: Master switch (boolean) - `storage_path`: Path to memory.json file (relative to backend/) diff --git a/backend/src/agents/lead_agent/prompt.py b/backend/src/agents/lead_agent/prompt.py index be53c8d..0a6fa7b 100644 --- a/backend/src/agents/lead_agent/prompt.py +++ b/backend/src/agents/lead_agent/prompt.py @@ -2,6 +2,67 @@ from datetime import datetime from src.skills import load_skills +SUBAGENT_SECTION = """ +You can delegate tasks to specialized subagents using the `task` tool. Subagents run in isolated context and return concise results. + +**Available Subagents:** +- **general-purpose**: For complex, multi-step tasks requiring exploration and action +- **bash**: For command execution (git, build, test, deploy operations) + +**When to Use task:** +✅ USE task when: +- Output would be verbose (tests, builds, large file searches) +- Multiple independent tasks can run in parallel (use `run_in_background=True`) +- Exploring/researching codebase extensively with many file reads + +❌ DON'T use task when: +- Task is straightforward → execute directly for better user visibility +- Need user clarification → subagents cannot ask questions +- Need real-time feedback → main agent has streaming, subagents don't +- Task depends on conversation context → subagents have isolated context + +**Background Task Protocol (CRITICAL):** +When you use `run_in_background=True`: +1. **You MUST wait for completion** - Background tasks run asynchronously, but you are responsible for getting results +2. **Poll task status** - Call `task_status(task_id)` to check progress +3. **Check status field** - Status can be: `pending`, `running`, `completed`, `failed` +4. **Retry if still running** - If status is `pending` or `running`, wait a moment and call `task_status` again +5. **Report results to user** - Only respond to user AFTER getting the final result + +**STRICT RULE: Never end the conversation with background tasks still running. You MUST retrieve all results first.** + +**Usage:** +```python +# Synchronous - wait for result (preferred for most cases) +task( + subagent_type="general-purpose", + prompt="Search all Python files for deprecated API usage and list them", + description="Find deprecated APIs" +) + +# Background - run in parallel (MUST poll for results) +task_id = task( + subagent_type="bash", + prompt="Run npm install && npm run build && npm test", + description="Build and test frontend", + run_in_background=True +) +# Extract task_id from the response +# Then IMMEDIATELY start polling: +while True: + status_result = task_status(task_id) + if "Status: completed" in status_result or "Status: failed" in status_result: + # Task finished, use the result + break + # Task still running, continue polling + +# Multiple parallel tasks +task_id_1 = task(..., run_in_background=True) +task_id_2 = task(..., run_in_background=True) +# Poll BOTH tasks until complete before responding to user +``` +""" + SYSTEM_PROMPT_TEMPLATE = """ You are DeerFlow 2.0, an open-source super agent. @@ -103,66 +164,7 @@ You have access to skills that provide optimized workflows for specific tasks. E - -You can delegate tasks to specialized subagents using the `task` tool. Subagents run in isolated context and return concise results. - -**Available Subagents:** -- **general-purpose**: For complex, multi-step tasks requiring exploration and action -- **bash**: For command execution (git, build, test, deploy operations) - -**When to Use task:** -✅ USE task when: -- Output would be verbose (tests, builds, large file searches) -- Multiple independent tasks can run in parallel (use `run_in_background=True`) -- Exploring/researching codebase extensively with many file reads - -❌ DON'T use task when: -- Task is straightforward → execute directly for better user visibility -- Need user clarification → subagents cannot ask questions -- Need real-time feedback → main agent has streaming, subagents don't -- Task depends on conversation context → subagents have isolated context - -**Background Task Protocol (CRITICAL):** -When you use `run_in_background=True`: -1. **You MUST wait for completion** - Background tasks run asynchronously, but you are responsible for getting results -2. **Poll task status** - Call `task_status(task_id)` to check progress -3. **Check status field** - Status can be: `pending`, `running`, `completed`, `failed` -4. **Retry if still running** - If status is `pending` or `running`, wait a moment and call `task_status` again -5. **Report results to user** - Only respond to user AFTER getting the final result - -**STRICT RULE: Never end the conversation with background tasks still running. You MUST retrieve all results first.** - -**Usage:** -```python -# Synchronous - wait for result (preferred for most cases) -task( - subagent_type="general-purpose", - prompt="Search all Python files for deprecated API usage and list them", - description="Find deprecated APIs" -) - -# Background - run in parallel (MUST poll for results) -task_id = task( - subagent_type="bash", - prompt="Run npm install && npm run build && npm test", - description="Build and test frontend", - run_in_background=True -) -# Extract task_id from the response -# Then IMMEDIATELY start polling: -while True: - status_result = task_status(task_id) - if "Status: completed" in status_result or "Status: failed" in status_result: - # Task finished, use the result - break - # Task still running, continue polling - -# Multiple parallel tasks -task_id_1 = task(..., run_in_background=True) -task_id_2 = task(..., run_in_background=True) -# Poll BOTH tasks until complete before responding to user -``` - +{subagent_section} - User uploads: `/mnt/user-data/uploads` - Files uploaded by the user (automatically listed in context) @@ -260,15 +262,17 @@ def apply_prompt_template() -> str: # Load only enabled skills skills = load_skills(enabled_only=True) - # Get skills container path from config + # Get config try: from src.config import get_app_config config = get_app_config() container_base_path = config.skills.container_path + subagents_enabled = config.subagents.enabled except Exception: - # Fallback to default if config fails + # Fallback to defaults if config fails container_base_path = "/mnt/skills" + subagents_enabled = True # Generate skills list XML with paths (path points to SKILL.md file) if skills: @@ -282,11 +286,15 @@ def apply_prompt_template() -> str: # Get memory context memory_context = _get_memory_context() + # Include subagent section only if enabled + subagent_section = SUBAGENT_SECTION if subagents_enabled else "" + # Format the prompt with dynamic skills and memory prompt = SYSTEM_PROMPT_TEMPLATE.format( skills_list=skills_list, skills_base_path=container_base_path, memory_context=memory_context, + subagent_section=subagent_section, ) return prompt + f"\n{datetime.now().strftime('%Y-%m-%d, %A')}" diff --git a/backend/src/config/app_config.py b/backend/src/config/app_config.py index d3886ea..a829659 100644 --- a/backend/src/config/app_config.py +++ b/backend/src/config/app_config.py @@ -11,6 +11,7 @@ from src.config.memory_config import load_memory_config_from_dict from src.config.model_config import ModelConfig from src.config.sandbox_config import SandboxConfig from src.config.skills_config import SkillsConfig +from src.config.subagents_config import SubagentsConfig from src.config.summarization_config import load_summarization_config_from_dict from src.config.title_config import load_title_config_from_dict from src.config.tool_config import ToolConfig, ToolGroupConfig @@ -26,6 +27,7 @@ class AppConfig(BaseModel): tools: list[ToolConfig] = Field(default_factory=list, description="Available tools") tool_groups: list[ToolGroupConfig] = Field(default_factory=list, description="Available tool groups") skills: SkillsConfig = Field(default_factory=SkillsConfig, description="Skills configuration") + subagents: SubagentsConfig = Field(default_factory=SubagentsConfig, description="Subagents configuration") extensions: ExtensionsConfig = Field(default_factory=ExtensionsConfig, description="Extensions configuration (MCP servers and skills state)") model_config = ConfigDict(extra="allow", frozen=False) diff --git a/backend/src/config/subagents_config.py b/backend/src/config/subagents_config.py new file mode 100644 index 0000000..2ccb47d --- /dev/null +++ b/backend/src/config/subagents_config.py @@ -0,0 +1,9 @@ +"""Configuration for subagents.""" + +from pydantic import BaseModel, Field + + +class SubagentsConfig(BaseModel): + """Configuration for subagents feature.""" + + enabled: bool = Field(default=True, description="Whether subagents are enabled") diff --git a/backend/src/tools/tools.py b/backend/src/tools/tools.py index 6e349ad..b64e44c 100644 --- a/backend/src/tools/tools.py +++ b/backend/src/tools/tools.py @@ -11,6 +11,9 @@ logger = logging.getLogger(__name__) BUILTIN_TOOLS = [ present_file_tool, ask_clarification_tool, +] + +SUBAGENT_TOOLS = [ task_tool, task_status_tool, ] @@ -54,13 +57,19 @@ def get_available_tools(groups: list[str] | None = None, include_mcp: bool = Tru except Exception as e: logger.error(f"Failed to get cached MCP tools: {e}") - # Conditionally add view_image_tool only if the model supports vision + # Conditionally add tools based on config builtin_tools = BUILTIN_TOOLS.copy() + # Add subagent tools only if enabled + if config.subagents.enabled: + builtin_tools.extend(SUBAGENT_TOOLS) + logger.info("Including subagent tools (task, task_status)") + # If no model_name specified, use the first model (default) if model_name is None and config.models: model_name = config.models[0].name + # Add view_image_tool only if the model supports vision model_config = config.get_model_config(model_name) if model_name else None if model_config is not None and model_config.supports_vision: builtin_tools.append(view_image_tool) diff --git a/config.example.yaml b/config.example.yaml index 3c339d0..999e8f6 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -282,6 +282,18 @@ summarization: # # For more information, see: https://modelcontextprotocol.io +# ============================================================================ +# Subagents Configuration +# ============================================================================ +# Enable or disable the subagent (task tool) functionality +# Subagents allow delegating complex tasks to specialized agents + +subagents: + enabled: true # Set to false to disable subagents + +# ============================================================================ +# Memory Configuration +# ============================================================================ # Global memory mechanism # Stores user context and conversation history for personalized responses memory: