deer-flow/config.example.yaml
qin-chenghan ad45f59d66
feat(memory): pluggable memory abstraction with self-contained DeerMem backend (#4122)
* feat(memory): pluggable + self-contained memory system (MemoryManager plan phases 1 & 2)

Phase 1 — Pluggable (steps 0-10):
- ABC MemoryManager (9 methods) + singleton factory + drop-in backend discovery
- DeerMem default backend with core/ (storage/queue/updater/prompt/message_processing)
- NoopMemoryManager backend (proves pluggability)
- All call sites (middleware/hook/prompt/gateway/client/app) routed through manager
- hasattr capability probing for DeerMem-internal methods (no hard imports)
- MemoryConfig gains manager_class field; shared vs DeerMem-private annotated

Phase 2 — Self-contained DeerMem (steps 11-18):
- backend_config passthrough + DeerMemConfig (all DeerMem-private fields moved off MemoryConfig)
- DI: DeerMem owns storage/queue/updater/llm as instance attributes (no global singletons)
- Storage independence: core/paths.py with own root (~/.deermem or ),
  factory auto-injects deer-flow's runtime_home() as absolute base_dir (zero-config)
- LLM independence: core/llm.py via langchain init_chat_model (no create_chat_model)
- Trace independence: optional tracing_callback replaces inject_langfuse_metadata/request_trace_context
- Message processing independence: hide_from_ui default-skip + optional should_keep_hidden_message hook
- Internal imports → relative (only deer_mem.py ABC import is host-relative)
- Carrier (deer_mem.py adapter) / portable (deermem/ config+core) split
- New tests: test_deermem_self_contained + test_memory_manager_pluggable; all memory tests migrated
- Other-agent demo: samples/other_agent_demo/ + automated portability test
- config.example.yaml memory section updated to phase-2 schema

* feat(memory): port consolidation + staleness fix into self-contained DeerMem; phase-2 host hooks

Port upstream #3996 (memory consolidation) and #3993 (staleness KeyError fix)
from origin/MemoryManager into the pluggable, self-contained DeerMem structure
(backends/deermem/deermem/), adapted to the DI MemoryUpdater (config injected,
not get_memory_config globals):

- DeerMemConfig: add consolidation_enabled (opt-in, default false) /
  consolidation_min_facts / consolidation_max_groups_per_cycle /
  consolidation_max_sources
- prompt.py: factsToConsolidate JSON field + {consolidation_section} placeholder
  + CONSOLIDATION_PROMPT constant
- updater.py: _coerce_source_confidence / _select_consolidation_candidates /
  _build_consolidation_section module helpers (matching the existing
  _select_stale_candidates style); consolidation normalization in
  _normalize_memory_update_data; consolidation apply in _apply_updates (after
  max_facts trim, with apply-time guardrails mirroring staleness); staleness
  KeyError fix (f["id"] -> f.get("id") is not None) applied to both the
  staleness guardrail and the consolidation allowed_source_ids comprehension
- config.example.yaml: consolidation section under memory.backend_config
- tests/test_memory_consolidation.py: 40 DI-adapted tests (running, not skipped)
  incl. the staleness KeyError regression

Also includes in-flight phase-2 host-integration work: storage_path semantics
(any absolute/relative value = root dir) and host-default tracing_callback /
should_keep_hidden_message hooks injected into backend_config by the factory.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(memory): add noop backend template and backends guide

- backends/noop/: complete drop-in template (config.py with zero deer-flow
  imports, noop_manager.py with a 6-step new-backend walkthrough in its
  docstring, commented optional fact-CRUD capabilities).
- backends/README.md: which files to touch when adding/swapping a backend,
  the 5-item backend contract, and common pitfalls.
- manager.py: generalize backend examples in comments (drop mem0-specific
  references).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(frontend): guard formatTimeAgo against invalid timestamps

Return a neutral placeholder when the input date is invalid (e.g. an empty lastUpdated from a backend with no memories) instead of throwing 'Invalid time value' from date-fns.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(memory): wire tool-driven memory mode through the MemoryManager ABC

tools.py (memory_search/add/update/delete) now calls get_memory_manager()
instead of the removed host memory module, so tool mode (memory.mode: tool)
works for any backend. DeerMem.search is implemented (case-insensitive
substring match, ranked by confidence) as a stand-in for the planned
semantic retrieval; noop.search returns [] (unchanged). Fact-CRUD tools
use getattr+callable probing -- backends lacking those ops (noop) get a
clear JSON error instead of crashing.

Tests: test_memory_tools rewired to mock the manager (handler tests) +
TestModeGating retained; test_memory_search now covers DeerMem.search;
pluggable stubs test updated (search no longer a stub).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: resolve lint errors (import sorting, type annotation quotes, E402 in skipped tests)

* docs: restore explanatory comments in config.example.yaml memory section

* fix(security): port html-escape memory facts fix (#4097) to vendored DeerMem prompt.py

* fix(memory): address review + port dropped upstream memory fixes

Review blockers (vendored DeerMem):
- #4044 restore _escape_memory_for_prompt (current_memory blob in
  MEMORY_UPDATE_PROMPT) - prevents </current_memory> breakout
- #4028 html.escape staleness-section cat/content in _build_staleness_section
- #4119 add _escape_summary for injection-path summaries (Work/Personal/
  Current Focus/Recent/Earlier/Background)
- default-model silent no-op: factory injects host default chat model via a
  new host_llm slot (create_chat_model(name=None)); DeerMem prefers host_llm
  over build_llm(model). Zero-config extraction works out of the box again
- MemoryConfigResponse: fix stale docstring (backend-agnostic shape; DeerMem
  knobs live under backend_config, not top-level - restoring flat would
  re-couple the API to DeerMem). Frontend audited: does not read /memory/config
- _host_default_tracing_callback: restore langfuse assistant_id/environment
- search: push category onto the ABC signature; DeerMem filters BEFORE the
  top_k slice (was filtered client-side after slicing -> starved results)
- _do_update_memory_sync: split into wrapper+impl; bind trace_id into the
  request-trace ContextVar on the Timer/executor worker via a new
  trace_context_manager host hook (None trace_id left unbound - no fabrication)
- client.py fact-CRUD now passes user_id (was writing to the global bucket
  while get_memory reads per-user)
- _resolve_manager_class: fail-fast (raise ValueError) on an unresolved
  explicit manager_class instead of silently falling back to DeerMem (memory is
  persistent state - a wrong store is a silent data-integrity footgun)

Upstream memory fixes dropped by the host->vendored rename conflict, re-ported
to backends/deermem/deermem/core/ (+ deer_mem.py):
- #4073 queue busy-timer-spin -> _reprocess_pending flag (core/queue.py)
- #4074 null source.confidence in staleness -> _coerce_source_confidence
  (core/updater.py: _build_staleness_section + _apply_updates stale sort)
- #4075 factsToRemove is optional (drop from _REQUIRED_MEMORY_UPDATE_TOP_LEVEL_KEYS)
- #4076 null confidence in search ranking -> _coerce_source_confidence
  (deer_mem.py DeerMem.search)

host_llm + trace_context_manager are host-injected via backend_config (factory
in manager.py), keeping backends/deermem/ at exactly one `from deerflow` line
(the ABC contract) - portability test preserved.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: resolve lint errors (F541 f-string without placeholders, E501 line too long)

* fix(memory): restore hide_from_ui clarification preservation, expose mode

Two memory-system fixes (F541/E501 lint was already fixed on this branch):

- filter_messages_for_memory: restore default preservation of well-formed
  human_input_response clarification answers (v2 regression). The
  self-containment refactor made the bare function skip ALL hide_from_ui when
  no hook was passed, but upstream preserves well-formed clarification
  responses by default (test_hide_from_ui_human_input_response_is_preserved).
  Inline a host-agnostic _is_human_clarification_response mirror of
  read_human_input_response as the default keep-decision; the host-injected
  should_keep_hidden_message hook still overrides (production path unchanged).
  Portable package stays zero `from deerflow`.

- /memory/config: expose `mode` (middleware|tool) in MemoryConfigResponse +
  the config/status endpoints + client.get_memory_config. mode is a host-
  shared, behavior-determining field missing from the response projection.
  Sync tests (mock .mode; e2e assert mode present).

- Align manager_class field docstring with fail-fast behavior.

Tests: filter/self-contained/portability (35) + memory-config (4) pass;
ruff clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(memory): resolve ruff format failures in memory module + tests

`make lint` runs `ruff format --check` in addition to `ruff check`; 8 memory
files had pending format changes -- 7 pre-existing (deer_mem, updater, tools,
test_memory_queue/router/search/tools) + message_processing from the
hide_from_ui fix. Apply `ruff format`: whitespace/wrapping only, no logic
change. 109 memory tests pass; ruff check + format --check both clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(memory): address PR review - legacy field migration, fact_id contract, path/docs

Address willem-bd's review on PR head bc8bf0d4 (risk:high, persistent state):

- config: auto-migrate pre-abstraction top-level memory.* DeerMem fields
  (storage_path, max_facts, debounce_seconds, model_name, token_counting,
  staleness_*, consolidation_*) into backend_config on load + warn, so an
  upgrade does NOT silently revert customized settings (was: silent
  extra='ignore' drop). model_name -> backend_config.model.model. Unknown
  top-level keys warned.
- factory: resolve a relative backend_config.storage_path against runtime_home()
  (base_dir-relative, CWD-independent) to preserve pre-abstraction semantics;
  paths.py stays portable (no runtime_home import).
- tools: memory_add uses the fact_id returned directly by create_fact instead of
  re-deriving it via content-key matching (coupled the tool to the backend's
  content normalization; could misreport a storage cap). create_fact now returns
  (memory_data, fact_id); gateway/client/tool updated. Fix terse
  {"error":"content"} -> {"error":"empty content"}.
- app.py: update stale token_counting=="char" warm-up comment to point at
  manager.warm (DeerMem.warm re-checks char and returns early).
- router: comment explaining reload_memory silent fallback vs fact 501 asymmetry
  (read-only degrade vs write fail-loud).
- CHANGELOG: document breaking changes (/memory/config + client.get_memory_config
  shape flat->backend_config; custom storage_class path moved + __init__ must
  accept config) and the legacy-field auto-migration.
- tests: add regression test pinning the per-user memory path
  ({storage_path}/users/{safe_user_id}/memory.json == host make_safe_user_id)
  across the abstraction; update create_fact mocks for (memory_data, fact_id).

Tests: 273 passed (memory suite); ruff check + format clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(memory): address PR review - storage_path, max_facts, tracing, parsing

Six review findings (willem-bd), each verified against upstream:

- storage_path semantics (file -> root dir): migration drops file-style
  (.json) legacy values with a warning; factory raises if storage_path
  resolves to an existing file (avoid silent NotADirectoryError write
  failure). CHANGELOG + config.example.yaml comment updated.
- create_memory_fact enforces max_facts again (via _trim_facts_to_max) and
  returns (memory, None) when the cap evicts the new fact; memory_add tool
  reports "not stored", client raises ValueError, POST /memory/facts -> 409.
- max_facts trim uses _coerce_source_confidence (was raw f.get("confidence",
  0) -> TypeError on non-float imported/legacy confidence, swallowed as
  silent update failure).
- memory-tracing assistant_id restored to "memory_agent" (was "lead-agent"
  copy-paste; matches upstream + DeerMem run_name).
- _is_human_clarification_response cross-checked against
  read_human_input_response (drift guard test).
- empty-string legacy values skipped silently in migration (narrow fix, not
  broad "if not value" which would skip explicit bool False).

8 new regression tests. make lint + 406 memory tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(memory): address internal review - storage fail-fast, build_llm degrade, config warn, noop template

Addresses 4 findings from the PR #4122 internal supplemental review
(parallel to willem-bd's review, no overlap):

- create_storage fail-fast: a misspelled/unimportable storage_class now
  raises ValueError instead of silently falling back to FileMemoryStorage.
  Memory is persistent state, so a wrong store is a data-integrity footgun;
  mirrors the existing manager_class resolution policy. (storage.py)

- noop template create_fact signature: the commented template used
  keyword-only `content` and returned a bare dict, while DeerMem's actual
  create_fact takes positional `content` and returns tuple[dict, str|None]
  (the memory_add tool passes content positionally; gateway/client/tools all
  tuple-unpack). A backend copied from the template would 500 on fact-CRUD.
  Template fixed; delete_fact/update_fact templates left (callers compatible).
  (noop_manager.py)

- build_llm graceful degrade: wrap init_chat_model in try/except, degrade to
  None + WARNING on failure (mirroring _host_default_llm) so a misconfigured
  explicit model does not crash app startup -- non-LLM memory ops still work
  and an update raises at runtime with the error logged. (llm.py)

- from_backend_config unknown-key warning: log a WARNING for unknown
  backend_config keys (mirrors the host layer's load_memory_config_from_dict)
  so a typo like `storage_pat` does not silently fall back to the default and
  write memory to an unintended location. (config.py)

Tests: rewrote 3 create_storage fallback tests to expect ValueError; added 4
tests (build_llm zero-config/degrade, from_backend_config warn/silent).
make lint green; full memory suite passes.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: lllyfff <2281215061@qq.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: lllyfff <122260771+lllyfff@users.noreply.github.com>
2026-07-15 11:21:04 +08:00

1999 lines
89 KiB
YAML

# Configuration for the DeerFlow application
#
# Guidelines:
# - Copy this file to `config.yaml` and customize it for your environment
# - The default path of this configuration file is `config.yaml` in the project root.
# You can set `DEER_FLOW_PROJECT_ROOT` to define that root explicitly, or use
# `DEER_FLOW_CONFIG_PATH` to point at a specific config file.
# - Runtime state defaults to `.deer-flow` under the project root. Override it
# with `DEER_FLOW_HOME` when you need a different writable data directory.
# - Environment variables are available for all field values. Example: `api_key: $OPENAI_API_KEY`
# - The `use` path is a string that looks like "package_name.sub_package_name.module_name:class_name/variable_name".
# ============================================================================
# Config Version (used to detect outdated config files)
# ============================================================================
# Bump this number when the config schema changes.
# Run `make config-upgrade` to merge new fields into your local config.yaml.
config_version: 25
# ============================================================================
# Logging
# ============================================================================
# Log level for deerflow modules (debug/info/warning/error)
log_level: info
# Request trace correlation for Gateway logs, HTTP response headers, and
# Langfuse metadata. Disabled by default to preserve existing HTTP/log output.
logging:
enhance:
enabled: false
format: text
# ============================================================================
# Tracing / Observability (Monocle)
# ============================================================================
# Optional agent tracing via Monocle. Configured through environment variables
# (MONOCLE_TRACING, MONOCLE_EXPORTERS, OKAHU_API_KEY — like LangSmith/Langfuse),
# not config.yaml keys, and OFF by default. See README.md → "Monocle Tracing"
# for setup, what each exporter captures, and where the trace data goes.
# ============================================================================
# Token Usage
# ============================================================================
# Enable token usage collection and display.
# When enabled, DeerFlow records input/output/total tokens per model call
# and shows usage metadata in the workspace UI when providers return it.
token_usage:
enabled: true
# ============================================================================
# Token Budget — Per-run token limits
# ============================================================================
# Prevents runaway API costs by enforcing hard token limits per run.
# When warn_threshold is crossed, the agent receives an in-context warning.
# When hard_stop_threshold is crossed, tool_calls are stripped and the agent
# is forced to produce a final answer immediately.
token_budget:
enabled: false # Set to true to activate budget enforcement
max_tokens: 200000 # Total token limit (input + output) per run
max_input_tokens: null # Optional separate input-only limit
max_output_tokens: null # Optional separate output-only limit
warn_threshold: 0.8 # Warn at 80% of the budget
hard_stop_threshold: 1.0 # Force stop at 100% of the budget
# ============================================================================
# Recursion Limit — Hard ceiling for a client-supplied run recursion_limit
# ============================================================================
# A run's recursion_limit caps the number of LangGraph super-steps (each is at
# least one LLM call). The Gateway never trusts a client-supplied value
# verbatim: any value above this ceiling is clamped down to it, preventing
# runaway API cost / DoS. Invalid or non-positive client values fall back to
# the server default of 100. Raise this only if you legitimately run very
# deeply nested subagent graphs.
max_recursion_limit: 1000
# ============================================================================
# Models Configuration
# ============================================================================
# Configure available LLM models for the agent to use
#
# Optional per-model pricing (powers the real-cost display on the workspace
# console). Add a `pricing` block to any model entry; use ONE currency across
# all models. Prices are per one million tokens.
#
# pricing:
# currency: CNY # ISO code shown in the console (CNY, USD, ...)
# input_per_million: 8.0 # price per 1M input tokens (cache miss)
# output_per_million: 32.0 # price per 1M output tokens
# input_cache_hit_per_million: 0.8 # price per 1M cache-hit input tokens
# # (optional; omit → hits billed at miss price)
models:
# Example: Volcengine (Doubao) model
# - name: doubao-seed-1.8
# display_name: Doubao-Seed-1.8
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
# model: doubao-seed-1-8-251228
# api_base: https://ark.cn-beijing.volces.com/api/v3
# api_key: $VOLCENGINE_API_KEY
# timeout: 600.0
# max_retries: 2
# supports_thinking: true
# supports_vision: true
# supports_reasoning_effort: true
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# Example: Volcengine Coding Plan (one key, multi-vendor gateway)
# The Coding Plan endpoint (/api/coding/v3) lets you access models from
# Doubao, GLM, DeepSeek, Kimi, and MiniMax with a single API key.
# Each model may differ in thinking/vision support - configure per-model.
#
# - name: glm-5.2-cp
# display_name: GLM-5.2 (Coding Plan)
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
# model: glm-5.2
# api_base: https://ark.cn-beijing.volces.com/api/coding/v3
# api_key: $VOLCENGINE_API_KEY
# timeout: 600.0
# max_retries: 2
# supports_thinking: true
# supports_vision: false
# supports_reasoning_effort: true
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
#
# - name: deepseek-v4-pro-cp
# display_name: DeepSeek-V4-Pro (Coding Plan)
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
# model: deepseek-v4-pro
# api_base: https://ark.cn-beijing.volces.com/api/coding/v3
# api_key: $VOLCENGINE_API_KEY
# timeout: 600.0
# max_retries: 2
# supports_thinking: true
# supports_vision: false
# supports_reasoning_effort: true
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# Example: OpenAI model
# - name: gpt-4
# display_name: GPT-4
# use: langchain_openai:ChatOpenAI
# model: gpt-4
# api_key: $OPENAI_API_KEY # Use environment variable
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# temperature: 0.7
# supports_vision: true # Enable vision support for view_image tool
# Example: OpenAI Responses API model
# - name: gpt-5-responses
# display_name: GPT-5 (Responses API)
# use: langchain_openai:ChatOpenAI
# model: gpt-5
# api_key: $OPENAI_API_KEY
# request_timeout: 600.0
# max_retries: 2
# use_responses_api: true
# output_version: responses/v1
# supports_vision: true
# Example: Ollama (native provider — preserves thinking/reasoning content)
#
# IMPORTANT: Use langchain_ollama:ChatOllama instead of langchain_openai:ChatOpenAI
# for Ollama models. The OpenAI-compatible endpoint (/v1/chat/completions) does NOT
# return reasoning_content as a separate field — thinking content is either flattened
# into <think> tags or dropped entirely (ollama/ollama#15293). The native Ollama API
# (/api/chat) correctly separates thinking from response content.
#
# Install: cd backend && uv pip install 'deerflow-harness[ollama]'
#
# - name: qwen3-local
# display_name: Qwen3 32B (Ollama)
# use: langchain_ollama:ChatOllama
# model: qwen3:32b
# base_url: http://localhost:11434 # No /v1 suffix — uses native /api/chat
# num_predict: 8192
# temperature: 0.7
# reasoning: true # Passes think:true to Ollama native API
# supports_thinking: true
# supports_vision: false
#
# - name: gemma4-local
# display_name: Gemma 4 27B (Ollama)
# use: langchain_ollama:ChatOllama
# model: gemma4:27b
# base_url: http://localhost:11434
# num_predict: 8192
# temperature: 0.7
# reasoning: true
# supports_thinking: true
# supports_vision: true
#
# For Docker deployments, use host.docker.internal instead of localhost:
# base_url: http://host.docker.internal:11434
# Example: Anthropic Claude model (with extended thinking)
# supports_thinking: true is required — without it, DeerFlow silently falls
# back to non-thinking mode even when the UI thinking toggle is on.
# budget_tokens is required by the Anthropic API when thinking.type=enabled
# (no server default; min 1024; must be less than max_tokens).
# - name: claude-sonnet-4
# display_name: Claude Sonnet 4
# use: langchain_anthropic:ChatAnthropic
# model: claude-sonnet-4-20250514
# api_key: $ANTHROPIC_API_KEY
# default_request_timeout: 600.0
# max_retries: 2
# max_tokens: 16000
# supports_vision: true
# supports_thinking: true
# when_thinking_enabled:
# thinking:
# type: enabled
# budget_tokens: 4096 # required; min 1024; must be < max_tokens
# when_thinking_disabled:
# thinking:
# type: disabled
# Example: Google Gemini model (native SDK, no thinking support)
# - name: gemini-2.5-pro
# display_name: Gemini 2.5 Pro
# use: langchain_google_genai:ChatGoogleGenerativeAI
# model: gemini-2.5-pro
# gemini_api_key: $GEMINI_API_KEY
# timeout: 600.0
# max_retries: 2
# max_tokens: 8192
# supports_vision: true
# Example: Gemini model via OpenAI-compatible gateway (with thinking support)
# Use PatchedChatOpenAI so that tool-call thought_signature values on tool_calls
# are preserved across multi-turn tool-call conversations — required by the
# Gemini API when thinking is enabled. See:
# https://docs.cloud.google.com/vertex-ai/generative-ai/docs/thought-signatures
# - name: gemini-2.5-pro-thinking
# display_name: Gemini 2.5 Pro (Thinking)
# use: deerflow.models.patched_openai:PatchedChatOpenAI
# model: google/gemini-2.5-pro-preview # model name as expected by your gateway
# api_key: $GEMINI_API_KEY
# base_url: https://<your-openai-compat-gateway>/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 16384
# supports_thinking: true
# supports_vision: true
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# Example: Xiaomi MiMo model (with thinking support)
# MiMo thinking mode returns reasoning_content and requires that field to be
# replayed on historical assistant messages in multi-turn agent/tool-call
# conversations. Use PatchedChatMiMo instead of plain ChatOpenAI.
# Use https://api.xiaomimimo.com/v1 with pay-as-you-go `sk-...` keys.
# Use your Token Plan regional URL (for example
# https://token-plan-cn.xiaomimimo.com/v1) with Token Plan `tp-...` keys.
# PatchedChatMiMo is model-id agnostic; use it for every MiMo thinking model
# entry you configure (for example mimo-v2.5-pro, mimo-v2.5, mimo-v2-pro,
# mimo-v2-omni, or mimo-v2-flash), including models referenced by subagent
# model overrides.
# See: https://platform.xiaomimimo.com/docs/en-US/usage-guide/passing-back-reasoning_content
# - name: mimo-v2.5-pro
# display_name: MiMo V2.5 Pro
# use: deerflow.models.patched_mimo:PatchedChatMiMo
# model: mimo-v2.5-pro
# api_key: $MIMO_API_KEY
# base_url: https://api.xiaomimimo.com/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 8192
# supports_thinking: true
# supports_vision: false
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# Example: DeepSeek V4 model (with thinking support)
# - name: deepseek-v4
# display_name: DeepSeek V4 (Thinking)
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
# model: deepseek-v4-pro
# api_key: $DEEPSEEK_API_KEY
# timeout: 600.0
# max_retries: 2
# max_tokens: 8192
# supports_thinking: true
# supports_vision: false # DeepSeek V4 does not support vision
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# Example: Kimi K2.5 model
# - name: kimi-k2.5
# display_name: Kimi K2.5
# use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
# model: kimi-k2.5
# api_base: https://api.moonshot.cn/v1
# api_key: $MOONSHOT_API_KEY
# timeout: 600.0
# max_retries: 2
# max_tokens: 32768
# supports_thinking: true
# supports_vision: true # Check your specific model's capabilities
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# Example: Novita AI (OpenAI-compatible)
# Novita provides an OpenAI-compatible API with competitive pricing
# See: https://novita.ai
# - name: novita-deepseek-v3.2
# display_name: Novita DeepSeek V3.2
# use: langchain_openai:ChatOpenAI
# model: deepseek/deepseek-v3.2
# api_key: $NOVITA_API_KEY
# base_url: https://api.novita.ai/openai
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# temperature: 0.7
# supports_thinking: true
# supports_vision: true
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# Example: StepFun (阶跃星辰) reasoning models
# StepFun provides OpenAI-compatible API with reasoning models.
# With reasoning_format: deepseek-style, the API returns reasoning_content
# (same field as DeepSeek), which must be replayed on historical assistant
# messages in multi-turn tool-call conversations.
# Use PatchedChatStepFun instead of plain ChatOpenAI.
# Docs: https://platform.stepfun.com/docs/api-reference/chat-completions
# - name: step-3.7-flash
# display_name: Step 3.7 Flash
# use: deerflow.models.patched_stepfun:PatchedChatStepFun
# model: step-3.7-flash
# api_key: $STEPFUN_API_KEY
# base_url: https://api.stepfun.com/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# supports_thinking: true
# supports_reasoning_effort: true
# supports_vision: true
# when_thinking_enabled:
# extra_body:
# reasoning_format: deepseek-style
# when_thinking_disabled:
# extra_body:
# reasoning_format: deepseek-style
# Example: MiniMax (OpenAI-compatible) - International Edition
# MiniMax provides high-performance models with 512K context window and 128K max output
# Docs: https://platform.minimax.io/docs/api-reference/text-openai-api
# - name: minimax-m3
# display_name: MiniMax M3
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
# model: MiniMax-M3
# api_key: $MINIMAX_API_KEY
# base_url: https://api.minimax.io/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
# supports_vision: true
# supports_thinking: true
# # PatchedChatMiniMax is the MiniMax adapter: it enables reasoning_split and
# # maps MiniMax's structured reasoning into reasoning_content (the field
# # DeerFlow understands), and it strips the per-message `name` field that
# # DeerFlow middlewares attach — MiniMax rejects requests whose user-message
# # names differ with "user name must be consistent (2013)". Declare the
# # thinking toggle so non-thinking paths (flash mode, follow-up suggestions,
# # title/memory generation) truly disable reasoning instead of spending
# # tokens on it.
# when_thinking_enabled:
# extra_body:
# thinking:
# type: adaptive
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# NOTE: M2.x models always think — passing thinking:{type:disabled} has no
# effect (per MiniMax docs), so the toggle above is omitted for M2.7. The
# follow-up-suggestions endpoint strips inline <think> defensively regardless.
# Still use the PatchedChatMiniMax adapter: it strips the per-message `name`
# field DeerFlow middlewares attach, which MiniMax otherwise rejects with
# "user name must be consistent (2013)".
# - name: minimax-m2.7
# display_name: MiniMax M2.7
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
# model: MiniMax-M2.7
# api_key: $MINIMAX_API_KEY
# base_url: https://api.minimax.io/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
# supports_vision: false # M2.7 is text-only; M3 supports vision
# supports_thinking: true
# - name: minimax-m2.7-highspeed
# display_name: MiniMax M2.7 Highspeed
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
# model: MiniMax-M2.7-highspeed
# api_key: $MINIMAX_API_KEY
# base_url: https://api.minimax.io/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
# supports_vision: false # M2.7 is text-only; M3 supports vision
# supports_thinking: true
# Example: MiniMax (OpenAI-compatible) - CN 中国区用户
# MiniMax provides high-performance models with 512K context window and 128K max output
# Docs: https://platform.minimaxi.com/docs/api-reference/text-openai-api
# - name: minimax-m3
# display_name: MiniMax M3
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
# model: MiniMax-M3
# api_key: $MINIMAX_API_KEY
# base_url: https://api.minimaxi.com/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
# supports_vision: true
# supports_thinking: true
# # PatchedChatMiniMax is the MiniMax adapter: it enables reasoning_split and
# # maps MiniMax's structured reasoning into reasoning_content (the field
# # DeerFlow understands), and it strips the per-message `name` field that
# # DeerFlow middlewares attach — MiniMax rejects requests whose user-message
# # names differ with "user name must be consistent (2013)". Declare the
# # thinking toggle so non-thinking paths (flash mode, follow-up suggestions,
# # title/memory generation) truly disable reasoning instead of spending
# # tokens on it.
# when_thinking_enabled:
# extra_body:
# thinking:
# type: adaptive
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# NOTE: M2.x models always think — passing thinking:{type:disabled} has no
# effect (per MiniMax docs), so the toggle above is omitted for M2.7. The
# follow-up-suggestions endpoint strips inline <think> defensively regardless.
# Still use the PatchedChatMiniMax adapter: it strips the per-message `name`
# field DeerFlow middlewares attach, which MiniMax otherwise rejects with
# "user name must be consistent (2013)".
# - name: minimax-m2.7
# display_name: MiniMax M2.7
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
# model: MiniMax-M2.7
# api_key: $MINIMAX_API_KEY
# base_url: https://api.minimaxi.com/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
# supports_vision: false # M2.7 is text-only; M3 supports vision
# supports_thinking: true
# - name: minimax-m2.7-highspeed
# display_name: MiniMax M2.7 Highspeed
# use: deerflow.models.patched_minimax:PatchedChatMiniMax
# model: MiniMax-M2.7-highspeed
# api_key: $MINIMAX_API_KEY
# base_url: https://api.minimaxi.com/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 4096
# temperature: 1.0 # MiniMax requires temperature in (0.0, 1.0]
# supports_vision: false # M2.7 is text-only; M3 supports vision
# supports_thinking: true
# Example: OpenRouter (OpenAI-compatible)
# OpenRouter models use the same ChatOpenAI + base_url pattern as other OpenAI-compatible gateways.
# - name: openrouter-gemini-2.5-flash
# display_name: Gemini 2.5 Flash (OpenRouter)
# use: langchain_openai:ChatOpenAI
# model: google/gemini-2.5-flash-preview
# api_key: $OPENAI_API_KEY
# base_url: https://openrouter.ai/api/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 8192
# temperature: 0.7
# Example: Atlas Cloud (OpenAI-compatible)
# Atlas Cloud exposes a single OpenAI-compatible endpoint in front of many open
# models (DeepSeek, Qwen, Kimi, GLM, MiniMax, Llama, ...), so it uses the same
# ChatOpenAI + base_url pattern as other OpenAI-compatible gateways.
# Browse model ids at https://api.atlascloud.ai/v1/models — see https://atlascloud.ai
# - name: atlascloud-deepseek-v3.2
# display_name: DeepSeek V3.2 (Atlas Cloud)
# use: langchain_openai:ChatOpenAI
# model: deepseek-ai/DeepSeek-V3.2-Exp
# api_key: $ATLASCLOUD_API_KEY
# base_url: https://api.atlascloud.ai/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 8192
# temperature: 0.7
# supports_vision: false
#
# For reasoning models on Atlas Cloud (e.g. a Qwen3 *-thinking id), use the
# patched OpenAI-compatible adapter so reasoning_content is replayed across
# multi-turn tool-call conversations:
# - name: atlascloud-qwen3-thinking
# display_name: Qwen3 235B Thinking (Atlas Cloud)
# use: deerflow.models.patched_openai:PatchedChatOpenAI
# model: qwen/qwen3-235b-a22b-thinking-2507
# api_key: $ATLASCLOUD_API_KEY
# base_url: https://api.atlascloud.ai/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 8192
# supports_thinking: true
# supports_vision: false
# when_thinking_enabled:
# extra_body:
# thinking:
# type: enabled
# when_thinking_disabled:
# extra_body:
# thinking:
# type: disabled
# Example: vLLM 0.19.0 (OpenAI-compatible, with reasoning toggle)
# DeerFlow's vLLM provider preserves vLLM reasoning across tool-call turns and
# toggles Qwen-style reasoning by writing
# extra_body.chat_template_kwargs.enable_thinking=true/false.
# Some reasoning models also require the server to be started with
# `vllm serve ... --reasoning-parser <parser>`.
# - name: qwen3-32b-vllm
# display_name: Qwen3 32B (vLLM)
# use: deerflow.models.vllm_provider:VllmChatModel
# model: Qwen/Qwen3-32B
# api_key: $VLLM_API_KEY
# base_url: http://localhost:8000/v1
# request_timeout: 600.0
# max_retries: 2
# max_tokens: 8192
# supports_thinking: true
# supports_vision: false
# when_thinking_enabled:
# extra_body:
# chat_template_kwargs:
# enable_thinking: true
# Example: Qwen3-Coder deployed on MindIE Engine
# - name: Qwen3_Coder_480B_MindIE
# display_name: Qwen3-Coder-480B (MindIE)
# use: deerflow.models.mindie_provider:MindIEChatModel
# model: Qwen3-Coder-480B-A35B-Instruct-Client
# base_url: http://localhost:8989/v1
# api_key: $OPENAI_API_KEY
# temperature: 0
# max_retries: 1
# supports_thinking: false
# supports_vision: false
# supports_reasoning_effort: false
# # --- Advanced Network Settings ---
# # Due to MindIE's streaming limitations with tool calling, the provider
# # uses mock-streaming (awaiting full generation). Extended timeouts are required.
# read_timeout: 900.0 # 15 minutes to prevent drops during long document generation
# connect_timeout: 30.0
# write_timeout: 60.0
# pool_timeout: 30.0
# ============================================================================
# Tool Groups Configuration
# ============================================================================
# Define groups of tools for organization and access control
tool_groups:
- name: web
- name: file:read
- name: file:write
- name: bash
# ============================================================================
# Tools Configuration
# ============================================================================
# Configure available tools for the agent to use
tools:
# Web search tool (uses DuckDuckGo, no API key required)
- name: web_search
group: web
use: deerflow.community.ddg_search.tools:web_search_tool
max_results: 5
# backend: auto # DDGS backend(s): auto, duckduckgo, brave, wikipedia, etc.
# region: wt-wt # wt-wt is normalized for Wikipedia when backend includes auto/all/wikipedia.
# safesearch: moderate # on, moderate, off
# Web search tool (uses SearXNG, self-hosted, no API key required)
# SearXNG is a free internet metasearch engine which aggregates results from
# various search services. Deploy your own instance: https://github.com/searxng/searxng
# For Docker deployments, use the Docker service name instead of localhost.
# - name: web_search
# group: web
# use: deerflow.community.searxng.tools:web_search_tool
# base_url: http://localhost:8088 # SearXNG instance URL (default: :8088; Docker: http://searxng:8080)
# max_results: 5 # Maximum number of search results
# Web search tool (uses Serper - Google Search API, requires SERPER_API_KEY)
# Serper provides real-time Google Search results. Sign up at https://serper.dev
# Note: set SERPER_API_KEY in your environment before starting the app.
# Avoid putting literal API keys in config.yaml; use the $VAR form instead.
# - name: web_search
# group: web
# use: deerflow.community.serper.tools:web_search_tool
# max_results: 5 # capped at 10 by the Serper provider
# # api_key: $SERPER_API_KEY # Optional explicit env-var reference
# Web search tool (uses Brave Search API, requires BRAVE_SEARCH_API_KEY)
# Brave Search returns results from an independent index. Sign up at
# https://brave.com/search/api/ to get a key. Unlike the DuckDuckGo
# `backend: brave` option above, this calls the official Brave API directly.
# - name: web_search
# group: web
# use: deerflow.community.brave.tools:web_search_tool
# max_results: 5 # Capped at 20 by the Brave Search API
# # api_key: $BRAVE_SEARCH_API_KEY # Optional if the env var is set
# Web search tool (requires Tavily API key)
# - name: web_search
# group: web
# use: deerflow.community.tavily.tools:web_search_tool
# max_results: 5
# # api_key: $TAVILY_API_KEY # Set if needed
# Web search tool (uses InfoQuest, requires InfoQuest API key)
# - name: web_search
# group: web
# use: deerflow.community.infoquest.tools:web_search_tool
# # Used to limit the scope of search results, only returns content within the specified time range. Set to -1 to disable time filtering
# search_time_range: 10
# Web search tool (uses Exa, requires EXA_API_KEY)
# - name: web_search
# group: web
# use: deerflow.community.exa.tools:web_search_tool
# max_results: 5
# search_type: auto # Options: auto, neural, keyword
# contents_max_characters: 1000
# # api_key: $EXA_API_KEY
# Web search tool (uses Firecrawl, requires FIRECRAWL_API_KEY)
# - name: web_search
# group: web
# use: deerflow.community.firecrawl.tools:web_search_tool
# max_results: 5
# # api_key: $FIRECRAWL_API_KEY
# Web search tool (uses GroundRoute, requires GROUNDROUTE_API_KEY)
# GroundRoute is a meta search layer: one API in front of six engines (Serper,
# Brave, Exa, Tavily, Firecrawl, Perplexity). It routes each query to the cheapest
# engine that clears a quality bar and fails over if one is down. Pricing is
# gain-share (you keep about half of any cache savings). Get a key at
# https://groundroute.ai/keys
# - name: web_search
# group: web
# use: deerflow.community.groundroute.tools:web_search_tool
# max_results: 5 # Clamped to 1-50 by GroundRoute
# # api_key: $GROUNDROUTE_API_KEY # Optional if the env var is set
# Web search tool (uses fastCRW - Firecrawl-compatible web scraper, single binary,
# self-host or cloud. Cloud requires CRW_API_KEY; self-host may need no key.)
# - name: web_search
# group: web
# use: deerflow.community.fastcrw.tools:web_search_tool
# max_results: 5
# # api_key: $CRW_API_KEY
# # base_url: https://fastcrw.com/api # default cloud; set to e.g. http://localhost:3000 for self-host
# Web fetch tool (uses Browserless - headless Chrome, self-hosted or cloud)
# Browserless renders pages with a real headless Chrome, ideal for JavaScript-heavy
# sites and SPAs. Deploy your own: https://github.com/browserless/browserless
# For Docker deployments, use the Docker service name instead of localhost.
# NOTE: Only one web_fetch provider can be active at a time.
# Comment out the Jina AI web_fetch entry below before enabling this one.
# - name: web_fetch
# group: web
# use: deerflow.community.browserless.tools:web_fetch_tool
# base_url: http://localhost:3032 # Browserless instance URL (default: :3032; Docker: http://browserless:3000)
# # token: $BROWSERLESS_TOKEN # API token (required for Browserless Cloud; optional for self-hosted)
# timeout_s: 30 # Request timeout in seconds
# # allow_private_addresses: false # SSRF guard: keep false in production. Set true ONLY for intentional internal targets.
# # wait_for_event: "networkidle" # Wait for a page event before returning (e.g. "load", "networkidle")
# # wait_for_timeout_ms: 2000 # Extra wait after page load in milliseconds
# # wait_for_selector: "article" # CSS selector to wait for before returning
# Web fetch tool (uses Crawl4AI - self-hosted headless Chromium, no API key)
# Crawl4AI returns server-cleaned "fit" markdown directly (no readability step needed),
# ideal for JavaScript-heavy sites. Self-host (JWT auth is off by default, no key):
# docker run -d -p 11235:11235 --shm-size=1g unclecode/crawl4ai:0.8.6
# For Docker deployments, use the Docker service name instead of localhost.
# NOTE: Only one web_fetch provider can be active at a time.
# Comment out the Jina AI web_fetch entry below before enabling this one.
# - name: web_fetch
# group: web
# use: deerflow.community.crawl4ai.tools:web_fetch_tool
# base_url: http://localhost:11235 # Crawl4AI server URL (Docker: http://crawl4ai:11235)
# timeout: 30 # Request timeout in seconds
# # allow_private_addresses: false # SSRF guard: keep false in production. Set true ONLY for intentional internal targets.
# # filter: fit # Markdown filter: fit (default) | raw | bm25 | llm
# # token: $CRAWL4AI_TOKEN # Bearer token (only if the server has JWT auth enabled)
# Web capture tool (uses Browserless /screenshot to render a page as an artifact)
# Browserless captures JavaScript-heavy pages with a real headless Chrome and
# writes the screenshot into the current thread's outputs. It can run against
# a self-hosted Browserless instance without a token, or Browserless Cloud with
# BROWSERLESS_TOKEN. For Docker deployments, use the Docker service name instead
# of localhost.
# - name: web_capture
# group: web
# use: deerflow.community.browserless.tools:web_capture_tool
# base_url: http://localhost:3032 # Browserless instance URL (Docker: http://browserless:3000)
# # token: $BROWSERLESS_TOKEN # Required for Browserless Cloud; optional for self-hosted
# timeout_s: 30 # Request timeout in seconds
# output_format: png # png, jpeg, or webp
# full_page: true # Capture entire page instead of viewport only
# viewport_width: 1280
# viewport_height: 720
# # wait_for_selector: "main" # CSS selector to wait for before capturing
# # wait_for_selector_timeout_ms: 5000
# # wait_for_timeout_ms: 1000 # Extra wait after navigation in milliseconds
# # best_attempt: true # Continue with current page state if waits time out
# # allow_private_addresses: false # SSRF guard: keep false in production. Set true ONLY to
# # # capture internal/private targets (loopback, RFC1918, etc.)
# Web fetch tool (uses Exa)
# NOTE: Only one web_fetch provider can be active at a time.
# Comment out the Jina AI web_fetch entry below before enabling this one.
# - name: web_fetch
# group: web
# use: deerflow.community.exa.tools:web_fetch_tool
# # api_key: $EXA_API_KEY
# Web fetch tool (uses Jina AI reader)
- name: web_fetch
group: web
use: deerflow.community.jina_ai.tools:web_fetch_tool
timeout: 10
# Optional proxy for restricted networks / Docker / WSL.
# Use host.docker.internal instead of 127.0.0.1 when the proxy runs on the host.
# proxy: $HTTPS_PROXY
# trust_env: true
# Web fetch tool (uses InfoQuest)
# - name: web_fetch
# group: web
# use: deerflow.community.infoquest.tools:web_fetch_tool
# # Overall timeout for the entire crawling process (in seconds). Set to positive value to enable, -1 to disable
# timeout: 10
# # Waiting time after page loading (in seconds). Set to positive value to enable, -1 to disable
# fetch_time: 10
# # Timeout for navigating to the page (in seconds). Set to positive value to enable, -1 to disable
# navigation_timeout: 30
# Web fetch tool (uses Firecrawl, requires FIRECRAWL_API_KEY)
# - name: web_fetch
# group: web
# use: deerflow.community.firecrawl.tools:web_fetch_tool
# # api_key: $FIRECRAWL_API_KEY
# Web fetch tool (uses GroundRoute, requires GROUNDROUTE_API_KEY)
# Fetches a page's extracted text via GroundRoute mode=page.
# NOTE: Only one web_fetch provider can be active at a time.
# Comment out the Jina AI web_fetch entry above before enabling this one.
# - name: web_fetch
# group: web
# use: deerflow.community.groundroute.tools:web_fetch_tool
# # api_key: $GROUNDROUTE_API_KEY
# Web fetch tool (uses fastCRW - Firecrawl-compatible web scraper, single binary,
# self-host or cloud. Cloud requires CRW_API_KEY; self-host may need no key.)
# NOTE: Only one web_fetch provider can be active at a time.
# Comment out the Jina AI web_fetch entry above before enabling this one.
# - name: web_fetch
# group: web
# use: deerflow.community.fastcrw.tools:web_fetch_tool
# # api_key: $CRW_API_KEY
# # base_url: https://fastcrw.com/api # default cloud; set to e.g. http://localhost:3000 for self-host
# # allow_private_addresses: false # SSRF guard: keep false in production. Set true ONLY for intentional internal targets.
# Image search tool (uses DuckDuckGo)
# Use this to find reference images before image generation
- name: image_search
group: web
use: deerflow.community.image_search.tools:image_search_tool
max_results: 5
# Image search tool (uses InfoQuest)
# - name: image_search
# group: web
# use: deerflow.community.infoquest.tools:image_search_tool
# # Used to limit the scope of image search results, only returns content within the specified time range. Set to -1 to disable time filtering
# image_search_time_range: 10
# # Image size filter. Options: "l" (large), "m" (medium), "i" (icon).
# image_size: "i"
# Image search tool (uses Serper - Google Images API, requires SERPER_API_KEY)
# Serper provides real-time Google Images results. Sign up at https://serper.dev
# Note: set SERPER_API_KEY in your environment before starting the app.
# Avoid putting literal API keys in config.yaml; use the $VAR form instead.
# - name: image_search
# group: web
# use: deerflow.community.serper.tools:image_search_tool
# max_results: 5 # capped at 10 by the Serper provider
# # api_key: $SERPER_API_KEY # Optional explicit env-var reference
# Image search tool (uses Brave Image Search API, requires BRAVE_SEARCH_API_KEY)
# Brave provides independent image results and works alongside the Brave web search tool.
# Note: set BRAVE_SEARCH_API_KEY in your environment before starting the app.
# Avoid putting literal API keys in config.yaml; use the $VAR form instead.
# - name: image_search
# group: web
# use: deerflow.community.brave.tools:image_search_tool
# max_results: 5 # capped at 200 by Brave Image Search
# # country: US
# # search_lang: en
# # safesearch: strict
# # spellcheck: true
# # api_key: $BRAVE_SEARCH_API_KEY # Optional explicit env-var reference
# File operations tools
- name: ls
group: file:read
use: deerflow.sandbox.tools:ls_tool
- name: read_file
group: file:read
use: deerflow.sandbox.tools:read_file_tool
- name: glob
group: file:read
use: deerflow.sandbox.tools:glob_tool
max_results: 200
- name: grep
group: file:read
use: deerflow.sandbox.tools:grep_tool
max_results: 100
- name: write_file
group: file:write
use: deerflow.sandbox.tools:write_file_tool
- name: str_replace
group: file:write
use: deerflow.sandbox.tools:str_replace_tool
# Bash execution tool
# Active only when using an isolated shell sandbox or when
# sandbox.allow_host_bash: true explicitly opts into host bash.
- name: bash
group: bash
use: deerflow.sandbox.tools:bash_tool
# ============================================================================
# Tool Search Configuration (Deferred Tool Loading)
# ============================================================================
# When enabled, MCP tools are not loaded into the agent's context directly.
# Instead, they are listed by name in the system prompt and discoverable
# via the tool_search tool at runtime.
# This reduces context usage and improves tool selection accuracy when
# multiple MCP servers expose a large number of tools.
tool_search:
enabled: false
# When tool_search is enabled, PR1 MCP routing metadata can auto-promote
# matching deferred MCP tool schemas before a model call. This is the maximum
# number of matched schemas promoted per model call. Valid range: 1..5.
auto_promote_top_k: 3
# ============================================================================
# Tool Output Budget Protection
# ============================================================================
# Prevents oversized tool results from blowing the model context window.
# Outputs exceeding `externalize_min_chars` are persisted to disk and replaced
# with a compact preview + file reference. The model can read the full output
# via read_file. When disk persistence is unavailable, outputs exceeding
# `fallback_max_chars` are head+tail truncated instead.
#
# `exempt_tools` prevents persist→read→persist infinite loops for read tools.
# `tool_overrides` allows per-tool threshold customization.
tool_output:
enabled: true
externalize_min_chars: 12000
preview_head_chars: 2000
preview_tail_chars: 1000
fallback_max_chars: 30000
fallback_head_chars: 8000
fallback_tail_chars: 3000
storage_subdir: ".tool-results"
exempt_tools:
- read_file
- read_file_tool
# tool_overrides:
# web_search: 8000
# bash: 20000
# ============================================================================
# Suggestions Configuration
# ============================================================================
# Configure whether the agent automatically generates follow-up question
# suggestions at the end of each response.
suggestions:
enabled: true
# ============================================================================
# Input Polish Configuration
# ============================================================================
# Configure whether the composer can rewrite draft input before sending.
input_polish:
enabled: true
# Maximum draft length accepted by /api/input-polish.
max_chars: 4000
# Optional fast model for draft polishing. Leave null to use the default chat model.
# For best UX, set this to your lowest-latency inexpensive model.
model_name: null
# ============================================================================
# Loop Detection Configuration
# ============================================================================
# Detect and interrupt repeated identical tool-call loops.
# Frequency thresholds are safety limits for repeated use of the same tool type.
loop_detection:
enabled: true
warn_threshold: 3
hard_limit: 5
window_size: 20
max_tracked_threads: 100
tool_freq_warn: 30
tool_freq_hard_limit: 50
# Per-tool overrides for tool_freq_warn / tool_freq_hard_limit. Values can be
# higher or lower than the global defaults. Commonly used to raise thresholds
# for high-frequency tools like bash in batch workflows (e.g. RNA-seq pipelines)
# without weakening protection on every other tool.
# tool_freq_overrides:
# bash:
# warn: 150
# hard_limit: 300
# ============================================================================
# Tool Progress State Machine Configuration (RFC #3177)
# ============================================================================
# Detects tool stagnation and repetition at the (thread, tool) level.
# Tracks consecutive "no-new-info" calls (error, partial_success, near-duplicate success).
# Three transition paths (determined by deerflow_tool_meta.recoverable_by_model):
# recoverable=true (no_results, not_found, permission): ACTIVE → WARNED (terminal; hint re-injected each call)
# recoverable=false (rate_limited, transient): ACTIVE → WARNED → BLOCKED after warn_escalation_count more
# recoverable=false + action=stop (auth, config): ACTIVE → BLOCKED immediately
# Requires ToolErrorHandlingMiddleware to be active (always on).
# tool_progress:
# enabled: false
# stagnation_threshold: 3 # Consecutive problems before WARNED
# warn_escalation_count: 2 # More problems after WARNED before BLOCKED
# inject_assessment: true
# jaccard_similarity_threshold: 0.8 # Word-set similarity threshold for near-duplicate detection
# min_word_count_for_similarity: 10 # Min unique words to apply Jaccard check
# max_tracked_threads: 100
# exempt_tools:
# - ask_clarification
# - write_todos
# - present_files
# - task
# ============================================================================
# Read-Before-Write File Gate (issue #3857)
# ============================================================================
# Blocks write_file (append / overwrite of an existing file) and str_replace
# unless the agent has read the file's current version first; any write
# invalidates earlier reads, forcing a re-read between consecutive edits.
# Deterministic guardrail against blind duplicate appends in long tasks.
read_before_write:
enabled: true
# ============================================================================
# Provider Safety Termination Configuration
# ============================================================================
# Intercept AIMessages where the provider stopped generation for safety reasons
# (e.g. OpenAI finish_reason='content_filter', Anthropic stop_reason='refusal',
# Gemini finish_reason='SAFETY') while still returning tool_calls. The
# tool_calls in such responses are typically truncated/unreliable and must
# not be executed. See issue #3028 for the full failure mode.
#
# Detectors are loaded by class path via reflection (same pattern as
# guardrails / models / tools). The built-in set covers OpenAI-compatible
# content_filter, Anthropic refusal, and Gemini SAFETY/BLOCKLIST/
# PROHIBITED_CONTENT/SPII/RECITATION.
safety_finish_reason:
enabled: true
# Leave `detectors` unset to use the built-in detector set. Set to a
# non-empty list to fully override (use `enabled: false` to disable instead
# of providing an empty list).
#
# Example — extend the OpenAI-compatible detector for a Chinese provider
# whose gateway uses a non-standard finish_reason token:
# detectors:
# - use: deerflow.agents.middlewares.safety_termination_detectors:OpenAICompatibleContentFilterDetector
# config:
# finish_reasons: ["content_filter", "sensitive", "risk_control"]
# - use: deerflow.agents.middlewares.safety_termination_detectors:AnthropicRefusalDetector
# - use: deerflow.agents.middlewares.safety_termination_detectors:GeminiSafetyDetector
#
# Example — add a custom detector for an in-house provider:
# detectors:
# - use: my_company.deerflow_ext:WenxinSafetyDetector
# config:
# error_codes: [336003, 17, 18]
# ============================================================================
# Sandbox Configuration
# ============================================================================
# Choose between local sandbox (direct execution) or Docker-based AIO sandbox
# Option 1: Local Sandbox (Default)
# Executes commands directly on the host machine
uploads:
# Application-level upload limits enforced by the gateway and exposed to the
# frontend before file selection.
max_files: 10
max_file_size: 52428800 # 50 MiB
max_total_size: 104857600 # 100 MiB
# Automatic Office/PDF conversion runs on the backend host before sandbox
# isolation applies. Keep this disabled unless uploads come from a fully
# trusted source and you intentionally accept host-side parser risk.
auto_convert_documents: false
# Controls which PDF-to-Markdown converter is used whenever PDF conversion
# runs. Automatic upload conversion is gated separately by
# auto_convert_documents.
# auto — prefer pymupdf4llm when installed; fall back to MarkItDown for
# image-based or encrypted PDFs (recommended default).
# pymupdf4llm — always use pymupdf4llm (must be installed: uv add pymupdf4llm).
# Better heading/table extraction; faster on most files.
# markitdown — always use MarkItDown (original behaviour, no extra dependency).
pdf_converter: auto
sandbox:
use: deerflow.sandbox.local:LocalSandboxProvider
# Host bash execution is disabled by default because LocalSandboxProvider is
# not a secure isolation boundary for shell access. Enable only for fully
# trusted, single-user local workflows.
allow_host_bash: false
# Optional: Mount additional host directories into the sandbox.
# Each mount maps a host path to a virtual container path accessible by the agent.
# Note: with LocalSandboxProvider under `make up` (docker-compose), host_path is
# checked from inside the deer-flow-gateway container — you must also bind-mount
# the same directory into services.gateway.volumes in docker/docker-compose.yaml
# for this mount to take effect (see issue #3244).
# mounts:
# - host_path: /home/user/my-project # Absolute path; see note above for Docker mode
# container_path: /mnt/my-project # Virtual path inside the sandbox
# read_only: true # Whether the mount is read-only (default: false)
# Tool output truncation limits (characters).
# bash uses middle-truncation (head + tail) since errors can appear anywhere in the output.
# read_file and ls use head-truncation since their content is front-loaded.
# Set to 0 to disable truncation.
bash_output_max_chars: 20000
read_file_output_max_chars: 50000
ls_output_max_chars: 20000
# Maximum wall-clock seconds a single host bash command may run before it is
# terminated (process group and all). A blocking foreground command — e.g. a
# server started without backgrounding — is killed after this long so the
# agent's turn cannot hang. Start long-lived processes in the background with
# output redirected (e.g. `your-command > /tmp/server.log 2>&1 &`) when you
# need logs; unredirected background output is drained with bounded capture
# and excess output is discarded.
bash_command_timeout: 600
# Option 2: Container-based AIO Sandbox
# Executes commands in isolated containers (Docker or Apple Container)
# On macOS: Automatically prefers Apple Container if available, falls back to Docker
# On other platforms: Uses Docker
# Uncomment to use:
# sandbox:
# use: deerflow.community.aio_sandbox:AioSandboxProvider
#
# # Optional: Container image to use (works with both Docker and Apple Container)
# # Default: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
# # The mirror's `:latest` tag is frozen on an old pre-1.9.3 digest that lacks
# # the /v1/bash/* routes required-secrets skills need (see #3921/#3922), so
# # pin an explicit version instead — recommended: 1.11.0 (multi-arch, works
# # on both x86_64 and arm64). Custom images should extend the default image
# # or implement the same AIO sandbox HTTP API used by agent-sandbox. See
# # backend/docs/CONFIGURATION.md.
# # image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:1.11.0
#
# # Optional: Base port for sandbox containers (default: 8080)
# # port: 8080
# # Optional: Maximum number of concurrent sandbox containers (default: 3)
# # When the limit is reached the least-recently-used sandbox is evicted to
# # make room for new ones. Use a positive integer here; omit this field to use the default.
# # replicas: 3
#
# # Optional: Prefix for container names (default: deer-flow-sandbox)
# # container_prefix: deer-flow-sandbox
#
# # Optional: Additional mount directories from host to container
# # NOTE: Skills directory is automatically mounted from skills.path to skills.container_path
# # mounts:
# # # Other custom mounts
# # - host_path: /path/on/host
# # container_path: /home/user/shared
# # read_only: false
# #
# # # DeerFlow will surface configured container_path values to the agent,
# # # so it can directly read/write mounted directories such as /home/user/shared
#
# # Optional: Environment variables to inject into the sandbox container
# # Values starting with $ will be resolved from host environment variables
# # environment:
# # NODE_ENV: production
# # DEBUG: "false"
# # API_KEY: $MY_API_KEY # Reads from host's MY_API_KEY env var
# # DATABASE_URL: $DATABASE_URL # Reads from host's DATABASE_URL env var
# Option 3: BoxLite micro-VM Sandbox
# Runs each sandbox as a BoxLite micro-VM. Released boxes stay in an in-process
# warm pool and can be reclaimed by the same user/thread without a cold start.
# Requires the boxlite runtime and host virtualization support (KVM on Linux,
# Hypervisor.framework on macOS).
# sandbox:
# use: deerflow.community.boxlite:BoxliteProvider
# image: python:3.12-slim
#
# # Optional: Per-box memory and CPU limits.
# # memory_mib: 1024
# # cpus: 2
#
# # Optional: Maximum active + warm BoxLite VMs per gateway process (default: 3).
# # Active boxes are never evicted; only warm-pool boxes are stopped to make room.
# # replicas: 3
#
# # Optional: Seconds before an idle warm-pool VM is stopped (default: 600).
# # Set to 0 to keep warm VMs until shutdown or replica eviction.
# # idle_timeout: 600
#
# # Optional: Skip the reclaim health check for very recently released VMs.
# # Default 0.0 keeps reliability-first validation before warm reuse.
# # health_check_skip_seconds: 0.0
#
# # Optional: Environment variables to inject into every command.
# # environment:
# # PYTHONUNBUFFERED: "1"
#
# Option 4: Provisioner-managed AIO Sandbox (docker-compose-dev)
# Each sandbox_id gets a dedicated Pod in k3s, managed by the provisioner.
# Recommended for production or advanced users who want better isolation and scalability.:
# sandbox:
# use: deerflow.community.aio_sandbox:AioSandboxProvider
# provisioner_url: http://provisioner:8002
# # API key for provisioner authentication. Must match PROVISIONER_API_KEY
# # set on the provisioner container. Both sides must have the same value set;
# # the provisioner rejects all /api/* requests when PROVISIONER_API_KEY is unset.
# # Generate a strong key: openssl rand -hex 32
# # provisioner_api_key: $PROVISIONER_API_KEY
# # Note: provisioner-created Pods use the provisioner's SANDBOX_IMAGE
# # environment variable, not sandbox.image from this config file.
# ============================================================================
# Subagents Configuration
# ============================================================================
# Configure timeouts for subagent execution
# Subagents are background workers delegated tasks by the lead agent
# subagents:
# # Default timeout (seconds) for built-in subagents (default: 1800 = 30 min).
# # Custom agents use their own timeout_seconds (default 900) unless overridden.
# timeout_seconds: 1800
# # Optional global max-turn override for all subagents.
# # Built-in defaults: general-purpose=150, bash=60. Leave unset to keep them.
# # max_turns: 120
#
# # Total number of subagent delegations allowed in one lead-agent run.
# # This is a deterministic backstop against repeated planning checkpoints
# # launching legal-sized batches forever. The default 6 allows two full
# # batches at the default concurrency of 3. Valid config range: 1-50.
# # Per-request runtime context can temporarily override this with
# # `max_total_subagents`, clamped to the same 1-50 range.
# max_total_per_run: 6
#
# # Per-run token ceiling for subagents (#3875 Phase 2). A backstop against a
# # subagent that burns tokens on trivial work. At the hard-stop threshold the
# # in-flight turn is capped (tool calls stripped, finish_reason forced to
# # "stop") so the run completes naturally with a final answer; the result is
# # stamped `completed` + `subagent_stop_reason=token_capped` so the lead and
# # UI can tell a budget-capped completion from a clean one. The 2,000,000
# # default is a generous ceiling — lower it to tighten cost controls. A
# # per-agent `token_budget` override (see `agents:` below) wins over this.
# # token_budget:
# # enabled: true
# # max_tokens: 2000000
# # warn_threshold: 0.7 # log a warning once this fraction of the budget is spent
#
# # Optional per-agent overrides (applies to both built-in and custom agents)
# agents:
# general-purpose:
# timeout_seconds: 2700 # 45 minutes for very long deep-research tasks
# max_turns: 250 # raise above the 150 default for very deep tasks
# # token_budget: # per-agent override of the global token_budget above
# # max_tokens: 3000000 # raise the ceiling for deep-research tasks
# # model: qwen3:32b # Use a specific model (default: inherit from lead agent)
# # skills: # Skill whitelist (default: inherit all enabled skills)
# # - web-search
# # - data-analysis
# bash:
# timeout_seconds: 300 # 5 minutes for quick command execution
# max_turns: 80
# # skills: [] # No skills for bash agent
#
# # Custom subagent types: define specialized agents with their own prompts,
# # tools, skills, and model configuration. Custom agents are available via
# # the `task` tool alongside built-in types (general-purpose, bash).
# # custom_agents:
# # analysis:
# # description: "Data analysis specialist for processing datasets and generating insights"
# # system_prompt: |
# # You are a data analysis subagent. Focus on:
# # - Processing and analyzing datasets
# # - Generating visualizations
# # - Providing statistical insights
# # tools: # Tool whitelist (null = inherit all)
# # - bash
# # - read_file
# # - write_file
# # skills: # Skill whitelist (null = inherit all, [] = none)
# # - data-analysis
# # - visualization
# # model: inherit # 'inherit' uses parent's model
# # max_turns: 80
# # timeout_seconds: 600
#
# # Model override: by default, subagents inherit the lead agent's model.
# # Set `model` to use a different model (e.g., a local Ollama model for cost savings).
# # The model name must match a name defined in the `models:` section above.
# ============================================================================
# ACP Agents Configuration
# ============================================================================
# Configure external ACP-compatible agents for the built-in `invoke_acp_agent` tool.
# acp_agents:
# claude_code:
# # DeerFlow expects an ACP adapter here. The standard `claude` CLI does not
# # speak ACP directly. Install `claude-agent-acp` separately or use:
# command: npx
# args: ["-y", "@zed-industries/claude-agent-acp"]
# description: Claude Code for implementation, refactoring, and debugging
# model: null
# # auto_approve_permissions: false # Set to true to auto-approve ACP permission requests
# # env: # Optional: inject environment variables into the agent subprocess
# # ANTHROPIC_API_KEY: $ANTHROPIC_API_KEY # $VAR resolves from host environment
#
# codex:
# # DeerFlow expects an ACP adapter here. The standard `codex` CLI does not
# # speak ACP directly. Install `codex-acp` separately or use:
# command: npx
# args: ["-y", "@zed-industries/codex-acp"]
# description: Codex CLI for repository tasks and code generation
# model: null
# # auto_approve_permissions: false # Set to true to auto-approve ACP permission requests
# # env: # Optional: inject environment variables into the agent subprocess
# # OPENAI_API_KEY: $OPENAI_API_KEY # $VAR resolves from host environment
# ============================================================================
# Skills Configuration
# ============================================================================
# Configure skills directory for specialized agent workflows
skills:
# Path to skills directory on the host (relative to project root or absolute)
# Default: skills under the project root
# Override with DEER_FLOW_SKILLS_PATH when this field is omitted.
# Uncomment to customize:
# path: /absolute/path/to/custom/skills
# Path where skills are mounted in the sandbox container
# This is used by the agent to access skills in both local and Docker sandbox
# Default: /mnt/skills
container_path: /mnt/skills
# Deferred skill discovery (default: false)
# When enabled, only skill names appear in the system prompt (<skill_index>).
# The LLM discovers skill details on demand via the describe_skill tool.
# This keeps the system prompt compact and prefix-cache friendly when many
# skills are installed.
# deferred_discovery: true
# ============================================================================
# SkillScan Configuration
# ============================================================================
# Native deterministic skill safety scanning. This runs before the LLM skill
# scanner on skill install/update and agent-managed skill writes.
skill_scan:
# Set false to disable the new deterministic analyzers. Safe archive
# extraction and the LLM skill scanner still run.
enabled: true
# Note: To restrict which skills are loaded for a specific custom agent,
# define a `skills` list in that agent's `config.yaml` (e.g. `agents/my-agent/config.yaml`):
# - Omitted or null: load all globally enabled skills (default)
# - []: disable all skills for this agent
# - ["skill-name"]: load only specific skills
# ============================================================================
# Title Generation Configuration
# ============================================================================
# Automatic conversation title generation settings
title:
enabled: true
max_words: 6
max_chars: 60
model_name: null # null = fast local fallback; set a model name to use LLM title generation
# ============================================================================
# Summarization Configuration
# ============================================================================
# Automatically summarize conversation history when token limits are approached
# This helps maintain context in long conversations without exceeding model limits
summarization:
enabled: true
# Model to use for summarization (null = use default model)
# Recommended: Use a lightweight, cost-effective model like "gpt-4o-mini" or similar
model_name: null
# Trigger conditions - at least one required
# Summarization runs when ANY threshold is met (OR logic)
# You can specify a single trigger or a list of triggers
trigger:
# Trigger when token count reaches 32000
- type: tokens
value: 32000
# Uncomment to also trigger when message count reaches 50
# - type: messages
# value: 50
# Uncomment to trigger when 80% of model's max input tokens is reached
# - type: fraction
# value: 0.8
# Context retention policy after summarization
# Specifies how much recent history to preserve
keep:
# Keep the most recent 10 messages (recommended)
type: messages
value: 10
# Alternative: Keep specific token count
# type: tokens
# value: 3000
# Alternative: Keep percentage of model's max input tokens
# type: fraction
# value: 0.3
# Maximum tokens to keep when preparing messages for summarization
# Set to null to skip trimming (not recommended for very long conversations)
trim_tokens_to_summarize: 15564
# Custom summary prompt template (null = use default LangChain prompt)
# The prompt should guide the model to extract important context
summary_prompt: null
# Loaded SKILL.md references (read_file calls under skills.container_path) are
# captured into the durable skill_context channel and re-injected after
# compaction as name/path/description reminders. The full skill body is not
# persisted; the agent should re-read the file before applying instructions.
# Tool names counted as skill reads:
# Legacy preserve_recent_skill_* summarization settings are no longer used;
# skill retention is handled by this durable reference channel instead. Set
# this list to [] to disable durable skill-reference capture.
skill_file_read_tool_names:
- read_file
- read
- view
- cat
# ============================================================================
# Memory Configuration
# ============================================================================
# Global memory mechanism (pluggable + self-contained).
#
# Shared fields (host level, backend-agnostic):
# enabled - Master switch for the memory mechanism (call-site gate)
# injection_enabled - Whether to inject memory into the system prompt (call-site gate)
# manager_class - Backend selector: registered name (deermem/noop) or dotted path
# backend_config - Backend-private config dict (passthrough; each backend self-interprets)
#
# DeerMem-private fields live under ``backend_config`` (NOT at the memory: top level):
# storage_path - Data root. Empty = deer-flow base_dir (factory injects absolute
# runtime_home). Absolute path = that root. Relative = CWD-relative.
# model - LLM config for memory extraction: {provider, model, api_key, base_url,
# temperature}. Omit all fields = no extraction (non-LLM ops still work).
# debounce_seconds - Debounce wait before processing queued updates (default: 30)
# max_facts - Maximum facts to store (default: 100)
# fact_confidence_threshold - Minimum confidence for storing facts (default: 0.7)
# max_injection_tokens - Token budget for memory injection (default: 2000)
# token_counting - tiktoken (accurate, network on first use) or char (network-free)
# guaranteed_categories - Fact categories always injected (default: ["correction"])
# guaranteed_token_budget - Token ceiling for guaranteed categories (default: 500)
# staleness_review_enabled - Enable staleness pruning (default: true)
# staleness_age_days - Age threshold for staleness candidates (default: 90)
# staleness_min_candidates - Minimum stale facts to trigger review (default: 3)
# staleness_max_removals_per_cycle - Safety cap on removals per cycle (default: 10)
# staleness_protected_categories - Categories exempt from staleness review (default: ["correction"])
memory:
enabled: true
injection_enabled: true # Whether to inject memory into system prompt
# Memory backend selector. Either a registered backend name (matching a
# backends/<name>/ folder that exposes MANAGER_CLASS, e.g. deermem / noop)
# or a dotted import path to a MemoryManager subclass.
manager_class: deermem
# Memory operation mode:
# middleware (default) - passive background extraction after each turn.
# tool - experimental opt-in; the model calls memory_search/memory_add/
# memory_update/memory_delete directly. This gives the model agency over
# memory writes, but effectiveness depends on model tool-use behavior.
# Only one mode runs at a time. (tool mode calls the MemoryManager ABC --
# memory_search/add/update/delete go through the active backend; backends
# without fact-CRUD return a JSON error instead of crashing.)
mode: middleware
# Backend-private config (a dict), passed verbatim to the backend __init__.
# Each backend self-interprets it (DeerMem parses it into DeerMemConfig).
backend_config:
storage_path: "" # empty = deer-flow base_dir (factory injects absolute runtime_home); a non-empty path is the root DIRECTORY (per-user memory under {storage_path}/users/{uid}/memory.json)
debounce_seconds: 30 # Wait time before processing queued updates
model: # LLM for memory extraction; omit all fields = no extraction (non-LLM ops still work; an update raises)
# provider: openai
# model: gpt-4o-mini
# api_key: $OPENAI_API_KEY
# base_url: # optional, for OpenAI-compatible gateways (e.g. DeepSeek)
# temperature: # optional
max_facts: 100 # Maximum number of facts to store
fact_confidence_threshold: 0.7 # Minimum confidence for storing facts
max_injection_tokens: 2000 # Maximum tokens for memory injection
# Token counting strategy for memory-injection budgeting:
# tiktoken (default) - accurate, but the encoding BPE data may be
# downloaded from a public network endpoint on first use. In
# network-restricted environments this download can block for a long
# time (see issues #3402 / #3429). Pre-cache the encoding or set this
# to "char" to avoid it.
# char - network-free CJK-aware character-based estimate; never touches
# tiktoken. Slightly less precise budgeting, zero network I/O.
token_counting: tiktoken
# Guaranteed injection: fact categories that bypass the regular token budget
# and draw from a reserved allowance, so high-signal corrections (e.g.
# "don use pip, use uv") survive even when the budget is tight.
guaranteed_categories:
- correction
guaranteed_token_budget: 500
# Staleness review: periodically prune aged facts that may no longer reflect
# the user current situation. When triggered, the LLM reviews facts older
# than staleness_age_days during the normal memory-update call (same LLM
# invocation - no extra API call) and decides KEEP or REMOVE for each.
staleness_review_enabled: true
staleness_age_days: 90
staleness_min_candidates: 3
staleness_max_removals_per_cycle: 10
staleness_protected_categories:
- correction
# Memory consolidation (opt-in, lossy: source facts are replaced by a
# synthesized one; only consolidatedFrom IDs are kept). Runs in the same
# memory-update LLM call as extraction/staleness - no extra API cost.
# consolidation_enabled defaults to false because consolidation is lossy.
consolidation_enabled: false # set true to opt into fact consolidation
consolidation_min_facts: 8 # min facts in one category to trigger review (3-30)
consolidation_max_groups_per_cycle: 3 # max groups merged per update cycle (1-10)
consolidation_max_sources: 8 # max source facts per consolidation group (2-20)
# ============================================================================
# Custom Agent Management API
# ============================================================================
# Controls whether the HTTP gateway exposes custom-agent SOUL/USER.md management.
# Keep this disabled unless the gateway is behind a trusted authenticated admin boundary.
agents_api:
enabled: false
# ============================================================================
# Skill Self-Evolution Configuration
# ============================================================================
# Allow the agent to autonomously create and improve skills in skills/custom/.
skill_evolution:
enabled: false # Set to true to allow agent-managed writes under skills/custom
moderation_model_name: null # Model for LLM-based security scanning (null = use default model)
# ============================================================================
# Checkpointer Configuration (DEPRECATED — use `database` instead)
# ============================================================================
# Legacy standalone checkpointer config. Kept for backward compatibility.
# Prefer the unified `database` section below, which drives the LangGraph
# checkpointer, LangGraph Store, and DeerFlow application data (runs,
# feedback, events) from a single backend setting.
#
# If both `checkpointer` and `database` are present, `checkpointer`
# takes precedence for the LangGraph checkpointer and Store only.
#
# checkpointer:
# type: sqlite
# connection_string: checkpoints.db
#
# checkpointer:
# type: postgres
# connection_string: postgresql://user:password@localhost:5432/deerflow
# ============================================================================
# Database
# ============================================================================
# Unified storage backend for the LangGraph checkpointer, LangGraph Store,
# and DeerFlow application data (runs, threads metadata, feedback, etc.).
#
# backend: memory -- No persistence, data lost on restart
# backend: sqlite -- Single-node deployment, files in sqlite_dir
# backend: postgres -- Production multi-node deployment
#
# If this section is omitted or empty in config.yaml, DeerFlow uses:
# backend: sqlite
# sqlite_dir: .deer-flow/data
#
# SQLite mode uses a single deerflow.db file with WAL journal mode
# for the checkpointer, Store, and application data.
#
# Postgres mode: put your connection URL in .env as DATABASE_URL,
# then reference it here with $DATABASE_URL.
#
# Install the driver — Issue #2754 fix lands `UV_EXTRAS` in every code path:
# Local `make dev` auto-detects from `database.backend: postgres` below
# and passes `--extra postgres` to `uv sync` on every restart, so
# the extra is no longer wiped. To opt in explicitly (or layer
# extras like `postgres,ollama`), set in project-root .env:
# UV_EXTRAS=postgres
# Docker dev `make docker-start` reads `UV_EXTRAS` from project-root .env via
# `env_file`. Set:
# UV_EXTRAS=postgres
# Multiple extras (`postgres,ollama`) supported here too — see
# docker/dev-entrypoint.sh.
# Docker img build-arg `UV_EXTRAS=postgres,discord docker compose build`
# supports comma- or whitespace-separated extras at build time
# (backend/Dockerfile expands them to repeated `--extra` flags).
#
# First-time bootstrap (before `make dev`):
# cd backend && uv sync --all-packages --extra postgres
# (--all-packages propagates the extra into workspace members — see PR #2584)
#
# NOTE: When both `checkpointer` and `database` are configured,
# `checkpointer` takes precedence for the LangGraph checkpointer and Store;
# `database` still controls DeerFlow application data.
# If you use `database`, you can remove the `checkpointer` section.
# database:
# backend: sqlite
# sqlite_dir: .deer-flow/data
#
# database:
# backend: postgres
# postgres_url: $DATABASE_URL
database:
backend: sqlite
sqlite_dir: .deer-flow/data
# ============================================================================
# Run Events Configuration
# ============================================================================
# Storage backend for run events (messages + execution traces).
#
# backend: memory -- No persistence, data lost on restart (default)
# backend: db -- SQL database via ORM, full query capability (production)
# backend: jsonl -- Append-only JSONL files (lightweight single-node persistence)
#
# run_events:
# backend: memory
# max_trace_content: 10240 # Truncation threshold for trace content (db backend, bytes)
# track_token_usage: true # Accumulate token counts to RunRow
run_events:
backend: memory
max_trace_content: 10240
track_token_usage: true
# ============================================================================
# Scheduled Tasks Configuration
# ============================================================================
# Background scheduler for one-time and recurring (cron) agent runs.
# All fields are restart-required (captured at Gateway lifespan startup).
#
# Multi-worker note: the scheduler runs once per uvicorn worker. SQLite silently
# ignores row-level locks, so multiple workers can double-fire the same task.
# For multi-worker deployments (GATEWAY_WORKERS > 1), use the Postgres database
# backend, where FOR UPDATE SKIP LOCKED serializes claims correctly.
#
# scheduler:
# enabled: false # Master switch for the background poller
# poll_interval_seconds: 5 # How often to scan for due tasks
# lease_seconds: 120 # Claim lease; a crashed process's task becomes reclaimable after this
# max_concurrent_runs: 3 # Global cap on active scheduled runs; each poll claims only into the remaining budget
# min_once_delay_seconds: 60 # Minimum future offset for one-time tasks at creation time
scheduler:
enabled: false
poll_interval_seconds: 5
lease_seconds: 120
max_concurrent_runs: 3
min_once_delay_seconds: 60
# ============================================================================
# Run Ownership Configuration
# ============================================================================
# Controls cross-process run ownership for multi-worker deployments.
# When GATEWAY_WORKERS > 1, each worker claims runs with a lease; the heartbeat
# renews leases, and reconciliation recovers orphaned runs from crashed workers.
#
# CLOCK-SYNC REQUIREMENT (multi-worker only): reconciliation compares another
# worker's UTC lease timestamp against this worker's datetime.now(UTC). Worker
# clocks MUST be synced (NTP / chrony / systemd-timesyncd — default on K8s and
# cloud VMs) within a few seconds. grace_seconds is the skew budget; worst case
# (owning worker's heartbeat just about to fire), a peer whose clock is more
# than grace_seconds ahead can mis-reclaim a still-live run as an orphan. Raise
# grace_seconds if your environment cannot keep clocks within a few seconds;
# the trade-off is longer recovery latency for genuinely dead workers
# (lease_seconds + grace_seconds from last heartbeat to reclaim).
run_ownership:
lease_seconds: 30 # Seconds before a run lease expires if not renewed.
# Heartbeat renews every lease_seconds / 3.
grace_seconds: 10 # Extra seconds past expiry before reclaiming an orphaned run.
# Also the cross-worker clock-skew budget — see note above.
heartbeat_enabled: false # Set to true for GATEWAY_WORKERS > 1
# ============================================================================
# Stream Bridge Configuration
# ============================================================================
# The stream bridge carries live agent events from gateway workers to SSE
# clients. Docker Compose sets DEER_FLOW_STREAM_BRIDGE_REDIS_URL automatically,
# so Docker deployments use Redis Streams even if this section is omitted.
#
# The redis bridge requires the optional `redis` extra. It is auto-detected from
# this section on `make dev`, and always installed in the Docker image. To install
# it manually: cd backend && uv sync --all-packages --extra redis
#
# stream_bridge:
# type: memory # single-process only
# queue_maxsize: 256
#
# stream_bridge:
# type: redis # recommended for Docker / multi-worker gateway
# redis_url: redis://redis:6379/0
# queue_maxsize: 256 # events retained per run (redis stream MAXLEN)
# stream_ttl_seconds: 86400 # rolling TTL for retained stream buffers.
# # Refreshed on each publish/publish_end; set 0
# # to disable. This is not a run timeout.
# recovered_stream_cleanup_delay_seconds: 60 # seconds to wait after
# # publishing END for a recovered orphan run
# # before deleting the stream key.
# max_connections: 100 # optional pool ceiling. Each live SSE client
# # holds one connection blocked in XREAD ... BLOCK
# # for up to heartbeat_interval (15s), so hundreds
# # of concurrent clients open hundreds of
# # connections. Unset = redis-py default (unbounded).
#
# NOTE: the redis bridge is fail-hard in v1. Redis.from_url is lazy, so a down
# Redis does not block gateway startup, but the first publish/xread raises. A
# mid-run Redis outage fails the active run — there is no automatic retry/backoff
# or fallback to the in-memory bridge. Run Redis with HA / a restart policy.
# ============================================================================
# User-Owned IM Channel Connections
# ============================================================================
# Lets logged-in users connect their own IM accounts from the DeerFlow frontend
# while reusing the existing `channels` runtime configuration below.
#
# Security notes:
# - No public IP, OAuth callback URL, or provider webhook is required.
# - Provider bot/app credentials stay under `channels.*`.
# - `channel_connections` stores per-user bindings and one-time connect codes.
# - Telegram uses a deep link when `bot_username` is configured.
# - Slack, Discord, Feishu, DingTalk, WeChat, and WeCom use `/connect <code>`
# through the already-running bot/app.
#
# channel_connections:
# enabled: false
# # Security: keep this enabled unless you intentionally want legacy open-bot behavior.
# # Disabling it lets unbound external IM users create DeerFlow threads/runs.
# require_bound_identity: true
#
# telegram:
# enabled: false
# bot_username: $TELEGRAM_BOT_USERNAME
#
# slack:
# enabled: false
#
# discord:
# enabled: false
#
# feishu:
# enabled: false
#
# dingtalk:
# enabled: false
#
# wechat:
# enabled: false
#
# wecom:
# enabled: false
# ============================================================================
# IM Channels Configuration
# ============================================================================
# Connect DeerFlow to external messaging platforms.
# All channels use outbound connections (WebSocket or polling) — no public IP required.
# channels:
# # LangGraph-compatible Gateway API base URL for thread/message management (default: http://localhost:8001/api)
# # For Docker deployments, use the Docker service name instead of localhost:
# # langgraph_url: http://gateway:8001/api
# # gateway_url: http://gateway:8001
# langgraph_url: http://localhost:8001/api
# # Gateway API URL for auxiliary queries like /models, /memory (default: http://localhost:8001)
# gateway_url: http://localhost:8001
# #
# # Docker Compose note:
# # If channels run inside the gateway container, use container DNS names instead
# # of localhost, for example:
# # langgraph_url: http://gateway:8001/api
# # gateway_url: http://gateway:8001
# # You can also set DEER_FLOW_CHANNELS_LANGGRAPH_URL / DEER_FLOW_CHANNELS_GATEWAY_URL.
#
# # Optional: default mobile/session settings for all IM channels
# session:
# assistant_id: lead_agent # or a custom agent name; custom agents route via lead_agent + agent_name
# config:
# recursion_limit: 100
# context:
# thinking_enabled: true
# is_plan_mode: false
# subagent_enabled: false
#
# feishu:
# enabled: false
# app_id: $FEISHU_APP_ID
# app_secret: $FEISHU_APP_SECRET
# # domain: https://open.feishu.cn # China (default)
# # domain: https://open.larksuite.com # International
#
# slack:
# enabled: false
# bot_token: $SLACK_BOT_TOKEN # xoxb-...
# app_token: $SLACK_APP_TOKEN # xapp-... (Socket Mode)
# allowed_users: [] # empty = allow all; can also be a single Slack user ID string, e.g. U123456, but list form is recommended
#
# telegram:
# enabled: false
# bot_token: $TELEGRAM_BOT_TOKEN
# allowed_users: [] # empty = allow all
#
# wechat:
# enabled: false
# bot_token: $WECHAT_BOT_TOKEN
# ilink_bot_id: $WECHAT_ILINK_BOT_ID
# # Optional: allow first-time QR bootstrap when bot_token is absent
# qrcode_login_enabled: true
# # Optional: sent as iLink-App-Id header when provided
# ilink_app_id: ""
# # Optional: sent as SKRouteTag header when provided
# route_tag: ""
# allowed_users: [] # empty = allow all
# # Optional: long-polling timeout in seconds
# polling_timeout: 35
# # Optional: QR poll interval in seconds when qrcode_login_enabled is true
# qrcode_poll_interval: 2
# # Optional: QR bootstrap timeout in seconds
# qrcode_poll_timeout: 180
# # Optional: persist getupdates cursor under the gateway container volume
# state_dir: ./.deer-flow/wechat/state
# # Optional: max inbound image size in bytes before skipping download
# max_inbound_image_bytes: 20971520
# # Optional: max outbound image size in bytes before skipping upload
# max_outbound_image_bytes: 20971520
# # Optional: max inbound file size in bytes before skipping download
# max_inbound_file_bytes: 52428800
# # Optional: max outbound file size in bytes before skipping upload
# max_outbound_file_bytes: 52428800
# # Optional: allowed file extensions for regular file receive/send
# allowed_file_extensions: [".txt", ".md", ".pdf", ".csv", ".json", ".yaml", ".yml", ".xml", ".html", ".log", ".zip", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".rtf"]
#
# # Optional: channel-level session overrides
# session:
# assistant_id: mobile-agent # custom agent names are supported here too
# context:
# thinking_enabled: false
#
# # Optional: per-user overrides by user_id
# users:
# "123456789":
# assistant_id: vip-agent
# config:
# recursion_limit: 150
# context:
# thinking_enabled: true
# subagent_enabled: true
# wecom:
# enabled: false
# bot_id: $WECOM_BOT_ID
# bot_secret: $WECOM_BOT_SECRET
#
# dingtalk:
# enabled: false
# client_id: $DINGTALK_CLIENT_ID
# client_secret: $DINGTALK_CLIENT_SECRET
# allowed_users: [] # empty = allow all
# card_template_id: "" # Optional: AI Card template ID for streaming updates
#
# discord:
# enabled: false
# bot_token: $DISCORD_BOT_TOKEN
# allowed_guilds: [] # empty = allow all guilds; can also be a single guild ID
# mention_only: false # If true, only respond when the bot is mentioned
# allowed_channels: [] # Optional: channel IDs exempt from mention_only (bot responds without mention)
# thread_mode: false # If true, group a channel conversation into a thread
# ============================================================================
# Guardrails Configuration
# ============================================================================
# Optional pre-execution authorization for tool calls.
# When enabled, every tool call passes through the configured provider
# before execution. Three options: built-in allowlist, OAP policy provider,
# or custom provider. See backend/docs/GUARDRAILS.md for full documentation.
#
# Providers are loaded by class path via resolve_variable (same as models/tools).
# --- Option 1: Built-in AllowlistProvider (zero external deps) ---
# guardrails:
# enabled: true
# provider:
# use: deerflow.guardrails.builtin:AllowlistProvider
# config:
# denied_tools: ["bash", "write_file"]
# --- Option 2: OAP passport provider (open standard, any implementation) ---
# The Open Agent Passport (OAP) spec defines passport format and decision codes.
# Any OAP-compliant provider works. Example using APort (reference implementation):
# pip install aport-agent-guardrails && aport setup --framework deerflow
# guardrails:
# enabled: true
# provider:
# use: aport_guardrails.providers.generic:OAPGuardrailProvider
# --- Option 3: Custom provider (any class with evaluate/aevaluate methods) ---
# guardrails:
# enabled: true
# provider:
# use: my_package:MyGuardrailProvider
# config:
# key: value
# ============================================================================
# Authorization Configuration
# ============================================================================
# Fine-grained resource authorization (RBAC and beyond). Disabled by default;
# every authenticated user has access to all resources. Schema is present but
# inert; the built-in RBAC provider and runtime wiring arrive in subsequent phases.
# See RFC: https://github.com/bytedance/deer-flow/issues/4063
authorization:
enabled: false
# ============================================================================
# Circuit Breaker Configuration
# ============================================================================
# Circuit breaker for LLM calls prevents repeated requests to a failing provider.
# When the failure threshold is reached, subsequent calls fast-fail until recovery.
#
# This is useful for:
# - Avoiding rate-limit bans during provider outages
# - Reducing resource exhaustion from retry loops
# - Gracefully degrading when LLM services are unavailable
# circuit_breaker:
# # Number of consecutive failures before opening the circuit (default: 5)
# failure_threshold: 5
# # Time in seconds before attempting to recover (default: 60)
# recovery_timeout_sec: 60
# ============================================================================
# SSO / OIDC Authentication (optional)
# ============================================================================
# Enable SSO login via any OIDC-compatible provider (Keycloak, Google, Azure AD, Okta, etc.).
# When enabled, the login page will show SSO buttons alongside the standard email/password form.
#
# Provider configuration:
# - issuer: The OIDC issuer URL (e.g. https://keycloak.example.com/realms/deerflow)
# - client_id: OAuth2 client ID from the provider
# - client_secret: OAuth2 client secret ($ENV_VAR references supported)
# - redirect_uri: Callback URL. In production, this must match what you configure in
# the provider. Defaults to a self-derived URL in development.
#
# Keycloak setup:
# 1. Create a client with type "confidential" and Standard Flow enabled
# 2. Add Valid Redirect URI: http://localhost:8001/api/v1/auth/callback/keycloak
# 3. Add Web Origin: http://localhost:8001 (or your frontend origin)
# auth:
# oidc:
# enabled: true
# # Base URL of the frontend, used for redirects after SSO callback.
# # In production behind a reverse proxy, set this to the public frontend URL
# # (e.g. https://deerflow.example.com). In development, leave unset.
# # frontend_base_url: http://localhost:3000
# providers:
# keycloak:
# display_name: Keycloak
# issuer: https://keycloak.example.com/realms/deerflow
# client_id: deerflow
# client_secret: $KEYCLOAK_CLIENT_SECRET
# # Optional: explicitly set the callback URL.
# # redirect_uri: https://deerflow.example.com/api/v1/auth/callback/keycloak
# scopes:
# - openid
# - email
# - profile
# token_endpoint_auth_method: client_secret_post
#
# # User provisioning settings (safe defaults shown below):
# auto_create_users: true # Auto-create DeerFlow account on first SSO login
# require_verified_email: true # Reject SSO logins without verified email
# # allowed_email_domains: # Restrict to specific email domains
# # - example.com
# admin_emails: [] # Auto-grant admin role to these emails
#
# # Security features (enabled by default):
# pkce_enabled: true # PKCE (S256) for authorization code flow
# nonce_enabled: true # Nonce validation in ID tokens