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: