2026-08-26 · MinimalAgent 记忆系统设计记录 对应设计文档:
design-20260826-memory-rule-engine.md(规则集/流程/验收标准) 本文档回答另一个问题:这些设计是怎么一步步拍板出来的。
agent 会积累大量候选记忆:用户说过的话、项目约定、踩过的坑、临时状态。如果全存成永久记忆,长期层被噪音淹没,真正重要的信息提取成本变高;如果存得保守,关键身份和偏好丢失,agent 反复问用户已经说过的事。
核心问题:什么值得长期记忆,什么只配短期停留?
当时摆在我面前两条路:交给 LLM 判断,或者自己定规则。我选了后者。下面的五个决策,是这条路逐步展开的过程。
第一个念头其实是"让 LLM 判断重要不重要"——模型读得懂语义,似乎天然适合。但被一个问题拦住了:记忆事故发生时,怎么回答"为什么"?
"这条记忆为什么进了长期层?"——LLM 给不出答案。它是个黑盒,判断结果无法回放、无法审计、无法修正。而记忆系统最怕的就是这种不可追责:记错了,你甚至不知道它是怎么错的。
规则引擎正好相反:判断是确定性分支,任何一条记忆都能回放完整的规则链。出了事故,回答"为什么"→ 找到规则 → 改规则即可。可解释性是记忆系统的底线,规则引擎是唯一能满足它的方案。
于是 LLM 的位置从"法官"降级为"司法解释":只处理规则拿不准的灰色地带,比例控制在 90% 规则 / 10% LLM。判断的主体是规则,模型只是补充。
定了用规则,下一个问题:规则凭什么定?
如果规则是我临时想的("我觉得身份重要"),那它和拍脑袋没有区别。需要一个统一的判别标准。我借用了认知科学里的一个视角——知识生命周期:
变更成本 ≈ 生命周期 ≈ 重建成本
一句话:知识的变更成本越高,越值得长久存。家乡(物理不可变)、职业(转换成本高)、设计目标(推翻=全盘重来)一眼长命;丢了重建代价大的知识必须深存——"存那个丢了最心疼的";八股短命,因为考纲变更成本为零。
落到规则上就是类型查表:身份/原则 → 长期;稳定偏好 → 长期;具体技术用法 → 短期(带 TTL);临时状态 → 最短命。每一条规则背后都有这个判据撑着,而不是"我觉得"。
记忆分层(L1 临时 / L2 工作 / L3 长期)最容易踩的坑是:想靠预测一次分对。但预测总会错——误杀一条长命知识的代价,远大于多存一条短命知识。
所以我的原则是:以使用定生死,不以预测定生死。 分层不是靠预测,是靠每层不同的淘汰策略:
- L1:会话结束即过期,不值得存
- L2:带 TTL,到期降级 + 对账提示,绝不静默删除(防误杀)
- L3:几乎不淘汰,仅显式纠正可降级
并且留了复活机制:L2 的记忆被频繁召回(≥3 次/30 天)自动升级 L3——初判错了没关系,使用反馈会修正。判断器给的是先验,使用验证给的是后验,后验永远有权推翻先验。
"规则引擎"这个词听起来很重——八股里听过 Drools、Rete 算法,好像是个需要专门学的体系。但调研了一遍才发现它是个光谱,从轻到重三档:
- 代码里的 if/elif 查表(零依赖)
- 决策表(规则写成 JSON/YAML,代码只写解释器)
- 正经规则引擎(Drools / durable_rules,Rete 算法,处理上千条规则)
我的选择是第一档,理由很直接:记忆分层的规则一共 12 条——4 条硬分流(类型查表/变更成本/来源门槛/显式反馈)+ 5 条打分 + 3 条后验修正。规则是"宪法"级的:数量少、几乎不变、需要可审计。这个量级用纯函数查表就是最优解,引一个 Drools 进来纯属给项目背了个大象。
第一档落成代码只有 45 行:classify() 里前六行是硬分流(短路),后面是打分和阈值,on_recall / on_ttl_expire / on_correction 三个函数管后验修正。每条记忆落库时把返回的 ["R1", "R3"] 存成 rule_chain——可审计性就是这么来的,不是额外设计,是代码的自然产物。
什么时候才需要升级第二档?规则膨胀到 50+ 条、且要让非程序员改规则的时候。这个信号出现前,第一档就是终局(YAGNI)。
规则引擎能判断的前提是:输入已经是"一条独立的事实"。但对话原文是一大段话——"我是 Java 后端,之前做流批一体,最近在学 RAG,这周五交周报",里面是 4 条生命周期完全不同的记忆。
这里有个容易混淆的点:切句(提取)和分层(判断)是两件事。切句需要语义理解——按标点切是切不对的,中文口语一逗到底,必须读懂内容才知道哪几个事实绑在一起;分层需要确定性——同一套规则永远给同一结果,可审计。前者是 LLM 的强项,后者是规则引擎的强项。所以各干各的:
对话原文 → LLM 提取(原子事实清单 + 类型标签)→ 规则引擎分层 → 落库
切分的粒度标准是一条硬原则:一条记忆只能有一个生命周期。如果一条内容里混着长命的("我是 Java 后端")和短命的("这周五交周报"),规则引擎就没法判。判断粒度够不够细,三个测试:独立失效(这条过期不影响别的)、独立变更(纠正一条不牵连别的)、独立复用(将来某个任务只需要这一条)。
提取时机也刻意低频:不每条消息都切,每 N 轮对话做一次批量提取(background review),正好和"事后批量对账"接上——收尾时一起给"本次我记住了这些"清单让用户确认。省成本,还多一次人工校准。
回头看这五个决策,其实是一条线:判断力是可以制度化的。
- 该让模型猜的(理解语义)→ 交给 LLM
- 该让规则定的(判生死)→ 交给规则引擎
- 猜错了怎么办 → 使用验证修正,规则表越用越厚(判过的灰色地带沉淀为新规则)
- 未来怎么演进 → 判定日志攒够了蒸馏成专用小模型,但三个升级信号出现之前,这套轻量体系就是终局
规则引擎不复杂。它就是把你不敢硬编码的判断,显式地写出来、编上号、接受审问。对记忆系统来说,这比任何黑盒判断都值钱——因为记忆的错,是要能追责的。
| 规则 | 内容 | 判定 |
|---|---|---|
| R1 | 知识类型查表 | 身份/原则/稳定偏好 → L3;具体用法 → L2+TTL;临时状态 → L1 |
| R2 | 变更成本判据 | 离底层约束近 / 重建代价大 → L3;可重新获取 → 降级 |
| R3 | 来源等级门槛 | 用户亲口说且是身份/原则 → L3 候选;纯工具观察无确认 → 最多 L2 |
| R4 | 显式用户反馈 | 置顶 → L3;归档 → 出局/降级 |
| 规则 | 条件 | 分值 |
|---|---|---|
| S1 | 类型接近身份/原则/稳定偏好 | +30 |
| S1 | 类型接近具体用法 | +10 |
| S2 | 变更成本高(重建代价大) | +30 |
| S2 | 变更成本低(可重新获取) | -20 |
| S3 | 来源 = 用户亲口说 | +20 |
| S3 | 来源 = 模型推断 / 工具观察 | +5 |
| S4 | 内容是"为什么"(原理/约束) | +15 |
| S4 | 内容是"怎么做"(步骤/命令) | +5 |
| S5 | 临时状态 | -40 |
| S5 | 带明确时效("这周"/"项目期间") | -20 |
阈值:≥80 → L3 长期;40–79 → L2 工作(带 TTL);<40 → L1 临时
| 规则 | 事件 | 动作 |
|---|---|---|
| P1 | 召回 ≥3 次/30 天 | L2 → L3 自动升级(复活机制) |
| P2 | L2 超 TTL 未召回 | 降级 L1 + 对账提示(不静默删除,防误杀) |
| P3 | 用户纠正 | 覆盖一切先验,按纠正后内容重新判定 |
优先级:显式反馈(R4/P3)> 来源门槛(R3)> 类型查表(R1/R2)> 打分(S1–S5)。冲突原则:用户说什么就是什么——规则引擎的职责是猜,用户反馈是事实,事实永远压过猜测。