记忆系统 — 让 Agent 拥有过去
每次对话都从零开始?不,Agent 应该记得你是谁、做过什么
📝 本章目标
读完本章,你将:
- 用三个 Prompt 让 AI 帮你构建一套完整的记忆系统
- 理解双层记忆架构——热记忆和冷记忆为什么要分开
- 了解记忆提取、注入、搜索背后的设计思想
想象你有一个非常聪明的助手。每天早上他来上班,你说”继续昨天的工作”,他一脸茫然地问”昨天做了什么?“你只好从头讲一遍项目背景、技术选型、昨天踩的坑。第二天,同样的对话再来一次。
这就是现在你的 harness 的状态——每次关掉再打开,AI 对之前发生的一切一无所知。它很聪明,但没有记忆。
一个真正有用的助手应该记住关键的事:你的项目用什么技术栈、上次讨论决定了什么方案、哪些文件改过、哪些坑踩过。它不需要记住每一句闲聊,但重要的决定和关键的上下文必须留下来。
这一章,我们给 Agent 装上记忆。
动手:用三个 Prompt 让 AI 拥有记忆
继续在你的 harness 项目里工作。如果你跟着前面几章做了,现在应该有查询引擎、工具系统、权限系统和 Agent 编排。如果没有,去 GitHub 仓库 git checkout ch05-agents 获取起点。
打开 Claude Code,确认你在项目根目录,然后跟着走。
Prompt 1:对话记忆——记住聊过什么
复制下面这段话,粘贴到 Claude Code 里:
帮我加个记忆功能——把每次对话的关键信息保存下来,
下次打开自动加载。不用记每句话,
记住重要决定和关键点就行。
具体要求:
1. 每次对话结束时,自动提取这次聊了什么重点,
保存成一条摘要
2. 下次启动时,自动加载最近几次对话的摘要,
注入到系统提示里
3. 加个 /memory 命令,能查看和管理保存的记忆
4. 记忆保存在本地文件里,简单可靠就行
等 AI 跑完,试一下:
$ harness
You > 我们的项目决定用 FastAPI 做后端,数据库用 PostgreSQL
Assistant > 好的,我记下了...
You > exit
$ harness
You > 我们上次讨论的技术选型是什么?
Assistant > 根据之前的对话记录,你们决定用 FastAPI 做后端,
数据库选择了 PostgreSQL。
(AI 记住了!)
You > /memory
[1] 2025-01-15 — 技术选型:FastAPI + PostgreSQL
[2] 2025-01-14 — 项目初始化,讨论目录结构
对话记忆有了。但有些信息不是某一次对话产生的,而是一直有效的——比如项目约定、编码规范、反复出现的问题。下一个 Prompt 解决这个问题。
Prompt 2:项目笔记——长期有效的知识
帮我加一个长期笔记功能:
具体要求:
1. 在项目根目录维护一个笔记文件,
AI 每次启动时自动读取并注入上下文
2. AI 在对话中发现值得长期记住的信息时,
自动追加到笔记里
3. 用户也可以手动让 AI 记住某些事,
比如"记住:我们的 API 前缀统一用 /api/v2"
4. 加个 /notes 命令查看和编辑笔记内容
$ harness
You > 记住:我们团队的代码风格是用 Black 格式化,
行宽 88,用 ruff 做 lint
Assistant > 好的,我已经把这个编码规范记到项目笔记里了。
You > /notes
## 编码规范
- 格式化工具:Black,行宽 88
- Lint 工具:ruff
## 技术栈
- 后端:FastAPI
- 数据库:PostgreSQL
(下次启动 harness,AI 自动知道这些)
现在 AI 有了两种记忆:对话摘要和项目笔记。但随着使用时间变长,对话记录会越来越多。全部加载?太浪费 token。只加载最近几条?可能漏掉很久以前但现在很相关的信息。
Prompt 3:智能搜索——按需回忆
帮我实现智能搜索——AI 需要回忆什么时不翻所有历史,
而是按语义相关性搜最相关的几条。
这个功能做成可选的,不装额外东西也能用基本记忆。
具体要求:
1. 把每条对话摘要生成一个向量嵌入,
存到一个本地向量数据库里
2. AI 开始新对话时,用当前话题去搜索相关的历史记忆,
只加载最相关的几条
3. 向量搜索作为可选功能——如果没装依赖库,
就退回到只加载最近 N 条的基本模式
4. 加个 /recall 命令,让用户手动搜索历史记忆
5. 搜索结果带相关度评分,只注入评分高于阈值的记忆
$ pip install chromadb # 可选,装了就有向量搜索
$ harness
You > 我记得之前讨论过数据库连接池的配置,具体怎么说的?
Assistant > 根据搜索到的历史记忆(相关度 0.89):
在 1 月 10 日的对话中,你们讨论了连接池配置...
You > /recall 数据库性能
[0.92] 2025-01-10 — 连接池配置:最大 20 连接,超时 30s
[0.85] 2025-01-08 — PostgreSQL 索引优化讨论
[0.71] 2025-01-05 — 数据库选型:决定用 PostgreSQL
(没装 chromadb 的情况下:)
$ harness
[memory] 向量搜索不可用,退回到最近 10 条模式
💡 三个 Prompt 做了什么
- Prompt 1 建立了短期记忆——对话摘要自动保存和加载
- Prompt 2 建立了长期记忆——项目笔记永久存在
- Prompt 3 建立了智能检索——按语义相关性搜索历史
现在你的 Agent 有了记忆系统。完整代码在 GitHub 仓库,对应 tag
ch06-memory。接下来我们回过头,理解你刚刚构建的东西。
深入理解
双层记忆架构
打开你刚生成的代码,你会发现记忆被分成了两个截然不同的部分。这不是随意的——它对应的是人类记忆的两种模式。
图 6-1:双层记忆架构
- 热记忆(随时在脑子里,每次启动自动加载)
- 会话摘要 → 自动注入 → AI 上下文
- 项目笔记 → 自动注入 → AI 上下文
- 冷记忆(存在档案柜里,需要时按语义搜索)
- 向量搜索 → 按需检索 → AI 上下文
- 历史归档 → 按需检索 → AI 上下文
热记忆是”随时在脑子里的东西”——最近几次对话的摘要、项目笔记里的约定。它们体积小、读取快、每次启动直接注入系统提示。就像你记得自己的名字、公司的地址——不需要去翻档案柜。
冷记忆是”存在档案柜里的东西”——几周前的对话、很久以前讨论过的细节。它们体积大、数量多,不可能全部加载。需要时用语义搜索找到最相关的几条,拉出来注入上下文。就像你去档案室查某个项目的历史文件——你不会把整个档案室搬到办公桌上。
💡 核心概念:双层记忆
记忆系统的核心设计原则:
热记忆负责”总是知道的事”,冷记忆负责”需要时能想起来的事”。
热记忆保证基本的连续性——AI 不会每次都从零开始。冷记忆保证深度——再久远的信息也不会永久丢失。两者结合,才能在 token 成本和信息完整性之间取得平衡。
为什么不全部用热记忆?因为 token 是有限的。你的上下文窗口就那么大,塞满记忆就没有空间给当前对话了。为什么不全部用冷记忆?因为向量搜索有延迟,而且语义搜索不是万能的——有些信息(比如”项目用 Python”)不是搜出来的,而是一直应该知道的。
热记忆:会话与项目
热记忆由两部分组成:会话摘要和项目笔记。
会话摘要
每次对话结束时,AI 会自动提取一段摘要。这段摘要不是对话记录的复制品,而是经过压缩的关键信息:
# harness/memory/session.py — 核心逻辑
def save_session_summary(messages, session_id):
"""对话结束时,提取摘要并保存"""
summary = llm_extract_summary(messages)
record = {
"id": session_id,
"date": datetime.now().isoformat(),
"summary": summary,
"topics": extract_topics(summary),
}
db = load_memory_db()
db["sessions"].append(record)
save_memory_db(db)
def load_recent_sessions(n=5):
"""启动时加载最近 N 条会话摘要"""
db = load_memory_db()
return db["sessions"][-n:]
会话摘要存在一个 JSON 文件里——简单、可读、可手动编辑。每条记录包含日期、摘要文本和话题标签。启动时加载最近 5 条,注入系统提示。
项目笔记:HARNESS.md 模式
项目笔记的实现更简单——就是项目根目录下的一个 Markdown 文件。AI 每次启动时读取它,就像新员工第一天读公司 Wiki。
这个模式直接借鉴了 Claude Code 的做法。Claude Code 读取项目根目录下的 CLAUDE.md 文件作为项目指令——你可以在里面写技术栈、编码规范、架构决定、已知问题,AI 每次启动都会看到。
💡 HARNESS.md 的最佳实践
一个好的项目笔记文件应该包含:
- 技术栈——语言、框架、数据库、关键依赖
- 编码约定——格式化工具、命名规范、目录结构
- 架构决定——为什么选了某个方案,拒绝了什么替代方案
- 已知问题——踩过的坑、临时的 workaround
- 当前进展——正在做什么、下一步计划
不要写成几十页的文档。几百字足够——AI 的上下文窗口很宝贵。
系统提示注入
热记忆的最终归宿是系统提示。你在第 2 章见过系统提示的分层结构——记忆注入的位置在动态区域的最后:
# harness/memory/injection.py — 注入逻辑
def build_memory_context():
"""构建记忆上下文,注入系统提示"""
parts = []
# 1. 项目笔记(始终加载)
notes = load_project_notes()
if notes:
parts.append(f"## 项目笔记\n{notes}")
# 2. 最近会话摘要
sessions = load_recent_sessions(n=5)
for s in sessions:
parts.append(f"- [{s['date']}] {s['summary']}")
# 3. 相关冷记忆(如果可用)
if vector_search_available():
relevant = search_cold_memory(current_topic)
for r in relevant:
parts.append(f"- [相关记忆] {r['summary']}")
return "\n".join(parts)
这段代码的关键在于优先级:项目笔记始终加载(它是最稳定、最重要的上下文),会话摘要加载最近几条,冷记忆只在有向量搜索能力时才注入。
冷记忆:向量搜索
当对话历史积累到几十、上百条时,全部加载既浪费 token 又会稀释当前话题的注意力。冷记忆解决这个问题——把所有历史摘要转成向量,需要时按语义搜索。
工作流程
图 6-2:冷记忆搜索流程
- Query — 用户提问
- Embed — 生成向量
- Search — 相似度搜索
- Top-K — 取前 K 条
- Inject — 注入上下文
整个流程是这样的:用户开始新对话时,系统把当前话题转成一个向量(embedding),然后在向量数据库里搜索最相似的历史摘要,取相关度最高的几条注入上下文。
什么是向量嵌入
向量嵌入(Embedding)是把一段文字变成一组数字的技术。“数据库性能优化”和”PostgreSQL 索引调优”这两句话,虽然用词不同,但含义相近,所以它们的向量在数学上”距离很近”。
搜索时,用当前话题的向量去找数据库里距离最近的几个向量——距离越近,语义越相关。这就是为什么搜”数据库性能”能找到几周前讨论”连接池配置”的对话。
优雅降级
你在 Prompt 3 里要求向量搜索是可选的。这是一个重要的设计决策——不是每个用户都愿意装额外依赖。
💡 优雅降级策略
记忆系统的可用性不应该依赖于一个可选的第三方库:
- 有向量搜索(装了 ChromaDB)——按语义相关性检索,精确找到最相关的记忆
- 无向量搜索——退回到”加载最近 N 条”的简单模式,按时间排序
两种模式共享同一套存储格式,切换成本为零。用户随时可以装上 ChromaDB 获得增强体验,也可以永远不装——基本记忆照样好用。
这个设计原则在整个 harness 中反复出现:核心功能零依赖,增强功能可选安装。记忆系统的基本能力只需要 JSON 文件和标准库,向量搜索是锦上添花。
记忆提取策略
记忆系统最关键的问题不是”怎么存”,而是**“记什么”**。不是所有对话内容都值得记住——“你好”、“谢谢”显然不需要保存。但什么信息值得提取?
- 决策和结论——“我们决定用 FastAPI”、“最终选择方案 B”。这是最高优先级,因为决策影响后续所有工作
- 技术细节——配置参数、架构选择、依赖版本。这些信息具体且容易遗忘
- 问题和解决方案——“遇到了跨域问题,通过加 CORS 中间件解决”。踩坑经验是最有价值的记忆
- 待办和计划——“下次需要处理用户认证模块”。帮助 AI 理解当前进度
- 用户偏好——“我喜欢简洁的代码风格”、“不要用缩写变量名”。让 AI 越来越了解你
提取不是简单的关键词匹配——它是一个 LLM 任务。对话结束时,用一段专门的提示词让 AI 回顾整个对话,提取符合上述标准的信息,生成结构化的摘要。
llm_extract_summary() 内部的提示词长这样:
# harness/memory/extraction.py — 提取提示词
EXTRACTION_PROMPT = """回顾以下对话,提取值得长期记住的信息。
只保留这些类型:
- 决策和结论(选了什么方案、为什么)
- 技术细节(配置、版本、架构选择)
- 问题和解决方案(踩了什么坑、怎么修的)
- 待办和计划(下一步要做什么)
- 用户偏好(喜欢什么风格、讨厌什么写法)
不要记录闲聊、问候、确认性回复。
输出 JSON 格式:
{
"summary": "一句话概括这次对话",
"key_points": ["要点1", "要点2", ...],
"topics": ["话题标签1", "话题标签2"]
}"""
这段提示词的设计有两个关键点:第一,明确告诉 AI 不要记什么——排除噪音和”不要记”同样重要;第二,输出是结构化 JSON——方便后续程序解析、存储和搜索,而不是自由文本。
这就是为什么每条摘要都不长——通常就几句话。它不是对话的复制品,而是蒸馏后的精华。
记忆注入流程
理解了记忆的存储和提取,最后一个关键问题是:这些记忆什么时候、以什么顺序注入到 AI 的上下文中?
图 6-3:记忆注入流程
- 新会话启动 — 用户打开 harness,开始新对话
- 加载项目笔记 — 读取项目根目录的笔记文件,注入基础上下文
- 加载最近会话 — 读取最近 5 条对话摘要,提供短期连续性
- 搜索冷记忆 — 用当前话题向量搜索相关历史(如果可用)
- 注入系统提示 — 按优先级组装所有记忆,写入系统提示的动态区域
注入顺序很重要。项目笔记放最前面——它是最稳定、最通用的上下文。最近会话紧随其后——提供时间上的连续性。冷记忆搜索结果放最后——它们是按需补充的细节。
为什么这个顺序很重要?因为 LLM 对系统提示中靠前的内容赋予更高的权重。项目级的约定(“我们用 Python 3.12”)比某次对话的细节(“上周三讨论了连接池”)更重要,所以放前面。
记忆容量控制
所有注入的记忆加起来不能超过上下文窗口的一个比例(通常 10-15%)。超过这个限制,记忆就会”挤掉”当前对话的空间。你的代码里有一个 MAX_MEMORY_TOKENS 常量控制这个上限。
当记忆总量超过上限时,按优先级裁剪:先砍冷记忆(相关度低的先去掉),再减少会话摘要条数,项目笔记永远保留。
Claude Code 的四层记忆
我们实现的双层架构已经很实用了。Claude Code 更进一步,把记忆分成四层,每层的持久性和作用范围不同。
图 6-4:Claude Code 的四层记忆架构
- 持久化记忆(跨 session 保留)— CLAUDE.md, settings.json, mcp.json, memories.json
- 会话记忆(当前 context window)— 对话历史, 工具调用结果, 读取的文件内容(窗口满时 compact 压缩 → 摘要保留,细节丢失)
- 外部知识(按需检索)— MCP 服务器, 代码库搜索, 向量检索
- 模型权重(训练知识,只读)— 截止训练日期,时效不可控
(注入上下文 → 工具调用 → fallback,从上到下持久性递减、动态性递增)
表 6-1:Claude Code 四层记忆对比
| 维度 | 项目指令 | 跨会话记忆 | 会话记忆 | 上下文窗口 |
|---|---|---|---|---|
| 载体 | CLAUDE.md 文件 | 本地数据库 | 内存 + 压缩 | API 请求体 |
| 生命周期 | 永久(版本控制) | 跨会话持久 | 单次会话 | 单次请求 |
| 作用范围 | 团队所有成员 | 当前用户 | 当前会话 | 当前轮次 |
| 写入方式 | 手动编辑 | AI 自动提取 | 对话自然产生 | 系统自动组装 |
| 容量 | 几百字 | 几百条摘要 | 受窗口限制 | ~200K token |
项目指令(最持久层)
CLAUDE.md 文件放在项目根目录,通过 Git 版本控制,团队所有成员共享。它不是 AI 自动生成的——而是人类手动编写和维护的。这保证了最高层记忆的可控性和一致性。
Claude Code 还支持嵌套——子目录可以有自己的 CLAUDE.md,进入该目录时自动叠加。比如 tests/CLAUDE.md 可以额外声明”这个目录下的代码用 pytest 风格”。
跨会话记忆(长期个人层)
这一层对应我们实现的对话摘要 + 项目笔记。Claude Code 的做法更精细——它会在对话中检测到”值得记住的信息”时主动提议保存,用户确认后才写入。这避免了自动提取可能带来的噪音。
会话记忆(中期层)
就是第 2 章讨论的上下文压缩机制。对话太长时自动摘要、折叠、截断。它不跨会话——关掉就没了(除非被提取到上一层)。
上下文窗口(即时层)
最底层、最短命——就是当前 API 请求里的消息列表。每次请求重新组装,包含系统提示、记忆注入、对话历史、工具结果。它是所有记忆最终的”汇合点”。
❗ 四层记忆的设计哲学
四层记忆的核心思想是信息的自然流动:
- 重要信息从下往上”沉淀”——一个临时的对话发现,如果足够重要,会被提取为跨会话记忆,最终可能被人类写进项目指令
- 不重要的信息自然”蒸发”——闲聊、临时尝试、失败的探索,不会污染长期记忆
- 每一层都有自己的容量上限和淘汰策略——确保系统不会无限膨胀
延伸思考
在进入下一章之前,回头看看你的记忆系统,思考几个问题:
- 如果两条历史记忆互相矛盾(比如”用方案 A”和后来的”改用方案 B”),AI 应该怎么处理?你的系统能处理这种情况吗?
- 向量搜索依赖嵌入模型的质量——如果嵌入模型把两个不相关的话题误判为相关,会发生什么?怎么防止?
- 多人协作时,每个人的对话记忆应该共享还是隔离?项目笔记呢?
章节小结
- 三个 Prompt 构建了完整的记忆系统:对话摘要 → 项目笔记 → 向量搜索
- 记忆分为热记忆(会话摘要 + 项目笔记,每次启动自动加载)和冷记忆(向量搜索,按需检索)
- 记忆提取的关键不是”记住一切”,而是只保留决策、技术细节、问题方案、待办和偏好
- 注入顺序按优先级排列:项目笔记 → 最近会话 → 相关冷记忆,总量控制在上下文的 10-15%
- Claude Code 使用四层记忆架构:项目指令 → 跨会话记忆 → 会话记忆 → 上下文窗口
- 核心设计原则:核心功能零依赖,增强功能可选安装;信息从下往上沉淀,不重要的自然蒸发