文件系统Filesystem详解一句话概括文件系统是 Agent 的文件柜——它决定了 Agent 的文件存在哪里是本机磁盘、远程存储还是隔离的沙箱环境。你能学到什么为什么需要抽象文件系统直接用 Java 的 File API 不行吗三种文件系统模式的区别本机模式、共享存储模式、沙箱模式什么是多租户隔离为什么要用NamespaceFactory类层次结构AbstractFilesystem家族成员之间的关系如何根据你的场景选择正确的文件系统配置前置知识需了解 Java 接口与继承的基本概念以及 05-memory.md 中 Agent 的双层记忆系统——记忆最终要落到文件系统上。核心概念AbstractFilesystem — 文件柜的统一说明书生活类比想象你去图书馆借书。图书馆有各种藏书的地方普通书架、珍本室、电子资源库。但作为读者你只需要知道我要借这本书图书馆员会帮你从正确的地方取。AbstractFilesystem就是那个统一的借书接口——不管文件实际存在哪里Agent 都用同样的方式操作。技术解释AbstractFilesystem是一个接口定义了所有文件系统都必须支持的基本操作publicinterfaceAbstractFilesystem{// 列出目录内容就像在文件管理器里打开文件夹ListStringls(Stringpath);// 读取文件内容就像用记事本打开文件Stringread(Stringpath);// 写入文件内容就像保存记事本内容voidwrite(Stringpath,Stringcontent);// 编辑文件就像在 Word 里修改文档voidedit(Stringpath,StringoldText,StringnewText);// 在文件中搜索就像 CtrlF 查找ListStringgrep(Stringpattern,Stringpath);// 用通配符找文件就像搜索 *.txtListStringglob(Stringpattern);// 上传文件voiduploadFiles(MapString,Stringfiles);// 下载文件MapString,StringdownloadFiles(ListStringpaths);}为什么需要抽象如果直接用 Java 的FileAPIAgent 就只能操作本机文件。但实际场景中我们可能需要把 Agent 的记忆存到云端多台机器共享在沙箱里执行不受信任的代码根据用户 ID 隔离数据多租户抽象接口让这些需求变成换个实现类的事而不是重写整个 Agent。三种声明式模式 — 选哪种文件柜生活类比假设你要开一家公司需要一个地方存档案。你有三种选择本机模式在公司自己的档案室里放铁皮柜——方便、省钱但只有一个房间不能多地点共享。共享存储模式租用云端档案服务——多个分公司都能访问同一份档案但贵一些而且不能在档案室里做实验不能执行脚本。沙箱模式租一个独立的实验室——里面有档案柜还能做化学实验执行脚本就算爆炸了也不会影响主楼。技术对比表模式配置方法能否执行 Shell适用场景模式 1本机 shellfilesystem(LocalFilesystemSpec)或默认能宿主机上单机部署、测试环境、受信任的 Agent模式 2复合 Storefilesystem(RemoteFilesystemSpec)不能多副本共享记忆、生产环境、不需要执行脚本模式 3沙箱filesystem(SandboxFilesystemSpec)能沙箱内需要隔离执行、处理不受信任的代码下面我们逐一详解这三种模式。模式一本机 ShellLocalFilesystemSpec— 自己的档案室生活类比你在自家书房里放了一个文件柜。你想存什么就存什么想在柜子上贴便签、想用订书机装订文件都可以——这是你自己的地盘。但问题是如果家里来客人了他们也能翻你的柜子如果房子着火了文件就没了。技术细节// 模式 1本机 shell默认就是这种HarnessAgentagentHarnessAgent.builder().name(local)// Agent 的名字.model(model)// 使用的大模型.workspace(workspace)// 工作区目录.filesystem(newLocalFilesystemSpec().executeTimeoutSeconds(120))// Shell 命令超时时间.build();工作原理LocalFilesystemWithShell是实际创建的类根目录就是你的workspace目录执行 Shell 命令时直接在宿主机上跑sh -c 你的命令所有文件操作都是普通的 Java 文件 APIFile、Files等适用场景你在开发测试阶段想快速跑起来你信任 Agent 执行的代码不会恶意删除文件你只需要单机部署不需要多机器共享数据安全提醒因为能直接在宿主机执行 Shell如果 Agent 被诱导执行rm -rf /后果很严重生产环境要谨慎使用。模式二复合 共享存储RemoteFilesystemSpec— 云端档案服务生活类比你的公司在多个城市有分部每个分部都需要查看同一份客户档案。于是你租了一个专业的档案托管服务——所有档案存在云端每个分部都能访问。但是这个托管服务只负责存取档案不能在里面做实验或开派对。技术细节// 模式 2共享存储无宿主 shellHarnessAgentagentHarnessAgent.builder().name(store).model(model).workspace(workspace).filesystem(newRemoteFilesystemSpec(redisStore)// Redis 作为后端存储.isolationScope(IsolationScope.USER))// 按用户隔离.build();工作原理创建的是CompositeFilesystem——一个组合型文件系统默认路径未匹配的路径→ 本地文件系统无 Shell共享路径如MEMORY.md、memory/、sessions/→ 远程存储Redis、数据库等通过IsolationScope控制不同用户/会话的数据隔离共享路径是什么默认情况下以下路径会存到远程存储MEMORY.md # Agent 的长期记忆 memory/ # 记忆系统的详细记录 agents/agentId/sessions/ # 会话日志你可以用addSharedPrefix(shared/)添加更多共享路径。为什么默认没有 Shell设计目标是跨节点一致的长记忆与日志。如果允许在宿主机执行 Shell多个节点可能会产生冲突。如果你需要 Shell 能力请选择模式 1 或模式 3。适用场景生产环境需要多副本共享数据Agent 不需要执行 Shell 命令需要按用户/会话隔离数据多租户模式三沙箱SandboxFilesystemSpec— 安全实验室生活类比你有一间化学实验室里面放着一个防爆玻璃做的安全箱。你可以在里面做危险的化学实验——就算发生爆炸也只损坏箱子里的东西主实验室完好无损。而且这个安全箱有专门的通风管道和排污系统与主楼隔离。技术细节// 模式 3沙箱以 Docker 为例HarnessAgentagentHarnessAgent.builder().name(sandbox).model(model).workspace(workspace).filesystem(dockerFilesystemSpec)// 继承自 SandboxFilesystemSpec.build();工作原理创建的是SandboxBackedFilesystem文件操作和 Shell 执行都在沙箱容器内进行通过SandboxClient与沙箱通信沙箱有独立的生命周期管理创建、快照、恢复、销毁沙箱的生命周期┌─────────────────────────────────────────┐ │ 单次 Agent 调用 │ ├─────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ acquire │ → │ execute │ → │ │ │ (获取) │ │ (执行) │ ... │ │ └─────────┘ └─────────┘ │ │ ↓ ↓ │ │ ┌─────────┐ ┌─────────┐ │ │ │ persist │ ← │ release │ │ │ │ (持久化) │ │ (释放) │ │ │ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────────┘适用场景需要执行不受信任的代码需要强隔离的多租户环境需要可恢复的执行状态比如暂停后继续需要执行快照和回滚详细配置参见 11-sandbox.md。NamespaceFactory — 多租户的分房间管理生活类比想象一个大型写字楼里面有很多公司租办公室。每家公司都有自己的区域——A 公司在 101 室B 公司在 102 室。虽然是同一栋楼、同一个物业管理但 A 公司不能进 B 公司的办公室。NamespaceFactory就是那个前台接待员——根据你是哪家公司用户 ID把你带到正确的房间。技术细节FunctionalInterfacepublicinterfaceNamespaceFactory{ListStringgetNamespace();// 返回路径前缀列表}每次文件操作时调用返回当前请求应该使用的路径前缀。例如隔离级别路径前缀示例说明GLOBAL[]全局共享无前缀AGENT[agents, myAgent]按 Agent 隔离USER[users, alice]按用户隔离SESSION[sessions, sess-123]按会话隔离实际应用// 从 RuntimeContext 获取当前用户 ID用于命名空间AtomicReferenceStringcurrentUserIdnewAtomicReference();HarnessAgentagentHarnessAgent.builder().namespaceFactory(()-{StringuserIdcurrentUserId.get();returnList.of(users,userId);// 每个用户有自己的目录}).build();// 这样用户 alice 的文件存在 /users/alice/ 下// 用户 bob 的文件存在 /users/bob/ 下// 互不干扰为什么重要如果没有命名空间隔离用户 A 可能读取用户 B 的私密记忆不同会话的日志可能混在一起多个 Agent 实例可能互相覆盖配置AbstractSandboxFilesystem — 带执行能力的文件系统生活类比普通的文件柜只能存取文件。但如果你的文件柜里还有一个小机器人能帮你执行指令呢AbstractSandboxFilesystem就是这样的带机器人的文件柜——不仅能存取文件还能在里面执行命令。技术细节publicinterfaceAbstractSandboxFilesystemextendsAbstractFilesystem{// 沙箱的唯一标识Stringid();// 在沙箱内执行命令ExecuteResultexecute(Stringcmd,Durationtimeout);}继承关系只有实现了AbstractSandboxFilesystem的文件系统才会注册ShellExecuteToolShell 执行工具。这意味着LocalFilesystemWithShell— 有 Shell本地执行SandboxBackedFilesystem— 有 Shell沙箱内执行CompositeFilesystem—没有 Shell只是路由器RemoteFilesystem—没有 Shell只是存储关键代码解读文件系统配置的选择逻辑// HarnessAgent.Builder 内部的选择逻辑简化版publicHarnessAgentbuild(){AbstractFilesystemfs;if(remoteFilesystemSpec!null){// 模式 2复合 共享存储fsremoteFilesystemSpec.toFilesystem();// 注意CompositeFilesystem 不实现 AbstractSandboxFilesystem// 所以不会注册 ShellExecuteTool}elseif(sandboxFilesystemSpec!null){// 模式 3沙箱fssandboxFilesystemSpec.toFilesystem();// SandboxBackedFilesystem 实现了 AbstractSandboxFilesystem// 所以会注册 ShellExecuteTool}else{// 模式 1本机 shell默认fsnewLocalFilesystemWithShell(workspace,executeTimeout);// LocalFilesystemWithShell 实现了 AbstractSandboxFilesystem// 所以会注册 ShellExecuteTool}// 如果实现了 AbstractSandboxFilesystem注册 ShellExecuteToolif(fsinstanceofAbstractSandboxFilesystem){toolkit.register(newShellExecuteTool((AbstractSandboxFilesystem)fs));}// 注册基础文件工具toolkit.register(newFilesystemTool(fs));}逐行注释第 5 行检查是否配置了RemoteFilesystemSpec共享存储模式第 7 行创建CompositeFilesystem实例第 9-10 行关键点CompositeFilesystem不继承AbstractSandboxFilesystem所以不能执行 Shell第 12-16 行检查是否配置了沙箱模式创建沙箱文件系统第 18-22 行默认情况创建本机文件系统可以执行 Shell第 25-27 行判断是否注册 Shell 工具的关键逻辑——只有实现了AbstractSandboxFilesystem才注册第 30 行无论哪种模式都注册基础的文件操作工具CompositeFilesystem 的路由逻辑// CompositeFilesystem 的读写路由简化版publicclassCompositeFilesystemimplementsAbstractFilesystem{privatefinalAbstractFilesystemdefaultBackend;// 默认后端本地privatefinalMapString,AbstractFilesystemroutes;// 前缀 - 后端的映射OverridepublicStringread(Stringpath){// 遍历所有前缀找最长匹配StringmatchedPrefixfindLongestMatchPrefix(path);if(matchedPrefix!null){// 找到匹配的前缀用对应的远程后端AbstractFilesystembackendroutes.get(matchedPrefix);returnbackend.read(stripPrefix(path,matchedPrefix));}else{// 没匹配到用默认的本地后端returndefaultBackend.read(path);}}privateStringfindLongestMatchPrefix(Stringpath){StringlongestMatchnull;for(Stringprefix:routes.keySet()){if(path.startsWith(prefix)){if(longestMatchnull||prefix.length()longestMatch.length()){longestMatchprefix;}}}returnlongestMatch;}}逐行注释第 5 行默认后端通常是LocalFilesystem无 Shell第 6 行路由表存储前缀 - 后端的映射第 10 行最长前缀匹配——如果有memory/和memory/important/两个前缀memory/important/file.txt会匹配后者第 13-15 行找到匹配后去掉前缀再传给后端。例如memory/diary.txt匹配memory/前缀后传给远程后端的路径是diary.txt第 17-19 行没匹配到就用默认后端WorkspaceIndex 索引加速// RemoteFilesystem 使用本地索引加速查询publicclassRemoteFilesystemimplementsAbstractFilesystem{privatefinalBaseStorestore;// 远程存储后端privatefinalWorkspaceIndexindex;// 本地 SQLite 索引OverridepublicListStringls(Stringpath){// 先查本地索引快ListStringfromIndexindex.list(path);if(!fromIndex.isEmpty()){returnfromIndex;}// 索引没命中回退到远程存储扫描慢但完整returnstore.listAll(path);}OverridepublicListStringgrep(Stringpattern,Stringpath){// 先用索引找候选文件ListStringcandidatesindex.findCandidates(path);if(candidates.isEmpty()){// 索引返回空可能是还没有索引到// 回退到全量扫描确保不遗漏其他节点写入的内容returnstore.grepAll(pattern,path);}// 在候选文件中搜索returnsearchInCandidates(pattern,candidates);}}逐行注释第 5-6 行索引是可选的性能优化存储在本地 SQLite 中第 10-12 行先查本地索引速度快第 15-16 行索引没命中时回退到远程扫描。这是尽力而为的策略——索引可能不包含其他节点新写入的内容但全量扫描保证不遗漏第 20-30 行grep同样的策略——先索引后回退整体流程图文件系统类层次结构┌─────────────────────┐ │ AbstractFilesystem │ ← 文件操作的统一接口 │ ───────────────────│ │ ls/read/write/edit │ │ grep/glob/upload │ │ download │ └─────────┬───────────┘ │ ┌───────────────────────┼───────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ LocalFilesystem │ │ RemoteFilesystem│ │CompositeFilesystem│ │ (纯本地无Shell)│ │ (KV存储后端) │ │ (路由器组合多个) │ └────────┬────────┘ └─────────────────┘ └─────────────────┘ │ │ 继承 ▼ ┌─────────────────────────┐ │LocalFilesystemWithShell │ ← 本地 Shell 执行 │ implements │ │ AbstractSandboxFilesystem│ └─────────────────────────┘ ┌─────────────────────┐ │AbstractSandboxFilesystem│ ← 带执行能力的接口 │ ───────────────────│ │ id() │ │ execute() │ └─────────┬───────────┘ │ ┌───────────────────────┼───────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │LocalFilesystemWithShell│ │BaseSandboxFilesystem│ │SandboxBackedFilesystem│ │ (本地执行) │ │ (远程Unix基类) │ │ (沙箱代理) │ └─────────────────────┘ └─────────────────┘ └─────────────────────┘简单解读AbstractFilesystem是最顶层的接口定义了所有文件系统都能做的事读、写、列目录、搜索等。AbstractSandboxFilesystem扩展了上层接口增加了执行命令的能力。只有继承它才能注册ShellExecuteTool。LocalFilesystem是最简单的实现——就是操作本地磁盘不能执行命令。LocalFilesystemWithShell在本地文件系统基础上增加了执行 Shell 命令的能力。RemoteFilesystem把文件存到远程 KV 存储如 Redis适合多实例共享。CompositeFilesystem是个路由器——根据文件路径前缀把请求转发到不同的后端。SandboxBackedFilesystem把所有操作都转发到沙箱容器内执行。三种模式的工作流程┌─────────────────────────────────────────────────────────────────┐ │ Agent 调用文件操作 │ └─────────────────────────────────┬───────────────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 选择哪种文件系统模式 │ └─────────────┬───────────────┘ │ ┌─────────────────────────┼─────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ 模式 1本机 │ │ 模式 2复合Store │ │ 模式 3沙箱 │ │ │ │ │ │ │ │ 本地磁盘 │ │ 路由到不同后端 │ │ Docker/容器 │ │ sh -c 执行 │ │ 共享路径→Redis │ │ 隔离执行 │ │ │ │ 本地路径→本地 │ │ │ └───────┬───────┘ └─────────┬─────────┘ └─────────┬─────────┘ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ workspace/ │ │ MEMORY.md → Redis │ │ 沙箱容器内 │ │ ├── MEMORY.md │ │ memory/ → Redis │ │ 独立的文件系统 │ │ ├── memory/ │ │ agents/ → Redis │ │ 独立的进程空间 │ │ └── sessions/ │ │ 其他 → 本地 │ │ 可快照/恢复 │ └───────────────┘ └───────────────────┘ └───────────────────┘与其他模块的关系⬅️ 上一篇05-memory | 回到目录 | ➡️ 下一篇07-tool学习要点必须记住的三个概念抽象接口的价值AbstractFilesystem让 Agent 代码与存储实现解耦。今天存本地明天存 Redis后天存 S3——Agent 代码不需要改。三种模式的本质区别本机模式 文件在本地 Shell 在本地执行复合模式 文件在远程 无 Shell沙箱模式 文件在沙箱 Shell 在沙箱执行命名空间隔离多租户场景下必须用NamespaceFactory隔离不同用户的数据。常见错误错误现象解决方案在复合模式下调用 ShellShellExecuteTool不存在改用本机模式或沙箱模式忘记配置命名空间用户 A 能看到用户 B 的数据配置NamespaceFactory在本机模式执行危险命令误删宿主机文件使用沙箱模式隔离沙箱忘记持久化容器重启后数据丢失配置SandboxStateStore选择指南┌─────────────────────┐ │ 需要执行 Shell 吗 │ └─────────┬───────────┘ │ ┌───────────────┴───────────────┐ │ 否 │ 是 ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ 需要多实例共享 │ │ 信任执行的代码 │ └────────┬────────┘ └────────┬────────┘ │ │ ┌───────┴───────┐ ┌───────┴───────┐ │ 否 │ 是 │ 是 │ 否 ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────────┐ ┌─────────┐ ┌─────────┐ │本机模式 │ │复合Store模式│ │本机模式 │ │沙箱模式 │ │(默认) │ │ │ │ │ │ │ └─────────┘ └─────────────┘ └─────────┘ └─────────┘进一步阅读沙箱详解11-sandbox.md——深入了解沙箱的生命周期管理工具详解07-tool.md——了解FilesystemTool和ShellExecuteTool的使用工作区详解03-workspace.md——了解WorkspaceManager如何使用文件系统
【AgentScope】6.文件系统(Filesystem)详解
发布时间:2026/6/9 11:15:05
文件系统Filesystem详解一句话概括文件系统是 Agent 的文件柜——它决定了 Agent 的文件存在哪里是本机磁盘、远程存储还是隔离的沙箱环境。你能学到什么为什么需要抽象文件系统直接用 Java 的 File API 不行吗三种文件系统模式的区别本机模式、共享存储模式、沙箱模式什么是多租户隔离为什么要用NamespaceFactory类层次结构AbstractFilesystem家族成员之间的关系如何根据你的场景选择正确的文件系统配置前置知识需了解 Java 接口与继承的基本概念以及 05-memory.md 中 Agent 的双层记忆系统——记忆最终要落到文件系统上。核心概念AbstractFilesystem — 文件柜的统一说明书生活类比想象你去图书馆借书。图书馆有各种藏书的地方普通书架、珍本室、电子资源库。但作为读者你只需要知道我要借这本书图书馆员会帮你从正确的地方取。AbstractFilesystem就是那个统一的借书接口——不管文件实际存在哪里Agent 都用同样的方式操作。技术解释AbstractFilesystem是一个接口定义了所有文件系统都必须支持的基本操作publicinterfaceAbstractFilesystem{// 列出目录内容就像在文件管理器里打开文件夹ListStringls(Stringpath);// 读取文件内容就像用记事本打开文件Stringread(Stringpath);// 写入文件内容就像保存记事本内容voidwrite(Stringpath,Stringcontent);// 编辑文件就像在 Word 里修改文档voidedit(Stringpath,StringoldText,StringnewText);// 在文件中搜索就像 CtrlF 查找ListStringgrep(Stringpattern,Stringpath);// 用通配符找文件就像搜索 *.txtListStringglob(Stringpattern);// 上传文件voiduploadFiles(MapString,Stringfiles);// 下载文件MapString,StringdownloadFiles(ListStringpaths);}为什么需要抽象如果直接用 Java 的FileAPIAgent 就只能操作本机文件。但实际场景中我们可能需要把 Agent 的记忆存到云端多台机器共享在沙箱里执行不受信任的代码根据用户 ID 隔离数据多租户抽象接口让这些需求变成换个实现类的事而不是重写整个 Agent。三种声明式模式 — 选哪种文件柜生活类比假设你要开一家公司需要一个地方存档案。你有三种选择本机模式在公司自己的档案室里放铁皮柜——方便、省钱但只有一个房间不能多地点共享。共享存储模式租用云端档案服务——多个分公司都能访问同一份档案但贵一些而且不能在档案室里做实验不能执行脚本。沙箱模式租一个独立的实验室——里面有档案柜还能做化学实验执行脚本就算爆炸了也不会影响主楼。技术对比表模式配置方法能否执行 Shell适用场景模式 1本机 shellfilesystem(LocalFilesystemSpec)或默认能宿主机上单机部署、测试环境、受信任的 Agent模式 2复合 Storefilesystem(RemoteFilesystemSpec)不能多副本共享记忆、生产环境、不需要执行脚本模式 3沙箱filesystem(SandboxFilesystemSpec)能沙箱内需要隔离执行、处理不受信任的代码下面我们逐一详解这三种模式。模式一本机 ShellLocalFilesystemSpec— 自己的档案室生活类比你在自家书房里放了一个文件柜。你想存什么就存什么想在柜子上贴便签、想用订书机装订文件都可以——这是你自己的地盘。但问题是如果家里来客人了他们也能翻你的柜子如果房子着火了文件就没了。技术细节// 模式 1本机 shell默认就是这种HarnessAgentagentHarnessAgent.builder().name(local)// Agent 的名字.model(model)// 使用的大模型.workspace(workspace)// 工作区目录.filesystem(newLocalFilesystemSpec().executeTimeoutSeconds(120))// Shell 命令超时时间.build();工作原理LocalFilesystemWithShell是实际创建的类根目录就是你的workspace目录执行 Shell 命令时直接在宿主机上跑sh -c 你的命令所有文件操作都是普通的 Java 文件 APIFile、Files等适用场景你在开发测试阶段想快速跑起来你信任 Agent 执行的代码不会恶意删除文件你只需要单机部署不需要多机器共享数据安全提醒因为能直接在宿主机执行 Shell如果 Agent 被诱导执行rm -rf /后果很严重生产环境要谨慎使用。模式二复合 共享存储RemoteFilesystemSpec— 云端档案服务生活类比你的公司在多个城市有分部每个分部都需要查看同一份客户档案。于是你租了一个专业的档案托管服务——所有档案存在云端每个分部都能访问。但是这个托管服务只负责存取档案不能在里面做实验或开派对。技术细节// 模式 2共享存储无宿主 shellHarnessAgentagentHarnessAgent.builder().name(store).model(model).workspace(workspace).filesystem(newRemoteFilesystemSpec(redisStore)// Redis 作为后端存储.isolationScope(IsolationScope.USER))// 按用户隔离.build();工作原理创建的是CompositeFilesystem——一个组合型文件系统默认路径未匹配的路径→ 本地文件系统无 Shell共享路径如MEMORY.md、memory/、sessions/→ 远程存储Redis、数据库等通过IsolationScope控制不同用户/会话的数据隔离共享路径是什么默认情况下以下路径会存到远程存储MEMORY.md # Agent 的长期记忆 memory/ # 记忆系统的详细记录 agents/agentId/sessions/ # 会话日志你可以用addSharedPrefix(shared/)添加更多共享路径。为什么默认没有 Shell设计目标是跨节点一致的长记忆与日志。如果允许在宿主机执行 Shell多个节点可能会产生冲突。如果你需要 Shell 能力请选择模式 1 或模式 3。适用场景生产环境需要多副本共享数据Agent 不需要执行 Shell 命令需要按用户/会话隔离数据多租户模式三沙箱SandboxFilesystemSpec— 安全实验室生活类比你有一间化学实验室里面放着一个防爆玻璃做的安全箱。你可以在里面做危险的化学实验——就算发生爆炸也只损坏箱子里的东西主实验室完好无损。而且这个安全箱有专门的通风管道和排污系统与主楼隔离。技术细节// 模式 3沙箱以 Docker 为例HarnessAgentagentHarnessAgent.builder().name(sandbox).model(model).workspace(workspace).filesystem(dockerFilesystemSpec)// 继承自 SandboxFilesystemSpec.build();工作原理创建的是SandboxBackedFilesystem文件操作和 Shell 执行都在沙箱容器内进行通过SandboxClient与沙箱通信沙箱有独立的生命周期管理创建、快照、恢复、销毁沙箱的生命周期┌─────────────────────────────────────────┐ │ 单次 Agent 调用 │ ├─────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ acquire │ → │ execute │ → │ │ │ (获取) │ │ (执行) │ ... │ │ └─────────┘ └─────────┘ │ │ ↓ ↓ │ │ ┌─────────┐ ┌─────────┐ │ │ │ persist │ ← │ release │ │ │ │ (持久化) │ │ (释放) │ │ │ └─────────┘ └─────────┘ │ │ │ └─────────────────────────────────────────┘适用场景需要执行不受信任的代码需要强隔离的多租户环境需要可恢复的执行状态比如暂停后继续需要执行快照和回滚详细配置参见 11-sandbox.md。NamespaceFactory — 多租户的分房间管理生活类比想象一个大型写字楼里面有很多公司租办公室。每家公司都有自己的区域——A 公司在 101 室B 公司在 102 室。虽然是同一栋楼、同一个物业管理但 A 公司不能进 B 公司的办公室。NamespaceFactory就是那个前台接待员——根据你是哪家公司用户 ID把你带到正确的房间。技术细节FunctionalInterfacepublicinterfaceNamespaceFactory{ListStringgetNamespace();// 返回路径前缀列表}每次文件操作时调用返回当前请求应该使用的路径前缀。例如隔离级别路径前缀示例说明GLOBAL[]全局共享无前缀AGENT[agents, myAgent]按 Agent 隔离USER[users, alice]按用户隔离SESSION[sessions, sess-123]按会话隔离实际应用// 从 RuntimeContext 获取当前用户 ID用于命名空间AtomicReferenceStringcurrentUserIdnewAtomicReference();HarnessAgentagentHarnessAgent.builder().namespaceFactory(()-{StringuserIdcurrentUserId.get();returnList.of(users,userId);// 每个用户有自己的目录}).build();// 这样用户 alice 的文件存在 /users/alice/ 下// 用户 bob 的文件存在 /users/bob/ 下// 互不干扰为什么重要如果没有命名空间隔离用户 A 可能读取用户 B 的私密记忆不同会话的日志可能混在一起多个 Agent 实例可能互相覆盖配置AbstractSandboxFilesystem — 带执行能力的文件系统生活类比普通的文件柜只能存取文件。但如果你的文件柜里还有一个小机器人能帮你执行指令呢AbstractSandboxFilesystem就是这样的带机器人的文件柜——不仅能存取文件还能在里面执行命令。技术细节publicinterfaceAbstractSandboxFilesystemextendsAbstractFilesystem{// 沙箱的唯一标识Stringid();// 在沙箱内执行命令ExecuteResultexecute(Stringcmd,Durationtimeout);}继承关系只有实现了AbstractSandboxFilesystem的文件系统才会注册ShellExecuteToolShell 执行工具。这意味着LocalFilesystemWithShell— 有 Shell本地执行SandboxBackedFilesystem— 有 Shell沙箱内执行CompositeFilesystem—没有 Shell只是路由器RemoteFilesystem—没有 Shell只是存储关键代码解读文件系统配置的选择逻辑// HarnessAgent.Builder 内部的选择逻辑简化版publicHarnessAgentbuild(){AbstractFilesystemfs;if(remoteFilesystemSpec!null){// 模式 2复合 共享存储fsremoteFilesystemSpec.toFilesystem();// 注意CompositeFilesystem 不实现 AbstractSandboxFilesystem// 所以不会注册 ShellExecuteTool}elseif(sandboxFilesystemSpec!null){// 模式 3沙箱fssandboxFilesystemSpec.toFilesystem();// SandboxBackedFilesystem 实现了 AbstractSandboxFilesystem// 所以会注册 ShellExecuteTool}else{// 模式 1本机 shell默认fsnewLocalFilesystemWithShell(workspace,executeTimeout);// LocalFilesystemWithShell 实现了 AbstractSandboxFilesystem// 所以会注册 ShellExecuteTool}// 如果实现了 AbstractSandboxFilesystem注册 ShellExecuteToolif(fsinstanceofAbstractSandboxFilesystem){toolkit.register(newShellExecuteTool((AbstractSandboxFilesystem)fs));}// 注册基础文件工具toolkit.register(newFilesystemTool(fs));}逐行注释第 5 行检查是否配置了RemoteFilesystemSpec共享存储模式第 7 行创建CompositeFilesystem实例第 9-10 行关键点CompositeFilesystem不继承AbstractSandboxFilesystem所以不能执行 Shell第 12-16 行检查是否配置了沙箱模式创建沙箱文件系统第 18-22 行默认情况创建本机文件系统可以执行 Shell第 25-27 行判断是否注册 Shell 工具的关键逻辑——只有实现了AbstractSandboxFilesystem才注册第 30 行无论哪种模式都注册基础的文件操作工具CompositeFilesystem 的路由逻辑// CompositeFilesystem 的读写路由简化版publicclassCompositeFilesystemimplementsAbstractFilesystem{privatefinalAbstractFilesystemdefaultBackend;// 默认后端本地privatefinalMapString,AbstractFilesystemroutes;// 前缀 - 后端的映射OverridepublicStringread(Stringpath){// 遍历所有前缀找最长匹配StringmatchedPrefixfindLongestMatchPrefix(path);if(matchedPrefix!null){// 找到匹配的前缀用对应的远程后端AbstractFilesystembackendroutes.get(matchedPrefix);returnbackend.read(stripPrefix(path,matchedPrefix));}else{// 没匹配到用默认的本地后端returndefaultBackend.read(path);}}privateStringfindLongestMatchPrefix(Stringpath){StringlongestMatchnull;for(Stringprefix:routes.keySet()){if(path.startsWith(prefix)){if(longestMatchnull||prefix.length()longestMatch.length()){longestMatchprefix;}}}returnlongestMatch;}}逐行注释第 5 行默认后端通常是LocalFilesystem无 Shell第 6 行路由表存储前缀 - 后端的映射第 10 行最长前缀匹配——如果有memory/和memory/important/两个前缀memory/important/file.txt会匹配后者第 13-15 行找到匹配后去掉前缀再传给后端。例如memory/diary.txt匹配memory/前缀后传给远程后端的路径是diary.txt第 17-19 行没匹配到就用默认后端WorkspaceIndex 索引加速// RemoteFilesystem 使用本地索引加速查询publicclassRemoteFilesystemimplementsAbstractFilesystem{privatefinalBaseStorestore;// 远程存储后端privatefinalWorkspaceIndexindex;// 本地 SQLite 索引OverridepublicListStringls(Stringpath){// 先查本地索引快ListStringfromIndexindex.list(path);if(!fromIndex.isEmpty()){returnfromIndex;}// 索引没命中回退到远程存储扫描慢但完整returnstore.listAll(path);}OverridepublicListStringgrep(Stringpattern,Stringpath){// 先用索引找候选文件ListStringcandidatesindex.findCandidates(path);if(candidates.isEmpty()){// 索引返回空可能是还没有索引到// 回退到全量扫描确保不遗漏其他节点写入的内容returnstore.grepAll(pattern,path);}// 在候选文件中搜索returnsearchInCandidates(pattern,candidates);}}逐行注释第 5-6 行索引是可选的性能优化存储在本地 SQLite 中第 10-12 行先查本地索引速度快第 15-16 行索引没命中时回退到远程扫描。这是尽力而为的策略——索引可能不包含其他节点新写入的内容但全量扫描保证不遗漏第 20-30 行grep同样的策略——先索引后回退整体流程图文件系统类层次结构┌─────────────────────┐ │ AbstractFilesystem │ ← 文件操作的统一接口 │ ───────────────────│ │ ls/read/write/edit │ │ grep/glob/upload │ │ download │ └─────────┬───────────┘ │ ┌───────────────────────┼───────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ LocalFilesystem │ │ RemoteFilesystem│ │CompositeFilesystem│ │ (纯本地无Shell)│ │ (KV存储后端) │ │ (路由器组合多个) │ └────────┬────────┘ └─────────────────┘ └─────────────────┘ │ │ 继承 ▼ ┌─────────────────────────┐ │LocalFilesystemWithShell │ ← 本地 Shell 执行 │ implements │ │ AbstractSandboxFilesystem│ └─────────────────────────┘ ┌─────────────────────┐ │AbstractSandboxFilesystem│ ← 带执行能力的接口 │ ───────────────────│ │ id() │ │ execute() │ └─────────┬───────────┘ │ ┌───────────────────────┼───────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │LocalFilesystemWithShell│ │BaseSandboxFilesystem│ │SandboxBackedFilesystem│ │ (本地执行) │ │ (远程Unix基类) │ │ (沙箱代理) │ └─────────────────────┘ └─────────────────┘ └─────────────────────┘简单解读AbstractFilesystem是最顶层的接口定义了所有文件系统都能做的事读、写、列目录、搜索等。AbstractSandboxFilesystem扩展了上层接口增加了执行命令的能力。只有继承它才能注册ShellExecuteTool。LocalFilesystem是最简单的实现——就是操作本地磁盘不能执行命令。LocalFilesystemWithShell在本地文件系统基础上增加了执行 Shell 命令的能力。RemoteFilesystem把文件存到远程 KV 存储如 Redis适合多实例共享。CompositeFilesystem是个路由器——根据文件路径前缀把请求转发到不同的后端。SandboxBackedFilesystem把所有操作都转发到沙箱容器内执行。三种模式的工作流程┌─────────────────────────────────────────────────────────────────┐ │ Agent 调用文件操作 │ └─────────────────────────────────┬───────────────────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 选择哪种文件系统模式 │ └─────────────┬───────────────┘ │ ┌─────────────────────────┼─────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ 模式 1本机 │ │ 模式 2复合Store │ │ 模式 3沙箱 │ │ │ │ │ │ │ │ 本地磁盘 │ │ 路由到不同后端 │ │ Docker/容器 │ │ sh -c 执行 │ │ 共享路径→Redis │ │ 隔离执行 │ │ │ │ 本地路径→本地 │ │ │ └───────┬───────┘ └─────────┬─────────┘ └─────────┬─────────┘ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────────┐ ┌───────────────────┐ │ workspace/ │ │ MEMORY.md → Redis │ │ 沙箱容器内 │ │ ├── MEMORY.md │ │ memory/ → Redis │ │ 独立的文件系统 │ │ ├── memory/ │ │ agents/ → Redis │ │ 独立的进程空间 │ │ └── sessions/ │ │ 其他 → 本地 │ │ 可快照/恢复 │ └───────────────┘ └───────────────────┘ └───────────────────┘与其他模块的关系⬅️ 上一篇05-memory | 回到目录 | ➡️ 下一篇07-tool学习要点必须记住的三个概念抽象接口的价值AbstractFilesystem让 Agent 代码与存储实现解耦。今天存本地明天存 Redis后天存 S3——Agent 代码不需要改。三种模式的本质区别本机模式 文件在本地 Shell 在本地执行复合模式 文件在远程 无 Shell沙箱模式 文件在沙箱 Shell 在沙箱执行命名空间隔离多租户场景下必须用NamespaceFactory隔离不同用户的数据。常见错误错误现象解决方案在复合模式下调用 ShellShellExecuteTool不存在改用本机模式或沙箱模式忘记配置命名空间用户 A 能看到用户 B 的数据配置NamespaceFactory在本机模式执行危险命令误删宿主机文件使用沙箱模式隔离沙箱忘记持久化容器重启后数据丢失配置SandboxStateStore选择指南┌─────────────────────┐ │ 需要执行 Shell 吗 │ └─────────┬───────────┘ │ ┌───────────────┴───────────────┐ │ 否 │ 是 ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ 需要多实例共享 │ │ 信任执行的代码 │ └────────┬────────┘ └────────┬────────┘ │ │ ┌───────┴───────┐ ┌───────┴───────┐ │ 否 │ 是 │ 是 │ 否 ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────────┐ ┌─────────┐ ┌─────────┐ │本机模式 │ │复合Store模式│ │本机模式 │ │沙箱模式 │ │(默认) │ │ │ │ │ │ │ └─────────┘ └─────────────┘ └─────────┘ └─────────┘进一步阅读沙箱详解11-sandbox.md——深入了解沙箱的生命周期管理工具详解07-tool.md——了解FilesystemTool和ShellExecuteTool的使用工作区详解03-workspace.md——了解WorkspaceManager如何使用文件系统