Files
deer-flow/config.example.yaml
T
4af6178358 feat(trace): add agent observability with Monocle (#4024)
* Add Monocle tracing

Enable Monocle (OpenTelemetry tracing for LLM apps) with one setup call plus the monocle_apptrace dependency. setup_monocle_telemetry auto-instruments the frameworks already in use and writes traces to .monocle/. Additive; no changes to application logic.

* Config-gate Monocle telemetry in the Gateway lifespan

Addresses review on #4024: moves setup_monocle_telemetry out of agents/__init__ import time into the Gateway lifespan, gated by MonocleTracingConfig (MONOCLE_TRACING env, default off). Warns on the Langfuse/global-OTel-provider conflict and relies on monocle_apptrace's own duplicate-setup guard and existing-provider attach. Pins monocle_apptrace>=0.8.8 (+ uv.lock), adds .monocle/ to .gitignore, adds tests (default-off / toggle-on / no import-time setup), and documents exporters, Okahu, and the VS Code viewer in README, config.example.yaml, and backend/AGENTS.md.

* Clarify Monocle/Langfuse single-provider guidance

Make the docstring, warning, and AGENTS.md consistent with the README: only one library can own the global OpenTelemetry provider; Monocle initializes at startup before Langfuse's per-run handler, so enabling both drops Langfuse's spans — enable one OTel tracer (LangSmith, a callback, coexists fine).

* Address review: optional extra, exporter validation, off-box warning, tests

Responds to the second review round.

- Make monocle_apptrace an optional extra (deerflow-harness[monocle], re-exposed
  as deer-flow[monocle]) following the boxlite/tui precedent, so a default
  install no longer pulls the OpenTelemetry stack. It stays pinned in the dev
  group for the tracing tests, and enabling MONOCLE_TRACING without the extra
  raises a clear install error.
- Warn loudly at startup whenever any exporter other than `file` is configured,
  since those move prompts, tool inputs/outputs, and completions beyond the
  local .monocle/ directory.
- Validate MONOCLE_EXPORTERS against the known exporter names and require
  OKAHU_API_KEY when okahu is selected, mirroring the Langfuse pattern.
  Validation runs from Monocle's own init (not validate_enabled) so a config
  typo can never fail agent runs; errors surface at Gateway startup instead.
- Grow the tests from 5 to 13: caplog coverage for the Langfuse-conflict and
  off-box warnings, exporter validation cases, a stronger import-time
  regression that asserts the global TracerProvider is not replaced, and a
  subprocess double-invoke test exercising the real check_duplicate_setup.
- Docs: config.example.yaml block retitled to a dedicated tracing header;
  README documents the [monocle] install and scopes tracing to Gateway runs.

* docs: align Monocle README section with the other tracing providers

Lead with what Monocle is and captures, drop the install step (the dev
group already ships monocle_apptrace via uv sync; unusual installs get
the RuntimeError), and point the missing-package error at the repo-native
command (uv sync --extra monocle / deerflow-harness[monocle]).

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

* Address review: verified Langfuse coexistence, lifespan test, scope docs

Responds to the third review round.

The Langfuse conflict claim was wrong, verified empirically against langfuse
4.5.1 in both init orders: whichever library initializes second reuses the
existing global TracerProvider and attaches its own span processor, so neither
side loses spans. Dropped the warning and its tests, corrected the README,
AGENTS.md, and config.example.yaml statements, and pinned the verified behavior
with test_coexists_with_langfuse (real monocle + real langfuse in a subprocess,
no mocks). One honest caveat documented: both processors see all spans, so
Monocle's exporters also capture Langfuse's spans when both are enabled.

Also from the review:
- Document the Gateway-only scope in AGENTS.md: the lifespan is the sole call
  site, so the embedded DeerFlowClient and TUI are not instrumented; embedded
  users call setup_monocle_tracing_if_enabled() themselves.
- Add test_gateway_lifespan_initializes_monocle pinning the lifespan wiring.
- Comment why MonocleTracingConfig.is_configured is intentionally coarser than
  LangSmith/Langfuse (composite validation lives in validate() at startup).
- Note that monocle_exporters_list takes the comma-separated string as-is.
- Module-level importorskip("monocle_apptrace") so minimal installs collect
  the test module cleanly.

* docs: reword Monocle intro sentence

* fix(tests): run the import-time regression in a subprocess

test_no_import_time_setup deleted deerflow.agents* from sys.modules and
re-imported to force __init__ to re-execute. The re-import creates new module
objects, and restoring the old sys.modules entries afterwards leaves the parent
package's attribute bindings pointing at the new ones, so any later test that
resolves a deerflow.agents.* dotted path (monkeypatch.setattr in
test_summarization_middleware, test_thread_data_middleware, and others) failed
with "module 'deerflow.agents' has no attribute ...".

Run the check in a subprocess instead: the import is genuinely fresh, the
assertion is stronger (the provider must still be the SDK-less proxy, proving
nothing was installed at any point), and no module identity leaks into the
rest of the suite.

* Address review: console warning scope, embedded hint, honest naming, doc alignment

Responds to the post-approval review round:

- Scope the off-box exporter warning to the remote exporters (okahu, s3,
  blob, gcs): console writes to local stdout and no longer trips it.
  config.example.yaml's data-handling note now distinguishes file /
  console / remote likewise.
- Rename MonocleTracingConfig.is_configured to is_enabled so the boolean
  reads as what it checks; the exporter-dependent credential check stays
  in validate(), run at Gateway startup.
- Hint on the embedded path: build_tracing_callbacks() logs a debug line
  when MONOCLE_TRACING is set but setup never ran in this process, so
  embedded DeerFlowClient/TUI users are not left with silent no-op
  tracing. Backed by a process-global setup flag.
- Re-export setup_monocle_tracing_if_enabled from deerflow.tracing,
  matching the package convention.
- Note the deliberate fail-open-at-startup contrast with
  LangSmith/Langfuse in the lifespan, and the OTel SDK-internals
  dependency in the coexistence test.
- Test hygiene: clear MONOCLE_* env in the tracing config/factory
  fixtures; reset the setup flag in the monocle test fixture; reword the
  README Langfuse-spans claim as the shared-provider inference it is.
- Document that .monocle/ trace files are never rotated or cleaned up.

* fix(tests): pin the factory logger level in the embedded-hint tests

configure_logging() from earlier tests in the full suite pins an explicit
INFO level on the logger hierarchy, so a root-level caplog.at_level(DEBUG)
never sees the factory's debug hint. Scope caplog to
deerflow.tracing.factory so the test is independent of suite ordering.

* Address review: co-export disclosure, lifespan failure test, exporter parse dedup

- Off-box warning now notes that Langfuse's spans are exported too when
  both providers are enabled and share the global OTel provider; pinned
  both ways by tests.
- Pin the lifespan fail-open contract: a raising Monocle setup is logged
  and the Gateway keeps serving (pragma dropped now that the path is
  exercised). README notes a config error is reported at startup and
  tracing stays off until restart.
- Hoist exporter parsing into MonocleTracingConfig.exporter_list so
  validate() and the off-box warning cannot diverge, and note the
  upstream coupling on the exporter allow-list.
- Reduce config.example.yaml's Monocle block to a pointer; the capture,
  retention, and data-handling detail lives in README's Monocle section.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-07-14 08:58:06 +08:00

1938 lines
86 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: 24
# ============================================================================
# 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: 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
# Stores user context and conversation history for personalized responses
memory:
enabled: true
# 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.
mode: middleware
storage_path: memory.json # Absolute path opts out of per-user isolation; a relative path resolves under the data base_dir, not the backend directory
debounce_seconds: 30 # Wait time before processing queued updates
model_name: null # Use default model
max_facts: 100 # Maximum number of facts to store
fact_confidence_threshold: 0.7 # Minimum confidence for storing facts
injection_enabled: true # Whether to inject memory into system prompt
max_injection_tokens: 2000 # Maximum tokens for memory injection
# Token counting strategy for memory-injection budgeting:
# tiktoken (default) - accurate, but the encoding's 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't use `pip`, use `uv`") survive even when the budget is tight.
# guaranteed_categories - list of fact categories to guarantee. Pass [] to
# disable; defaults to ["correction"].
# guaranteed_token_budget - token ceiling for guaranteed facts. In the
# common case the total injection stays within ``max_injection_tokens``
# (guaranteed lines displace regular ones); the allowance becomes
# additive only when guaranteed lines alone would overflow
# ``max_injection_tokens``, in which case the safety-truncation ceiling
# is raised accordingly.
guaranteed_categories:
- correction
guaranteed_token_budget: 500
# Staleness review: periodically prune aged facts that may no longer reflect
# the user's 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 - master switch (default: true)
# staleness_age_days - facts older than this are candidates (default: 90)
# staleness_min_candidates - minimum stale facts required to trigger a review
# cycle; avoids wasteful LLM calls when there are
# very few candidates (default: 3)
# staleness_max_removals_per_cycle - safety cap on removals per cycle; when
# exceeded, the lowest-confidence entries
# are kept (default: 10)
# staleness_protected_categories - fact categories exempt from review
# (default: ["correction"])
staleness_review_enabled: true
staleness_age_days: 90
staleness_min_candidates: 3
staleness_max_removals_per_cycle: 10
staleness_protected_categories:
- correction
# Memory consolidation: when a single category accumulates many fragmented
# facts, the LLM reviews them during the normal memory-update call (same
# invocation — no extra API call) and decides whether groups of related facts
# can be synthesized into a single richer fact.
# consolidation_enabled defaults to false because consolidation is lossy:
# source fact content is permanently replaced by the LLM-synthesized fact
# (only the source IDs are kept in consolidatedFrom). Enable explicitly once
# you are comfortable with that trade-off.
# consolidation_enabled - master switch (default: false)
# consolidation_min_facts - minimum facts in a category to trigger
# consolidation review (default: 8)
# consolidation_max_groups_per_cycle - safety cap on merges per cycle
# (default: 3)
# consolidation_max_sources - max source facts per merge group;
# prevents over-merging (default: 8)
consolidation_enabled: false
consolidation_min_facts: 8
consolidation_max_groups_per_cycle: 3
consolidation_max_sources: 8
# ============================================================================
# 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
# ============================================================================
# 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