代码语言

知识点思维导图

29 个知识节点

Harness Engineering(07) - Memory 接口与运行时集成

读完后,你应能完成以下任务:

  • 绘制“Harness Engineering(07) - Memory 接口与运行时集成 / 先分清两个容易混的概念:上下文 ≠ 记忆”的关键对象与数据流,解释“上下文是"短期记忆"(这次聊天记得),记忆是"长期记忆"(永远记得)。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Harness Engineering(07) - Memory 接口与运行时集成 / 最实用的方案:基于文件的记忆”设计正常与异常输入,验证“启动时:harness 把 MEMORY.md(索引)读进上下文,让 Agent "瞄一眼"有哪些记忆。 -> 干活时:Agent 发现某条记忆相关,就用工具读取那个具体文件,拿到详情。 -> 学到新东西时:Agent 把新事实写成一个新文件,并在 MEMORY.md 加一行索引。”,输出首个偏差位置与回归测试结果。
  • 实现“Harness Engineering(07) - Memory 接口与运行时集成 / 最关键的问题:什么该记,什么不该记”的最小代码或配置,检验“记忆系统最大的坑不是技术,是记错东西。”,输出命令、结果与 Diff,并说明不适用边界。

上一章我们说"把该记的信息挪到窗口外"。这一章就讲清楚:怎么让 Agent 关掉再打开,还认得你、记得上次的约定。 这正是本 harness 一直在用的能力——我的 MEMORY.md 和那一堆记忆文件,就是这么回事。


一、先分清两个容易混的概念:上下文 ≠ 记忆

新手常把这两个搞混,其实它俩差别很大:

上下文(Context) 记忆(Memory)
存在哪 模型的输入窗口里 窗口外(文件/数据库)
活多久 本次会话,会话结束就没了 跨会话,关机重启还在
容量 有硬上限(会爆) 几乎无限
谁在用 每轮都喂给模型 需要时才用工具读回来

一句话:

上下文是"短期记忆"(这次聊天记得),记忆是"长期记忆"(永远记得)。 上一章治的是短期记忆爆仓,这一章建的是长期记忆。

为什么需要长期记忆? 因为你不希望每次打开 Agent, 都要重新告诉它"我喜欢用 TypeScript""这个项目用 4 空格缩进"。 记一次,以后都认得——这才像个靠谱的助手。


二、最实用的方案:基于文件的记忆

记忆系统可以做得很复杂(向量数据库、语义检索……), 但最朴素也最好用的, 是基于文件的记忆——就是本 harness 用的方式:

memory/
├── MEMORY.md              ← 索引:每条记忆一行,启动时加载进上下文
├── user-prefers-ts.md     ← 一条记忆:用户偏好 TypeScript
├── project-uses-4space.md ← 一条记忆:本项目用 4 空格缩进
└── ...

它怎么运转:

  1. 启动时:harness 把 MEMORY.md(索引)读进上下文,让 Agent "瞄一眼"有哪些记忆。
  2. 干活时:Agent 发现某条记忆相关,就用工具读取那个具体文件,拿到详情。
  3. 学到新东西时:Agent 把新事实写成一个新文件,并在 MEMORY.md 加一行索引。

💡 为什么用"索引 + 分文件"而不是把所有记忆塞一个大文件?因为索引轻量、每轮加载不占多少窗口;详情按需读取,不相关的就不占上下文。这又呼应了第 06 章"窗口只放现在要用的"。


三、最关键的问题:什么该记,什么不该记

记忆系统最大的坑不是技术,是记错东西。 记太多没用的,记忆就成了垃圾堆; 该记的没记,又等于白做。

值得记的:

  • 用户是谁、有什么偏好("用户是后端工程师,偏好 Go")
  • 明确给过的工作指令/纠正("以后提交代码前先跑测试")—— 尤其要记下"为什么"
  • 项目的特殊约定(代码里看不出来的那种,比如"这个老接口不能动,有历史依赖")
  • 外部资源指针("监控面板在 xxx""相关工单是 #123")

不该记的:

  • 代码里已经写着的(项目结构、函数签名)—— 读代码就知道,记了是冗余
  • 能从 git 历史查到的(谁改了什么)
  • 只跟这次对话有关的临时信息("把这个变量改名"——做完就没用了)

判断口诀:

"这条信息,下次会话还用得上吗?是不是代码/历史里查不到的?" 两个都是"是",才值得记。

这正是本 harness 记忆规则的核心——别记重复信息,别记一次性的东西。


四、记忆的"半衰期":会过期、会变错

记忆不是写完就一劳永逸。一个常被忽略的真相:

记忆反映的是"写下它的那一刻"为真的事实。代码会变,记忆可能就过时甚至变错了。

比如你记了"配置在 config.json", 后来文件改名成了 settings.yaml, 那条记忆就成了误导。 所以:

  • 用到记忆里提到的文件/函数/配置前,先验证它还在不在,别盲信。
  • 发现记忆错了,及时更新或删除,而不是任由它误导。
  • 写记忆时,把相对时间转成绝对时间("下周上线" → "2026-06-24 上线"),否则过段时间就看不懂了。

五、写记忆的格式:让"以后的自己"看得懂

记忆要写得结构化、可检索。 一个实用的格式(本 harness 用的就是类似结构):

---
name: user-prefers-typescript        # 唯一标识,kebab-case
description: 用户写前端偏好 TypeScript   # 一句话摘要,用于判断这条记忆相不相关
type: user                            # 分类:user / feedback / project / reference
---

用户做前端项目时一律用 TypeScript,不要用纯 JavaScript。
(如果是"纠正/指令"类,再补一句 **为什么**:因为团队规范要求类型安全。)

关键是那个 description——它是"这条记忆相不相关"的判断依据, 相当于第 04 章工具的 description, 写好了才检索得准。


六、常见误区

❌ **误区 1:把上下文当记忆用。 ** 指望"这次会话说过的话下次还在"——不会,会话一结束上下文就清空了。 要持久,必须写到窗外(文件/库)。

❌ **误区 2:什么都往记忆里塞。 ** 记忆变垃圾堆,检索时全是噪音。 少而精,只记"跨会话有用且查不到"的。

❌ **误区 3:盲信旧记忆。 ** 记忆会过时。 用之前先验证,错了就改/删。

❌ **误区 4:记了相对时间。 ** "明天""下周"过几天就没意义了,写绝对日期。

❌ **误区 5:记忆没有索引/摘要。 ** 一堆记忆没有 description,Agent 不知道哪条相关,等于没记。


七、最佳实践

分清上下文(短期)和记忆(长期),长期信息一律外置。 ✅ 索引 + 分文件:轻量索引每轮加载,详情按需读取,省窗口。 ✅ 只记"跨会话有用且代码/历史里查不到"的,记指令/纠正时连"为什么"一起记。 ✅ 每条记忆带 description,作为相关性判断依据。 ✅ 用前先验证、过时就更新、相对时间转绝对时间。


八、动手实践:跨会话记忆:跑两次,它就认得你了

这就是"上下文(短期)"和"记忆(长期)"的本质区别的活演示。

8.1 怎么跑

无需 API Key,直接跑。关键是连跑两次:

python agent.py    # 第一次:它不认识你,会"学习"并记下你的偏好
python agent.py    # 第二次:它从记忆文件认出你,直接用你的偏好

第一次输出大致是:

🤖 你好,我们好像是第一次见面。你写代码偏好哪门语言?
(学习到偏好:TypeScript,已写入记忆文件 memory/)

第二次输出大致是:

🤖 欢迎回来!我记得你偏好 TypeScript,这次还用它对吧?

跑完后会生成 memory/ 目录,里面有 MEMORY.md(索引)和一条记忆文件。 想"失忆重来"就删掉 memory/ 目录再跑。

8.2 看点

  1. 索引 + 分文件结构:打开 memory/MEMORY.md 和那条记忆文件,对照第 07 章的格式。
  2. 启动加载 vs 按需写入load_memory 在启动时读索引,save_memory 在学到新东西时写文件。
  3. 记忆条目带 description:看记忆文件的 frontmatter,理解它为什么重要。
  4. 改改记忆文件内容再跑第二次,看 Agent 的反应——记忆是真从文件读的,不是写死的。

九、总结

  • 先分清两个容易混的概念:上下文 ≠ 记忆:| 存在哪 | 模型的输入窗口里 | 窗口外(文件/数据库) |
  • 最实用的方案:基于文件的记忆:启动时:harness 把 MEMORY.md(索引)读进上下文,让 Agent "瞄一眼"有哪些记忆。 -> 干活时:Agent 发现某条记忆相关,就用工具读取那个具体文件,拿到详情。 -> 学到新东西时:Agent 把新事实写成一个新文件,并在 MEMORY.md 加一行索引。
  • 最关键的问题:什么该记,什么不该记:记忆系统最大的坑不是技术,是记错东西。
  • 记忆的"半衰期":会过期、会变错:记忆不是写完就一劳永逸。
  • 写记忆的格式:让"以后的自己"看得懂:一个实用的格式(本 harness 用的就是类似结构):
  • 常见误区:指望"这次会话说过的话下次还在"——不会,会话一结束上下文就清空了。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“Memory 接口与运行时集成”中,需要同时满足“先分清两个容易混的概念:上下文 ≠ 记忆”与“最实用的方案:基于文件的记忆”。给定正文约束“因为你不希望每次打开 Agent,”,哪些判断保持了原有处理机制?多选
2“Memory 接口与运行时集成”出现偏差:“在“Memory 接口与运行时集成 / 最关键的问题:什么该记,什么不该记”中,即使不满足“项目的特殊约定(代码里看不出来的那种,比如"这个老接口不能动,有历史依赖")”,结果与副作用仍会保持不变。”已成为实际行为。围绕“最关键的问题:什么该记,什么不该记”与“记忆的"半衰期":会过期、会变错”,哪些判断能定位被改变的职责或边界?多选
3评审“Memory 接口与运行时集成”方案时,验收条件包含“一个实用的格式(本 harness 用的就是类似结构)。”。关于“写记忆的格式:让"以后的自己"看得懂”与“常见误区”的哪些决策符合正文机制?多选