尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AgentScope Java Harness:4. 双层记忆系统 让 Agent 拥有真正的“长期大脑“

AgentScope Java Harness:4. 双层记忆系统 让 Agent 拥有真正的“长期大脑“ 目录1. 如何优雅地驾驭长期运行的 AI Agent2. 上下文压缩让长期 Agent 永不“失忆“3. 工作区Workspace文件即真理目录即架构4. 双层记忆系统 让 Agent 拥有真正的“长期大脑“5. 文件系统一套代码三种部署零改动切换6. 沙箱Sandbox让 Agent 在安全笼子里自由奔跑7. 子 Agent 编排 文件驱动的多智能体协作架构8. Skill技能让 Agent 从“会说话“进化为“会做事“9. Plan Mode 让 Agent 先想清楚再动手10. Channel Agent 通信的“神经系统“设计对话会结束但记忆不应该。AgentScope Harness 用日流水账 精炼笔记的双层架构让 Agent 在跨会话、跨进程、跨天的场景下依然记得你。一、引言金鱼记忆的 Agent 走不远想象这样一个场景用户“帮我订明天去上海的航班靠窗。”Agent“好的已为您查询到 3 个航班…”——第二天——用户“还是老规矩。”Agent“抱歉请问您指的是什么”这就是大多数 Agent 的真实困境**没有跨会话记忆。**每次对话都是一张白纸用户不得不反复交代偏好、背景和历史决策。更棘手的是即使有了某种记忆机制还面临两个工程难题**信息爆炸**对话越积越多全部塞进 prompt 会撑爆上下文窗口信息噪声大量对话是寒暄和确认真正有价值的事实可能只占 5%。AgentScope Java 2.0 的 Harness 模块用一套双层记忆架构优雅地解决了这些问题。二、架构全景双层存储 三个 LLM 角色2.1 整体架构图┌─────────────────────────────────────────────────────────────────┐ │ 每轮推理 │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ WorkspaceContextHook │ │ │ │ 读取 MEMORY.md → 注入 system prompt全局背景知识 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ ReAct 推理循环对话 工具调用 │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ MemoryFlushMiddleware │ │ │ │ 对话结束 → 提取有价值事实 → 追加到 memory/YYYY-MM-DD.md │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ MemoryConsolidator后台定时任务 │ │ │ │ 读取多日流水账 → LLM 合并/去重/提炼 → 更新 MEMORY.md │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘2.2 双层存储设计层级存储位置数据属性写入方式读取时机第一层日流水账memory/YYYY-MM-DD.md原始、完整、未清洗只追加append-onlymemory_search 工具按需检索第二层精炼记忆MEMORY.md合并、去重、提炼后后台 LLM 周期性覆写每轮推理自动注入 system prompt设计精妙之处第一层负责**“节流”**——控制每轮注入 prompt 的 token 量只有精炼后的 MEMORY.md 被注入第二层负责**“开源”**——确保任何有价值的信息都不会丢失原始流水账永久保留两层之间由 MemoryConsolidator 后台任务桥接对业务完全透明。三、第一层日流水账memory/YYYY-MM-DD.md3.1 存储规则!-- memory/2026-08-11.md -- ## 15:32 - 用户偏好更新 - 用户张三确认偏好靠窗座位 - 出差报销标准经济舱单程不超过 2000 元 ## 16:45 - 项目决策 - 决定将数据迁移窗口定在周六凌晨 2:00-6:00 - 原因工作日 QPS 峰值在下午 2-5 点迁移风险大关键规则规则说明按日期分文件每天一个 YYYY-MM-DD.md自然归档只追加不修改、不删除已有内容保证审计可追溯不做清洗原始事实直接写入不去重、不合并增量写入每次 Flush 只追加新提取的事实3.2 为什么是只追加这是一个深思熟虑的工程决策并发安全多个会话可能同时写入同一天的文件追加操作天然无冲突审计友好任何时间点都能回溯Agent 当时知道了什么幂等性即使 Flush 重复执行最坏情况是多写一条不会覆盖已有信息解耦清洗和合并的职责交给 ConsolidatorFlush 只管记下来。四、第二层精炼记忆MEMORY.md4.1 生成逻辑MemoryConsolidator 是一个后台定时调度器周期性地调用 LLM 执行合并MemoryConsolidatorconsolidatornewMemoryConsolidator(model,// 用于合并摘要的 LLM 模型memoryConfig// 记忆配置);// 启动后台调度默认每小时执行一次合并consolidator.start();// 也可以手动触发一次合并consolidator.consolidate();4.2 合并流程memory/2026-08-09.md ─┐ memory/2026-08-10.md ─┼──▶ Consolidation LLM ──▶ MEMORY.md memory/2026-08-11.md ─┘ │ ├── 合并同类信息归并 ├── 去重重复事实只保留一条 ├── 提炼去除寒暄、确认等噪声 └── 归纳抽象为可复用的知识点4.3 MEMORY.md 示例# Long-term Memory ## 用户偏好 - 张三靠窗座位、经济舱、报销上限 2000 元/程 - 李四偏好早班机8:00 前、素食餐 ## 项目约定 - 数据迁移窗口周六 02:00-06:00避开工作日下午峰值 - 代码审查所有 SQL 变更必须经 DBA 审批 ## 技术决策 - 2026-08-05选定 RocketMQ 替代 Kafka团队更熟悉运维 - 2026-08-10前端框架升级为 React 194.4 注入时机每轮推理启动时WorkspaceContextHook 自动将 MEMORY.md 的内容拼入 system prompt[System Prompt] ├── AGENTS.md人格与行为约定 ├── MEMORY.md精炼长期记忆 ← 这里 ├── knowledge/领域知识 └── skills/技能描述 关键设计只有精炼后的 MEMORY.md 被注入而非所有流水账。这确保了注入的 token 量可控同时关键信息不丢失。五、MemoryFlush事实提取的三大触发时机MemoryFlushMiddleware 负责从对话中提取有价值的事实并写入流水账。它有三个触发时机5.1 触发时机一每轮对话正常结束用户提问 → Agent 推理 → 工具调用 → 返回结果 │ ▼ MemoryFlushMiddleware 提取本轮新事实 追加到 memory/2026-08-11.md这是最常见的触发路径。一轮问答/工具调用完整跑完后中间件自动执行 Flush。5.2 触发时机二对话压缩前置预提取上下文达到压缩阈值 │ ▼ flushBeforeCompact true默认 │ ├── MemoryFlush先提取即将被压缩的消息中的事实 │ └── Compaction再执行摘要压缩为什么需要这一步压缩会把前缀消息替换为摘要细节必然丢失。在压缩前先把有价值的事实抢救到流水账中确保信息不因压缩而永久丢失。5.3 触发时机三上下文溢出兜底紧急 Flush模型返回 context_length_exceeded │ ▼ HarnessAgent.recoverFromOverflow() │ ├── 紧急 Flush尽最大努力提取当前上下文中的事实 │ └── 极端压缩triggerMessages1只保留最近 1 条这是最后防线——即使真的溢出了也要在丢弃消息前尽可能保存信息。5.4 flushTrigger 策略配置MemoryConfig.builder().flushTrigger(FlushTrigger.ALWAYS)// 默认每轮都提取// .flushTrigger(FlushTrigger.ON_COMPACT) // 仅在压缩时提取// .flushTrigger(FlushTrigger.NEVER) // 从不自动提取.build();策略行为适用场景ALWAYS默认每轮对话结束都提取事实需要精细记忆的场景ON_COMPACT仅在压缩触发时才提取成本敏感减少 LLM 调用NEVER从不自动提取完全依赖手动 Tool 写入六、三个 LLM 角色分工Harness 记忆系统涉及三个独立的 LLM 调用角色各司其职角色职责触发时机输入输出Flush LLM从对话中提取有价值事实每轮结束 / 压缩前 / 溢出时本轮对话消息结构化事实列表Consolidation LLM合并多日流水账为精炼记忆后台定时默认每小时多日 memory/*.md 现有 MEMORY.md更新后的 MEMORY.mdCompaction LLM压缩当前对话窗口消息数/token 达到阈值待压缩的消息前缀摘要文本为什么要分三个角色关注点分离提取事实 ≠ 合并记忆 ≠ 压缩对话三者的 prompt 和评判标准完全不同独立调优可以为每个角色指定不同的模型如 Flush 用轻量模型Consolidation 用旗舰模型独立调度Flush 是同步的跟随对话Consolidation 是异步的后台定时Compaction 是条件触发的。七、记忆检索工具Agent 主动回忆除了 MEMORY.md 的自动注入Harness 还注册了记忆检索工具让 Agent 可以主动查阅历史7.1 内置记忆工具工具功能数据来源memory_search关键词搜索记忆内容memory/*.md流水账memory_get获取指定日期的完整记忆memory/YYYY-MM-DD.mdsession_search搜索历史会话原文agents//sessions/*.log.jsonl7.2 使用场景用户上次我们讨论的数据库迁移方案最终定的几点执行 Agent 内部推理 → 当前上下文中没有这个信息可能已被压缩 → 调用 memory_search(数据库迁移 执行时间) → 命中 memory/2026-08-05.md 中的记录 → 回复最终定在周六凌晨 2:00-6:00 执行...7.3 与 MEMORY.md 注入的互补关系┌─────────────────────────────────────────────────────────┐ │ MEMORY.md 自动注入 │ │ → 高频、精炼、全局背景知识 │ │ → 每轮都有零成本已在 prompt 中 │ ├─────────────────────────────────────────────────────────┤ │ memory_search / memory_get 主动检索 │ │ → 低频、细节、特定历史事实 │ │ → 按需调用有工具调用开销 │ └─────────────────────────────────────────────────────────┘这种热数据自动注入 冷数据按需检索的分层策略在 token 效率和信息完整性之间取得了最佳平衡。八、完整工作流程8.1 链路一正常单轮对话无压缩触发用户发送消息 │ ▼ WorkspaceContextHook │ 读取 MEMORY.md → 注入 system prompt │ ▼ ReAct 推理循环 │ 思考 → 调用工具 → 生成回复 │ ▼ MemoryFlushMiddleware │ 提取本轮新事实 │ 追加到 memory/2026-08-11.md │ ▼ 返回响应给用户 ...后台... ▼ MemoryConsolidator定时触发 读取近几日流水账 LLM 合并 → 更新 MEMORY.md8.2 链路二触发对话压缩用户发送消息 │ ▼ WorkspaceContextHook │ 读取 MEMORY.md → 注入 system prompt │ ▼ 压缩检测消息数 ≥ 阈值 │ ├── ① MemoryFlushflushBeforeCompact │ 提取即将被压缩的消息中的事实 │ 追加到 memory/YYYY-MM-DD.md │ ├── ② OffloadBeforeCompact │ 原始消息写入 sessions/*.log.jsonl永不压缩 │ ├── ③ Compaction LLM │ 前缀消息 → 结构化摘要 │ └── ④ 写回 AgentState [summary] [recent tail] │ ▼ ReAct 推理循环使用压缩后的上下文 │ ▼ 返回响应给用户8.3 完整生命周期视图Day 1: 对话 → Flush → memory/2026-08-09.md Day 2: 对话 → Flush → memory/2026-08-10.md Day 3: 对话 → Flush → memory/2026-08-11.md │ ▼ Consolidation后台定时 │ MEMORY.md 更新合并 Day1-3 的事实 │ ▼ 下一轮推理 │ MEMORY.md 注入 system prompt Agent 记得 Day 1-3 的关键信息九、配置实战9.1 最简配置开启默认记忆HarnessAgentagentHarnessAgent.builder().name(assistant).model(newOpenAIChatModel(apiKey,gpt-4o)).workspace(Path.of(./workspace)).memory(MemoryConfig.defaults())// 开启双层记忆.build();9.2 进阶配置精细控制HarnessAgentagentHarnessAgent.builder().name(travel-assistant).model(newOpenAIChatModel(apiKey,gpt-4o)).workspace(Path.of(./workspace))// 记忆配置.memory(MemoryConfig.builder().flushTrigger(FlushTrigger.ALWAYS)// 每轮都提取.consolidationInterval(Duration.ofHours(1))// 每小时合并一次.maxMemoryTokens(4000)// MEMORY.md 注入上限.build())// 压缩配置与记忆联动.compaction(CompactionConfig.builder().triggerMessages(30).keepMessages(10).flushBeforeCompact(true)// 压缩前先 Flush默认 true.offloadBeforeCompact(true)// 压缩前先备份默认 true.build()).build();9.3 成本优化配置用轻量模型做 FlushHarnessAgentagentHarnessAgent.builder().name(assistant).model(expensiveModel)// 主推理用旗舰模型.workspace(Path.of(./workspace)).memory(MemoryConfig.builder().flushModel(cheapModel)// Flush 用轻量模型.consolidationModel(cheapModel)// 合并也用轻量模型.flushTrigger(FlushTrigger.ON_COMPACT)// 只在压缩时提取减少调用.build()).build();十、与 1.x LongTermMemory 的对比AgentScope Java 1.x 使用 LongTermMemory 接口及其实现如 Mem0LongTermMemory2.0 中已被彻底替代维度1.x LongTermMemory2.0 Harness 双层记忆存储向量数据库 / 外部服务工作区文件Markdown观测性需要查询接口直接打开文件版本管理不支持天然 Git 友好与压缩联动无flushBeforeCompact 原生集成Agent 自主写入有限Tool 主动写 自动 Flush多租户隔离需要自行实现工作区目录天然隔离人机共编不支持人类可直接编辑 MEMORY.md2.0 的设计哲学记忆不是外部服务的附属品而是工作区的一等公民。十一、与其他子系统的协作关系记忆系统不是孤立存在的它与 Harness 的其他子系统紧密协作┌─────────────────────────────────────────────────────────────┐ │ Harness 记忆生态 │ │ │ │ ┌──────────┐ flushBeforeCompact ┌──────────────┐ │ │ │ 压缩链路 │ ──────────────────────▶ │ 记忆 Flush │ │ │ │Compaction│ │MemoryFlush │ │ │ └──────────┘ └──────┬───────┘ │ │ │ │ │ ▼ │ │ ┌──────────┐ 定时合并 ┌──────────────┐ │ │ │Consolidator│ ◀──────────────────── │memory/*.md │ │ │ └─────┬────┘ └──────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────┐ 每轮注入 ┌──────────────┐ │ │ │MEMORY.md │ ──────────────────────▶ │System Prompt │ │ │ └──────────┘ └──────────────┘ │ │ │ │ ┌──────────┐ 按需检索 ┌──────────────┐ │ │ │memory_search│ ◀──────────────────── │ Agent 推理 │ │ │ │memory_get │ └──────────────┘ │ │ └──────────┘ │ │ │ │ ┌──────────┐ 全量备份 ┌──────────────┐ │ │ │session日志│ ◀────────────────────── │offloadBefore │ │ │ │.log.jsonl│ │ Compact │ │ │ └──────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘协作方关系压缩链路压缩前触发 Flush确保信息不丢失Session 存储原始对话备份到 .log.jsonl供 session_search 检索Workspace记忆文件是工作区的一部分共享文件系统抽象子 Agent子 Agent 的决策也可被 Flush 记录沙箱记忆文件通过 AbstractFilesystem 路由沙箱内外一致十二、设计哲学与工程启示12.1 先记后炼宽进严出流水账宽进——不做过滤宁可多记MEMORY.md严出——经过 LLM 提炼只保留高价值信息。这种先全量记录、后选择性注入的策略在信息完整性和 token 效率之间取得了最佳平衡。12.2 文件即记忆人机共编记忆不是锁在数据库里的黑盒而是工作区中的 Markdown 文件。这意味着开发者可以直接编辑 MEMORY.md 修正错误记忆产品经理可以预置领域知识Agent 自己可以在运行时更新记忆自我进化。12.3 节流闸门按需触发Flush 和 Consolidation 都有节流机制不会每轮都触发 LLM 调用。特别是 FlushTrigger.ON_COMPACT 策略可以大幅降低 Flush 的调用频率适合成本敏感场景。12.4 与压缩深度联动flushBeforeCompact 这个看似简单的布尔值实际上是整个记忆系统最关键的安全阀。它确保了无论压缩如何激进有价值的事实永远有逃生通道。12.5 三层记忆模型从更宏观的视角看Harness 实际上实现了一个三层记忆模型层级对应人类记忆实现生命周期工作记忆当前正在想的事AgentState.contextMutable()单次调用情景记忆昨天发生了什么memory/.md sessions/.log.jsonl永久语义记忆我知道的事实和规则MEMORY.md永久持续更新十三、最佳实践清单场景推荐配置个人助手对话轮次少MemoryConfig.defaults() 即可企业客服多用户、高频flushTrigger(ALWAYS) 每小时 Consolidation编码 Agent长对话flushTrigger(ALWAYS) flushBeforeCompact(true) 压缩成本敏感flushTrigger(ON_COMPACT) 轻量模型做 Flush需要人工干预记忆直接编辑 MEMORY.md下轮立即生效多租户 SaaS每个 userId 独立工作区记忆天然隔离十四、结语AgentScope Harness 的双层记忆系统回答了一个根本问题如何让 Agent 在有限的上下文窗口中拥有无限的长期记忆答案是不要试图把所有东西塞进窗口而是建立一套记录 → 提炼 → 注入 → 检索的完整生命周期。流水账确保不丢失Consolidation 确保不膨胀自动注入确保不遗忘检索工具确保可回忆。这四个环节形成闭环让 Agent 真正拥有了跨会话、跨天、跨进程的长期大脑。如果你正在构建需要记住用户的 Agent 系统这套架构设计值得深入研究和借鉴。
返回列表