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 inbackend/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):
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):
- 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 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):
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):
{"type": "fraction", "value": 0.8}- 80% of modelβs max input tokens{"type": "tokens", "value": 4000}- 4000 tokens{"type": "messages", "value": 50}- 50 messages
backend/src/agents/lead_agent/agent.py:41-80):
6. TodoListMiddleware (Optional)
Purpose: Provideswrite_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):
pending- Not startedin_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):
8. MemoryMiddleware
Purpose: Queues conversation for asynchronous memory extraction and updates. Lifecycle:after_agent
Implementation (backend/src/agents/middlewares/memory_middleware.py:53-117):
backend/src/agents/middlewares/memory_middleware.py:19-50):
- Middleware queues conversation after agent completes
- Queue debounces (30s default) and batches updates
- Background thread invokes LLM to extract facts and context
- Updates stored atomically in
backend/.deer-flow/memory.json - Next interaction injects top 15 facts into system prompt
9. ViewImageMiddleware (Optional)
Purpose: Injects base64 image data into conversation whenview_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):
viewed_images dict in ThreadState with custom reducer:
10. SubagentLimitMiddleware (Optional)
Purpose: Enforces maximum concurrent subagent calls by truncating excesstask 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):
task calls, middleware truncates deterministically.
11. ClarificationMiddleware
Purpose: Interceptsask_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):
Command(goto=END) to interrupt graph execution, forcing wait for user input.
Middleware Ordering Rationale
The strict order ensures correct dependency resolution:- ThreadDataMiddleware β Creates thread directories first (required by UploadsMiddleware, SandboxMiddleware)
- UploadsMiddleware β Injects file info before sandbox/model sees it
- SandboxMiddleware β Acquires environment before tool execution
- DanglingToolCallMiddleware β Patches message history before model sees it
- SummarizationMiddleware β Reduces context early (before other processing)
- TodoListMiddleware β Enables task tracking (before clarification)
- TitleMiddleware β Generates title after first exchange
- MemoryMiddleware β Queues after title generation (complete turn)
- ViewImageMiddleware β Injects images before model call (if vision supported)
- SubagentLimitMiddleware β Truncates after model generates tool calls
- ClarificationMiddleware β MUST BE LAST to intercept all tool calls
Runtime Configuration
Middlewares can be conditionally enabled viaconfig.configurable:
State Schema Compatibility
All middlewares use state schemas compatible withThreadState (backend/src/agents/thread_state.py:48-56):
merge_artifacts- Deduplicates artifact paths while preserving ordermerge_viewed_images- Merges image dicts, empty dict{}clears all
Debugging Middlewares
Each middleware logs key actions:See Also
- Thread State - State schema and custom reducers
- Context Engineering - Summarization and memory management
- Model Factory - Model instantiation and configuration