第 42 章 走向生产
本章导读:把
HarnessAgent在笔记本上跑起来很容易,搬到生产环境是另一回事——多副本要共享会话、要隔离用户、要支持不可信代码执行、要在 pod 重启后接着上次跑。本章只讲单机 → 分布式生产的差异:哪些组件必须换、换成什么、为什么 builder 会在漏配时直接抛IllegalStateException。主线是DistributedStore一键配置:一行代码同时覆盖 Agent 状态、工作区 KV、沙箱快照与执行锁;随后按组件展开——AgentStateStore与租户隔离、Filesystem 三种模式与IsolationScope、Skill 仓库选型、沙箱与快照,最后汇总为多副本部署 checklist、完整 builder 模板与常见坑位。组件细节可回查第 24 章(会话存储与分布式部署)。
单机跑通的 Agent
搬到多副本生产环境"] --> B["单机到分布式的差异
哪些组件必须换"] B --> C["DistributedStore 一键配置
状态 · 工作区 KV
沙箱快照 · 执行锁"] B --> D["按组件展开
状态与租户隔离 · 文件系统模式
Skill 仓库 · 沙箱与快照"] B --> E["生产落地
多副本 checklist
builder 模板 · 常见坑位"] C --> F["收益
多副本共享会话 · 隔离用户
重启后接着上次跑"] D --> F E --> F
本章导览图
快速上手:DistributedStore 一键配置
最快上手方式:用 DistributedStore 一键配置所有分布式组件:
DistributedStore store = RedisDistributedStore.fromJedis(jedis);
// 或 JdbcDistributedStore.create(dataSource);
// 或 OssDistributedStore.create(ossClient, bucket, prefix);
HarnessAgent.builder()
.distributedStore(store)
.filesystem(...) // 选择你的 workspace 模式
.build();
混合 store(如 JDBC 管状态 + Redis 管 sandbox)也行:
DistributedStore store = DistributedStore.builder()
.agentStateStore(JdbcDistributedStore.create(ds).agentStateStore())
.baseStore(JdbcDistributedStore.create(ds).baseStore())
.sandboxSnapshotSpec(RedisDistributedStore.fromJedis(jedis).sandboxSnapshotSpec())
.sandboxExecutionGuard(RedisDistributedStore.fromJedis(jedis).sandboxExecutionGuard())
.build();
备选:Control Plane 托管 Store
若已部署 Control Plane 控制面,可由其托管 BaseStore / 沙箱锁与快照 / MessageBus / AsyncToolRegistry / TaskRepository / 可选 SessionTurnGate。仍需自备一个 AgentStateStore(Redis/JDBC/OSS);core 提供 getVersioned / saveIfVersion,但存储不在控制面:
ControlPlaneStores cp = ControlPlaneStores.fromEnv();
HarnessAgent.builder()
.distributedStore(cp.withAgentStateStore(redis.agentStateStore()))
.filesystem(new RemoteFilesystemSpec().isolationScope(IsolationScope.USER))
.build();
控制面开启 --enable-hosted-store(生产建议 Postgres)。withAgentStateStore 已含托管 TaskRepository;SandboxFilesystem 模式下的子 agent 后台任务需此路径。AgentStateStore 的 Redis/JDBC(MySQL / PostgreSQL / H2 / SQLite)/InMemory 支持 versioning CAS,其余后端仍为 LWW;多副本可选 turn gate + ConflictPolicy.FAIL 减少重复 turn,正确性靠 CAS。鉴权为共享 internal token,租户取自请求体——不适用于同一控制面上互不信任的多租户。queueDrain 为 destructive(读即 ack)。详见第 24 章“Control Plane 托管 Store”一节。
一图速览:单机默认 vs 分布式生产
| 维度 | 单机默认(开发 / demo) | 分布式生产替换 |
|---|---|---|
| 一键配置 | 不需要 | .distributedStore(RedisDistributedStore.fromJedis(jedis)) |
AgentStateStore |
JsonFileAgentStateStore(本地 JSON) |
自动由 distributedStore 注入 |
| Filesystem | LocalFilesystemSpec(不配 = 此项) |
RemoteFilesystemSpec 或 SandboxFilesystemSpec(baseStore 自动由 store 注入) |
| Sandbox 快照 | NoopSnapshotSpec / LocalSnapshotSpec |
自动由 distributedStore 注入 |
| 沙箱执行串行化 | 单进程内即可 | 自动由 distributedStore 注入 |
| Skill 来源 | workspace/skills/ |
GitSkillRepository / MysqlSkillRepository / NacosSkillRepository |
| 观测 | 默认无 tracing | OtelTracingMiddleware + OpenTelemetry SDK |
DistributedStore 能力矩阵
| 能力 | Redis (agentscope-extensions-redis) |
OSS (agentscope-extensions-oss) |
JDBC (agentscope-extensions-jdbc) |
|---|---|---|---|
AgentStateStore |
RedisAgentStateStore |
OssAgentStateStore |
JdbcAgentStateStore |
BaseStore |
RedisStore |
OssBaseStore |
JdbcStore |
SandboxSnapshotSpec |
RedisSnapshotSpec |
OssSnapshotSpec |
JdbcSnapshotSpec |
SandboxExecutionGuard |
RedisSandboxExecutionGuard |
— (对象存储不适合做锁) | JdbcSandboxExecutionGuard |
这些组件分别解决不同的生产问题:
AgentStateStore:保存 Agent 的运行时会话状态,包括对话历史、压缩摘要、权限规则、Plan Mode 状态和 tool state。它决定一个请求落到另一台机器、或进程重启后,Agent 能不能继续同一个(userId, sessionId)。BaseStore:给RemoteFilesystemSpec提供共享 KV 文件存储,用来承载MEMORY.md、memory/、skills/、sessions/等 workspace 路径。多副本部署时,它让不同 pod 看到同一份长期记忆和共享文件。SandboxSnapshotSpec:保存沙箱 workspace 快照。沙箱容器销毁、pod 重启、下一次请求落到新节点时,它负责把上一次的工作区恢复回来,避免pip install、生成文件、临时项目状态全部丢失。SandboxExecutionGuard:对同一个 sandbox slot 的命令执行做跨节点串行化。AGENT/GLOBAL等共享 scope 下,多个副本可能同时对同一个沙箱执行命令;guard 用 Redis/JDBC 锁避免并发写 workspace、并发启动/停止 sandbox 等竞态。
OSS 不提供
SandboxExecutionGuard——对象存储不适合做分布式锁。需要 sandbox 并发控制的 OSS 用户,用DistributedStore.builder()混入 Redis 的 guard 即可。
核心校验链路:
filesystem(RemoteFilesystemSpec)+ 没换stateStore(...)也没配distributedStore(...)→build()抛IllegalStateException。filesystem(SandboxFilesystemSpec)+ 本地AgentStateStore→build()正常通过但打一条 warning 日志,提醒你沙箱状态不能跨 JVM 恢复;生产环境务必配distributedStore。
1. 状态存储:先把 AgentState 放对地方
推荐:直接用
distributedStore(...)一键配置,不需要手动设置stateStore。下面的详细表格供需要单独控制AgentStateStore的高级用户参考。
AgentState(对话上下文、压缩摘要、权限规则、Plan Mode 状态、tool state)跨进程恢复的唯一通路就是 AgentStateStore(见第 24 章)。单机默认的 JsonFileAgentStateStore 把状态写在单个进程的本地磁盘;多副本部署时下一个请求可能落到另一台机器,pod 重启也会丢掉本地文件——这是第一个必须换的组件。
| 实现 | 模块 | 何时使用 |
|---|---|---|
InMemoryAgentStateStore |
agentscope-core |
单元测试;进程退出全部丢 |
JsonFileAgentStateStore |
agentscope-core |
单机开发;按 (userId, sessionId) 在文件系统分目录。HarnessAgent 默认,根目录 ~/.agentscope/state/<agentId>/;单机 |
RedisAgentStateStore |
agentscope-extensions-redis |
多副本生产首选;支持 Jedis / Lettuce / Redisson(Standalone / Cluster / Sentinel) |
JdbcAgentStateStore |
agentscope-extensions-jdbc |
需要把状态沉淀进关系型库(审计 / 报表 / 联表查询) |
Redis 三种 client adapter 都通过 RedisAgentStateStore.builder() 切换:
import io.agentscope.core.state.AgentStateStore;
import io.agentscope.extensions.redis.state.RedisAgentStateStore;
import redis.clients.jedis.JedisPooled;
// Jedis Standalone
AgentStateStore stateStore = RedisAgentStateStore.builder()
.jedisClient(new JedisPooled("redis://localhost:6379"))
.keyPrefix("myapp:session:")
.build();
// Lettuce Cluster(写多读少更顺)
// .lettuceClusterClient(RedisClusterClient.create(...))
// Redisson(如果你已经在用 Redisson 做其他事)
// .redissonClient(redisson)
按租户隔离。 单用 sessionId 只够单租户。生产应在每次调用的 RuntimeContext 上同时设置 userId 与 sessionId,防止跨用户串读——存储按 (userId, sessionId) 二元组寻址每个槽位(RedisAgentStateStore 把 userId 折进 Redis key,JdbcAgentStateStore 折进槽位 ID)。其他维度(租户、agent)自行拼进 sessionId 字符串:
agent.call(msg, RuntimeContext.builder()
.userId(tenantId + ":" + userId)
.sessionId(agentId + ":" + sessionId)
.build()).block();
完整细节见第 8 章(上下文)。
2. Filesystem 模式 & IsolationScope:决定"谁和谁共享文件"
状态放对地方之后,下一个要决定的是文件:单机默认的 LocalFilesystemSpec 把工作区写在进程本地,多副本部署时各 pod 看到的是各自的文件副本,共享记忆与共享文件无从谈起。三种模式快速回顾(详见第 12 章“工作区与文件系统”):
| 模式 | 配置 | 提供 shell? | 适用 |
|---|---|---|---|
| 本机 + shell | filesystem(new LocalFilesystemSpec()...) 或不配 |
✅ 宿主 sh -c |
单进程 / 信任环境 |
| 共享存储 | filesystem(new RemoteFilesystemSpec(store)) |
❌(要 shell 请走沙箱) | 多副本 / 多 pod 共享长期记忆 |
| 沙箱 | filesystem(new DockerFilesystemSpec()...) 等 5 种 |
✅ 沙箱内执行 | 不可信代码 / 跨调用恢复 / 多用户隔离 |
IsolationScope 是多用户隔离的核心钥匙。共享存储和沙箱两种模式都用同一套 scope 决定命名空间分桶:
| Scope | 含义 | 典型场景 |
|---|---|---|
SESSION(沙箱默认) |
每个 sessionId 独立 slot | 多用户 SaaS,每段对话独立 |
USER(Remote 默认) |
同一 userId 跨 session 共享 |
同一用户多设备共享长期记忆 |
AGENT |
agent 内所有用户共享 | 公共知识库型 agent |
GLOBAL |
全局一个 slot | 谨慎使用 |
// distributedStore 自动注入 baseStore 到 RemoteFilesystemSpec
DistributedStore store = RedisDistributedStore.fromJedis(jedis);
HarnessAgent.builder()
.distributedStore(store)
.filesystem(new RemoteFilesystemSpec()
.isolationScope(IsolationScope.USER)
.anonymousUserId("_default")) // 未传 userId 时的 fallback
.build();
anonymousUserId 是个生产细节——很多场景下 RuntimeContext.userId 可能为 null(系统任务、调度器触发、admin 操作),fallback 别用空字符串,否则所有匿名调用会聚到一个共享桶。
3. Remote 模式的 BaseStore:KV 选型与"不要把 OSS 当 KV 用"
RemoteFilesystemSpec 建在一个 BaseStore 接口之上。选定模式后,后端选型直接决定延迟与成本——工作区里高频小写入很多,选错后端会立刻把两者放大。内置实现两种:
| 实现 | 依赖 | 并发安全 | 适用 |
|---|---|---|---|
RedisStore |
agentscope-extensions-redis |
Lua 实现 CAS putIfVersion,ZRANGEBYLEX 做 prefix search |
主推;多副本共享 |
JdbcStore |
agentscope-extensions-jdbc;MySQL / PostgreSQL / SQLite / H2 dialect 自动判别 |
单语句 CAS UPDATE | 已有关系型基础设施 / 需要联表 |
InMemoryStore |
— | — | 测试 |
// 推荐:DistributedStore 一键配置
DistributedStore store = RedisDistributedStore.fromJedis(
new JedisPooled("redis://prod-redis:6379"));
HarnessAgent agent = HarnessAgent.builder()
.name("multi-tenant-agent")
.model(model)
.workspace(workspace)
.distributedStore(store) // 自动注入 stateStore + baseStore
.filesystem(new RemoteFilesystemSpec() // baseStore 由 store 自动注入
.isolationScope(IsolationScope.USER)
.workspaceIndex(WorkspaceIndex.open(workspace))) // 加速 ls/glob
.build();
// 或者用 JDBC store:
DistributedStore jdbcStore = JdbcDistributedStore.create(dataSource);
那 OSS / NAS / S3 怎么放进来?
不要为了 OSS 写一个 BaseStore 实现——MEMORY.md / memory/YYYY-MM-DD.md / agents/<id>/context/<sid>/ 每秒可能写几次,OSS 的延迟与 per-request 成本会立刻失控。正确分工是:
| 数据形态 | Store | 谁来管 |
|---|---|---|
| 高频小 KV(记忆、会话快照、任务记录) | Redis / MySQL(BaseStore) |
RemoteFilesystemSpec |
| 大对象(沙箱整个 workspace tar archive,几十 MB) | OSS / S3 | OssSnapshotSpec / 自定义 RemoteSnapshotSpec |
| 跨节点共享卷(多个沙箱实例挂同一份目录) | NAS / EFS | AgentRunFilesystemSpec.nasConfig(...)(仅 AgentRun 原生支持) |
RemoteFilesystemSpec 的路由表
为避免不同子系统的 key 撞车,spec 把工作区路由切成多个命名空间段(每段独立):
| Workspace 路径 | 命名空间段 |
|---|---|
AGENTS.md / MEMORY.md / tools.json |
root |
memory/ |
memory |
skills/ |
skills |
subagents/ |
subagents |
knowledge/ |
knowledge |
agents/<agentId>/sessions/ |
sessions |
agents/<agentId>/tasks/ |
tasks |
额外目录:.addSharedPrefix("prompts/") |
自动派生 |
每段下面再按 IsolationScope 切桶(USER → agents/<agentId>/users/<userId>/)。Redis key 大致长成 agentscope:store:item:agents\0X\0users\0alice\0memory\0memory/2026-06-02.md。
CompositeFilesystem:两层读+写穿透
RemoteFilesystemSpec.toFilesystem(...) 实际产出的是 CompositeFilesystem:底层一个不带 shell 的 LocalFilesystem(兜底读本地模板),顶层每条路由是一个 OverlayFilesystem(上层 RemoteFilesystem + 下层只读 LocalFilesystem 模板)。
效果:写永远落 Remote,读优先 Remote、没有再退回本地模板。这就是第 12 章(工作区与文件系统)里讲的"两层读架构"在 Remote 模式下的具体形态——本地 <workspace>/AGENTS.md 是种子(团队 git 同步),Remote 一旦写入就接管。
WorkspaceIndex:可选 SQLite 索引
.filesystem(new RemoteFilesystemSpec(store).workspaceIndex(WorkspaceIndex.open(workspace)))
加速 Remote 模式下的 ls / glob / exists / grep——不开的话每次都全表扫 KV。WorkspaceIndex 是 best-effort 的 SQLite 文件(落在 <workspace>/.index/),失败会自动降级,不影响功能。
4. Skill 集中管理:选哪种 SkillRepository
单机开发时技能随项目走(workspace/skills/ 进 git 即可);生产环境要回答的是治理问题——谁发布、agent 能否改写、变更如何审核,这就要把技能来源换成集中的 SkillRepository。
Skill 优先级从低到高合成(详见第 15 章“技能(Skill)”):
| 层 | 来源 | 用什么 | 适用 |
|---|---|---|---|
| 1 | 项目全局 | .projectGlobalSkillsDir(Path) |
个人开发机器;~/.agentscope/skills/ |
| 2 | Marketplace | .skillRepository(...) |
跨项目共享 |
| 3 | 工作区共用 | workspace/skills/ |
项目专属;进 git |
| 4 | 用户隔离 | <userId>/skills/ |
用户级覆盖 |
Marketplace 存储源选型
| Repository | 模块 | 特点 | 推荐场景 |
|---|---|---|---|
GitSkillRepository |
agentscope-extensions-skill-git-repository |
团队 git 仓库;HEAD 变化才拉;只读分发 | 早期 / 小团队;改 skill 走 git PR review |
MysqlSkillRepository |
agentscope-extensions-skill-mysql-repository |
DataSource 注入;writeable(true/false) 双模式;从 agent 侧可写回 |
平台侧统一治理;多团队多 agent |
NacosSkillRepository |
agentscope-extensions-nacos-skill |
在线下发 + 配置中心变更订阅;AutoCloseable |
阿里系生态;要"改一次全网立即生效" |
ClasspathSkillRepository |
agentscope-core |
和 JAR 一起发;Spring Boot Fat JAR 兼容 | 产品内置不可改的能力包 |
HarnessAgent agent = HarnessAgent.builder()
// ...
.skillRepository(new GitSkillRepository("https://github.com/your-org/team-skills.git"))
.skillRepository(MysqlSkillRepository.builder(dataSource)
.databaseName("agentscope")
.skillsTableName("skills")
.createIfNotExist(true)
.writeable(false) // 只读分发,生产建议
.build())
.build();
skillRepository(...) 可重复调用;后注册的优先级更高,同名覆盖。
生产 checklist
- 优先
MysqlSkillRepository(writeable=false)或NacosSkillRepository——平台集中治理,agent 端只读;写回走管理台 + 审核流。 - 不希望 agent 看到
workspace/skills/?.disableDefaultWorkspaceSkills()。 - 开
enableSkillManageTool让 agent 自己起草新 skill 时,必须配enableSkillPromotionGate(...);生产严禁autoPromote=true。 NacosSkillRepository是AutoCloseable——Spring@PreDestroy或者try-with-resources关掉它,否则会泄露订阅。
5. 需要 shell:选 Sandbox + 必配 Snapshot
什么场景必走沙箱:
- 模型可能跑不可信代码(Python / shell /
npm install/ 编译) - 需要跨调用恢复整个工作目录状态(
node_modules、生成文件、pip install后的环境) - 多用户硬隔离(不能让一个用户的进程看到另一个用户的)
五种沙箱实现
| Spec | 模块路径 | 适用 |
|---|---|---|
DockerFilesystemSpec |
io.agentscope.harness.agent.sandbox.impl.docker |
单机 / 本地集群;从 image 起容器;最熟悉 |
KubernetesFilesystemSpec |
...impl.kubernetes |
已经跑 K8s;走 pod / Job |
DaytonaFilesystemSpec |
...impl.daytona |
Daytona 服务(开发环境即服务) |
E2bFilesystemSpec |
...impl.e2b |
E2B 云沙箱;最快上云、不依赖自有基础设施 |
AgentRunFilesystemSpec |
...impl.agentrun |
阿里云 AgentRun;原生 NAS / OSS mount、企业级方案 |
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.isolationScope(IsolationScope.SESSION))
Snapshot 是沙箱的"分布式生命线"
推荐:使用
distributedStore(...)后,SandboxSnapshotSpec和SandboxExecutionGuard都会自动注入到SandboxFilesystemSpec,不需要手动配置。下面的表格供需要单独控制快照实现或使用LocalSnapshotSpec的场景参考。
沙箱默认是"瞬时"的——下一次 call() 可能起在另一个节点的新容器里,之前 pip install / 写入的所有产物全丢。SandboxSnapshotSpec 把工作区打成 tar 持久化,下次 call() 自动 hydrate 回新容器。
| Spec | Store | 模块 | 何时用 |
|---|---|---|---|
NoopSnapshotSpec |
— | agentscope-harness |
不要在生产用;容器丢了就走冷启动 |
LocalSnapshotSpec(Path) |
本地目录 tar 文件 |
agentscope-harness |
单机调试 |
OssSnapshotSpec |
阿里云 OSS | agentscope-extensions-oss |
大对象首选;天然适合对象存储 |
RedisSnapshotSpec |
Redis | agentscope-extensions-redis |
小工作区 + 短 TTL(注意 Redis 内存代价) |
JdbcSnapshotSpec |
JDBC BLOB | agentscope-extensions-jdbc |
已有关系型数据库、不想引入额外中间件 |
自实现 RemoteSnapshotClient → RemoteSnapshotSpec |
S3 / GCS / MinIO | — | 不在内置列表里 |
DistributedStore redisStore = RedisDistributedStore.fromJedis(jedis);
DistributedStore ossStore = OssDistributedStore.create(
ossClient,
"agentscope-sandbox-snapshots",
"prod/"); // key 前缀,多环境隔离
DistributedStore store = DistributedStore.builder()
.agentStateStore(redisStore.agentStateStore())
.baseStore(redisStore.baseStore())
.sandboxSnapshotSpec(ossStore.sandboxSnapshotSpec())
.sandboxExecutionGuard(redisStore.sandboxExecutionGuard())
.build();
HarnessAgent agent = HarnessAgent.builder()
.name("coding-agent")
.model(model)
.workspace(workspace)
.distributedStore(store)
.filesystem(new DockerFilesystemSpec()
.image("python:3.12-slim")
.isolationScope(IsolationScope.USER))
.build();
使用 distributedStore(...) 后,快照和执行锁都会自动注入,不需要在 SandboxFilesystemSpec 上手动配置。如果只是要改 OSS bucket / prefix,优先在创建 OssDistributedStore 时配置;只有需要完全自定义 SandboxSnapshotSpec 时,才在 SandboxFilesystemSpec 上显式覆盖。
沙箱执行节点串行化:SandboxExecutionGuard
SESSION / USER scope 下天然按 session/user 分桶,并发不会撞。但 AGENT / GLOBAL scope 多副本部署时,可能同时有 N 个节点要在同一个 sandbox slot 上 exec——会撞。distributedStore(...) 会自动注入对应 store 的执行锁:
| 实现 | 模块 | 机制 |
|---|---|---|
RedisSandboxExecutionGuard |
agentscope-extensions-redis |
Redis SET NX PX 租约 |
JdbcSandboxExecutionGuard |
agentscope-extensions-jdbc |
方言决定:MySQL GET_LOCK(),其余为通用表锁 |
推荐仍然通过 DistributedStore 注入执行锁:
DistributedStore store = RedisDistributedStore.fromJedis(jedis);
HarnessAgent.builder()
.distributedStore(store)
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.isolationScope(IsolationScope.GLOBAL))
.build();
只有需要自定义锁参数(如租约 TTL)时,才在 SandboxFilesystemSpec 上显式覆盖:
HarnessAgent.builder()
.distributedStore(store)
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.isolationScope(IsolationScope.GLOBAL)
.executionGuard(RedisSandboxExecutionGuard.builder(jedis)
.leaseTtl(Duration.ofMinutes(30))
.build()))
.build();
也可以实现 SandboxExecutionGuard 接口接入 Zookeeper、etcd 等其他锁。
Workspace projection:把工作区里的种子投到沙箱
SandboxFilesystemSpec 默认会把 AGENTS.md, skills, subagents, knowledge, .skills-cache 五个 root 打 tar 在沙箱启动时 hydrate 进去(内容 hash 比对、增量重写)。要调整:
.filesystem(new DockerFilesystemSpec()
.image("...")
.workspaceProjectionRoots(List.of("AGENTS.md", "skills", "knowledge")) // 不要 subagents/.skills-cache
// .workspaceProjectionEnabled(false) // 完全关掉
)
AgentRun 特有:NAS / OSS mount
AgentRunFilesystemSpec 是唯一原生支持多 sandbox 实例共享同一个目录的实现(通过 NAS mount);如果业务是"一个用户在不同 session 里看到同一份 workspace",用 AgentRun 比每次 hydrate snapshot 更高效:
.filesystem(new AgentRunFilesystemSpec()
.apiKey(System.getenv("AGENTRUN_API_KEY"))
.accountId(System.getenv("ALI_ACCOUNT_ID"))
.region("cn-hangzhou")
.templateName("python-3.12")
.nasConfig(new AgentRunNasMountConfig().fileSystemId("...").mountTargetDomain("...").mountDir("/workspace"))
.addOssMount(new AgentRunOssMountConfig().bucketName("data").mountDir("/mnt/oss")))
完整字段见 AgentRunNasMountConfig / AgentRunOssMountConfig 源码。
6. 多副本部署 checklist(综合)
把上面单点替换串成一张表:
| 关注点 | 推荐组合 |
|---|---|
会话 / AgentState |
RedisDistributedStore 或混合 DistributedStore 注入 AgentStateStore;(userId, sessionId) 承载租户/用户/agent 维度 |
| 工作区文件 | distributedStore(...) 注入 BaseStore + RemoteFilesystemSpec + WorkspaceIndex + IsolationScope.USER |
| 大对象 / 快照 | 混合 DistributedStore 中使用 OssDistributedStore.sandboxSnapshotSpec()(不要把大快照写 Redis) |
| 跨节点 sandbox 共享 | AgentRun + NAS mount,或自管 K8s + distributedStore(...) 注入的 SandboxExecutionGuard |
| Skill 治理 | MysqlSkillRepository(writeable=false) 或 NacosSkillRepository;agent 端禁用 autoPromote |
| 子 agent 任务记录 | 自动用 WorkspaceTaskRepository,落 Remote / Sandbox;不需要额外配 |
| 暴露的子 agent(用户直接和子 agent 对话) | 注册表由 distributedStore 自动接好——subagentId 在任意副本/重启后都能解析并恢复子 agent;把某个 subagentId 的消息路由回同一节点(粘性)即可让恢复只作为故障切换兜底。多 agent 的 GatewayBootstrap 传 .distributedStore(...) |
| 优雅停机 | GracefulShutdownManager(默认注册 JVM hook);接好 SIGTERM;视需要 setConfig(...) 调 inflight 等待时间 |
| 可观测 | OtelTracingMiddleware + OpenTelemetry SDK + OTLP exporter |
| 限流 | 自写 MiddlewareBase(onModelCall);参考第 9 章“限速 middleware”一节 |
多副本生产的部署拓扑如图 42-1 所示。
共享 Builder 按请求构建实例"] P2["应用副本 2
共享 Builder 按请求构建实例"] end P1 --> AG["HarnessAgent
distributedStore 一键配置"] P2 --> AG AG --> ST["AgentStateStore
会话状态跨副本与重启恢复"] AG --> KV["BaseStore
共享工作区文件 KV"] AG --> SN["SandboxSnapshotSpec
沙箱工作区快照"] AG --> GD["SandboxExecutionGuard
跨节点执行串行化"] ST --> R["Redis"] KV --> R GD --> R SN --> O["OSS 大对象存储"] AG --> SB["沙箱集群
Docker 或 Kubernetes"] SB -.-> SN AG --> SK["Skill 仓库
MySQL 或 Nacos 只读分发"]
图 42-1:多副本生产部署拓扑
7. 一个完整的生产 builder 模板
应用启动时配置共享 Builder 和外部依赖;每次请求用 agentBuilder.build() 获得新的实例,并在执行结束后关闭。Builder 不保存本次请求的用户或会话参数,身份通过 RuntimeContext 传入。完整生命周期约定见第 4 章“实例生命周期”一节。
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.skill.repository.mysql.MysqlSkillRepository;
import io.agentscope.extensions.redis.RedisDistributedStore;
import io.agentscope.core.tracing.OtelTracingMiddleware;
import io.agentscope.harness.agent.DistributedStore;
import io.agentscope.harness.agent.HarnessAgent;
import io.agentscope.harness.agent.IsolationScope;
import io.agentscope.harness.agent.sandbox.impl.docker.DockerFilesystemSpec;
import io.agentscope.core.memory.compaction.CompactionConfig;
import io.agentscope.core.memory.compaction.ToolResultEvictionConfig;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import redis.clients.jedis.JedisPooled;
// --- 依赖(应用启动时创建一次) ---
Path workspace = Paths.get("/var/agentscope/workspace");
JedisPooled jedis = new JedisPooled(System.getenv("REDIS_URI"));
DistributedStore store = RedisDistributedStore.fromJedis(jedis);
// --- 共享 Builder(应用启动时配置一次) ---
HarnessAgent.Builder agentBuilder = HarnessAgent.builder()
.name("coding-assistant")
.agentId("coding-assistant")
.model("dashscope:qwen-plus")
.workspace(workspace)
.distributedStore(store) // 自动注入 stateStore + snapshotSpec + executionGuard
.filesystem(new DockerFilesystemSpec()
.image("python:3.12-slim")
.isolationScope(IsolationScope.USER))
.compaction(CompactionConfig.builder()
.triggerMessages(50)
.keepMessages(20)
.build())
.toolResultEviction(ToolResultEvictionConfig.defaults())
.skillRepository(MysqlSkillRepository
.builder(skillsDataSource())
.createIfNotExist(false)
.writeable(false)
.build())
.middlewares(List.of(new OtelTracingMiddleware()));
同步请求使用 try-with-resources,等待执行结束后再释放实例。同一会话跨实例的调用由应用调度协调:
// 在 HTTP handler 中
try (HarnessAgent agent = agentBuilder.build()) {
agent.call(msg, RuntimeContext.builder()
.userId(httpRequest.tenantUserId())
.sessionId(httpRequest.sessionId())
.build()).block();
}
8. 常见坑位
- 忘记传
RuntimeContext——不传sessionId时所有请求共享defaultSessionId的状态,造成串台。在多用户场景下,每次call()都应通过RuntimeContext.builder().userId(...).sessionId(...).build()传入,确保各会话状态隔离。参见第 4 章“多用户 / 多会话并发”一节。 java.nio.Files写工作区——在沙箱 / Remote 模式下落到错的位置。永远走agent.getWorkspaceManager()。例外:builder 装配时的种子文件(initWorkspaceIfAbsent之类)那时还没有运行时上下文,用java.nio.Files是 OK 的。tools.json的allow会过滤内置工具——用白名单时务必把read_file/memory_search/agent_spawn这些保留下来,否则整套内置工具一起被砍。IsolationScope改了,旧数据不会自动迁移——上线前定下来,别上线后改。改了等同于"换了一个命名空间"。- 本地
AgentStateStore单机限制——K8s 多副本部署里如果把分布式文件系统和本地JsonFileAgentStateStore搭配,第一次 build 就抛IllegalStateException,这是设计如此——告诉你别把 agent 状态留在某个 pod 的本地磁盘上。 NacosSkillRepository不关闭——会泄露订阅,集群规模大了 Nacos 会喊。Spring 注入用@PreDestroy或destroyMethod="close"。- OSS / NAS 走完 IAM 再上线——
OssSnapshotSpec的 AK/SK 是平台凭证;用 RAM Role + STS 临时凭证更稳。 - 本地
AgentStateStore+ 沙箱模式仅用于开发——构建时的 warning 日志是故意的,生产环境别忽略。
本章小结
单机到分布式的差异收敛为一组组件替换:DistributedStore 一行配置同时接好 AgentStateStore、BaseStore、快照与执行锁;Filesystem 按共享与隔离需求选 Remote 或 Sandbox 模式,并用 IsolationScope 分桶;Skill 换成集中仓库统一治理;需要 shell 的场景必配快照。动手部署前对照第 6 节 checklist 逐项确认,遇到问题先查第 8 节的坑位。
相关文档
- 第 2 章(快速开始) —— 端到端跑通第一个
HarnessAgent - 第 11 章(Harness 架构深入) —— 各能力如何协作
- 第 8 章(上下文) ——
AgentState/AgentStateStore/ 跨节点恢复 - 第 13 章(上下文管理) —— 对话摘要、工具结果卸载、溢出恢复
- 第 12 章(工作区与文件系统) —— 目录布局、两层读、
tools.json、三种部署模式、IsolationScope - 第 18 章(沙箱执行) —— 沙箱细节、五种实现、快照机制
- 第 15 章(技能(Skill)) —— 四层合成、市场存储源、自学习闭环
- 第 9 章(中间件) —— 自定义观测 / 限流 / fallback 中间件