前面我们已经看了 DeerFlow 的可观测性、Clarification 这些设计。
这一篇继续看一个 Agent Harness 里很重要的东西:Memory。
现在很多 Agent 都会说自己有"长期记忆",但是记忆这个东西是怎么来的?
text
用户说了一句话
↓
LLM 提取重点
↓
存到向量数据库
↓
下次聊天再搜索出来
我一开始也以为 DeerFlow 差不多是这么做的。让我们开始看看具体是怎么实现的吧!真实的设计更复杂也更简单!
比如:
- 存储记忆的过程的设计;
- 不是 LLM 说要记就直接记;
- 默认甚至没有用向量数据库。
1. DeerFlow 到底把什么叫做 Memory
先区分三个东西。
| 类型 | 作用 | 生命周期 |
|---|---|---|
| 会话状态 | 当前线程里的消息、运行状态 | 当前线程 |
| 上下文摘要 | 对旧消息做压缩,避免上下文越来越长 | 当前线程 |
| 长期记忆 | 保存以后聊天还可能有用的信息 | 跨会话 |
DeerFlow 这里说的 Memory,主要是第三种。
比如:
text
用户主要使用 Python 和 LangGraph
用户喜欢简洁一点的技术回答
用户长期在做 Agent 相关项目
这些东西下一次新开一个会话,可能依然有用。
但下面这些一般就不应该进长期记忆:
text
这个 PR 先用 Redis
今天帮我删除这个文件
当前这个 bug 暂时这么绕过去
所以长期记忆真正麻烦的地方其实不是"存"。
而是:
什么东西值得存?
2. after_agent() - agent跑完之后,并不是直接写 Memory
默认情况下,DeerFlow 使用的是 middleware 模式。
每一轮 Agent 跑完之后,会进入:
python
MemoryMiddleware.after_agent()
然后调用:
python
get_memory_manager().add(
thread_id,
messages,
agent_name=agent_name,
user_id=user_id,
trace_id=trace_id,
)
add() 不是直接新增memory,如果这样memory直接爆炸了
其实是:
把这批对话提交给 Memory 系统,让它判断有没有东西值得记。
真正的链路大概是:
text
Agent 一轮执行结束
↓
MemoryMiddleware
↓
DeerMem.add()
↓
消息过滤
↓
debounce 队列
↓
MemoryUpdater
↓
Memory LLM
↓
代码校验
↓
真正写入 Memory
3. 写入之前,先把大部分垃圾过滤掉
DeerMem 收到消息以后,并不会马上扔给模型。
它先做一轮比较确定性的处理。
比如:
- 工具调用不要;
- 工具返回不要;
- 框架隐藏消息不要;
- 上传文件之类的临时信息不要;
- "好的""谢谢""OK"这种确认信息也没必要;
- 没有有效用户消息或者 AI 回复,直接跳过。
除此之外,它还会检测一些信号,比如:
text
correction
preference
identity
goal
decision
比如用户说:
text
不是,我以后主要写 Python,不写 Java 了。
这里同时有:
text
correction
+
preference
这种内容显然比:
text
好的
更值得让 Memory LLM 看一眼。
不过这里有个细节。
这些规则并不会直接决定保存。
它们只是先筛掉明显没价值的东西。
最后到底记不记,还是要进入下一层。
另外 DeerFlow 这里还做了一个挺实用的优化:debounce。
默认是:
yaml
debounce_seconds: 30
意思不是每 30 秒处理一次,而是等待用户暂时停止输入。
这样连续聊几轮时,不需要每轮都单独跑一次 Memory LLM。
这里还有一个 watermark。
middleware 有时候传过来的是完整会话,但 Memory 系统不会每次把整段聊天重新抽一遍。
本质上就是:
text
debounce
解决重复调用
watermark
解决重复处理
4. 最关键的一层:LLM 提议,代码裁决
过滤之后,真正需要处理的消息才会进入 MemoryUpdater。
这里会调用一个专门的 Memory LLM。
Prompt 里面大概会放:
- 当前已经存在的 memory;
- watermark 后的新消息;
- correction / preference 等信号;
- 一些需要重新检查或者合并的旧 fact。
然后让模型返回结构化结果。
类似:
json
{
"user": {
"workContext": {
"summary": "用户主要使用 Python 和 LangGraph",
"shouldUpdate": true,
"scope": "user",
"authority": "descriptive"
}
},
"newFacts": [
{
"content": "用户偏好简洁的技术回答",
"category": "preference",
"confidence": 0.9,
"scope": "user",
"durability": "durable",
"authority": "descriptive"
}
]
}
这里我觉得是 DeerFlow Memory 里最值得看的一个设计:
LLM 只负责提议,代码才负责最终决定。
比如一个 fact 想真正写进去,默认需要满足:
text
scope = user
durability = durable
authority = descriptive
confidence >= 0.7
代码要继续判断:
text
是不是用户级信息?
是不是长期有效?
是不是描述性事实?
置信度够不够?
是不是重复了?
fact 数量有没有超限?
通过之后才写。
5. 一个挺反直觉的地方:DeerMem 默认没用向量数据库
说到长期记忆,很多人的第一反应应该都是:
text
Embedding
+
Vector DB
但 DeerMem 默认不是这么做的。
它真正保存 Memory 的地方其实非常朴素:
text
{storage_path}/
├── users/{user_id}/memory.json
└── users/{user_id}/agents/{agent_name}/facts/.../*.md
简单理解:
text
memory.json
放一些用户级 summary。
text
facts/*.md
保存具体 fact。
而搜索默认走的是:
text
SQLite FTS5
+
BM25
也就是:
text
facts/*.md
↓
建立 FTS5 索引
↓
BM25 排序
不是 Chroma,不是 Milvus,也不是 FAISS。
因为长期记忆的数据量通常并没有大家想象得那么大。
一个用户就算存几十条、上百条 fact:
text
100 条 memory
这个规模用 FTS5 完全够用。
反而如果一开始就引入:
text
Embedding
Vector DB
向量同步
索引更新
模型版本
系统复杂度一下子就上来了。
所以 DeerFlow 这里把:Markdown / JSON作为权威数据,FTS5 只是派生索引。即使索引删了,也可以重新构建。
这是一个很典型的 Harness 思路:
能简单解决的问题,不急着上重型基础设施。
6. 写进去之后,下次 Agent 怎么看到?
最后还有一个问题:
Memory 已经保存了,下次聊天模型怎么知道?
DeerFlow 会在动态上下文 middleware 里读取当前用户的长期记忆,然后格式化成类似:
xml
<memory>
User Context: ...
History: ...
Facts: ...
</memory>
再放回 Agent 的上下文里。
所以完整链路其实是:
text
上一轮聊天
↓
MemoryMiddleware
↓
过滤 / debounce / watermark
↓
Memory LLM
↓
代码门禁
↓
JSON / Markdown
↓
FTS5
到了下一次聊天:
text
读取 Memory
↓
格式化
↓
注入 Agent Context
↓
LLM
这样模型才会表现出:
text
"它还记得我之前说过什么。"
DeerFlow 这里还有个设计。
Memory 内容不会直接当成最高权限的 System Prompt 塞进去。它通常作为隐藏上下文注入
原因也很好理解:
Memory 本质上来自用户,不能因为被保存了一次,就突然获得 System 权限。
DeerFlow 还提供了另一种 tool 模式。
也就是不给 Agent 自动处理,而是提供:
text
memory_search
memory_add
memory_update
memory_delete
让模型自己决定什么时候读、什么时候写。
但从设计上看,它们底层并不是两套 Memory。
只是两种入口:
text
middleware 模式
→ 系统自动管理
tool 模式
→ 模型主动管理
最终还是走同一个 MemoryManager backend。
最后
把 DeerFlow 的 Memory 拆完之后,感觉DeerFlow 给出的方案其实没有特别玄学:
text
规则先过滤
↓
LLM 做语义判断
↓
代码做最终门禁
↓
简单可靠地持久化
甚至连检索都没有急着上向量数据库。
从这个角度看,Memory 其实和前面讲的 Clarification、可观测性很像。
真正重要的是把模型前后的这些边界控制好。