主题
长期记忆机制
本章目标:
- 理解记忆为什么是"数字同事"的命门,以及它与 ChatBot 的本质区别
- 看懂记忆的写入链路:过滤 → 去抖队列 → LLM 抽取 → 原子落盘 → 注入
- 弄清 a-cdm 的 per-user/per-agent 命名空间隔离与"专家无记忆"策略
TL;DR
记忆系统让 Agent 跨对话记住用户的偏好/背景/事实。MemoryMiddleware.after_agent 过滤出 user+final-AI 消息塞进 get_memory_queue();队列去抖(默认 30s)批处理,MemoryUpdater 调 LLM 抽取事实、按内容 dedupe、原子写 memory/<namespace>.json;下次交互 apply_prompt_template 注入 top facts + context 到系统提示。namespace 隔离是 a-cdm 关键定制:默认 chat 落 usermem-{sub前8}(per-user),user-/project- 前缀 agent 落对应文件,原型专家 agent 不挂记忆(防跨用户套话泄漏)。
Overview(为什么记忆是"同事 vs ChatBot"的分水岭)
openspec/specs/roadmap/spec.md 把"做成了 RAG 工具不是同事"列为三大失败路径之一,早期症状是"每次对话都是新开始,没有记忆/偏好/习惯",根因是"agent-persistent memory 未启用"。换句话说:没有记忆,Agent 永远只是个聪明的问答器,不可能成为"记得你、懂你"的同事。
但记忆不能简单"把所有历史塞进上下文"——会爆 token、会噪声淹没、会跨用户泄漏。deer-flow 的解法是结构化记忆 + 异步抽取 + 命名空间隔离:LLM 把对话蒸馏成离散事实(带 category/confidence),去抖批量更新避免每轮都跑,按 namespace 隔离保证 A 的偏好不会进 B 的提示。a-cdm 在此之上加了一层关键约束:谁有记忆、记忆落哪个文件,是按"防套话泄漏"原则设计的。
Architecture:memory 包组成
| 文件 | 职责 | Source |
|---|---|---|
memory_middleware.py | after_agent 过滤+入队 | deer-flow/backend/packages/harness/deerflow/agents/middlewares/memory_middleware.py:24-81 |
queue.py | 去抖批处理队列(per-thread dedup) | deer-flow/backend/packages/harness/deerflow/agents/memory/queue.py |
updater.py | LLM 抽取 + dedupe + 原子落盘 + CRUD | deer-flow/backend/packages/harness/deerflow/agents/memory/updater.py:326 |
storage.py | memory.json 读写 | deer-flow/backend/packages/harness/deerflow/agents/memory/storage.py |
namespace.py | 命名空间隔离 | deer-flow/backend/packages/harness/deerflow/agents/memory/namespace.py |
message_processing.py | filter_messages_for_memory / detect_correction / detect_reinforcement | deer-flow/backend/packages/harness/deerflow/agents/memory/message_processing.py |
scope_extraction.py | 上下文边界检测 | deer-flow/backend/packages/harness/deerflow/agents/memory/scope_extraction.py |
prompt.py | 记忆更新 prompt 模板 | deer-flow/backend/packages/harness/deerflow/agents/memory/prompt.py |
数据结构(config.yaml:883-891 + updater.py):memory.json 含 User Context(workContext/personalContext/topOfMind)、History(recentMonths/earlierContext/longTermBackground)、Facts(id/content/category(preference/knowledge/context/behavior/goal)/confidence/createdAt/source)。
Components:写入链路细节
MemoryMiddleware.__init__(agent_name)(memory_middleware.py:36-43):agent_name 决定记忆落哪个 namespace。after_agent(memory_middleware.py:46-81):filter_messages_for_memory 只留 user + final AI(:76),统计 human/ai 消息,有意义对话才入队(:78-81),避免空轮污染记忆。
MemoryUpdater(updater.py:326):
_prepare_update_prompt(:368)把当前 memory + 新对话喂 LLM;_fact_content_key(:317)做内容归一化 key 用于 dedupe——updater.py:43注释点明:category 用英文 canonical 存储,前端 i18n 翻译显示,storage 一致才能 dedupe(否则中英文同义事实重复存);_save_memory_to_file(:68)原子写(temp file + rename),get_memory_data/reload_memory_data/clear_memory_data(:73-110)是 CRUD,供 Gateway memory API 用;_strip_upload_mentions_from_memory(:294)剔除上传文件提及——上传是临时的,不该进长期记忆。detect_correction/detect_reinforcement(message_processing.py)识别用户在纠正/强化某事实,据此调整 confidence。
config.yaml:883-891 配置:debounce_seconds: 30、max_facts: 100、fact_confidence_threshold: 0.7、injection_enabled: true、max_injection_tokens: 2000。
a-cdm 关键定制:namespace 隔离与"专家无记忆"
这是 a-cdm 在 vendored 记忆系统上最重要的改造,逻辑在 _build_middlewares(agents/lead_agent/agent.py:250-259):
| agent_name | 是否挂 MemoryMiddleware | 记忆落 | 设计意图 | Source |
|---|---|---|---|---|
None(默认 chat) | ✓ | memory/usermem-{sub前8}.json | 透明 per-user 隔离 | agent.py:251-258 |
user-* / project-* / usermem-* 前缀 | ✓ | memory/{agent_name}.json | 平台 provision 的用户/项目 agent | agent.py:258 |
| 其他原型 agent(consultant-agent / ba-expert 等) | ✗ 不挂 | 无记忆 | 防跨用户跨项目套话泄漏 | agent.py:250-256 |
为什么原型专家不挂记忆:这些是"咨询专家""BA 专家"这类共享原型,如果挂全局记忆,A 用户跟"咨询专家"说的话会被抽进记忆,B 用户再问"咨询专家"时可能被注入——跨用户信息泄漏。a-cdm 对齐 OpenSpec spec.md 方案 B "专家无记忆"(agent.py:253-256 注释),用 per-user 的默认 chat agent 承载个人记忆,专家保持无状态。这与"鉴权授权"章的隔离哲学一脉相承。
Configuration
config(config.yaml → memory) | 默认 | 含义 | Source |
|---|---|---|---|
enabled / injection_enabled | true | 总开关 / 是否注入提示 | deer-flow/config.yaml:884,890 |
debounce_seconds | 30 | 入队后等待批处理时间 | deer-flow/config.yaml:887 |
max_facts | 100 | 事实存储上限 | deer-flow/config.yaml:889 |
fact_confidence_threshold | 0.7 | 低于此 confidence 不存 | deer-flow/config.yaml:889 |
max_injection_tokens | 2000 | 注入提示 token 上限 | deer-flow/config.yaml:891 |
storage_path | memory.json | 相对 backend 目录 | deer-flow/config.yaml:885 |
Common Pitfalls / 实战 Tips
- 记忆是异步的,不是即时的:去抖 30s + 批处理,刚说的偏好不会立刻在下一句生效,要等队列 flush。
- category 必须英文 canonical 存:前端靠 i18n map 翻译显示,storage 存中文会破坏 dedupe(
updater.py:43)。 - 给专家加记忆是危险"修复":consultant-agent/ba-expert 不挂记忆是防泄漏的有意设计(
agent.py:250-256),不要因为"专家不记事"就给它挂上。 - 上传提及不进记忆:
_strip_upload_mentions_from_memory(updater.py:294)有意剔除,别指望"我上传过的文件"被长期记住。 - 原子写防损坏:
_save_memory_to_file用 temp+rename,但并发写不同 namespace 是安全的(文件按 namespace 分),同 namespace 靠队列 per-thread dedup 串行。
References
deer-flow/backend/packages/harness/deerflow/agents/middlewares/memory_middleware.py:24-81— 过滤+入队(本章主源)deer-flow/backend/packages/harness/deerflow/agents/memory/updater.py:294-411— LLM 抽取/dedupe/原子落盘deer-flow/backend/packages/harness/deerflow/agents/memory/queue.py— 去抖批处理队列deer-flow/backend/packages/harness/deerflow/agents/lead_agent/agent.py:250-259— namespace 隔离/专家无记忆定制deer-flow/config.yaml:883-891— 记忆配置项deer-flow/backend/packages/harness/deerflow/agents/memory/message_processing.py— 消息过滤/纠正检测
Related Pages
| Page | Relationship |
|---|---|
| Agent 中间件链机制 | 本章 MemoryMiddleware 是该章链 #14,条件挂载逻辑同源 |
| Lead Agent 设计与图构建 | 本章记忆由该章 apply_prompt_template 注入 |
| 鉴权与授权双层体系 | 本章"专家无记忆"与该章隔离哲学一脉相承 |
| 项目概览与产品愿景 | 本章是该章"不做成 RAG 工具"失败路径的关键缓解 |
| 审计、SSE 与后台任务 | 本章记忆 CRUD 经 Gateway memory API 暴露 |