本页目录
Lesson 03 — 跨会话记忆文件:Agent 的操作记忆
本课目标:让会话结束不再等于失忆——「用户要求报告 ≤500 字」这类操作性事实落成记忆文件:单事实单文件 + 索引常驻正文按需 + 写入纪律 gate + 可反悔。跨会话生效有测试为证;一条记忆的常驻成本只有一行。
1. 三种记忆分家:谁都不取代谁
v4 已有两种记忆,但「用户上次说报告要短」在哪都放不下:
| 任务经验(memory.py,frontier) | 任务进度(task_ledger.py) | 操作记忆(本课) | |
|---|---|---|---|
| 存什么 | 研究结论与教训 | TODO 树、增量简报 | 用户偏好、长期约束、项目状态 |
| 例子 | 「LangGraph 1.2 已稳定」 | 「子题 3 已完成」 | 「报告必须中文 ≤500 字」 |
| 形态 | 向量库(量大,语义召回) | sqlite(结构化语义) | 文件+索引(量小、价值密度高) |
| 谁能改 | agent | agent | agent 和人都能直接改 |
按信息形态选存储,不是谁先进谁淘汰:任务经验量大适合向量检索;操作记忆一共几十条,文件+索引让它可读、可编辑、可审计、可 git。
活参考:Claude Code 对它的用户就是这套机制——MEMORY.md 索引每次会话装载、单事实单文件、正文按需读。你学这门课的过程中,很可能正在被这套机制服务着。
2. 形态:单事实单文件 + 索引常驻、正文按需
memory_files/
MEMORY.md # 索引:每条记忆一行(常驻窗口的全部成本)
report-style.md # ---
# name: report-style
# description: 报告语言与长度偏好(用户纠正)
# type: preference
# triggers: 报告,写作,格式
# ---
# 报告必须用中文撰写;正文控制在 500 字以内。
索引是文件的视图:每次写/删后从文件重建(按名排序,双跑逐字节一致)——单一事实源是文件,索引坏了删掉重建即可。这是渐进披露在本课程的第一次出现:把「可能有用」和「此刻在场」分开计价。实测(demo 会话 2):5 条记忆,索引 48 token 常驻,正文合计 1,526 token 住磁盘,本次任务只命中 1 条——另外 4 条只付了一行索引的钱。
3. 写入纪律:比怎么记更难的是何时记
「这句话值不值得记」是模型的判断(真实系统里 LLM 提名 MemoryCandidate);gate 是代码的纪律(判断交给模型、纪律交给代码,第三次出现):
| 候选 | gate 判定 | 理由 |
|---|---|---|
| 「报告必须中文 ≤500 字」(durable) | ✅ 收 | 用户纠正,跨会话有效 |
| 「这次先查 3 个来源」(session) | 🚫 拒 | 只对本轮有意义——记了就是噪音 |
| 「本项目用 LangGraph」(already_recorded) | 🚫 拒 | 仓库/代码已记录,记忆不做代码的镜像 |
| 空名/空正文/未知类型 | 🚫 拒 | 结构完整性 |
另外两条:同名 = 更新而非新增(用户改主意是常态,「500 字→800 字」不该留下两条打架的记忆);记错了 = delete(记忆可反悔,索引同步消行)。
4. 召回纪律:记忆是背景,不是指令
- trigger 命中才读正文:每条记忆带
triggers触发词,query(summary+feedback)命中任一才注入——未命中零注入,「按需」不是「常注入」(测试锁死)。 - 防污染提示内置:注入块固定携带「可能过时,与当前对话冲突时以当前对话为准」——旧记忆可能过时(用户上季度要 500 字,这次明说要长文),盲从旧记忆比没有记忆更糟。
- 归桶:记忆块进账本的 history 桶(agent 过往学到的产物),与 researcher 的 memory_instr 同桶。
5. 流派对比:跨会话记住用户的四条路线
| 流派 | 思路 | 取舍 |
|---|---|---|
| 无记忆 | 每会话从零 | v4 现状:用户每次重复自己,长途任务跨会话即失忆 |
| 全历史回放 | 把旧会话全塞回窗口 | 窗口炸 + 大海捞针(lost in the middle 拿捏这种做法) |
| 向量检索记忆 | 旧对话切块进向量库 | 适合海量任务经验(frontier 路线);操作记忆几十条用它=杀鸡用牛刀,且人没法直接审计改错 |
| 文件记忆+索引 | 少而精、索引常驻正文按需 | 本课选择:可读可改可 git;代价是写入纪律要维护(记什么/不记什么) |
业界锚点:Claude Code(CLAUDE.md/MEMORY.md 双层:指令文件人写、记忆文件 agent 写——恰好是 L08 skill 与本课记忆的同构预告);MemGPT(记忆分层的学术先驱:主上下文/外部存储 + 自编辑记忆);ChatGPT memory(产品化的偏好记忆,同样走「少而精+可管理」而非全量回放)。
6. 跑起来
cd harness-lessons/03_memory_files
python code.py # 三会话生命周期:纠正→gate→落盘 / 跨会话召回+账本量证 / 改主意→更新→删除
关键输出:会话 2 的 writer prompt 含「500 字」✅(跨会话生效);关开关重跑不含 ✅(默认零差异);无关任务(财务图表)零注入 ✅;改主意后文件数仍 1、索引仍一行 ✅。
7. 落地清单
| 文件 | 改动 |
|---|---|
src/research_assistant/memory_files.py |
新增:MemoryCandidate + gate_memory(写入纪律)+ write/delete(upsert/可反悔/索引重建)+ match/recall_block(trigger 召回+防污染提示) |
src/research_assistant/nodes.py |
writer 在开关下按需召回注入(skill 段之后;账本入 history 桶;prompt 关态逐字节不变) |
src/research_assistant/config.py |
enable_memory_files(默认 off)、memory_files_dir |
tests/test_memory_files.py |
新增 15 测试(gate 四规则/upsert/删除/索引确定性/trigger 召回/writer 三态集成/经济性) |
「索引常驻 system」的完整形态属于长程单窗模式(v5/L09)——v4 map-reduce 没有常驻 system 段,诚实起见不做注入戏,RA 的落点是 writer 按需召回。
验收
cd portfolio-projects/research-assistant
python -m pytest tests/test_memory_files.py -q # 15 passed
python -m pytest -q # 387 passed(372 + 15)
cd ../../harness-lessons/03_memory_files && python code.py
8. 本课在两条主线上的位置
窗口经济:操作记忆的常驻成本被压到「每条一行」——第一次把「可能有用」和「此刻在场」分开计价(48 token 索引 vs 1,526 token 正文)。外置化:跨会话事实是第一块真正落盘的内容——会话断了文件还在;L06 把工作集也搬出去。
🎯 面试话术
「我的 Agent 有三种记忆各管一段:任务经验走向量召回、任务进度进账本、操作记忆进文件。文件记忆的关键是写入纪律——用户纠正记、临时参数拒收、代码里已有的不重复记、记错了能删、改主意是更新不是新增;这些规则是代码 gate 强制的,不靠模型自觉。召回侧索引常驻正文按需:5 条记忆常驻成本 48 token,触发词命中才读正文,注入块自带『过时以当前对话为准』防记忆污染。这套机制和 Claude Code 对用户做的是同一件事——我手写过一遍,知道每条纪律是防哪种事故的。」