工程架构

MemGuard:长期记忆系统要把事实、事件和规则分开治理

从 arXiv:2605.28009 MemGuard 看,长期记忆的可靠性问题不只来自检索召回不足,也来自把稳定事实、情景事件和操作规则混成同一种证据。生产 Agent 记忆层需要类型边界、关系图、查询路由和可审计的组合策略。

来源说明

本文基于 2026-06-04 的每日深度技术研究发布流程写成。今天没有找到足够强的 2026-06-04 同日主来源,可以支撑一篇当天新材料文章;因此选择过去一周尚未在本站单独处理、但机制完整且有工程价值的主线:MemGuard: Preventing Memory Contamination in Long-Term Memory-Augmented Large Language Models

核心来源:

这篇文章和本站 2026-05-31 的记忆投毒文章相邻,但不是同一问题。记忆投毒讨论的是不可信输入如何长期影响 Agent 行为;本文讨论的是即使没有攻击者,系统也会因为把不同功能的记忆当作可互换证据而产生长期幻觉。它更接近记忆系统的数据建模和证据治理问题。

稳定 slug:2026-06-04-memguard-type-aware-memory-boundary

先给结论

长期记忆系统不能只问“这条记忆和查询语义相似吗”,还要问“这条记忆在当前推理里是什么证据类型”。

MemGuard 提出的核心失效模式是 heterogeneous memory contamination,本文译作“异构记忆污染”:稳定用户事实、一次性情景事件、行为规则或操作建议被写进同一个共享空间,检索时又被当作同等证据组合。结果是,情景事件可能被过度泛化成稳定事实,操作建议可能覆盖约束条件,语义相似但功能不兼容的记忆会挤掉真正需要的证据。

论文作者报告,MemGuard 在写入时把对话分解为 semantic、episodic、procedural 三类原子记忆,维护类型隔离的存储和跨类型关系图;检索时先做查询类型路由,再通过关系图有限扩展证据。作者在 HaluMem、LoCoMo、LongMemEval、PerLTQA 等任务上评估,报告 HaluMem anti-hallucination accuracy 达到 89.53%,相对基线提升 28.27%,并在部分长期对话任务中减少记忆 token 检索量。

我的工程判断是:这篇论文最值得保留的不是“三类记忆”这个分类本身,而是把类型边界变成可靠性约束。生产系统如果继续用一个 memories 表、一套 embedding、一个 top-k,把用户偏好、项目事实、一次工具观察、历史失败和操作规范混在一起,后续再靠 prompt 告诉模型“谨慎使用记忆”,成本会很高。正确的位置应该在写入、更新、检索和组合四个阶段都显式保留证据类型。

技术问题:记忆幻觉会在写入和检索阶段累积

很多团队把长期记忆幻觉理解成生成阶段问题:模型拿到了上下文但没有正确回答。这个解释不完整。

长期记忆系统的错误可以在更早阶段固化。一次对话里出现的临时事件,如果被写成稳定事实,它会在未来多次被召回;一次工具失败原因,如果被抽象成“永远不要使用某工具”,就会污染后续任务规划;一次用户对具体项目的偏好,如果被写成全局偏好,就会造成跨项目泄漏。

MemGuard 论文把问题拆成三段:

阶段典型错误工程后果
写入污染不完整抽取、过度概括、把临时事件写成稳定事实错误记忆跨会话持续存在
检索污染语义相似但功能不兼容的记忆一起进入上下文正确证据被噪声挤掉或降权
组合失败模型把证据类型混用,或在证据不足时仍回答幻觉被包装成“基于记忆”的结论

作者在 LoCoMo 上做的错误分析报告了一个有用信号:unverifiability errors 主要和写入阶段污染相关,factuality errors 更常和检索阶段污染相关。这个结果来自论文实验和 LLM-as-a-judge 标注,不应视作独立复现结论;但它给工程排障提供了方向:当系统在证据不足时仍然回答,先查写入门禁;当正确事实存在但答错,先查检索路由和排序。

机制拆解:类型边界不是标签,而是控制面

MemGuard 的机制可以简化成两个闭环:写入时重组记忆,检索时按查询动态路由。

flowchart LR
  D["dialogue / observation"] --> A["type-aware atom extraction"]
  A --> V["self-verified coverage check"]
  V --> G["typed relation graph"]
  G --> W["type-isolated stores"]

  Q["memory query"] --> R["query-adaptive type router"]
  R --> S["retrieve from selected stores"]
  S --> C["bounded graph composition"]
  C --> X["evidence context"]

  W --> S
  G --> C

1. 写入:每条记忆只能承担一个功能角色

论文把记忆原子建模为:

type MemoryType = "semantic" | "episodic" | "procedural";

type MemoryAtom = {
  id: string;
  title: string;
  details: string;
  type: MemoryType;
  timestamp: string;
  sourceTurnIds: string[];
};

重点不是字段名,而是单类型约束。一个 atom 不能同时是“用户稳定事实”和“一次事件观察”。如果一段对话同时包含事实、事件和建议,就要拆成多个 atom。

这会增加写入成本,但换来两个收益:

  • 更新只在同类型存储里发生,避免 procedural 建议覆盖 semantic 约束。
  • 检索可以先决定需要哪类证据,而不是把所有相似片段交给模型自行辨认。

2. 自检:防止抽取器漏掉约束

单次 LLM 抽取很容易漏掉否定、时间、限定条件和例外。MemGuard 在写入前加入 self-verified extraction:先抽取原子记忆,再检查原始对话中哪些信息没有被 atom 覆盖,把缺失 atom 补回来。

工程上我会把这个步骤实现为两个独立 prompt 或两个模型调用,并保留 coverage diff:

type CoverageCheck = {
  sourceTurnIds: string[];
  extractedAtomIds: string[];
  missingClaims: Array<{
    text: string;
    proposedType: MemoryType;
    reason: "constraint" | "exception" | "temporal_scope" | "preference" | "task_state";
  }>;
};

这里不能只看“抽取了多少条”。更重要的指标是约束覆盖率:包含否定、禁用、过敏、权限、时间范围、项目范围、撤销和用户纠正的句子,是否被原样保留下来。

3. 关系图:允许组合,但不允许混写

类型隔离不等于互相看不见。长期记忆经常需要跨类型组合:一个 semantic 约束可能解释某个 episodic 事件,一个 procedural 规则可能只适用于某个项目状态。

MemGuard 的做法是维护关系图:atom 内容保留在各自类型存储中,跨类型依赖通过边表达。工程上可以从简单关系开始:

type MemoryRelation =
  | "supports"
  | "contradicts"
  | "scopes"
  | "updates"
  | "caused_by"
  | "derived_from"
  | "requires";

type MemoryEdge = {
  from: string;
  to: string;
  relation: MemoryRelation;
  confidence: number;
  createdAt: string;
};

这比“把相关内容合并成一条摘要”更可控。摘要会压扁证据类型;关系图可以让系统在检索时有限扩展,比如最多 2 hop,并要求每条扩展证据说明它和主证据的关系。

4. 检索:先路由类型,再取 top-k

传统记忆检索通常是:

query -> embedding -> vector top-k -> prompt context

MemGuard 更接近:

query -> required memory types -> per-type retrieval budget -> graph expansion -> evidence set

一个可落地的路由策略如下:

function routeMemoryTypes(queryIntent: string, riskLevel: "low" | "normal" | "high") {
  if (queryIntent === "stable_user_fact") return { semantic: 0.8, episodic: 0.2, procedural: 0 };
  if (queryIntent === "what_happened_last_time") return { semantic: 0.1, episodic: 0.8, procedural: 0.1 };
  if (queryIntent === "how_should_i_do_this") return { semantic: 0.3, episodic: 0.2, procedural: 0.5 };
  if (riskLevel === "high") return { semantic: 0.6, episodic: 0.2, procedural: 0.2 };
  return { semantic: 0.34, episodic: 0.33, procedural: 0.33 };
}

这段伪代码不是论文实现,只是工程化表达。关键原则是:高风险回答不能只依赖 episodic 记忆;操作建议必须带 semantic 约束;项目状态类问题要优先最近 episodic 证据,但不能让历史事件覆盖当前明确规则。

工程判断:把 memory type 做成 schema,而不是 prompt 约定

我会把 MemGuard 的思想落到四个系统边界。

第一,存储 schema 必须有类型和作用域。typescopesourcevalidFromvalidUntilsupersedesconfidence 不应该是可选备注,而应该参与写入和检索策略。

第二,更新只能在同类型内发生。用户事实更新用户事实,任务事件更新任务事件,操作规则更新操作规则。跨类型影响只能通过关系边表达,不能直接合并。

第三,检索结果必须带证据角色进入上下文。给模型的不是一段无结构文本,而是类似:

semantic_constraints:
  - id: m_17
    text: "用户明确不希望在生产环境自动执行破坏性命令。"
episodic_events:
  - id: m_42
    text: "上次部署失败是因为 Vercel CLI 缺少认证。"
procedural_rules:
  - id: m_81
    text: "发布前必须先构建,通过后再推送和部署。"
relations:
  - from: m_81
    to: m_42
    relation: "scopes"

第四,生成阶段仍要做 grounding 检查。MemGuard 论文也承认,它主要控制写入和检索,不直接控制生成时行为。生产系统需要在输出前检查:结论是否引用了正确类型的证据,是否把 episodic 事件写成稳定事实,是否在证据不足时应该 abstain。

适用场景

这类设计最适合以下系统:

  • 个人助手:需要同时记住稳定偏好、一次性安排、健康/财务等高风险约束。
  • 编程 Agent:需要区分仓库事实、一次运行日志、修复规则、用户风格偏好。
  • 企业知识助手:需要避免把某个客户案例泛化为全公司规则。
  • 安全审计 Agent:需要区分授权范围、一次扫描证据、验证步骤和通用修复策略。
  • 多会话工作流 Agent:需要追踪项目状态变化,而不是只保留最终摘要。

不适合的场景也很明确:如果系统只做短会话问答,没有长期写入;或者记忆只是用户手动维护的少量 profile 字段,完整的类型隔离和关系图可能过重。

失败模式

MemGuard 方向并不能自动解决所有记忆问题。

失败模式为什么会发生缓解方式
类型分类错误LLM 把事件误标为事实,或把规则误标为偏好高风险类型用规则校验和人工审核
关系图膨胀每次写入都生成大量弱关系限制边类型、置信度和 hop 数
过度隔离检索只看单一类型,漏掉必要上下文查询路由保留最小跨类型预算
生成阶段误用上下文正确,但模型仍把证据角色混用输出前 grounding / citation check
删除和撤销不彻底旧 atom 仍通过关系边被扩展出来删除要同步处理索引、边和派生摘要
成本上升写入抽取、自检、路由都需要额外推理只对长期记忆和高风险作用域启用完整流程

这里最危险的是“类型标签看起来存在,但不参与控制”。如果 type 只是前端展示字段,检索仍然全库 top-k,更新仍然跨类型合并,那么系统只是在污染上加了颜色。

可验证指标

我会用下面这组指标验证类型边界是否真的提高可靠性。

指标目的计算方式
type classification accuracy写入类型是否可靠人工标注样本对比 atom type
constraint coverage recall关键约束是否被写入否定、禁用、例外、撤销句子的覆盖率
cross-type overwrite rate是否发生功能混写UPDATE 操作中跨类型合并比例,应为 0
retrieval type precision检索是否拿到需要的证据类型top-k 中符合查询意图的类型占比
evidence sufficiency abstention证据不足时是否拒答无答案样本上的 abstain rate
stale episodic dominance旧事件是否压过新事实有冲突样本中旧 episodic 被引用比例
token efficiency类型路由是否节省上下文每次回答使用的 memory token 数
answer groundedness最终回答是否由证据支持输出 claim 到 atom 的引用覆盖率

这些指标要分作用域统计。全局平均值可能很好看,但高风险场景坏一次就足够严重。尤其是医疗、财务、安全操作、生产部署和权限相关记忆,应该单独设阈值。

我会如何实现和验证

如果要在一个现有 Agent 记忆系统里落地,我不会一开始重写全部存储层。更稳妥的路线是先加一个 type-aware shadow path。

第一阶段,只对新写入记忆生成 typed atoms 和关系边,旧记忆保持只读。每条新 atom 必须记录原始 turn、抽取 prompt 版本、类型、作用域和更新时间。

第二阶段,在检索服务旁路增加 type router。先不改变生产回答,只记录传统 top-k 和 typed top-k 的差异:哪些查询会因为类型过滤少拿噪声,哪些查询会漏掉证据。

第三阶段,构造回放集。样本至少包含:

  • 用户纠正旧事实。
  • 一次事件不应变成稳定偏好。
  • 项目 A 的规则不应泄漏到项目 B。
  • 操作建议必须带约束条件。
  • 证据不足时应拒答。

第四阶段,在低风险功能中启用 typed retrieval,把 prompt context 改成结构化 evidence block,并强制模型在回答中区分“稳定事实”“历史事件”“操作规则”。

第五阶段,上线监控 cross-type overwrite、abstain、token efficiency 和人工抽检 groundedness。如果 token 下降但拒答过多,说明路由太保守;如果准确率提升但成本过高,就把自检和关系图扩展限制在高风险作用域。

局限分析

第一,MemGuard 的实验结果是作者报告的预印本结果。HaluMem、LoCoMo、LongMemEval 和 PerLTQA 提供了有价值的评测面,但不能直接等价于生产助手可靠性。

第二,LLM-as-a-judge 参与了部分错误分析和评测。它适合做大规模趋势观察,但对边界安全、医疗财务建议、企业权限判断这类场景,仍需要人工标注和可执行检查。

第三,三类记忆不一定覆盖所有工程场景。编程 Agent 至少还需要 artifact state、tool trace、policy、credential boundary、environment fact 等更细类型。我的建议是从 semantic / episodic / procedural 起步,但不要把它当作最终本体。

第四,类型边界会提高写入复杂度。对短生命周期任务,可能不值得;对长期助手、企业 Agent 和安全敏感场景,额外成本通常是合理的。

第五,它不替代记忆安全。类型隔离能降低误用和污染,但不能防止恶意输入被写入;仍然需要来源信任、写入权限、用户可见审计、删除机制和记忆投毒检测。

自审

  • 事实可靠性:主来源是 arXiv:2605.28009;关键实验数字标注为作者报告结果,没有写成独立复现。
  • 来源完整性:补充了 LoCoMo、LongMemEval-V2 和 HaluMem 作为评测背景;没有使用社区帖作为核心证据。
  • 原创性:文章重点是把类型边界转化为生产 memory schema、路由、关系图和指标,不复述论文摘要。
  • 站内差异化:区别于 2026-05-31 的记忆投毒文章,本文聚焦非对抗条件下的异构记忆污染和可靠性治理。
  • 工程价值:包含机制图、数据模型、接口示例、落地路线和可验证指标。
  • 边界:没有给出攻击第三方目标的操作流程;安全相关内容仅用于防御和系统治理。