【AgentScope 2.0】05-文件系统(Filesystem)详解

文件系统(Filesystem)详解

版本基准 :本文档基于 AgentScope 2.0 GA(v2.0.0)编写。具体版本号以 Release Notes 为准。

一句话概括

文件系统是 Agent 的"住处选择器"------它决定了 Agent 的文件存在哪里、命令在哪里跑:是住快捷酒店(共享存储,只住不干活)、租公寓(沙箱,独立空间随意折腾)、还是自己家(本机,自由但没隔离)。

你能学到什么

  • 为什么需要"抽象文件系统"?直接用 Java 的 File API 不行吗?
  • 三种声明式模式的区别:RemoteFilesystemSpec(共享存储,无 shell)、DockerFilesystemSpec 等 SandboxFilesystemSpec(沙箱)、LocalFilesystemSpec(本机+shell,默认)
  • IsolationScope 四种隔离作用域(SESSION / USER / AGENT / GLOBAL)怎么选,各自的降级规则
  • 两层读取架构(filesystem-first + local fallback)的设计意图
  • LocalFsMode 三种路径解析策略(ROOTED / SANDBOXED / UNRESTRICTED)与 GA 的 ROOTED 前导 / 修复
  • abstractFilesystem(...) 自定义文件系统的逃生口
  • 如何根据你的场景选择正确的文件系统配置

前置知识

详见 README.md 前置知识部分。本篇额外需要:

  • 工作区布局 :了解 AGENTS.mdMEMORY.md 等文件的目录结构,详见 02-workspace
  • RuntimeContext :了解 sessionIduserId 的含义,详见 01-overview

核心概念

为什么需要抽象文件系统 ------ 就像"换办公室不用改工作习惯"

HarnessAgent 把 Agent 对工作区 的访问从"一定是本机磁盘"抽象成统一接口(AbstractFilesystem)。所有文件工具(read_file / write_file / edit_file / grep_files / glob_files / list_files)和可选的 execute(shell)都从这个抽象走。

这样做让你能在三种部署模式之间切换,而不改 Agent 代码

  • 本机 + shell ------ 单进程、本地、信任环境;
  • 共享存储 ------ 多副本 / 多 pod 共享同一份长期记忆;
  • 沙箱 ------ 文件与命令都在隔离容器里执行,跨调用恢复同一份工作区。

三种声明式模式 ------ 就像"住酒店 vs 租公寓 vs 自己家"

假设你刚到一个新城市工作,需要找个地方住。你有三种选择:

  1. 住快捷酒店(共享存储 / RemoteFilesystemSpec):你只是在这里睡觉,所有行李统一存在前台的大保险箱里。多个房客共用同一个保险箱(当然有编号区分),但你不能在房间里做饭、搞装修(没有 shell)。好处是换一家酒店,行李自动跟着走------你的东西存在云端,哪家分店都能取。

  2. 租一间公寓(沙箱 / DockerFilesystemSpec 等 SandboxFilesystemSpec):你有独立的空间,可以做饭、钉钉子、养宠物(有 shell)。就算你把厨房炸了,也只是自己这间的事,隔壁邻居完全不受影响。而且下出差回来,公寓会帮你保留原样------包括冰箱里没吃完的披萨。

  3. 住自己家(本机+shell / LocalFilesystemSpec):你就是房东,想干啥干啥。拆墙、挖地、装新电线都随你。但问题是:如果你不小心把水管弄爆了,整栋楼都得赔。而且你只能住这一套房子,没法"异地共享"。

技术对比表

模式 配置方法 提供 Shell? 适用场景
1 · 共享存储 filesystem(new RemoteFilesystemSpec(store)) 没有(故意设计) 多副本共享记忆,不需要执行脚本
2 · 沙箱 filesystem(new DockerFilesystemSpec()...) 或 K8s / Daytona / E2B / AgentRun 有(沙箱内执行) 隔离执行不可信代码、跨调用恢复状态
3 · 本机+shell(默认) filesystem(new LocalFilesystemSpec()...) 或不配 有(宿主 sh -c 单机开发、测试、受信任环境

注意:filesystem(...)abstractFilesystem(...) 互斥。后者是完全自管文件系统的逃生口,正常用法不需要。

下面逐一详解这三种模式。


模式一:共享存储(RemoteFilesystemSpec)------ "住酒店"

生活类比:你在快捷酒店开了一间房。房间本身没有厨房、没有工具箱------你只能睡觉和看书。但你有一个云保险箱(KV 存储),所有贵重物品都存在那里。你出差到了另一个城市的同品牌酒店,用同一个房卡(userId),保险箱里的东西一模一样。

技术细节 :传入一个 BaseStore(Redis / JDBC / 内存),框架按路径前缀把工作区文件路由到这个 KV 存储:

java 复制代码
// 最小配置(推荐通过 DistributedStore 一键配置 stateStore + baseStore)
DistributedStore store = RedisDistributedStore.fromJedis(jedis);

HarnessAgent agent = HarnessAgent.builder()
    .name("store")
    .model(model)
    .workspace(workspace)
    .distributedStore(store)                      // stateStore + baseStore 一键配置
    .filesystem(new RemoteFilesystemSpec()        // baseStore 由 store 自动注入
        .isolationScope(IsolationScope.USER))     // 按用户隔离
    .build();

所有配置项

方法 说明 默认值
isolationScope(IsolationScope) 命名空间隔离维度 USER
anonymousUserId(String) userId 为空时的兜底标识 "_default"
addSharedPrefix(String) 额外路由到 KV 的工作区相对路径前缀(如 "prompts/" / "configs/"
workspaceIndex(WorkspaceIndex) 加速远端 ls/glob/grep 的 SQLite 索引 不加索引,走全量扫描

内置路由规则:框架自动把以下路径路由到共享 KV,每个路径段各自独立命名空间,互不污染:

路径 KV 命名空间段
AGENTS.mdMEMORY.mdtools.json root
memory/ memory
skills/ skills
subagents/ subagents
knowledge/ knowledge
agents/<agentId>/sessions/ sessions
agents/<agentId>/tasks/ tasks

其余路径落到本地 LocalFilesystem(无 shell)。

BaseStore 可用实现

实现 说明 模块
RedisStore 基于 Jedis,适合低延迟高并发 agentscope-extensions-redis
JdbcStore 基于 JDBC,适合 MySQL / PostgreSQL / H2 agentscope-extensions-mysql
InMemoryStore 内存实现,适合测试 agentscope-harness

为什么故意不提供 Shell? 设计目标是跨节点一致的长记忆与日志。如果允许在宿主机执行 Shell,多个节点可能同时操作产生冲突。需要 Shell 能力请选模式 2 或模式 3。

适用场景

  • 生产环境,需要多副本共享数据
  • Agent 不需要执行 Shell 命令
  • 需要按用户/会话隔离数据(多租户)

模式二:沙箱(SandboxFilesystemSpec 系列)------ "租公寓"

生活类比:你租了一间独立公寓。公寓里有厨房、工具箱------想做什么实验都行。就算你把厨房弄得一团糟,也只影响自己这间房,隔壁邻居完全不知道。而且公寓有个神奇的功能:你出差前拍张照片(快照),回来后公寓会自动恢复成拍照时的样子,连冰箱里的披萨都在。

技术细节(以 Docker 为例):所有文件操作和 Shell 命令都在沙箱容器内执行,宿主完全不受影响:

java 复制代码
// 模式 2:沙箱(Docker)
HarnessAgent agent = HarnessAgent.builder()
    .name("sandbox")
    .model(model)
    .workspace(workspace)
    .filesystem(new DockerFilesystemSpec()
        .image("ubuntu:24.04")                      // 沙箱镜像
        .isolationScope(IsolationScope.SESSION)      // 按会话隔离
        .memorySizeBytes(512 * 1024 * 1024L)         // 512 MB 内存
        .cpuCount(2L)                                // 2 核
        .snapshotSpec(new LocalSnapshotSpec("/data/snapshots")))
    .build();

DockerFilesystemSpec 所有配置项:

方法 说明 默认值
image(String) Docker 镜像 必填
isolationScope(IsolationScope) 隔离维度 SESSION
memorySizeBytes(Long) 容器内存限制 Docker 默认
cpuCount(Long) CPU 限制 Docker 默认
network(String) Docker network Docker 默认
exposedPorts(int...) 暴露端口
environment(Map) 容器环境变量
workspaceRoot(String) 容器内工作区挂载点 /workspace
additionalRunArgs(String...) 额外的 docker run 参数
snapshotSpec(SandboxSnapshotSpec) 快照策略 NoopSnapshotSpec(不快照)
workspaceSpec(WorkspaceSpec) 工作区挂载规则 默认
executionGuard(SandboxExecutionGuard) AGENT/GLOBAL scope 下的并发串行化守卫
workspaceProjectionEnabled(boolean) 是否从宿主投影静态资产到沙箱 true
workspaceProjectionRoots(List) 投影的根路径列表 AGENTS.md, skills, subagents, knowledge, .skills-cache

工作原理

  • 所有文件操作和 Shell 命令都在沙箱容器内执行,宿主完全不受影响
  • 沙箱有独立的生命周期管理:创建、快照、恢复、销毁
  • 下次 call() 时连同 node_modulespip install 都能恢复回来
  • 框架启动时把工作区的"静态资产"(AGENTS.mdskills/subagents/knowledge/.skills-cache/)打成 tar,hydrate 进沙箱;按内容 SHA-256 做增量比对,没变的跳过

可选沙箱后端:Docker / Kubernetes / E2B / Daytona / AgentRun(阿里云)。

快照策略

实现 说明
NoopSnapshotSpec 不快照(默认)
LocalSnapshotSpec(Path) 快照存宿主本地磁盘
RedisSnapshotSpec 快照存 Redis
OssSnapshotSpec 快照存对象存储(阿里云 OSS)
RemoteSnapshotSpec 快照存 BaseStore

适用场景

  • 需要执行不受信任的代码
  • 需要强隔离的多租户环境
  • 需要可恢复的执行状态(暂停后继续)
  • 需要跨调用保留运行时环境

详细配置参见 07-sandbox.md


模式三:本机+shell(LocalFilesystemSpec)------ "自己家"

生活类比 :你住在自己买的房子里。你就是房东,想怎么改就怎么改。要拆墙?行。要挖地?行。想装什么软件、跑什么脚本都可以------但如果你不小心 rm -rf /,整台机器就废了。而且只有这一套房子,没有"异地共享"这回事。

技术细节 :什么都不写就是这个;工作区落到 ${cwd}/.agentscope/workspace/,shell 在宿主上跑 sh -c

java 复制代码
// 模式 3:本机 + shell(什么都不写就是这个)
HarnessAgent agent = HarnessAgent.builder()
    .name("local")
    .model(model)
    .workspace(workspace)
    // .filesystem(...) 不写 = 本机 + shell
    .build();

// 需要调整超时、环境变量、路径策略时
.filesystem(new LocalFilesystemSpec()
    .executeTimeoutSeconds(120)    // Shell 命令超时 120 秒
    .maxOutputBytes(100_000)       // 单条命令最大输出 100 KB
    .env("MY_VAR", "value")        // 注入环境变量
    .inheritEnv(false)             // 不继承宿主环境变量
    .mode(LocalFsMode.ROOTED)      // 路径解析策略
    .project(Paths.get("/my/project"))  // 项目根(shell 的 cwd + overlay 下层)
    .addRoot(Paths.get("/extra/dir")))  // 额外可访问目录

LocalFilesystemSpec 所有配置项:

方法 说明 默认值
executeTimeoutSeconds(int) 单条 shell 命令超时(秒) 120
maxOutputBytes(int) 单条命令最大捕获输出字节数 100,000
env(String, String) 添加 shell 环境变量
inheritEnv(boolean) 是否继承父进程环境 false
mode(LocalFsMode) 路径解析策略 ROOTED
project(Path) 项目根(overlay 下层 + shell cwd) System.getProperty("user.dir")
addRoot(Path) 额外允许访问的宿主目录
additionalRoots(Collection) 批量设置额外目录
projectWritable(boolean) 非元数据写入落到项目目录而非 workspace false

Overlay 文件系统 :本机模式实际产出的是一个 OverlayFilesystem------

  • 上层 (读写):LocalFilesystemWithShell,根在 workspace,提供 shell;
  • 下层 (只读):LocalFilesystem,根在 project

读取时先看 workspace,没有再退到 project(copy-on-write 语义)。shell 的 pwd 是 project 目录,所以 Agent 执行 ls 看到的是项目文件。

项目可写模式(projectWritable) :默认所有写入都落到 workspace------这对阅读/分析类场景足够,但如果 Agent 的核心任务是生成代码 (如写一个微服务),文件会全写到 .agentscope/workspace/ 而不是项目目录。开启 projectWritable(true) 后,框架按路径自动路由:

路径类型 写入位置 示例
工作区元数据 workspace MEMORY.mdmemory/agents/skills/knowledge/plans/subagents/tools.json
其他所有文件 项目目录 src/main/java/App.javapom.xmlREADME.md

读取行为不变------仍是 workspace 优先、project 兜底。

安全提醒:因为能直接在宿主机执行 Shell,如果 Agent 被诱导执行危险命令,后果很严重。生产环境请谨慎使用。

适用场景

  • 开发测试阶段,想快速跑起来
  • 信任 Agent 执行的代码(不会恶意操作)
  • 单机部署,不需要多机器共享数据

LocalFsMode 三种路径解析策略 ------ 就像"门禁卡的权限范围"

生活类比:小区门禁有三种规则。ROOTED(默认)= 卡只能开你自己家和小区公共区,去别人家直接被拦;SANDBOXED = 只能待在自己屋里,连走廊都不能去;UNRESTRICTED = 物业超级卡,哪都能进(危险,只给维修工用)。

模式 行为
ROOTED(默认) 绝对路径只允许 workspace + project + additionalRoots 范围内;.. 穿越被拒绝
SANDBOXED 所有路径强制锚定到 workspace 根,绝对路径和 .. 全部拒绝
UNRESTRICTED 绝对路径原样透传,不做限制。仅用于测试或完全信任的环境

GA 的 ROOTED 前导 / 修复

早期版本的 ROOTED 对"带前导 / 的绝对路径"处理不够友好------如果路径不在 allow-list 内,直接抛 SecurityException,导致 Agent 写出的 /src/App.java 这类本意是工作区内文件的路径被拒。

GA 修复后的行为LocalFilesystem.resolveRooted):按以下顺序判定------

  1. 路径在 allow-list(workspace / project / additionalRoots)内 → 直接放行
  2. 路径已存在但不 allow → 抛 SecurityException(防止窥探宿主其他文件)
  3. 路径以 / 开头但不在 allow-list 内且不存在剥掉前导 /,锚定到 workspace 根workspace.resolve(stripped)),再校验 .. 穿越
text 复制代码
Agent 写 "/src/App.java"
       │
       ▼
┌────────────────────────────────────────┐
│ /src/App.java 在 allow-list 内?       │
│   否(/src 不属于 project/workspace)  │
└──────────────────┬─────────────────────┘
                   │
                   ▼
┌────────────────────────────────────────┐
│ /src/App.java 在宿主存在?             │
│   否                                   │
└──────────────────┬─────────────────────┘
                   │
                   ▼
┌────────────────────────────────────────┐
│ 剥掉前导 /,锚定到 workspace 根        │
│ 实际写入 workspace/src/App.java        │
│ 再校验 .. 穿越 → 放行                  │
└────────────────────────────────────────┘

这条修复让 ROOTED 模式既守住"不允许越界访问宿主真实文件"的安全底线,又对 Agent 习惯性写出的"伪绝对路径"保持兼容,不再频繁误拒。


IsolationScope ------ 就像"大厦的分租规则"

生活类比:想象一栋写字楼,里面有四层分租规则。最严格的是"每家公司一间办公室"(SESSION),最宽松的是"整栋楼共享一个大开间"(GLOBAL)。你的公司选哪种规则,取决于你有多少个部门、要不要共享办公设备。

模式 1(共享存储)和模式 2(沙箱)都共用同一个 IsolationScope,决定谁和谁共享同一份状态

作用域 含义 命名空间键 典型场景
USER(默认) 同一 userId 跨 session 共享 agents/<agentId>/users/<userId>/... 同一用户的多个会话共享长期记忆
SESSION 每个 sessionId 独立 agents/<agentId>/sessions/<sessionId>/... 多用户 SaaS,每段对话各跑各的
AGENT 该 agent 的所有用户/会话共享 agents/<agentId>/shared/... 公共知识库型 agent
GLOBAL 全局共享一份 global/... 谨慎使用

各 scope 的降级规则(GA):

  • USER scope 下,如果 RuntimeContext.userId 为空 → 降级为 SESSION(按 sessionId 隔离)
  • SESSION scope 下,如果 sessionId 为空 → 跳过状态查找,创建全新环境
  • AGENT scope 的命名空间键由 agent name(build 时固定)决定,不会因缺少上下文字段而降级

沙箱模式下的并发行为IsolationScope 在沙箱模式下是顺序复用 的共享,不是实时实例共享。同一 scope key 的并发调用各自启动独立容器;每次调用结束时,最后写入的快照胜出。对 AGENT / GLOBAL 这种多用户共享 scope,如果需要串行化,使用 executionGuard(SandboxExecutionGuard) 做并发守卫。

实际应用 :要让 alice 在不同设备上的多个对话共享同一份长期记忆------选 USER,再把 userId="alice" 放进 RuntimeContext

java 复制代码
// 按用户隔离:alice 的所有会话共享同一份记忆
HarnessAgent agent = HarnessAgent.builder()
    .name("store")
    .model(model)
    .workspace(workspace)
    .filesystem(new RemoteFilesystemSpec(redisStore)
        .isolationScope(IsolationScope.USER))   // 同一用户跨会话共享
    .build();

// 调用时传入 userId
agent.call("帮我回忆一下上周的讨论",
    RuntimeContext.builder()
        .sessionId("sess-123")
        .userId("alice")       // alice 的命名空间
        .build());

两层读取架构 ------ 就像"查本地书架,再查中央图书馆"

生活类比:想象你在一家连锁书店工作。顾客要一本书时,你先看看自己店里有没有(快,但库存有限)。如果店里没有,你再打电话去中央仓库调货(慢,但什么都有)。这就是"两层读取"------先查近的,没有再查远的。

技术解释 :对所有被注入到 prompt 的关键文件(AGENTS.mdMEMORY.mdknowledge/KNOWLEDGE.mdadditionalContextFile),框架走两层读取:

复制代码
1. 问当前配置的 AbstractFilesystem:有没有这个文件?
   ├── 有 → 返回该内容(filesystem 层,"覆盖"层)
   └── 没有 → 走第 2 步

2. 读本地磁盘 workspace.resolve(path)

写入永远走第 1 层(filesystem 后端),从不直接落到本地磁盘。

这套设计的价值在共享存储模式最明显:

  • 第一个副本启动时,本地磁盘上有团队 git 同步过来的 AGENTS.md 模板,立刻可用
  • 之后任何节点对 AGENTS.md 的修改会写到共享 KV
  • 所有副本下一次 call() 就读到最新版本
  • 本地模板是 fallback(兜底),远端覆盖是事实

abstractFilesystem(...) ------ "自己盖房子"

生活类比 :前三种模式就像开发商盖好的精装房、公寓、酒店------拎包入住。但如果你是个建筑设计师,想要完全定制的房子?abstractFilesystem(...) 就是给你一块空地,你自己从地基开始盖。

java 复制代码
// 完全自管文件系统(与 filesystem(...) 互斥)
HarnessAgent agent = HarnessAgent.builder()
    .name("custom")
    .model(model)
    .workspace(workspace)
    .abstractFilesystem(myCustomFilesystem)  // 你自己实现的文件系统
    .build();

通常不需要------三种声明式模式覆盖了绝大多数场景。只有当你需要对接特殊的存储后端(如 S3、HDFS、自研存储)时才用得到。


关键代码解读

1. 三种模式的配置与选择

java 复制代码
// ===== 模式 1:共享存储(无 shell)=====
HarnessAgent agent1 = HarnessAgent.builder()
    .name("store")
    .model(model)
    .workspace(workspace)
    .distributedStore(store)                          // 一键配齐 stateStore + baseStore
    .filesystem(new RemoteFilesystemSpec()            // baseStore 由 store 自动注入
        .isolationScope(IsolationScope.USER))         // 按用户隔离
    .build();

// ===== 模式 2:沙箱(隔离执行)=====
HarnessAgent agent2 = HarnessAgent.builder()
    .name("sandbox")
    .model(model)
    .workspace(workspace)
    .filesystem(new DockerFilesystemSpec()            // Docker 沙箱
        .image("ubuntu:24.04")                        // 镜像
        .isolationScope(IsolationScope.SESSION)       // 按会话隔离
        .snapshotSpec(new LocalSnapshotSpec("/snapshots")))
    .build();

// ===== 模式 3:本机+shell(默认)=====
HarnessAgent agent3 = HarnessAgent.builder()
    .name("local")
    .model(model)
    .workspace(workspace)
    // .filesystem(...) 不写 = 默认本机+shell
    .build();

逐行注释

  • 模式 1RemoteFilesystemSpec + KV 存储后端,运行时数据存到远端。注意:这个模式不注册 Shell 工具,Agent 无法执行命令
  • 模式 2DockerFilesystemSpec,Agent 在容器内操作。isolationScope 决定沙箱实例的分配粒度
  • 模式 3:什么都不配就是默认。最简单,但生产环境需谨慎

2. LocalFilesystemSpec 的参数调优

java 复制代码
// 本机模式的详细配置
HarnessAgent agent = HarnessAgent.builder()
    .name("local")
    .model(model)
    .workspace(workspace)
    .filesystem(new LocalFilesystemSpec()
        .project(Paths.get("/Users/alice/my-project"))  // 项目根(shell cwd + overlay 下层)
        .addRoot(Paths.get("/Users/alice/.config"))     // 额外可访问目录
        .mode(LocalFsMode.ROOTED)                       // 路径策略(默认)
        .executeTimeoutSeconds(300)                     // Shell 命令最长跑 300 秒
        .maxOutputBytes(200_000)                        // 单条命令输出上限
        .env("MY_VAR", "value")                         // 给 Shell 环境注入变量
        .inheritEnv(true))                              // 继承宿主机的环境变量
    .build();

逐行注释

  • project :设定项目根,Agent 的 shell pwd 落在这里;读取静态资产(AGENTS.mdknowledge/)从 project 兜底
  • addRoot:把额外宿主目录加入 allow-list,让 ROOTED 模式允许 Agent 访问
  • modeROOTED(默认,兼顾安全与兼容)、SANDBOXED(最严)、UNRESTRICTED(不限)
  • executeTimeoutSeconds:防止 Shell 命令卡死,超时后自动终止进程
  • maxOutputBytes:限制单条命令输出,防止日志爆炸
  • env:按需注入环境变量,避免在宿主机上全局配置
  • inheritEnv :设为 false 可以隔离宿主环境,防止敏感信息(如 AWS_SECRET_KEY)泄露给 Agent

3. 代码生成型 Agent:开启 projectWritable

java 复制代码
// Agent 的核心任务是写代码到项目目录
HarnessAgent coder = HarnessAgent.builder()
    .name("coder")
    .model(model)
    .workspace(workspace)
    .filesystem(new LocalFilesystemSpec()
        .project(Paths.get("/Users/alice/my-project"))
        .projectWritable(true)      // 代码文件直接落到项目目录
        .inheritEnv(true))
    .build();

开启后,src/main/java/App.javapom.xml 这类非元数据写入路由到 project 目录;MEMORY.mdplans/ 等工作区元数据仍落 workspace。读取仍是 workspace 优先、project 兜底。

4. 多用户隔离的路径映射

java 复制代码
// IsolationScope 决定了"运行时数据"的物理落点
// 以下展示不同模式下,用户 alice 的 MEMORY.md 实际存储位置

// 本机模式:路径前缀
// 物理路径 = workspace/alice/MEMORY.md

// 共享存储模式:KV 命名空间
// Redis key = "agents/store/users/alice/MEMORY.md"

// 沙箱模式:沙箱状态 slot
// slot key = "alice-session-123"(配合 IsolationScope.USER)

要点

  • RuntimeContext.userId 是切多用户的钥匙
  • 不传 userId 时走单租户默认,所有人共享一个根
  • 静态资产AGENTS.mdknowledge/)不按 userId 切,所有用户共享
  • 运行时数据(sessions、tasks、memory)才会跟着 userId 走

整体流程图

三种模式的工作流程

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                     Agent 调用文件操作                            │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
                ┌─────────────────────────────┐
                │    选择哪种文件系统模式?      │
                └─────────────┬───────────────┘
                              │
      ┌───────────────────────┼───────────────────────┐
      │                       │                       │
      ▼                       ▼                       ▼
┌──────────────┐     ┌──────────────────┐     ┌──────────────────┐
│ 模式 1:共享  │     │ 模式 2:沙箱     │     │ 模式 3:本机     │
│   存储       │     │                  │     │                  │
│              │     │ Docker 容器      │     │ 本地磁盘         │
│ 无 Shell     │     │ 有 Shell         │     │ 有 Shell         │
│              │     │ 完全隔离         │     │ 宿主执行         │
└──────┬───────┘     └────────┬─────────┘     └────────┬─────────┘
       │                      │                        │
       ▼                      ▼                        ▼
┌──────────────┐     ┌──────────────────┐     ┌──────────────────┐
│              │     │                  │     │                  │
│ MEMORY.md    │     │ 沙箱容器内       │     │ workspace/       │
│   → Redis    │     │ 独立文件系统     │     │ ├── MEMORY.md    │
│ memory/      │     │ 独立进程空间     │     │ ├── memory/      │
│   → Redis    │     │ 可快照/恢复      │     │ └── sessions/    │
│ sessions/    │     │                  │     │                  │
│   → Redis    │     │                  │     │                  │
│ AGENTS.md    │     │                  │     │                  │
│   → 本地     │     │                  │     │                  │
│              │     │                  │     │                  │
└──────────────┘     └──────────────────┘     └──────────────────┘

两层读取架构

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                     WorkspaceManager.readWithOverride()         │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
                ┌─────────────────────────────┐
                │  第 1 层:当前 Filesystem    │
                │  (远端 KV / 沙箱 / 本机)   │
                └─────────────┬───────────────┘
                              │
                    ┌─────────┴─────────┐
                    │                   │
                 找到了               没找到
                    │                   │
                    ▼                   ▼
              ┌──────────┐   ┌─────────────────────┐
              │ 返回内容  │   │ 第 2 层:本地磁盘    │
              │ (覆盖层)│   │ workspace/<path>     │
              └──────────┘   │ (模板/兜底层)       │
                             └──────────┬──────────┘
                                        │
                                        ▼
                                  ┌──────────┐
                                  │ 返回内容  │
                                  │ 或空     │
                                  └──────────┘

注意:写入永远走第 1 层,不直接落到本地磁盘

IsolationScope 四种作用域

复制代码
┌────────────────────────────────────────────────────────────────┐
│                     数据隔离粒度(从严到宽)                     │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  SESSION            USER(默认)     AGENT          GLOBAL     │
│  ┌──────────┐    ┌──────────┐     ┌──────────┐   ┌──────────┐ │
│  │sess-001  │    │ alice    │     │          │   │          │ │
│  │ ──────── │    │├─sess-001│     │  所有     │   │  全局    │ │
│  │ 独立数据 │    │├─sess-002│     │  用户     │   │  共享    │ │
│  │ 其他会话 │    │└─sess-003│     │  共享     │   │  一份    │ │
│  │ 互不可见 │    │ 共享记忆 │     │  数据     │   │  数据    │ │
│  └──────────┘    └──────────┘     └──────────┘   └──────────┘ │
│                                                                │
│  每个会话独立    同一用户多会话    同一 Agent      所有人共享    │
│  互不干扰        共享长期记忆     所有用户共享    谨慎使用      │
│                                                                │
│  降级规则:                                                      │
│  · USER 缺 userId → 降级为 SESSION                              │
│  · SESSION 缺 sessionId → 跳过状态查找,全新环境                  │
│  · AGENT 按 agent name 寻址,不降级                              │
└────────────────────────────────────────────────────────────────┘

模块关系与学习顺序

模块 与本篇的关联
工作区 两层读取的"下层"来源、工作区目录布局
上下文 AgentStateAgentStateStore(userId, sessionId) 寻址
沙箱 沙箱模式的运行时细节,包括容器生命周期和快照恢复链路
技能 技能文件的三模式加载、四层合成优先级

学习要点

必须记住

  1. 三种模式对应三种部署形态:共享存储(多副本共享记忆,无 shell)、沙箱(隔离执行,有 shell)、本机+shell(默认,最简单但最不安全)
  2. IsolationScope 是多租户的钥匙USER(默认,推荐生产)、SESSION(每段对话独立)、AGENT(公共知识库)、GLOBAL(慎用);USER 缺 userId 时降级为 SESSION
  3. 两层读取是 fallback 机制:先查文件系统后端,没有再查本地磁盘。写入永远走文件系统后端
  4. 共享存储模式故意没有 Shell:设计目标是跨节点一致性。需要 Shell 请选沙箱或本机模式
  5. LocalFsMode 默认 ROOTED :GA 修复了前导 / 路径的处理------不在 allow-list 且不存在的 /xxx 会剥掉 / 锚定到 workspace,不再误拒
  6. filesystem(...)abstractFilesystem(...) 互斥:前者声明式选模式,后者完全自管。95% 的场景用前者就够了

容易混淆

  1. RemoteFilesystemSpec vs DockerFilesystemSpec

    • RemoteFilesystemSpec(共享存储):文件存到 KV 后端,没有 Shell,重点是"数据共享"
    • DockerFilesystemSpec(沙箱):文件和命令都在容器里,有 Shell,重点是"执行隔离"
  2. IsolationScope.SESSION vs IsolationScope.USER

    • SESSION:每个对话独立,适合"一问一答"型服务
    • USER:同一用户跨对话共享记忆,适合"长期助手"型服务
  3. 两层读取 vs 用户覆盖

    • 两层读取:同一个文件的 fallback 机制(远端有就用远端,没有用本地模板)
    • 用户覆盖:不同用户的文件隔离(alice/skills/ 覆盖 skills/,这是目录优先级)
  4. ROOTED vs SANDBOXED

    • ROOTED:允许访问 allow-list 内的绝对路径,GA 对带前导 / 的伪绝对路径做锚定兼容
    • SANDBOXED:所有路径强制锚定 workspace,绝对路径一律拒绝
  5. projectWritable 关闭 vs 开启

    • 关闭(默认):所有写入落 workspace
    • 开启:非元数据写入落 project 目录(适合代码生成型 Agent)

实践建议

  1. 开发阶段用本机模式,上线前切沙箱或共享存储:不要在生产环境用默认的本机模式跑不可信代码
  2. 多副本部署必须用共享存储或沙箱:否则每个副本各自为政,记忆和会话状态不共享
  3. 多用户场景选 IsolationScope.USER:这是最常用的生产配置,同一用户跨设备、跨对话共享记忆
  4. 代码生成型 Agent 开 projectWritable(true):让 Agent 写出的代码直接落到项目目录,而不是被堆进 workspace
  5. 不要直接用 java.nio.Files 读写工作区文件 :在沙箱或共享存储模式下会写到错误的位置。通过 harnessAgent.getWorkspaceManager() 操作

常见问题

Q:我选了共享存储模式,但 Agent 提示找不到 Shell 工具,怎么办?

A:这是正常的。共享存储模式故意不提供 Shell。如果你的 Agent 需要执行命令,请改用沙箱模式(DockerFilesystemSpec)或本机模式(LocalFilesystemSpec)。

Q:三种模式可以混合使用吗?比如文件存 Redis、命令跑 Docker?

A:不能。filesystem(...) 三选一。如果你需要更复杂的组合逻辑,用 abstractFilesystem(...) 完全自管。

Q:IsolationScope 影响哪些文件?

A:只影响运行时数据 (sessions、tasks、memory)。静态资产(AGENTS.mdknowledge/、共用 skills/)不按用户隔离。用户级目录(<userId>/skills/)可以覆盖共用版本,但这是目录优先级机制,不是 IsolationScope。

Q:两层读取在沙箱模式下也有用吗?

A:有。沙箱启动时框架会把本地模板 hydrate 进去。之后如果远端(沙箱内)有更新,以沙箱内为准。本地磁盘上的模板只是初始种子。

Q:Agent 写出 /src/App.java 这种带前导 / 的路径,在 ROOTED 模式下会被拒吗?

A:GA 之前可能会误拒;GA 之后不会------只要 /src 不是宿主真实存在的目录、且不在 allow-list 内,框架会剥掉前导 /,锚定到 workspace 根,最终写入 workspace/src/App.java。这既守住安全底线(不会越界访问宿主真实文件),又兼容了 Agent 的伪绝对路径习惯。

相关推荐
苏灿烤鱼1 小时前
今日 GitHub 热门|自改进 Agent 首日登顶,+2,180 项目却只排第四
agent
fthux1 小时前
MCP协议开发实战:从零搭建AI Agent工具链
前端·人工智能·ai·开源·github
苏灿烤鱼2 小时前
GitHub #1 拆解|它说自己在进化,但 worker 跑的是你本机权限,不是沙箱
人工智能·typescript·agent
mifengxing11 小时前
LeetCode 41.缺失的第一个正数|Hard题O(n)+O(1)最优解法深度解析
java·算法·leetcode·排序算法
魔镜前的帅比11 小时前
(开源项目)x-claw(总)
python·ai·rust·开源
markinmarkin13 小时前
Spring 中Bean 的作用域有哪些?
java·后端·spring
赵大仁13 小时前
生成式 UI 实战:用 JSON Schema + React 动态渲染 AI 界面
前端·ai·react·next.js·前端架构·生成式ui