返回部落格
技術乾貨

Agent Sandbox 设计:FastGPT 如何把 Agent 沙箱从代码执行器做成持久 Workspace

为什么 Agent 需要的不只是代码执行器

在 FastGPT 中,Agent 不仅要完成知识库问答与工作流编排,还需要处理复杂的文件交互、运行 Python 脚本、动态安装第三方依赖,并持续产出能被后续多轮任务复用的成果。

这类场景与传统的代码执行有着本质区别。传统的代码执行通常是“输入一段脚本,执行后返回结果”的单次行为。但在实际的 Agent 任务中,模型往往需要先拉取数据、编写清洗脚本、生成图表与分析报告;过了一段时间,用户可能再次进入同一场景,要求调整图表细节、追加分析维度或开展新的下游工作。如果每次触发都启动一个全新的空容器,先前生成的文件、环境依赖和中间结果便无法自然延续。

要支撑这种工作形态,系统需要从“单次代码执行器”转向“受控且可持续工作的 Agent Workspace”。本文将从技术定位与业内常见路径出发,深入拆解 FastGPT 的 Agent Sandbox 架构方案,并重点回顾三个核心设计变更:

  • Workspace 归属收敛:从 Chat 级独立环境,走向应用内同用户共享 Workspace。
  • 环境就绪度解耦:从固定镜像生命周期,走向支持镜像源配置与脚本注入的 Runtime Prepare。
  • 计算与状态分离:通过快照归档释放长闲置实例,同时保留 Workspace 的即时可恢复性。

什么是 Agent Sandbox

Agent 本质上是一个无状态的决策器。它依靠当前上下文规划下一步动作,但生成的文件、安装的依赖、进程产物和中间状态,必须交由外部计算环境维护。

一个完整的 Agent 运行环境至少涵盖三层状态:

  • 任务状态:当前轮次正在处理什么意图;
  • 工作状态:积累了哪些中间文件、数据产物与修改记录;
  • 运行状态:准备了哪些运行时依赖、脚本配置与网络代理。

如果脱离 Sandbox,这些能力很难兼顾安全性与连续性:直接使用宿主机,多租户隔离与安全边界无法成立;纯对象存储可以存取文件,却无法提供持续运行命令、动态编译与加载 Skill 的环境;而单次运行容器则会将多轮连续操作切割得支离破碎。

因此,Agent Sandbox 并不是单纯在架构中堆叠一个容器组件,而是为 Agent 的工作状态划定一个受控的执行边界:

Agent Sandbox = 受控计算环境 + Workspace + Agent 工具 + 生命周期管理
  • 受控计算环境:提供隔离的 CPU/内存与 Linux 运行时,收敛容器权限,限制内网访问与资源配额;
  • Workspace:提供持久化的文件系统,承载脚本源码、三方包、用户上传文件和任务衍生数据;
  • Agent 工具:以标准抽象向模型暴露文件读写、搜索、命令执行与结果预览接口;
  • 生命周期管理:按需负责实例的拉起、保活、就绪度检查、归档与快照恢复。

在 FastGPT 内部,Agent Loop 并不直接穿透到底层物理容器,而是通过 SandboxClient 消费由运行时调度并就绪的 Workspace。

Agent 沙箱的两条路线:Dify、Coze Studio 与 FastGPT 对比

开源社区中,围绕代码运行与 Agent 环境的实现通常沿着两条路线演进:

  • 代码执行服务路线:将不可信代码推送到独立的沙箱容器中执行,重点在于系统调用拦截、文件系统只读保护、内存与时间限额。这种模式调用链清晰、无状态且易于横向扩展,非常适合工作流中的独立数据处理节点。
  • Agent Workspace 路线:不再追求即用即弃,而是为会话或应用构建一套具备文件系统与依赖上下文的持久工作区,支持在多轮工具调用间持续修改文件、执行命令与保留资产。

我们选取同处于应用与 Agent 编排层的 Dify 和 Coze Studio 进行横向对比:

Dify:从独立执行服务到会话级 Workspace

Dify 传统上使用独立的 Dify Sandbox 承载不可信代码执行。随着 Agent 复杂度的提升,其最新的 Agent Runtime 引入了包含 Workspace、Home Snapshot、Execution Binding 与 RuntimeLease 的运行时资源模型,使 Agent 能够获得类似本地终端的操作体验。

需要区分的是其两层空间定义:

  • 平台层 Workspace:划分租户、团队、应用与配置的协作边界;
  • 运行态 Sandbox Workspace:负责承接运行时文件读写与依赖沉淀。

在 Dify 当前的开源实现中,Sandbox Workspace 的归属绑定于 conversation、build draft 或 Workflow run 等产品对象。同一对话内的多轮交互可以共享工作区,但开启新会话通常意味着分配新的工作区;跨会话延续的主要是 Agent 级的不可变 Home Snapshot(如由 Build Draft 生成),而非用户在对话中持续产生的工作文件。

Coze Studio:工作流节点级沙箱

Coze Studio 的 Workflow 代码节点可在部署时选择 local 与 sandbox 两种运行模式(local 基于 venv,不做环境隔离;sandbox 基于 Deno + Pyodide)。其沙箱核心聚焦于单节点的安全限制:控制环境变量读取、限制文件读写路径、约束可执行指令白名单、按白名单限制网络访问,并硬性限制执行超时与内存占用。

这种设计将沙箱明确界定为工作流图中的受控执行算子,配置清晰且便于调试。但它并未围绕长期持续演进的 Agent 交互构建工作区身份,对于需要连续几天反复操作同一批本地资产的 Agent,依然需要上层平台额外设计工作区存储与还原机制。

方案对比

维度独立代码执行器(如 Coze Studio 代码节点 / Dify Sandbox)会话级 Workspace(新 Dify Agent Runtime)应用内用户级 Workspace(FastGPT)
核心定位工作流节点内的安全计算函数承接多轮对话的临时终端持续沉淀资产的应用工作空间
持久化生命周期单次调用结束即销毁随所属会话、草稿或 Workflow 运行退役回收;单次请求仅持有 RuntimeLease与应用及用户身份绑定,按需归档与唤醒
跨轮复用范围无复用仅限于单个会话内同应用同用户跨会话自由复用
运行时准备依赖打包在静态镜像中会话启动时拉起动态处理包管理器镜像源、初始化脚本与 Skill 挂载

FastGPT 选择跳过单纯的会话级绑定,将普通 App Chat 的执行环境提升至“应用内用户级”,并在底层贯穿了环境动态就绪(Runtime Prepare)与对象存储归档机制。

FastGPT Agent Sandbox 技术架构

FastGPT 的 Agent Sandbox 在工程架构上分为四层:

  • 计算与隔离层:底层容器与 Provider 适配,提供 Linux 隔离运行实例;
  • 工作区存储层:基于物理挂载与目录规范,划分跨会话共享区与单会话目录;
  • 运行时就绪层(Runtime Prepare):负责环境预热、动态镜像配置、初始化脚本执行与 Skill 注入;
  • 生命周期与租约调度:负责实例探活、并发 Lease 保护、CAS 状态流转与对象存储归档。

上层业务并不感知具体的计算节点。Workflow 或 Skill 编辑器只需传递业务身份,Runtime 负责寻址、唤醒或拉起环境,最终把已就绪的 SandboxClient 注入到 Agent Loop 中:

图 1:一次 Agent 请求从业务入口到计算环境的处理链路

图 1:一次 Agent 请求从业务入口到计算环境的处理链路

1. 用业务身份确定 Sandbox 归属

Sandbox 的归属由三元组唯一确定:

sourceType + sourceId + userId

在日常 App 对话场景中,这对应于“具体应用 + 当前有效用户”。而在 Skill 编辑场景下,系统分配独立的 source 类型与 Skill ID,避免开发调试态污染线上运行环境。

Chat ID 不参与决定物理 Sandbox 的归属,仅作为工作目录的细分标识。这一解耦保证了:

  • Sandbox 是稳定的中长期环境,归属于用户在应用内的连续活动;
  • Chat 是一次会话窗口,提供默认工作目录;
  • 用户清理单条聊天记录,不会连带清空其在该应用下积累的代码、三方依赖与生成物;
  • 只有当整个 App 或 Skill 被显式删除时,才级联清理底层的 Sandbox 与归档资源。

2. Workspace 目录结构

一个 Workspace 在文件系统层面划分为明确的边界:

  • 共享区域(Shared Root):存放用户跨会话共用的脚本库、公共数据和已发布的 Skill 依赖;
  • 会话区域(Session Directory):对应当前 Chat ID 的独立子目录,默认存放该会话上传的源文件与最新产物;
  • 系统运行时区域(Runtime Builtin):平台内置 Skill 同步到 Sandbox HOME 下的独立目录,按内容指纹增量同步,不进入用户 Workspace,避免污染用户空间。

这种结构允许用户在会话 B 中直接读取会话 A 生成的中间数据,但默认写操作收敛于自身会话目录。

3. 统一工具层:SandboxClient

Agent Loop 交互的不是原生 Docker 或 Kubernetes API,而是统一封装的 SandboxClient。核心工具包含:

  • 命令执行:带超时、工作目录与环境变量控制的终端调用;
  • 文件管理:文件的写入、增量编辑、切片读取与状态检测;
  • 文件检索:基于正则 / 文本的内容搜索(grep)与基于 glob 的路径搜索(find);
  • 资产预览:为工作区生成的图表、报告或静态页面签发短期有效的只读直连链接,用于下载或 HTML 预览。

底层所有工具调用均经过参数 Schema 校验、路径越界防御(Path Traversal 防护)、按行数与字节上限截断输出,以及异常标准化。除了依赖解压或特定管道必须调用 Shell 外,常规读写优先通过轻量级文件系统直接操作,避免频繁拉起 Shell 进程导致开销过高。

4. 运行前准备:Runtime Prepare

实例处于 running 状态,并不意味着它满足当前任务的特定要求。在进入 Agent Loop 之前,必须先经过确定性的装配管线:

图 2:Agent 开始工作前的 Runtime Prepare 流程

图 2:Agent 开始工作前的 Runtime Prepare 流程

Prepare 管线由一系列可组合的幂等步骤顺序编排:

  • 身份寻址与恢复:检查实例是否存在,若处于归档态则触发反序列化唤醒;
  • 目录上下文对齐:初始化当前 Chat 专属工作子目录;
  • 包管理器镜像源配置:按部署配置写入 npm / PyPI / apt 等镜像源(如企业私有源或国内镜像);
  • 依赖与 Skill 挂载:挂载指定版本的 Skill 包及其模板;
  • 初始化脚本执行:运行应用或 Skill 指定的预置构建脚本;
  • 能力元数据扫描:提取就绪的 Skill 接口定义,注入 Agent 上下文。

5. Skill 挂载与按需加载

Skill 不只是提示词中的静态说明,它往往伴随着可执行脚本、环境包与前置依赖。

为了避免命名冲突并确保运行环境可追踪,Skill 在工作区内按不可变的发布版本 ID进行物理隔离存放,而不是使用可变的展示名称。Agent 获得的初级 Prompt 仅包含极精简的能力路由表,只有当模型决策需要激活该 Skill 时,才会按需读取该目录下的完整规格说明与操作脚本:

图 3:Skill 从发布版本到 Agent 能力索引的部署过程

图 3:Skill 从发布版本到 Agent 能力索引的部署过程

6. 生命周期状态与并发控制

Sandbox 的主生命周期包含三种稳定态:running(实例在线)、stopped(计算释放、本地盘保留)、archived(计算释放、全量状态沉淀为对象存储快照)。

为了抵御分布式环境下的复杂并发问题,系统引入了多层保护机制:

  • Lifecycle Lease:串行化针对同一 Sandbox 物理实例的拉起、销毁与归档操作,防止并发冲突;
  • Prepare Lease:当同一用户在同一应用内同时打开多个 Chat 触发初始化时,锁住共享 Workspace 的环境准备与脚本执行过程,防止三方包安装交织破坏环境;
  • Mongo CAS 状态机:所有生命周期流转记录细粒度进度(如 archiveUploaded、providerDeleted),以“状态 + 操作令牌(operation id)”做条件更新,实现乐观并发控制与故障分步重试。

三个关键设计变更

当前稳定的 Sandbox 方案,是在解决实际业务摩擦的过程中迭代成型的。以下三个核心变更构成了整个架构的演进主线。

变更一:从 Chat 级沙箱到应用内用户级 Workspace

在原型阶段,为每个 Chat 独立创建 Sandbox 是最直接的设计:Chat 启动时新建容器,会话终止时直接销毁。

但实际使用中,这种模式暴露出明显痛点:

  • 认知断层:用户前一个会话清洗好的几十兆 CSV 数据,换一个会话便完全不可见,无法进行“基于上一轮产物写一份周报”的自然延伸;
  • 资源浪费严重:同一用户连续发起的多次简短交互,频繁触发容器 Cold Start,重复下载上百兆的 Python/Node 依赖包;
  • 生命周期错位:在用户的认知中,“清除一条聊天记录”不等于“抹除所有的生成文件与工作积累”。

架构重构:将 Sandbox 的宿主从 Chat ID 提升至 sourceId + userId。

  • 同一用户在同一应用下的所有交互收敛至同一个物理 Sandbox;
  • Chat 回退为 Workspace 内部的会话逻辑子目录;
  • 会话删除只清理自身聊天日志与临时标记,不破坏顶层存储;
  • 引入并发租约处理同用户跨会话同时准备环境的锁竞争。

这一变更将 Sandbox 从“短命的执行器”重构为“属于用户的工作区”,为复杂、跨周期的 Agent 任务提供了连续性支持。

变更二:从静态镜像启动到可重试的 Runtime Prepare

早期的沙箱生命周期极其简单:创建容器 → 等待启动 → 执行命令 → 停止容器。环境完全依赖静态构建好的基础镜像。

但在落地私有化交付与复杂业务场景时,这一假设迅速失效:

  • 客户内网环境无法直连公网,包管理器需要指向内网或国内镜像源;
  • 不同的行业分析应用需要预置各异的初始化数据脚本;
  • Skill 具备版本属性,需要在运行时无感注入 Workspace。

这意味着“容器进程启动成功”绝不等于“环境已满足任务需求”。

架构重构:剥离并抽象出独立的 Runtime Prepare 阶段。

  • 环境按需组装:通过可插拔的 Prepare Steps,实现不同场景的动态定制。未启用 Sandbox 的普通问答不进入准备流程,需要 Sandbox 的任务按需配置包管理器镜像源与初始化脚本;
  • 执行幂等性:镜像源配置、初始化脚本和内置 Skill 同步等关键步骤记录指纹(Hash / ETag),Skill 入口脚本按不可变版本 ID 记录,内容未变更时直接跳过;
  • 失败可重试:初始化脚本失败或超时只记录日志、不写入成功指纹,下次准备时会重新执行,且不阻断 Agent 主流程;实例本身无法就绪时(如仍在初始化或生命周期操作未完成)则返回明确的不可用状态,不会把未就绪的沙箱交给 Agent Loop。

变更三:用快照归档分离计算与存储

当沙箱从 Chat 级提升为用户级长期空间后,第二个系统性问题浮出水面:实例闲置率与资源成本。

如果保持所有用户的 Sandbox 实例全天候运行,主机资源消耗会随着用户基数线性暴增;但若因闲置简单地销毁容器,用户此前积累的工作环境与已安装的依赖就会永久丢失。

架构重构:在单纯的“停止”与“删除”之间,建立具备快照能力的归档机制。

  • 冷热状态分离:运行中的实例承担高频实时计算;闲置超过阈值(默认 60 分钟)先停止释放计算资源,停止后持续闲置超过归档阈值(默认 7 天)再触发归档流程,两个阈值均可配置;
  • 快照持久化:将 Workspace 完整打包并推送到 S3 兼容的对象存储,随后安全销毁底层计算实例与存储卷,计算成本直接归零;
  • 分阶段重试与防并发:归档操作涵盖“打包快照并上传对象存储 → 删除计算实例 → 删除存储卷 → 提交 archived 状态”等多个外联阶段,每一步均以原子 CAS 记录进度,支持异常后断点续传;
  • 无感按需唤醒:当该用户重新进入应用提问时,Runtime 命中归档态,拉起新的计算实例、拉取快照解压恢复 Workspace,再照常执行 Runtime Prepare,整个过程对上层 Agent 保持透明。

需要说明的是,归档只打包 Workspace 目录本身;安装在 Workspace 之外(如系统目录或用户 HOME)的依赖不会进入快照,恢复后由初始化脚本按需重新安装。

总结

如果我们的目标仅仅是在工作流中安全地执行几行 Python 脚本并返回一个数值,一个无状态的一次性代码执行器就足够了。

但在真正的 Agent 应用场景中,Agent 扮演的是一位长时间陪伴用户的工作助手。它必须拥有可自由读写的桌面,必须能在遇到新任务时随时安装依赖,必须能跨越多轮会话连续修改文件,并且在无人值守的休眠期后,依然能准确找回上次工作的现场。

FastGPT 在 Sandbox 上的技术收敛与迭代最终将不可信代码的执行、多轮任务的工作状态、动态的运行时就绪能力以及弹性的生命周期管理整合在一起,构建成一套面向 Agent 的正式基础设施。唯有建立起这样的执行边界,Agent 才能真正从只能一次性对话的问答机器人,蜕变为能够持久交付价值的应用空间。

FastGPT
讓AI成為業務增長的新引擎

即刻嘗試