AI 智能体跨会话记忆:进度文件、决策日志、git 与初始化脚本
AI 智能体在会话之间没有记忆:凡是没有写入仓库文件的内容,新会话都不知道。AI 智能体的记忆由各种产物构成:PROGRESS.md(我们现在在哪里)、DECISIONS.md(为什么这样做、放弃了什么)、带有设计说明的 git 提交,以及 AGENTS.md 中的交接协议,要求智能体在开始时读取它们、在结束时更新它们;初始化脚本则检查环境并输出当前状态。
为什么智能体什么都不记得
模型在两次运行之间不保存任何状态。智能体“知道”的关于你任务的一切,都存在于当前会话的上下文窗口中:读过的文件、命令的输出、与你的对话。会话结束或上下文溢出之后,新会话从零开始,只能看到磁盘上写下的东西。
一个有用的类比:一位天才工程师每天早上来上班时,记忆都被清空了。他很快就能看懂代码,但想不起来昨天选定的是哪种架构、为什么放弃了其他方案、测试目前处于什么状态。如果没有记录,他要花十五到二十分钟重新摸索,而且很可能会重做你已经有意否决的东西。
因此,AI 智能体的记忆并不是模型的属性,而是 Harness 的一部分:由你亲自设计的一组文件和规则。好消息是,这不需要向量数据库或专门的服务。几个 Markdown 文件、git 和一点纪律就足够了。
为什么更大的上下文窗口不能解决问题
你可能觉得,等模型的窗口再大一些就好了。但复杂任务会填满任何窗口:智能体要读代码库,积累决策历史,接收冗长的测试输出,并保留整个对话。需求会随着窗口一起增长。这是架构问题,而不是容量问题。
还有第二个效应。在接近窗口上限时,许多模型会急于结束:跳过检查,选择简单的方案而不是正确的方案。Anthropic 对其自家模型描述过这一现象,不过不同模型的表现程度不同。在上下文接近上限时进行长会话,不只是丢失信息的风险,也会降低工作质量。
压缩:保留什么,丢失什么
压缩(compaction)是把对话前半部分压缩为简短摘要,以便在同一会话中腾出空间。在 Claude Code 中,/compact 命令执行这一操作,接近上限时也会自动压缩。相反,/clear 命令会清空历史,重新开始对话。
压缩的问题在于,摘要通常保留了“是什么”,却丢失了“为什么”。写好的代码仍然在文件里,但推理过程——为什么选方案 B,为什么没用某个库,为什么有意跳过优化——被压缩成几个词,或者直接消失了。智能体的下一步就会“改进”那个有意为之的决定,因为它已经看不到理由了。
| 压缩 | 重置(新会话) | |
|---|---|---|
| 发生了什么 | 对话的前半部分被摘要替换 | 会话关闭,新会话读取状态文件 |
| 保留了什么 | 模型在压缩时认为重要的内容 | 恰好是你写入文件的内容 |
| 接近窗口上限时的急躁 | 可能被保留下来 | 归零 |
| 何时适用 | 任务肯定能放进当前会话 | 任务周期长或范围不清楚 |
一条不需要复杂指标的实用规则:当任务已经占用大约 60% 的窗口时,停止写代码,开始准备交接。如果上下文只用了 40%,而任务离完成已经不远,压缩就够用了。
构成智能体记忆的四个产物
与其指望模型的记忆,不如建立一本“航海日志”。它由四部分组成,每部分各有作用。
- PROGRESS.md:我们现在在哪里。
- DECISIONS.md:为什么这样做,而不是那样做。
- git 提交:免费的状态检查点。
- AGENTS.md 中的交接协议:接班和交班时要做什么。
PROGRESS.md:进度文件
它只回答一个问题:项目现在处于什么状态。智能体在会话开始时首先读取它,在结束前最后更新它。文件应当简短,是重写而不是无限追加:历史保存在 git 中。
# PROGRESS.md
## 当前目标
商品目录搜索(功能 search-01)
## 状态
- HEAD:4f2a9c1
- make check:绿色,142/142 个测试
- 已完成:PostgreSQL 中的索引(tsvector),接口 GET /search
- 已验证:单元测试 + 对 3 个查询的手动 curl 请求
## 阻塞
- 缺少哈萨克语的词形数据——等待决定
## 下一步
为 GET /search 添加分页,标准:第 2 页的测试
注意,交接只需要四个字段:提交哈希、测试通过情况、阻塞项和下一步。如果一个全新的会话根据这个文件无法明确地继续工作,说明模板中缺了字段。
DECISIONS.md:决策日志
它保存的正是压缩时会丢失的那个“为什么”。记录格式:决定、原因、放弃了什么以及为什么放弃、约束条件。
## 2026-03-14 — 在 Redis 中缓存用户设置
- 决定:Redis,TTL 5 分钟,写入时主动失效。
- 原因:每个请求都会读取,数据量很小。
- 放弃:PostgreSQL 中的 materialized view——变更太频繁,维护不划算。
- 约束:不缓存支付设置。
其中最有价值的一行是“放弃”。它能防止下一个会话把决定“优化”回已经考虑并舍弃过的方案。与进度文件不同,决策日志只增不减:旧记录不删除,而是标记为被新决定取代。
git 历史作为检查点
提交本身就是工作中内置的版本化状态快照。智能体可以读取 git log 和 git diff,还原做过哪些改动。要让历史真正起到记忆的作用,提交信息应当解释意图,而不是复述差异内容。
# 不好
git commit -m "update search.py"
# 好
git commit -m "search: индекс tsvector вместо LIKE
LIKE '%q%' не использует индекс, на 200k товаров запрос 1.8 с.
Пагинация ещё не сделана, см. PROGRESS.md."
每次测试通过后进行小而频繁的提交,还能提供一个回滚点。如果会话走偏了,回到最后一个已验证的状态,比理清一堆半成品的修改要容易得多。
AGENTS.md 中的交接协议
如果智能体不知道状态文件的存在,它们就毫无用处。因此,要在入口指令文件(AGENTS.md 或 CLAUDE.md)中写一段简短的协议:接班时做什么,交班时做什么。
## 接班
1. 阅读 PROGRESS.md 和 DECISIONS.md。
2. 在 .agent/feature_list.json 中找到活动任务。
3. 运行 make check,确认仓库状态一致。
4. 从“下一步”继续,不要重新讨论已经决定的事情。
## 交班
1. 运行 make check,提交绿色状态。
2. 更新 PROGRESS.md:HEAD、测试、阻塞、下一步。
3. 在 DECISIONS.md 中补充新的决定。
4. 工作区中不留下未完成的内容。
协议最好保持简短:这是入口文件中的几行,而不是一大段文字。
初始化脚本:加快会话启动
为了让智能体不必手动拼凑全貌,给它一个脚本,检查环境并输出状态。脚本应当是幂等的——可以随意多次运行——并以明确的就绪提示结束。
#!/usr/bin/env bash
set -euo pipefail
echo "== 1. 环境 =="
command -v uv >/dev/null || { echo "FAIL: 未安装 uv"; exit 1; }
uv sync --frozen
echo "== 2. 仓库 =="
if [ -n "$(git status --porcelain)" ]; then
echo "WARN: 工作区不干净"
else
echo "OK: 干净"
fi
echo "HEAD: $(git log -1 --oneline)"
echo "== 3. 检查 =="
make check
echo "== 4. 状态 =="
cat PROGRESS.md
echo "已就绪,可以开始工作"
这样,接班协议中只需一行:“运行 scripts/init.sh”。在 Claude Code 中,可以用会话启动事件(SessionStart)的 hook 自动执行它;即使没有 hook,智能体也会按照文件中的指示运行脚本。
哪些内容应该放入智能体的记忆,哪些不该放
最常见的错误是把记忆变成垃圾场。智能体在启动时读取的每一行都会占用上下文空间,多余的内容会冲淡重要的信息。
| 应该放入 | 不应放入 |
|---|---|
| 当前目标和下一步 | 代码的复述——智能体自己会读 |
| 决定及其原因、被放弃的方案 | 完整的对话日志和命令输出 |
| 提交哈希和测试状态 | 依赖列表——它已经在包清单中 |
| 阻塞项和未解决的问题 | “做得很好,一切正常”,却没有说明如何验证 |
| 死胡同:尝试过什么,为什么没有成功 | 密钥、令牌、密码 |
| 带验收标准的任务 | 已关闭任务的过时笔记 |
还要按生命周期区分。项目规则很少变化,且适用于所有任务,应当放在入口指令文件中。状态每次会话都在变化,应当放在进度文件中。如果把两者混在一起,入口文件很快就会积满过时的笔记。
第一次会话:把初始化当作单独的阶段
如果第一次会话陷入混乱,之后的记忆也救不了。典型情况是:你在一个全新的仓库里写“添加搜索”,半小时后才发现测试框架配置错了,迁移的格式也不对。搜索功能确实出来了,但地基是歪的。
原因在于,实现和初始化优化的目标不同:前者追求本次会话能完成多少功能,后者追求之后所有会话的速度和可靠性。智能体倾向于选择看得见的成果,也就是代码。因此,应当把初始化单独作为一次会话,并设定一个包含四个条件的明确完成标准。
- 能够启动:在全新的克隆中,
make setup和make dev都能正常运行。 - 能够自检:存在
make test,并且至少有一个真正通过的测试。 - 能够看到进度:状态文件存在且可读。
- 能够接续工作:存在带验收标准的任务列表。
如果你经常开始新项目,可以准备一个仓库模板:Makefile、测试框架、AGENTS.md 骨架、.agent/ 目录、scripts/init.sh、linter 和 CI。这样,初始化就只剩下各项目自己的特殊部分。
如何验证记忆是否有效
最诚实的指标是:从新会话开始到第一次有意义的代码改动所花的时间。没有任何产物时,这通常要十五到二十分钟的摸索;有了良好的交接,只需两三分钟。测量一次改进前后的时间,就能看出这种纪律是否值得。
第二种方法是冷启动测试。打开一个全新的会话,输入“继续”,然后问五个问题:当前目标是什么,测试处于什么状态,关于某个关键主题做了什么决定、为什么,什么在阻塞,下一步是什么。如果智能体能依据文件、不靠猜测地回答全部五个问题,说明记忆的结构是正确的。每一个含糊的回答,都说明模板里缺了一个字段。
常见错误
- 进度文件只增不减。一个月后,它会有数百行历史。应当按当前状态重写它,历史留在 git 中。
- 没有“放弃”一栏。没有记录放弃方案的决策日志,无法防止反复争论。
- 智能体凭感觉更新状态。要写明如何验证:具体命令和结果,而不是“一切正常”。
- 在上下文上限附近交接。智能体在窗口边缘已经变得急躁;交接应当提前开始,大约在 60% 时。
- 交班时工作区不干净。未提交的半成品修改会让下一个会话摸不着头脑。
总结
AI 智能体的跨会话记忆不建立在模型之上,而是建立在文件之上:PROGRESS.md 保存当前状态,DECISIONS.md 保存原因和被放弃的方案,git 提交是附带设计说明的检查点,AGENTS.md 中的交接协议确保所有这些内容都会被读取和更新。初始化脚本把启动压缩成一条命令,冷启动测试则能检验记录是否足够。智能体的航海日志和引导(bootstrap)契约,在课程的 Harness 工程实践中有详细讲解。
常见问题
能否直接请智能体记住某件事?
只有当它把这件事写进下一个会话会读取的文件中,它才会记住。在 Claude Code 中,可以使用记忆文件和 CLAUDE.md;但对于具体任务的状态,显式的进度文件放在仓库中更可靠。
PROGRESS.md 与 CHANGELOG 有什么区别?
CHANGELOG 面向用户,按版本描述变化。PROGRESS.md 面向智能体的下一个会话,描述当前的工作状态,每次状态变化时都会重写。
为了给开发智能体做记忆,需要向量数据库吗?
在单个仓库中工作通常不需要:任务状态只占几个简短的文件,代码智能体可以直接读取。向量检索适用于需要在大量外部文档中搜索的场景。
状态文件应该提交到 git 吗?
应该。这样任何会话、任何团队成员都能访问状态,并且保留其变更历史。例外是个人笔记,以及任何包含密钥的内容。
如果智能体结束时忘了更新 PROGRESS.md,怎么办?
把更新作为交班协议中完成标准的一部分,并用机制来检查,例如 hook 或脚本,比较文件中记录的 HEAD 与当前提交是否一致。