* refactor: extract shared utils to break harness→app cross-layer imports Move _validate_skill_frontmatter to src/skills/validation.py and CONVERTIBLE_EXTENSIONS + convert_file_to_markdown to src/utils/file_conversion.py. This eliminates the two reverse dependencies from client.py (harness layer) into gateway/routers/ (app layer), preparing for the harness/app package split. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: split backend/src into harness (deerflow.*) and app (app.*) Physically split the monolithic backend/src/ package into two layers: - **Harness** (`packages/harness/deerflow/`): publishable agent framework package with import prefix `deerflow.*`. Contains agents, sandbox, tools, models, MCP, skills, config, and all core infrastructure. - **App** (`app/`): unpublished application code with import prefix `app.*`. Contains gateway (FastAPI REST API) and channels (IM integrations). Key changes: - Move 13 harness modules to packages/harness/deerflow/ via git mv - Move gateway + channels to app/ via git mv - Rename all imports: src.* → deerflow.* (harness) / app.* (app layer) - Set up uv workspace with deerflow-harness as workspace member - Update langgraph.json, config.example.yaml, all scripts, Docker files - Add build-system (hatchling) to harness pyproject.toml - Add PYTHONPATH=. to gateway startup commands for app.* resolution - Update ruff.toml with known-first-party for import sorting - Update all documentation to reflect new directory structure Boundary rule enforced: harness code never imports from app. All 429 tests pass. Lint clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add harness→app boundary check test and update docs Add test_harness_boundary.py that scans all Python files in packages/harness/deerflow/ and fails if any `from app.*` or `import app.*` statement is found. This enforces the architectural rule that the harness layer never depends on the app layer. Update CLAUDE.md to document the harness/app split architecture, import conventions, and the boundary enforcement test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add config versioning with auto-upgrade on startup When config.example.yaml schema changes, developers' local config.yaml files can silently become outdated. This adds a config_version field and auto-upgrade mechanism so breaking changes (like src.* → deerflow.* renames) are applied automatically before services start. - Add config_version: 1 to config.example.yaml - Add startup version check warning in AppConfig.from_file() - Add scripts/config-upgrade.sh with migration registry for value replacements - Add `make config-upgrade` target - Auto-run config-upgrade in serve.sh and start-daemon.sh before starting services - Add config error hints in service failure messages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix comments * fix: update src.* import in test_sandbox_tools_security to deerflow.* Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: handle empty config and search parent dirs for config.example.yaml Address Copilot review comments on PR #1131: - Guard against yaml.safe_load() returning None for empty config files - Search parent directories for config.example.yaml instead of only looking next to config.yaml, fixing detection in common setups Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct skills root path depth and config_version type coercion - loader.py: fix get_skills_root_path() to use 5 parent levels (was 3) after harness split, file lives at packages/harness/deerflow/skills/ so parent×3 resolved to backend/packages/harness/ instead of backend/ - app_config.py: coerce config_version to int() before comparison in _check_config_version() to prevent TypeError when YAML stores value as string (e.g. config_version: "1") - tests: add regression tests for both fixes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: update test imports from src.* to deerflow.*/app.* after harness refactor Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
7.2 KiB
Plan Mode with TodoList Middleware
This document describes how to enable and use the Plan Mode feature with TodoList middleware in DeerFlow 2.0.
Overview
Plan Mode adds a TodoList middleware to the agent, which provides a write_todos tool that helps the agent:
- Break down complex tasks into smaller, manageable steps
- Track progress as work progresses
- Provide visibility to users about what's being done
The TodoList middleware is built on LangChain's TodoListMiddleware.
Configuration
Enabling Plan Mode
Plan mode is controlled via runtime configuration through the is_plan_mode parameter in the configurable section of RunnableConfig. This allows you to dynamically enable or disable plan mode on a per-request basis.
from langchain_core.runnables import RunnableConfig
from deerflow.agents.lead_agent.agent import make_lead_agent
# Enable plan mode via runtime configuration
config = RunnableConfig(
configurable={
"thread_id": "example-thread",
"thinking_enabled": True,
"is_plan_mode": True, # Enable plan mode
}
)
# Create agent with plan mode enabled
agent = make_lead_agent(config)
Configuration Options
- is_plan_mode (bool): Whether to enable plan mode with TodoList middleware. Default:
False- Pass via
config.get("configurable", {}).get("is_plan_mode", False) - Can be set dynamically for each agent invocation
- No global configuration needed
- Pass via
Default Behavior
When plan mode is enabled with default settings, the agent will have access to a write_todos tool with the following behavior:
When to Use TodoList
The agent will use the todo list for:
- Complex multi-step tasks (3+ distinct steps)
- Non-trivial tasks requiring careful planning
- When user explicitly requests a todo list
- When user provides multiple tasks
When NOT to Use TodoList
The agent will skip using the todo list for:
- Single, straightforward tasks
- Trivial tasks (< 3 steps)
- Purely conversational or informational requests
Task States
- pending: Task not yet started
- in_progress: Currently working on (can have multiple parallel tasks)
- completed: Task finished successfully
Usage Examples
Basic Usage
from langchain_core.runnables import RunnableConfig
from deerflow.agents.lead_agent.agent import make_lead_agent
# Create agent with plan mode ENABLED
config_with_plan_mode = RunnableConfig(
configurable={
"thread_id": "example-thread",
"thinking_enabled": True,
"is_plan_mode": True, # TodoList middleware will be added
}
)
agent_with_todos = make_lead_agent(config_with_plan_mode)
# Create agent with plan mode DISABLED (default)
config_without_plan_mode = RunnableConfig(
configurable={
"thread_id": "another-thread",
"thinking_enabled": True,
"is_plan_mode": False, # No TodoList middleware
}
)
agent_without_todos = make_lead_agent(config_without_plan_mode)
Dynamic Plan Mode per Request
You can enable/disable plan mode dynamically for different conversations or tasks:
from langchain_core.runnables import RunnableConfig
from deerflow.agents.lead_agent.agent import make_lead_agent
def create_agent_for_task(task_complexity: str):
"""Create agent with plan mode based on task complexity."""
is_complex = task_complexity in ["high", "very_high"]
config = RunnableConfig(
configurable={
"thread_id": f"task-{task_complexity}",
"thinking_enabled": True,
"is_plan_mode": is_complex, # Enable only for complex tasks
}
)
return make_lead_agent(config)
# Simple task - no TodoList needed
simple_agent = create_agent_for_task("low")
# Complex task - TodoList enabled for better tracking
complex_agent = create_agent_for_task("high")
How It Works
- When
make_lead_agent(config)is called, it extractsis_plan_modefromconfig.configurable - The config is passed to
_build_middlewares(config) _build_middlewares()readsis_plan_modeand calls_create_todo_list_middleware(is_plan_mode)- If
is_plan_mode=True, aTodoListMiddlewareinstance is created and added to the middleware chain - The middleware automatically adds a
write_todostool to the agent's toolset - The agent can use this tool to manage tasks during execution
- The middleware handles the todo list state and provides it to the agent
Architecture
make_lead_agent(config)
│
├─> Extracts: is_plan_mode = config.configurable.get("is_plan_mode", False)
│
└─> _build_middlewares(config)
│
├─> ThreadDataMiddleware
├─> SandboxMiddleware
├─> SummarizationMiddleware (if enabled via global config)
├─> TodoListMiddleware (if is_plan_mode=True) ← NEW
├─> TitleMiddleware
└─> ClarificationMiddleware
Implementation Details
Agent Module
- Location:
packages/harness/deerflow/agents/lead_agent/agent.py - Function:
_create_todo_list_middleware(is_plan_mode: bool)- Creates TodoListMiddleware if plan mode is enabled - Function:
_build_middlewares(config: RunnableConfig)- Builds middleware chain based on runtime config - Function:
make_lead_agent(config: RunnableConfig)- Creates agent with appropriate middlewares
Runtime Configuration
Plan mode is controlled via the is_plan_mode parameter in RunnableConfig.configurable:
config = RunnableConfig(
configurable={
"is_plan_mode": True, # Enable plan mode
# ... other configurable options
}
)
Key Benefits
- Dynamic Control: Enable/disable plan mode per request without global state
- Flexibility: Different conversations can have different plan mode settings
- Simplicity: No need for global configuration management
- Context-Aware: Plan mode decision can be based on task complexity, user preferences, etc.
Custom Prompts
DeerFlow uses custom system_prompt and tool_description for the TodoListMiddleware that match the overall DeerFlow prompt style:
System Prompt Features
- Uses XML tags (
<todo_list_system>) for structure consistency with DeerFlow's main prompt - Emphasizes CRITICAL rules and best practices
- Clear "When to Use" vs "When NOT to Use" guidelines
- Focuses on real-time updates and immediate task completion
Tool Description Features
- Detailed usage scenarios with examples
- Strong emphasis on NOT using for simple tasks
- Clear task state definitions (pending, in_progress, completed)
- Comprehensive best practices section
- Task completion requirements to prevent premature marking
The custom prompts are defined in _create_todo_list_middleware() in /Users/hetao/workspace/deer-flow/backend/packages/harness/deerflow/agents/lead_agent/agent.py:57.
Notes
- TodoList middleware uses LangChain's built-in
TodoListMiddlewarewith custom DeerFlow-style prompts - Plan mode is disabled by default (
is_plan_mode=False) to maintain backward compatibility - The middleware is positioned before
ClarificationMiddlewareto allow todo management during clarification flows - Custom prompts emphasize the same principles as DeerFlow's main system prompt (clarity, action-oriented, critical rules)