diff --git a/backend/docs/plan_mode_usage.md b/backend/docs/plan_mode_usage.md new file mode 100644 index 0000000..2e4aedb --- /dev/null +++ b/backend/docs/plan_mode_usage.md @@ -0,0 +1,204 @@ +# 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. + +```python +from langchain_core.runnables import RunnableConfig +from src.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 + +## 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: +1. Complex multi-step tasks (3+ distinct steps) +2. Non-trivial tasks requiring careful planning +3. When user explicitly requests a todo list +4. When user provides multiple tasks + +### When NOT to Use TodoList + +The agent will skip using the todo list for: +1. Single, straightforward tasks +2. Trivial tasks (< 3 steps) +3. 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 + +```python +from langchain_core.runnables import RunnableConfig +from src.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: + +```python +from langchain_core.runnables import RunnableConfig +from src.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 + +1. When `make_lead_agent(config)` is called, it extracts `is_plan_mode` from `config.configurable` +2. The config is passed to `_build_middlewares(config)` +3. `_build_middlewares()` reads `is_plan_mode` and calls `_create_todo_list_middleware(is_plan_mode)` +4. If `is_plan_mode=True`, a `TodoListMiddleware` instance is created and added to the middleware chain +5. The middleware automatically adds a `write_todos` tool to the agent's toolset +6. The agent can use this tool to manage tasks during execution +7. 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**: `src/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`: +```python +config = RunnableConfig( + configurable={ + "is_plan_mode": True, # Enable plan mode + # ... other configurable options + } +) +``` + +## Key Benefits + +1. **Dynamic Control**: Enable/disable plan mode per request without global state +2. **Flexibility**: Different conversations can have different plan mode settings +3. **Simplicity**: No need for global configuration management +4. **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 (``) 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/src/agents/lead_agent/agent.py:57`. + +## Notes + +- TodoList middleware uses LangChain's built-in `TodoListMiddleware` with **custom DeerFlow-style prompts** +- Plan mode is **disabled by default** (`is_plan_mode=False`) to maintain backward compatibility +- The middleware is positioned before `ClarificationMiddleware` to allow todo management during clarification flows +- Custom prompts emphasize the same principles as DeerFlow's main system prompt (clarity, action-oriented, critical rules) diff --git a/backend/src/agents/lead_agent/agent.py b/backend/src/agents/lead_agent/agent.py index 29ee826..b3e8922 100644 --- a/backend/src/agents/lead_agent/agent.py +++ b/backend/src/agents/lead_agent/agent.py @@ -1,5 +1,5 @@ from langchain.agents import create_agent -from langchain.agents.middleware import SummarizationMiddleware +from langchain.agents.middleware import SummarizationMiddleware, TodoListMiddleware from langchain_core.runnables import RunnableConfig from src.agents.lead_agent.prompt import apply_prompt_template @@ -54,10 +54,134 @@ def _create_summarization_middleware() -> SummarizationMiddleware | None: return SummarizationMiddleware(**kwargs) +def _create_todo_list_middleware(is_plan_mode: bool) -> TodoListMiddleware | None: + """Create and configure the TodoList middleware. + + Args: + is_plan_mode: Whether to enable plan mode with TodoList middleware. + + Returns: + TodoListMiddleware instance if plan mode is enabled, None otherwise. + """ + if not is_plan_mode: + return None + + # Custom prompts matching DeerFlow's style + system_prompt = """ + +You have access to the `write_todos` tool to help you manage and track complex multi-step objectives. + +**CRITICAL RULES:** +- Mark todos as completed IMMEDIATELY after finishing each step - do NOT batch completions +- Keep EXACTLY ONE task as `in_progress` at any time (unless tasks can run in parallel) +- Update the todo list in REAL-TIME as you work - this gives users visibility into your progress +- DO NOT use this tool for simple tasks (< 3 steps) - just complete them directly + +**When to Use:** +This tool is designed for complex objectives that require systematic tracking: +- Complex multi-step tasks requiring 3+ distinct steps +- Non-trivial tasks needing careful planning and execution +- User explicitly requests a todo list +- User provides multiple tasks (numbered or comma-separated list) +- The plan may need revisions based on intermediate results + +**When NOT to Use:** +- Single, straightforward tasks +- Trivial tasks (< 3 steps) +- Purely conversational or informational requests +- Simple tool calls where the approach is obvious + +**Best Practices:** +- Break down complex tasks into smaller, actionable steps +- Use clear, descriptive task names +- Remove tasks that become irrelevant +- Add new tasks discovered during implementation +- Don't be afraid to revise the todo list as you learn more + +**Task Management:** +Writing todos takes time and tokens - use it when helpful for managing complex problems, not for simple requests. + +""" + + tool_description = """Use this tool to create and manage a structured task list for complex work sessions. + +**IMPORTANT: Only use this tool for complex tasks (3+ steps). For simple requests, just do the work directly.** + +## When to Use + +Use this tool in these scenarios: +1. **Complex multi-step tasks**: When a task requires 3 or more distinct steps or actions +2. **Non-trivial tasks**: Tasks requiring careful planning or multiple operations +3. **User explicitly requests todo list**: When the user directly asks you to track tasks +4. **Multiple tasks**: When users provide a list of things to be done +5. **Dynamic planning**: When the plan may need updates based on intermediate results + +## When NOT to Use + +Skip this tool when: +1. The task is straightforward and takes less than 3 steps +2. The task is trivial and tracking provides no benefit +3. The task is purely conversational or informational +4. It's clear what needs to be done and you can just do it + +## How to Use + +1. **Starting a task**: Mark it as `in_progress` BEFORE beginning work +2. **Completing a task**: Mark it as `completed` IMMEDIATELY after finishing +3. **Updating the list**: Add new tasks, remove irrelevant ones, or update descriptions as needed +4. **Multiple updates**: You can make several updates at once (e.g., complete one task and start the next) + +## Task States + +- `pending`: Task not yet started +- `in_progress`: Currently working on (can have multiple if tasks run in parallel) +- `completed`: Task finished successfully + +## Task Completion Requirements + +**CRITICAL: Only mark a task as completed when you have FULLY accomplished it.** + +Never mark a task as completed if: +- There are unresolved issues or errors +- Work is partial or incomplete +- You encountered blockers preventing completion +- You couldn't find necessary resources or dependencies +- Quality standards haven't been met + +If blocked, keep the task as `in_progress` and create a new task describing what needs to be resolved. + +## Best Practices + +- Create specific, actionable items +- Break complex tasks into smaller, manageable steps +- Use clear, descriptive task names +- Update task status in real-time as you work +- Mark tasks complete IMMEDIATELY after finishing (don't batch completions) +- Remove tasks that are no longer relevant +- **IMPORTANT**: When you write the todo list, mark your first task(s) as `in_progress` immediately +- **IMPORTANT**: Unless all tasks are completed, always have at least one task `in_progress` to show progress + +Being proactive with task management demonstrates thoroughness and ensures all requirements are completed successfully. + +**Remember**: If you only need a few tool calls to complete a task and it's clear what to do, it's better to just do the task directly and NOT use this tool at all. +""" + + return TodoListMiddleware(system_prompt=system_prompt, tool_description=tool_description) + + # ThreadDataMiddleware must be before SandboxMiddleware to ensure thread_id is available # SummarizationMiddleware should be early to reduce context before other processing +# TodoListMiddleware should be before ClarificationMiddleware to allow todo management # ClarificationMiddleware should be last to intercept clarification requests after model calls -def _build_middlewares(): +def _build_middlewares(config: RunnableConfig): + """Build middleware chain based on runtime configuration. + + Args: + config: Runtime configuration containing configurable options like is_plan_mode. + + Returns: + List of middleware instances. + """ middlewares = [ThreadDataMiddleware(), SandboxMiddleware()] # Add summarization middleware if enabled @@ -65,6 +189,12 @@ def _build_middlewares(): if summarization_middleware is not None: middlewares.append(summarization_middleware) + # Add TodoList middleware if plan mode is enabled + is_plan_mode = config.get("configurable", {}).get("is_plan_mode", False) + todo_list_middleware = _create_todo_list_middleware(is_plan_mode) + if todo_list_middleware is not None: + middlewares.append(todo_list_middleware) + middlewares.extend([TitleMiddleware(), ClarificationMiddleware()]) return middlewares @@ -75,11 +205,12 @@ def make_lead_agent(config: RunnableConfig): thinking_enabled = config.get("configurable", {}).get("thinking_enabled", True) model_name = config.get("configurable", {}).get("model_name") or config.get("configurable", {}).get("model") - print(f"thinking_enabled: {thinking_enabled}, model_name: {model_name}") + is_plan_mode = config.get("configurable", {}).get("is_plan_mode", False) + print(f"thinking_enabled: {thinking_enabled}, model_name: {model_name}, is_plan_mode: {is_plan_mode}") return create_agent( model=create_chat_model(name=model_name, thinking_enabled=thinking_enabled), tools=get_available_tools(), - middleware=_build_middlewares(), + middleware=_build_middlewares(config), system_prompt=apply_prompt_template(), state_schema=ThreadState, )