> Labels 建议:`bug` / `harness` / `memory` ## 环境 | | | |---|---| | agentscope-harness | 2.0.2(2.0.0 起即如此) | | 场景 | 调用方显式 `disableMemoryHooks()` + `disableMemoryTools()`,即「完全不使用长期记忆」 | ## 问题 调用 `HarnessAgent.Builder.disableMemoryHooks()` 后,`MemoryFlushMiddleware` 与 `MemoryMaintenanceMiddleware` 都不会注册,看起来记忆功能已彻底关闭。 但 `CompactionMiddleware` **自己 new 了一个 `MemoryFlushManager`**,完全不看这个开关: ```java // CompactionMiddleware.java:102-104 MemoryFlushManager flushManager = new MemoryFlushManager(workspaceManager, model); ConversationCompactor compactor = new ConversationCompactor(model, flushManager); ``` 而 `CompactionConfig` 的两个相关默认值**都是 true**: ```java // CompactionConfig.java:273/282/283 private int triggerMessages = 50; private boolean flushBeforeCompact = true; private boolean offloadBeforeCompact = true; ``` 于是在「记忆已关闭」的前提下,只要会话长到 `triggerMessages`(默认 50 条),压缩前仍会: 1. **执行一次记忆抽取** —— 额外一次 LLM 调用,并把结果写进 `memory/<date>.md` 2. **把整份 messages offload 进 session JSONL** ## 实际后果(我们踩到的) 我们的服务在很长一段时间里都是「记忆关闭」状态(`disableMemoryHooks()` + `disableMemoryTools()`),workspace 挂在 NAS 上。后来准备启用长期记忆、检查 NAS 时,发现上面**已经躺着一批记忆日更文件和会话 JSONL** —— 全部是这条路径在「记忆已关闭」期间写出来的。 也就是说: - **成本**:每次触发压缩都多一次 LLM 调用,而调用方认为自己关闭了这项功能 - **数据**:产生了从未被审阅、也不预期存在的记忆文件;一旦后续启用记忆,这些旧文件会被 `memory_search` 之类的入口读到 - **认知**:`disableMemoryHooks()` 的语义被违反,且没有任何日志提示 ## 复现 1. `HarnessAgent.builder().disableMemoryHooks().disableMemoryTools()`,不设置 `compaction(...)` 2. 跑一个超过 50 条消息的会话,触发压缩 3. 观察 workspace:`memory/<date>.md` 被创建;同时可观察到一次额外的 LLM 调用 ## 规避方式(供其他遇到的人参考) 必须**无条件显式**设置 compaction 配置,不能依赖默认值: ```java builder.compaction(CompactionConfig.builder() .flushBeforeCompact(false) .offloadBeforeCompact(false) .build()); ``` ## 建议 任一即可: - `CompactionMiddleware` 中的 flush / offload 受 `disableMemoryHooks` 约束(关闭时跳过,等价于把两个 flag 视为 false) - 或者把 `MemoryFlushManager` 由外部注入,让它与其他记忆组件共享同一套开关 - 最低限度:在 `disableMemoryHooks()` 与 `CompactionConfig` 的 javadoc 里写明「compaction 内部的 flush/offload **不受** `disableMemoryHooks` 管辖,需单独关闭」 个人认为第一种最符合直觉:一个名为 "disable memory hooks" 的开关,不应该留下一条仍在写记忆文件的路径。
Activity