# 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: 15

# ============================================================================
# Logging
# ============================================================================
# Log level for deerflow modules (debug/info/warning/error)
log_level: info

# ============================================================================
# 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


# ============================================================================
# Models Configuration
# ============================================================================
# Configure available LLM models for the agent to use

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 model (with thinking support)
  # - name: deepseek-v3
  #   display_name: DeepSeek V3 (Thinking)
  #   use: deerflow.models.patched_deepseek:PatchedChatDeepSeek
  #   model: deepseek-reasoner
  #   api_key: $DEEPSEEK_API_KEY
  #   timeout: 600.0
  #   max_retries: 2
  #   max_tokens: 8192
  #   supports_thinking: true
  #   supports_vision: false  # DeepSeek V3 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
  #   # 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 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

  # 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

  # 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

# ============================================================================
# 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


# ============================================================================
# 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

# ============================================================================
# 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

# 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
#   # Recommended: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest (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:latest
#
#   # 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: 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
#   # 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
#
#   # 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
#       # 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

# 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 # Use default model (first model in models list)

# ============================================================================
# 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

  # Recently-loaded skill files are excluded from summarization so the agent
  # does not lose skill instructions after a compression pass. Claude Code uses
  # a similar strategy (keep the most recent ~5 skills, ~25k total tokens, with
  # a ~5k cap per skill). Set preserve_recent_skill_count to 0 to disable.
  preserve_recent_skill_count: 5
  preserve_recent_skill_tokens: 25000
  preserve_recent_skill_tokens_per_skill: 5000
  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
  storage_path: memory.json # Path relative to 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

# ============================================================================
# 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 BOTH the
# LangGraph checkpointer AND DeerFlow application data (runs, feedback,
# events) from a single backend setting.
#
# If both `checkpointer` and `database` are present, `checkpointer`
# takes precedence for LangGraph state persistence only.
#
# checkpointer:
#   type: sqlite
#   connection_string: checkpoints.db
#
# checkpointer:
#   type: postgres
#   connection_string: postgresql://user:password@localhost:5432/deerflow

# ============================================================================
# Database
# ============================================================================
# Unified storage backend for LangGraph checkpointer 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 both checkpointer 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 docker compose build` — single
#              extra only at build time (backend/Dockerfile passes the value
#              as one token to `--extra`).
#
# 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 LangGraph state persistence.
# 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

# ============================================================================
# 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
