An open-source long-horizon SuperAgent harness that researches, codes, and creates. With the help of sandboxes, memories, tools, skill, subagents and message gateway, it handles different levels of...
Overview
On February 28th, 2026, DeerFlow claimed the 🏆 #1 spot on GitHub Trending following the launch of version 2. Thanks a million to our incredible community — you made this happen! 💪🔥 DeerFlow (**eep **xploration and **fficient **esearch ) is an open-source that orchestrates , , and to do almost anything — powered by . https://github.com/user-attachments/assets/a8bcadc4-e040-4cf2-8fda-dd768b999c18 It shares no code with v1. If you're looking for the original Deep Research framework, it's maintained on the 1.x branch — contributions there are still welcome. Active development has moved to 2.0. - We strongly recommend using Doubao-Seed-2.0-Code, DeepSeek v3.2 and Kimi 2.
README
🦌 DeerFlow - 2.0
English | 中文 | 日本語 | Français | Русский | Português | 한국어
On February 28th, 2026, DeerFlow claimed the 🏆 #1 spot on GitHub Trending following the launch of version 2. Thanks a million to our incredible community — you made this happen! 💪🔥
DeerFlow (Deep Exploration and Efficient Research Flow) is an open-source super agent harness that orchestrates sub-agents, memory, and sandboxes to do almost anything — powered by extensible skills.
https://github.com/user-attachments/assets/a8bcadc4-e040-4cf2-8fda-dd768b999c18
[!NOTE] DeerFlow 2.0 is a ground-up rewrite. It shares no code with v1. If you’re looking for the original Deep Research framework, it’s maintained on the
1.xbranch — contributions there are still welcome. Active development has moved to 2.0.
Official Website
Learn more and see real demos on our official website. The landing-page case studies open as allowlisted, read-only showcases without requiring a sign-in.
Sister Projects
- LLM Space - Meet our secret weapon behind DeerFlow — one desktop tool to prototype agent ideas, inspect each harness step, replay failures, and benchmark performance.
Coding Plan from ByteDance Volcengine
- We strongly recommend using Doubao-Seed-2.0-Code, DeepSeek v3.2 and Kimi 2.5 to run DeerFlow
- Learn more
- 中国大陆地区的开发者请点击这里
InfoQuest
InfoQuest reader, web search, and image search use a 30-second HTTP connect/read
inactivity timeout. The crawl timeout and navigation_timeout settings remain
separate server-side options; they do not control the local HTTP timeout.
DeerFlow has newly integrated the intelligent search and crawling toolset independently developed by BytePlus — InfoQuest (supports free online experience)
Table of Contents
- 🦌 DeerFlow - 2.0
- Official Website
- Sister Projects
- Coding Plan from ByteDance Volcengine
- InfoQuest
- Table of Contents
- One-Line Agent Setup
- Quick Start
- From Deep Research to Super Agent Harness
- Core Features
- Recommended Models
- Embedded Python Client
- Projects
- Scheduled Tasks
- Terminal Workbench (TUI)
- Documentation
- ⚠️ Security Notice
- Contributing
- License
- Acknowledgments
- Star History
One-Line Agent Setup
If you use Claude Code, Codex, Cursor, Windsurf, or another coding agent, you can hand it the setup instructions in one sentence:
Help me clone DeerFlow if needed, then bootstrap it for local development by following https://raw.githubusercontent.com/bytedance/deer-flow/main/Install.md
That prompt is intended for coding agents. It tells the agent to clone the repo if needed, choose Docker when available, and stop with the exact next command plus any missing config the user still needs to provide.
Quick Start
Configuration
With run_events.backend: db, caller-supplied ISO 8601 event timestamps are normalized to UTC before storage, preserving their instant when SQLite drops timezone information. Timestamps without an offset are treated as UTC. Previously stored timestamps are not rewritten.
Run event streams include run.terminal for the owning worker’s confirmed final
Run status, while run.end continues to describe each root graph completion.
Terminal publication is best effort; event persistence failure does not undo the
Run outcome or prevent stream completion. Recovery with built-in SQL Run and Event stores
sharing an engine uses one pooled connection for each guarded event write, so it
can publish with only one pool slot available. See Run Event Stream.
Operators can extend lead-agent, subagent, and DeerMem extraction prompts with literal prepend/append configuration without editing source templates. See prompt overlays.
Optional per-model request_admission
paces requests to help stay within provider request-per-minute limits.
It is disabled by default; see the linked guide to enable it.
Enabling it disables exposed SDK retries and the Claude and Codex adapters’
internal retry loops so middleware retries pass through request admission again.
Middleware retries also cover HTTP 529 overload responses.
A warning identifies retry_max_attempts values overridden by admission.
For Google’s official Gemini OpenAI-compatible endpoint, use the Gemini reasoning profile.
For MindIE XML tool calls, see the argument parsing and newline compatibility guide. Both synchronous and asynchronous streams retain this compatibility: tool-enabled streams simulate chunks from a non-streaming response, while no-tool streams stay native. Newline compatibility preserves literal escapes inside backtick and tilde code fences, including unclosed fences, in complete and streamed replies.
-
Clone the DeerFlow repository
git clone https://github.com/bytedance/deer-flow.git cd deer-flow -
Run the setup wizard
From the project root directory (
deer-flow/), run:make setupThis launches an interactive wizard that guides you through choosing an LLM provider, optional web search, and execution/safety preferences such as sandbox mode, bash access, and file-write tools. It generates a minimal
config.yamland writes your keys to.env. Takes about 2 minutes.The wizard also lets you configure an optional web search provider, or skip it for now.
Brave web search preserves valid entries in mixed result lists. Malformed response containers or lists containing no usable entries return a structured format error; missing, null, or empty results keep the existing “No results found” response. Format errors also log the malformed container’s path and type, or the absence of usable result objects, without including search queries, credentials, or payload values.
GroundRoute web search and fetch contain malformed HTTP-success payloads the same way: a non-object payload, a
resultscontainer that is not a list, or a non-empty list with no object entries returns the provider’s format error, while non-object entries in an otherwise valid list are skipped in order. Missing, null, or empty results keep the existing “No results found” response.DuckDuckGo web search distinguishes execution failures from empty results: rate limits, timeouts, blocked requests, client-setup failures, SDK errors, and a missing
ddgsdependency return a structuredDuckDuckGo search failed: ...error instead of “No results found”, while a genuinely empty result set keeps the existing “No results found” response. The structured error text lets the harness classify an outage as transient or a configuration problem instead of a query that matched nothing.Tavily web search and fetch contain malformed HTTP-success payloads the same way: a non-object payload, or a
results/failed_resultscontainer that is not a list, returns the provider’s format error, and non-object entries in an otherwise valid list are skipped in order. Missing, null, or empty results keep the existing response ([]for search,Error: No results foundfor fetch); missing or null search fields (title,url,content) normalize to empty text, and a non-string fetchraw_contentis coerced to text before the truncation limit.Sofya web search and fetch contain malformed HTTP-success payloads the same way: a non-object payload, a
resultscontainer that is not a list, or a non-empty list with no object entries returns the provider’s format error, while non-object entries in an otherwise valid list are skipped in order. Missing, null, or empty results keep the existing “No results found” response.Serper web and image search preserve valid entries in mixed result lists. A malformed
organic/imagescontainer, or a non-empty list holding no object entries, returns Serper’s format error; missing, null, or empty containers keep the existing “No results found” / “No images found” response. Format errors log the container field name only, without queries, credentials, or payload values.Unbrowse web fetch preserves the extracted text but prefixes a warning when the target page’s HTTP status is 400–599, even when the scraping service itself returns a successful response. Empty-page errors retain that status too. Both messages include up to 500 characters of the reported final URL after removing any username and password; final URLs that cannot be parsed as HTTP(S) URLs with a host are omitted.
Tencent Cloud WSA web search contains malformed HTTP-success payloads the same way: a
Pagescontainer that is not a list, or a non-empty container holding no usable page, returns the provider’s format error, while malformed page entries in an otherwise valid container are skipped so the valid rows survive. Missing, null, or emptyPageskeep the existing “No results found” response.Serply web search contains malformed HTTP-success payloads the same way in every vertical: a
results/entries/articlescontainer that is not a list, or a non-empty list with no object entries, returns the provider’s format error, while non-object entries in an otherwise valid list are skipped in order. Missing, null, or empty containers keep the existing “No results found” response.SearXNG web search contains malformed HTTP-success payloads the same way: a payload that is not an object, a body that is not JSON, a
resultscontainer that is not a list, or a non-empty list holding no object entries returns the provider’s format error, while non-object entries in an otherwise valid page are skipped in order, each skip logged with its entry position, so the valid rows survive and provider-side drift leaves a trace. Missing, null, or emptyresultskeep the existing empty result ([]). This matters most for a self-hosted instance: one behind a reverse proxy or captive portal answers an HTML or plain-text error page with HTTP 200, and reading that as an empty search ends a research loop as “no hits”.InfoQuest web and image search contain malformed HTTP-success payloads the same way: a
search_resultenvelope that is not an object, or anorganic/top_stories.items/images_resultscontainer that is not a list or that holds no usable entry, returns the provider’s format error, while malformed entries in an otherwise valid payload are skipped so the usable rows survive. Missing, null, or empty containers keep the existing empty result. Format errors log the container or entry position only, without queries or payload values.InfoQuest web and image search screen result URLs through the shared public-URL policy and omit rejected rows. Responses without the required
search_resultenvelope return a format error instead of exposing the raw provider payload.Jina, Browserless, and InfoQuest web fetches resolve relative links and image sources using the requested page URL (or a usable HTML base URL), so returned Markdown includes complete destinations. Link resolution preserves the surrounding HTML source, including malformed-page formatting.
Self-hosted Browserless, Crawl4AI, Firecrawl, and fastCRW fetch backends require outbound isolation from private, loopback, link-local, and cloud-metadata networks before setting
network_isolation_confirmed: trueon each tool. DeerFlow rejects private or unverifiable backends by default because those services handle redirects, DNS resolution, and subresources themselves.allow_private_addressespermits intentional internal target URLs and does not bypass the backend check. See delegated fetch deployment;make doctorreports backend configurations that would be refused.Jina fetches support opt-in bounded retries via
max_retries(default0) andretry_budget_seconds(default30) in the tool configuration. ValidRetry-Afterhints set a minimum wait for HTTP 429/503; 429 without a valid hint stays terminal. Hints that cannot fit the remaining budget stop retries. Local backoff remains randomized. Retries may increase upstream requests and cost; see Jina fetch retries.At DEBUG level, Jina emits one bounded crawl completion summary with attempts, elapsed/backoff seconds, outcome and stop reason. See diagnostics for configuration and correlation.
Jina also accepts an opt-in
max_response_bytestool setting (positive integer; omitted/null disables it). It stops oversized decoded responses before extraction, with no partial success or retry. This leaves the 4096-character output cap unchanged and does not bound HTTPX decompressor allocations or wire bandwidth; see response budget.Jina can also bound concurrent HTTP attempts and FIFO waiting with the opt-in
request_admissiontool extra. One immutable budget is shared across clients and agent threads in each process; replicas have independent budgets. See Jina request admission.Run
make doctorat any time to verify your setup and get actionable fix hints. The setup wizard and health check support legacy terminal encodings, including Windows cp936, replacing unsupported characters so both commands can finish. If you are opening a GitHub issue about a local setup or runtime problem, runmake support-bundle. The command prints reporter next steps, writes a*-issue-summary.mdfile to paste into the issue, a*-issue-draft.mdfile for AI-assisted issue filing, and an optional evidence zip under.deer-flow/support-bundles/. If an AI assistant files the issue, start from the draft and replace every REQUIRED placeholder instead of inventing missing facts. Attach the zip only if a maintainer asks for it, or if the summary alone is not enough. Maintainers and AI triage tools can start withtriage.json; the bundle includes redacted diagnostics and file manifests only, and does not include.env, raw conversation messages, or user file contents. Subprocess diagnostics are captured as UTF-8, with Python helpers emitting UTF-8 even on non-UTF-8 hosts and escaping unencodable characters. Thread manifests follow the local launcher’s runtime paths: checkout.envvalues override shell exports, and a project-root override alone still usesbackend/.deer-flowfirst. Simple variable references such asDEER_FLOW_HOME="$PWD/backend/.deer-flow"use the checkout asPWD; single-quoted references remain literal. If the checkout.envcannot be read or decoded as UTF-8, thread diagnostics retain shell exports and default storage paths. For standalone Gateway launches usingbackend/.envorDEER_FLOW_ENV_FILE, export the effectiveDEER_FLOW_HOMEwhen collecting the bundle and ensure the checkout.envdoes not override it. Doctor’s internal tool probes also decode UTF-8 with replacement for invalid bytes so the remaining diagnostic output stays available.When a thread manifest is requested, a nonempty
DEER_FLOW_HOMEselects the runtime data directory instead of stale checkout data. External data paths appear as{DEER_FLOW_HOME}in the report; file contents remain excluded. Runtime path settings also come from the checkout.env, with shell exports taking precedence. Relative paths resolve from the checkout as inmake dev. Without an explicit home, a configuredDEER_FLOW_PROJECT_ROOTselects its.deer-flowdirectory; external paths use that variable name in the report.Advanced / manual configuration: If you prefer to edit
config.yamldirectly, runmake configinstead to copy the full template. Optional dependency auto-detection accepts UTF-8 configuration files with or without a byte-order mark (BOM). It checks an existingDEER_FLOW_CONFIG_PATHfirst, thenDEER_FLOW_PROJECT_ROOT/config.yaml, before the checkout’s default locations. Local startup also accepts the project-root configuration when the checkout has noconfig.yaml. Seeconfig.example.yamlfor the complete reference including CLI-backed providers (Codex CLI, Claude Code OAuth), OpenRouter, Responses API, subagent runtime caps such assubagents.max_total_per_run, and more.Optional per-model pricing must use one currency across all priced models. DeerFlow disables Console cost estimates when currencies are mixed rather than presenting an invalid aggregate. For older runs without a per-model usage breakdown, Console attributes the recorded total and input token counts to the run’s model.
Administrators can open Settings → Models to select a provider and enter only its API Key and model ID. Endpoints, authentication and adapters are backend presets for OpenAI, Anthropic, DeepSeek, Alibaba Cloud Bailian/Qwen, Volcengine/Doubao, Moonshot/Kimi, Zhipu/Z.AI and MiniMax. Region and plan-specific entries are explicitly distinguished. Arbitrary model IDs are accepted; internal names are generated automatically and remain stable when a model ID changes. Newly saved models are enabled and immediately refresh existing chat and Agent model selectors, without editing YAML or restarting the Gateway.
The Moonshot (China) preset recognizes
kimi-k3.1-proandkimi-k3.1-pro-highspeedwith required thinking, image input and nativelow/high/maxreasoning effort (defaultmax). Thinking stays on even when a caller requests it off. Requests to the official endpoint omit the unsupported thinking switch and fixed sampling parameters. See the model registration reference for the image-input and parameter constraints.Custom OpenAI-compatible still supports existing/custom base URLs and unauthenticated endpoints. Advanced settings retains display name, token limits and vision overrides; unknown IDs do not receive invented capabilities. Leave the key blank on edit to retain it for the same connection. Switching provider or custom endpoint requires a new key, and preset models must be disabled before clearing their key. YAML models remain read-only.
Connection testing uses the same model factory and protocol as chat, sends a bounded streaming tool-call request and never saves the draft. It may incur provider charges and does not verify every agent, vision or thinking workflow. Official DeepSeek custom endpoints retain their existing adapter; a proxy does not become DeepSeek merely because of its model ID. See the Web model registration reference for presets, compatibility, extension instructions and backup requirements.
DeepSeek regression tests run offline with the normal backend suite. To verify the real provider explicitly, set
DEEPSEEK_TEST_API_KEYin your environment and run frombackend/:DEER_FLOW_RUN_LIVE_TESTS=1 uv run --no-sync pytest tests/test_managed_deepseek_live.py -qThese opt-in tests send short requests to DeepSeek and may incur charges; they use temporary state, never save credentials to the deployment catalog, and are skipped in CI.
DEEPSEEK_TEST_MODELoptionally selects a different DeepSeek model ID (default:deepseek-flash). The same tests can be run on unfixed and fixed revisions; success is always the expected result.YAML models remain read-only in this page and take precedence on name conflicts. Managed models are appended after YAML models; edits apply to new configuration snapshots, while active runs retain their existing snapshot. Disabling a model removes it from future selection/resolution, so update any custom-agent or scheduled task definitions that explicitly reference it before disabling it. Managed models are shared by the deployment, not personal API-key profiles, and remain subject to the existing model authorization policy.
The encrypted catalog and a generated local encryption key are stored in
$DEER_FLOW_HOME/managed-models/(default.deer-flow/managed-models/). Persist and back up the whole directory, restrict filesystem access, and share it across Gateway workers/replicas that should use the same catalog. The local key is protected by filesystem permissions; encryption does not protect against someone who can read both files. Losing the key requires restoring the backup. Reads and writes fail if the catalog cannot be decrypted, rather than replacing it. This storage is independent of the SQL backend and works with read-only YAML mounts.When several models are configured, open either model picker and use the star beside a model to favorite it. Favorites appear first in both the main chat and Side Chat pickers without changing either chat’s selected or default model. They are stored for the signed-in user in the current browser, so they do not sync to another browser or device and do not require a startup setting. The compact favorites picker intentionally omits search and only adds favorite ordering to the two-line model list.
For an explicit backend dotenv file, export DEER_FLOW_ENV_FILE before startup,
alongside DEER_FLOW_CONFIG_PATH if needed. For example, from backend/:
DEER_FLOW_ENV_FILE=/srv/deer-flow/stage.env DEER_FLOW_CONFIG_PATH=/srv/deer-flow/stage.yaml make gateway
Relative dotenv paths use the backend process working directory; absolute paths
work regardless of that directory. Existing process variables win. An unset
selector preserves default dotenv discovery; a specified empty, missing,
non-file or unreadable path fails startup. Explicit selection also fails when
PYTHON_DOTENV_DISABLED disables dotenv loading. Restart after changing the file.
This selects backend dotenv input only: shell launchers, Docker Compose and the
frontend retain their own environment loading. Values they already export win.
It does not select ENV profiles or isolate databases, storage or tenants.
See backend dotenv selection.
Custom blob storage backends accept blob_storage.backend: module.Class or
module:Class, including a single .py module importable by the Gateway.
The selected class must implement BlobStore; backend options go under
blob_storage.backend_config. See blob storage.
Viewed-image artifact URLs can use shared blobs when the serving Gateway lacks the local file. This fallback requires an existing thread record accessible to the caller; deleting that record revokes access even if checkpoint cleanup fails. When blob storage is disabled, a missing local file returns 404 without loading checkpoint state. Shared-store failures also return 404 and emit a warning with the exception type, without backend error details.
Running the Application
Deployment Sizing
Use the table below as a practical starting point when choosing how to run DeerFlow:
| Deployment target | Starting point | Recommended | Notes |
|---|---|---|---|
Local evaluation / make dev |
4 vCPU, 8 GB RAM, 20 GB free SSD | 8 vCPU, 16 GB RAM | Good for one developer or one light session with hosted model APIs. 2 vCPU / 4 GB is usually not enough. |
Docker development / make docker-start |
4 vCPU, 8 GB RAM, 25 GB free SSD | 8 vCPU, 16 GB RAM | Image builds, bind mounts, and sandbox containers need more headroom than pure local dev. |
Long-running server / make up |
8 vCPU, 16 GB RAM, 40 GB free SSD | 16 vCPU, 32 GB RAM | Preferred for shared use, multi-agent runs, report generation, or heavier sandbox workloads. |
- These numbers cover DeerFlow itself. If you also host a local LLM, size that service separately.
- Linux plus Docker is the recommended deployment target for a persistent server. macOS and Windows are best treated as development or evaluation environments.
- If CPU or memory usage stays pinned, reduce concurrent runs first, then move to the next sizing tier.
Option 1: Docker (Recommended)
Requires Docker Desktop / Docker Engine and Docker Compose v2.24+
(docker compose version). Older Compose clients cannot parse the optional
env_file syntax in docker/docker-compose.yaml and docker/docker-compose-dev.yaml.
Development (hot-reload, source mounts):
make docker-init # Pull sandbox image (only once or when image updates)
make docker-start # Start services (auto-detects sandbox mode from config.yaml)
make docker-logs # View logs
make docker-start starts provisioner only when config.yaml uses provisioner mode (sandbox.use: deerflow.community.aio_sandbox:AioSandboxProvider with provisioner_url). Direct Compose users can add --profile provisioner to the dev command for the same mode; the default stack does not require Kubernetes.
With USERDATA_PVC_NAME, sandbox data, extra mounts and Lark broker credentials share one user-data volume with separate subPath mounts, avoiding duplicate references to the same claim on NFS CSI drivers.
Docker builds use the upstream uv registry by default. The gateway image installs with uv sync --locked, and uv.lock pins https://pypi.org/simple, so UV_INDEX_URL has to keep that value: exporting a mirror such as https://pypi.tuna.tsinghua.edu.cn/simple fails the build with The lockfile at `uv.lock` needs to be updated, but `--locked` was provided (reproduce in an already-synced environment with cd backend && UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple uv sync --locked --check; if the environment itself is stale, --check reports that instead for either index, so use uv lock --check to see it without syncing). Installing from a mirror requires regenerating the lock or dropping --locked, and both change what the image installs, so keep the default index for a checkout you intend to contribute back. NPM_REGISTRY=https://registry.npmmirror.com is independent of this: it applies to the frontend image’s pnpm install and to the gateway image’s Lark CLI install (@larksuite/cli, which backend/Dockerfile installs with npm).
Local AIO sandbox control traffic is always direct: loopback/private addresses,
single-label cluster hosts, and Docker/Podman internal hostnames do not inherit
HTTP_PROXY or HTTPS_PROXY. External sandbox FQDNs and public IPs still
honor environment proxy settings.
After native runtime shutdown and MCP teardown, Gateway closes an initialized sandbox provider, including AIO sandboxes first created by a worker-thread tool call. It does not start an unused sandbox provider solely for cleanup.
Backend processes automatically pick up config.yaml changes on the next config access, so model metadata updates do not require a manual restart during development.
Gateway runs use the top-level recursion_limit in config.yaml when an API
request does not provide one. The default is 100; valid per-request values
take precedence, and max_recursion_limit (default 1000) caps both. Changes
apply to the next run without restarting the Gateway. This top-level setting
applies to Gateway API and IM channel runs. A recursion_limit in an IM
channel’s session.config takes precedence, and GitHub channel runs keep
their own floor. Embedded DeerFlowClient runs retain their own defaults and
per-call override paths. A run that reaches its limit stops with a plain message
that names up to five files it created in /mnt/user-data/outputs without
presenting them; send a message to continue. IM channels reply with the same
message.
A custom frontend can add an optional context.client to a Gateway run request:
a client name, capabilities such as artifacts or charts, and display
preferences. DeerFlow sanitizes it and passes the values to the agent as
untrusted, request-scoped hints, so skills can pick an output shape the client
can show. It is never an authorization or identity signal. See
Create Run.
Client hints are omitted from stored run configuration and must be sent again
for each run; later runs, including server-started tasks, do not inherit them.
The checkpoint storage settings database.checkpoint_channel_mode and
database.checkpoint_delta.snapshot_frequency (default 10) are exceptions:
both are frozen when the process first builds an agent (including through
DeerFlowClient) and require a process restart to change safely.
In delta mode, a run selecting an older checkpoint reports an error before rewriting
state if either the selected checkpoint or the current head has pending tool
execution. For Command(resume=...), other pending graph nodes also prevent the
rewrite, except for the synthetic input entry node. Resume pending work from the
latest checkpoint; to regenerate completed work, submit new input there. Full-mode
resumes and ordinary replay without pending execution keep their existing behavior.
The optional database.checkpoint_cache section (delta channel mode only)
caches materialized checkpoint histories: type is memory (default) or
redis, and max_entries: 0 disables the cache. The redis backend is
Gateway/async-only; the sync TUI/embedded path supports memory only. The
cache is performance-only — results are identical with it disabled — so it is
never frozen and workers sharing one checkpoint database may safely run
different cache settings. Returned state snapshots have independent nested values,
so editing one cannot change later checkpoint reads. Redis thread cleanup treats a configured key_prefix
as literal text, including glob metacharacters, so deletion stays within that
namespace and thread. Redis rejects malformed write records on ingestion; malformed
or wrong-channel cached histories are rebuilt in full from authoritative checkpoints.
Seeds and write values remain opaque to the cache: these checks do not validate
schema-specific state contents.
Checkpoint reads, valid neighboring writes and optional-cache cleanup continue
even if a warning handler fails.
Gateway checkpoint cleanup runs when the next run wins durable same-thread admission, before its first checkpoint read. It checks only the root head and its parent, removes at most one metadata-only duration leaf without pending writes, and preserves explicit checkpoint selections and delta replay state. See checkpoint operations for the retention constraints.
[!TIP] On Linux, if Docker-based commands fail with
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock, add your user to thedockergroup and re-login before retrying. See CONTRIBUTING.md for the full fix.
Production (builds images locally, mounts runtime config and data):
make up # Build images and start all production services
make down # Stop and remove containers
Access: http://localhost:2026
make up waits for the Gateway /health endpoint before reporting success.
If the Gateway does not become healthy within the startup window, deployment
exits non-zero and prints the container status plus recent Gateway logs. The
production image starts from its already-built environment and never resolves
or installs Python dependencies at container startup.
If make up reports an unwritable runtime home or an unreadable persisted secret
after make docker-start, run the printed sudo chown -R : ''
recovery command and retry. The check honors secret overrides from the shell or
.env, accepts readable read-only secret files, and leaves make down available
without reading or generating secrets.
Production Docker commands can select a root dotenv profile with
DEER_FLOW_COMPOSE_ENV_FILE=/srv/deer-flow/stage.env make up. The same file
feeds Compose interpolation, Gateway/provisioner variables, and deployment
secret checks. YAML and runtime-state locations remain separate selectors;
see production env files.
For persistent deployments, configure database.backend as sqlite or
postgres. The selected backend is shared by the LangGraph checkpointer,
LangGraph Store, and DeerFlow application data. The deprecated checkpointer
section, when present, overrides the first two for backward compatibility.
Gateway startup automatically repairs the missing run-change schema affecting some existing databases (#5516). The repair preserves run history and existing change positions; downgrading the repair to its predecessor also retains the schema and positions required by that version.
For lightweight single-process event persistence, run_events.backend: jsonl
keeps Unicode message content intact, including line and paragraph separators.
Existing valid JSONL records remain readable without rewriting the files.
For per-call usage audits, an immediate LLM response replay with populated usage updates both nested usage and top-level token counters, even if initial usage was absent or zero. The original request, model, and status metadata stay intact; flushed events are not rewritten. See the run event contract.
The unified nginx endpoint is same-origin by default and does not emit browser CORS headers. If you run a split-origin or port-forwarded browser client, set GATEWAY_CORS_ORIGINS to comma-separated exact origins such as http://localhost:3000; the Gateway then applies the CORS allowlist and matching CSRF origin checks.
When fine-grained authorization is enabled, Live Browser connections require threads:write as well as ownership of the thread, even when only viewing frames: the same connection can control the browser. Permission checks run when connecting. Restart Gateway after upgrading to disconnect sessions admitted by older code.
The bundled Docker, local and Helm nginx configs proxy Live Browser WebSocket upgrades with a 600-second read timeout for quiet streams. Helm uses nginx’s default connection and send timeouts for the in-cluster Gateway. Any additional Ingress or reverse proxy must also allow WebSocket upgrades.
Browser login uses HttpOnly session cookies. The login page offers a “keep me signed in” option that extends the browser session when the request is HTTPS (including trusted X-Forwarded-Proto: https) or localhost HTTP. The localhost exception uses the direct request Host and ignores forwarded host headers. Public HTTP deployments, including many temporary sandbox URLs, fall back to session cookies by default. DeerFlow never stores the password in browser storage; the UI may remember only the email address.
If an authentication refresh fails during a temporary network outage, a visible tab rechecks the unknown session when connectivity returns or on a later tab switch. Reconnects received in a background tab are remembered until it becomes visible, without waiting for the tab-switch throttle. Identity is restored only after a successful auth response; a confirmed expired session or explicit logout still requires signing in again.
Password forms accept non-ASCII email addresses and trim surrounding email whitespace. Sign-in requires at least six password characters; creating an account or choosing a new password requires eight. These checks run before sending a request. Passwords, including surrounding spaces, are submitted unchanged.
OIDC/SSO authorization endpoints may include provider query parameters such as ?prompt=login. DeerFlow preserves those parameters when adding the login request, including for configured authorization_endpoint overrides.
After an SSO sign-in, DeerFlow returns you to the page that asked you to sign in instead of the default workspace. Opening the login page while already signed in does the same for its next target.
Administrators can suspend and restore accounts from Settings → Users (admin-only; the section is hidden for ordinary users). The page lists each account’s email, role, and status and exposes the same enable/disable operation as PATCH /api/v1/admin/users/{id}; the last remaining active admin cannot be disabled, and the Gateway’s rejection detail is shown verbatim. A disabled account is rejected by every authentication surface, and its live sessions end on the next request with a login page that states the account has been disabled. Password login for a disabled account deliberately still answers “incorrect email or password” so the state is not disclosed to whoever holds the credentials.
DeerFlow still uses Forwarded / X-Forwarded-* headers to recover the browser-facing scheme and origin behind a proxy. The bundled nginx sets X-Forwarded-Proto, but preserves an upstream HTTPS value and does not overwrite every forwarded header. Configure the outer trusted proxy to replace or strip client-supplied forwarding headers before traffic reaches DeerFlow.
[!IMPORTANT] The Gateway still owns active run tasks in process, so production defaults to a single Gateway worker (
GATEWAY_WORKERS=1). Multi-worker deployments require Postgres, the Redis stream bridge (stream_bridge.type: redis),run_ownership.heartbeat_enabled: true, andrun_events.backend: db; process-local memory/JSONL event stores cannot enforce singleton delivery receipts across workers. Kubernetes replicas run one worker per Pod, which the worker count cannot see: declare them withdeployment.multi_instance: true(orDEER_FLOW_MULTI_INSTANCE=1, which deploy tooling such as a Helm chart can set from its replica count) so the startup gate enforces the same prerequisites instead of staying inert. Declared instances that store credentials (channel_connections, or MCP servers using the OAuthrefresh_tokengrant, whose rotating tokens they share through the database) must also share one at-restDEER_FLOW_CREDENTIALS_KEY(the Helm chart andmake upgenerate it; back it up, see backend/docs/CONFIGURATION.md). The bridge shares SSE delivery and boundedLast-Event-IDreplay across workers. When a trimmed reconnect cursor cannot be verified for complete replay, or a subscriber that already established an empty-stream wait falls behind before its first delivery, Memory and Redis emit a machine-readable SSEgapevent instead of silently returning a partial replay; the Web UI reloads durable thread/event state and resumes from the retained tail. The Memory bridge can replay without recovery when the cursor is its exact last evicted event: every successor is still retained. Older or unverifiable evicted cursors still trigger recovery. An already-observed Redis stream removed during replay also signals a gap, so slow subscribers reload durable state instead of heartbeating forever after cleanup. Redis also tracks terminal stream producers with a short lease: if a worker dies after committing a terminal run status but before publishing END, SSE and/waitfinish after the lease expires instead of waiting for the retained stream’s TTL. Lease reconciliation marks runs from dead workers as errors, persists their delivery receipts, publishes the terminal stream marker, schedules retained-stream cleanup, and updates the affected thread status. Redis END publication is atomic and idempotent: owner and recovery retries confirm an existing marker without appending another, events cannot be appended once END is the stream tail, and an unowned recovery retry after cleanup does not recreate the stream. Legacy streams with an earlier END followed by late data can still accept data. The Redis compatibility scan (stream_bridge.legacy_end_scan_enabled: true) also runs on first END publication; disable it only after old writers stop and all legacy replay buffers expire or are removed, then restart every Gateway to use tail-only END detection. SSE,/wait, and internal stream consumers usestream_bridge.heartbeat_interval_seconds(default15) for idle liveness checks; changing it requires a Gateway restart. Malformed Redis reconnect IDs live-tail new events instead of replaying the retained buffer, and the rolling retained-buffer TTL (stream_ttl_seconds) remains a cleanup safety net rather than a run timeout. Failed-login counters and lockouts forPOST /api/v1/auth/login/local(auth.local.max_login_attempts/lockout_seconds) are kept in the sharedlogin_throttletable whenever the application database is SQLite or Postgres (auth.local.throttle_storage: auto, the default), so every replica enforces one lockout per client IP;memorykeeps the historical per-process counter, which under N replicas hands an attacker N ×max_login_attemptsguesses and logs a startup warning. IM chat-to-thread bindings live in the sharedchannel_thread_bindingstable whenever the application database is SQLite or Postgres (an existingchannels/store.jsonis imported once at startup and renamedstore.json.migrated); IM bot credentials entered in the web UI live in the sharedchannel_runtime_configstable, encrypted withDEER_FLOW_CREDENTIALS_KEY(an existingchannels/runtime-config.jsonis imported once and then deleted), so a save on any instance reaches the provider’s connection within one lease renewal; each provider’s platform connection is held by one instance through the sharedchannel_leasestable —channels.leader_lease(defaultauto, on wherever the multi-process signal is) hands it to another instance when the holder dies, and the startup gate refusesenabled: falsewhile an IM channel other than GitHub is enabled (see IM channel leader lease); follow-up buffers still need their own multi-worker coordination. To try this topology on one machine,make dev-multistarts two Gateways on shared Postgres, Redis andDEER_FLOW_HOME, andmake dev-multi-checkruns the cross-instance checks; see the local two-Gateway harness in backend/docs/CONFIGURATION.md.The producer-lease recovery behavior above is gated by
stream_bridge.producer_lease_recovery_enabled. Keep it disabled while old Gateway binaries or their runs remain; enable it on every instance only after the writer rollout and drain complete.With run ownership heartbeats enabled, terminal success requires acknowledgment from that worker’s own durable write. Run creation and replacement serialize per conversation: a slow admission does not hold the manager-wide lock needed by another conversation’s cached run reads or heartbeat snapshots. Their own Store I/O can still encounter database or pool contention. New runs remain hidden until their initial Store write succeeds. A matching database row alone cannot confirm success, and completion metadata cannot retry a rejected terminal transition. Each run renews independently, so a stalled store operation does not delay other runs. If acknowledgment remains unavailable when the last confirmed lease expires, the worker stops conservatively even if its commit actually reached the database. Expired leases cannot be renewed. Interrupt-and-replace admission acknowledges only the old runs actually claimed by its atomic transaction. Rollback keeps renewing its acknowledged interrupted row until restoration and the separate final error commit finish. An active-only renewal that raced that admission gets one bounded retry after the interrupted acknowledgment, without weakening the owner or live-lease checks. A failed renewal leaves a local expiry guard armed. Shutdown bounds its wait for orphan recovery, but cancellation-resistant storage may still be using its resources.
The local two-Gateway harness disables IM providers under both
channelsandchannel_connectionsby default.DEERFLOW_MI_KEEP_CHANNELS=1preserves them;channels.leader_leasestays as configured.Cleaning up an in-memory run stream, or closing its bridge, releases retained payloads and signals a replay gap to subscribers with unfinished replay. They reload durable state instead of waiting on a detached buffer or consuming stale history. Established readers that fully consumed a terminal stream retain their normal
endsignal. A new reader whose initial attachment loses the race with cleanup receives a gap so it can recover the saved result.In-memory stream events retain the payload as it was published. Reusing a producer’s data object or editing a received event does not change another subscriber’s output or reconnect replay.
During SSE replay-gap recovery, the Web UI preserves a reconnect pointer claimed by another run while durable state is loading. An older recovery cannot overwrite that pointer when it rejoins its own stream. Even a stale foreign pointer is preserved, so the next reload may first check that run before active-run recovery rejoins, adding a reconnect cycle.
Store-only SSE and
/waitconsumers can finish after a heartbeat when run-manager or scheduled-task reconciliation recovers a dead worker’s run, even if no END marker was published. Ordinary completions still wait for END to preserve tail events.In single-process JSONL deployments, cancelling an admitted event-store mutation waits for its background file I/O, rollback, and bookkeeping to settle before releasing the thread write lock. This prevents an older cancelled write from recreating deleted records or rolling back a later successful write. Cancellation can therefore wait on slow storage; it does not stop an in-flight filesystem operation. Callers still waiting to acquire the lock can cancel without starting a mutation. A batch spanning multiple threads drains its current thread group before propagating cancellation; subsequent thread groups do not start.
After a run publishes its terminal stream marker, its process-local
RunRecordremains available for the existing five-minute grace period before cleanup; durable run history remains available throughRunStore, while the stream bridge retains its delivery tail on its separate cleanup schedule.With in-memory run storage, replacing a run snapshot under another thread removes its previous thread membership. Old-thread history and usage queries exclude it, and deleting the old thread preserves the reassigned run.
Run cancellation may land on any Gateway worker. A non-owning worker now persists the interrupt or rollback request for the live owner, which observes it during lease renewal and performs the normal cancellation flow; load-balancer routing alone no longer produces a 409. The first accepted action wins even if a retry lands on the owner, and accepted cancellation competes atomically with owner completion. Dead owners still follow lease takeover and orphan recovery. Cancellation latency is therefore bounded by the lease heartbeat interval.
Cancelling a model recovery probe, including while it is queued or waiting to retry, lets the next call check whether the provider has recovered. Cancellation does not count as a provider failure or release another call’s active recovery probe.
Model retries honor valid provider
Retry-Afterhints up to 24 hours, including waits longer than the local backoff cap. Malformed, non-finite, or over-limit hints use normal backoff instead. This applies to numeric and HTTP-date hints in LLM error handling and Codex’s retry path, and integer-second hints in Claude’s retry path. Codex prioritizes numericretry-after-mshints; past HTTP-date hints retry immediately.With lease heartbeat enabled, a transient RunStore renewal error is retried only until the last confirmed lease expires; the stale worker then cancels local execution and suppresses checkpoint, completion-hook, delivery-receipt, and thread-status finalization. A remote tool side effect already in flight may still be outside local cancellation.
Reconciliation uses an atomic takeover claim that re-checks the lease after candidate selection, so a successful owner renewal wins over orphan recovery and only one reconciler can report a run as recovered. When multiple Gateway workers share the Docker/AIO or E2B sandbox backend, also configure
sandbox.ownership.type: redis; E2B uses the leases during background startup and periodic reconciliation so duplicate/orphan cleanup cannot terminate a live peer’s sandbox.
See CONTRIBUTING.md for detailed Docker development guide.
Upgrading an existing checkout
Keep config.yaml, .env, and extensions_config.json. Stop the services you
currently use, run git pull --ff-only, then start the same mode again. Do not run
make config or make docker-init again for a routine source upgrade. If the new
version requires configuration changes, run make config-upgrade before restarting.
See Operations and Troubleshooting
for the commands for each mode.
To continue a thread paused at a static interrupt_before or interrupt_after
breakpoint, submit input: null (or omit input) to its /runs, /runs/wait,
or /runs/stream endpoint. This continues pending graph work with the source
turn’s knowledge scope from the selected checkpoint (the head by default).
An explicit empty object is new input, not a continuation; dynamic interrupts
still require command.resume. In delta mode, historical continuation rejects
rewrites that would discard pending execution; continue at the head instead.
Continuations retain the checkpoint’s MCP background_tasks projection; only
dictionary input refreshes it from the task repository.
Old null-input runs stored as {} cannot safely match a keyed null-input retry
after upgrading and return 409. Check the original run before submitting a new key.
When rolling out the resume-command idempotency fix across multiple Gateway
workers or Pods, route all keyed resume submissions and retries (command.resume
with Idempotency-Key, on thread-scoped /runs, /runs/stream, or /runs/wait)
only to upgraded workers until every worker serving these endpoints is upgraded.
Older workers ignore the private resume identity and compare only input: with
input: null, retrying deny can incorrectly reuse an earlier approve run.
If the load balancer cannot isolate upgraded workers, pause keyed resume traffic
until the rollout finishes, or stop all old workers before restarting on the new
version. The additive database migration keeps old run-history readers compatible;
it does not make old workers safe for resume admission. An upgraded worker returns
409 when retrying an identity-less legacy resume run, even for the same decision;
inspect that run and the current thread state before deciding to submit a new
action. See the run API contract.
Custom run stores must explicitly support every supplied idempotency field.
Keyed input and resume requests return HTTP 503 before run admission when the
store cannot guarantee that support. The built-in Memory and SQL stores support
both fields; older custom stores with an explicit idempotency_key parameter
continue to accept keyed input requests.
Gateway runs also assign IDs to input messages that omit one before storing the run record and invoking the graph. This keeps the message visible as one turn when the Web UI reconnects to an active run started by an API client. During a rolling upgrade, route keyed runs with ID-less input and their retries to upgraded workers until all Gateway workers are upgraded; older workers may reject those retries with 409. See the run API contract.
Option 2: Local Development
If you prefer running services locally:
Prerequisite: complete the “Configuration” steps above first (make setup). make dev requires a valid config.yaml in the project root. Set DEER_FLOW_PROJECT_ROOT to define that root explicitly, or DEER_FLOW_CONFIG_PATH to point at a specific config file. Runtime state defaults to .deer-flow under the project root and can be moved with DEER_FLOW_HOME; skills default to skills/ under the project root and can be moved with DEER_FLOW_SKILLS_PATH. Run make doctor to verify your setup before starting.
Configuration checks and agent-storage migrations use the installed backend environment through uv run --no-sync --project backend from the repository root. This preserves relative runtime paths and installed optional dependencies; see the setup checks and agent-storage migration.
On Windows, run the local development flow from Git Bash. Native cmd.exe and PowerShell shells are not supported for the bash-based service scripts, and WSL is not guaranteed because some scripts rely on Git for Windows utilities such as cygpath.
The documented root make commands invoke repository .sh files through Bash
explicitly. They therefore continue to work from source archives or filesystems
that do not preserve POSIX executable bits. When calling a script directly from
such a checkout, use bash ./scripts/.sh ....
-
Check prerequisites:
make check # Verifies Node.js 22+, pnpm, uv, nginxThe local
make check,make install,make dev, andmake startentry points use a directpnpmexecutable when available and otherwise fall back tocorepack pnpm. With native Windows Python, the shared runner checkspnpm.cmdbefore the genericpnpmlookup, which followsPATH/PATHEXTand may select an.exeor.batin the same or an earlier PATH directory. The Corepack fallback likewise checkscorepack.cmdbeforecorepack. POSIX Python keeps the generic names first, including when running under MSYS/Cygwin. The runner and diagnostics resolve repository paths absolutely, so these checks work regardless of the caller’s current directory. Corepack runs fromfrontend/, so it honors thepackageManagerversion pinned infrontend/package.json; enabling a global pnpm shim is not required.make checkdecodes tool diagnostics as UTF-8 and preserves Unicode pnpm errors on non-UTF-8 hosts. Invalid output bytes are replaced so the checker can report dependency failures instead of aborting while decoding output. -
Install dependencies:
make install # Install backend + frontend dependencies + pre-commit hooksHook setup calls pre-commit through uv, so uv’s tool directory need not be on
PATH. -
(Optional) Pre-pull sandbox image:
# Recommended if using Docker/Container-based sandbox make setup-sandboxReads the configured sandbox image from UTF-8
config.yaml, with or without a leading BOM, using LF or CRLF line endings. On macOS, a successful Apple Container pull completes this step even when Docker is not installed. If Docker is available, its image is also pulled. -
Start services:
make dev -
Access: http://localhost:2026
-
(Optional) Load sample memory data for local review: open
Settings > Memory, click Import memory, and selectbackend/docs/memory-settings-sample.json. The browser imports into the signed-in user’s memory.To replace memory for every registered user in a disposable review environment:
cd backend uv run python ../scripts/load_memory_sample.py --all-usersBulk mode supports SQLite/PostgreSQL user registries, creates a distinct timestamped backup directory for every import under
.deer-flow/memory-sample-backups/, and rejects the non-persistentdatabase.backend: memorymode. See backend/docs/MEMORY_SETTINGS_REVIEW.md for the complete review flow.
Local services always use their internal ports (8001, 3000, and 2026).
The root .env variable PORT configures only the published Docker ingress;
it does not change the Next.js port used by make dev.
Local startup waits up to 30 seconds for the Gateway port. On a slow cold start, set a longer wait without editing the launcher:
DEER_FLOW_GATEWAY_STARTUP_TIMEOUT=240 make dev
The value must be a positive integer in seconds. You can also set it in the
root .env, whose value takes precedence over the shell. It applies to local
make dev, make start, and their daemon variants, not Docker health checks
or LLM request timeouts. Check logs/gateway.log for startup errors before
increasing the wait; an expired deadline still stops the services.
Startup Modes
DeerFlow runs the agent runtime inside the Gateway API. Development mode enables hot-reload; production mode uses a pre-built frontend.
| Local Foreground | Local Daemon | Docker Dev | Docker Prod | |
|---|---|---|---|---|
| Dev | ./scripts/serve.sh --dev``make dev |
./scripts/serve.sh --dev --daemon``make dev-daemon |
./scripts/docker.sh start``make docker-start |
— |
| Prod | ./scripts/serve.sh --prod``make start |
./scripts/serve.sh --prod --daemon``make start-daemon |
— | ./scripts/deploy.sh``make up |
| Action | Local | Docker Dev | Docker Prod |
|---|---|---|---|
| Stop | ./scripts/serve.sh --stop``make stop |
./scripts/docker.sh stop``make docker-stop |
./scripts/deploy.sh down``make down |
| Restart | ./scripts/serve.sh --restart [flags] |
./scripts/docker.sh restart |
— |
Local and Docker development stop commands also stop running sandbox containers
whose names start with deer-flow-sandbox-. Containers with that text elsewhere
in their names are left alone.
make start and make start-daemon rebuild the frontend with next build on
every run. To reuse the last build instead, pass SKIP_FRONTEND_BUILD=1 (or add
--skip-frontend-build when calling ./scripts/serve.sh --prod directly). This
is opt-in: it fails fast when frontend/.next has no completed build.
Gateway owns /api/langgraph/* and translates those public LangGraph-compatible paths to its native /api/* routers behind nginx.
Cold agent imports during run creation use a dedicated worker pool, keeping unrelated Gateway requests responsive while the agent stack loads. A failed factory import prevents the run from being admitted.
For a read-only demo without the Gateway, run make build-static from frontend/,
then HOSTNAME=127.0.0.1 PORT=3000 node --env-file=.env .next/standalone/server.js
from the same directory. The build includes public demo assets and resolves
supported demo API reads locally; writes are unavailable. To display the homepage
GitHub star count, set GITHUB_OAUTH_TOKEN in frontend/.env before starting Node.
The token stays on the server; missing credentials or GitHub failures hide the
count. Restart Node after changing the token; no rebuild is needed.
LangGraph Studio (Optional)
The default make dev topology uses DeerFlow’s Gateway-embedded runtime and
does not require LangGraph Studio. To inspect and test the registered lead-agent
graph with the standalone development server, run the command from backend/
so the CLI discovers langgraph.json:
cd backend
uv run langgraph dev --allow-blocking
The command prints the local API and Studio UI URLs. This in-memory server is
for development and testing only. The flag permits DeerFlow’s synchronous
configuration and graph-factory setup during local Studio requests; it must not
be treated as a production-server setting. Local Studio authentication is
handled automatically, so the connection does not require custom headers. Use
DeerFlow’s documented production startup modes or a supported LangSmith
deployment for production workloads. Assistant ownership and provenance in this
standalone mode are server-owned: Studio can discover registered graphs and the
assistants it creates, and normal assistant-version selection remains available.
Before the locked local runtime loads its persisted development store, DeerFlow
repairs legacy assistant rows and version history so historical client metadata
cannot restore server privileges or be discarded by the runtime’s startup
cleanup. Keep the backend dependencies synchronized with uv sync; this
compatibility path requires the declared LangGraph runtime versions and logs a
warning if the persisted-store contract no longer matches its expectations.
The documented command uses LangGraph’s file-based custom-app loader, which is
also covered directly by DeerFlow’s regression tests.
Standalone runs using if_not_exists="create" retain config and run metadata
on the newly created thread, including searchable tags; run metadata takes
precedence for duplicate keys. Thread ownership and MCP incarnation remain
server-owned, and later runs do not replace the thread’s creation metadata.
For workflows that invoke backend/langgraph.json through LangGraph Studio or
a direct LangGraph Server, DeerFlow consumes the authenticated identity
published by that runtime and uses it for custom-agent configuration/SOUL, user
skills and skill policy, uploads, thread data, and memory reads/writes. This
keeps authenticated runs out of the shared default filesystem bucket, and the
server-owned identity takes precedence over ordinary client-supplied user_id
values. External identities such as email addresses are mapped to stable,
collision-resistant directory-safe user IDs before accessing DeerFlow storage.
The default DeerFlow service topology remains the Gateway-embedded runtime
described above.
Gateway runs automatically enforce native delivery for artifacts created or modified under /mnt/user-data/outputs: present_files must present at least one output produced by the current run, and the terminal run.delivery receipt must be durably recorded. Virtual artifact paths are resolved within the same authenticated user and thread scope that produced the output before the output-directory boundary is validated. Runs that do not produce output artifacts keep ordinary conversational behavior.
Thread-scoped runs.wait() calls that finish with status: error report the current run error instead of an earlier answer. The asynchronous Python LangGraph SDK raises by default; pass raise_error=False to inspect the returned status and error.
DeerFlow’s built-in custom events are available through both LangGraph streaming interfaces: native clients can continue subscribing to stream_mode="custom", while callback-based integrations can consume the same payloads as on_custom_event records from astream_events(version="v2"). The callback event name matches the payload’s type field.
Docker Production Deployment
./scripts/deploy.sh supports building and starting separately:
# One-step (build + start)
./scripts/deploy.sh
# Two-step (build once, start later)
./scripts/deploy.sh build # build all images
./scripts/deploy.sh start # start pre-built images
# Stop
./scripts/deploy.sh down
Advanced
Sandbox Mode
DeerFlow supports multiple sandbox execution modes:
- Local Execution (runs sandbox code directly on the host machine)
- Docker Execution (runs sandbox code in isolated Docker containers)
- Docker Execution with Kubernetes (runs sandbox code in Kubernetes pods via provisioner service)
The optional Tenki provider retains failed termination handles for retry and rejects late acquisitions after reset or shutdown. If shutdown reports outstanding work, retry after its SDK workers finish or the remote service recovers. At process exit, outstanding cleanup is logged without an exception traceback.
In Local Execution, glob and grep can search the skills root (default
/mnt/skills) across its mounted categories. Patterns stay relative to that
root, and one result limit covers all categories. User isolation, disabled-skill
filtering and read-only mounts still apply. Configured category mounts such as
build or logs remain searchable; ignore rules apply inside each mount. A broken
category mount stops the root search, and the Gateway warning log identifies its
virtual path.
Sandbox references in conversation state are server-owned. External run and
thread-state APIs reject caller-supplied sandbox values; when restoring a
checkpoint, the runtime resolves the reference against the authenticated user
and thread before a tool can reuse it. A missing runtime thread ID raises an
error even when the referenced sandbox is cached.
When host Bash is enabled for Local Execution, DeerFlow starts OS detection with uname -s, then uses sw_vers on Darwin. On Linux, it reads host system files such as /etc/os-release only when the active sandbox policy permits it. Host filesystem path checks still apply; after a blocked path, the agent is directed to use a permitted command-only probe or virtual path instead of repeating the rejected command.
For Docker development and production, service startup follows the configured
sandbox mode. With AioSandboxProvider, an omitted provisioner_url, an empty
string ("" or ''), or YAML null (null, ~, or a blank value), including
trailing comments, selects aio: provisioner is not started and the Docker
socket overlay is enabled. This grants the gateway host root-equivalent Docker
access; see the sandbox security notes.
A nonempty URL selects provisioner mode without that overlay. Quoted "null"
and "~" are nonempty strings, not YAML nulls; mode detection does not validate
URLs. The default local provider never enables the socket overlay.
Local AIO sandbox port allocation stops at TCP port 65535. If the remaining ports from the configured starting port are occupied, allocation reports that no port is available instead of attempting an out-of-range bind.
AIO teardown failures retain a generation-bound cleanup record. Partial shutdown reports failure and can be retried; retries verify the runtime identity and absence before completing cleanup. Healthy replacements with the same logical ID remain reusable, including after background recovery into the warm pool.
See the Sandbox Configuration Guide to configure your preferred mode. For absolute skills paths and custom directory mappings on Linux, macOS, Windows, WSL, or a Docker Gateway, see Skills and sandbox paths.
Managed async AIO runs can opt into bounded sandbox prewarming to overlap startup with the first model call. Direct SDK graphs keep lazy acquisition. It defaults off; see the guide and sandbox benchmarks before enabling it on a shared host.
Remote directory listings report traversal failures (for example, unreadable directories) as incomplete results, even when no entries were returned. A missing start path is reported separately as “Directory not found.”
The optional Tenki cloud sandbox provider
uses Tenki SDK 1.4.0 or newer. Timed-out commands preserve partial output and
report Exit Code: 124; unsuccessful health checks cannot reclaim a warm sandbox.
Health probes tolerate login-shell output around the ok line, and failures log
the sandbox ID and probe output before replacing the sandbox.
The optional OpenSandbox provider mirrors generated output files to local thread storage at turn completion for Gateway previews and downloads; see its synchronization limits and failure behavior. An invalid UTF-8 filename is skipped without blocking other outputs, and same-size content edits are still copied on release. Outputs with case- or Unicode-equivalent file or directory names are skipped before copying, and aliases of existing local paths are rejected, preserving previous complete copies.
The sandbox benchmark also supports OpenSandbox cold acquisition, warm reuse and concurrent scopes, using the configured service connection and reporting unconfirmed cleanup as a failed run. Live runs require a disposable OpenSandbox service and runtime home.
BoxLite shutdown rejects late VM registration and keeps its SDK loop open while in-flight acquisitions finish. If they cannot drain within five seconds, shutdown fails with resources still owned and can be retried.
Successful read_file calls treat a JSON document’s error field as file data,
not a failure of the read tool. This keeps receipts accurate when inspecting
saved API error responses and avoids blocking later reads when the optional
tool_progress guard is enabled. Actual read failures and output truncation
still retain their error or partial-result status.
MCP Server
Tool steps mark explicit execution failures. Scheduled-task steps show completed operations only after a valid response, rather than when the request is issued.
In the chat UI, enable Token Usage → Debug to inspect generic/MCP tool calls. Each Tool details panel starts collapsed and shows the tool name, call ID, input, and received result or explicit error. Large previews are truncated; fields whose names exceed the remaining preview budget are omitted rather than renamed. Structured previews retain complete JSON syntax, including escaped strings and closing delimiters. Array previews stop when the text budget cannot display another element; literal ellipsis values are preserved. Consecutive generated markers at an array’s end share one ellipsis indicating an omitted suffix; markers before later values retain their positions. Text results retain their original representation, including large numeric IDs and duplicate JSON keys, without reparsing. Text exceeding the limit is shown as a prefix with a truncation notice; structured objects and arrays are formatted separately. Copy actions copy only the displayed preview. This is a frontend view of data already received by the browser, without an additional secret-redaction layer.
In plan mode, malformed TODO statuses return normal tool-validation errors without aborting token attribution, so the agent can correct the call.
Tool-produced paths and URLs can be retained as short artifact handles across context compaction (tool_artifacts in config.yaml). Handles distinguish separate tool-result occurrences, even when a provider reuses call IDs. Standard MCP image URLs are captured alongside file references, including results from stdio and HTTP servers. Detected file URLs preserve their query strings and fragments. Free-text sandbox paths retain spaces inside matching ASCII quotes, backticks, or curly quote pairs, including backslash-escaped wrappers. Unterminated spans and delimited values containing a line break or tab are skipped. Free-text artifact references retain balanced ASCII and CJK brackets in literal paths and URLs while trimming surrounding prose punctuation. When PII redaction is enabled, model-visible artifact labels follow that policy; internal references stay intact for tool argument resolution. The configured registry limit retains the newest artifacts, while checkpointed processing identities prevent evicted results from being recaptured after restart. Resolution runs before authorization and write-safety checks; unknown or expired handles return an error without executing the tool. Small unknown structured results may be retained as complete JSON up to 4096 UTF-8 bytes; empty or oversized payloads are skipped. Handles are agent-local: task arguments resolve parent handles to concrete references, and delegated reports must return concrete references rather than child-local handles. A truncated model projection reports how many handles are omitted.
DeerFlow supports configurable MCP servers and skills to extend its capabilities.
Inline MCP audio results are not supported. Stdio tools report an explicit error, matching HTTP/SSE tools, rather than treating base64 audio as a successful text result.
When durable MCP background tasks are enabled, agents can use list_background_tasks(status="failed") or status="input_required" to find failed tasks or tasks awaiting input in the current chat. Other supported statuses are submitted, working, completed, and cancelled; omitting the status preserves existing behavior. The database applies the filter before limiting results to the 20 most recent matching tasks. When combined with active_only=true, both filters apply: active statuses are submitted, working, and input_required, so terminal statuses return an empty list. GET /api/threads/{thread_id}/mcp-tasks supports the same status and active_only filters before its limit (default 50, range 1–100); for example, ?status=failed&limit=20. Unknown statuses return 422, and omitting the filters preserves the existing response.
For HTTP/SSE MCP servers, OAuth token flows are supported (client_credentials, refresh_token).
Missing, malformed, or out-of-range token response expires_in values use a one-hour default lifetime. This includes lifetimes that cannot be added to the current time without overflowing the expiry timestamp.
Durable HTTP/SSE task status and cancellation calls select configured user_auth credentials using the persisted task owner, including after restart; per-request secrets are not retained for background calls. If a request-scoped credential overrides submit authentication, both credentials must authorize access to the same remote task.
For stdio MCP servers, per-tool call timeouts can be configured with tool_call_timeout; durable background-task calls honor the same setting for HTTP/SSE servers as well.
Durable background-task status and cancellation calls renew their claims while they run, so a slow response can still be saved after the original lease duration. If ownership is lost or renewal cannot be confirmed, the worker stops its local call and leaves recovery to the durable state. See control-call leases for recovery and shutdown behavior.
The persistent stdio session pool evicts only idle sessions at capacity. If every pooled session is executing a tool, a new session request fails with a capacity error and can be retried after a call finishes.
For stdio file outputs, a bare filename is linked to a uniquely matching file created or changed by that call. Filenames embedded in unrelated paths, including Windows backslash paths, are left intact. Explicit remote URLs (scheme://...) retain their original query parameters and fragments even when these contain a generated file’s name or path, including URLs with backslash-escaped quotes or markup delimiters; local references outside the URL still translate normally.
For HTTP/SSE background-task calls, session_init_timeout separately bounds connection setup (including the SSE endpoint event) and MCP initialization together; it stops applying once the tool call begins. Initialization deadline errors identify the server and configured time limit.
Ordinary task subagents retain the parent run’s captured thread incarnation for MCP calls, including legacy threads, so delegation preserves the same lifecycle scope.
MCP tool names are prefixed with _ by default to prevent collisions across servers. If a server already namespaces its own tools, set tool_name_prefix: false on that server in extensions_config.json to keep the original names. Disable the prefix only when the resulting names remain unique across all enabled servers.
Signed-in users’ notification toggle, default model, conversation mode, and reasoning effort are saved to their account and restored on other browsers or after clearing browser storage. Browser notification permission still needs to be granted on each device. Changes retry after network failures; unsent changes survive a reload in the same tab. Concurrent edits to different fields are preserved; for the same field, the last server write wins. Existing unscoped browser preferences are not uploaded automatically because they have no account owner; reselect those settings once after upgrading. Static demos and auth-disabled development keep browser-local settings. Thread-specific model overrides and other display preferences remain local.
In a new chat, the submitted question stays above its streamed reasoning and tool steps while the server creates the conversation and confirms the message.
Markdown and JSON conversation exports from the chat header or sidebar read all persisted history pages, including earlier turns outside the loaded view or compacted model context. A failed history read stops the export instead of downloading a partial transcript. Public demos export their bundled messages.
Capability Center groups plugins by office collaboration, documents and knowledge, search and research, business and data, and development and operations. The directory includes setup references alongside existing MCP configurations and Lark. Recommended integrations and built-in support do not imply an installed or verified connection; the Installed filter shows configured MCP entries and installed Lark only.
Personal MCP connections configured in the web interface are persisted per user. Unexpected failures when changing deployment or personal MCP configuration return fixed error messages; exception details stay in server logs. Deployment tools remain shared. Administrators can add, edit, enable, disable and delete shared MCP servers under Platform provided; ordinary users see their status without controls. Personal plugin switches affect only the signed-in user’s connections. See connection ownership.
For plugin manifests, adapter registration, and Agent capability selection, see Capability Center integration contract.
DingTalk and WeCom group notifications and HubSpot CRM are bundled configurable plugins. Administrators supply robot credentials or a HubSpot private app token; Agents can then send requested group notifications, list companies, or create contacts. Saving configuration performs no external write. These plugins reuse the existing MCP lifecycle and require no separate plugin service. See the integration contract above for required fields, scopes, and feature boundaries.
Plugin brand icons are bundled locally. When adding or editing one personal MCP plugin, users can upload a PNG, JPG, or WebP image (up to 2 MB), preview it, or restore the default icon. Changes take effect only after Save; custom icons persist across browsers as a normalized 128px PNG in the server entry’s display-only presentation.icon metadata. They are not sent to the MCP transport.
Capability Center > Plugins adds, replaces, and deletes one MCP server at a time through targeted mutations that preserve concurrent sibling changes; deletes use a bodyless URL-addressed request. An invalid stdio command on one server no longer blocks toggling another, while enabling that invalid server remains protected by the command allowlist and surfaces the backend validation message in the UI.
Targeted updates accept both DeerFlow’s type field and the MCP-spec transport field for SSE/HTTP servers.
Runtime MCP and skill updates replace extensions_config.json atomically, so an interrupted write cannot leave the shared configuration truncated or partially written. Every Gateway worker or instance that reads the same file picks up a new revision on its next read (the cache checks the file’s content signature), so MCP and skill changes made through one replica apply to the others without a restart; a missing, truncated, or invalid revision keeps the previous configuration until a complete one lands, including when the file disappears during a reload.
The admin MCP cache reset advances a durable generation marker in the writable config directory. Every Gateway worker mounting that same directory retires its own cached tools and pooled sessions before the next lookup; replicas with independent filesystems are not implicitly covered. If no config path is available, the API reports a process-local reset instead.
The parsed extensions configuration and its recorded content digest come from the same read, so a racing edit followed by a timestamp-preserving backup restore cannot leave a different revision cached indefinitely.
Changes to the global mcpInterceptors chain rebuild every server’s tool wrappers,
including when a server configuration is edited at the same time.
extensions_config.json accepts UTF-8 with or without a leading byte-order mark (BOM), including files saved as UTF-8 with BOM by an editor.
When deferred MCP tool discovery is enabled, tool_search supports opt-in literal keyword queries such as keywords:notebook jupyter. Keywords match names and descriptions in any order, ranked by distinct term coverage and then name hits; ties retain catalog order. This mode uses the first 256 characters after the prefix and at most 16 unique whitespace-separated terms, returning up to five tools. Punctuation stays literal. Bare queries and +required ranking keep their existing regular-expression semantics; select:tool_a,tool_b still fetches all exact, case-sensitive name matches.
MCP routing hints can also prefer a specific MCP tool for matching requests without forbidding other tools. When tool_search defers MCP schemas, matching routing metadata can auto-promote up to tool_search.auto_promote_top_k deferred schemas before the model call.
Deferred MCP schema promotions last for the current user turn. They remain active across that turn’s model/tool loop and checkpoint resume, then become deferred again when a new user message starts the next turn; the next turn must discover them again through tool_search or match their routing hints. Resuming a legacy in-flight checkpoint preserves previously searched tools even when routing hints also match that turn.
Deferred tool_search regex queries share a 100 ms execution budget across the catalog, including +required ranking. Patterns are limited to 256 characters and searchable names/descriptions to 65,536 characters each. Queries that exceed a limit return a retry hint without promoting partial results; use a simpler pattern, keywords:, or select: with exact names. Oversized catalog fields are reported with the tool name, field and length, and logged with the MCP server name when available; use keywords: or select: because simplifying the pattern cannot shrink a catalog field. Exact selection remains uncapped and bypasses regex limits; the literal keywords: mode also bypasses regex limits. Invalid regex syntax still falls back to a literal substring match.
OpenViking users can register the official Streamable HTTP endpoint at /mcp
with an owner-bound USER API key. The native forget tool is exposed for
capability parity; deletion is irreversible, so it should be called only after
explicit user confirmation. DeerFlow does not enforce that confirmation. This
explicit, model-selected MCP tool path can run alongside the separate automatic
OpenViking memory backend; it does not replace automatic turn capture or recall. See the
OpenViking MCP tools configuration.
The Gateway can adapt an MCP server’s ordinary submit / status / cancel tools into durable background tasks. The Agent sees only the configured submit tool and a DeerFlow-local task ID; remote IDs are persisted before the submit call returns, while status and cancel stay internal to the runtime. Polling uses cross-worker leases, exponential retry backoff, scoped MCP sessions, bounded result storage, and restart recovery. A status-tool isError is retained as a bounded diagnostic and retried; servers report a permanent remote-task outcome through a normal structured result with status: "failed". Remote poll hints are finite positive numbers capped at 24 hours, artifact-reference JSON is limited to 64 KiB, and task/server identifiers are validated against their durable SQL column limits before persistence. Input-required and terminal updates wake the current chat through idempotent Agent runs, while list_background_tasks and cancel_background_task let the Agent manage tasks without asking users for remote handles. Current-thread tasks are available through GET /api/threads/{thread_id}/mcp-tasks, its detail endpoint, and POST /api/threads/{thread_id}/mcp-tasks/{task_id}/cancel; when the task runtime actually starts, the Web UI exposes the same safe local view from the chat header with live status refresh, cancellation, and on-demand result, artifact, input-request, status-error, and cancellation-retry details. Default-disabled and memory-backend deployments hide that UI and do not poll the task endpoints. A failed remote cancellation remains queued with backoff, and its latest bounded error and attempt count stay visible in the expanded task card. Enable mcp_tasks in config.yaml, configure task_toolsets with exact raw tool names in extensions_config.json, and use a SQL database backend (sqlite or postgres). Task-enabled server connection, authentication, interceptor, timeout, or binding changes require a Gateway restart so Agent tool discovery and background calls cannot use different configuration versions. input_required is notification-only for now: DeerFlow can display the request but cannot yet submit the user’s answer back to the remote task.
For deployment-level HTTP/SSE servers with task_toolsets, discovery, ordinary
tools, and background task calls share OAuth token state within one Gateway
process, including across tool-cache resets. Rotated refresh tokens stay in
memory; they are not written back to configuration or shared across processes.
After a restart, the configured refresh token must still be valid.
Notification launch and failed Agent-run deliveries use capped exponential backoff with a visible attempt count and stop after five failed attempts. When a bounded ordinary release exceeds its drain deadline, the service retains ownership until it settles. A permanently rejected target such as a deleted chat is dead-lettered immediately instead of retried forever or recreated. Cancellation endpoints return after durably recording the request; the background service owns the potentially slow remote MCP call and its retry schedule.
Notification runs keep their trusted delivery instruction separate from the framed, untrusted remote event payload. The process-started task runtime—not a hot config read—controls whether the task-management tools are exposed, so changing mcp_tasks requires a Gateway restart. list_background_tasks and cancel_background_task are ordinary business tools governed by the agent’s own authorization, like every other tool.
See the MCP Server Guide for detailed instructions.
Security: pass per-request MCP credentials only through config.context.secrets;
credentials must never be placed in either run metadata surface
(metadata.auth_token or config.metadata.auth_token). See MCP credential migration and cleanup
for the supported interceptor flow and the required rotation and retained-copy
cleanup when migrating from legacy metadata credentials.
IM Channels
DeerFlow supports receiving tasks from messaging apps. Channels auto-start when configured — no public IP required for any of them.
Generated-file replies explain when a channel cannot send attachments. If some or all uploads fail, DeerFlow sends one combined notice naming the unsent files when the platform still accepts replies. Successful uploads add no duplicate confirmation. Files remain in the DeerFlow conversation; an expired reply window is logged without switching to proactive messaging.
Cancelling a channel restart discards its pending configuration reload, preserving newer runtime settings applied afterward.
Invalid UTF-8 in channels/store.json follows the same recovery policy as malformed JSON:
SQL migration warns and skips the file, leaving it unchanged so channels can start; the
memory backend’s JSON store warns and starts empty, replacing the file on its next write.
On Windows, JSON runtime channel-config saves wait for a competing file-lock holder without the operating system’s roughly ten-second cutoff. A wait reaching 50 retries (about ten seconds) logs the lock-file path once; permanent lock errors fail immediately.
Conversation-binding, runtime channel-credential and WeChat auth-state saves preserve the original persistence failure when staged-file cleanup also fails. Remaining cleanup still runs, and warnings identify copies that could not be removed. Buzz keeps failed snapshots available for retry, even if cleanup or logging fails.
On startup, runtime credential and WeChat auth stores remove their own ..deerflow-*.tmp
staging files older than 24 hours. Fresh files, symlinks and unrelated files are preserved. Older
anonymous tmp*.tmp files require manual inspection because their writer cannot be identified safely.
DeerFlow can also expose user-owned IM channel connections in the workspace UI. When channel_connections is enabled, logged-in users can bind Telegram, Slack, Discord, Feishu/Lark, DingTalk, WeChat, WeCom, QQ, or Buzz from the sidebar / Settings > Channels. It reuses the existing outbound channels.* transports, so no public IP or provider callback URL is required. Incoming IM messages then run under the connected DeerFlow user account. See IM Channel Connections for setup and security notes. Buzz backlog paging stops with a warning if timestamp-based queries cannot advance, and channel removal closes both live and backlog subscriptions.
For Discord and Buzz, the local and Docker launchers detect the required optional
dependencies when both channel_connections.enabled and the corresponding
channel_connections..enabled are true. Selecting Discord in
make setup sets these flags. A nonempty UV_EXTRAS overrides configuration
auto-detection, so include discord or buzz there if you set it manually.
| Channel | Transport | Difficulty |
|---|---|---|
| Telegram | Bot API (long-polling) | Easy |
| Slack | Socket Mode | Moderate |
| Feishu / Lark | WebSocket | Moderate |
| Tencent iLink (long-polling) | Moderate | |
| WeCom | WebSocket | Moderate |
| WebSocket (text-only C2C and group @mentions; four/five passive replies per source) | Moderate | |
| DingTalk | Stream Push (WebSocket) | Moderate |
| Buzz | Nostr relay (WebSocket, NIP-42) | Moderate |
Attachments saved by the shared IM ingestion pipeline or Feishu/DingTalk’s embedded downloads keep distinct filenames, including when concurrent uploads choose the same name. The final filename is passed to the agent and used for sandbox sync; an existing conversation file is not overwritten. Embedded client uploads also keep earlier same-name files under distinct names. Renamed originals and Markdown companions retain private source snapshots and become visible only after the complete copy is published.
Feishu and DingTalk attachment transfers return standalone temporary sandboxes to the pool after synchronization, without interrupting an active run.
Configuration in config.yaml:
Discord’s channels.discord.allowed_guilds accepts one positive numeric guild
ID (quoted or unquoted) or a YAML list. Unset, null, [], or a blank string
allows all guilds. Invalid entries are ignored with a warning; any other
configured value yielding no valid ID denies every guild and logs an error.
allowed_channels accepts one channel ID (quoted or unquoted) or a YAML list
of IDs exempt from mention_only, within allowed guilds. An empty value gives
no exemptions, so mention_only applies everywhere when enabled.
Discord inbound attachments have a 25 MiB download limit. Oversized-file notices show the enforced limit so senders can resize or compress the attachment.
channels:
# LangGraph-compatible Gateway API base URL (default: http://localhost:8001/api)
langgraph_url: http://localhost:8001/api
# Gateway API URL (default: http://localhost:8001)
gateway_url: http://localhost:8001
# Maximum queued or provider-reserved inbound messages (default: 1000)
inbound_queue_maxsize: 1000
# Fixed number of long-lived inbound handler workers (default: 5)
max_concurrency: 5
# Seconds to drain accepted work before cancelling active handlers (default: 3)
shutdown_grace_period_seconds: 3
# Optional: global session defaults for all mobile channels
session:
assistant_id: lead_agent # or a custom agent name; custom agents are routed via lead_agent + agent_name
context:
thinking_enabled: true
is_plan_mode: false
subagent_enabled: false
feishu:
enabled: true
app_id: $FEISHU_APP_ID
app_secret: $FEISHU_APP_SECRET
# domain: https://open.feishu.cn # China (default)
# domain: https://open.larksuite.com # International
qq:
enabled: true
app_id: $QQ_APP_ID
client_secret: $QQ_CLIENT_SECRET
allowed_users: [] # QQ OpenIDs, not QQ account numbers; empty allows all; one ID is one entry
wecom:
enabled: true
bot_id: $WECOM_BOT_ID
bot_secret: $WECOM_BOT_SECRET
# Optional: extra host suffixes inbound media downloads may come from, in
# addition to the built-in qq.com family and WeCom's official COS media
# host (ww-aibot-img-1258476243..myqcloud.com); add one here if
# WeCom rotates to a new COS account or media goes through a proxy
allowed_media_hosts: []
slack:
enabled: true
bot_token: $SLACK_BOT_TOKEN # xoxb-...
app_token: $SLACK_APP_TOKEN # xapp-... (Socket Mode)
allowed_users: [] # empty = allow all
telegram:
enabled: true
bot_token: $TELEGRAM_BOT_TOKEN
# Optional: render final Markdown replies as Telegram Rich Messages.
rich_messages: false
allowed_users: [] # numeric user IDs, not @usernames; empty = allow all
wechat:
enabled: false
bot_token: $WECHAT_BOT_TOKEN
ilink_bot_id: $WECHAT_ILINK_BOT_ID
qrcode_login_enabled: true # optional: allow first-time QR bootstrap when bot_token is absent
allowed_users: [] # iLink user IDs; empty allows all; one ID is one entry
polling_timeout: 35 # timing values must be positive finite seconds
polling_retry_delay: 5
qrcode_poll_interval: 2
qrcode_poll_timeout: 180
state_dir: ./.deer-flow/wechat/state
max_inbound_image_bytes: 20971520
max_outbound_image_bytes: 20971520
max_inbound_file_bytes: 52428800
max_outbound_file_bytes: 52428800
# Inbound media downloads stream with the caps above and are restricted to
# these host suffixes (plus *.qq.com and the cdn_base_url host by default)
allowed_media_hosts: []
# Optional: per-channel / per-user session settings
session:
assistant_id: mobile-agent # custom agent names are also supported here
context:
thinking_enabled: false
users:
"123456789":
assistant_id: vip-agent
config:
recursion_limit: 150
context:
thinking_enabled: true
subagent_enabled: true
dingtalk:
enabled: true
client_id: $DINGTALK_CLIENT_ID # Client ID of your DingTalk application
client_secret: $DINGTALK_CLIENT_SECRET # Client Secret of your DingTalk application
allowed_users: [] # empty = allow all
card_template_id: "" # Optional: AI Card template ID for streaming typewriter effect
Notes:
assistant_id: lead_agentcalls the default LangGraph assistant directly.- If
assistant_idis set to a custom agent name, DeerFlow still routes throughlead_agentand injects that value asagent_name, so the custom agent’s SOUL/config takes effect for IM channels. - IM channel workers call Gateway’s LangGraph-compatible API internally and automatically attach process-local internal auth plus the CSRF cookie/header pair required for thread and run creation.
- Inbound work is bounded to
inbound_queue_maxsizepending messages plusmax_concurrencyactive workers. When capacity is exhausted, socket/polling providers drop new messages before sending DeerFlow’s working acknowledgment and emit a rate-limited warning. Buzz leaves its replay cursor unchanged and reconnects for relay replay; GitHub webhooks return503, marking the delivery failed for manual/API redelivery. Shutdown closes admission immediately, keeps channel transports available while accepted messages drain for up toshutdown_grace_period_seconds, then cancels and awaits active handlers before closing provider resources; the Gateway’s outer timeout can cancel an incomplete shutdown without detaching those resources. - Telegram shutdown retains ongoing cleanup across a timeout or cancellation so a retry can finish it. Stopping during startup also cancels initialization and closes the partially initialized bot’s HTTP clients before retiring its worker loop. If the Telegram loop has already stopped, shutdown warns that download clients may remain open and releases the abandoned cleanup state; later stops do not repeat that warning.
- Slack stop waits up to five seconds for an in-flight Socket Mode connection and its cleanup. If cleanup is still pending, the previous instance stays tracked and a restart can be retried after it finishes. SDK cleanup failures report their original error and remain retryable.
- In Telegram private chats, text, attachment and streaming replies (including “Working on it…”) can still be delivered if their referenced user or bot message was deleted, such as after clearing chat history during a run. Group reply-target requirements remain unchanged to preserve forum routing.
- In Telegram groups, replying to a delivered bot response or attachment continues its DeerFlow conversation. Reply mappings survive restarts and stay scoped to the user’s connection when one is bound. New, unrelated group messages still start separate conversations; private-chat routing is unchanged.
- Feishu/Lark now queues rapid follow-up messages per mapped DeerFlow
thread_idinstead of immediately surfacing the generic busy reply, and topic replies keep a per-message card with a compact source-message preview across queued/running/final patches. - Streaming IM channels treat backend
errorevents like transport failures and follow the same reply and retry path. The channel logs the error type and message for diagnosis.
Set the corresponding API keys in your .env file:
# Telegram
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrSTUvwxYZ
# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
# Feishu / Lark
FEISHU_APP_ID=cli_xxxx
FEISHU_APP_SECRET=your_app_secret
# WeChat iLink
WECHAT_BOT_TOKEN=your_ilink_bot_token
WECHAT_ILINK_BOT_ID=your_ilink_bot_id
# WeCom
WECOM_BOT_ID=your_bot_id
WECOM_BOT_SECRET=your_bot_secret
# DingTalk
DINGTALK_CLIENT_ID=your_client_id
DINGTALK_CLIENT_SECRET=your_client_secret
Telegram Setup
- Chat with @BotFather, send
/newbot, and copy the HTTP API token. - Set
TELEGRAM_BOT_TOKENin.envand enable the channel inconfig.yaml. - The bot accepts inbound text, photos, and documents (with or without captions). Hosted Bot API downloads are limited to 20 MB per attachment.
Slack Setup
- Create a Slack App at api.slack.com/apps → Create New App → From scratch.
- Under OAuth & Permissions, add Bot Token Scopes:
app_mentions:read,chat:write,im:history,im:read,im:write,files:write. - Enable Socket Mode → generate an App-Level Token (
xapp-…) withconnections:writescope. - Under Event Subscriptions, subscribe to bot events:
app_mention,message.im. - Set
SLACK_BOT_TOKENandSLACK_APP_TOKENin.envand enable the channel inconfig.yaml.
Feishu / Lark Setup
- Create an app on Feishu Open Platform → enable Bot capability.
- Add permissions:
im:message,im:message.p2p_msg:readonly,im:resource. - Under Events, subscribe to
im.message.receive_v1and select Long Connection mode. - Copy the App ID and App Secret. Set
FEISHU_APP_IDandFEISHU_APP_SECRETin.envand enable the channel inconfig.yaml. - The bot supports inbound text, image, and file messages. Inbound attachment downloads are limited to 20 MB per attachment.
- (Optional) Interactive clarification cards: set
card_interaction_enabled: trueunderchannels.feishuto let the agent’s option-based clarifications render as clickable button cards, preserving any background context before the question. Then, in the developer console, open Callback Configuration (回调配置 — a separate tab from Event Subscriptions), subscribe tocard.action.triggerin Long Connection mode, and publish a new app version. A click resolves the clarification exactly like typing the choice; only the user who triggered the clarification can operate its card. Button answers are saved in the conversation history, and subsequent clarifications continue in the same thread.
If the SDK repeatedly logs CERTIFICATE_VERIFY_FAILED, see Feishu / Lark TLS troubleshooting to check the Gateway’s CA trust configuration.
Scheduled-task result and lifecycle notices can also be pushed to a bound Feishu/Lark user, without a new inbound message. They use the same notification outbox and localized summaries as WeCom. The app must be able to send to that user; see Feishu scheduled-task notifications.
WeChat Setup
- Enable the
wechatchannel inconfig.yaml. - Either set
WECHAT_BOT_TOKENin.env, or setqrcode_login_enabled: truefor first-time QR bootstrap. - When
bot_tokenis absent and QR bootstrap is enabled, watch backend logs for the QR content returned by iLink and complete the binding flow. - After the QR flow succeeds, DeerFlow persists the acquired token under
state_dirfor later restarts. - For Docker Compose deployments, keep
state_diron a persistent volume so theget_updates_bufcursor and saved auth state survive restarts. - Outbound images/files enforce
max_outbound_image_bytes/max_outbound_file_bytes(20 MiB / 50 MiB defaults) while reading, including files that grow after resolution. Oversize reads are rejected before encryption/upload instead of sending a truncated prefix. Non-positive limits disable the corresponding cap. allowed_userstakes iLink user IDs. Unset,null,[], or a blank string allows everyone. A single ID is one entry, not a sequence of characters, and an unquoted integer-valued number is stored as that integer’s text. A scalar string containing commas or interior whitespace logs a warning but remains one literal ID; use a YAML list for multiple IDs. Invalid entries are ignored with a warning; any other configured value that yields no valid ID denies every user and logs an error./connectis still accepted before that check, and a denied sender is dropped before inbound media is downloaded.- Shutdown waits for in-flight cursor writes. On token expiry, DeerFlow persists the cursor reset and removes the saved token before completing poller cancellation.
- Inbound attachments keep separate staged copies even when filenames match within one message or across messages. Their original filenames are preserved for upload ingestion. Private staged copies are removed after ingestion or when a message is dropped or fails.
WeCom Setup
- Create a bot on the WeCom AI Bot platform and obtain the
bot_idandbot_secret. - Enable
channels.wecominconfig.yamland fill inbot_id/bot_secret. - Set
WECOM_BOT_IDandWECOM_BOT_SECRETin.env. - Make sure backend dependencies include
wecom-aibot-python-sdk. The channel uses a WebSocket long connection and does not require a public callback URL. - The current integration supports inbound text, image, and file messages. Final images/files generated by the agent are also sent back to the WeCom conversation.
DingTalk Setup
- Create a DingTalk application in the DingTalk Developer Console and enable Robot capability.
- Set the message receiving mode to Stream Mode in the robot configuration page.
- Copy the
Client IDandClient Secret, setDINGTALK_CLIENT_IDandDINGTALK_CLIENT_SECRETin.env, and enable the channel inconfig.yaml. - (Optional) To enable streaming AI Card replies (typewriter effect), create an AI Card template on the DingTalk Card Platform, then set
card_template_idinconfig.yamlto the template ID. You also need to apply for theCard.Streaming.WriteandCard.Instance.Writepermissions.
DingTalk’s Markdown fallback displays tables as labeled blockquotes, preserving escaped pipes (\|) within cells and empty leading or trailing cells so values stay under the correct column labels.
DingTalk token caching uses a two-hour fallback when the provider’s token lifetime is invalid or no longer than the five-minute refresh margin, avoiding a refresh on every request.
When DeerFlow runs in Docker Compose, IM channels execute inside the gateway container. In that case, do not point channels.langgraph_url or channels.gateway_url at localhost; use container service names such as http://gateway:8001/api and http://gateway:8001, or set DEER_FLOW_CHANNELS_LANGGRAPH_URL and DEER_FLOW_CHANNELS_GATEWAY_URL.
Commands
Once a channel is connected, you can interact with DeerFlow directly from the chat:
| Command | Description |
|---|---|
/new |
Start a new conversation |
/status |
Show current thread info |
/models |
List available models |
/model [name|default] |
Show or pin the current conversation’s model |
/memory |
View memory |
/agent list |
List your Custom Agents |
/agent use |
Start a new conversation with a Custom Agent |
/help |
Show help |
Messages without a command prefix are treated as regular chat — DeerFlow creates a thread and responds conversationally.
Agent selection is conversation-scoped: /agent use starts a fresh conversation and pins that Custom Agent in the thread metadata. Existing conversations never switch agents midway, the selection survives a Gateway restart, and opening the IM-created thread in the Web UI continues through the same Custom Agent.
Use /agent use lead_agent to return to the default agent in a new conversation.
Model selection is conversation-scoped too: /model pins a model to the current conversation — validated against the caller-visible model list, persisted in the thread metadata so it survives a Gateway restart, and applied from the next message without starting a new conversation. /model shows the effective model and its source, /model default clears the pin, and /models reports the pinned model.
Request Trace Correlation
Every Gateway HTTP response carries an X-Trace-Id header. The id is inherited
from an inbound X-Trace-Id when the caller sends one and generated otherwise, so
a proxy or an upstream service can pin one id across services. It needs no
configuration and cannot be turned off.
The same id stays attached to work that outlives the HTTP response: the detached
run task, any subagents it delegates to, and the background memory-update threads.
It is recorded as deerflow_trace_id on the run record (visible in the runs API),
in the thread’s checkpoint metadata, and in Langfuse traces. Scheduled tasks, MCP
task notification runs, and IM channel messages start outside HTTP and mint their
own id per occurrence.
Log records carry that id only when enhanced logging is on:
logging:
enhance:
enabled: true # print trace_id into log records
format: text # or json
This is off by default because turning it on changes the log format. logging is
restart-required, so edit config.yaml and restart the Gateway. The setting
affects log output only — the id, the response header, and the run metadata are
unaffected.
deerflow_trace_id is a DeerFlow correlation id: it is not a run id, and it is not
a provider’s native trace id. It is not a lookup key either — nothing resolves a
thread or a run from it; use it to correlate log lines. A deerflow_trace_id sent
in a run request’s metadata or config.context is ignored and overwritten, so
the response header, the logs, and the persisted run can never disagree. To pin a
correlation id, send the X-Trace-Id header.
Gateway run history also records one terminal run.delivery receipt per run,
including zero-output and crash-recovered runs. The receipt is persisted before
the durable terminal run status during normal execution. Orphan recovery first
atomically claims an expired lease and then idempotently backfills the receipt,
so a stale recovery scan cannot overwrite a live run’s detailed delivery facts.
Receipt persistence remains best-effort during an event-store outage. Runs that
fail checkpoint preflight (or are cancelled while waiting for prior
finalization) keep the existing completion-data behavior: they receive the
zero-delivery receipt but do not overwrite RunStore completion fields with an
empty snapshot.
When tool_progress.enabled is true, the same run event history also records
result-quality guard phase changes. It records loop-detection decisions and
deferred MCP tool promotions for both the lead agent and ordinary task
subagents. Promotion
events identify newly promoted deferred-tool names and whether routing metadata or
tool_search selected them, without copying the search query, routing keywords,
schemas, arguments, results, or catalog hash into the promotion event itself.
LangSmith Tracing
DeerFlow has built-in LangSmith integration for observability. When enabled, all LLM calls, agent runs, and tool executions are traced and visible in the LangSmith dashboard.
Add the following to your .env file:
LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=lsv2_pt_xxxxxxxxxxxxxxxx
LANGSMITH_PROJECT=xxx
Langfuse Tracing
DeerFlow also supports Langfuse observability for LangChain-compatible runs.
Add the following to your .env file:
LANGFUSE_TRACING=true
LANGFUSE_PUBLIC_KEY=pk-lf-xxxxxxxxxxxxxxxx
LANGFUSE_SECRET_KEY=sk-lf-xxxxxxxxxxxxxxxx
LANGFUSE_BASE_URL=https://cloud.langfuse.com
If you are using a self-hosted Langfuse instance, set LANGFUSE_BASE_URL to your deployment URL.
Trace correlation fields. Every agent run is annotated with Langfuse’s reserved trace attributes so the Sessions and Users pages light up automatically:
session_id= LangGraphthread_id— groups every trace of the same conversationuser_id= effective user fromget_effective_user_id()(falls back todefaultin no-auth mode)trace_name= assistant id (defaults tolead-agent)tags=[env:, model:](built-in Gateway lead-agent and embedded client runs tag the selected model after default or fallback resolution; custom graph factories may only provide the requested model; the environment tag is omitted when unset)metadata.deerflow_trace_id= DeerFlow request correlation id, matchingX-Trace-Idwhen request trace correlation is enabled
These are injected into RunnableConfig.metadata at the graph invocation root for both the gateway path (runtime/runs/worker.py::run_agent) and the embedded path (client.py::DeerFlowClient.stream), so any LangChain-compatible callback can read them. Set DEER_FLOW_ENV (or ENVIRONMENT) to tag traces by deployment environment.
Monocle Tracing
DeerFlow also supports Monocle, an OpenTelemetry-based tracer for agentic applications. It records each run end-to-end: LLM calls, agent steps, and tool and MCP invocations, with their inputs, outputs, timings, and token counts.
Add the following to your .env file:
MONOCLE_TRACING=true
MONOCLE_EXPORTERS=file # file, console, okahu, s3, blob, gcs (default: file)
OKAHU_API_KEY=okh_xxxxxxxx # required only for the `okahu` exporter
Each run writes one trace file to .monocle/; open it in the Monocle VS Code extension to inspect the span timeline and token counts. Connect to Okahu, an agent-observability platform, to analyze traces across runs and run trace-based and agentic evaluations (via the okahu exporter).
Traces capture span inputs and outputs verbatim — prompts, tool arguments, and model responses — plus token usage and timings. The file exporter keeps them on local disk and never rotates or cleans them up, so prune .monocle/ periodically; the remote exporters (okahu, s3, blob, gcs) send that same data off-box, so enable only destinations you trust. Monocle is initialized once at Gateway startup: a configuration error (unknown exporter, missing OKAHU_API_KEY) is logged there and tracing stays off until the Gateway restarts.
Using Multiple Providers
LangSmith and Langfuse attach as LangChain callbacks, so you can enable both and DeerFlow reports each run to both. If an enabled provider is missing required credentials or fails to initialize, DeerFlow fails fast and names it. Monocle uses a global OpenTelemetry provider rather than a callback; Langfuse shares that provider, so all three can run together. Because both span processors sit on the same shared provider, Monocle’s exporters also see Langfuse’s spans when both are enabled.
For Docker deployments, tracing is disabled by default. Set LANGSMITH_TRACING=true and LANGSMITH_API_KEY in your .env to enable it.
Existing-Run Stream Actions
Existing-run SSE joins are observation-only on GET: supplying
action=interrupt|rollback returns 405. Cancellation on this stream route is
POST-only and requires the runs:cancel permission. Accordingly, the OpenAPI
contract exposes action and wait only on POST; the GET operation exposes
only its path parameters.
Personal Access Tokens
Non-interactive clients (CI pipelines, scripts, server-to-server integrations)
can call the Gateway API with a personal access token (PAT) instead of a
browser session. Create one while logged in via POST /api/v1/auth/pats — the
raw dfp_... value is shown exactly once; only its SHA-256 digest is stored —
then send it as a Bearer credential:
POST /api/threads/search
Authorization: Bearer dfp_...
Content-Type: application/json
{}
Each token runs with its owning user’s identity (owner filtering and per-user
memory keep working), carries a scope set that can only narrow that user’s
permissions, and is admitted only to the thread/run lifecycle routes — every
other route answers 403 to PAT callers, and a PAT never carries admin
capability. Tokens can be listed and revoked at any time; revocation is
immediate. PATs require a database backend (SQLite/PostgreSQL). Full
reference: API Reference — Personal Access Tokens.
From Deep Research to Super Agent Harness
DeerFlow started as a Deep Research framework — and the community ran with it. Since launch, developers have pushed it far beyond research: building data pipelines, generating slide decks, spinning up dashboards, automating content workflows. Things we never anticipated.
That told us something important: DeerFlow wasn’t just a research tool. It was a harness — a runtime that gives agents the infrastructure to actually get work done.
So we rebuilt it from scratch.
DeerFlow 2.0 is no longer a framework you wire together. It’s a super agent harness — batteries included, fully extensible. Built on LangGraph and LangChain, it ships with everything an agent needs out of the box: a filesystem, memory, skills, sandbox-aware execution, and the ability to plan and spawn sub-agents for complex, multi-step tasks.
Use it as-is. Or tear it apart and make it yours.
Core Features
Workspace
The workspace keeps a collapsed icon sidebar visible on narrow screens. Its toggle opens the full navigation drawer; closing the drawer returns to the icon bar without changing your saved desktop sidebar preference. Navigating to another page also closes the mobile drawer and preserves the destination page’s focus. Dismissing the drawer returns keyboard focus to the control that opened it. The Agents gallery header wraps to the available width so its import and create actions remain visible beside the compact sidebar on narrow screens.
Skills & Tools
Agents can call the read-only inspect_run_status tool to inspect visible run
token usage, message and tool-call counts, an approximate context-window fill,
and whether a summary or unfinished to-dos exist. It returns counts only, not
conversation or to-do text. Token counts depend on provider usage metadata;
compaction can remove earlier messages, and context fill is unavailable when
the model has no configured context_window or its latest response reports no
input usage. It is also unavailable after a subagent dispatch because subagent
usage may be attributed to that response. The result identifies whether a
current-run boundary is known.
Open Capability Center from the workspace sidebar to manage Plugins
(MCP servers and Lark/Feishu integration) and Skills. Both catalogs support
search; skill cards show concise descriptions with full descriptions in a detail
view. Built-in and user-created/imported skills are listed separately. The
Community tab supports importing .skill archives into My skills.
General preferences remain in Settings.
The ppt-generation skill applies photo EXIF orientation before fitting and embedding slide images, so rotated or mirrored photos retain their displayed orientation.
The ppt-generation skill preserves transparent slide images when composing a presentation, including grayscale and palette PNG transparency.
Administrators can see Could not load warnings in Capability Center > Skills
when their own custom skill’s SKILL.md contains invalid YAML. Warnings show the
package-relative file and, when available, its line and column, with a quoting
hint for unquoted colon values. Fix the file on disk and select Reload skills
to refresh the list and invalidate skill prompt caches for subsequent runs;
active runs retain their existing snapshot. This covers caller-owned custom
skills only, excluding legacy/global, public, integration, and external linked
packages. These warnings cover YAML syntax errors only: missing frontmatter,
non-mapping metadata, missing/invalid name or description, invalid allowed-tools
or required-secrets declarations, non-UTF-8 files, and file-read failures
(including permission denied) are not reported. A missing warning does not
establish that a package is valid or readable. The scope note remains visible
even when there are no YAML warnings; check Gateway logs for other load failures.
Parsing remains strict and invalid skills stay unavailable to agents.
Read-only knowledge sources
Three retrieval tools can be configured independently in tools[]: document
knowledge_search, published knowledge_entries_search through the
WeKnora plugin, and
entity_relations_search through the optional
LightRAG plugin. Remove a tool
entry to disable it. They share scope admission, source validation, budgets and
citation rendering. Wiki queries that return no pages retry up to four individual
keywords within the same authorized base. Unsupported filters fail closed;
multiple providers with different resource namespaces support only all/disabled scope. The new adapters
have offline coverage and local live validation
with bge-m3 and GLM-5.3; see that record for coverage limits.
Configure knowledge_search with either
deerflow.community.ragflow.tools:knowledge_search_tool or
deerflow.community.lightrag.tools:knowledge_search_tool in config.yaml.
Both support attributable retrieval evidence. LightRAG and RAGFlow responses
without complete provider locators use the shared DeerFlow source contract;
complete RAGFlow responses retain their existing citation snapshots and scope
controls. Knowledge citations show the
provider, document title, and retrieved excerpt in chat, including after reload
and when a subagent performed the retrieval. Missing page numbers and public
URLs remain absent; sources without a URL are readable as local evidence.
Inline citations remain readable after Markdown sanitization, and supplied page
numbers use the selected interface language. New providers use the shared
envelope; existing RAGFlow citation histories remain supported.
Expand a completed subtask card to inspect its report’s forwarded knowledge
sources, including when reopening a saved conversation.
Shortened mixed-provider task reports remove dangling citation fragments
without flattening retained shared-source links.
Existing web citations and the text-only Python knowledge_search(query) APIs
remain supported. No knowledge management service or page is required.
See the knowledge contract guide.
Knowledge scope selection also works with operator-installed read-only Provider extensions. The shared selector shows only the filters a Provider supports: knowledge-base selection requires a catalog and dataset filtering; file selection additionally requires document listing and document filtering. Unsupported or inaccessible filters fail without expanding the search scope. Choosing no knowledge search stops retrieval before loading the Provider. Provider resource IDs are preserved up to 256 Unicode code points; longer IDs are rejected without truncation. RAGFlow keeps its existing configuration and citations, and search-only Providers remain usable without a catalog or management page. See the offline Provider example for extension registration and configuration. The installable WeKnora read-only extension targets WeKnora v0.8.3 and adds knowledge-base/document selection and shared retrieval citations. Configure explicit user-to-workspace credential bindings; resource authorization remains with WeKnora. The extension directory includes its installation guide.
The optional knowledge platform extension adds connection policies, hierarchical spaces, members and explicit resource sharing. Space-routed management retains existing administrator and Provider grants. It is disabled by default and includes no management page or global chat retrieval ACL.
The optional knowledge management extension adds administrator-authorized file, URL and manual ingestion, processing status, reprocessing and cancellation through WeKnora. Parsing, indexing and optional Wiki/graph generation remain upstream. It is disabled by default, requires explicit resource/operation grants, and installs no management page. Its provider-independent API is covered by offline contract tests. Real ingestion, GLM-5.3/BGE integration, browser citation replay and observed limitations are recorded in the acceptance report.
Skills are what make DeerFlow do almost anything.
A standard Agent Skill is a structured capability module — a Markdown file that defines a workflow, best practices, and references to supporting resources. DeerFlow ships with built-in skills for research, report generation, slide creation, web pages, image and video generation, and more. But the real power is extensibility: add your own skills, replace the built-in ones, or combine them into compound workflows.
The video-generation skill derives reference-image MIME metadata from .png,
.jpg, .jpeg, .webp, and .gif filename extensions (case-insensitive). A
submitted reference with a missing or unrecognized extension raises a clear error
before API submission. Filenames must match their contents; image bytes are not
validated or converted, and provider-specific format restrictions still apply.
The music-generation skill preserves existing output when the provider returns malformed, empty or unrecognized audio.
The bundled skill-creator trigger evaluator drains CLI output through EOF, so a completed subprocess does not turn a recorded skill trigger into a false negative. The bundled skill-creator description optimizer reads YAML frontmatter consistently with skill validation, preserving quoted strings and multiline descriptions. Its scripts require PyYAML, as does the skill package validator. Invalid skill metadata is reported on stderr with a nonzero exit status rather than a traceback.
Skills are loaded progressively — only when the task needs them, not all at once. This keeps the context window lean and makes DeerFlow work well even with token-sensitive models.
The bundled video-generation command reports failures on stderr and exits with status 1, including when the selected provider has no API key. Callers can check the exit status before presenting an output file.
The bundled podcast-generation command returns a nonzero exit status and writes diagnostics to stderr when generation fails. Callers can check the status before presenting audio; an existing MP3 or generated transcript is not proof of success.
The bundled chart-visualization command does the same for an unknown chart tool,
a spec missing tool, or a chart-service HTTP failure. Successful chart URLs,
including spreadsheets (generate_spreadsheet), stay on stdout. A mixed batch
still prints URLs for the charts that succeeded, then exits with status 1.
When deferred skill discovery is enabled, describe_skill ranks installed skills by bounded, Unicode-normalized intent-term coverage across names and descriptions. Natural multi-term requests can therefore find a relevant skill without requiring one exact phrase, while exact select: and required-name +prefix lookups remain available. Ranked searches use up to 256 characters and return up to five results; exact select: lists are not truncated and return all requested catalog matches.
A skill directory is a package boundary: once DeerFlow finds its SKILL.md, nested SKILL.md files under that package (for example evaluation fixtures) remain supporting data and are not registered as runtime skills. This applies to managed integration packs as well as public and custom skills. Namespace directories without their own SKILL.md can still group nested skills.
Discovery follows operator-managed directory symlinks, but skips links back to an ancestor directory so a cyclic namespace does not repeatedly rescan the same tree. Independent links to the same external skill tree remain supported.
Skill Markdown and bundled text resources use UTF-8. Skill-creator CLI and review utilities read and write text explicitly as UTF-8 so localized skills behave consistently across operating systems.
For standalone skill exports, the bundled skill creator’s scripts/package_skill.py can be run by file path from any directory, or as python -m scripts.package_skill from the skill-creator directory. Both entry points validate the skill before writing a .skill archive.
Custom-skill writes and security-scan staging preserve submitted line endings on every platform, so the scanner checks the bytes that will be stored.
Skill-creator benchmark aggregation uses recorded timing.json token counts even
when grading.json already contains the run duration. Recorded zero counts are
preserved; output character counts remain a legacy proxy only when token data is missing.
Unreadable, non-UTF-8, malformed, or non-object timing.json data emits a warning
and is treated as missing measurements. Its token and duration fields accept finite,
non-negative numbers (including zero); invalid fields emit a warning naming the file
and field and use the existing fallback independently, preserving valid measurements
and other runs.
SKILL.md YAML frontmatter must open and close with --- on its own line (trailing spaces or tabs are allowed). A line such as ---suffix is not a closing delimiter.
Custom skill history preserves Unicode line separators inside saved content and metadata, keeping those revisions readable for history inspection and rollback. Malformed JSON history records are still rejected.
The bundled github-deep-research API client also works without requests, using
Python’s standard-library HTTP client. Its fallback preserves spaces, Unicode, and
reserved characters in search queries and label filters.
File reads accept raw repository-relative paths and preserve spaces, Unicode,
#, ?, and literal percent signs with either HTTP client.
Directory listings ask for the repository’s own default branch, so a repository that
uses neither main nor master reports its current tree instead of a stale branch.
The issues command excludes pull requests and follows additional pages to fill
its requested issue limit; the prs command remains available for pull requests.
Users can explicitly activate an enabled skill for a single turn by starting the request with /skill-name, for example /data-analysis analyze uploads/foo.csv. DeerFlow loads that skill’s SKILL.md as hidden current-turn context while leaving the base prompt limited to skill metadata. Slash activation respects disabled skills, custom-agent skill whitelists, and existing channel commands such as /new and /help.
Data-analysis Markdown exports escape pipes and backslashes in column names and values, and render cell line breaks as `` so multiline data stays within its table cell.
Data-analysis JSON exports require unique result column names. For joins that
return repeated names such as id, use SQL aliases (AS left_id, AS right_id)
before exporting. Duplicate names cause an error before the output file is opened,
preserving any existing file.
The CLI names the repeated columns in a short stderr error and exits nonzero,
so callers can correct their aliases without reading a traceback.
JSON exports likewise reject non-finite numbers (NaN and infinities), including
values nested in lists or structs, before opening the destination. Filter them
or replace them with NULL in SQL; the CLI reports an error and preserves any
existing output rather than writing invalid JSON.
The data-analysis skill preserves each CSV file or Excel sheet when names collide across inputs. Prepare its Python environment explicitly with DuckDB and openpyxl; missing packages report setup instructions instead of installing themselves during analysis. Use the SQL names printed by inspect (such as report, report_1) for queries and summaries. Its cache follows ordered input basenames and contents, so renaming or reordering inputs rebuilds their table map instead of silently selecting a different source. Quoted original names also resolve without rewriting query text; apostrophes in upload paths and double quotes in column names are preserved through loading, inspection, queries, and summaries. CSV, JSON, and Markdown exports accept bare filenames such as report.csv in the current working directory as well as nested relative and absolute paths.
After an answer loads skills, its toolbar includes Skills used. Hover over or click the icon to see the skills and their sources; hovering a skill name underlines it. Select a skill to inspect its SKILL.md in the resizable side panel (a drawer on mobile). The view uses snapshots captured during successful configured read-tool loads or explicit slash activation, so later edits or removal of a skill do not rewrite its history. Repeated loads appear once per run, in first-load order. Range reads and bounded snapshots are labeled as partial; older conversations without captured evidence do not show this menu. Copy returns the captured Markdown, including YAML frontmatter. Package-relative links and images remain readable references rather than navigating away from the conversation. Skill loads performed through other tools, such as shell commands, are not inferred from answer text.
A skill’s allowed-tools frontmatter is compatibility metadata carried for interop with other agent frameworks: DeerFlow parses, validates, exports, and describes it, but it never filters model-visible tool schemas, refuses tool calls, or rewrites tool-search promotions. Merely enabling, advertising, or listing a skill in a custom agent or subagent skills allowlist never changes an agent’s toolset either. Real tool authority lives in Agent configuration and authorization: assembly-time provider filtering, runtime Guardrail enforcement, guardrail allowlists, and the deferred-promotion catalog own the permission ceiling. Skill visibility and activation remain independently enforced: slash activation honors disabled skills and custom-agent whitelists, and the skill:activate authorization decision still gates both explicit slash activation and autonomous SKILL.md loads.
When you install .skill archives through the Gateway, DeerFlow accepts standard space-separated allowed-tools, optional frontmatter metadata, and the Claude-compatible argument-hint field instead of rejecting otherwise valid external skills. YAML lists remain supported for allowed-tools and preserve exact runtime names. Exact portable spellings such as WebFetch, WebSearch, Glob, Grep, and Read map to DeerFlow’s web_fetch, web_search, glob, grep, and read_file tools; lowercase or otherwise unknown scalar names remain unchanged so custom and MCP tools keep their exact runtime spelling. Parenthesized entries such as Bash(tvly *) are tokenized as one literal entry, including spaces, quoted text, and escaped parentheses; such scoped declarations remain literal compatibility metadata and do not authorize or restrict commands — DeerFlow’s Agent configuration and authorization, not tool arguments, decide what may run.
Disabling a skill also removes it from the sandbox filesystem view, so shell commands and structured file tools follow the same enabled state. Local, Docker/AIO, hostPath provisioner, and newly created E2B sandboxes source /mnt/skills from enabled-only projections that update when public, custom, legacy, or managed integration skills are toggled, edited, created, deleted, or installed. Structured read_file calls (including line ranges and read-before-write checks) use the sandbox provider’s mount mapping, so the user identity captured when the sandbox was acquired remains authoritative. Managed integration packages remain shared, while their projected filesystem visibility follows each user’s enabled state. Multi-worker Gateways re-read on-disk enable state while rebuilding user projections, so a toggle handled by one worker is honored by another worker’s next sandbox acquire. Existing E2B sandboxes retain their creation-time snapshot until they are recreated. PVC-backed provisioner skills keep their configured PVC snapshot/layout for now; dynamic PVC materialization is tracked separately.
Chat-specific skill views used by an Agent’s skill allowlist verify copied file contents before sandbox reuse. Edited instructions, missing or injected files, and changed links are repaired from the canonical skills on the next acquire; inode and timestamp drift on shared storage does not force a rebuild. Integrity checks read the projected files, so larger packages increase acquire-time read I/O.
Skill projection manifest saves preserve the original error or interruption even if temporary-file cleanup or its logging also fails. Manifest files retain their private filesystem permissions.
# Paths inside the sandbox container
/mnt/skills/public
├── research/SKILL.md
├── report-generation/SKILL.md
├── slide-creation/SKILL.md
├── web-page/SKILL.md
└── image-generation/SKILL.md
/mnt/skills/custom
└── your-custom-skill/SKILL.md ← yours
/mnt/skills/integrations
└── lark-cli/lark-doc/SKILL.md ← managed, read-only
The built-in image-generation skill supports Gemini, MiniMax, and
OpenAI-compatible Images APIs. Select the latter with
IMAGE_GENERATION_PROVIDER=openai, then configure
IMAGE_GENERATION_API_KEY, IMAGE_GENERATION_BASE_URL, and
IMAGE_GENERATION_MODEL. For a containerized sandbox or an explicitly enabled
local host shell, expose these variables through sandbox.environment; sandbox
commands intentionally do not inherit API keys from the Gateway process.
The local provider uses the resolved configuration values unchanged, and
request-scoped secrets override operator values. Local bash output and command
errors redact credential values while leaving benign settings such as model
names and base URLs readable.
For LocalSandboxProvider, this is a managed tool-path boundary rather than host filesystem isolation. Explicit per-Agent skill policies are accepted only while host bash is disabled (the default), because a host subprocess can address canonical paths without using the provider’s virtual-path mappings. Use Docker/AIO, the Kubernetes provisioner, or E2B when the filesystem boundary must remain enforceable alongside shell access.
When the local sandbox uses PowerShell, mapped host-path spaces and shell metacharacters are escaped for the caller’s quoting context; wildcard expansion is preserved. The public bash tool’s existing workspace prefix uses && and POSIX path quoting: it requires PowerShell 7 for that separator and does not support workspace paths containing apostrophes. These workspace-prefix limitations are separate from virtual-path mapping.
On Windows, the local sandbox’s cmd.exe fallback decodes builtin output using
the console output code page, including UTF-8 after chcp 65001, independently
of Python’s UTF-8 mode. If no console page is available, it uses the system OEM
page, with ANSI as a last resort. Native programs can choose a different output
encoding; configure those programs to match the console page when needed.
Managed integrations install shared read-only skill packs without mixing them
into custom skills. The Lark/Feishu CLI integration is available under
Capability Center → Plugins → Lark / Feishu; an administrator installs or
upgrades the official lark-* pack once under
{DEER_FLOW_HOME}/integrations/skills/lark-cli, and every user discovers that
same pack with an independent enabled state. Each user’s app configuration and
OAuth data remain isolated under
{DEER_FLOW_HOME}/users/{user_id}/integrations/lark-cli/{config,data}. These
secret directories are restricted to 0700, regular credential files to
0600, and symlinks are rejected.
On Windows, waiting for another process’s integration file lock retries for up
to 15 minutes; other file-lock errors are reported immediately.
After installation, users can click Connect Lark to open a browser
authorization link; no terminal authorization is required. The same UI can
request additional permission domains such as Calendar, Docs, or Drive, or a
specific OAuth scope reported by lark-cli. A cheap status refresh only
inspects the local credential tree, so the UI reports Credentials configured
(not live-verified) until an explicit browser completion performs live token
verification. The action then remains Reconnect Lark so users can replace
or extend authorization. If an agent hits missing Lark authorization during a
conversation, the managed lark-shared guidance points the user back to the
same plugin configuration with /workspace/capabilities?tab=plugins&plugin=lark.
Browser authorization continues across polling timeouts; repeated polls share the same attempt, including when Gateway runs multiple workers. Starting a new flow or changing the app cancels the previous attempt. If DeerFlow cannot confirm the outcome of a finished attempt, restart authorization for a new link.
Once configured, Change Lark app lets a user point their DeerFlow account at a different Lark/Feishu app without a reinstall — either by pasting an existing app’s App ID / App Secret or by re-registering an app in the browser. Switching is per-user (it never touches another user’s credentials), validates the new credentials through the official CLI’s live tenant-token probe before replacing the active app, and revokes/removes the previous app’s OAuth tokens. A rejected credential change does not supersede an in-progress setup or authorization flow. The previous OAuth data is cleared before the CLI stores the replacement app, so the new file-backed keychain secret remains available during reconnection. DeerFlow then immediately opens browser authorization for the newly bound app so the switch ends in a usable connection.
Installing the Lark skill pack resolves the latest official larksuite/cli
release from GitHub and downloads that version’s skills at install time, so the
Gateway needs outbound internet access for that step (it falls back to a
bottom-line pinned version if the release lookup fails). The settings page shows
the installed version and, when available, the newest published version so an
admin can reinstall to upgrade. Air-gapped deployments can pre-stage the archive
and point DEER_FLOW_LARK_CLI_SKILLS_ARCHIVE at the local file. Integrity does
not depend on a pinned archive byte hash (GitHub does not guarantee stable
source-archive bytes); instead the download is restricted to the official GitHub
host, every archive member passes structural safety guards, and a content hash
of the effective installed skill tree (including DeerFlow’s injected shared
guidance) is recorded so content changes are auditable across reinstalls.
When sandbox.use selects the AIO provider, the same install also downloads the
official Linux amd64 and arm64 CLI release archives, verifies their published
SHA-256 checksums, safely extracts one executable per architecture, and mounts
the resulting runtime read-only at /mnt/integrations/lark-cli/runtime. An
architecture-selecting launcher in that mount makes lark-cli available in the
sandbox PATH. Air-gapped AIO deployments can pre-stage a symlink-free runtime
tree containing bin/lark-cli plus both linux-{amd64,arm64}/lark-cli files and
set DEER_FLOW_LARK_CLI_SANDBOX_RUNTIME_DIR to that directory.
Sandbox trust boundary: the browser never receives the Lark app secret, but agent conversations run
lark-cliinside the sandbox, so the per-user credential directories are mounted into it:config(holding the long-livedappSecret) is mounted read-only, its otherwise emptyconfig/lockssubdirectory is over-mounted writable forlark-clicoordination files, anddata(refreshable OAuth tokens) is writable. The credential-bearing config and data mounts remain readable by any process the agent runs there, so code reached via prompt injection in a tool result could read them. Treat the sandbox as inside the Lark credential trust boundary until the sidecar credential-broker follow-up removes these mounts from sandbox execution.
For remote/Kubernetes deployments (the provisioner backend), the sandbox
lark-cli runtime can instead be supplied by an optional init container that
copies the binaries into a shared emptyDir — no install-time GitHub download and
no hostPath/PVC runtime mount. Publish the image under
docker/lark-cli-init and set
LARK_CLI_INIT_IMAGE on the provisioner (with the Helm chart,
provisioner.larkCliInitImage / provisioner.larkCliBrokerImage); it stays off
(legacy behavior) when unset. The Lark integration status
(GET /api/integrations/lark/status) reports sandbox_runtime_mode,
sandbox_runtime_probed, and sandbox_runtime_ready.
sandbox_runtime_probed marks whether runtime readiness was actually
evaluated; responses from older backends may omit the flag, in which case the
Settings mutation cache keeps the last probed runtime fields instead of
overwriting them with an unevaluated fallback — so the Settings UI shows
whether lark-cli will actually be present in the sandbox at chat time, rather
than a green status hiding a later command not found.
The optional Lark CLI broker image bundles its required Python helper modules, so the sidecar runs without a separate DeerFlow harness installation.
In Lark broker mode, AIO’s fresh Bash runs close inherited pipe stdin. Persistent terminal commands keep their input; the shim ignores terminal stdin. Explicit pipelines, heredocs, and file redirections still supply input normally, and the shim forwards that input only after EOF. A stdin idle timeout aborts without executing the command, and broker execution logs omit argument values. See the broker image guide for timeout settings and image rebuild requirements.
If a trusted operator manages the configured skills directory through an external mount such as MinIO, NFS, or CSI, an administrator can call POST /api/skills/reload after changing files. This invalidates skill prompt caches in the handling Gateway process, waits up to the bounded refresh timeout, and then advances a durable reset marker (.extensions_config.json.skills-cache-reset.json) in the writable config directory. Every Gateway worker or Pod mounting that directory notices the marker within about a second of its next prompt build and rescans the latest files, so one call reaches every replica sharing the volume; the response reports scope: shared_config, or scope: process when no extensions config path is available. Running tasks are unchanged. Skill installs, edits, deletions, rollbacks and enable/disable toggles made through the API publish the same marker, so a change made on one replica takes effect on the others without a restart. A loader-level filesystem failure returns a generic server error and preserves the last successfully loaded process cache rather than publishing an empty catalog. Replicas with independent filesystems are not covered. Direct mount writes bypass the validation, SkillScan, and history applied by DeerFlow’s install/edit APIs, so only operator-controlled systems should have write access.
Skill installs and agent-managed skill edits run through SkillScan, a native deterministic safety scanner before the LLM-based skill scanner. Phase 1 runs offline with no Semgrep/OpenGrep dependency, blocks high-confidence CRITICAL findings such as private keys or shell execution, and passes warning findings to the LLM scanner for contextual review. Code files (anything under scripts/, a script suffix such as .py, .sh, or .js, or an extensionless file starting with #!) that are not NUL-free UTF-8 text raise a warning and are still analyzed over a lossy decode, so a single stray byte cannot hide them from CRITICAL checks. The moderation adapter normalizes both plain-text model responses and LangChain Responses API text blocks before parsing the required JSON decision. Python instance-client exfiltration checks follow a minimal same-scope evidence chain: a simple name bound to a known client constructor, optional name-to-name aliases, and an actual outbound method or context-manager use supported by that constructor. Constructor roots must be proven imports; bare canonical-looking names are not inferred as modules. Nested scopes do not inherit client handles and inherit only constructor import aliases that are never rebound in the enclosing scope. Comprehensions, walrus-bearing statements, annotations, complex binding targets, unsupported operations, and ambiguous branch flows produce no finding from this signal; skipped constructs conservatively invalidate every name they may bind so stale client state cannot create a finding. A deterministic work budget or recursion limit reached by this best-effort analysis does not discard findings already collected for the file. Set skill_scan.enabled: false in config.yaml to disable only the deterministic analyzers; safe archive extraction and the LLM scanner still run.
Windows scripts (.bat, .cmd, .ps1, .psm1, .js, .jse, .vbs, .vbe, .wsf), HTML applications (.hta), and scriptlets (.sct) count as code even outside scripts/, regardless of filename case. They receive both SkillScan analysis and the installer’s executable-code policy.
SkillScan warns about remote downloads piped into common shells, including env launchers, sudo, interpreter paths, and shell line continuations. It distinguishes environment dumps from env commands, including assignments and GNU split-string escapes. Shell parsing and split expansion are bounded; reaching a limit preserves critical findings already detected. Pipes to non-shell tools such as jq and tee do not trigger this warning.
SkillScan treats HTTP/HTTPS scheme spellings and HTTP hostnames case-insensitively and recognizes bracketed IPv6
loopback ([::1]) URLs as local. External IPv6 endpoints still trigger network findings,
and cloud-metadata hostname detection is case-insensitive.
SkillScan recognizes unquoted integer model-usage counters such as total_tokens and input_tokens in skill examples as measurements. Benchmark token statistics and signed deltas are also recognized; credential fields and other string values remain subject to secret detection.
For Python credential mappings, SkillScan checks literal values while treating ordinary dictionary keys as labels. A mapping such as tokens = {"access_token": os.getenv("ACCESS_TOKEN")} does not report a hardcoded credential. Keys matching a recognized cloud or API token format are still checked as embedded credentials.
SkillScan blocks embedded legacy and modern sk- API tokens, including OpenAI project, service-account, and admin keys and Anthropic keys, even in Bearer headers without a secret assignment. Token bodies may contain - and _; finding evidence is fully redacted. Placeholder examples do not suppress detection of later credentials in the same file. Scheduled-task notification summaries also redact these key formats before sending them to external IM platforms.
DeerFlow also ships with skill-reviewer, a public skill for read-only skill quality review. It uses the built-in review_skill_package tool to inspect installed skills, local packages, archives, or pasted SKILL.md content without activating the target skill, binding its secrets, executing its scripts, or installing it. The tool returns a compact, tag-neutralized JSON payload to the model context and keeps the full raw review payload in the tool artifact for programmatic consumers. The deterministic review core reuses DeerFlow parsing and SkillScan facts, emits versioned JSON contracts under contracts/skill_review/, and can be run from the backend CLI:
cd backend
uv run python -m deerflow.skills.review.cli ../skills/public/data-analysis --format text --fail-on error --fail-on-incomplete
Public-skill CI waivers are exact, expiring exceptions in .github/skill-review-waivers.v1.json. Because only the trusted base manifest can suppress a finding, a file-changing pull request can be preauthorized safely by first merging a manifest-only change that lists the reviewed future full-file SHA-256 in preapproved_file_sha256s; the file change can then land in a later pull request.
Tools follow the same philosophy. DeerFlow comes with a core toolset — web search, web fetch, rendered web capture, file operations, bash execution — and supports custom tools via MCP servers and Python functions. The bundled DDG, Brave, Tavily, SearXNG, and Serper search providers accept an optional time_range of day, week, month, or year; omitting it preserves existing search behavior. For DDG recency searches, DeerFlow excludes DDGS backends that ignore time limits. Swap anything. Add anything.
For DDG web search and DDG image search, max_results in config.yaml can be a positive integer or an environment-variable reference such as max_results: $DDG_MAX_RESULTS with DDG_MAX_RESULTS=5. The configured value takes precedence over the tool call’s max_results argument. Invalid values (for example, abc, an empty string, or 3.5), zero, and negative counts produce a warning and fall back to the default of 5 results. DDG, Brave, and Serper image search share one result-URL screen that omits non-HTTP(S), localhost, cloud-metadata hostnames, and non-public address literals, including multicast and IPv6 spellings embedding private IPv4 addresses. The screen does not resolve DNS; downloaders must validate the resolved address when connecting. A result with neither a safe full image URL nor a safe thumbnail is omitted.
Image result screening also recognizes Unicode hostname separators (。, .,
。) as dots, matching IDNA client normalization before classifying local/private
hosts. Accepted result URLs retain their original spelling.
DDG image search reports timeouts, rate limits, and setup failures as search errors with the original query. No images found is reserved for a returned empty result set. The SDK’s No results found. exception remains a search error because it can also hide an unfinished backend request; this conservatively includes SDK exceptions from genuinely empty searches whose completion cannot be confirmed.
Stdio MCP servers can set cwd in extensions_config.json when their entrypoint
or data files depend on a specific working directory. The setting applies to
both discovery and tool calls; see MCP configuration.
Omitted, null, or empty values keep the default working directories.
Tavily web_search also accepts optional include_domains and exclude_domains
lists in its config.yaml tool entry to control search sources. Non-empty
include_domains uses Tavily’s filter mode to restrict results to those domains.
These are deployment settings; the model still supplies only query and optional
time_range. Omitted filters preserve the existing SDK request; an explicit
empty list is forwarded and imposes no restriction of that kind. See the
tool configuration example.
Serper web and image search can use another Serper-compatible provider through
SERPER_BASE_URL and that provider’s SERPER_API_KEY. Each tool can instead set
its own base_url and api_key in config.yaml; each request reads them from
one configuration snapshot. Use an absolute HTTP(S) base URL with a host,
without /search, /images, a query string or a fragment. Invalid overrides
return a configuration error before HTTP; blank environment values keep
Serper’s default unless a tool overrides them. See
Serper endpoints for precedence
and how the provider receives the API key.
Serper web_search supports deployment-level include_domains and
exclude_domains too. It checks returned URL hosts (including subdomains), with
exclusion taking precedence. Filters can return fewer results, including zero;
there are no refill requests. This selects sources, not factual accuracy or a
global URL-access policy. The model arguments and image search are unchanged.
See Serper configuration
for validation and query-length limits.
Serper web and image search support opt-in transient recovery with
max_retries: 1 (default 0, maximum 3) in each tool’s configuration.
retry_budget_seconds bounds retry scheduling, not active synchronous requests.
Budget exhaustion is logged separately while the tool returns the last error.
Extra requests may consume quota or incur cost. See
Serper retries.
When using Tavily for web_fetch, extracted pages without a title use their URL
as the heading; their content remains available to the agent.
For providers that use the shared Readability extractor, Readability.js is
optional. DeerFlow caches an unavailable Node.js toolchain or failed process
launch and uses the existing Python fallback for later fetches instead of
repeatedly starting a missing toolchain. A non-zero extraction exit falls back
only for that document, because malformed or oversized input can fail while
the toolchain remains healthy. The fallback keeps resolved article links in
the returned Markdown.
Chat tool-step titles accept leading blank lines and up to three spaces before
a page’s first H1 heading. Indented code, including mixed spaces and tabs, is
not used as a title; the tool step falls back to the URL.
Tavily search and fetch each read api_key from their own tool entry in
config.yaml, falling back to TAVILY_API_KEY when omitted. Fetch does not
reuse the search entry’s key, so search can use a different provider. If you
previously configured a shared Tavily key only under web_search, also set it
under web_fetch or use TAVILY_API_KEY for both.
For news-focused research, select Webz.io News Search in make setup, or
replace the web_search tool’s use with
deerflow.community.webz.tools:web_search_tool and set WEBZ_API_KEY.
An explicit tool api_key takes precedence over the environment variable.
An explicit call’s max_results overrides the configured default; omission
uses configuration or 5 (clamped to 1–100). Invalid configured counts, including
booleans and fractional numbers, fall back to 5. Results include the matching
passage, title, URL, publication date, and source metadata. The chat search step
displays source titles and links, including after reloading a conversation, and
skips malformed source entries without losing valid links. returned_results
counts the returned page, not all matching news articles. Malformed entries are
skipped with a warning while valid entries are retained; invalid responses or
pages with no valid entries return an error. Empty result lists remain valid. The tool accepts
language, country, source-domain, sentiment, category, and publication-date
filters. Its time_range maps to a UTC lower date bound of 1, 7, 30, or 365
days; an explicit published_from overrides that bound. Webz searches recent
news rather than the general web: date filters cannot extend the provider’s
documented 30-day coverage.
Exporting Custom Skills
Administrators can export their own custom skills from Capability Center → Skills → My skills → View details → Export. Review the file list and declared environment requirements, then choose Download .skill. The archive contains the currently saved skill, including supporting files and empty directories; disabled skills can also be exported. If the skill changes after preview, refresh the file list before downloading. Import the archive on another DeerFlow instance with Install .skill; existing-name conflicts and normal installation security checks still apply.
Account settings, conversations and history outside the skill folder are excluded. Files inside the folder are preserved unchanged, including any credentials an author placed there; filename notices are advisory. Configure dependencies and credentials on the destination. Linked folders/files, hard links, unsupported executable binaries, nested SKILL.md files and nonportable paths cannot be exported. Export supports hosts with descriptor-relative no-follow filesystem APIs (Linux/macOS); unsupported hosts fail explicitly. Limits: 4096 ZIP entries, 64 MiB per file, 100 MiB total content/archive and 1 MiB frontmatter. YAML aliases and excessively complex declarations are not supported. Ordinary script executable semantics are preserved on POSIX import, without restoring special permissions. See the export API contract.
Claude Code Integration
The claude-to-deerflow skill lets you interact with a running DeerFlow instance directly from Claude Code. Send research tasks, check status, manage threads — all without leaving the terminal.
Install the skill:
npx skills add https://github.com/bytedance/deer-flow --skill claude-to-deerflow
Then make sure DeerFlow is running (default at http://localhost:2026) and use the /claude-to-deerflow command in Claude Code.
What you can do:
- Send messages to DeerFlow and get streaming responses
- Choose execution modes: flash (fast), standard, pro (planning), ultra (sub-agents)
- Check DeerFlow health, list models/skills/agents
- Manage threads and conversation history
- Upload files for analysis
Environment variables (optional, for custom endpoints):
DEERFLOW_URL=http://localhost:2026 # Unified proxy base URL
DEERFLOW_GATEWAY_URL=http://localhost:2026 # Gateway API
DEERFLOW_LANGGRAPH_URL=http://localhost:2026/api/langgraph # LangGraph API
See skills/public/claude-to-deerflow/SKILL.md for the full API reference.
Private Knowledge Retrieval (RAGFlow)
Answers can cite retrieved RAGFlow evidence with clickable knowledge citations.
Click a citation, or an entry in the answer’s knowledge sources list, to see the
original retrieved excerpt, dataset and document names, and page numbers when
RAGFlow supplies them. These are retrieval-time snapshots retained with the
conversation, including sources forwarded by ordinary task subagents; they
remain inspectable after reloading the conversation. An excerpt is not a live
copy of the full document: changes in RAGFlow do not rewrite past evidence.
Missing source records are shown as unavailable rather than turned into guessed
links. Source snapshots do not add a knowledge-management page or expose the
RAGFlow API key. Durable batch exports and standalone Markdown files do not
carry these interactive conversation source records.
Ordinary document-title links in a Sources section open the same evidence as
inline citations. When a tool-output budget applies, only complete evidence
entries that fit remain citable; omitted sources are reported rather than
retaining a source record for a cut-off excerpt.
DeerFlow can optionally connect to a tenant-scoped RAGFlow deployment. The
knowledge_search Agent tool resolves the configured dataset scope, groups
datasets by embedding model, and retrieves those groups in parallel so mixed
embedding models do not cause a provider error. Dataset IDs and API keys are
never exposed to the model. The optional list_knowledge_bases tool returns
names only.
Main and custom-agent chats can optionally expose a page-local, icon-only
Knowledge selector beside the mode control. Its persistent highlight
indicates that knowledge retrieval is active; the neutral state means retrieval
is off. Set knowledge_base.scope_selection_enabled: true in config.yaml
while using the built-in RAGFlow knowledge_search provider to allow all
permitted datasets, selected datasets/files, or no retrieval for a turn. The
same config flag controls both chat types; when disabled, neither composer
shows the selector or submits a scope. The choice resets to the custom agent’s saved default (or all when unbound)
when the page is refreshed or another conversation is opened; each sent human message keeps
an immutable scope snapshot for replay and history. The Gateway validates
every snapshot, intersects it with the operator’s dataset allowlist, propagates
the execution-only scope to native and durable subagents, and removes it from
model inputs and external traces. Client-supplied internal runtime controls
and credentials are also stripped from run context before execution or
checkpoint persistence. Idempotent retries accept both canonical snapshots and
legacy raw run inputs, preserving retry compatibility across upgrades.
Custom agents can save a Default knowledge selection from Agents → Agent
settings, including optional file filters or retrieval off. Selecting all
knowledge bases clears the binding. The same knowledge_scope field is available
on agent create/update APIs and in the agent’s stored configuration; omitted
updates preserve it and null clears it. It is a default, not an authorization
boundary: an explicit per-message selection overrides it, and the operator’s
allowlist still applies at retrieval time. Gateway runs without a message scope
(including scheduled and channel turns) use and snapshot the saved default even
when the composer selector is hidden. Regenerate/resume retain the original
turn’s scope, including legacy unscoped turns, rather than picking up later
configuration changes. Unknown or unavailable selections never broaden retrieval.
Idempotent retries keep the original run and scope when a default is added,
changed, or cleared, including unscoped runs accepted before this feature.
This default applies to Gateway-hosted custom-agent turns; direct harness/client
integrations continue to supply their own execution scope.
In Agent settings, Tool access can inherit every configured tool group,
allow selected groups, or disable all groups. Saved groups stay visible and
removable while the catalog loads or fails to load. A saved group is marked
unavailable only when a successful catalog load confirms it is missing.
Saving a selection that removes knowledge also clears the saved default
knowledge selection. The in-chat update_agent tool applies the same rule
when it changes the tool groups to a list without knowledge, so later
Gateway turns do not inherit an unusable scope. PUT /api/agents/{name} also
clears this inherited default on such a change when knowledge_scope is
omitted; an explicitly supplied scope or null still takes precedence.
Unrelated updates, unchanged groups, and inheriting all tool groups preserve
the default. When enabling knowledge access for an agent that
previously lacked it, save the tool access first, then reopen settings to
choose its default knowledge bases.
The knowledge_base block is provider-neutral and only controls whether the
knowledge capability and selector are enabled. RAGFlow connection, dataset
allowlist, and retrieval parameters (base_url, api_key, datasets,
page_size, thresholds, and output limits) must be configured on the
tools[].name: knowledge_search entry; they are never read from
knowledge_base.
Custom-agent chat requests carry the selected agent name as both assistant_id
and context.agent_name, so Gateway scope admission and runtime agent loading
use the same identity. Main chat requests use lead_agent; both identities are
admitted only when the shared configuration enables the RAGFlow provider.
When answering a pending clarification, an explicitly submitted current
selector snapshot wins; clients that omit it inherit the prior turn’s accepted
scope. Edit-and-regenerate follows the same fallback, and the file catalog is
loaded only after a dataset is switched from all files to selected files.
This release does not add an independent Knowledge item to the workspace
sidebar or a DeerFlow knowledge-management page; create, upload, parse, and
delete datasets and documents directly in RAGFlow.
Each message can still select up to 1000 documents. When more than 100 documents are selected from a single dataset, DeerFlow validates them in batches of at most 100 while preserving the complete selection. If any batch contains an inaccessible or non-searchable document, retrieval is rejected.
Advanced deployments can enable pluggable authorization with authorization.enabled in config.yaml. A configured AuthorizationProvider filters denied tools before they reach the model or deferred-tool catalog, then the same provider is checked again before every business-tool execution through the existing guardrail middleware. Gateway threads:* and runs:* route permissions are derived from the same provider, while existing owner checks and admin-only management gates remain in force. Every HTTP route that starts or enables a future Agent run requires runs:create: this includes the stateless POST /api/runs/stream and POST /api/runs/wait endpoints plus scheduled-task create, update, resume, and manual-trigger mutations. Scheduled-task mutations retain their existing threads:write requirement, and the stateless routes separately enforce ownership when the optional thread ID is supplied in the request body. A generated tool_search may bypass the second tool check only when it fronts the current build’s already-filtered deferred catalog. Model access follows the same provider: the Gateway models list is filtered per principal, model:use is enforced on model detail requests and again when the runtime resolves the agent’s model, and a denied default model falls back to the first remaining candidate that also passes model:use. The built-in RBAC provider supports per-role tools, routes, models, skills, and sandbox allow/deny policies and validates that default_role names a configured role; authorization is disabled by default. Custom roles declared in the provider’s roles mapping become reachable by assigning them to a user through the admin API (GET /api/v1/admin/users, PATCH /api/v1/admin/users/{id}; admin-only, last-admin demotion protected) — a declared role nobody holds grants nothing. See config.example.yaml and the authorization RFC.
Follow-up suggestions also check model:use before calling the selected model, including the default model when no name is supplied. A denied model returns HTTP 403 without an LLM call; authorization-provider failures follow the configured fail_closed policy.
For vision-capable agents, view_image and the subsequent model-context image read also require sandbox:execute. Allowing the tool name alone does not grant image-file access; a role denied sandbox execution cannot reread previously recorded image metadata after its permissions change. External run input and thread-state updates cannot set viewed_images or thread_data; when a saved image is read from a host copy, its path must resolve to the current user’s thread and the recorded virtual image path.
Each model request includes at most four recently viewed images and 28 MiB of base64 image data. Images excluded by these limits are reported as omitted; the agent can call view_image again to bring one back into context.
Advanced deployments can also extend the agent runtime itself by declaring AgentMiddleware classes under extensions.middlewares in config.yaml or extensions_config.json. Each entry is a module.path:ClassName string (zero-argument constructor) or an object {class, kwargs} whose kwargs are passed to the constructor. kwargs values must be JSON types (object, array, string, number, boolean, or null); YAML dates and timestamps are coerced to ISO strings so they match JSON. DeerFlow loads the same configured list into the lead-agent and subagent pipelines after their built-in runtime middlewares and loop/token guards, but before the terminal-response/safety/clarification tail, so enterprise forks can add domain guardrails, tool-call governance, or observability hooks without patching the built-in middleware builders. Missing packages, invalid classes, broken modules, and constructor errors fail loudly at agent creation. Treat config.yaml and extensions_config.json as trusted operator-controlled files: middleware paths are code execution, just like custom tool, model, sandbox, guardrail, MCP server, and MCP interceptor declarations. Gateway skill/MCP toggle endpoints preserve this field but do not expose an API write path for extensions.middlewares. Separate lead-only/subagent-only middleware lists are not supported yet.
For packaged and configurable runtime integrations, use DeerFlow’s extension manager.
It accepts a Python package requirement, a public HTTPS Git URL, or a local directory, installs the
package into the backend’s dedicated extensions dependency group, updates
backend/uv.lock, and adds an enabled entry to the startup-only top-level plugins: list
in config.yaml:
# PyPI — pin a version for a reproducible deployment
make extension-install SOURCE="deerflow-extension-acme==1.2.3"
# Public HTTPS Git — pin an immutable commit
make extension-install \
SOURCE="git+https://github.com/acme/deerflow-extension-acme.git@0123456789abcdef0123456789abcdef01234567"
# Local package — an absolute path avoids Make's backend-relative working directory
make extension-install SOURCE="$PWD/examples/deerflow-extension-example"
make extension-list
make extension-upgrade SOURCE="$PWD/examples/deerflow-extension-example"
make extension-disable NAME=acme
make extension-enable NAME=acme
make extension-remove NAME=acme
Installation is interactive because package installation can execute Python build hooks,
and the loaded extension later runs with Gateway privileges. For an already-reviewed
source, automation can acknowledge that boundary explicitly with
cd backend && uv run --frozen --no-group extensions deerflow extensions install --yes.
The manager requires uv 0.8.0 or newer; the provided Docker images pin uv 0.11.1.
The other direct
commands are deerflow extensions upgrade SOURCE, list, enable NAME, disable NAME, and remove NAME;
NAME may be the extension name, Python distribution, or module:install value. Do not
put credentials in a source URL — a URL carrying embedded userinfo or a credential-looking
query parameter is rejected before uv runs. Remote Git sources must use public HTTPS; SSH
Git URLs are rejected because the stock Docker builder does not forward host SSH
credentials. Installing from a loopback URL is allowed for local tooling but warns, because
127.0.0.1 recorded in the lock is a different machine inside the Docker builder.
For scripts, deerflow extensions list --json returns a JSON array in configuration
order, with name, package, entry_point, enabled, and required fields. An
unspecified package is "-"; an empty inventory is []. Private plugin configuration
and source metadata are excluded. The default list output remains a table.
For example, from backend/, check that demo is configured as enabled and required:
inventory=$(uv run --frozen --no-group extensions deerflow extensions list --json) || exit 1
printf '%s\n' "$inventory" |
jq -e 'any(.[]; .name == "demo" and .enabled == true and .required == true)'
This reports configured state without executing plugins or changing files; it does not verify package installation or successful Gateway loading. Configuration errors exit non-zero with diagnostics on stderr and no inventory on stdout.
A managed package declares exactly one standard PEP 621 entry point:
[project.entry-points."deerflow.extensions"]
acme = "acme_deerflow_extension:install"
That callable uses the standalone deerflow-extension-api contract and can register several
contribution kinds: isolated middleware at semantic lead/subagent model or tool positions,
lead and subagent task-lifecycle hooks, observers for DeerFlow-owned model calls that are
not wrapped by middleware model-call hooks (goal, memory, title, and summarization),
Gateway-lifetime services, and eager FastAPI HTTP routers. The contract package has no
framework dependencies; extensions must declare FastAPI, LangChain, LangGraph, or other
libraries they import.
Cancelled subagents report aborted to task-lifecycle hooks after stream cleanup;
synchronous timeouts remain failures, and background hard timeouts retain their
failure outcome.
The fetched-content screening example
contributes one such middleware at the visible tool position. After operator opt-in it
classifies the sanitized text a remote tool result shows the model, PII-redacted when
pii_redaction is enabled, and adds an advisory warning through a lifecycle state
update; it imports only the extension contract.
Extensions may opt into owner-scoped completed-run evidence and safe updates to
existing user-custom skills through host_access in operator-controlled
config.yaml. These optional APIs stage an immutable candidate, check its exact
contents, and publish with version conflict detection and crash recovery. They
do not implement a learning/evolution plugin. Automatic changes are limited to
the body and description of SKILL.md; changing permissions, supporting files,
or enabled state is out of scope. The host contract requires deerflow-extension-api==0.2.7. See Skill evolution host API
for deployment requirements, grants, recovery, and the plugin integration flow.
Once an owner is enrolled, use the Gateway for skill management: standalone SDK
processes without its durable mutation runtime refuse enrolled skill access,
including custom-skill YAML diagnostics. Gateway diagnostics recover pending
publications before scanning.
Skill discovery preserves user scope when the caller uses the default configuration.
Admin operation lists return next_cursor: null at the end of pagination.
Keyed resume retries preserve the original run’s evidence origin and agent identity.
This host slice follows up the Skill Self-Evolution RFC
and the bilingual Plugin Host APIs RFC.
Full-stack contributions can additionally provide browser pages, conversation actions,
authenticated backend operations and model tools through the
plugin APIs. The independent
bookmarks example demonstrates
one package with persistent user data, its own sidebar page and a read-only search tool.
Model tools can opt into separate structured artifacts with
response_format="content_and_artifact" and PluginToolResult (extension-api
0.2.6). Artifact object keys must be strings at every depth. Existing tools keep
their JSON text output. The
local retrieval example
demonstrates bounded, read-only results and checkpoint replay without an external
knowledge service; artifacts do not automatically enable citations. It also appears
in the extension catalog with localized installation-guide metadata.
Reopening a bookmark resolves the conversation’s current agent through the host, so
custom-agent conversations retain their original chat entry point, including older bookmarks.
The independent Agent teams example
lets full Custom Agents collaborate through native @ mentions, shared messages and
asynchronous peer requests, with a separate team page and persistent member conversations.
Capability Center lists the example with localized installation information even before it is installed.
Installation and activation remain deployment-controlled; Capability Center shows plugin
information and status. Browser code runs as trusted same-origin code.
The browser API and inline BrowserModule.code transport are experimental. The
plugin guide describes an additive path to manifests and packaged static resources.
DeerFlow allocates a task-scoped extension store only for middleware, lifecycle, or
system-model observation. Services receive app-scoped runtime dependencies after Gateway
persistence is ready and stop in reverse order after active runs drain. The optional
ExtensionRuntimeDeps.run_evidence_reader is a stable, read-only interface for audit,
evaluation, synchronization, and observability services: it discovers changed runs with an
opaque resumable cursor, pages a known run’s persisted events with after_seq, and reads the
authoritative run status separately from event evidence. A database-backed run store keeps
the discovery cursor valid across Gateway restarts; the memory run store provides the same
ordering only for the current process lifetime. Event metadata is secret-redacted at this
boundary, but event content is returned unchanged. Both payloads are detached snapshots,
so modifying nested values cannot c
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add bytedance/deer-flow