Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Memory Provider 架构:可插拔的记忆后端

本章核心源码agent/memory_provider.py(231 行)、agent/memory_manager.py(367 行)、agent/builtin_memory_provider.py(114 行)、plugins/memory/

定位:本章将 Memory Provider 系统作为“如何设计可插拔记忆系统”的案例来写,涵盖 MemoryProvider ABC、MemoryManager 的职责边界、当前运行时里内置记忆与外部 provider 的接线关系,以及 8 个外部插件。 前置依赖:第 10 章(SessionDB)。适用场景:想理解 agent 如何实现"越用越懂你",或准备开发自定义 memory provider。

为什么记忆需要可插拔

SessionDB(第 10 章)存储的是会话状态——完整的消息历史。但"记忆"不等于"历史"。

  • 历史是原始数据:用户在第 37 次对话中说了什么
  • 记忆是提炼后的知识:用户偏好 Python 而非 JavaScript、用户的项目使用 PostgreSQL

不同的记忆策略各有优劣:有的擅长用户建模(Honcho),有的擅长向量检索(Mem0),有的擅长时序窗口(Hindsight)。Hermes 的解法不是选一种,而是提供一个可插拔的抽象层,让用户按需选择。

MemoryProvider ABC

MemoryProvideragent/memory_provider.py:42)定义了 17 个方法的抽象接口:

graph TD
    subgraph "生命周期方法"
        INIT["initialize()"] --> PROMPT["system_prompt_block()"]
        PROMPT --> PREFETCH["prefetch()"]
        PREFETCH --> SYNC["sync_turn()"]
        SYNC --> SHUTDOWN["shutdown()"]
    end

    subgraph "事件钩子"
        ON_TURN["on_turn_start()"]
        ON_SESSION["on_session_end()"]
        ON_COMPRESS["on_pre_compress()"]
        ON_DELEGATE["on_delegation()"]
        ON_WRITE["on_memory_write()"]
    end

    subgraph "工具接口"
        SCHEMA["get_tool_schemas()"]
        HANDLE["handle_tool_call()"]
    end

    subgraph "配置"
        AVAIL["is_available()"]
        CONFIG["get_config_schema()"]
        SAVE["save_config()"]
    end

核心生命周期(4 个 @abstractmethod)

# agent/memory_provider.py:42-139
class MemoryProvider(ABC):
    @property
    @abstractmethod
    def name(self) -> str: ...                          # "honcho", "mem0", ...

    @abstractmethod
    def is_available(self) -> bool: ...                  # 依赖检查

    @abstractmethod
    def initialize(self, session_id, **kwargs): ...      # 连接/初始化

    @abstractmethod
    def system_prompt_block(self) -> str: ...             # 注入 system prompt 的文本

数据流方法(默认空实现)

方法调用时机作用
prefetch(query)主循环前根据用户消息预取相关记忆
queue_prefetch(query)主循环后为下一轮预取排队(后台)
sync_turn(user, assistant)主循环后将本轮对话同步到记忆存储
shutdown()session 结束清理连接

事件钩子(可选)

钩子触发时机用途
on_turn_start(turn, message)每轮对话开始每轮 tick(如更新用户模型)
on_session_end(messages)session 结束最终记忆归档
on_pre_compress(messages)压缩前在消息被压缩丢弃前提取信息
on_delegation(task, result)子代理完成观察子代理工作
on_memory_write(action, target, content)内置记忆被修改镜像内置记忆的写入

on_memory_write 值得特别说明:当 agent 通过内置 memory 工具修改 MEMORY.md 时,外部 provider 会收到通知。这让 Honcho 可以将内置记忆的变更纳入自己的用户模型——两个系统保持同步。

MemoryManager:抽象目标与当前接线

MemoryManageragent/memory_manager.py:72)的抽象目标是统一编排内置 provider 和一个外部 provider;agent/builtin_memory_provider.py 也已经为此准备好了适配层:

# agent/memory_manager.py:72-86
class MemoryManager:
    def __init__(self):
        self._providers: List[MemoryProvider] = []
        self._tool_provider_map: Dict[str, MemoryProvider] = {}

    def add_provider(self, provider: MemoryProvider):
        self._providers.append(provider)
        for schema in provider.get_tool_schemas():
            self._tool_provider_map[schema["name"]] = provider

但当前主运行路径还没有完全收敛到这条抽象线上。run_agent.py 里的接线是双轨的:

  • 内置记忆 MEMORY.md / USER.md 仍由 MemoryStore 直接加载,并直接注入 system prompt
  • MemoryManager 当前主路径承接的是外部 provider:初始化、system prompt block、prefetch、tool routing、sync、压缩前钩子

换句话说,BuiltinMemoryProvider 更像是代码库已经存在的统一抽象方向,而不是当前运行时唯一的内置记忆接线方式。

Fan-out 容错

所有 fan-out 方法都对每个 provider 独立 try/except:

# agent/memory_manager.py:204(简化)
def sync_all(self, user_content, assistant_content, *, session_id=""):
    for provider in self._providers:
        try:
            provider.sync_turn(user_content, assistant_content, session_id=session_id)
        except Exception as exc:
            logger.warning("Memory provider %s sync failed: %s", provider.name, exc)

一个 provider 失败不影响其他 provider,也不影响主对话流程。这是 graceful degradation 原则的直接应用。当前主路径通常只挂一个外部 provider,但 manager 的接口已经按多 provider 容错来设计。

为什么只允许一个外部 provider

设计上限制最多一个外部 provider。原因很直接:多个外部 provider 的 prefetch 结果可能冲突,tool schema 可能重名,成本也会线性增长。Hermes 的现实取舍不是“堆多个后端”,而是“保留一套内置记忆,再允许用户额外接一个最想要的外部记忆后端”。

BuiltinMemoryProvider 与当前主路径

agent/builtin_memory_provider.py(114 行)实现了最简单的记忆方案:

  • MEMORY.md:agent 的持久记忆(用户偏好、环境细节、工具经验)
  • USER.md:用户画像(角色、背景、习惯)

需要特别注意:当前主路径里,真正负责读写这两份 Markdown 的仍是 MemoryStoretools/memory_tool.py),不是 BuiltinMemoryProvider 也就是说,这个 provider 已经存在,但运行时还没有完全把内置记忆切到 provider 管理器里。现阶段已经落地的桥接点主要有两处:

  • system prompt 仍由 run_agent.py 直接从 MemoryStore 读取并拼装
  • 当内置 memory 工具写入 MEMORY.md / USER.md 时,run_agent.py 会调用 MemoryManager.on_memory_write() 通知外部 provider

这种状态反映的是一次增量重构中的中间形态:抽象已经建立,统一接线尚未彻底完成。无论是旧路径还是抽象路径,字符数上限仍由 memory_char_limit=2200user_char_limit=1375 控制。

8 个外部 Provider

Provider方案特色
HonchoDialectic 用户建模Plastic Labs 的对话式用户模型,自动生成用户画像
Hindsight时序滑动窗口按时间衰减的记忆,近期对话权重高
Mem0向量数据库Qdrant 后端,语义相似度检索
Holographic压缩全息存储将对话压缩为高密度表示
OpenViking语义嵌入嵌入向量驱动的语义记忆
RetainDB保留策略可配置的记忆保留规则
SuperMemory多源聚合聚合多个来源的记忆
ByteRover替代向量存储轻量级向量存储方案

每个 provider 实现 MemoryProvider ABC 的对应方法。以 Honcho 为例:它的 prefetch() 返回用户的对话式画像,sync_turn() 将每轮对话提交给 Honcho API 更新用户模型,on_memory_write() 将内置记忆的变更同步到 Honcho 的知识图谱。

设计启示

Memory Provider 系统展示了可插拔架构的三个关键决策:

  1. ABC 定义生命周期而非行为:17 个方法中只有 4 个是 abstract——其余 13 个有默认空实现,让 provider 只需实现自己关心的部分
  2. 增量收敛而非一次性替换:Hermes 先引入统一的 provider 抽象,再逐步把原有内置记忆逻辑向它收拢,这降低了重构风险
  3. Mirror hookon_memory_write 让内置记忆和外部 provider 可以保持同步,即使在“双轨接线”阶段也能减少两套记忆系统的分歧

第 12 章将分析上下文压缩——当对话太长时,如何在不丢失关键信息的情况下缩减消息历史。


设计赌注回扣:Memory Provider 是 Personal Long-Term 赌注的核心基础设施。Honcho 的用户建模让 agent "越用越懂你"从理念变为可测量的工程实现。


版本演化说明

本章核心分析基于 Hermes Agent v0.8.0(2026 年 4 月)。 MemoryProvider / MemoryManager 这套可插拔抽象可以明确定位到 v0.7.0 发布窗口。它进入主线之后,provider hook 和外部记忆后端的生态在 v0.7.0-v0.8.0 之间快速成型。