Skip to content

沙箱与工具配置

本章目标:

  1. 掌握 sandbox.use 三种模式(local / docker-AIO / provisioner-K8s)如何在 config.yaml 配置,以及 scripts/docker.sh 如何在启动前从配置中检测模式。
  2. 理解 tools[]tool_groups[] 的配置结构(use 变量路径 + group 分组),以及 skills.path / skills.container_path 的解析与容器挂载方式。
  3. 识别会"放开隔离边界"的安全开关(allow_host_bash 等),知道它们的默认值、风险与触发条件。

TL;DR

sandbox.use 是一个类路径字符串,DeerFlow 用反射(resolve_class)把它实例化成 SandboxProvider:指向 LocalSandboxProvider 即本地直跑(local),指向 AioSandboxProvider 即容器隔离(aio),后者再加 provisioner_url 即升级为 Kubernetes/k3s 动态 Pod(provisioner)。tools[] 每项靠 use 变量路径(如 deerflow.sandbox.tools:bash_tool)被 resolve_variable 解析成工具实例,并按 group 落入 tool_groups[] 声明的逻辑分组以供按组过滤。skills.path(主机路径)与 skills.container_path(容器内路径,默认 /mnt/skills)决定技能目录如何只读挂载进沙箱。核心安全开关 sandbox.allow_host_bash 默认 false,因为本地沙箱并非安全隔离边界;把它设为 true 等于在主机上直接执行模型生成的 shell 命令。

Overview

DeerFlow 的"沙箱"是 agent 执行 bash / 文件读写等工具的运行边界。为什么要把它做成"配置即可切换的三种模式",而不是写死一种?因为不同部署场景对隔离强度运维复杂度的取舍完全不同:本地单人开发追求零依赖、快速迭代(local);团队共享 / 不可信输入需要容器隔离(aio);生产 / 多租户需要每个会话独立 Pod 的强隔离与弹性伸缩(provisioner)。

把模式收敛到一个 sandbox.use 类路径字段,带来三个直接收益:配置层只声明"用哪个 provider 类",实例化交给反射系统(sandbox_provider.py:48-62);AioSandboxProvider 自己根据有无 provisioner_url 在 aio 与 provisioner 之间二选一(aio_sandbox_provider.py:135-156);启动脚本 docker.sh 可以纯文本扫描 config.yaml 提前判断要不要拉起 provisioner 服务(docker.sh:18-61)。

同理,工具不写死在代码里,而是 tools[] 列表 + use 变量路径,让"用哪个 web 搜索 provider""是否开 bash"都变成配置开关。技能目录通过 skills.path / skills.container_path 两个字段,统一以只读方式呈现给本地与容器两种沙箱。

Architecture

配置到运行实例的转换链涉及四个 Pydantic 配置模型(SandboxConfig / ToolConfig / ToolGroupConfig / SkillsConfig),它们在 AppConfig 中被聚合,并由反射系统与各 provider 在运行时消费。

Source:

Components / Subsystems

SandboxConfig — 沙箱配置

SandboxConfig.use唯一必填字段,描述 sandbox provider 的类路径(如 deerflow.sandbox.local:LocalSandboxProvider)sandbox_config.py:30-33。模型设了 extra="allow",因此 provisioner_url 这种未在模型中显式声明的字段也能被接受并通过 getattr 读取 sandbox_config.py:83

AIO 专属字段都是可选(None 默认),由 AioSandboxProvider._load_config() 在为空时套用各自的 DEFAULT_* 常量:image / port / container_prefix / idle_timeout / replicas / mounts / environment,并把 provisioner_urlgetattr(..., None) or "" 取出 aio_sandbox_provider.py:160-178environment 中以 $ 开头的值会被 _resolve_env_vars 解析为主机环境变量 aio_sandbox_provider.py:180-190

输出截断三项(bash_output_max_chars 默认 20000、read_file_output_max_chars 默认 50000、ls_output_max_chars 默认 20000)对所有模式生效,设为 0 表示不截断 sandbox_config.py:67-81

VolumeMountConfig — 额外目录挂载

mounts[] 每项是一个 VolumeMountConfig,三字段:host_path(主机绝对路径)、container_path(容器内虚拟路径)、read_only(默认 false)sandbox_config.py:4-9。仅 AioSandboxProvider 的本地容器后端会消费这些挂载(aio_sandbox_provider.py:150-156)。

ToolConfig / ToolGroupConfig — 工具与分组

ToolGroupConfig 只有一个必填字段 name(分组唯一名)tool_config.py:4-8ToolConfig 有三个必填字段:name(工具唯一名)、group(所属分组名,需与某个 tool_groups[].name 对应)、use(工具 provider 的变量路径,如 deerflow.sandbox.tools:bash_tool)tool_config.py:11-20。两个模型均 extra="allow",因此 max_results / timeout / api_key 等 provider 特有参数可以直接写在工具项里(见 config.example.yamlweb_searchweb_fetch 等)。

get_available_tools()groups 参数按 tool.group 过滤,然后对每个 cfg.use 调用 resolve_variable(cfg.use, BaseTool) 得到工具实例;当配置里的 name 与工具对象自身的 .name 不一致时仅记 warning,实际绑定以工具对象的 .name 为准 tools.py:66-88

SkillsConfig — 技能目录与容器路径

SkillsConfig 三个相关字段:use(SkillStorage 实现类路径,默认 deerflow.skills.storage.local_skill_storage:LocalSkillStorage)、path(主机技能目录,可空)、container_path(技能在沙箱容器内的挂载路径,默认 /mnt/skills)skills_config.py:16-30

get_skills_path() 的解析顺序固定为:① 显式 path 字段 → ② DEER_FLOW_SKILLS_PATH 环境变量 → ③ 项目根下的 skills 目录 → ④ monorepo 兼容的仓库根候选;都不存在时仍返回项目根默认路径以便上层稳定显示"无技能"位置 skills_config.py:32-59get_skill_container_path(){container_path}/{category}/{skill_name} 拼出单个技能在容器内的完整路径 skills_config.py:61-72

安全门控 — host bash 是否放行

is_host_bash_allowed() 决定 bash 工具是否暴露给 agent:若当前 provider 不是 LocalSandboxProvider(即用了隔离容器),直接返回 True;若是本地 provider,则只在 sandbox.allow_host_bash 显式为 True 时返回 True,否则 False security.py:35-45get_available_tools() 据此过滤掉 group=="bash"use=="deerflow.sandbox.tools:bash_tool" 的工具项 tools.py:26-34,69-71

Data Flow

下图描述「config.yaml 文本 → 沙箱与工具运行实例」的解析时序,以及启动脚本独立的文本探测路径。

速查表

三种 sandbox 模式对比矩阵

维度localaio(docker)provisioner(K8s)Source
sandbox.usedeerflow.sandbox.local:LocalSandboxProviderdeerflow.community.aio_sandbox:AioSandboxProvider同 aioconfig.example.yaml:560 / :586 / :627
是否需 provisioner_url(如 http://provisioner:8002)config.example.yaml:626-628
隔离强度主机直跑(非安全边界)Docker/Apple Container 容器每会话独立 k3s Podsandbox_config.py:17-19
后端选择逻辑LocalSandboxProviderLocalContainerBackendRemoteSandboxBackend(provisioner_url 触发)aio_sandbox_provider.py:138-156
启动脚本检测结果localaioprovisioner(额外起 provisioner 服务)docker.sh:50-60 / :166-176
host bash 默认行为默认禁用,需 allow_host_bash:true默认放行(容器内隔离)默认放行security.py:35-45
未知 provider 时检测回退回退到 localdocker.sh:58-60

模式检测的回归用例完整覆盖了上述所有分支(含被注释掉的 provisioner_url 不应激活 provisioner)test_docker_sandbox_mode_detection.py:44-107

tools / tool_groups 配置项

字段所属必填含义Source
tool_groups[].nameToolGroupConfig分组唯一名(如 web/file:read/file:write/bash)tool_config.py:7 / config.example.yaml:358-362
tools[].nameToolConfig工具唯一名(绑定时以工具对象 .name 为准)tool_config.py:14
tools[].groupToolConfig所属分组,对应某个 tool_groups[].nametool_config.py:15
tools[].useToolConfig工具 provider 变量路径(module:variable)tool_config.py:16-19
额外参数(如 max_results/timeout/api_key)ToolConfig(extra=allow)透传给 provider 的特有参数tool_config.py:20 / config.example.yaml:371-374
skills.pathSkillsConfig主机技能目录(绝对或相对项目根)skills_config.py:23-26
skills.container_pathSkillsConfig否(默认 /mnt/skills)技能在沙箱内的只读挂载路径skills_config.py:27-30 / config.example.yaml:725

Configuration

config.example.yamlsandbox 段以 local 模式为默认启用项,aio / provisioner 以注释形式给出模板,切换时取消注释并注释掉 local 段即可 config.example.yaml:559-628

配置键默认值作用安全相关Source
sandbox.usedeerflow.sandbox.local:LocalSandboxProvider选择 provider(决定模式)config.example.yaml:560
sandbox.allow_host_bashfalse是否允许 bash 工具在主机直接执行⚠️ 放开隔离:本地 provider 非安全边界,设 true 等于让模型在主机跑任意 shell;仅限完全可信单用户环境sandbox_config.py:34-37 / config.example.yaml:561-564
sandbox.provisioner_url未设(extra 字段)设置后 AIO 切到远程 provisioner / k3s 模式config.example.yaml:626-628 / aio_sandbox_provider.py:144-147
sandbox.imageNone→内置 DEFAULT_IMAGEAIO 容器镜像sandbox_config.py:38-41
sandbox.portNone→DEFAULT_PORT(8080)AIO 容器基础端口sandbox_config.py:42-45
sandbox.replicasNone→DEFAULT_REPLICAS(3)并发容器上限,超限按 LRU 回收sandbox_config.py:46-49
sandbox.container_prefixNonedeer-flow-sandbox容器名前缀sandbox_config.py:50-53
sandbox.idle_timeoutNone→600 秒(0 禁用)空闲多久后释放沙箱sandbox_config.py:54-57
sandbox.mounts[][]额外主机→容器目录挂载⚠️ read_only:false(默认)会把主机目录可写暴露给沙箱进程sandbox_config.py:58-61 / config.example.yaml:565-570
sandbox.environment{}注入容器的环境变量($VAR 解析主机环境)⚠️ 注入的密钥会进入沙箱内可执行环境sandbox_config.py:62-65
sandbox.bash_output_max_chars20000(0 禁用)bash 输出保留字符上限(头尾截断)sandbox_config.py:67-71
sandbox.read_file_output_max_chars50000(0 禁用)read_file 输出上限(头部截断)sandbox_config.py:72-76
sandbox.ls_output_max_chars20000(0 禁用)ls 输出上限(头部截断)sandbox_config.py:77-81
skills.use...:LocalSkillStorageSkillStorage 实现类skills_config.py:19-22
skills.pathNone(走解析顺序)主机技能目录skills_config.py:23-26
skills.container_path/mnt/skills沙箱内技能挂载路径(只读)skills_config.py:27-30

技能目录在 AIO 模式下由 _get_skills_mount() 统一以只读方式挂载(read_only=True),并在 DooD 场景下用 DEER_FLOW_HOST_SKILLS_PATH 修正主机侧路径 aio_sandbox_provider.py:286-304

Common Pitfalls / Tips

  • 切模式时别忘了关掉旧段:config.example.yaml 中 local 段是默认启用的;启用 aio/provisioner 时必须同时把 local 的 sandbox: 段注释掉,否则 YAML 中后出现的 sandbox: 才生效,前面的会被忽略,容易误判 config.example.yaml:559-628
  • 被注释的 provisioner_url 不会激活 provisioner:docker.sh 的 awk 探测会跳过以 # 开头的行,这一行为有专门回归测试覆盖 test_docker_sandbox_mode_detection.py:88-96
  • 未知 provider 不会报错而是退回 local:docker.sh 对识别不了的 use 字符串静默回退 local,启动看起来正常但隔离已失效,务必核对 use 拼写 docker.sh:58-60
  • tools[].name 与工具对象 .name 不一致只警告不报错:实际绑定以工具对象自身 .name 为准,配置里写错名字会导致模型 schema 与运行时路由对不上(issue #1803),改 use 后留意日志 warning tools.py:75-88
  • bash 工具消失多半是安全门控:用 local provider 且 allow_host_bash:false(默认)时,所有 group:bash / bash_tool 工具项会被静默过滤,这是设计行为而非配置丢失 security.py:35-45
  • skills.path 留空时的兜底:不写 path 也不设 DEER_FLOW_SKILLS_PATH 时,会依次找项目根 skills/ 与 monorepo 仓库根候选;都没有也不报错而是返回项目根默认路径,排查"技能没加载"先确认目录是否真实存在 skills_config.py:45-59
  • AIO 可选字段为空才套默认值:replicas/idle_timeoutgetattr(..., None) 读取,显式写 0 与"不写"语义不同(idle_timeout:0 表示禁用空闲回收)aio_sandbox_provider.py:165-173

References

章节关系
./04-配置系统与AppConfig.md上游:本章四个配置模型如何被聚合进 AppConfig 及缓存/优先级机制
./06-MCP与Skills扩展配置.md平行:extensions_config.json 中技能启用状态与本章 skills.path 互补
./17-沙箱系统架构.md下游:本章配置如何驱动 SandboxProvider 的 acquire/get/release 运行机制与路径映射
./18-沙箱工具与路径映射.md下游:/mnt/user-data/mnt/skills 虚拟路径在工具层的实际翻译
./20-工具系统与内置工具.md下游:tools[] 解析出的工具与内置/MCP/子代理工具如何汇总绑定
./25-Community工具与搜索集成.md下游:tools[] 中 community 搜索/抓取 provider 的具体实现与所需 API key 在 25 章

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析