Skip to main content
The DeerFlow agent system uses a sophisticated middleware chain that processes every agent invocation through 11 specialized middleware components. Each middleware executes at specific lifecycle hooks (before_agent, after_agent, before_model, after_model, wrap_model_call, wrap_tool_call) to augment agent behavior without modifying core logic.

Execution Order

Middlewares execute in strict order defined in backend/src/agents/lead_agent/agent.py:217-250:

Middleware Components

1. ThreadDataMiddleware

Purpose: Creates per-thread isolated directory structure for workspace, uploads, and output files. Lifecycle: before_agent Implementation (backend/src/agents/middlewares/thread_data_middleware.py):
Directory Structure Created:

2. UploadsMiddleware

Purpose: Injects uploaded file information into the conversation, tracking new uploads across turns. Lifecycle: before_agent Implementation (backend/src/agents/middlewares/uploads_middleware.py:139-220):
Key Features:
  • Deduplicates files already shown in previous turns
  • Formats file list with size and virtual path: /mnt/user-data/uploads/{filename}
  • Supports filenames with spaces via regex r"^-\s+(.+?)\s*\("

3. SandboxMiddleware

Purpose: Acquires and manages isolated execution environments for agent tool calls. Lifecycle: before_agent Implementation (backend/src/sandbox/middleware.py:18-61):
Sandbox Lifecycle:
  • Sandbox reused across turns within same thread (not released after each call)
  • Cleanup occurs at application shutdown via SandboxProvider.shutdown()
  • Supports local filesystem (LocalSandboxProvider) and Docker (AioSandboxProvider)

4. DanglingToolCallMiddleware

Purpose: Fixes message history gaps caused by interrupted tool calls (e.g., user cancellation). Lifecycle: wrap_model_call Implementation (backend/src/agents/middlewares/dangling_tool_call_middleware.py:28-111):
Why wrap_model_call instead of before_model: Ensures patches are inserted immediately after each dangling AIMessage, not appended to the end (which before_model + add_messages reducer would do).

5. SummarizationMiddleware (Optional)

Purpose: Automatic context reduction when approaching token limits. Lifecycle: before_model, after_model Configuration (backend/src/config/summarization_config.py):
Trigger Types:
  • {"type": "fraction", "value": 0.8} - 80% of model’s max input tokens
  • {"type": "tokens", "value": 4000} - 4000 tokens
  • {"type": "messages", "value": 50} - 50 messages
Keep Policies: Same types as triggers, defines how much context to preserve after summarization. Creation (backend/src/agents/lead_agent/agent.py:41-80):

6. TodoListMiddleware (Optional)

Purpose: Provides write_todos tool for structured task tracking in complex multi-step workflows. Lifecycle: Tool injection + state management Activation: Enabled when config.configurable.is_plan_mode = True Custom Configuration (backend/src/agents/lead_agent/agent.py:83-195):
Task States:
  • pending - Not started
  • in_progress - Currently working (one at a time, or multiple if parallel)
  • completed - Finished successfully

7. TitleMiddleware

Purpose: Auto-generates thread title after first complete user-assistant exchange. Lifecycle: after_agent Implementation (backend/src/agents/middlewares/title_middleware.py:19-94):
Fallback: If LLM fails, uses first 50 characters of user message.

8. MemoryMiddleware

Purpose: Queues conversation for asynchronous memory extraction and updates. Lifecycle: after_agent Implementation (backend/src/agents/middlewares/memory_middleware.py:53-117):
Message Filtering (backend/src/agents/middlewares/memory_middleware.py:19-50):
Memory Workflow:
  1. Middleware queues conversation after agent completes
  2. Queue debounces (30s default) and batches updates
  3. Background thread invokes LLM to extract facts and context
  4. Updates stored atomically in backend/.deer-flow/memory.json
  5. Next interaction injects top 15 facts into system prompt

9. ViewImageMiddleware (Optional)

Purpose: Injects base64 image data into conversation when view_image tool completes. Lifecycle: before_model Activation: Only added if model_config.supports_vision = true Implementation (backend/src/agents/middlewares/view_image_middleware.py:19-222):
State Management: Uses viewed_images dict in ThreadState with custom reducer:

10. SubagentLimitMiddleware (Optional)

Purpose: Enforces maximum concurrent subagent calls by truncating excess task tool calls. Lifecycle: after_model Activation: Only added if config.configurable.subagent_enabled = True Implementation (backend/src/agents/middlewares/subagent_limit_middleware.py:24-76):
Why This Works: More reliable than prompt-based limits. Model can generate unlimited task calls, middleware truncates deterministically.

11. ClarificationMiddleware

Purpose: Intercepts ask_clarification tool calls and interrupts execution to present questions to user. Lifecycle: wrap_tool_call Position: MUST BE LAST in middleware chain to intercept after all other processing. Implementation (backend/src/agents/middlewares/clarification_middleware.py:20-174):
Key Behavior: Uses Command(goto=END) to interrupt graph execution, forcing wait for user input.

Middleware Ordering Rationale

The strict order ensures correct dependency resolution:
  1. ThreadDataMiddleware → Creates thread directories first (required by UploadsMiddleware, SandboxMiddleware)
  2. UploadsMiddleware → Injects file info before sandbox/model sees it
  3. SandboxMiddleware → Acquires environment before tool execution
  4. DanglingToolCallMiddleware → Patches message history before model sees it
  5. SummarizationMiddleware → Reduces context early (before other processing)
  6. TodoListMiddleware → Enables task tracking (before clarification)
  7. TitleMiddleware → Generates title after first exchange
  8. MemoryMiddleware → Queues after title generation (complete turn)
  9. ViewImageMiddleware → Injects images before model call (if vision supported)
  10. SubagentLimitMiddleware → Truncates after model generates tool calls
  11. ClarificationMiddlewareMUST BE LAST to intercept all tool calls

Runtime Configuration

Middlewares can be conditionally enabled via config.configurable:

State Schema Compatibility

All middlewares use state schemas compatible with ThreadState (backend/src/agents/thread_state.py:48-56):
Custom Reducers:
  • merge_artifacts - Deduplicates artifact paths while preserving order
  • merge_viewed_images - Merges image dicts, empty dict {} clears all

Debugging Middlewares

Each middleware logs key actions:
View logs via:

See Also