Agent Scope Java 2.x 系列【17】Harness:工作区远程存储模式
文章目录
- 1. 概述
- 2. 架构原理
- 2.1 核心抽象
- 2.2 BaseStore 接口
- 2.3 RemoteFilesystemSpec 路由策略
- 2.4 命名空间与多租户隔离
- 3. JDBC 模式
- 3.1 适用场景
- 3.2 表结构
- 3.3 配置案例
- 3.4 优势
- 4. OSS 模式
- 4.1 适用场景
- 4.2 配置案例
- 4.3 优势
- 5. Redis 模式
- 5.1 适用场景
- 5.2 配置案例
- 5.3 优势
- 6. 混合模式
- 7. 自定义 BaseStore 实现
- 8. 三种模式对比
1. 概述
AgentScope Harness的工作区文件系统通过BaseStore抽象层实现了存储与逻辑解耦,支持将工作区数据(记忆、技能、会话日志、子Agent配置等)从本地磁盘迁移到远程共享存储。
官方提供了三个扩展模块:
| 模块 | 存储后端 | 适用场景 |
|---|---|---|
agentscope-extensions-redis | Redis | 高性能缓存型存储,适合低延迟读写 |
agentscope-extensions-mysql | MySQL / JDBC | 关系型数据库,适合需要事务和 SQL 查询的场景 |
agentscope-extensions-oss | 阿里云 OSS | 对象存储,适合大文件、低成本海量存储 |
2. 架构原理
2.1 核心抽象
┌──────────────────────────────────────────────┐ │ HarnessAgent │ │ .distributedStore(store) │ │ .filesystem(new RemoteFilesystemSpec(...)) │ └──────────────┬───────────────────────────────┘ │ ┌───────▼────────┐ │ DistributedStore │ ← 统一入口 └───┬─────────┬──┘ │ │ ┌──────▼──┐ ┌──▼───────────┐ │BaseStore │ │AgentStateStore│ │(工作区KV) │ │(会话状态持久化) │ └──────┬──┘ └──────────────┘ │ ┌──────┼──────────┬──────────┐ │ │ │ │ Redis MySQL/JDBC OSS 自定义实现2.2 BaseStore 接口
工作区文件的读写最终落到BaseStore(基于命名空间的KV存储):
核心源码:
publicinterfaceBaseStore{StoreItemget(List<String>namespace,Stringkey);voidput(List<String>namespace,Stringkey,Map<String,Object>value);List<StoreItem>search(List<String>namespace,intlimit,intoffset);voiddelete(List<String>namespace,Stringkey);// CAS 乐观锁写入(可选实现)defaultbooleanputIfVersion(List<String>namespace,Stringkey,Map<String,Object>value,longexpectedVersion){returnfalse;}}2.3 RemoteFilesystemSpec 路由策略
RemoteFilesystemSpec将工作区目录按路径前缀分片路由到不同的存储命名空间:
workspace/ ├── AGENTS.md ──→ store namespace [agents, <id>, users, <uid>, root] ├── MEMORY.md ──→ store namespace [agents, <id>, users, <uid>, root] ├── tools.json ──→ store namespace [agents, <id>, users, <uid>, root] ├── memory/ ──→ store namespace [agents, <id>, users, <uid>, memory] ├── skills/ ──→ store namespace [agents, <id>, users, <uid>, skills] ├── subagents/ ──→ store namespace [agents, <id>, users, <uid>, subagents] ├── knowledge/ ──→ store namespace [agents, <id>, users, <uid>, knowledge] └── agents/<id>/ ├── sessions/ ──→ store namespace [agents, <id>, users, <uid>, sessions] └── tasks/ ──→ store namespace [agents, <id>, users, <uid>, tasks]Overlay模式:每条路由采用RemoteFilesystem(上层,可读写)+LocalFilesystem(下层,只读模板)的叠加结构——本地模板文件作为基线,用户编辑后copy-on-write落入远程存储,后续读取优先使用远程版本。
2.4 命名空间与多租户隔离
IsolationScope控制命名空间前缀,实现无侵入的多租户隔离:
| IsolationScope | 命名空间结构 | 共享范围 |
|---|---|---|
SESSION | agents/<id>/sessions/<sid> | 单会话完全隔离 |
USER(默认) | agents/<id>/users/<uid> | 同一用户跨会话共享 |
AGENT | agents/<id>/shared | 所有用户共享 |
GLOBAL | global | 全局共享 |
3. JDBC 模式
3.1 适用场景
- 已有
MySQL/PostgreSQL基础设施的团队 - 需要通过 SQL 对存储数据进行查询和管理的场景
- 需要事务性保证(如记忆更新 + 会话日志写入的原子性)
- 企业内部合规要求使用关系型数据库
3.2 表结构
JdbcStore使用一张通用 KV 表,包含乐观锁版本控制:
CREATETABLEIFNOTEXISTSagentscope_store(namespace_pathVARCHAR(300)NOTNULL,-- 命名空间路径,段间用 0x1F 分隔item_keyVARCHAR(200)NOTNULL,-- 条目 Keyvalue_jsonLONGTEXTNOTNULL,-- Jackson 序列化的 Map<String, Object>versionBIGINTNOTNULLDEFAULT0,-- 乐观锁版本号,每次写入 +1updated_atBIGINTNOTNULLDEFAULT0,-- 最后写入时间(epoch millis)PRIMARYKEY(namespace_path,item_key))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;utf8mb4 主键超长问题:MySQL InnoDB 索引键最大 3072 字节。
VARCHAR(300)+VARCHAR(200)= 500 字符 × 4 字节 = 2000 字节,安全落在限制内。JdbcStore自带的 DDL 列宽偏大,建议用schema.sql预建表,并将JdbcStore设为initializeSchema(false)。
3.3 配置案例
importio.agentscope.extensions.mysql.state.MysqlAgentStateStore;importio.agentscope.extensions.mysql.store.JdbcStore;importio.agentscope.harness.agent.DistributedStore;importio.agentscope.harness.agent.HarnessAgent;importio.agentscope.harness.agent.IsolationScope;importio.agentscope.harness.agent.filesystem.spec.RemoteFilesystemSpec;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjavax.sql.DataSource;@ConfigurationpublicclassJdbcWorkspaceConfig{@BeanpublicDistributedStoredistributedStore(DataSourcedataSource){returnDistributedStore.builder()// Agent 会话状态走 MySQL(自动建表).agentStateStore(newMysqlAgentStateStore(dataSource,true))// 工作区 KV 走 MySQL(表由 schema.sql 预建,避免 utf8mb4 主键超长).baseStore(JdbcStore.builder(dataSource).initializeSchema(false).build()).build();}@BeanpublicHarnessAgentharnessAgent(Modelmodel,DistributedStorestore){returnHarnessAgent.builder().name("jdbc-demo").model(model).workspace(Paths.get(".agentscope/workspace")).distributedStore(store).filesystem(newRemoteFilesystemSpec().isolationScope(IsolationScope.USER)).build();}}关键约束:
RemoteFilesystemSpec要求AgentStateStore也是分布式的。直接用本地默认的JsonFileAgentStateStore会在build()时抛IllegalStateException。必须通过DistributedStore.builder()同时提供agentStateStore+baseStore,再通过.distributedStore(store)注入。
3.4 优势
- 无需额外运维组件:复用现有
MySQL集群,不需要引入Redis或其他中间件 - SQL 查询能力:可以直接通过
SQL对存储数据进行查询、统计、清理 - 事务支持:多个
KV操作可在同一事务中原子提交,保证一致性 - 成熟生态:连接池(
HikariCP)、监控(Druid)、分库分表工具链完善 - 合规友好:政企环境通常已有
MySQL运维规范,接入成本低
4. OSS 模式
4.1 适用场景
- 技能包附带大体积脚本/模型文件(数十
MB级别) - 会话日志长期归档(合规留存要求
180天以上) - 多
Region部署需要跨地域共享工作区数据 - 需要低存储成本的海量数据场景
4.2 配置案例
@BeanpublicDistributedStoredistributedStore(){returnOssDistributedStore.create().endpoint("oss-cn-hangzhou.aliyuncs.com").accessKeyId(System.getenv("OSS_ACCESS_KEY_ID")).accessKeySecret(System.getenv("OSS_ACCESS_KEY_SECRET")).bucketName("agentscope-workspace").basePath("prod/")// 可选:统一路径前缀.build();}@BeanpublicHarnessAgentharnessAgent(Modelmodel,DistributedStorestore){returnHarnessAgent.builder().name("oss-demo").model(model).workspace(Paths.get(".agentscope/workspace")).distributedStore(store).filesystem(newRemoteFilesystemSpec().isolationScope(IsolationScope.USER)).build();}4.3 优势
- 低成本海量存储:对象存储的
GB/月成本远低于数据库和Redis - 大文件友好:技能包的脚本、模型文件直接走对象存储,不受
KV大小限制 - 自动扩展:无需关心容量规划,
OSS按需分配 - 跨 Region 同步:
OSS的跨区域复制能力天然支持多Region部署 - 生命周期管理:
OSS的自动归档策略可直接用于过期会话日志清理
5. Redis 模式
5.1 适用场景
- 高并发读写场景(数百
QPS以上) - 需要低延迟(<
1ms)的实时会话 - 多副本共享工作区且对一致性要求不高
- 作为
MySQL/OSS的热数据缓存层
5.2 配置案例
@BeanpublicJedisPooljedisPool(){JedisPoolConfigconfig=newJedisPoolConfig();config.setMaxTotal(50);config.setMaxIdle(10);returnnewJedisPool(config,"localhost",6379);}@BeanpublicDistributedStoredistributedStore(JedisPooljedisPool){returnRedisDistributedStore.builder().jedisPool(jedisPool).keyPrefix("agentscope:")// 可选:Redis Key 前缀.build();}@BeanpublicHarnessAgentharnessAgent(Modelmodel,DistributedStorestore){returnHarnessAgent.builder().name("redis-demo").model(model).workspace(Paths.get(".agentscope/workspace")).distributedStore(store).filesystem(newRemoteFilesystemSpec().isolationScope(IsolationScope.USER)).build();}5.3 优势
- 极低延迟:内存操作,单次读写 <
1ms - 高吞吐:单机可支持数万
QPS,适合高并发Agent服务 - 数据结构丰富:
Hash/List/Sorted Set可用于优化搜索和索引 - 集群支持:
Redis Cluster天然支持水平扩展
6. 混合模式
可以通过DistributedStore.builder()组合不同存储后端,各取所长:
@BeanpublicDistributedStoremixedStore(DataSourcedataSource,JedisPooljedisPool){DistributedStoremysql=MysqlDistributedStore.create(dataSource);DistributedStoreredis=RedisDistributedStore.builder().jedisPool(jedisPool).build();returnDistributedStore.builder().agentStateStore(mysql.agentStateStore())// AgentState 用 MySQL — 需要持久性.baseStore(redis.baseStore())// 工作区文件用 Redis — 需要低延迟.sandboxExecutionGuard(redis.sandboxExecutionGuard())// 沙箱锁用 Redis.build();}7. 自定义 BaseStore 实现
如果以上三种后端都不满足需求,可以实现BaseStore接口接入任意存储:
publicclassPostgresStoreimplementsBaseStore{privatefinalJdbcTemplatejdbc;publicPostgresStore(DataSourcedataSource){this.jdbc=newJdbcTemplate(dataSource);}@OverridepublicStoreItemget(List<String>namespace,Stringkey){Stringns=String.join("/",namespace);returnjdbc.queryForObject("SELECT item_key, value_json, version FROM agentscope_store"+" WHERE namespace_path = ? AND item_key = ?",(rs,i)->newStoreItem(rs.getString("item_key"),parseJson(rs.getString("value_json")),rs.getLong("version")),ns,key);}@Overridepublicvoidput(List<String>namespace,Stringkey,Map<String,Object>value){Stringns=String.join("/",namespace);Stringjson=newObjectMapper().writeValueAsString(value);// UPDATE first, INSERT if not existintupdated=jdbc.update("UPDATE agentscope_store SET value_json = ?, version = version + 1"+" WHERE namespace_path = ? AND item_key = ?",json,ns,key);if(updated==0){jdbc.update("INSERT INTO agentscope_store (namespace_path, item_key, value_json, version)"+" VALUES (?, ?, ?, 1)",ns,key,json);}}@OverridepublicList<StoreItem>search(List<String>namespace,intlimit,intoffset){Stringns=String.join("/",namespace);returnjdbc.query("SELECT item_key, value_json, version FROM agentscope_store"+" WHERE namespace_path LIKE ? ORDER BY namespace_path, item_key LIMIT ? OFFSET ?",(rs,i)->newStoreItem(rs.getString("item_key"),parseJson(rs.getString("value_json")),rs.getLong("version")),ns+"%",limit,offset);}@Overridepublicvoiddelete(List<String>namespace,Stringkey){Stringns=String.join("/",namespace);jdbc.update("DELETE FROM agentscope_store WHERE namespace_path = ? AND item_key = ?",ns,key);}}8. 三种模式对比
| 维度 | Redis | JDBC (MySQL) | OSS |
|---|---|---|---|
| 读写延迟 | < 1ms | 1-5ms | 10-50ms |
| 单 Key 大小限制 | 512MB | 受 JSON 列限制(~1GB) | 无限制 |
| 查询能力 | 仅 Key 前缀扫描 | 完整 SQL | 仅 List 前缀 |
| 事务 | 有限(Lua 脚本) | 完整 ACID | 最终一致 |
| 运维成本 | 需独立 Redis 集群 | 可复用现有 DB | 云服务免运维 |
| 存储成本 | 高(内存) | 中(磁盘) | 低(对象存储) |
| 适合的文件类型 | 小文本、配置 | 结构化数据、JSON | 大文件、归档 |
| 多 Region | 需自行同步 | 需自行主从复制 | 原生跨区域同步 |
