跳转到内容

记忆压缩模式

记忆压缩模式:Claude-Mem 的具体实现

学习目标

本章聚焦 Claude-Mem 如何将 Claude Code 的工具使用事件转化为结构化记忆。建议先阅读前置知识,下文直接聚焦具体落地方案。

前置知识

本章涉及记忆压缩的通用原理,建议先阅读:

项目实践

XML 解析器架构

Claude-Mem 使用自定义 XML 解析器(src/sdk/parser.ts)处理 LLM 生成的观察和摘要。解析器的工作流程:

解析器的关键设计:

  1. 代码围栏兼容:LLM 可能将 XML 输出包裹在 xml ... 围栏中,解析器首先检测并剥离
  2. 多 observation 支持:使用全局正则 /g 匹配多个 <observation>
  3. 概念去重:自动从 concepts 数组中移除与 type 相同的值,避免冗余
  4. 空观察过滤:当 title、narrative、facts、concepts 全部为空时,跳过该观察

观察类型验证

Claude-Mem 通过 ModeManager 动态管理有效的观察类型列表。每种 mode(如 codecode--zhcode--ja)定义了自己支持的观察类型集合。解析器在提取到 type 字段后:

  • 若 type 在当前 mode 的有效列表中 → 使用
  • 若 type 不在有效列表中 → 记录错误日志,回退到默认类型
  • 若缺少 type 字段 → 记录错误日志,使用默认类型

这种设计避免了硬编码类型列表,允许通过 mode 配置灵活扩展。

上下文注入管线

ContextBuildersrc/services/context/ContextBuilder.ts)负责将数据库中的观察和摘要组装为可注入的上下文文本:

每个渲染器是独立的模块,输出字符串数组,最终通过 \n 拼接。这种分节渲染的好处是:

  • 每节可以独立启用/禁用(通过 ContextConfig)
  • 每节有自己的 token 预算计算
  • 便于单元测试和替换

Token 经济学计算

TokenCalculatorsrc/services/context/TokenCalculator.ts)统计观察的 token 消耗:

  • 每条观察的 discovery_tokens 记录生成时的消耗
  • 上下文输出中显示总计和平均每观察的 token 数
  • 帮助 Agent 判断注入成本是否合理

问题与规避

解析器误匹配

问题:正则表达式 /\<observation\>([\s\S]*?)\<\/observation\>/g 可能在用户代码中包含 <observation> 标签时误匹配。

规避:解析器在提取后检测字段完整性,若关键字段(type)缺失或所有内容字段为空,跳过该块。同时,观察类型验证进一步降低误匹配的影响。

去重窗口竞争

问题:在高并发场景下,同一工具使用事件可能被多个 Hook 捕获,导致重复写入。

规避:数据库 schema 中 observations 表设置了 UNIQUE(memory_session_id, content_hash) 约束,写入时使用 INSERT ... ON CONFLICT DO NOTHING,天然吸收重复。

设计取舍

为什么用 XML 而非 JSON?

Claude-Mem 选择 XML 作为 LLM 结构化输出格式,而非 JSON:

  • 优势:XML 标签天然支持嵌套和数组(<facts><fact>...</fact><fact>...</fact></facts>),LLM 更容易生成正确的嵌套结构。JSON 需要精确的引号和逗号,LLM 容易出错。
  • 代价:解析复杂度高于 JSON,需要自定义正则解析器。
  • 权衡:代码注释中标注了 TODO,计划迁移到 Anthropic tool-use API 的结构化输出,届时将不再需要 XML 解析。

为什么同步解析而非异步?

XML 解析是纯正则操作,不涉及 I/O 或模型调用,同步执行即可。这确保了解析器可以在 Hook 层和 Worker 层复用,无需异步包装。

参考来源