作者为 Agent 添加记忆层后,发现 Reddit 评论竟与系统 prompt 具有同等权重被 Agent 引用,根源是记忆存储架构未区分可信来源优先级。
今年三月我给 Agent 加了一层记忆。到六月时,我却在排查一些它从未告诉过我的"事实"——它信心满满地引用着它们,仿佛它们是金科玉律。记忆本身没坏,Agent 也不是在幻觉。问题出在架构上:我把每一次记忆写入都当作等价的来处理。
关于 Agent 记忆这件事,没人告诉你的真相是:存储很容易,真正的问题在于信任。
大多数 Agent 记忆教程展示的代码大概是这样的:
def remember(agent_id, content, source="user"):
"""The naive 'just stick it in a vector DB' pattern."""
embedding = embed(content)
db.execute(
"INSERT INTO memories (agent_id, content, embedding, source, ts) "
"VALUES (?, ?, ?, ?, ?)",
(agent_id, content, embedding, source, now()),
)
def recall(agent_id, query, k=5):
"""Cosine similarity, top-k, return whatever comes back."""
q = embed(query)
return db.search(q, k=k)
这样没问题——直到你的 Agent 开始用引用 Reddit 评论的权重去引用你写在系统提示词里的硬约束。我就亲眼看着它这么做了。检索是正确的,问题出在优先级层。
当所有东西都是记忆时,所有东西都成了候选项。模型没有任何信号来告诉它应该权重哪些候选项。于是它取平均,而"平均"一个用户偏好和三条无关的日志输出得到的结果是……一个言之凿凿的错误答案。
我把记忆分成四个层级,每个层级有各自的来源和召回行为。命名是我自己起的——你可以用,可以改,不重要。重要的是结构。
from dataclasses import dataclass
from enum import IntEnum
class Tier(IntEnum):
SYSTEM = 4 # Inviolable. Cannot be overwritten by retrieval.
OPERATOR = 3 # High-trust. Operator-defined rules. Can be evicted with confirmation.
CORROBORATED = 2 # User/tool output verified by another source.
UNVERIFIED = 1 # First-party claim with no confirmation. Default for raw memory writes.
@dataclass
class Memory:
content: str
tier: Tier
source: str # who/what wrote this
confidence: float # 0..1, decays on conflict
corroborations: int
last_used: float
created_at: float
最重要的一条规则:层级是分数的一部分,不是单独的标签。一条低置信度的 SYSTEM 记忆仍然胜过一条高置信度的 UNVERIFIED 记忆,因为层级本身就是先验。
def score(memory: Memory, query_embedding, k=60) -> float:
"""Rerank score = similarity * tier_prior * confidence * freshness."""
sim = cosine(query_embedding, embed(memory.content))
tier_prior = memory.tier.value / 4.0 # 0.25 .. 1.0
freshness = 1.0 / (1.0 + (now() - memory.last_used) / 86400) # days
corroboration_bonus = min(memory.corroborations, 3) * 0.05
return sim * tier_prior * memory.confidence * freshness + corroboration_bonus
三个值得注意的点:
层级是乘数,不是过滤器。如果一条 UNVERIFIED 记忆在余弦相似度上完美匹配且查询足够明确,它仍然可以浮出水面。你不想把低层级记忆黑盒删除——你想做的是降权它们。
置信度在冲突时会衰减。当新记忆与已有记忆矛盾时,已有记忆的置信度降低 0.2,新记忆从 0.5 开始。三次冲突后,旧记忆实际上已经死了。
验证是升级的唯一途径。一条 UNVERIFIED 记忆只有在第二个来源——不同会话、不同工具或不同 operator——独立得出相同结论后,才能变成 CORROBORATED。
真正有用的溯源表
我加了一个小型的审计表,这让调试变得可控。每次记忆写入都会记录来源,每次矛盾都是一行:
CREATE TABLE memory_provenance (
id INTEGER PRIMARY KEY,
memory_id INTEGER REFERENCES memories(id),
source_type TEXT, -- 'user', 'tool:<name>', 'doc:<url>', 'inference'
source_id TEXT, -- session id, tool call id, doc hash, etc.
tier_at_write INTEGER,
wrote_at REAL
);
CREATE TABLE memory_conflicts (
id INTEGER PRIMARY KEY,
memory_a INTEGER,
memory_b INTEGER,
detected_at REAL,
resolution TEXT -- 'a_wins', 'b_wins', 'both_demoted', 'merged'
);
六周后,这个表告诉我一件我本应预料到的事:47% 的记忆写入来自工具输出,其中 22% 来自一个我从未验证过输出 schema 的工具。Agent 把日志行当作事实来记。这个溯源表用一条查询就让它暴露无遗。
我学到的教训,按痛苦程度排序
检索只是问题的一小部分。我整个三月都在调 embedding 和分块大小。这只把召回率提高了约 6 个百分点。分层机制提高了 19 个。别在错误的层次上优化。
Agent 不知道什么是真的。它只知道它有什么。信任是一个你必须编码的先验。如果你不去编码它,模型就会平均一切,然后你得到的就是众数答案——而对于任何非平凡的上下文来说,恰恰是错误的答案。
Operator 记忆是杠杆效应最高的层级。我可以教我的 Agent"当用户说'deploy'时,他们指的是 staging,除非他们说了 prod",说一次就够了。如果没有比用户记忆更高的层级,这句话离被覆盖只差一次转述。
验证优于置信度。单独的置信度分数只是一种感觉。验证次数是一个事实。必须有两个独立来源一致同意,一条记忆才能升级。这样做了之后,"幽灵事实"(Agent 自己编造的记忆)的比例从约 14% 的写入降到了 2% 以下。
记忆需要 GC 清理。如果不回收,枯萎的记忆比新鲜的记忆更吵。我每周跑一次衰减扫描:任何 last_used > 30 天 且置信度 < 0.4 的记忆都会被归档。没有这个,召回很快就会变得嘈杂。
我现在用的评分卡
对于看了昨天那篇文章的 eval 从业者,这是我在四指标评分卡中加入的唯一一个在这项工作之后新增的指标:
memory_recall_reliability: 0.93 (8周内从0.74上升)
memory_phantoms_per_1k_writes: 18 (从142下降)
memory_conflict_resolution_time_s: 47 (中位数)
memory_automatic_eviction_pct: 22 (30天内自动归档的写入比例)
memory_recall_reliability 是 operator 定义的"必须记住"的事实中,Agent 在被问到时正确引用的比例,按层级加权。memory_phantoms_per_1k_writes 是 Agent 事后无任何溯源凭空编造的写入比例——这个指标让我意识到分层机制确实在起作用。
那篇促使我深入这个问题的 HN 帖子——"我们在 Elasticsearch 上构建了持久化 Agent 记忆层,召回率 0.89"——是一篇很棒的工程文章。但召回不是目标。召回是容易衡量的指标。难的指标是:当 Agent 决定说什么的时候,被召回的事实是否被正确加权。
如果你在 2026 年构建 Agent 记忆,问题不是"我怎么存储这个",而是"这个记忆为真的先验是什么,以及当新证据到来时这个先验如何更新"。数据库是无聊的部分。认识论才是产品。
Bug 从来不在我的向量数据库里。Bug 是我没有认识论。现在我有了。它是一个四级表、一个冲突解决循环、和一个每周 GC 清理。就是不优雅。但有用。