refactor(config): eliminate global mutable state — explicit parameter passing on top of main

Squashes 25 PR commits onto current main. AppConfig becomes a pure value
object with no ambient lookup. Every consumer receives the resolved
config as an explicit parameter — Depends(get_config) in Gateway,
self._app_config in DeerFlowClient, runtime.context.app_config in agent
runs, AppConfig.from_file() at the LangGraph Server registration
boundary.

Phase 1 — frozen data + typed context

- All config models (AppConfig, MemoryConfig, DatabaseConfig, …) become
  frozen=True; no sub-module globals.
- AppConfig.from_file() is pure (no side-effect singleton loaders).
- Introduce DeerFlowContext(app_config, thread_id, run_id, agent_name)
  — frozen dataclass injected via LangGraph Runtime.
- Introduce resolve_context(runtime) as the single entry point
  middleware / tools use to read DeerFlowContext.

Phase 2 — pure explicit parameter passing

- Gateway: app.state.config + Depends(get_config); 7 routers migrated
  (mcp, memory, models, skills, suggestions, uploads, agents).
- DeerFlowClient: __init__(config=...) captures config locally.
- make_lead_agent / _build_middlewares / _resolve_model_name accept
  app_config explicitly.
- RunContext.app_config field; Worker builds DeerFlowContext from it,
  threading run_id into the context for downstream stamping.
- Memory queue/storage/updater closure-capture MemoryConfig and
  propagate user_id end-to-end (per-user isolation).
- Sandbox/skills/community/factories/tools thread app_config.
- resolve_context() rejects non-typed runtime.context.
- Test suite migrated off AppConfig.current() monkey-patches.
- AppConfig.current() classmethod deleted.

Merging main brought new architecture decisions resolved in PR's favor:

- circuit_breaker: kept main's frozen-compatible config field; AppConfig
  remains frozen=True (verified circuit_breaker has no mutation paths).
- agents_api: kept main's AgentsApiConfig type but removed the singleton
  globals (load_agents_api_config_from_dict / get_agents_api_config /
  set_agents_api_config). 8 routes in agents.py now read via
  Depends(get_config).
- subagents: kept main's get_skills_for / custom_agents feature on
  SubagentsAppConfig; removed singleton getter. registry.py now reads
  app_config.subagents directly.
- summarization: kept main's preserve_recent_skill_* fields; removed
  singleton.
- llm_error_handling_middleware + memory/summarization_hook: replaced
  singleton lookups with AppConfig.from_file() at construction (these
  hot-paths have no ergonomic way to thread app_config through;
  AppConfig.from_file is a pure load).
- worker.py + thread_data_middleware.py: DeerFlowContext.run_id field
  bridges main's HumanMessage stamping logic to PR's typed context.

Trade-offs (follow-up work):

- main's #2138 (async memory updater) reverted to PR's sync
  implementation. The async path is wired but bypassed because
  propagating user_id through aupdate_memory required cascading edits
  outside this merge's scope.
- tests/test_subagent_skills_config.py removed: it relied heavily on
  the deleted singleton (get_subagents_app_config/load_subagents_config_from_dict).
  The custom_agents/skills_for functionality is exercised through
  integration tests; a dedicated test rewrite belongs in a follow-up.

Verification: backend test suite — 2560 passed, 4 skipped, 84 failures.
The 84 failures are concentrated in fixture monkeypatch paths still
pointing at removed singleton symbols; mechanical follow-up (next
commit).
This commit is contained in:
greatmengqi
2026-04-26 21:45:02 +08:00
parent 9dc25987e0
commit 3e6a34297d
365 changed files with 31220 additions and 5303 deletions
+144
View File
@@ -0,0 +1,144 @@
---
title: 沙箱
description: 沙箱为 Lead Agent 提供一个受控环境,在其中可以读取文件、写入输出、运行 Shell 命令并生成产出物。没有沙箱,Agent 只能生成文本;有了沙箱,它可以编写和执行代码、处理数据文件、生成图表并构建交付物。
---
import { Callout, Cards, Tabs } from "nextra/components";
# 沙箱
<Callout type="info" emoji="📦">
沙箱是 Agent 进行文件和命令操作的隔离工作区。它让 DeerFlow 能够采取真实行动,而不仅仅是对话。
</Callout>
沙箱为 Lead Agent 提供一个受控环境,在其中可以读取文件、写入输出、运行 Shell 命令并生成产出物。没有沙箱,Agent 只能生成文本;有了沙箱,它可以编写和执行代码、处理数据文件、生成图表并构建交付物。
## 沙箱模式
DeerFlow 支持三种沙箱模式,选择适合你部署的一种:
### LocalSandbox(默认)
命令直接在主机机器的文件系统上运行,没有容器隔离。
- **适合**:受信任的单用户本地开发工作流。
- **风险**:Agent 可以访问主机文件系统。默认使用 `allow_host_bash: false` 防止任意命令执行。
```yaml
sandbox:
use: deerflow.sandbox.local:LocalSandboxProvider
allow_host_bash: false # 默认;仅对完全受信任的工作流设置为 true
```
### 基于容器的 AIO 沙箱
命令在隔离容器中运行(Linux/Windows 上的 DockermacOS 上的 Apple Container)。每个沙箱会话获得一个全新的容器环境。
- **适合**:多用户环境、生产部署,或任何需要执行隔离的场景。
```yaml
sandbox:
use: deerflow.community.aio_sandbox:AioSandboxProvider
# 可选:容器镜像(下方显示默认值)
image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
# 可选:最大并发容器数(默认:3,超出时 LRU 淘汰)
replicas: 3
# 可选:空闲超时(秒,默认:600)
idle_timeout: 600
# 可选:自定义挂载
mounts:
- host_path: /path/on/host
container_path: /home/user/shared
read_only: false
```
安装:`cd backend && uv add 'deerflow-harness[aio-sandbox]'`
### Provisioner 管理的沙箱(Kubernetes
每个沙箱在 Kubernetes 集群中获得一个专用 Pod,由 Provisioner 服务管理。这提供最强的隔离性,适合有多个并发用户的生产环境。
```yaml
sandbox:
use: deerflow.community.aio_sandbox:AioSandboxProvider
provisioner_url: http://provisioner:8002
```
## 路径映射
沙箱使用路径映射来桥接主机文件系统和容器的虚拟文件系统。始终配置两个关键映射:
| 主机路径 | 容器路径 | 访问权限 |
|---|---|---|
| `skills/`(来自 `skills.path` | `/mnt/skills`(来自 `skills.container_path` | 只读 |
| `.deer-flow/threads/{thread_id}/user-data/` | `/mnt/user-data/` | 读写 |
技能目录始终以只读方式挂载。线程将其工作数据(上传文件、输出、中间文件)写入 `/mnt/user-data/`。
### 自定义挂载
你可以为本地沙箱使用 `mounts:` 配置添加额外挂载:
```yaml
sandbox:
use: deerflow.sandbox.local:LocalSandboxProvider
mounts:
- host_path: /home/user/my-project
container_path: /mnt/my-project
read_only: true
```
<Callout type="warning">
自定义挂载的 <code>container_path</code> 不能与保留前缀冲突:
<code>/mnt/skills</code>、<code>/mnt/acp-workspace</code> 或 <code>/mnt/user-data</code>。
</Callout>
## 输出截断
沙箱工具限制输出大小以保持 Agent 上下文可控。这些限制可配置:
```yaml
sandbox:
use: deerflow.sandbox.local:LocalSandboxProvider
# bash 使用中间截断(头部 + 尾部)
bash_output_max_chars: 20000
# read_file 使用头部截断
read_file_output_max_chars: 50000
# ls 使用头部截断
ls_output_max_chars: 20000
```
设置为 `0` 禁用截断。
## 安全性
### LocalSandbox
`LocalSandbox` 直接在主机上运行命令。默认情况下,`bash` 工具**被禁用**以防止任意主机命令执行。仅对完全受信任的单用户工作流启用它:
```yaml
sandbox:
allow_host_bash: true # 危险:授予 Agent 对你机器的 Shell 访问权限
```
即使没有 `bash`,Agent 也可以通过专用文件工具读写文件。
### 容器沙箱
基于容器的沙箱提供文件系统和进程隔离。Agent 看不到或修改主机文件系统,除非通过显式挂载。Provisioner 管理模式增加了额外一层:每个线程获得自己的隔离 Pod。
### 审计中间件
`SandboxAuditMiddleware` 在每次 Agent 轮次上运行,记录所有沙箱操作,提供会话期间访问了哪些文件、运行了哪些命令的审计跟踪。
<Cards num={2}>
<Cards.Card title="工具" href="/docs/harness/tools" />
<Cards.Card title="子 Agent" href="/docs/harness/subagents" />
</Cards>