§08 长期记忆与历史
DeerFlow 的记忆分成两层,一定要分清:长期记忆(Long-Term Memory) 和 事件历史(event store)。
为什么需要两套
LangGraph 的 checkpointer 存的是「当前消息列表」的快照。问题是——当上下文太长,系统会做压缩(summarization),把旧消息原地替换成摘要。结果就是:你去查历史,最早的用户原话不见了,只剩一段摘要。
DeerFlow 的解法:再加一条 event store(RunJournal),追加式记录每一次事件,不覆盖、不压缩。
RunJournal 记了什么
RunJournal 把运行过程写成 run_events 表,按类别分:
| category | 内容 |
|---|---|
| trace | LLM 请求、工具调用等链路追踪 |
| message | 每一条消息 |
| lifecycle | run 开始/结束/出错 |
| middleware | 中间件事件(如压缩发生点) |
关键点:event store 不受 summarization 影响。即使消息被压缩成摘要,原始 human 消息还在 event store 里。
/history 读的是 event store
前端历史接口原本从 checkpointer 读,压缩后丢原话。2.0 改成了优先读 event store 的 list_messages(),覆盖 checkpointer 的 messages 字段。这样你在界面上能看到压缩之前的真实对话。
技术上还有两个细节值得记:
- 消息 ID 用
uuid5(NAMESPACE_URL, f"{thread_id}:{seq}")确定性生成,稳定可复现。 - 历史分页不能用固定
limit=1000,长对话会丢头——必须count_messages()后全量拉,或游标翻页。
标叔的结论:DeerFlow 对「记忆不可丢」这件事很较真。checkpointer 管「当下状态」,event store 管「完整真相」。你做审计、做复盘,永远信 event store。
代码要执行,得有隔离环境。下一章讲沙箱。