文章目录
- [1. 简单示例](#1. 简单示例)
-
- [1.1 环境准备](#1.1 环境准备)
- [1.2 构建 HarnessAgent](#1.2 构建 HarnessAgent)
- [1.3 运行测试](#1.3 运行测试)
- [2. 跨调用恢复与快照机制](#2. 跨调用恢复与快照机制)
-
- [2.1 跨调用恢复](#2.1 跨调用恢复)
- [2.2 沙箱恢复优先级](#2.2 沙箱恢复优先级)
- [2.3 快照存储实现](#2.3 快照存储实现)
- [3. 分布式多副本部署](#3. 分布式多副本部署)
-
- [3.1 三大必备配置项](#3.1 三大必备配置项)
-
- [3.1.1 分布式 AgentStateStore](#3.1.1 分布式 AgentStateStore)
- [3.1.2 远端持久化快照](#3.1.2 远端持久化快照)
- [3.1.3 合理 IsolationScope 隔离粒度](#3.1.3 合理 IsolationScope 隔离粒度)
- [3.2 并发控制](#3.2 并发控制)
-
- [3.2.1 并发冲突根源](#3.2.1 并发冲突根源)
- [3.2.2 分布式锁](#3.2.2 分布式锁)
- [4. 自行管控沙箱生命周期](#4. 自行管控沙箱生命周期)
-
- [4.1 场景一:复用外部已提前启动的 Docker 容器](#4.1 场景一:复用外部已提前启动的 Docker 容器)
- [4.2 场景二:指定历史快照版本恢复沙箱](#4.2 场景二:指定历史快照版本恢复沙箱)
- [4.3 场景三:多 Agent 共用同一个沙箱](#4.3 场景三:多 Agent 共用同一个沙箱)
1. 简单示例
1.1 环境准备
当前运行环境:
- 操作系统:
Linux 64 JDK 17Dokcer 20.x
拉取镜像:
bash
docker pull ubuntu:20.04
1.2 构建 HarnessAgent
基础构建代码:
java
// 构建绑定 Docker 沙箱的 HarnessAgent
HarnessAgent agent = HarnessAgent.builder()
.name("code-agent")
.model(model)
.workspace(workspace)
// 声明文件系统底层为 Docker 沙箱,基础镜像 ubuntu20.04
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:20.04"))
.build();
1.3 运行测试
项目打包后本地运行:
bash
java -jar agentscope-demo-0.0.1-SNAPSHOT.jar
启动后,在控制台会打印一些关于沙箱的日志:
java
2026-06-24T15:31:50.942+08:00 WARN 3027805 --- [agentscope-demo] [ main] i.agentscope.harness.agent.HarnessAgent : [harness] Sandbox mode is using a local AgentStateStore (JsonFileAgentStateStore). Sandbox state will not survive JVM restarts and cannot be shared across instances. For production, configure a distributed AgentStateStore via .stateStore(...).
2026-06-24T15:31:50.942+08:00 INFO 3027806 --- : HarnessAgent 'harness-demo' built [workspace=/root/.agentscope/workspace/demo-agent, filesystem=SandboxBackedFilesystem, subagents=true]
第一条 WARN 警告日志:
- 当前沙箱模式使用本地文件存储
JsonFileAgentStateStore保存沙箱元数据; - 沙箱状态无法在
JVM重启后保留,也不能在多服务实例 /Pod之间共享; - 生产环境请通过
.stateStore(...)配置分布式状态存储。
第二条 INFO 构建完成日志:
- 工作区路径:宿主本地目录
/root/.agentscope/workspace/demo-agent,skills/knowledge等文件会从此目录增量同步到Docker沙箱; - 文件系统底层:
SandboxBackedFilesystem,代表当前开启沙箱隔离模式,所有read_file/write_file/execute全部走Docker容器,不是原生本地磁盘。
发起一个 read_file 对话:

可以看到自动创建了一个容器:

对话结束后,该容器实例会被停止、删除。
2. 跨调用恢复与快照机制
上述案例执行后,会出现一个报错问题,大概意思是:用户询问沙箱内文件路径,框架尝试复用用户 demo-user 上次的沙箱快照恢复环境,触发反序列化报错:

2.1 跨调用恢复
一次 agent.call() 对话执行完毕后,沙箱会把完整运行环境元数据打包为快照持久存储;下一次同 slot(同用户 / 会话)发起调用时,优先读取快照复原环境,不用重新初始化容器、重装依赖、重建文件,实现跨对话环境复用,这个整套能力统称「跨调用恢复」。
2.2 沙箱恢复优先级
每次 call 启动时执行时,框架按从快到慢依次判断:
- 容器进程存活、工作区完好(最优) :
- 上次对话结束仅执行
sandbox.stop()停止进程,未执行shutdown()删除容器; - 本次直接
start()复用原有容器,所有已安装包、临时文件全部保留,无重建开销。
- 上次对话结束仅执行
- 容器已销毁,但存在可用快照 :容器被删除、进程丢失时,读取快照
JSON,反序列化出完整容器配置、工作区记录,新建容器并还原全部环境。 - 无任何快照记录(冷启动兜底) :找不到当前
slot对应的快照,基于WorkspaceSpec全新初始化空白容器,同步宿主目录、重新执行依赖安装。
2.3 快照存储实现
提供了五种实现:
| 快照实现 | 存储载体 | 核心特性 | 适用场景 |
|---|---|---|---|
| NoopSnapshotSpec(默认) | 无持久化 | 不保存任何快照;容器销毁后环境永久丢失,下次必冷启动 | 本地快速调试、临时一次性对话 |
| LocalSnapshotSpec | 宿主机本地磁盘文件 | 快照落地本地目录,单机重启可恢复;多实例无法共享 | 单机长期部署、无集群需求 |
| OssSnapshotSpec | S3/OSS 兼容对象存储 | 远端云端持久化,多副本节点可拉取同一份快照 | 分布式集群、线上多 Pod 生产环境 |
| RedisSnapshotSpec | Redis 内存数据库 | 读写延迟极低,适合体积小的工作区快照 | 追求极速恢复、环境文件少的场景 |
| JdbcSnapshotSpec | MySQL 等关系库 BLOB 字段 | 复用现有数据库,无需额外中间件 | 已有 MySQL 基础设施,不想部署 OSS/Redis |
OSS 快照配置示例: |
java
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
// 指定OSS存储快照,桶+路径前缀
.snapshotSpec(new OssSnapshotSpec(ossClient, "my-bucket", "agentscope/")))
本地 File 快照配置示例:
java
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
// 指定OSS存储快照,桶+路径前缀
.snapshotSpec(new LocalSnapshotSpec(workspacePath)))
默认使用的是 NoopSnapshotSpec ,默认没有快照,道理上来说应该走冷启动兜底 ,但是框架还去执行 AgentState 序列化,所以导致了上面的报错 。
3. 分布式多副本部署
同一套 Agent 服务部署多台实例 / 多个 Pod 做负载均衡,用户请求会随机打到任意副本。
这种场景下,要求同一个用户的对话,无论路由到哪一台服务实例,都能读取到之前沙箱容器环境、安装的依赖、生成的文件,实现跨节点复用沙箱,不会每次切换实例都冷启动重建容器。
想要实现该能力,必须同时满足两大配套存储,搭配隔离粒度配置。
标准分布式构建代码:
java
HarnessAgent.builder()
.name("assistant")
.model(model)
.workspace(workspace)
// 分布式状态存储,存放沙箱元数据+对话状态
.stateStore(redisStateStore)
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.snapshotSpec(ossSnapshotSpec) // 远端快照支撑跨副本恢复
.isolationScope(IsolationScope.USER))// 用户粒度隔离,可省略(默认值)
.build();
3.1 三大必备配置项
集群跨节点共享沙箱两大必备条件:
- 分布式
AgentStateStore - 远端持久化快照
3.1.1 分布式 AgentStateStore
AgentStateStore 存储 slot 隔离键与沙箱元数据映射关系,包括 slot(用户 / 会话唯一标识)、容器 ID、快照指针、工作区就绪标记、Agent 运行时上下文等。
默认本地实现 JsonFileAgentStateStore 每个实例独立文件,数据隔离,跨 JVM / 跨实例无法互通,进程重启数据易损坏、序列化版本不兼容报错(之前遇到的日志问题)。
分布式 AgentStateStore 则所有服务副本共享同一份索引数据,任意实例通过 slot 就能查到对应沙箱快照地址,例如 RedisStateStore 全局统一存储,多实例共享、并发读写安全、支持集群。
3.1.2 远端持久化快照
持久化快照 真正存储沙箱完整环境快照,默认 NoopSnapshotSpec 无持久化,容器销毁环境直接丢失,分布式场景完全失效。可使用远端持久化快照,例如 OssSnapshotSpec / RedisSnapshotSpec / JdbcSnapshotSpec 等,容器销毁后环境不会丢失,多副本都能访问同一远端存储,任意节点均可拉取快照重建容器。
3.1.3 合理 IsolationScope 隔离粒度
默认 IsolationScope.USER 即可满足绝大多数多用户 SaaS 场景:同一 userId 全局共用一套沙箱环境;
若业务需要每个对话完全隔离,切换为 SESSION。
3.2 并发控制
3.2.1 并发冲突根源
USER / AGENT / GLOBAL 三种隔离粒度,共享同一个 slot 沙箱环境:
- 多台服务
Pod/副本同时收到同一用户请求; - 多线程并行创建、修改、持久化同一个
slot对应的沙箱快照; - 后执行完成的请求会覆盖前面的状态,环境文件、安装依赖出现错乱、丢失、不一致。
SESSION 粒度天然无冲突:每个会话独立 slot,不存在多请求争抢同一环境,不需要加锁。
3.2.2 分布式锁
带锁的并发安全时序完整流程:
- 多副本同时收到同一用户请求,计算出同一个
slot; - 抢占
slot对应的分布式锁,只有一个请求抢占成功,其余阻塞等待; - 抢占成功的实例执行沙箱恢复/新建、文件操作、
shell执行; - 对话结束停止容器、持久化快照;
- 自动释放锁,排队的下一条请求获取锁继续执行;
- 全程保证同一用户沙箱环境串行修改,不会出现状态覆盖。
(1)RedisDistributedStore
RedisDistributedStore 内部封装三件套,一行代码自动注入:
- 分布式
AgentStateStore(slot索引存储) Redis远端快照SnapshotSpecRedisSandboxExecutionGuard分布式执行锁
代码示例:
java
// 初始化Redis分布式统一存储
DistributedStore store = RedisDistributedStore.fromJedis(jedis);
HarnessAgent.builder()
.distributedStore(store)
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.isolationScope(IsolationScope.USER))
.build();
如果需要自定义锁超时时间、单独选用不同锁实现,在 DockerFilesystemSpec 显式覆盖锁配置,优先级高于 distributedStore 默认锁:
java
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.isolationScope(IsolationScope.USER)
// 自定义Redis锁,租约30分钟自动过期,防止死锁
.executionGuard(RedisSandboxExecutionGuard.builder(jedis)
.leaseTtl(Duration.ofMinutes(30))
.build()))
RedisSandboxExecutionGuard 实现底层原理:
- 基于
RedisSET key value NX PX原子命令实现排他锁; NX:仅key不存在时才抢占成功;PX:设置租约过期时间,避免服务崩溃死锁。
优势 :无需分别配置 .stateStore()、.snapshotSpec()、.executionGuard(),统一由 Redis 承载,减少配置冗余。
(2)JdbcSandboxExecutionGuard
实现底层原理:
- 基于
MySQL内置函数GET_LOCK()/RELEASE_LOCK()数据库排他锁; - 适合无
Redis、仅依赖MySQL的架构。
使用示例:
java
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.isolationScope(IsolationScope.USER)
// 自定义 Mysql 锁,租约 30 分钟自动过期,防止死锁
.executionGuard(JdbcSandboxExecutionGuard .builder(dataSource)
.leaseTtl(Duration.ofMinutes(30))
.build()))
(3)自定义锁扩展
实现顶层接口 SandboxExecutionGuard,可对接 Zookeeper、etcd、Redisson 等自研分布式锁,适配企业现有中间件体系。
4. 自行管控沙箱生命周期
框架默认全权管理容器创建、启动、销毁,提供三类手动接管场景,通过 SandboxContext 注入单次调用上下文。
4.1 场景一:复用外部已提前启动的 Docker 容器
java
// 业务手动创建并启动沙箱
Sandbox mySandbox = dockerClient.create(workspaceSpec, snapshotSpec, options);
mySandbox.start();
// 构造外部沙箱上下文
SandboxContext callCtx = SandboxContext.builder()
.client(dockerClient)
.externalSandbox(mySandbox) // 框架仅执行stop,最终销毁交由业务
.build();
// 注入调用上下文执行对话
agent.call(msgs, RuntimeContext.builder()
.sessionId("my-session")
.put(SandboxContext.class, callCtx)
.build()).block();
// 业务手动销毁容器
mySandbox.shutdown();
4.2 场景二:指定历史快照版本恢复沙箱
java
// 反序列化快照状态字符串
SandboxState savedState = dockerClient.deserializeState(savedStateJson);
SandboxContext callCtx = SandboxContext.builder()
.client(dockerClient)
.externalSandboxState(savedState) // 按指定快照恢复,生命周期仍由框架管理
.build();
4.3 场景三:多 Agent 共用同一个沙箱
将同一个 externalSandbox 实例传入多个Agent 的调用上下文,全部对话执行完成后,业务统一调用 shutdown() 释放容器。