deer-flow

bytedanceMIT活跃维护

开源的长时程 SuperAgent 运行框架,借助沙箱、记忆、工具、技能、子代理和消息网关,自动完成从研究、编码到内容创作的分钟级到小时级复杂任务。

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 tasks that could take minutes to hours.

v2.2.0发布于 2026-11-04 12:56 UTC85,218star +124 / 7 天11,992fork同步于 2026-11-10 17:17 UTC

怎么上手

由 README 提炼,执行前请确认
git clone https://github.com/bytedance/deer-flow.git
cd deer-flow
make docker-up
硬件要求按 README 说明

README 未说明显存或 GPU 要求,仅标注 Python 3.12+ 与 Node.js 22+ 运行环境

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

字节开源的长时程超级智能体框架,用子智能体、记忆和沙箱完成分钟到小时级任务

开源的长时程 SuperAgent 运行框架,借助沙箱、记忆、工具、技能、子代理和消息网关,自动完成从研究、编码到内容创作的分钟级到小时级复杂任务。

核心能力

  • 用子智能体、记忆与沙箱编排长时程任务
  • 支持可扩展技能与工具,可导出自定义技能
  • 提供沙箱与文件系统隔离执行环境
  • 内置长期记忆与手动上下文压缩
  • 支持 MCP Server、IM 通道与定时任务

容易踩的坑

需先完成配置(模型与数据库等)才能启动应用;2.0 与 1.x 不共享代码,旧版文档不适用;生产部署需注意 Gateway 管理员权限等同代码执行的安全风险

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

3 个版本v2.2.0
  1. v2.2.0DeerFlow 2.2.0最新正式版2026-11-04 · 2026-11-04 12:56 UTC

    DeerFlow 2.2.0

    DeerFlow 2.2 turns the 2.1 super-agent harness into a deployment-grade platform with a focus on operating at scale and delegating with trust: scheduled tasks grow into goal-driven workflows with fair multi-owner execution, every subsystem from IM channels to workflows runs across multiple Gateway instances, the extension platform ships full-stack plugins, knowledge retrieval becomes provider-independent with verifiable citations, and models, memory and channels gain production-grade management — hardened by fail-closed defaults, at-rest credential encryption, and a much stricter uploads and paths layer.

    This release closes the 2.2.0 milestone with 1068 merged PRs since the 2.1.0 release.

    📖 Full notes: CHANGELOG.md · 中文版


    ⚠️ Breaking changes

    • Local dev binds loopback — make dev / make start no longer listen on all interfaces; ports 2026/8001/3000 and /setup stay off LAN/VPN. Set BIND_HOST=0.0.0.0 in .env to expose the stack, and note that values nginx would misread (0, 1.2.3, bracketed IPv6) are now rejected before any service stops. ([#6587], [#6635])
    • Stored credentials are encrypted at rest — a new env-only DEER_FLOW_CREDENTIALS_KEY (Fernet, comma-separated for rotation) protects IM channel credentials and MCP OAuth refresh tokens; multi-instance deployments with channel_connections or refresh-token OAuth refuse to start without it. IM bot credentials move from plaintext channels/runtime-config.json into the shared channel_runtime_configs table — existing bots are imported once on first start, and a downgrade must re-enter UI-configured bots. ([#6611], [#7207], [#6988])
    • The multi-process gate is stricter — GATEWAY_WORKERS > 1 (or deployment.multi_instance: true, or WEB_CONCURRENCY) now requires the Redis stream bridge; the memory bridge no longer starts multi-process. The new channels.leader_lease section must stay enabled when a non-GitHub IM channel is configured. ([#6328], [#6746], [#6252])
    • Scheduler contracts change — /api/scheduled-tasks* errors return a structured {"detail": {"code", "message", "params"}} envelope (read detail.message); a new scheduler.max_concurrent_runs_per_user (default 2) caps one owner's concurrent scheduled runs; IM notices are honest, localized, and sent at most once per occurrence; DST-skipped once wall times are rejected with 422. ([#6378], [#6393], [#7281])
    • Thread deletion cascades — deleting a thread now also removes its runs, run events and feedback, so deleted threads can no longer be read or inherited by recreated thread ids. ([#5535])
    • Model-bound history stays append-only — superseded write_file payload elision becomes opt-in (tool_output.elide_superseded_writes, default false) because elision rewrote earlier request segments and invalidated provider prompt caches; skill allowed-tools is compatibility metadata only and no longer hides or blocks tools. ([#7319], [#7455], [#6935])
    • Tool authorization covers middleware tools — plan-mode write_todos and extension middleware tools now pass Layer-1 authorization like any other tool; deployments with an explicit tools policy that omitted them stop binding them. ([#6105])
    • Fail-closed security defaults — delegated web_fetch/web_capture backends (Browserless, Crawl4AI, Firecrawl, fastCRW) screen their base URL and refuse loopback/private defaults until confirmed; the SSRF guard refuses the CGNAT range 100.64.0.0/10 (tailnets need allow_private_addresses: true); IM allowed_users parse fail-closed on Slack, DingTalk, QQ and Telegram (an invalid list now denies everyone instead of silently allowing); external system/developer messages are rejected with 400; permission checks no
  2. v2.1.0v2.1.0 Release2026-09-24 · 2026-09-24 10:40 UTC
    查看更新说明

    DeerFlow 2.1.0

    DeerFlow 2.1 builds on the 2.0 super-agent harness with a focus on trust, scale, and operability: verifiable agent execution, durable batch delegation, pluggable memory backends, four new sandbox providers, an out-of-tree extension system, and enterprise-grade authentication and authorization — wrapped in a much richer workspace with projects, conversation branching, and referenced conversations.

    This release closes the 2.1.0 milestone with 772 merged PRs since the 2.0.0 release.

    📖 Full notes: CHANGELOG.md · 中文版


    ⚠️ Breaking changes

    • Trace ids are unconditional — every Gateway HTTP response carries an X-Trace-Id header, and logging.enhance.enabled now controls log output only. Client-supplied deerflow_trace_id values in run metadata are overwritten so header, logs, and the persisted run cannot disagree — send the X-Trace-Id request header to pin a correlation id. ([#5119])
    • /mnt/skills is reserved for managed enabled-only skill projections. DEER_FLOW_HOST_SKILLS_PATH / SKILLS_HOST_PATH are no longer used, and E2B operator mounts targeting that path are skipped with a warning. ([#4178])
    • sandbox.replicas is enforced for E2B as a capacity limit; the default wait policy can now fail the agent turn instead of growing past the cap. ([#4391])
    • A SKILL.md directory is a runtime package boundary — nested SKILL.md files are supporting data, no longer registered as independent skills. ([#4098])
    • Memory is pluggable (memory.manager_class): DeerMem-private settings move into memory.backend_config (auto-migrated with a warning), /memory/config changed shape, storage_path is now a root directory (legacy file-style values are dropped with a warning), custom storage classes move to a new path and take config in __init__, and a backend without search() fails fast in tool mode. ([#4122], [#4023], [#4324])
    • database.checkpoint_delta_snapshot_frequency moved to database.checkpoint_delta.snapshot_frequency and its default changed from 1000 to 10 — set it explicitly to keep the old cadence. ([#4516])
    • Docker binds loopback by default — the published entry port binds 127.0.0.1 in both compose files, matching the documented local-trust deployment model; set BIND_HOST to expose the stack on other interfaces. ([#4618])

    ✨ Highlights

    • Verifiable agent execution — every tool call carries a tamper-evident, runtime-stamped receipt and a bounded receipt ledger reaches the model context; subagent reports must cite tool receipts with verifiable deliverable handles that the lead agent cross-checks; and acceptance_criteria on delegations are checked deterministically parent-side (file existence, recorded test-command exit status), with anything undecidable reported UNVERIFIED. ([#4659], [#5076], [#5090], [#5109], [#4365])
    • Subagents at scale — a system-maintained delegation ledger prevents redundant re-delegation and a total cap bounds fan-out ([#3877], [#4115]); one process-wide capacity controller governs concurrency, and the opt-in batch_task tool runs large collections of independent items as durable, resumable SQL-backed batches with leases, retries, pause/resume/cancel, and a chat progress panel ([#4998], [#5289]); task(context_mode="snapshot") carries a dispatch-time copy of the parent conversation ([#5367]); and subagent step history is persisted and displayed in the thread. ([#3845])
    • Scheduled tasks grow up — interval schedules join once and cron ([#5291]); upcoming cron occurrences can be previewed before saving ([#5381]); run history filters server-side by status and pages backwards in the UI ([#5384], [#5363]); tasks can pin lead_agent or a custom agent ([#5286], [
  3. v2.0.0DeerFlow 2.0.0 released2026-06-25 · 2026-06-25 16:23 UTC
    查看更新说明

    DeerFlow 2.0.0

    DeerFlow 2.0 is a ground-up rewrite around a "super agent" harness that orchestrates sub-agents, persistent memory, sandboxed execution, and an extensible skills/tools system. It shares no code with the 1.x line, which lives on the main-1.x branch.

    This release closes the 2.0.0 milestone with 182 merged PRs since the first 2.0 milestone tag.

    📖 Full notes: CHANGELOG.md · 中文版


    ⚠️ Breaking change

    • Run hydration — runs now hydrate from RunStore and persist interrupted status. Cancellation now requires the worker who owns the run; cross-worker cancels return 409 instead of silently appearing successful. ([#2932])

    ✨ Highlights

    • Custom agents that update themselves — agents can persist edits to their own SOUL.md / config.yaml from a normal chat, with full per-user isolation. ([#2713])
    • User-owned IM channel connections — users can bind their own Slack, Telegram, Discord, Feishu/Lark, DingTalk, WeChat, and WeCom accounts on top of operator-configured bots. ([#3487])
    • More models & community search — StepFun and MiMo reasoning models, MiniMax for image/video/podcast skills plus a new music-generation skill, and Brave Search / SearXNG / Browserless / Serper Google Images tools. ([#3461], [#3298], [#3437], [#3528], [#3451], [#3575])
    • Richer channels — Discord gets mention-only mode, thread routing, and typing indicators; Telegram streams replies by editing the placeholder message in place. ([#2842], [#3534])
    • Configurable loop detection with per-tool frequency overrides and deferred warning injection. ([#2586], [#2711], [#2752])
    • Subagent token usage streams to the header in real time, with spans attributed to the parent thread's Langfuse trace. ([#2882], [#3611])
    • Trace polish — clean lead_agent / custom-agent trace names; session_id
      • user_id propagated to Langfuse; non-ASCII memory traces no longer escape. ([#3101], [#2944], [#3104])
    • Token-usage tracking on by default, with refined display modes and usage attributed to the actual models. ([#2841], [#2329], [#3658])
    • Flexible memory & suggestions — opt out of tiktoken in restricted networks via memory.token_counting; make AI follow-up suggestions optional. ([#3465], [#3591])

    🚀 Performance

    • Push thread metadata filters into SQL. ([#2865])
    • Index runs by thread_id in RunManager to eliminate O(n) scans. ([#3499])
    • Index messages in MemoryRunEventStore to eliminate O(n) scans. ([#3531])
    • Cache Base.to_dict column reflection per class. ([#3654])
    • Speed up should_ignore_name in glob/grep sandbox walks. ([#3657])

    🔒 Security

    • Reject symlinked upload destinations on Linux and Windows. ([#2623], [#2794])
    • Mask sensitive values in MCP config responses; harden the endpoint. ([#2667], [#3425])
    • Reject cross-site auth POSTs. ([#2740])
    • Cap skill artifact preview decompression (zip-bomb defense). ([#2963])
    • Mount the host Docker socket only in aio (DooD) sandbox mode. ([#3517])
    • Don't bind-mount host CLI auth dirs by default. ([#3521])

    🐛 Notable fixes

    • Memory runs on a persistent event loop instead of a short-lived one asyncio.run(); queued updates isolated per agent; wrapped JSON responses parsed correctly. ([#2627], [#2941], [#3252])
    • Runs hydrate from the persistent store after a gateway restart, return ISO 8601 timestamps, and have an idempotent cancel. ([#2989], [#2599], [#3058])
    • Subagents isolated from the parent run's checkpointer; timeout terminal state atomic; respect model overrides for tools/middleware. ([#3559], [#2583], [#2641])
    • Sandbox readiness polling no longer blocks the event loop; /mnt/user-data contract enforced a

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    🦌 DeerFlow - 2.0

    English | 中文 | 日本語 | Français | Русский | Português | 한국어

    Python Node.js License: MIT

    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.x branch — 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

    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

    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

    Application SQL JSON writes normalize non-finite floats (NaN, Infinity, -Infinity) to null on SQLite and PostgreSQL, including DB-backed agent definitions and skill-mutation records. Existing rows are not rewritten.

    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.

    PostgreSQL async ORM connections and the offline command accept postgres:// and postgresql:// URLs, including ?sslmode=disable or ?sslmode=verify-full. SSL modes are preserved; conflicting ssl/sslmode settings are rejected. Explicit channel_binding options are rejected because asyncpg cannot enforce them. Synchronous psycopg and LangGraph DSNs retain their libpq options.

    With a fencing-capable SQL RunStore and DbRunEventStore, host-authorized producer writes validate the persisted Run incarnation, producer epoch and state even with run_events.producer_fencing: "off". Stale Journal flushes after takeover or deletion may therefore be rejected in the default configuration. On PostgreSQL, these writes hold the Thread Fence and RunRow lock through commit. Checkpoint and branch history imports use a separate host grant bound to a durable thread-operation reservation and the captured thread identity. Strict mode permits these imports without treating synthetic turn IDs as admitted Runs; deletion, reservation revocation or lease expiry invalidates their grant. Messages preceding the first human input retain their existing synthetic turn 0.

    On PostgreSQL, run_events.producer_fencing: "strict" additionally enables database-level authorization checks against raw SQL and unaware legacy writers after an offline cutover. The "off" setting keeps application-level producer validation active. Before activation, the database INSERT trigger skips its Thread/RunRow locks and retains a shared cutover lock until each INSERT transaction ends. This does not remove the application locks above. Off-mode event INSERTs require READ COMMITTED (also accepting PostgreSQL's READ UNCOMMITTED); REPEATABLE READ and SERIALIZABLE are rejected to prevent stale policy snapshots from bypassing activation. A write that loses a race with activation rolls back with SQLSTATE 40001; retry the entire transaction with strict producer authority. Activation requires stopping every Gateway and using the offline cutover command; all restarted Gateways must use the same cutover ID. This does not recover missing terminal events. To activate:

    1. Deploy this version everywhere with run_events.producer_fencing: "off". Complete migrations 0049 and 0050 and recover previously active Runs, then drain admissions and completion hooks. A terminal Run status alone does not prove its producer has drained.

    2. Stop every Gateway, Worker and other database client, including checkpointer pools and monitoring clients, and keep them stopped during activation.

    3. From the repository root, using the installed backend environment, run:

      uv run --no-sync --project backend python backend/scripts/run_producer_fencing.py \
        --config config.yaml --cutover-id deployment-2026-10-27 --all-gateways-stopped
      

      The command refuses activation if other database sessions or pending/running Runs remain. Use a deployment ID that every Gateway will share.

    4. Set database.backend: postgres, run_events.backend: db, run_events.producer_fencing: strict and run_events.producer_cutover_id: deployment-2026-10-27 everywhere, then restart. Verify a new Run persists its initial event, Journal/Hook, delivery and terminal events and closes normally; unaware old writers must receive a database rejection.

    Activation is durable: missing or mismatched cutover IDs and downgrades to "off" or unsupported stores fail startup. This release requires controlled downtime; it does not support a mixed-version rolling cutover. A crashed producer may stay open until explicitly closed or revoked by authorized takeover/deletion; no producer-expiry or terminal-reconciliation job is introduced.

    Scheduled-task orphan recovery carries the captured Run incarnation and retention revision when publishing delivery and terminal evidence, including in strict mode. Unconfirmed publication is logged separately from the committed takeover; it does not undo the recovered Run or queue a new publication attempt.

    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. Tool-result recovery treats null and empty call IDs alike — including the "None" and "missing_tool_call_id" spellings middleware producers use when a provider call had no ID — preserving middleware guidance when a result can be uniquely paired; ambiguous partial results remain rejected. 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.

    1. Clone the DeerFlow repository

      git clone https://github.com/bytedance/deer-flow.git
      cd deer-flow
      
    2. Run the setup wizard

      From the project root directory (deer-flow/), run:

      make setup
      

      This 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.yaml and 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.

      Several providers, including You.com, GroundRoute, Webz, InfoQuest and Jsonify, initialize HTTPX clients in a worker thread so synchronous TLS trust loading does not block the agent event loop; requests and session cleanup remain asynchronous.

      GroundRoute web search and fetch contain malformed HTTP-success payloads the same way: a non-object payload, a results container 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 ddgs dependency return a structured DuckDuckGo 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_results container 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 found for fetch); missing or null search fields (title, url, content) normalize to empty text, and a non-string fetch raw_content is 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 results container 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/images container, 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 Pages container 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 empty Pages keep the existing "No results found" response.

      Serply web search contains malformed HTTP-success payloads the same way in every vertical: a results/entries/articles container 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 results container 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 empty results keep 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_result envelope that is not an object, or an organic/top_stories.items/images_results container 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_result envelope 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.

      Direct Python calls to web_fetch_tool return (content, artifact) so Browserless can keep the target page's HTTP status out of page prose. Callers that previously expected a string should unpack the tuple; Agent tool calls continue to expose content and artifact through LangChain's tool result.

      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: true on each tool. DeerFlow rejects private or unverifiable backends by default because those services handle redirects, DNS resolution, and subresources themselves. allow_private_addresses permits intentional internal target URLs and does not bypass the backend check. See delegated fetch deployment; make doctor reports backend configurations that would be refused.

      Jina fetches support opt-in bounded retries via max_retries (default 0) and retry_budget_seconds (default 30) in the tool configuration. Valid Retry-After hints 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_bytes tool 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_admission tool extra. One immutable budget is shared across clients and agent threads in each process; replicas have independent budgets. See Jina request admission.

      Run make doctor at 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, run make support-bundle. The command prints reporter next steps, writes a *-issue-summary.md file to paste into the issue, a *-issue-draft.md file 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 with triage.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 .env values override shell exports, and a project-root override alone still uses backend/.deer-flow first. Simple variable references such as DEER_FLOW_HOME="$PWD/backend/.deer-flow" use the checkout as PWD; single-quoted references remain literal. If the checkout .env cannot be read or decoded as UTF-8, thread diagnostics retain shell exports and default storage paths. For standalone Gateway launches using backend/.env or DEER_FLOW_ENV_FILE, export the effective DEER_FLOW_HOME when collecting the bundle and ensure the checkout .env does 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_HOME selects 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 in make dev. Without an explicit home, a configured DEER_FLOW_PROJECT_ROOT selects its .deer-flow directory; external paths use that variable name in the report.

      Advanced / manual configuration: If you prefer to edit config.yaml directly, run make config instead 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 existing DEER_FLOW_CONFIG_PATH first, then DEER_FLOW_PROJECT_ROOT/config.yaml, before the checkout's default locations. Local startup also accepts the project-root configuration when the checkout has no config.yaml. See config.example.yaml for the complete reference including CLI-backed providers (Codex CLI, Claude Code OAuth), OpenRouter, Responses API, subagent runtime caps such as subagents.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, MiniMax and StepFun. 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.

      Use Fetch and add models to reuse a saved connection or enter a new one, search its catalog, and test selected models before adding them. Existing entries are retained on retries; individual failures remain available to retry. Chat pickers support name/ID search, provider filtering, favorites and the eight most recently selected models, scoped to your account in this browser.

      The OpenAI preset routes gpt-6-astra, gpt-6.1-sol, gpt-6.1-terra, gpt-6-sol and gpt-6-luna through the Responses API for tool calling, with model-specific thinking and reasoning-effort defaults. Flash mode turns off reasoning for GPT-6.1 Terra, GPT-6 Sol and GPT-6 Luna.

      The Moonshot (China) preset recognizes kimi-k3.1-pro and kimi-k3.1-pro-highspeed with required thinking, image input and native low / high / max reasoning effort (default max). 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_KEY in your environment and run from backend/:

      DEER_FLOW_RUN_LIVE_TESTS=1 uv run --no-sync pytest tests/test_managed_deepseek_live.py -q
      

      These 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_MODEL optionally 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.

      models:
        - name: gpt-4o
          display_name: GPT-4o
          use: langchain_openai:ChatOpenAI
          model: gpt-4o
          api_key: $OPENAI_API_KEY
      
        - name: openrouter-gemini-2.5-flash
          display_name: Gemini 2.5 Flash (OpenRouter)
          use: langchain_openai:ChatOpenAI
          model: google/gemini-2.5-flash-preview
          api_key: $OPENROUTER_API_KEY
          base_url: https://openrouter.ai/api/v1
      
        - name: opper-claude-sonnet-4-6
          display_name: Claude Sonnet 4.6 (Opper)
          use: langchain_openai:ChatOpenAI
          model: claude-sonnet-4-6
          api_key: $OPPER_API_KEY
          base_url: https://api.opper.ai/v3/compat
      
        - name: api-route-gpt-6.1-sol
          display_name: GPT 6.1 Sol (API Route)
          use: langchain_openai:ChatOpenAI
          model: gpt-6.1-sol
          api_key: $API_ROUTE_API_KEY
          base_url: https://global.api-route.com/v1
      
        - name: gpt-5-responses
          display_name: GPT-5 (Responses API)
          use: langchain_openai:ChatOpenAI
          model: gpt-5
          api_key: $OPENAI_API_KEY
          use_responses_api: true
          output_version: responses/v1
      
        - name: qwen3-32b-vllm
          display_name: Qwen3 32B (vLLM)
          use: deerflow.models.vllm_provider:VllmChatModel
          model: Qwen/Qwen3-32B
          api_key: $VLLM_API_KEY
          base_url: http://localhost:8000/v1
          supports_thinking: true
          when_thinking_enabled:
            extra_body:
              chat_template_kwargs:
                enable_thinking: true
      

      OpenRouter and similar OpenAI-compatible gateways should be configured with langchain_openai:ChatOpenAI plus base_url. If you prefer a provider-specific environment variable name, point api_key at that variable explicitly (for example api_key: $OPENROUTER_API_KEY).

      API Route is also available in the setup wizard. It saves your key as API_ROUTE_API_KEY and uses the Chat Completions endpoint above. Model availability depends on your key's group and permissions; check the authenticated GET https://global.api-route.com/v1/models catalog before replacing the model in config.yaml, and use its returned ID unchanged without an upstream provider prefix.

      The write_file tool's output-budget hint uses the active model's effective max_tokens. Missing or unusable limits, including YAML .inf, -.inf, .nan, and values too large for the character estimate, omit this hint without failing tool assembly; provider-specific model validation still applies.

      To route OpenAI models through /v1/responses, keep using langchain_openai:ChatOpenAI and set use_responses_api: true with output_version: responses/v1.

      The lead agent also recognizes Responses output-token caps (status: incomplete with incomplete_details.reason: max_output_tokens), including streaming responses. It suppresses tool execution, removes the suppressed calls from later model requests, and preserves text and reasoning. A capped tool-call response includes a visible notice. Text-only output caps preserve the response and end the turn without unfinished-todo retries; other incomplete reasons retain their existing handling.

      When thinking is disabled on a legacy profile, blank extra_body, thinking, and nested extra_body.thinking keys in a when_thinking_enabled template are treated as absent during dialect inference. When thinking is enabled, template values are forwarded as configured.

      Models whose provider contract differs from DeerFlow's generic thinking/effort assumptions can declare a per-model mapping-valued reasoning: block (thinking unsupported/optional/required, the accepted effort values with aliases and a default, the payload dialect, and the reasoning-history requirement). The setup wizard's Z.AI GLM-5.3-Flash profile uses it: thinking stays on for every foreground and background call, and the effort selector offers the model's own low/high/max levels. Ollama's existing boolean reasoning: true remains a native provider setting and is forwarded to ChatOllama. When migrating a profile to a custom effort path, remove any old reasoning_effort setting from the profile and thinking templates; configuration validation rejects the leftover key. The chat UI drops a remembered provider-specific effort when switching to a legacy model that does not advertise it. Profiles without the block keep their existing provider behavior. See config.example.yaml for the shape and the equivalent manual configuration.

      For vLLM 0.19.0, use deerflow.models.vllm_provider:VllmChatModel. For Qwen-style reasoning models, DeerFlow toggles reasoning with extra_body.chat_template_kwargs.enable_thinking and preserves vLLM's non-standard reasoning field across multi-turn tool-call conversations. Legacy thinking configs are normalized automatically for backward compatibility, without modifying model defaults or caller-owned extra_body dictionaries; a reused dictionary can change the legacy switch between requests. If the endpoint reports a cumulative usage snapshot on every streaming chunk, set cumulative_stream_usage: true so DeerFlow converts those snapshots into per-chunk deltas; the option is disabled by default and leaves usage unchanged when a stable completion id is unavailable. Reasoning models may also require the server to be started with --reasoning-parser .... If your local vLLM deployment accepts any non-empty API key, you can still set VLLM_API_KEY to a placeholder value.

      When prompt caching is enabled for a model configured with deerflow.models.claude_provider:ClaudeChatModel, DeerFlow preserves thinking and redacted-thinking history without placing cache breakpoints directly on those blocks. Extended-thinking tool follow-ups can keep using prompt caching.

      CLI-backed provider examples:

      models:
        - name: gpt-5.4
          display_name: GPT-5.4 (Codex CLI)
          use: deerflow.models.openai_codex_provider:CodexChatModel
          model: gpt-5.4
          supports_thinking: true
          supports_reasoning_effort: true
      
        - name: claude-sonnet-4.6
          display_name: Claude Sonnet 4.6 (Claude Code OAuth)
          use: deerflow.models.claude_provider:ClaudeChatModel
          model: claude-sonnet-4-6
          max_tokens: 4096
          supports_thinking: true
      

      ClaudeChatModel normalizes manual extended-thinking budgets when auto_thinking_budget: true (the default). An omitted or null budget gets 80% of max_tokens, with a minimum of 1024 tokens. Explicit budgets must be integers of at least 1024 and, for ordinary thinking, strictly below max_tokens; output limits of 1024 or less fail locally. For supported manual interleaved thinking, configure tools and betas: ["interleaved-thinking-2025-05-14"]: the thinking budget may equal or exceed the positive output limit. See Anthropic's interleaved-thinking rules for supported models. auto_thinking_budget: false bypasses this normalization and validation; disabled and adaptive thinking are unchanged.

      • Codex CLI reads ~/.codex/auth.json

      • Codex function tools preserve explicit strict: true or strict: false in wrapped or flat dictionary definitions. Missing or null settings keep the provider default. bind_tools applies the same conversion to dictionaries and BaseTool schemas.

      • Codex honors tool_choice in bind_tools and direct calls: auto, none, required, or a function name/selection. any and True mean required; None and False keep the provider default. Per-call choices override the bound policy. Bound choices are validated when binding; direct calls and overrides are validated before HTTP. required needs at least one function tool, and named selections must match a supplied function. auto and none also work without tools.

      • Completed Codex responses still return their text and tool calls when token usage is null, omitted, or empty; usage metadata remains unavailable.

      • The Codex model provider returns completed responses without waiting for the SSE connection to close. Failed or incomplete responses report the provider's error or reason; partial output is not returned as a successful answer. Non-object error details are reported as text.

      • ClaudeChatModel honors integer-second and HTTP-date Retry-After hints in its provider retry loop. Dates use UTC; past dates retry immediately, while invalid hints or delays beyond 24 hours retain local backoff.

      • Claude Code accepts CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_CREDENTIALS_PATH, or ~/.claude/.credentials.json

      • CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR accepts a UTF-8 token handoff and reuses it for later model instances in the same process. Undecodable handoffs are skipped so Claude Code can still try its override or default credentials file.

      • CLI credential JSON files accept UTF-8 with or without a BOM, independently of the host locale. make doctor accepts the same files when checking CLI authentication. Invalid text encoding is treated as an unreadable source; Claude Code can still try its default file after an invalid override.

      • ACP agent entries are separate from model providers — if you configure acp_agents.codex, point it at a Codex ACP adapter such as npx -y @zed-industries/codex-acp

      • A bare or absolute ACP agent command is resolved before the agent starts, so npm-installed launchers work on Windows too: npx or mcode is spawned as its npx.cmd/mcode.cmd shim instead of failing with a command-not-found error, and an absolute path such as C:\Users\me\AppData\Roaming\npm\mcode resolves to the mcode.cmd shim beside npm's extensionless shell script instead of failing with WinError 193. The lookup uses the PATH the agent subprocess will actually see (acp_agents.<name>.env.PATH when set, otherwise the Gateway's) and hands the spawn an absolute path, so a relative PATH entry cannot be re-interpreted inside the agent's own workspace. A relative command (for example bin/mcode) is used as configured, and a command that cannot be resolved is still reported with the install guidance below.

      • Each ACP agent's timeout_seconds (default: 1800) is one shared budget for initialization, session creation, and the prompt, starting after the subprocess launches. On timeout, DeerFlow aborts the invocation and closes the subprocess before returning an error. Workspace/MCP preparation and subprocess cleanup are outside this budget. A TimeoutError raised by the SDK before this deadline expires retains its own error message.

      • If an ACP agent exits before answering (for example because it is not authenticated or a configured argument is wrong), DeerFlow reports its exit code within a few seconds instead of waiting for timeout_seconds, and records the end of the agent's stderr in the Gateway log. Agent stderr is read continuously, so an agent that logs heavily cannot stall on a full pipe.

      • MiniMax Code speaks ACP directly. Install and authenticate it, then add it as an ACP agent:

      npm install --global @minimax-ai/code
      mcode login
      
      acp_agents:
        mcode:
          command: mcode
          args: ["acp"]
          description: MiniMax Code for implementation, refactoring, debugging, and repository tasks
          auto_approve_permissions: false
      

      mcode must be on the Gateway process's PATH; installing it only on the Docker host does not make it available inside the Gateway container. DeerFlow invokes it through invoke_acp_agent in a per-thread ACP workspace and forwards enabled MCP servers. Keep auto_approve_permissions: false for untrusted tasks; enable it only when mcode must edit files or run commands and you trust the task.

      • On macOS, export Claude Code auth explicitly if needed:
      eval "$(python3 scripts/export_claude_code_oauth.py --print-export)"
      

      The exporter rejects malformed credential containers and non-string or blank access tokens before printing a token, emitting a shell export, or writing a credentials file. Valid tokens are exported unchanged, and file exports preserve the full credential container.

      API keys can also be set manually in .env (recommended) or exported in your shell:

      OPENAI_API_KEY=your-openai-api-key
      TAVILY_API_KEY=your-tavily-api-key
      

      make dev and make start read .env as data, never as a shell script, so a value means what the Gateway reads: $$, backticks and $(...) stay as written, and an unquoted value may contain spaces. $NAME, ${NAME}, ${NAME:-default} and an unquoted leading ~ still expand, and single-quoted values stay literal.

    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 targetStarting pointRecommendedNotes
    Local evaluation / make dev4 vCPU, 8 GB RAM, 20 GB free SSD8 vCPU, 16 GB RAMGood for one developer or one light session with hosted model APIs. 2 vCPU / 4 GB is usually not enough.
    Docker development / make docker-start4 vCPU, 8 GB RAM, 25 GB free SSD8 vCPU, 16 GB RAMImage builds, bind mounts, and sandbox containers need more headroom than pure local dev.
    Long-running server / make up8 vCPU, 16 GB RAM, 40 GB free SSD16 vCPU, 32 GB RAMPreferred 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 the docker group 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.

    Images built from a checkout never contain its local configuration: .dockerignore keeps config.yaml, config.yaml.bak, extensions_config.json and mcp_config.json (at the root or in the legacy backend/ location), .env and .env*.local files, local HTTPS keys (frontend/*.pem), and the credential files the extension manager refuses inside backend/extensions/sources/ out of the build context. Compose mounts the config and passes the environment at runtime, so pushing or sharing an image does not publish those credentials. A locally built frontend image therefore no longer picks up NEXT_PUBLIC_* values from frontend/.env.local: next build inlines them into the client bundle, Compose's environment reaches only next start, and frontend/Dockerfile takes no build arguments for them. The Docker stack serves the frontend and Gateway from one nginx origin and needs none of them.

    If make up reports an unwritable runtime home or an unreadable persisted secret after make docker-start, run the printed sudo chown -R <uid>:<gid> '<home>' 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 r