3.5 对比学习:三个框架怎么处理记忆
前两节讲了我自己的记忆系统是怎么演进的。这一节换一个视角:把微软 MAF、阿里 AgentScope、Hermes 三个框架的记忆机制拆开来看,对比它们的设计选择,以及我从中学到了什么。
微软 Agent Framework (MAF):压缩系统
MAF 的记忆设计围绕一个核心概念:CompactionSystem(压缩系统)。
它不关心"记忆是什么",它关心的是"上下文快满了怎么办"。
压缩策略矩阵
MAF 提供了 7 种压缩策略,可以组合使用:
| 策略 | 原理 | 适用场景 |
|---|---|---|
| TruncationCompactionStrategy | 简单截断,删除最早的消息 | 最简单场景,不需要保留历史 |
| SlidingWindowCompactionStrategy | 滑动窗口,保留最近 N 条 | 通用场景 |
| SummarizationCompactionStrategy | 用 LLM 生成摘要替换旧消息 | 需要保留关键信息的长对话 |
| ToolResultCompactionStrategy | 压缩工具调用的结果(通常很大) | 工具密集型 Agent |
| ContextWindowCompactionStrategy | 根据上下文窗口大小自动触发 | Token 预算敏感场景 |
| PipelineCompactionStrategy | 多种策略串联执行 | 复杂场景 |
| ChatReducerCompactionStrategy | 用 LLM 减少消息数量 | 需要精细控制 |
核心设计:CompactionProvider
MAF 的压缩系统本身就是一个 AIContextProvider——它挂在上下文提供者链上,每次调用 LLM 前自动检查是否需要压缩:
用户发消息
↓
CompactionProvider 检查触发条件(Token 超阈值?消息数超限?)
↓
如果需要压缩:
├── 把消息按"原子组"分组(tool_call + tool_result 不能拆开)
├── 应用压缩策略(截断 / 摘要 / 工具结果压缩)
└── 返回压缩后的消息列表
↓
其他 AIContextProvider 执行
↓
调用 LLMSummarizationCompactionStrategy 是最值得关注的。它的压缩 Prompt 是这样的:
You are a conversation summarizer. Produce a concise summary that preserves:
- Key facts, decisions, and user preferences
- Important context needed for future turns
- Tool call outcomes and their significance
Omit pleasantries and redundant exchanges. Be factual and brief.注意它保护了最近的 8 组消息(MinimumPreservedGroups = 8)不被压缩——这是一个硬性底线,即使 Token 还没超,也不会动最近的对话。
MAF 的向量记忆
除了压缩,MAF 还有一个 ChatHistoryMemoryProvider——把对话历史存到向量数据库里,下次需要时通过语义搜索召回相关的历史对话。
这个设计思路跟我的完全不同。MAF 走的是"用计算换检索":Embedding → 向量存储 → 语义搜索。我走的是"用结构换检索":L0/L1/L2 分层 → 文本匹配。
我从 MAF 学到了什么
原子组保护:tool_call 和 tool_result 是一对,压缩时不能拆开。我的 CompactionEngine 没考虑这个问题,MAF 的
CompactionMessageIndex专门处理了这个。策略可组合:7 种策略可以串联成 Pipeline。比如先压缩工具结果,再做摘要,最后截断。这种组合性比我"一个 CompactionEngine 打天下"灵活得多。
LLM 失败时的回滚:SummarizationCompactionStrategy 在 LLM 摘要失败时,会把被标记为"已排除"的消息组恢复原状。这个容错设计很优雅——我的 CompactionEngine 失败时直接降级为简单截断,没有回滚。
阿里 AgentScope:短期 + 长期 + RAG 三件套
AgentScope 的记忆架构最"完整"——它把记忆分成了明确的短期和长期,还外加了 RAG 知识库。
短期记忆
短期记忆就是 InMemoryMemory——一个内存消息列表,没有压缩能力,消息会无限增长。
java
ReActAgent agent = ReActAgent.builder()
.name("Assistant")
.model(model)
.memory(new InMemoryMemory())
.build();简单粗暴。如果需要持久化,配合 SessionManager 序列化到文件。
长期记忆
长期记忆是独立组件,有两个实现:
- Mem0LongTermMemory:对接 Mem0 服务(云端或自建),自动提取和召回记忆
- ReMeLongTermMemory:对接 ReMe 服务,阿里自研的记忆引擎
长期记忆的工作方式很清晰:
用户发消息
↓
长期记忆.retrieve(msg) → 召回相关记忆 → 注入短期记忆
↓
LLM 推理
↓
长期记忆.record(msgs) → 异步存入新记忆
↓
回复用户长期记忆有两种控制模式:
- STATIC_CONTROL:框架自动在推理前召回、回复后记录
- AGENT_CONTROL:通过工具让 Agent 自主决定何时记录和召回
RAG 知识库
AgentScope 的 RAG 支持是最丰富的——4 种知识库实现:
| 类型 | 实现 | 适用场景 |
|---|---|---|
| 本地知识库 | SimpleKnowledge | 开发测试,完全控制数据 |
| 百炼知识库 | BailianKnowledge | 企业级,多轮对话 |
| Dify 知识库 | DifyKnowledge | 多种检索模式 |
| RAGFlow 知识库 | RAGFlowKnowledge | OCR、知识图谱 |
RAG 还有两种集成模式:
- Generic 模式:每次推理前自动检索注入(被动)
- Agentic 模式:Agent 通过工具决定何时检索(主动)
我从 AgentScope 学到了什么
短期/长期分离是正确的设计。短期记忆管"这次对话说了什么",长期记忆管"这个用户是谁"。我的
Messages + Facts其实也是这个思路,但 AgentScope 把它做成了显式的接口分离。记忆的控制权可以交给 Agent。
AGENT_CONTROL模式让 Agent 自己决定什么时候记、什么时候查。这比"框架自动管理"更灵活——Agent 知道什么时候信息重要,什么时候不需要记。RAG 的 Generic vs Agentic 模式是个好思路。Generic 是"每次都查",Agentic 是"需要时才查"。对于编码场景,Agentic 模式更合适——不是每句话都需要查知识库。
Hermes:记忆提供者生态
Hermes 的记忆设计跟另外三个完全不同。它不做"记忆系统",它做"记忆生态"。
MemoryProvider 抽象
Hermes 的核心抽象是 MemoryProvider——一个有完整生命周期的插件:
initialize() → 启动时连接后端
system_prompt_block() → 静态文本注入系统提示词
prefetch(query) → 每轮对话前,主动召回相关记忆
sync_turn(user, asst) → 每轮对话后,异步写入新记忆
on_session_end() → 会话结束时,提取关键事实
on_pre_compress() → 压缩前,抢救即将被丢弃的信息
on_session_switch() → 会话切换时,刷新状态
on_delegation() → 子 Agent 完成时,观察结果
shutdown() → 退出时清理这个生命周期覆盖了记忆的完整链路:从初始化到召回、写入、抢救、清理。
内置 + 插件
Hermes 的记忆提供者分两层:
- 内置提供者(builtin):始终存在,处理基础记忆功能
- 外部提供者(插件):最多一个,可以是 Honcho、Mem0、Hindsight 等
为什么限制只允许一个外部提供者?Hermes 的设计者认为:多个记忆后端会导致工具 Schema 膨胀和记忆冲突。一个内置 + 一个外部,够了。
记忆的主动召回

Hermes 最核心的设计是主动召回:
用户发消息:"帮我看看数据库连接池的配置"
↓
MemoryManager.prefetch_all("帮我看看数据库连接池的配置")
↓
内置提供者:根据关键词召回 → "项目用 PostgreSQL,连接池最大 100"
外部提供者:根据语义召回 → "上次连接池溢出是因为 max_connections 设太大"
↓
两段记忆合并,包裹在 <memory-context> 标签里注入上下文
↓
调用 LLM注意 <memory-context> 标签的设计——它告诉 LLM:"这是你的记忆,不是用户输入。" 这防止了 LLM 把记忆内容当成用户的新指令。
压缩前的抢救
Hermes 有一个独特的钩子:on_pre_compress()。
在上下文压缩之前,记忆提供者有机会"抢救"即将被丢弃的信息。这就像人在整理桌子时,先把重要的东西拿出来放好,再扔剩下的。
我从 Hermes 学到了什么
记忆是管道,不是仓库。Hermes 的
MemoryProvider不是一个存储后端,而是一个有生命周期的管道——initialize → prefetch → sync → shutdown。信息在这个管道里流动,而不是堆在仓库里。主动召回是关键。不是把所有记忆都塞给 LLM,而是在每轮对话前,根据用户当前说的话,精准召回最相关的记忆。这是"越用越懂你"的核心机制。
压缩前抢救。
on_pre_compress()这个钩子非常巧妙。我的 OpenViking 的 CompactionEngine 也做了类似的事(压缩前生成摘要),但没有把它做成一个显式的钩子让外部扩展。标签隔离。
<memory-context>标签告诉 LLM "这是记忆,不是用户输入"——这个设计防止了记忆注入被当成指令执行。安全细节很重要。
三个框架的对比

| 维度 | MAF | AgentScope | Hermes |
|---|---|---|---|
| 记忆分类 | 压缩系统 + 向量记忆 | 短期 + 长期(显式分离) | 内置 + 外部(插件式) |
| 压缩方式 | 7 种策略可组合 | 无内置压缩 | 压缩前抢救钩子 |
| 检索方式 | 向量语义搜索 | Mem0/ReMe 外部服务 | 关键词 + 语义混合召回 |
| RAG 支持 | 无内置 | 4 种知识库实现 | 无内置(靠插件) |
| 扩展性 | 策略模式组合 | 接口 + 外部服务 | 插件生态 |
| 核心洞察 | 上下文快满了怎么办 | 短期管对话,长期管用户 | 记忆是管道不是仓库 |
我的选择
对比完三个框架,我做了几个决策:
压缩策略:借鉴 MAF 的 SummarizationCompactionStrategy,但简化为"有 LLM 就智能压缩,没有就简单截断"。不需要 7 种策略矩阵,我的场景没那么复杂。
记忆结构:借鉴 OpenViking 的 L0/L1/L2 三层结构,而不是 AgentScope 的短期/长期显式分离。原因:三层结构更灵活——一条短信息可以 L0=L1=L2,一条长信息可以三层分开。
召回机制:借鉴 Hermes 的 prefetch + sync_turn 模式。每轮对话前主动召回,每轮对话后异步写入。但实现上简化了——我用加权文本匹配代替了 Hermes 的混合召回。
RAG:不用。用 RepoWiki + AGENTS.md 的"结构化知识摘要"方案代替。原因在第 3.3 节讲过了。
关键认知

对比学习的最大收获不是“抄作业”,是理解每个设计选择背后的权衡:
- MAF 选择了"策略可组合",代价是复杂度高——7 种策略要理解、要配置
- AgentScope 选择了"短期/长期显式分离",代价是需要外部服务——Mem0、ReMe 都要独立部署
- Hermes 选择了"插件生态",代价是限制了扩展数量——只允许一个外部提供者
没有"最好"的记忆系统,只有"最适合当前产品阶段"的记忆系统。
我的产品是桌面端软件,用户量不大,场景明确。所以我选择了:内存存储 + L0/L1/L2 三层 + 文本匹配召回 + 降级策略。够用,不过度设计。
等产品到了需要跨会话持久记忆的阶段,再升级到向量检索 + 外部记忆服务。一步一步来。