diff --git a/.env.example b/.env.example index 3f66d0ae6..3f27fc7c9 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,14 @@ INFOQUEST_API_KEY=your-infoquest-api-key # Leave unset when using the unified nginx endpoint, e.g. http://localhost:2026. # GATEWAY_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 +# Host interface the Docker stack publishes its entry port on. Defaults to +# 127.0.0.1 (loopback only), matching the local-trusted-environment deployment +# model documented in README.md -- the agent can execute commands. +# Set 0.0.0.0 only when the host is protected by your own TLS/auth front door +# or firewall, and complete first-run setup before it becomes reachable. +# BIND_HOST=0.0.0.0 +# PORT=2026 + # Optional: # FIRECRAWL_API_KEY=your-firecrawl-api-key # VOLCENGINE_API_KEY=your-volcengine-api-key diff --git a/.github/workflows/backend-unit-tests.yml b/.github/workflows/backend-unit-tests.yml index f6b48effd..29779d00a 100644 --- a/.github/workflows/backend-unit-tests.yml +++ b/.github/workflows/backend-unit-tests.yml @@ -61,6 +61,15 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 env: TEST_POSTGRES_URI: postgresql://deerflow:deerflow@localhost:5432/deerflow_test?sslmode=disable diff --git a/AGENTS.md b/AGENTS.md index c9aab70db..8533c8336 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,18 @@ Nginx is the single public entry: it serves the frontend and proxies `/api/langg to the Gateway's LangGraph runtime, rewriting it to Gateway's native `/api/*` routes; all other `/api/*` go straight to the Gateway REST routers. See [backend/AGENTS.md](backend/AGENTS.md) for the runtime and router detail. +It compresses HTML and configured textual assets, while deliberately leaving SSE, +fonts, images, audio, and video uncompressed at the proxy layer. + +Both compose files publish that entry as `"${BIND_HOST:-127.0.0.1}:${PORT:-2026}:2026"` +— **loopback by default**, matching the README's documented deployment model. A bare +`"${PORT}:2026"` binds `0.0.0.0`, which does not. +Nginx itself listens `default_server` on IPv4+IPv6 and the +Gateway binds `0.0.0.0:8001` inside the container on purpose — both are container- +internal; the published nginx port is the entire external surface, and the Gateway's +`8001` is deliberately not published. Any new published port needs an explicit bind +address; `backend/tests/test_compose_default_bind_host.py` pins this for every service +in both compose files. ## Repository Map diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e79d4d75..d5772a54b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,18 @@ This section accumulates work toward the **2.1.0** milestone ### ⚠ Breaking changes +- **skills:** Sandboxes now reserve `/mnt/skills` for managed enabled-only + projections. `DEER_FLOW_HOST_SKILLS_PATH` and `SKILLS_HOST_PATH` are no longer + used; Docker/AIO and hostPath deployments derive projection paths from + `DEER_FLOW_HOST_BASE_DIR`. E2B operator mounts targeting `/mnt/skills` or any + child path are skipped with a warning so they cannot shadow the managed + projection; move extra E2B content to a different container path. User + projections re-read global enable state from disk so toggles propagate across + Gateway workers on the next sandbox acquire. Existing E2B sandboxes retain + their creation-time snapshot until they are recreated. PVC-backed provisioner + deployments still mount the operator-supplied PVC snapshot directly, so + disabled-skill filesystem isolation does not apply in PVC mode until dynamic + PVC materialization is implemented. ([#4178]) - **sandbox:** E2B now enforces `sandbox.replicas` as a process-local capacity limit. The default `wait` policy waits for `acquire_timeout`, then fails the agent turn. DeerFlow does not retry the turn automatically. Use `burst` with @@ -100,6 +112,8 @@ This section accumulates work toward the **2.1.0** milestone - **runtime:** Dual-mode checkpoint storage with LangGraph `DeltaChannel` cuts thread storage from O(N²) to near-linear for long research/coding runs. ([#4292]) +- **runtime:** Delta-mode checkpoint history cache (memory/redis) with O(1) + incremental composition, configured via `database.checkpoint_cache`. - **agent:** Config-declared lead-agent middlewares let deployments add custom `AgentMiddleware` classes without patching the runtime chain. ([#3964]) - **agents:** Per-agent model and generation settings (`temperature`, @@ -210,6 +224,17 @@ This section accumulates work toward the **2.1.0** milestone ### Changed +- **frontend performance:** Keep the public root and localized docs static; + lazy-load closed workspace panels and editor/highlighter dependencies; + incrementally derive streamed message state; bound streaming Markdown work; + virtualize long message and chat lists; pause offscreen decorative effects; + and enforce representative route JS/CSS budgets. +- **browser:** Negotiate binary Browser Live JPEG frames, retain the legacy + JSON/base64 protocol for older clients, coalesce presentation to the latest + frame per refresh, and revoke replaced object URLs. +- **artifacts:** Stream regular text artifacts with HTTP byte-range support and + limit the initial Web UI preview to 1 MiB until the user explicitly loads the + complete file. - **sandbox:** The Helm chart now defaults per-sandbox Services to `ClusterIP` instead of `NodePort`, so the code-execution sandbox is reachable only inside the cluster via Service DNS (`http://sandbox--svc..svc.cluster.local`) @@ -245,6 +270,26 @@ This section accumulates work toward the **2.1.0** milestone ### Fixed +- **artifacts:** Keep explicit full-file loading scoped to the source thread, so a same-path artifact in another conversation keeps its 1 MiB preview. +- **sandbox:** `SandboxAuditMiddleware` no longer blocks ordinary command + substitution that only captures output. The rule now judges *position* instead + of matching any `$(`: `x=$(curl url)`, `echo $(curl url)`, an argument, and a + `for` word list all run normally, while a substitution in command position + (`$(curl url)`, after a `|`/`&&`/`;`, behind leading assignments or an + `env`/`nohup`/`time` style wrapper, or as an `eval`/`source` argument) still + blocks because it executes fetched content. An interpreter's code-string flag + (`bash -c`, `python -c`, `perl -e`, `node -p`, `php -r`, and the `<<<` + here-string) is treated as an execution context wherever it appears, so + `bash -c "$(curl url)"` blocks; `source <(curl url)` and the backtick spelling + of `eval`/`source` now block too, neither of which was detected before. An + unquoted newline separates statements like `;`, so `echo hi` followed by a + new line starting `$(curl url)` blocks as well, while heredoc bodies are + consumed as data — writing a file whose content happens to start a line with + `$(curl url)` is not a command. + Variable expansions whose name merely starts with a risky executable + (`$shell`, `$bashrc`, `$python_version`) and lookalike binaries + (`shellcheck`, `shasum`) are no longer false positives. + ([#4611]) - **mcp:** Isolate Settings > Tools enable/disable updates to one MCP server, so an unrelated disallowed stdio command no longer blocks every switch; allow disabling a disallowed target while still rejecting its re-enable, preserve @@ -1264,6 +1309,7 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4170]: https://github.com/bytedance/deer-flow/pull/4170 [#4171]: https://github.com/bytedance/deer-flow/pull/4171 [#4174]: https://github.com/bytedance/deer-flow/pull/4174 +[#4178]: https://github.com/bytedance/deer-flow/pull/4178 [#4181]: https://github.com/bytedance/deer-flow/pull/4181 [#4187]: https://github.com/bytedance/deer-flow/pull/4187 [#4188]: https://github.com/bytedance/deer-flow/pull/4188 @@ -1359,3 +1405,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag. [#4469]: https://github.com/bytedance/deer-flow/pull/4469 [#4471]: https://github.com/bytedance/deer-flow/pull/4471 [#4516]: https://github.com/bytedance/deer-flow/pull/4516 +[#4611]: https://github.com/bytedance/deer-flow/issues/4611 diff --git a/README.md b/README.md index 642eecea7..4783a710a 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,14 @@ The checkpoint storage settings `database.checkpoint_channel_mode` and both are frozen when the process first builds an agent (including through `DeerFlowClient`) and require a process restart to change safely. +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. + > [!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](CONTRIBUTING.md#linux-docker-daemon-permission-denied) for the full fix. @@ -401,6 +409,7 @@ See the [Sandbox Configuration Guide](backend/docs/CONFIGURATION.md#sandbox) to DeerFlow supports configurable MCP servers and skills to extend its capabilities. For HTTP/SSE MCP servers, OAuth token flows are supported (`client_credentials`, `refresh_token`). For stdio MCP servers, per-tool call timeouts can be configured with `tool_call_timeout`. +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. Settings > Tools updates one MCP server at a time: 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. @@ -718,6 +727,8 @@ An enabled skill's `allowed-tools` policy applies only after that skill is expli When you install `.skill` archives through the Gateway, DeerFlow accepts standard optional frontmatter metadata such as `version`, `author`, and `compatibility` instead of rejecting otherwise valid external skills. +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. 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. + Managed integrations install shared read-only skill packs without mixing them into custom skills. The Lark/Feishu CLI integration is available under `Settings → Integrations → Lark / Feishu CLI`; an administrator installs or @@ -889,6 +900,8 @@ The Web UI shows the active goal above the composer. The same command is availab Use `/compact` in the Web UI composer to summarize older context for the current thread. DeerFlow keeps the full chat visible, but future model calls use the compacted summary plus recent messages. The command is ignored when there is not enough history to compact, and it is blocked while the thread has a run in flight, including when that run is owned by another Gateway worker. If a multi-worker reservation loses its lease, DeerFlow cancels the checkpoint writer before the replacing run proceeds and returns a retryable conflict after cleanup. Thread-title edits are serialized through the same state-write boundary and show a conflict without closing the rename dialog when a run is active. +The chat header also shows a context-window gauge when the selected model has a positive `context_window` configured. It estimates the latest materialized checkpoint's message tokens and keeps the previous same-thread percentage visible while data refetches, independently of the cumulative token-usage setting. + ### Sub-Agents Sub-agents are an optimization, not the default response to a complex request. @@ -907,7 +920,17 @@ Use `burst` with `burst_limit` to permit bounded extra VMs. The `wait` and `reject` policies use only `replicas`. The `reject` policy can remove one warm VM before it returns an error. -`replicas` limits one Gateway process. It does not limit all Gateway processes. +With in-memory ownership, `replicas` limits one Gateway process. With Redis +ownership, E2B shares one capacity Hash between workers using the same +`sandbox.ownership.key_prefix`; `replicas` (plus a configured burst) is then a +deployment-wide hard limit. Use one unique prefix and the same effective limit +per deployment. To change the limit, stop its Gateways, delete the capacity +Hash, and restart; mismatched workers fail closed. + +The Hash counts remote VMs and in-flight creates, repairs interrupted creates +from E2B metadata, grace-protects stale inventory omissions, and blocks new +creates while Redis or initial inventory is unavailable. Run Redis with persistence, non-evicting memory, and HA. + E2B acquisition uses a bounded executor. Waiting acquisitions do not use the default asyncio executor. @@ -929,7 +952,13 @@ Image bytes loaded for a vision-model call are transient: DeerFlow removes the h After each run, DeerFlow records a workspace change summary for the run-owned `workspace` and `outputs` directories. The Web UI shows a compact "files changed" badge on the assistant turn; opening it reveals created, modified, and deleted files with text diffs when safe to display. Uploads are excluded because they are user inputs, not agent-generated changes. Large, binary, or sensitive-looking files are shown as metadata only. -Files presented through `present_files` remain part of the thread's artifact state, and the Web UI restores the artifact panel and selected document after a page refresh. The currently selected formal artifact is refreshed once when the run finishes so edits become visible without a manual reload. +Files presented through `present_files` remain part of the thread's artifact state, and the Web UI restores the artifact panel and selected document after a page refresh. The currently selected formal artifact is refreshed once when the run finishes so edits become visible without a manual reload. Existing UTF-8 text artifacts under `/mnt/user-data/outputs` can also be edited and explicitly saved from the panel on Unix and Windows while the thread is idle; saves use content revisions to prevent overwriting agent changes. + +Text artifacts are streamed with HTTP byte-range support. The Web UI initially +loads at most 1 MiB, shows the preview size when a file is larger, and waits for +an explicit **Load full file** action before fetching the remainder or mounting +the full code editor. Active HTML, XHTML, and SVG artifacts remain forced +downloads at the Gateway boundary. With `AioSandboxProvider`, shell execution runs inside isolated containers. With `LocalSandboxProvider`, file tools still map to per-thread directories on the host, but host `bash` is disabled by default because it is not a secure isolation boundary. Re-enable host bash only for fully trusted local workflows. Host bash commands have a wall-clock timeout, and long-lived processes should be started in the background with output redirected to a workspace log. @@ -965,6 +994,11 @@ uv run playwright install chromium Then uncomment the `group: browser` tool entries in `config.yaml` (`browser_navigate`, `browser_snapshot`, `browser_click`, `browser_type`, `browser_get_text`, `browser_back`, `browser_screenshot`, `browser_close`). `make dev` / Docker startup detects an enabled `browser_navigate` tool and preserves the `browser` extra on dependency syncs. The Gateway fails startup if browser control is configured but Playwright is missing, and `/api/features` hides the Browser UI unless the backend can actually serve it. Keep `headless: true` and `allow_private_addresses: false` for anything but local, trusted debugging. Attaching to an existing Chrome with `cdp_url` cannot enforce DeerFlow's subresource/redirect SSRF guard and therefore fails closed unless `allow_unguarded_cdp: true` explicitly acknowledges that risk; use it only with a trusted local browser. Browser sessions are process-local; keep `GATEWAY_WORKERS=1` while this tool group is enabled because ordinary uvicorn worker dispatch does not provide thread affinity. +The workspace Browser Live client negotiates binary JPEG WebSocket frames, +keeps only the newest pending frame per display refresh, and revokes replaced +object URLs. Gateway control messages remain JSON, and clients that do not +request the binary capability retain the legacy JSON/base64 frame protocol. + ### Context Engineering **Isolated Sub-Agent Context**: Each sub-agent runs in its own isolated context. This means that the sub-agent will not be able to see the context of the main agent or other sub-agents. This is important to ensure that the sub-agent is able to focus on the task at hand and not be distracted by the context of the main agent or other sub-agents. @@ -1000,6 +1034,8 @@ requires an explicit local-development opt-in. See the Memory updates now skip duplicate fact entries at apply time, so repeated preferences and context do not accumulate endlessly across sessions. +In the default DeerMem `middleware` mode, automatic extraction now classifies every proposed fact by scope, durability, and authority before a deterministic write gate accepts it. Only durable, descriptive user-level facts are stored; current-thread or project constraints and one-time action permissions stay in conversation state. User-global summaries require both user scope and descriptive authority, contradiction removals are scope-gated, and a replacement-dependent removal is applied only when its replacement actually survives validation and storage. These classification labels are extraction-only metadata, add no extra LLM call, and are not written into the fact files. The explicit CRUD tools in `memory.mode: tool` remain a separate, model-directed path. Deployments that override the bundled DeerMem prompts via `memory.backend_config.prompts_dir` must add the new classification fields to their custom templates (the `memory_update` fact/summary/removal formats and the `consolidation` consolidated-fact schema): the write gate fails closed, so an un-migrated template stops every extraction-driven fact, summary, and removal write, surfacing only through the `rejected_by_scope_gate` metrics and the high-rejection-rate warning. + File-backed memory now separates global user context from agent facts. Each user has one `memory.json` containing only the project-independent `user` and `history` summaries; every fact is a canonical Markdown file below `agents/{agent_name}/facts/`. Existing lead-agent middleware, API, Settings, import/export, and embedded-client calls that omit `agent_name` resolve inside DeerMem to the reserved `__default__` bucket. That bucket is outside the valid custom-agent name grammar, so a real custom agent named `lead-agent` has a separate fact repository and deleting a custom agent cannot delete a memory-only directory without `config.yaml`. Public agent identifiers are case-insensitive and canonicalized to lowercase. Runtime/API readers still receive a compatibility `facts` array for the selected/default agent, so the frontend does not read agent facts from `memory.json`; structured Markdown `source` metadata is projected to the historical string field at the MemoryManager boundary. An unscoped Clear All first migrates facts from unread legacy per-agent JSON without adopting its soon-to-be-cleared summaries, then removes shared summaries and facts from every agent bucket while preserving agent configuration files, so a later read cannot resurrect skipped legacy facts; an explicitly agent-scoped clear removes only that agent's facts. On first normal read, old facts embedded in the user JSON are migrated automatically to `__default__`; facts written to the earlier implicit `lead-agent` bucket are also moved when that directory is not a real custom agent. Migration and normal writes notify the configured retrieval adapter only after durable storage locks are released. DeerMem uses a scope-aware SQLite FTS5/BM25 adapter by default, stores only rebuildable derived index data under `.retrieval/`, and rebuilds it in the background during Gateway startup or lazily on the first scoped search. A corrupt derived index is recreated automatically. Set `memory.backend_config.retrieval_adapter` to an empty string to disable it and use the local substring fallback. Chinese tokenization is optional; install the backend `memory-zh` extra (`uv sync --extra memory-zh`) for jieba-assisted sub-phrase search. Journaled writes, a shared user lock, and optimistic user-memory revisions prevent silent lost updates. Memory injection follows the configured operation mode. In `middleware` mode, DeerMem injects the user-global summaries and the selected agent's facts. In `tool` mode, the automatic `` block contains only the global `user` and `history` summaries; agent facts are retrieved explicitly through `memory_search`, avoiding duplicate automatic and tool-returned fact context. Setting `memory.injection_enabled: false` still disables the entire block in either mode. @@ -1032,6 +1068,18 @@ DeerFlow is model-agnostic — it works with any LLM that implements the OpenAI- DeerFlow can be used as an embedded Python library without running the full HTTP services. The `DeerFlowClient` provides direct in-process access to all agent and Gateway capabilities, returning the same response schemas as the HTTP Gateway API. The HTTP Gateway also exposes `DELETE /api/threads/{thread_id}` to remove DeerFlow-managed local thread data after the LangGraph thread itself has been deleted: +Thread IDs may be supplied by callers and do not have to be UUIDs. Explicit +IDs must contain 1–64 ASCII letters, digits, hyphens, or underscores +(`^[A-Za-z0-9_-]{1,64}$`). DeerFlow generates a UUID only when `thread_id` is +omitted or `None`; an explicitly supplied empty string is invalid. +Existing route-addressable threads created under older, looser rules remain +readable and deletable, but cannot start new runs or create new filesystem or +sandbox state. Legacy deletion skips local path cleanup when the ID is not +safe under the canonical contract. For canonical legacy threads whose +conversation exists only in LangGraph checkpoints, DeerFlow seeds an empty +run-event feed from the checkpoint before the first new run so +`/messages/page` keeps both the old and new turns. + ```python from deerflow.client import DeerFlowClient @@ -1122,6 +1170,27 @@ DeerFlow has key high-privilege capabilities including **system command executio - **Unauthorized illegal invocation**: Agent functionality could be discovered by unauthorized third parties or malicious internet scanners, triggering bulk unauthorized requests that execute high-risk operations such as system commands and file read/write, potentially causing serious security consequences. - **Compliance and legal risks**: If the agent is illegally invoked to conduct cyberattacks, data theft, or other illegal activities, it may result in legal liability and compliance risks. +### Gateway Admin Is Equivalent to Code Execution + +An admin can register stdio MCP servers, which run commands inside the Gateway +container. The API restricts them to an allowlist (`npx`, `uvx` by default, +extended via `DEER_FLOW_MCP_STDIO_COMMAND_ALLOWLIST`) and rejects arguments and +environment variables that would evaluate arbitrary code. That is defense in +depth, not a boundary: these launchers exist to fetch and run remote packages, +so **treat Gateway admin as equivalent to code execution on the host** and grant +it accordingly. + +### Deployment Defaults + +The Docker stack publishes its entry port on `127.0.0.1` only, matching the +local-trusted-environment model described above. To reach it from another +machine, set `BIND_HOST` in `.env` (e.g. `BIND_HOST=0.0.0.0`) — and only after +putting the security measures below in place. + +**Complete first-run setup before the host becomes reachable.** A fresh +instance has no accounts yet, so create the admin account through `/setup` +immediately after starting any deployment that is not loopback-only. + ### Security Recommendations **Note: We strongly recommend deploying DeerFlow in a local trusted network environment.** If you need cross-device or cross-network deployment, you must implement strict security measures, such as: @@ -1151,6 +1220,12 @@ The JSON includes compact review records with `priority`, `location`, `blocking_call`, `event_loop_exposure`, `reason`, and `code`. Gateway artifact serving now forces active web content types (`text/html`, `application/xhtml+xml`, `image/svg+xml`) to download as attachments instead of inline rendering, reducing XSS risk for generated artifacts. +Frontend route asset budgets can be checked with `cd frontend && pnpm +perf:check`. The command measures `/login` from a normal production build, then +performs a production static-demo build for the fixture-backed workspace routes. +It measures the unique JavaScript and CSS referenced by representative routes +and writes the detailed result to `.next/performance-results.json`. + ## License This project is open source and available under the [MIT License](./LICENSE). diff --git a/README_zh.md b/README_zh.md index 29be95063..b8da18e6e 100644 --- a/README_zh.md +++ b/README_zh.md @@ -651,6 +651,8 @@ DeerFlow 不只是“会说它能做”,它是真的有一台自己的“电 跨 session 使用时,DeerFlow 会逐步积累关于你的持久 memory,包括你的个人偏好、知识背景,以及长期沉淀下来的工作习惯。你用得越多,它越了解你的写作风格、技术栈和重复出现的工作流。memory 保存在本地,控制权也始终在你手里。 +默认 DeerMem `middleware` 模式会先判断候选信息的作用域、持久性和授权属性,再由确定性写入门决定是否保存。只有稳定、描述性的用户级事实能进入长期 memory;当前对话或项目的约束、一次性操作授权仍留在对话状态中。用户全局 summary 必须同时具有用户级作用域和描述性授权属性,基于矛盾的删除也会经过作用域保护;如果删除依赖一条替代事实,只有替代事实真正通过校验并保留下来后才执行删除。这些分类字段只用于本次抽取,不写入 fact 文件,也不增加 LLM 调用次数。`memory.mode: tool` 的显式 CRUD 仍是独立的模型直写路径。如果通过 `memory.backend_config.prompts_dir` 覆盖了内置抽取模板,必须同步在自定义模板中加入新的分类字段(`memory_update` 的 fact/summary/removal 格式与 `consolidation` 的合并 fact 结构):写入门是 fail closed 的,未迁移的旧模板会导致所有抽取驱动的 fact、summary 与删除写入停止,只能通过 `rejected_by_scope_gate` 指标和高拒绝率告警发现。 + ## 推荐模型 DeerFlow 对模型没有强绑定,只要实现了 OpenAI 兼容 API 的 LLM,理论上都可以接入。不过在下面这些能力上表现更强的模型,通常会更适合 DeerFlow: diff --git a/backend/AGENTS.md b/backend/AGENTS.md index 9e86b0181..2e87a64f3 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -267,6 +267,11 @@ Blocking-IO runtime gate (`tests/blocking_io/`): `test_uploads_router.py` (locks Gateway upload/list/delete endpoints offloading upload directory creation, staged writes, chmod/cleanup, directory scans/deletes, and remote sandbox sync off the event loop); + `test_feishu_receive_file.py` (locks Feishu attachment path preparation and + persistence plus remote sandbox acquisition/sync off the event loop, and + skips redundant sandbox sync when thread data is already mounted); + `test_channel_outbound_files.py` (locks Feishu, Telegram, and WeCom outbound + attachment open/read/hash work off the event loop); `test_openviking_memory_backend.py` (locks the OpenViking backend's async add/context/search entrypoints offloading synchronous HTTP and watermark filesystem IO); and @@ -309,6 +314,13 @@ Agentic browser sessions are process-local. The Gateway startup safety gate reje uvicorn worker dispatch does not provide thread affinity for browser tools, REST navigation, and the Live WebSocket. +Browser Live screenshots remain JPEG bytes inside the harness and the Gateway's +bounded, drop-oldest frame queue. WebSocket clients that request +`frame_format=binary` receive binary messages; control metadata remains JSON. +The legacy no-parameter protocol still base64-encodes frames into JSON at the +Gateway boundary for backward compatibility. Unknown `frame_format` values +receive a JSON error and close code 1008. + ## Architecture ### Harness / App Split @@ -390,7 +402,7 @@ Lead-agent middlewares are assembled in strict order across three functions: the 7. **DanglingToolCallMiddleware** - Injects placeholder ToolMessages for AIMessage tool_calls that lack responses (e.g., user interruption), preserving raw provider tool-call payloads in `additional_kwargs["tool_calls"]`; malformed tool-call names and arguments are sanitized in the model-bound request so strict OpenAI-compatible providers do not reject the next request 8. **LLMErrorHandlingMiddleware** - Normalizes provider/model invocation failures into recoverable assistant-facing errors before later stages run 9. **Authorization / GuardrailMiddleware** - Up to two independent pre-tool-call gates run here. When `authorization.enabled`, the `AuthorizationProvider` instance already used for Layer 1 capability filtering is wrapped by `GuardrailAuthorizationAdapter` and reused for Layer 2 execution checks. A generated `tool_search` bypasses the adapter's second provider call only when the current build has a concrete deferred setup; its catalog was already filtered by Layer 1, and an ordinary same-named tool without that deferred setup receives no exemption. When `guardrails.enabled`, the explicitly configured `GuardrailProvider` is appended after authorization and still evaluates every call, including `tool_search`. Authorization therefore runs outermost and can deny before an external guardrail call; both use the existing middleware's fail-closed, audit, sync/async, and error-`ToolMessage` behavior. See the authorization RFC and [docs/GUARDRAILS.md](docs/GUARDRAILS.md). -10. **SandboxAuditMiddleware** - Audits sandboxed shell/file operations for security logging before tool execution +10. **SandboxAuditMiddleware** - Audits sandboxed shell/file operations for security logging before tool execution. Command classification is **defense-in-depth and audit, not a security boundary** — the sandbox itself is the isolation boundary. Command substitution is judged by *position*, not by the presence of `$(`: a substitution in **command position** (`$(curl url)`, `` `curl url` ``, the word after a `|`/`&&`/`;`, or any `eval`/`source` argument) executes fetched or interpreted content and is blocked, while **value position** (`x=$(curl url)`, `echo $(curl url)`, an argument, a `for` word list) only captures output and passes (#4611). `_HIGH_RISK_COMMAND_POSITION_PATTERNS` is therefore matched anchored against each split sub-command, never against the whole compound string, and `_split_compound_command(split_pipes=True)` supplies those sub-commands; rules that span a pipe (`| sh`, `base64 -d | ...`) still rely on `_classify_command`'s whole-command Pass 1. `_COMMAND_POSITION_PREFIX` extends the anchor over leading variable assignments and exec wrappers (`FOO=1 $(curl url)`, `env`/`command`/`builtin`/`exec`/`nohup`/`time`/`sudo`/`doas`), which are still command position; its assignment branch requires whitespace before the substitution, which is exactly what keeps `x=$(curl url)` in value position. Two execution contexts are deliberately **position-blind** and matched against the whole command in Pass 1, because they execute what they receive wherever they appear (including as an argument to something else, e.g. `xargs sh -c "$(curl url)"`): an `eval`/`source` argument, and an interpreter's **code-string flag** — `-c` (shells, `python`), `-e` (`perl`/`ruby`/`node`), `-p` (`perl`/`node`), `-r` (`php`) — plus the here-string (`<<<`) that reaches the same place through stdin. All three substitution spellings (`$(cmd`, `<(cmd`, `` `cmd ``) share one `_RISKY_SUBSTITUTION` opener so a rule cannot cover one spelling and miss another. An unquoted newline splits like `;`, because it separates statements the same way: leaving it joined let `echo hi\n$(curl url)` evade the anchored rules that its `;` spelling triggers. A heredoc body is data rather than statements, so `_split_compound_command` records headers (`<