Skip to main content
DeerFlow’s agent system uses a custom ThreadState schema that extends LangChain’s AgentState with domain-specific fields for sandbox management, file tracking, and user interactions. The state is managed by LangGraph’s checkpointing system and enhanced with custom reducers for intelligent state merging.

ThreadState Schema

Defined in backend/src/agents/thread_state.py:

State Fields

sandbox

Type: SandboxState | None Purpose: Tracks the active sandbox environment for isolated tool execution. Structure:
Lifecycle:
  • Set by SandboxMiddleware in before_agent hook
  • Persisted across turns within same thread (sandbox not released)
  • Used by sandbox tools (bash, read_file, write_file, str_replace, ls)
Example:

thread_data

Type: ThreadDataState | None Purpose: Provides path mappings between virtual (agent-visible) and physical (host) paths. Structure:
Virtual Path Mappings:
  • Agent sees: /mnt/user-data/workspace, /mnt/user-data/uploads, /mnt/user-data/outputs
  • Physical: backend/.deer-flow/threads/{thread_id}/user-data/{workspace,uploads,outputs}
Lifecycle:
  • Set by ThreadDataMiddleware in before_agent hook
  • With lazy_init=True (default): Paths computed but directories created on-demand
  • With lazy_init=False: Directories eagerly created in middleware

title

Type: str | None Purpose: Human-readable thread title for UI display. Lifecycle:
  • Set by TitleMiddleware after first complete user-assistant exchange
  • Generated via lightweight LLM based on first user message and assistant response
  • Persisted by LangGraph checkpointer
Generation (backend/src/agents/middlewares/title_middleware.py:46-81):
Configuration (config.yaml):

artifacts

Type: Annotated[list[str], merge_artifacts] Purpose: Tracks files presented to user via present_files tool. Custom Reducer (backend/src/agents/thread_state.py:21-28):
Behavior:
  • Maintains insertion order (first occurrence preserved)
  • Automatically deduplicates paths
  • Survives across turns (cumulative)
Usage in Tools:

todos

Type: list | None Purpose: Stores task list when TodoListMiddleware is enabled (is_plan_mode=True). Structure:
Task States:
  • pending - Not yet started
  • in_progress - Currently working (one at a time, or multiple if parallel)
  • completed - Finished successfully
Managed By: TodoListMiddleware (LangChain built-in) with custom prompts Tool: write_todos (injected by middleware)

uploaded_files

Type: list[dict] | None Purpose: Tracks newly uploaded files for current turn. Structure:
Lifecycle:
  • Set by UploadsMiddleware in before_agent hook
  • Only includes files NOT already shown in previous messages (deduplication)
  • Cleared on next turn (not cumulative)
Deduplication Logic (backend/src/agents/middlewares/uploads_middleware.py:110-136):

viewed_images

Type: Annotated[dict[str, ViewedImageData], merge_viewed_images] Purpose: Tracks images loaded via view_image tool for vision model analysis. Structure:
Custom Reducer (backend/src/agents/thread_state.py:31-45):
Behavior:
  • Normal updates: Merge dictionaries (new keys added, existing keys updated)
  • Empty dict {}: Clears all viewed images (reset)
  • Used by ViewImageMiddleware to inject images before LLM call
Lifecycle:
  1. Agent calls view_image tool → Tool returns base64 data in ToolMessage
  2. Tool also updates state: {"viewed_images": {path: {base64, mime_type}}}
  3. ViewImageMiddleware detects completed view_image tool calls in before_model
  4. Middleware injects HumanMessage with multimodal content (text + images)
  5. LLM analyzes images automatically
  6. Middleware clears state: {"viewed_images": {}} after processing

State Management Patterns

1. Middleware State Updates

Middlewares return state updates as dictionaries:
Merge Behavior:
  • Fields without custom reducers: Replace existing value
  • Fields with custom reducers: Call reducer function
  • messages: Uses LangChain’s add_messages reducer (append to list)

2. Tool State Updates

Tools can update state by returning dictionaries:

3. State Persistence

State persisted via LangGraph checkpointer:
Persisted Fields:
  • All ThreadState fields
  • Full message history
  • Checkpoints created after each agent step
Retrieval:

4. Thread Isolation

Each thread maintains independent state:
Physical Isolation:
  • Separate directories: backend/.deer-flow/threads/{thread_id}/
  • Separate sandboxes (if using Docker provider)
  • Separate checkpoint history

Custom Reducer Implementation

When to Use Custom Reducers

  1. Deduplication - Remove duplicates while merging (like merge_artifacts)
  2. Merging Dicts - Intelligently merge nested structures (like merge_viewed_images)
  3. Reset Semantics - Support clearing values (empty dict resets viewed_images)
  4. Aggregation - Accumulate values with custom logic

Creating Custom Reducers

Reducer Contract:
  • Takes two arguments: existing (current state) and new (update)
  • Both arguments can be None
  • Returns merged value of same type
  • Pure function (no side effects)

Testing Reducers

State Debugging

Inspecting Current State

State Size Monitoring

Performance Considerations

Memory Usage

  • messages: Grows unbounded without summarization (use SummarizationMiddleware)
  • viewed_images: Store base64 data (can be large, clear after processing)
  • artifacts: Small (just file paths)

Database Size

  • Each checkpoint persists full state to database
  • With PostgresCheckpointer: One row per checkpoint
  • Recommend periodic cleanup of old threads

State Transfer

  • State serialized/deserialized on every agent step
  • Keep state schema simple (avoid deeply nested structures)
  • Use NotRequired for optional fields (reduces serialization overhead)

See Also