第十一章 中断与恢复用 AgentStateStore 持久化会话sessionId 实现断点续聊用户问了 3 轮突然关掉浏览器20 分钟后用同一个会话 ID 回来——上一轮的上下文、todo、subagent 状态怎么全在这是 2.0 的AgentStateStore后端JsonFileAgentStateStore/RedisAgentStateStore/MysqlAgentStateStore干的事比 1.x 自己把Memory序列化 JSON 简单很多。本章你将学到如何让HarnessAgent在中断后秒级恢复、如何在前端用sessionId做断点续聊、以及如何配合Plan Mode让用户的待办清单也跟着恢复。11.1 中断与恢复的本质1.x 时代中断恢复 要自己做写一个Memory的 JSON 序列化器启动时检查 session 目录有旧 JSON 就 load 进Memory手动管理 session ID 与 cookie / token 的对应2.0 把这件事下沉到AgentStateStore后端——HarnessAgent调用时根据RuntimeContext.sessionId()去后端查状态查到就 load 进AgentState查不到就当新会话。业务代码不需要写任何序列化逻辑。11.2 第一个可恢复会话import io.agentscope.core.agent.RuntimeContext; import io.agentscope.core.message.UserMessage; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.state.JsonFileAgentStateStore; import io.agentscope.harness.HarnessAgent; import java.nio.file.Path; import java.util.List; public class Chapter11_Resume { public static void main(String[] args) { // 用同一个 sessionId 模拟用户先问一通再回来问 String sessionId demo-user-9527-2026-06-07; HarnessAgent agent HarnessAgent.builder() .name(assistant) .sysPrompt(你是一个中文助理简洁回答。) .model(DashScopeChatModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .modelName(qwen-plus) .build()) .workspace(Path.of(./workspace)) .stateStore(new JsonFileAgentStateStore(Path.of(./workspace))) // 文件版 AgentStateStore .build(); RuntimeContext ctx RuntimeContext.builder() .sessionId(sessionId) .userId(9527) .build(); // ---- 第一通对话 ---- agent.call(List.of(new UserMessage(user, 我叫小李在杭州做程序员。)), ctx).block(); agent.call(List.of(new UserMessage(user, 我用什么语言开发)), ctx).block(); // 假装用户关掉浏览器。20 分钟后 JVM 重新跑 main() —— 实际生产里这是另一个进程。 // ---- 20 分钟后用同一 sessionId 接着问 ---- agent.call(List.of(new UserMessage(user, 我叫什么在哪个城市)), ctx).block(); // 输出会包含你叫小李在杭州做程序员。 } }第一次跑[reply] 你好小李很高兴认识你。 [reply] 根据你刚才提供的信息你在杭州做程序员。关掉进程删掉进程内变量第二次跑同一个workspace、同一个sessionId[reply] 早上好小李。有什么可以帮你 [reply] 你叫小李在杭州做程序员。——完整恢复。11.3 三个常见的断点续聊场景11.3.1 浏览器刷新最简单前端把sessionId存localStorage刷新后从localStorage取出、塞回请求参数。后端完全无感。let sessionId localStorage.getItem(sessionId); if (!sessionId) { sessionId crypto.randomUUID(); localStorage.setItem(sessionId, sessionId); } fetch(/api/agent/chat?sessionId${sessionId}, { method: POST, body: JSON.stringify({ text: 我叫什么 }), });11.3.2 用户切换设备用户在手机问了一通到电脑上想接着聊。把手机端的sessionId暴露成会话恢复码让用户在电脑端输入后端用同一个sessionId调 agent。注意AgentStateStore不感知设备。要加一层 user_id → session_id 的映射自己做或者用 Spring Session。11.3.3 用户主动重置提供清空对话按钮——后端开新sessionIdPostMapping(/session/reset) public MapString, String reset(RequestParam String userId) { String newId user- userId - System.currentTimeMillis(); return Map.of(sessionId, newId); }老会话状态会自然过期按AgentStateStore后端的 TTL 清理。11.4 Plan Mode 与 todo 的恢复如果开了 Plan ModeHarnessAgent.enablePlanMode()todo_write写下的待办也会被AgentStateStore持久化。例HarnessAgent agent HarnessAgent.builder() ... .enablePlanMode(true) .build();用户在第一通对话里让 agent 计划调研 5 件事——agent_todo会被持久化用户回来时上次我让你调研的 5 件事现在做到哪了agent 会读AgentStateStore里的 todo 列表把状态念给用户听。11.5 用 RedisAgentStateStore 做生产级恢复JsonFileAgentStateStore适合开发与单机部署多实例 / 多机部署要用RedisAgentStateStore见第 5 章HarnessAgent agent HarnessAgent.builder() .stateStore(RedisAgentStateStore.builder() .jedisClient(new UnifiedJedis(redis://127.0.0.1:6379)) .build()) .build();恢复流程一模一样区别只在状态存在 Redis 而非本地 JSON。11.6 主动重连 vs 自动重连场景推荐策略用户离开 5 分钟不主动重连——AgentStateStore一直保留用户回来直接续30 分钟未活动由前端弹是否继续按钮避免 token 浪费1 小时未活动后端 cron 清理 Redis 里的旧 session24 小时未活动强制清空保留MEMORY.md长期记忆RedisAgentStateStore可以设 TTLJsonFileAgentStateStore可以用Files.deleteIfExists自己写清理脚本。11.7 完整可运行示例import io.agentscope.core.agent.RuntimeContext; import io.agentscope.core.message.UserMessage; import io.agentscope.core.model.DashScopeChatModel; import io.agentscope.core.state.JsonFileAgentStateStore; import io.agentscope.harness.HarnessAgent; import java.nio.file.Path; import java.util.List; import java.util.concurrent.TimeUnit; public class Chapter11_Interrupted { public static void main(String[] args) throws InterruptedException { String sessionId u-9527-2026-06-07; HarnessAgent agent HarnessAgent.builder() .name(assistant) .sysPrompt(你是一个中文助理。) .model(DashScopeChatModel.builder() .apiKey(System.getenv(DASHSCOPE_API_KEY)) .modelName(qwen-plus) .build()) .workspace(Path.of(./workspace)) .stateStore(new JsonFileAgentStateStore(Path.of(./workspace))) .build(); RuntimeContext ctx RuntimeContext.builder() .sessionId(sessionId).userId(9527).build(); // ---- Round 1 ---- agent.call(List.of(new UserMessage(user, 我养了一只橘猫叫小年。)), ctx).block(); agent.call(List.of(new UserMessage(user, 它叫什么)), ctx).block(); System.out.println( 模拟用户关闭浏览器 5 秒 ); TimeUnit.SECONDS.sleep(5); // ---- Round 2模拟再回来 ---- agent.call(List.of(new UserMessage(user, 我养了什么宠物)), ctx).block(); } }11.8 本章小结AgentStateStore是 2.0 中断恢复的核心业务代码无需写序列化。同一sessionId 同一AgentStateStore后端 上下文自动恢复。Plan Mode的 todo 也会被AgentStateStore持久化。生产用RedisAgentStateStore开发用JsonFileAgentStateStore。RC2 中 Agent 完全无状态化——同一实例可安全并发服务多(userId, sessionId)组合。