沙盒
沙盒是你附加到智能体的执行环境:用于读取、写入和运行命令的文件系统与 Shell。除非你提供,否则智能体不会有沙盒——useSandbox() 用于添加文件和命令访问权限。本指南涵盖附加沙盒带来的功能、内存中的虚拟沙盒、使用 local() 将智能体绑定到主机、远程提供商沙盒,以及智能体环境如何在其生命周期内发生变化。
沙盒带来的功能
一个智能体最多只能有一个环境,附加沙盒会同时定义多个功能:
- 文件和 Shell 工具。 附加沙盒后,智能体的工具集会增加
read、write、edit、bash、grep和glob,所有这些工具都在沙盒上操作。当模型运行bash时,命令在沙盒指定的位置执行。(沙盒也可以用自己的工具集替换这些工具——参见沙盒提供的工具。) - 工作空间上下文。 在初始化时,Flue 会查看沙盒的工作目录,并将其发现的内容组合到智能体的系统提示中:工作目录路径、目录列表以及
AGENTS.md的内容(如果存在)。 - 工作空间技能。
<cwd>/.agents/skills/下的技能目录会在同一时间被发现,并按名称提供给智能体,无需导入。参见技能。 - 子智能体。 委托共享父级的环境——相同的文件系统、相同的工具。
task调用可以将子级限定在不同的工作目录,但不能限定在不同的沙盒。参见子智能体。 - 你的应用代码。 Harness 工具访问与
harness.sandbox相同的环境,用于在没有对话记录的情况下暂存文件。
没有沙盒时,智能体就没有这些功能:没有文件或 Shell 工具,提示中没有工作空间,harness.sandbox 会抛出异常。其他一切——自定义工具、技能、子智能体、状态——无论有没有沙盒都能正常工作,许多智能体根本不需要更多功能。
选择支持任务的最窄环境:扩大环境会扩大模型驱动的工作可以读取、更改、执行和访问的范围。
虚拟沙盒
最轻量的环境是虚拟沙盒——一个内存文件系统配合模拟的 bash,完全用 TypeScript 实现(just-bash 引擎)。大多数标准 Unix 工具都能工作——ls、sed、awk、jq、sort、管道、重定向——还有用于 HTTP 的 curl。不会生成任何真实进程。
将 just-bash 添加到项目的依赖中,并使用 bash(...) 辅助函数包装实例:
'use agent';
import { bash, useModel, useSandbox } from '@flue/runtime';
import { Bash, InMemoryFs } from 'just-bash';
export function ScratchWorker() {
useModel('anthropic/claude-haiku-4-5');
useSandbox(bash(() => new Bash({ fs: new InMemoryFs() })));
return '获取、重塑并总结用户指向的数据。';
}两个属性定义了它:
- 与主机隔离。 命令在进程内模拟;模型无法访问主机文件系统、进程或环境变量。网络是可选的:传递
network: { allowedUrlPrefixes: [...] }(或dangerouslyAllowFullInternetAccess: true)以允许模拟的curl访问外部。 - 临时性。 文件系统开始时为空,每次运行时为智能体初始化新工作时都会重新构建。处理一条消息时写入的文件在下一条消息时就消失了。将持久知识保存在持久状态中,当文件本身必须持久时,请使用真正的沙盒。
这对于许多生产智能体来说已经足够——curl 和 jq 数据工作、文本重塑、任何只需要临时空间的工作。
应用代码访问与 harness.sandbox 相同的环境,因此harness 工具可以暂存输入文件,让智能体处理它,然后收集结果:
import { defineTool } from '@flue/runtime';
import * as v from 'valibot';
export const reviewDocument = defineTool({
name: 'review_document',
description: '审查一份提供的文档并报告发现。',
input: v.object({ document: v.string() }),
harness: true,
async run({ harness, data }) {
await harness.sandbox.writeFile('document.md', data.document);
await harness.prompt('审查 document.md 并将发现写入 review.md。');
return { output: { review: await harness.sandbox.readFile('review.md') } };
},
});模型看到 document.md 出现在其工作空间中,并使用文件工具处理它;你的应用提供输入并检索 review.md;暂存过程不会进入对话。
初始化文件和命令
just-bash 实例由你配置——初始化文件、允许列表网络访问或添加自定义命令:
'use agent';
import { bash, useModel, useSandbox } from '@flue/runtime';
import { Bash, InMemoryFs } from 'just-bash';
import { exportCatalogCsv } from '../shared/catalog.ts';
export function CatalogAnalyst() {
useModel('anthropic/claude-haiku-4-5');
useSandbox(
bash(
() =>
new Bash({
fs: new InMemoryFs({ '/data/catalog.csv': exportCatalogCsv() }),
network: { allowedUrlPrefixes: ['https://api.example.com/'] },
}),
),
);
return '回答有关 /data/catalog.csv 中产品目录的问题。';
}智能体获得一个包含目录的文件系统和一个只能访问你的 API 的 curl,所有这些仍然在内存中。
附加沙盒
useSandbox() hook 在智能体函数内部附加环境,就像任何其他智能体 hook:
useSandbox(factory);
useSandbox(factory, { cwd: '/srv/checkouts/flue' });
一些规则决定了它的行为:
- 每次渲染最多一次。 一个智能体有一个环境。在智能体主体或单个自定义 hook 内调用它;在同一渲染中第二次调用会抛出异常。在子智能体的渲染中也会抛出异常——委托共享父级的环境。
- 工厂是惰性的。 在每次渲染时构造工厂值在设计上是廉价的。昂贵的工作发生在工厂的
createSandbox()内部,运行时在初始化智能体时调用一次——在重新渲染时不会调用。 - 工厂接收智能体实例 ID。 适配器可以将其作为提供商资源的键,这就是远程沙盒如何为每个对话提供自己的持久工作空间(见下文)。
cwd限定工作目录在环境内部,在初始化时根据沙盒自身的基目录解析一次。它决定了命令默认在哪里运行以及工作空间发现(AGENTS.md、技能、目录列表)发生在哪里。
本地沙盒
在 Node.js 目标上,内置的 local() 工厂将智能体直接绑定到主机:文件操作使用真实文件系统,bash 命令通过主机 Shell 作为真实进程运行。设计上没有隔离。将其用于开发工具、CI 任务、编码智能体和自托管自动化,其中主机环境要么是工作空间,要么已经提供了隔离(容器、专用虚拟机)。不要将其用作不受信任请求或多租户的隔离边界。
'use agent';
import { useModel, useSandbox } from '@flue/runtime';
import { local } from '@flue/runtime/node';
export function ReleaseManager() {
useModel('anthropic/claude-sonnet-4-6');
useSandbox(local({ env: { GH_TOKEN: process.env.GH_TOKEN } }));
return '准备发布:检查 CI 状态、起草变更日志、标记发布。';
}工作目录默认为 process.cwd();使用 local({ cwd }) 覆盖它。
模型的 Shell 不会继承你的进程环境。默认情况下,只有一小部分 Shell 基本元素会通过——PATH、HOME、USER、LANG、TERM、TMPDIR 等——永远不会传递 API 密钥、令牌或云凭据。其他任何内容都需要通过 env 选项显式地按变量选择加入,如上面的 GH_TOKEN。将键设置为 undefined 可以删除一个默认值。传递 env: { ...process.env } 会将你的整个主机环境(包括密钥)交给模型的 Shell——只在你完全信任的环境中这样做。快照在沙盒构造时获取一次;之后对 process.env 的更改不会被获取。
在扩大 Shell 凭据之前,考虑是否可以使用窄范围的应用工具来执行特权操作——模型驱动的 Shell 应该持有尽可能少的权限。
参见 Node.js 目标指南获取完整的 local() 参考。
远程沙盒
当智能体工作需要每个对话的隔离、完整的 Linux 工具链或你不想在自己主机上运行的代码时,通过沙盒适配器附加提供商管理的沙盒。适配器是你项目中的一个小文件,将提供商的 SDK 包装成沙盒工厂契约。
使用 flue add 蓝图命令添加一个:
flue add sandbox e2b
蓝图会引导你的编码智能体创建 <source-dir>/sandboxes/e2b.ts 并安装提供商 SDK。生态系统目录列出了支持的提供商——包括 Daytona、E2B、Modal、Cloudflare Sandbox 和 Cloudflare Computer——参见生态系统中的沙盒。对于不支持的提供商,运行 flue add sandbox <docs-url>,你的编码智能体可以根据沙盒适配器 API构建适配器。
适配器特意设计得很薄:你的应用创建、重用和删除提供商沙盒;Flue 只连接你交给它的东西,从不销毁提供商基础设施。通常的模式是将提供商调用包装在工厂本身中,因此沙盒在初始化时被惰性创建(或重新连接):
'use agent';
import { Daytona } from '@daytona/sdk';
import { useModel, useSandbox } from '@flue/runtime';
import { daytona } from '../sandboxes/daytona.ts';
export function CodeRunner() {
useModel('anthropic/claude-sonnet-4-6');
useSandbox({
async createSandbox(options) {
const client = new Daytona();
const sandbox = await client.create();
return daytona(sandbox).createSandbox(options);
},
});
return '克隆用户指定的仓库,运行测试套件并报告结果。';
}createSandbox({ id }) 接收智能体实例 ID。一个在创建之前先通过该 ID 查找现有提供商沙盒的工厂,可以为每个对话提供一个持久的工作空间,该空间在消息和进程重启之间保持不变。
取消正在运行的命令——模型自身的 timeout,或你的应用中止周围任务——总是会立即拒绝,无论提供商在底层做什么。local() 的进程组杀戮实际上会停止命令,但大多数提供商 SDK 没有中途取消功能:远程命令在拒绝后继续在后台运行,其最终输出会被丢弃,而不是稍后出现在对话中。SDK 支持取消的提供商(Vercel、Mirage)会真正停止命令,而不是仅仅放弃它。
沙盒提供的工具
沙盒工厂还可以携带一个 tools 函数。当存在时,它会替换该智能体的框架默认面向模型的工具集——例如,无执行环境的适配器保留 read/write/edit 文件工具,但将 Shell 支持的 bash/grep/glob 替换为自己的执行器工具。适配器从导出的每工具工厂(createReadTool、createBashTool 等)组合这些集合,而不是从头重建。因为功能会以这种方式变化,请在假设普通文件或命令工具可用之前检查集成的文档。参见沙盒适配器 API了解契约。
条件附加
像其他 hook 一样,useSandbox() 可以被条件调用——智能体可以在对话中途合法地获得或失去其沙盒。根据持久状态设置调用门控,并让工具翻转它:
'use agent';
import { useModel, usePersistentState, useSandbox, useTool } from '@flue/runtime';
import { local } from '@flue/runtime/node';
export function SupportEngineer() {
useModel('anthropic/claude-sonnet-4-6');
const [investigating, setInvestigating] = usePersistentState('investigating', false);
useTool({
name: 'open_investigation',
description: '当问题需要在仓库中进行实际调试时调用。',
async run() {
setInvestigating(true);
return '调查已开启。仓库工作空间将被附加。';
},
});
if (investigating) {
useSandbox(local({ cwd: '/srv/support/repro' }));
}
return investigating
? '在工作空间中复现问题并报告发现。'
: '从对话中诊断问题。需要实际调试时开启调查。';
}useSandbox() 调用的存在在初始化时和每个回合边界都会被读取。当它翻转时,环境在下一个模型调用之前交换:附加会解析声明的工厂,附加会移除环境——文件和 Shell 工具随之消失,任何方向都不会有东西转移。模型会通过对话中的单个 environment 信号被告知交换,该信号重新声明完整的当前状态——新的工作目录加上完整的工具、技能和子智能体列表——并警告来自先前环境的文件和结果可能不再可访问(参见动态资源)。
系统提示保持冻结:它继续描述在初始化时发现的工作空间,直到下一次压缩,压缩会根据当前环境重新发现。交换还会重写原生工具数组,因为文件和 Shell 工具随之而来和消失,这会使提供商的提示缓存失效。
只有_存在_是可观察的:工厂在每次渲染时都是新对象,因此在保持附加的同时将沙盒 A 替换为沙盒 B 不会在运行中交换——它在下一次提交初始化时生效。因为条件存在于持久状态中,它会持久地重放:每个后续提交都会重新附加相同的声明,基于实例 ID 的适配器会解析回相同的工作空间。
后续步骤
- 智能体 Hooks ——
useSandbox()参与的 hook 模型。 - 智能体 API —— 完整的
useSandbox(...)和harness.sandbox契约。 - 沙盒适配器 API —— 为你的沙盒提供商构建适配器。
- 生态系统:沙盒 —— 支持的提供商目录。
- Node.js 目标 ——
local()参考和主机部署。 - Cloudflare 目标 —— Workers 上基于容器的沙盒。
- 持久性 —— 对话持久性和工作空间持久性是独立的选择。