backend/docs/task_tool_improvements.md
The task tool has been improved to eliminate wasteful LLM polling. Previously, when using background tasks, the LLM had to repeatedly call task_status to poll for completion, causing unnecessary API requests.
run_in_background ParameterThe run_in_background parameter has been removed from the task tool. All subagent tasks now run asynchronously by default, but the tool handles completion automatically.
Before:
# LLM had to manage polling
task_id = task(
subagent_type="bash",
prompt="Run tests",
description="Run tests",
run_in_background=True
)
# Then LLM had to poll repeatedly:
while True:
status = task_status(task_id)
if completed:
break
After:
# Tool blocks until complete, polling happens in backend
result = task(
subagent_type="bash",
prompt="Run tests",
description="Run tests"
)
# Result is available immediately after the call returns
The task_tool now:
This means:
task_status from LLM ToolsThe task_status_tool is no longer exposed to the LLM. It's kept in the codebase for potential internal/debugging use, but the LLM cannot call it.
SUBAGENT_SECTION in prompt.py to remove all references to background tasks and pollingLocated in packages/harness/deerflow/tools/builtins/task_tool.py:
# Start background execution
task_id = executor.execute_async(prompt)
# Poll for task completion in backend
while True:
result = get_background_task_result(task_id)
# Check if task completed or failed
if result.status == SubagentStatus.COMPLETED:
return f"[Subagent: {subagent_type}]\n\n{result.result}"
elif result.status == SubagentStatus.FAILED:
return f"[Subagent: {subagent_type}] Task failed: {result.error}"
# Wait before next poll
time.sleep(2)
# Timeout protection (5 minutes)
if poll_count > 150:
return "Task timed out after 5 minutes"
In addition to polling timeout, subagent execution now has a built-in timeout mechanism:
Configuration (packages/harness/deerflow/subagents/config.py):
@dataclass
class SubagentConfig:
# ...
timeout_seconds: int = 300 # 5 minutes default
Thread Pool Architecture:
To avoid nested thread pools and resource waste, we use two dedicated thread pools:
Scheduler Pool (_scheduler_pool):
run_task() function that manages task lifecycleExecution Pool (_execution_pool):
execute() method that invokes the agentHow it works:
# In execute_async():
_scheduler_pool.submit(run_task) # Submit orchestration task
# In run_task():
future = _execution_pool.submit(self.execute, task) # Submit execution
exec_result = future.result(timeout=timeout_seconds) # Wait with timeout
Benefits:
Two-Level Timeout Protection:
This ensures that even if subagent execution hangs, the system won't wait indefinitely.
To verify the changes work correctly:
task_status calls are madeExample test scenario:
# This should block for ~10 seconds then return result
result = task(
subagent_type="bash",
prompt="sleep 10 && echo 'Done'",
description="Test task"
)
# result should contain "Done"
For users/code that previously used run_in_background=True:
No other changes needed - the API is backward compatible (minus the removed parameter).