From 9d915ca8cab9eeb88d06930b2be3813367d57e04 Mon Sep 17 00:00:00 2001 From: Eilen Shin <136898293+Eilen6316@users.noreply.github.com> Date: Thu, 30 Jul 2026 07:21:55 +0800 Subject: [PATCH] fix(agent): route subagents by net benefit (#4384) * fix(agent): route subagents by net benefit * fix(agent): refine subagent routing boundaries * fix(agent): clarify routing limits and batches * fix(agent): handle single-subagent routing --- README.md | 6 +- README_zh.md | 6 +- backend/AGENTS.md | 1 + .../deerflow/agents/lead_agent/prompt.py | 239 ++++++++---------- .../deerflow/subagents/builtins/bash_agent.py | 12 +- .../subagents/builtins/general_purpose.py | 13 +- .../deerflow/tools/builtins/task_tool.py | 40 ++- backend/tests/test_lead_agent_prompt.py | 11 +- .../tests/test_subagent_prompt_security.py | 2 +- backend/tests/test_subagent_routing_prompt.py | 100 ++++++++ 10 files changed, 268 insertions(+), 162 deletions(-) create mode 100644 backend/tests/test_subagent_routing_prompt.py diff --git a/README.md b/README.md index 042d671b4..eb1827822 100644 --- a/README.md +++ b/README.md @@ -888,11 +888,11 @@ Use `/compact` in the Web UI composer to summarize older context for the current ### Sub-Agents -Complex tasks rarely fit in a single pass. DeerFlow decomposes them. +Sub-agents are an optimization, not the default response to a complex request. -The lead agent can spawn sub-agents on the fly — each with its own scoped context, tools, and termination conditions. Sub-agents run in parallel when possible, report back structured results, and the lead agent synthesizes everything into a coherent output. Their configured skills are resolved from the same user-scoped catalog as the lead agent, so user-owned custom skills remain available without exposing another user's version. Their internal AI and tool messages stay scoped to the delegated graph instead of entering the parent chat stream. Reloaded thread history enforces the same boundary: callback-captured sub-agent AI responses remain available in run-event diagnostics but are excluded from the parent transcript, while the parent `task` result remains attached to its subtask card. Long-running sub-agents compact older history when summarization is enabled and re-inject the summary as guarded, hidden durable context before continuing, so recent assistant/tool activity remains grounded in the task. Provider/model request failures are reported as failed sub-agent tasks rather than successful results, so the lead agent and Web UI can react to them correctly. Collapsed sub-agent cards show the effective model and, when the provider returns usage metadata, a cumulative token total that updates after each completed sub-agent LLM call and persists after a reload. When token usage tracking is enabled, completed sub-agent usage is also attributed back to the dispatching step. +The lead agent can spawn sub-agents on the fly — each with its own scoped context, tools, and termination conditions — when delegation has clear net benefit from real parallel latency, specialist capability, or context isolation. It keeps interdependent scopes and overlapping side effects out of parallel dispatch; a bounded sequential chain can still run in one sub-agent when specialist or context-isolation benefit clearly wins. The lead uses the fewest useful sub-agents and re-evaluates later batches instead of fanning out solely because a task is large or multi-step. Sub-agents report back structured results, and the lead agent verifies and synthesizes them into a coherent output. Their configured skills are resolved from the same user-scoped catalog as the lead agent, so user-owned custom skills remain available without exposing another user's version. Their internal AI and tool messages stay scoped to the delegated graph instead of entering the parent chat stream. Reloaded thread history enforces the same boundary: callback-captured sub-agent AI responses remain available in run-event diagnostics but are excluded from the parent transcript, while the parent `task` result remains attached to its subtask card. Long-running sub-agents compact older history when summarization is enabled and re-inject the summary as guarded, hidden durable context before continuing, so recent assistant/tool activity remains grounded in the task. Provider/model request failures are reported as failed sub-agent tasks rather than successful results, so the lead agent and Web UI can react to them correctly. Collapsed sub-agent cards show the effective model and, when the provider returns usage metadata, a cumulative token total that updates after each completed sub-agent LLM call and persists after a reload. When token usage tracking is enabled, completed sub-agent usage is also attributed back to the dispatching step. -This is how DeerFlow handles tasks that take minutes to hours: a research task might fan out into a dozen sub-agents, each exploring a different angle, then converge into a single report — or a website — or a slide deck with generated visuals. One harness, many hands. +For example, independent read-only research can run concurrently when the wall-clock savings outweigh duplicated discovery and synthesis cost, while a repository refactor with shared files and sequential test feedback remains with the lead agent. When `max_concurrent_subagents` is `1`, parallel and multi-batch routing guidance is disabled; delegation remains available only for material specialist or context-isolation benefit. ### Sandbox & File System diff --git a/README_zh.md b/README_zh.md index ddd57ca88..29be95063 100644 --- a/README_zh.md +++ b/README_zh.md @@ -617,11 +617,11 @@ Web UI 会在输入框上方展示当前激活的 goal。同样的命令在 TUI ### Sub-Agents -复杂任务通常不可能一次完成,DeerFlow 会先拆解,再执行。 +Sub-agent 是一种执行优化,而不是遇到复杂任务时的默认选择。 -lead agent 可以按需动态拉起 sub-agents。每个 sub-agent 都有自己独立的上下文、工具和终止条件。只要条件允许,它们就会并行运行,返回结构化结果,最后再由 lead agent 汇总成一份完整输出。 +lead agent 只会在委派具有明确净收益时动态拉起 sub-agents,例如真正缩短耗时的并行工作、专业能力收益或上下文隔离收益。存在跨 Agent 依赖或重叠副作用的工作不会并行分派;当专业能力或上下文隔离收益明显占优时,一条有界的顺序任务链仍可交给一个 sub-agent 完成。lead agent 会使用能取得收益的最少 sub-agents,并在每一批完成后重新评估,而不会仅仅因为任务规模大或步骤多就继续拆分。每个 sub-agent 都有自己独立的上下文、工具和终止条件,返回结构化结果后由 lead agent 验证并汇总成完整输出。 -这也是 DeerFlow 能处理从几分钟到几小时任务的原因。比如一个研究任务,可以拆成十几个 sub-agents,分别探索不同方向,最后合并成一份报告,或者一个网站,或者一套带生成视觉内容的演示文稿。一个 harness,多路并行。 +例如,彼此独立的只读研究可以在并行节省的时间明显高于重复检索和结果合并成本时并发执行;而会修改相同文件、依赖连续测试反馈的仓库重构则由 lead agent 直接完成。当 `max_concurrent_subagents` 为 `1` 时,提示词会关闭并行和多批次路由指导,仅在专业能力或上下文隔离具有明确收益时保留委派。 ### Sandbox 与文件系统 diff --git a/backend/AGENTS.md b/backend/AGENTS.md index cf45bc219..85fbee4c8 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -683,6 +683,7 @@ that cannot tell sibling branches apart. ### Subagent System (`packages/harness/deerflow/subagents/`) **Built-in Agents**: `general-purpose` (all tools except `task`) and `bash` (command specialist) +**Benefit-based routing policy**: Enabling subagents exposes delegation as an optimization, not a default response to complexity. The lead prompt defaults to direct execution and permits `task` only when parallel latency, specialist capability, or context-isolation benefit clearly exceeds startup, duplicate-discovery, synthesis, state-conflict, and side-effect costs. Inter-agent output dependencies and overlapping mutable state are hard vetoes for parallel dispatch, while duplicate discovery and a cheap direct path remain costs rather than categorical vetoes; a bounded sequential chain may run in one subagent when specialist or context-isolation benefit clearly wins. Parallel scopes must be independent and non-overlapping, the lead uses the fewest useful subagents, and every later batch is re-evaluated while retaining any within-batch parallel benefit. When the enforced per-response limit is 1, the rendered prompt removes parallel and multi-batch benefit guidance and permits delegation only for material specialist or context-isolation benefit. Keep this policy aligned across `lead_agent/prompt.py`, the `task` tool description, and both built-in role descriptions; routing regressions are pinned in `tests/test_subagent_routing_prompt.py`, `tests/test_subagent_prompt_security.py`, and `tests/test_lead_agent_prompt.py`. **User-scoped Skills**: Subagents resolve their configured skills through `get_or_new_user_skill_storage(user_id)` using the parent runtime identity, with `DEFAULT_USER_ID` only when no identity is available. This keeps custom-skill shadowing and visibility aligned with the lead agent instead of reading the global-only catalog. **Execution**: Dual thread pool - `_scheduler_pool` (3 workers) + `_execution_pool` (3 workers) **Concurrency and total delegation cap**: `MAX_CONCURRENT_SUBAGENTS = 3` is enforced by `SubagentLimitMiddleware` (truncates excess tool calls in `after_model`; runtime `max_concurrent_subagents` is clamped to 1-4). The same middleware also enforces `subagents.max_total_per_run` (default 6, config schema 1-50, runtime override `max_total_subagents` clamped to the same range) against current-run entries in the durable delegation ledger, so a long lead-agent run cannot bypass concurrency limits by launching repeated legal-sized batches at each planning checkpoint, but historical delegations from previous runs in the same thread do not consume the new run's budget. The lead-agent prompt uses the same clamped values, so model-visible limits match enforcement. Gateway `run_agent()` and embedded `DeerFlowClient.stream()` both provide a per-invocation `run_id` in runtime context; `DeerFlowClient.stream()` also tags its input `HumanMessage` with that same id so durable-context capture can identify the current request boundary. Gateway resume paths may not append a new `HumanMessage`, so the worker also exposes the pre-run checkpoint's message ids in runtime context; durable-context capture uses that as the current-run boundary and never re-tags older task calls as the resumed run. When no delegation slots remain, task calls are stripped, provider raw tool-call metadata is synced, `finish_reason` is forced to `stop`, and a visible "subagent delegation limit" note is appended so the agent can synthesize already-collected results. Default subagent timeout `subagents.timeout_seconds=1800` (30 min) and built-in `general-purpose` `max_turns=150` (raised from 100/15-min so deep-research subtasks stop hitting `GraphRecursionError` out of the box) diff --git a/backend/packages/harness/deerflow/agents/lead_agent/prompt.py b/backend/packages/harness/deerflow/agents/lead_agent/prompt.py index 3781fee38..235f6461f 100644 --- a/backend/packages/harness/deerflow/agents/lead_agent/prompt.py +++ b/backend/packages/harness/deerflow/agents/lead_agent/prompt.py @@ -305,11 +305,13 @@ def _build_available_subagents_description(available_names: list[str], bash_avai Mirrors Codex's pattern where agent_type_description is dynamically generated from all registered roles, so the LLM knows about every available type. """ - # Built-in descriptions (kept for backward compatibility with existing prompt quality) + # Compact model-visible descriptions for the built-in roles. builtin_descriptions = { - "general-purpose": "For ANY non-trivial task - web research, code exploration, file operations, analysis, etc.", + "general-purpose": "For bounded work with clear delegation benefit from specialist capability, context isolation, or independent parallel execution.", "bash": ( - "For command execution (git, build, test, deploy operations)" if bash_available else "Not available in the current sandbox configuration. Use direct file/web tools or switch to AioSandboxProvider for isolated shell access." + "For bounded shell workflows with clear context-isolation or independent-parallel benefit. Routine git, build, test, or deploy operations are not sufficient reason to delegate." + if bash_available + else "Not available in the current sandbox configuration. Use direct file/web tools or switch to AioSandboxProvider for isolated shell access." ), } @@ -361,140 +363,113 @@ def _build_subagent_section( available_subagents = _build_available_subagents_description(available_names, bash_available, app_config=app_config) direct_tool_examples = "bash, ls, read_file, web_search, etc." if bash_available else "ls, read_file, web_search, etc." direct_execution_example = ( - '# User asks: "Run the tests"\n# Thinking: Cannot decompose into parallel sub-tasks\n# → Execute directly\n\nbash("npm test") # Direct execution, not task()' + '# User asks: "Run the tests"\n# Thinking: Direct bash is cheaper than delegation\n# → Execute directly\n\nbash("npm test") # Direct execution, not task()' if bash_available else '# User asks: "Read the README"\n# Thinking: Single straightforward file read\n# → Execute directly\n\nread_file("/mnt/user-data/workspace/README.md") # Direct execution, not task()' ) + if n == 1: + expected_benefit = "specialist capability + context isolation" + parallel_dispatch_guidance = "" + valid_benefits = """- **Specialist capability**: A subagent has tools, skills, a model, or domain instructions that materially improve the result. +- **Context isolation**: A bounded, unusually context-heavy investigation would otherwise displace important lead-agent context. + +With a per-response limit of 1, delegate only for material specialist or context-isolation benefit. Parallel dispatch cannot reduce wall-clock latency in this configuration.""" + limit_action_guidance = """- When the per-response limit is reached, verify and synthesize the returned result or continue directly.""" + followup_guidance = """- After any delegated result, re-evaluate whether the remaining work still has specialist or context-isolation benefit. Do not chain delegations merely to work around the per-response limit.""" + workflow = """1. Establish the cheapest credible direct-execution path. +2. Include all negative signals in expected cost. +3. Compare specialist or context-isolation benefit with all listed costs. +4. If delegation wins clearly, give the single subagent a bounded scope, relevant known context and paths, an expected output, and explicit side-effect ownership. +5. Launch at most 1 call and stay within the remaining run allowance. +6. Verify and synthesize the returned result against primary evidence.""" + examples = """- Refactor authentication implementation and its tests directly when analysis, edits, and test feedback share files or depend on one another. Complexity alone does not justify delegation. +- Use one specialized subagent only when its configured capability provides material benefit unavailable on the direct path. +- Use one subagent for a bounded, unusually context-heavy investigation only when preserving lead-agent context clearly outweighs delegation and synthesis cost. +- Run a routine test, build, or git command directly. Use one Bash subagent only when a bounded shell workflow has material context-isolation benefit.""" + multi_batch_example = "" + else: + expected_benefit = "parallel wall-clock savings + specialist capability + context isolation" + parallel_dispatch_guidance = """**Hard vetoes for parallel dispatch - do not launch these scopes concurrently:** +- **Inter-agent dependencies**: One delegated task needs another delegated task's result. Keep the dependency chain together instead of splitting it across parallel subagents. +- **Unsafe shared state**: Tasks may touch overlapping files, shared mutable state, or external side effects without disjoint ownership. + +A bounded sequential chain may still be delegated to one subagent when specialist capability or context isolation clearly outweighs delegation overhead. +""" + valid_benefits = """- **Parallel latency**: Two or more independent, non-overlapping tasks can run concurrently and materially reduce wall-clock time. +- **Specialist capability**: A subagent has tools, skills, a model, or domain instructions that materially improve the result. +- **Context isolation**: A bounded, unusually context-heavy investigation would otherwise displace important lead-agent context. + +A single subagent is justified only by material specialist or context-isolation benefit. Parallelism requires independent scopes with no output dependency. **Use the fewest subagents needed** to realize the benefit.""" + limit_action_guidance = """- Never start a batch that would exceed either limit. When a limit is reached, synthesize existing results or continue directly.""" + followup_guidance = ( + "- **Re-evaluate the remaining work after every batch.** Later batches cannot overlap earlier batches, but can still deliver " + "material within-batch parallel savings. Recompute benefit and cost instead of automatically continuing or stopping." + ) + workflow = f"""1. Establish the cheapest credible direct-execution path. +2. Apply the parallel-dispatch hard vetoes and include all negative signals in expected cost. +3. Compare expected benefit with all listed costs. +4. If delegation wins clearly, give each subagent a bounded, non-overlapping scope, relevant known context and paths, an expected output, and explicit side-effect ownership. +5. Launch only the smallest useful batch, up to {n} calls and the remaining run allowance. +6. Verify and synthesize returned results. Resolve contradictions against primary evidence instead of forwarding incompatible conclusions.""" + examples = """- Refactor authentication implementation and its tests: execute directly when analysis, edits, and test feedback share files or depend on one another. Complexity alone does not justify delegation. +- Compare independent providers: parallel read-only research can be worthwhile when every subagent owns one provider and returns the same bounded schema. +- Use one specialized subagent only when its configured capability provides material benefit unavailable on the direct path. +- Run a routine test, build, or git command directly. Use one Bash subagent only when a bounded shell workflow has material context-isolation benefit.""" + multi_batch_example = f"""**Multi-batch example (limit {n}):** For independent scopes that exceed the per-response limit: +- **Batch 1: launch up to {n} independent scopes.** +- Wait for the batch, then re-evaluate the remaining work and net benefit. +- **Batch 2** may launch the next scopes if it still wins; otherwise continue directly. +- **Synthesize all retained results** at the end. +""" return f""" -**🚀 SUBAGENT MODE ACTIVE - DECOMPOSE, DELEGATE, SYNTHESIZE** +## Subagent Routing: Delegate Only for Clear Net Benefit -You are running with subagent capabilities enabled. Your role is to be a **task orchestrator**: -1. **DECOMPOSE**: Break complex tasks into parallel sub-tasks -2. **DELEGATE**: Launch multiple subagents simultaneously using parallel `task` calls -3. **SYNTHESIZE**: Collect and integrate results into a coherent answer +Subagents are optional. **Default to direct execution.** Do not delegate merely because a task is complex, has many steps, produces verbose output, or touches a large repository. -**CORE PRINCIPLE: Complex tasks should be decomposed and distributed across multiple subagents for parallel execution.** +**DELEGATION CHECK (required before every `task` call):** -**⛔ HARD CONCURRENCY LIMIT: MAXIMUM {n} `task` CALLS PER RESPONSE. THIS IS NOT OPTIONAL.** -- Each response, you may include **at most {n}** `task` tool calls. Any excess calls are **silently discarded** by the system — you will lose that work. -- **Before launching subagents, you MUST count your sub-tasks in your thinking:** - - If count ≤ {n}: Launch all in this response. - - If count > {n}: **Pick the {n} most important/foundational sub-tasks for this turn.** Save the rest for the next turn. -- **HARD TOTAL LIMIT: MAXIMUM {total} `task` CALLS PER RUN. THIS IS NOT OPTIONAL.** - - Before each batch, count `task` delegations already launched for the current user request/run. - - "Work already delegated" may include older thread history; reuse it when helpful, but do not count older runs against this run's {total} total. - - Do not launch a new batch if it would exceed {total} total subagents for this run. - - When the total limit is reached, synthesize with existing results or continue directly with ordinary tools. -- **Multi-batch execution** (for >{n} sub-tasks): - - Turn 1: Launch sub-tasks 1-{n} in parallel → wait for results - - Turn 2: Launch next batch in parallel → wait for results - - ... continue until all sub-tasks are complete - - Final turn: Synthesize ALL results into a coherent answer -- **Example thinking pattern**: "I identified 6 sub-tasks. Since the limit is {n} per turn, I will launch the first {n} now, and the rest in the next turn." +Expected benefit = {expected_benefit} + +Expected cost = delegation and startup overhead + duplicate context and repository discovery + coordination and synthesis + state-conflict risk + side-effect risk + +**Delegate only when the expected benefit is clearly greater than the expected cost.** When uncertain, execute directly. + +{parallel_dispatch_guidance} + +**Delegation costs and negative signals - include these in the net-benefit comparison:** +- **Duplicate discovery**: Each subagent would need to read the same repository area or reconstruct context the lead agent already has. +- **Cheap direct path**: The lead agent can finish with a small number of tool calls or less work than delegation plus synthesis. +- **Coordination burden**: The lead agent would spend substantial work reconciling or verifying subagent results. + +**Clarify first**: Requirements that need user input must be resolved before direct execution or delegation. + +**Valid sources of delegation benefit:** +{valid_benefits} + +**HARD LIMITS - NON-NEGOTIABLE:** +- **MAXIMUM {n} `task` CALLS PER RESPONSE - NEVER emit more. VIOLATION IS A HARD ERROR.** Excess calls are discarded and their work is lost. +- **MAXIMUM {total} `task` CALLS PER RUN - NEVER exceed it. VIOLATION IS A HARD ERROR.** Count only delegations for the current user request/run; older thread history does not consume this run's allowance. +{limit_action_guidance} +{followup_guidance} **Available Subagents:** {available_subagents} -**Your Orchestration Strategy:** +**Delegation workflow:** +{workflow} -✅ **DECOMPOSE + PARALLEL EXECUTION (Preferred Approach):** +**Examples:** +{examples} -For complex queries, break them down into focused sub-tasks and execute in parallel batches (max {n} per turn): +{multi_batch_example} -**Example 1: "Why is Tencent's stock price declining?" (3 sub-tasks → 1 batch)** -→ Turn 1: Launch 3 subagents in parallel: -- Subagent 1: Recent financial reports, earnings data, and revenue trends -- Subagent 2: Negative news, controversies, and regulatory issues -- Subagent 3: Industry trends, competitor performance, and market sentiment -→ Turn 2: Synthesize results - -**Example 2: "Compare 5 cloud providers" (5 sub-tasks → multi-batch)** -→ Turn 1: Launch {n} subagents in parallel (first batch) -→ Turn 2: Launch remaining subagents in parallel -→ Final turn: Synthesize ALL results into comprehensive comparison - -**Example 3: "Refactor the authentication system"** -→ Turn 1: Launch 3 subagents in parallel: -- Subagent 1: Analyze current auth implementation and technical debt -- Subagent 2: Research best practices and security patterns -- Subagent 3: Review related tests, documentation, and vulnerabilities -→ Turn 2: Synthesize results - -✅ **USE Parallel Subagents (max {n} per turn) when:** -- **Complex research questions**: Requires multiple information sources or perspectives -- **Multi-aspect analysis**: Task has several independent dimensions to explore -- **Large codebases**: Need to analyze different parts simultaneously -- **Comprehensive investigations**: Questions requiring thorough coverage from multiple angles - -❌ **DO NOT use subagents (execute directly) when:** -- **Task cannot be decomposed**: If you can't break it into 2+ meaningful parallel sub-tasks, execute directly -- **Ultra-simple actions**: Read one file, quick edits, single commands -- **Need immediate clarification**: Must ask user before proceeding -- **Meta conversation**: Questions about conversation history -- **Sequential dependencies**: Each step depends on previous results (do steps yourself sequentially) - -**CRITICAL WORKFLOW** (STRICTLY follow this before EVERY action): -1. **COUNT**: In your thinking, list all sub-tasks and count them explicitly: "I have N sub-tasks" -2. **PLAN BATCHES**: If N > {n}, explicitly plan which sub-tasks go in which batch: - - "Batch 1 (this turn): first {n} sub-tasks" - - "Batch 2 (next turn): next batch of sub-tasks" -3. **EXECUTE**: Launch ONLY the current batch (max {n} `task` calls). Do NOT launch sub-tasks from future batches. -4. **REPEAT**: After results return, launch the next batch. Continue until all batches complete. -5. **SYNTHESIZE**: After ALL batches are done, synthesize all results. -6. **Cannot decompose** → Execute directly using available tools ({direct_tool_examples}) - -**⛔ VIOLATION: Launching more than {n} `task` calls in a single response is a HARD ERROR. The system WILL discard excess calls and you WILL lose work. Always batch.** - -**Remember: Subagents are for parallel decomposition, not for wrapping single tasks.** - -**How It Works:** -- The task tool runs subagents asynchronously in the background -- The backend automatically polls for completion (you don't need to poll) -- The tool call will block until the subagent completes its work -- Once complete, the result is returned to you directly - -**Usage Example 1 - Single Batch (≤{n} sub-tasks):** - -```python -# User asks: "Why is Tencent's stock price declining?" -# Thinking: 3 sub-tasks → fits in 1 batch - -# Turn 1: Launch 3 subagents in parallel -task(description="Tencent financial data", prompt="...", subagent_type="general-purpose") -task(description="Tencent news & regulation", prompt="...", subagent_type="general-purpose") -task(description="Industry & market trends", prompt="...", subagent_type="general-purpose") -# All 3 run in parallel → synthesize results -``` - -**Usage Example 2 - Multiple Batches (>{n} sub-tasks):** - -```python -# User asks: "Compare AWS, Azure, GCP, Alibaba Cloud, and Oracle Cloud" -# Thinking: 5 sub-tasks → need multiple batches (max {n} per batch) - -# Turn 1: Launch first batch of {n} -task(description="AWS analysis", prompt="...", subagent_type="general-purpose") -task(description="Azure analysis", prompt="...", subagent_type="general-purpose") -task(description="GCP analysis", prompt="...", subagent_type="general-purpose") - -# Turn 2: Launch remaining batch (after first batch completes) -task(description="Alibaba Cloud analysis", prompt="...", subagent_type="general-purpose") -task(description="Oracle Cloud analysis", prompt="...", subagent_type="general-purpose") - -# Turn 3: Synthesize ALL results from both batches -``` - -**Counter-Example - Direct Execution (NO subagents):** +Otherwise execute directly using available tools ({direct_tool_examples}): ```python {direct_execution_example} ``` -**CRITICAL**: -- **Max {n} `task` calls per turn** - the system enforces this, excess calls are discarded -- Only use `task` when you can launch 2+ subagents in parallel -- Single task = No value from subagents = Execute directly -- For >{n} sub-tasks, use sequential batches of {n} across multiple turns +The `task` tool waits for the subagent and returns its result directly; no polling is needed. """ @@ -1038,22 +1013,30 @@ def apply_prompt_template( subagent_section = _build_subagent_section(n, total, app_config=app_config) if subagent_enabled else "" # Add subagent reminder to critical_reminders if enabled + reminder_benefits = "specialist capability or context isolation" if n == 1 else "real parallel latency, specialist capability, or context isolation" subagent_reminder = ( - "- **Orchestrator Mode**: You are a task orchestrator - decompose complex tasks into parallel sub-tasks. " - f"**HARD LIMITS: max {n} `task` calls per response, max {total} per run.** " - f"If >{n} sub-tasks, split into sequential batches of ≤{n} without exceeding {total} total. Synthesize after batches complete.\n" + f"- **Benefit-Based Delegation**: Default to direct execution. Use `task` only when expected benefit from {reminder_benefits} " + "clearly exceeds delegation, duplicate-discovery, synthesis, conflict, and side-effect costs. " + f"Use the fewest subagents needed. HARD LIMITS ARE NON-NEGOTIABLE: max {n} `task` calls per response, max {total} per run; excess calls are discarded and their work is lost.\n" if subagent_enabled else "" ) # Add subagent thinking guidance if enabled - subagent_thinking = ( - "- **DECOMPOSITION CHECK: Can this task be broken into 2+ parallel sub-tasks? If YES, COUNT them. " - f"If count > {n}, you MUST plan batches of ≤{n} and only launch the FIRST batch now. " - f"NEVER launch more than {n} `task` calls in one response or {total} total in this run.**\n" - if subagent_enabled - else "" - ) + if subagent_enabled and n == 1: + subagent_thinking = ( + "- **DELEGATION CHECK: Default to direct execution; complexity alone is not a reason to delegate. Before each `task` call, " + "require clear positive net benefit from specialist capability or context isolation. " + f"Never exceed {n} `task` call in one response or {total} total in this run.**\n" + ) + elif subagent_enabled: + subagent_thinking = ( + "- **DELEGATION CHECK: Default to direct execution; complexity alone is not a reason to delegate. Before each `task` call, " + "require clear positive net benefit; before parallel calls, rule out inter-agent dependencies and overlapping state or side effects. " + f"If delegating, use the fewest agents needed and never exceed {n} `task` calls in one response or {total} total in this run.**\n" + ) + else: + subagent_thinking = "" # Get skills section (deferred discovery when skill_names is provided) skills_section = get_skills_prompt_section( diff --git a/backend/packages/harness/deerflow/subagents/builtins/bash_agent.py b/backend/packages/harness/deerflow/subagents/builtins/bash_agent.py index 8ebe2cbc3..9fa937101 100644 --- a/backend/packages/harness/deerflow/subagents/builtins/bash_agent.py +++ b/backend/packages/harness/deerflow/subagents/builtins/bash_agent.py @@ -4,15 +4,15 @@ from deerflow.subagents.config import SubagentConfig BASH_AGENT_CONFIG = SubagentConfig( name="bash", - description="""Command execution specialist for running bash commands in a separate context. + description="""Command execution specialist for bounded shell workflows with clear delegation benefit. Use this subagent when: -- You need to run a series of related bash commands -- Terminal operations like git, npm, docker, etc. -- Command output is verbose and would clutter main context -- Build, test, or deployment operations +- A multi-command workflow's logs or intermediate state would materially displace lead context +- It owns an independent, non-overlapping shell workload that can run in parallel +- Keeping a justified sequential command chain in one isolated context reduces coordination cost -Do NOT use for simple single commands - use bash tool directly instead.""", +Routine git, build, test, or deploy operations are not sufficient reason to delegate. +Use the direct bash tool when delegation and synthesis cost more than the bounded workflow.""", system_prompt="""You are a bash command execution specialist. Execute the requested commands carefully and report results clearly. diff --git a/backend/packages/harness/deerflow/subagents/builtins/general_purpose.py b/backend/packages/harness/deerflow/subagents/builtins/general_purpose.py index 0a9f8e3c2..a2bc2cad0 100644 --- a/backend/packages/harness/deerflow/subagents/builtins/general_purpose.py +++ b/backend/packages/harness/deerflow/subagents/builtins/general_purpose.py @@ -4,15 +4,16 @@ from deerflow.subagents.config import SubagentConfig GENERAL_PURPOSE_CONFIG = SubagentConfig( name="general-purpose", - description="""A capable agent for complex, multi-step tasks that require both exploration and action. + description="""A capable agent for bounded exploration and action when there is clear delegation benefit. Use this subagent when: -- The task requires both exploration and modification -- Complex reasoning is needed to interpret results -- Multiple dependent steps must be executed -- The task would benefit from isolated context management +- Its specialist tools, skills, model, or instructions materially improve the result +- It owns one independent, non-overlapping part of genuinely parallel work +- A bounded, context-heavy investigation should be isolated from the lead context -Do NOT use for simple, single-step operations.""", +Do NOT use merely because work is complex or multi-step, or merely because it is sequential; +a bounded dependent chain may still be delegated when specialist or context-isolation benefit +clearly wins. Do not use when it would duplicate repository discovery or overlap side effects.""", system_prompt="""You are a general-purpose subagent working on a delegated task. Your job is to complete the task autonomously and return a clear, actionable result. diff --git a/backend/packages/harness/deerflow/tools/builtins/task_tool.py b/backend/packages/harness/deerflow/tools/builtins/task_tool.py index 3f11592ae..a9f9fdce6 100644 --- a/backend/packages/harness/deerflow/tools/builtins/task_tool.py +++ b/backend/packages/harness/deerflow/tools/builtins/task_tool.py @@ -235,20 +235,24 @@ async def task_tool( subagent_type: str, tool_call_id: Annotated[str, InjectedToolCallId], ) -> str | Command: - """Delegate a task to a specialized subagent that runs in its own context. + """Delegate a bounded task to a specialized subagent in its own context. - Subagents help you: - - Preserve context by keeping exploration and implementation separate - - Handle complex multi-step tasks autonomously - - Execute commands or operations in isolated contexts + Delegate only when expected benefit clearly exceeds delegation overhead. + Useful benefits are: + - Material wall-clock savings from independent parallel work + - Specialist tools, skills, models, or domain instructions + - Context isolation for a bounded, unusually context-heavy investigation Built-in subagent types: - - **general-purpose**: A capable agent for complex, multi-step tasks that require - both exploration and action. Use when the task requires complex reasoning, - multiple dependent steps, or would benefit from isolated context. + - **general-purpose**: A capable agent for bounded exploration and action. Use + when the assignment has clear specialist or context-isolation benefit, or is + one of several independent, non-overlapping tasks that can actually run in + parallel. - **bash**: Command execution specialist for running bash commands. This is only available when host bash is explicitly allowed or when using an isolated shell - sandbox such as `AioSandboxProvider`. + sandbox such as `AioSandboxProvider`. Use it only for a bounded shell workflow + with clear context-isolation or independent-parallel benefit. + Routine git, build, test, or deploy operations are not sufficient reason to delegate. Additional custom subagent types may be defined in config.yaml under `subagents.custom_agents`. Each custom type can have its own system prompt, @@ -256,15 +260,23 @@ async def task_tool( is provided, the error message will list all available types. When to use this tool: - - Complex tasks requiring multiple steps or tools - - Tasks that produce verbose output - - When you want to isolate context from the main conversation - - Parallel research or exploration tasks + - Independent tasks that materially reduce wall-clock time when run in parallel + - A specialist subagent provides capability unavailable on the direct path + - Bounded exploration that would otherwise displace important parent context When NOT to use this tool: - - Simple, single-step operations (use tools directly) + - Merely because a task is complex, multi-step, verbose, or touches a large repo + - Splitting dependent steps across parallel subagents; keep the chain together + and delegate it as one bounded task only when specialist or context-isolation + benefit clearly wins + - Parallel work with overlapping files, shared mutable state, or external side effects - Tasks requiring user interaction or clarification + Costs to include in the delegation decision: + - Repeating the same repository discovery in multiple contexts + - Coordination, verification, and synthesis of returned results + - Any task the parent can complete more cheaply with direct tools + Args: description: A short (3-5 word) description of the task for logging/display. ALWAYS PROVIDE THIS PARAMETER FIRST. prompt: The task description for the subagent. Be specific and clear about what needs to be done. ALWAYS PROVIDE THIS PARAMETER SECOND. diff --git a/backend/tests/test_lead_agent_prompt.py b/backend/tests/test_lead_agent_prompt.py index 20150325d..5ad449d57 100644 --- a/backend/tests/test_lead_agent_prompt.py +++ b/backend/tests/test_lead_agent_prompt.py @@ -234,6 +234,10 @@ def test_apply_prompt_template_includes_subagent_total_limit(monkeypatch): assert "MAXIMUM 3 `task` CALLS PER RESPONSE" in prompt assert "MAXIMUM 5 `task` CALLS PER RUN" in prompt + assert "Default to direct execution" in prompt + assert "DELEGATION CHECK" in prompt + assert "expected benefit from real parallel latency" in prompt + assert "HARD LIMITS ARE NON-NEGOTIABLE" in prompt def test_apply_prompt_template_clamps_subagent_limits_to_enforced_bounds(monkeypatch): @@ -304,7 +308,12 @@ def test_apply_prompt_template_single_subagent_limit_matches_middleware(monkeypa ) assert f"MAXIMUM {enforced} `task` CALLS PER RESPONSE" in prompt - assert f"HARD LIMITS: max {enforced} `task` calls per response" in prompt + assert f"HARD LIMITS ARE NON-NEGOTIABLE: max {enforced} `task` calls per response" in prompt + assert "Expected benefit = specialist capability + context isolation" in prompt + assert "delegate only for material specialist or context-isolation benefit" in prompt + assert "expected benefit from real parallel latency" not in prompt + assert "material within-batch parallel savings" not in prompt + assert "Multi-batch example" not in prompt def test_build_acp_section_uses_explicit_app_config_without_global_config(monkeypatch): diff --git a/backend/tests/test_subagent_prompt_security.py b/backend/tests/test_subagent_prompt_security.py index 9d558211e..93a8047d5 100644 --- a/backend/tests/test_subagent_prompt_security.py +++ b/backend/tests/test_subagent_prompt_security.py @@ -38,7 +38,7 @@ def test_build_subagent_section_includes_bash_when_available(monkeypatch) -> Non section = prompt_module._build_subagent_section(3) - assert "For command execution (git, build, test, deploy operations)" in section + assert "Routine git, build, test, or deploy operations are not sufficient reason to delegate" in section assert 'bash("npm test")' in section assert "available tools (bash, ls, read_file, web_search, etc.)" in section diff --git a/backend/tests/test_subagent_routing_prompt.py b/backend/tests/test_subagent_routing_prompt.py new file mode 100644 index 000000000..954df4dfe --- /dev/null +++ b/backend/tests/test_subagent_routing_prompt.py @@ -0,0 +1,100 @@ +"""Prompt-contract tests for benefit-based subagent routing.""" + +from deerflow.agents.lead_agent import prompt as prompt_module +from deerflow.subagents.builtins.bash_agent import BASH_AGENT_CONFIG +from deerflow.subagents.builtins.general_purpose import GENERAL_PURPOSE_CONFIG +from deerflow.tools.builtins.task_tool import task_tool + + +def _build_section(monkeypatch, names: list[str] | None = None, max_concurrent: int = 3) -> str: + monkeypatch.setattr(prompt_module, "get_available_subagent_names", lambda: names or ["general-purpose"]) + return prompt_module._build_subagent_section(max_concurrent) + + +def test_routing_requires_clear_net_benefit(monkeypatch) -> None: + section = _build_section(monkeypatch) + + assert "Default to direct execution" in section + assert "Do not delegate merely because a task is complex" in section + assert "Delegate only when the expected benefit is clearly greater than the expected cost" in section + assert "parallel wall-clock savings" in section + assert "specialist capability" in section + assert "context isolation" in section + assert "duplicate context and repository discovery" in section + assert "coordination and synthesis" in section + assert "state-conflict risk" in section + assert "side-effect risk" in section + + +def test_hard_vetoes_apply_to_parallel_dispatch_not_single_agent_chains(monkeypatch) -> None: + section = _build_section(monkeypatch) + + assert "Hard vetoes for parallel dispatch" in section + assert "Inter-agent dependencies" in section + assert "overlapping files, shared mutable state, or external side effects" in section + assert "A bounded sequential chain may still be delegated to one subagent" in section + assert "Delegation costs and negative signals" in section + assert "Duplicate discovery" in section + assert "Cheap direct path" in section + + +def test_later_batches_retain_within_batch_parallel_benefit(monkeypatch) -> None: + section = _build_section(monkeypatch) + + assert "Re-evaluate the remaining work after every batch" in section + assert "Later batches cannot overlap earlier batches" in section + assert "material within-batch parallel savings" in section + assert "Use the fewest subagents needed" in section + + +def test_hard_limit_warning_is_emphatic_and_explains_lost_work(monkeypatch) -> None: + section = _build_section(monkeypatch) + + assert "HARD LIMITS - NON-NEGOTIABLE" in section + assert "MAXIMUM 3 `task` CALLS PER RESPONSE - NEVER emit more" in section + assert "VIOLATION IS A HARD ERROR" in section + assert "Excess calls are discarded and their work is lost" in section + + +def test_multi_batch_example_preserves_reassessment_and_synthesis(monkeypatch) -> None: + section = _build_section(monkeypatch) + + assert "Multi-batch example (limit 3)" in section + assert "Batch 1: launch up to 3 independent scopes" in section + assert "Wait for the batch, then re-evaluate" in section + assert "Batch 2" in section + assert "Synthesize all retained results" in section + + +def test_single_subagent_limit_omits_parallel_batch_guidance(monkeypatch) -> None: + section = _build_section(monkeypatch, max_concurrent=1) + + assert "Expected benefit = specialist capability + context isolation" in section + assert "delegate only for material specialist or context-isolation benefit" in section + assert "Parallel dispatch cannot reduce wall-clock latency" in section + assert "parallel wall-clock savings" not in section + assert "material within-batch parallel savings" not in section + assert "Multi-batch example" not in section + assert "Compare independent providers" not in section + + +def test_general_purpose_and_task_descriptions_match_routing_policy() -> None: + tool_description = task_tool.description + role_description = GENERAL_PURPOSE_CONFIG.description + + assert "expected benefit" in tool_description + assert "independent" in tool_description + assert "Splitting dependent steps across parallel subagents" in tool_description + assert "clear delegation benefit" in role_description + assert "merely because it is sequential" in role_description + assert "bounded dependent chain may still be delegated" in role_description + + +def test_bash_descriptions_require_benefit_beyond_routine_commands(monkeypatch) -> None: + section = _build_section(monkeypatch, ["general-purpose", "bash"]) + policy = "Routine git, build, test, or deploy operations are not sufficient reason to delegate" + + assert policy in section + assert policy in task_tool.description + assert policy in BASH_AGENT_CONFIG.description + assert "Execute commands one at a time when they depend on each other" in BASH_AGENT_CONFIG.system_prompt