4. 记忆与状态
这是什么:状态层 = 把「做到哪儿了」「哪些结论不能忘」从对话里搬出来,落到 Agent 读得到的地方。
为什么用:上下文是易失的——压缩会丢、交接会漏、换会话就没了。任务状态必须活在文件里,才能跨轮次、跨会话、跨 Agent 继续。
如何用:本章先分清三种「记忆」,再讲进度文件怎么写、子代理怎么隔离状态,最后说熵的清理。
4.1 三种「记忆」别混
Agent 语境里的「记忆」至少有三个来源,混在一起是大故障的开始:
| 类型 | 是什么 | 例子 | 该放哪 |
|---|---|---|---|
| 对话历史 | 本轮会话的原始消息 | 模型上一句说了什么 | 上下文窗口里,短期 |
| 任务状态 | 当前任务做到哪儿 | 「改完模型层,正在改 API 层」 | 状态文件,随任务走 |
| 长期记忆 | 跨任务仍然成立的结论 | 「这个项目用 pnpm 不用 npm」 | 记忆文件,长期有效 |
再往外还有第四类:工具日志——它是副产品,不是记忆。放进上下文只会占地方,该落文件就落文件。
Anthropic 的「初始化 Agent + 编码 Agent」两阶段设计就是在分这个层:初始化阶段产出一份规格说明,编码阶段只消费它。中间产物不靠对话记忆传递,靠文件传递。
4.2 任务状态:写成文件
最实用的形式是一个进度文件,放在项目里,Agent 每完成一步就更新:
# .agent/progress.md
## 当前任务
给 `users` 表加软删除字段
## 状态
进行中
## 已完成
- <input type='checkbox' checked disabled> migration 脚本写好并跑通
- <input type='checkbox' checked disabled> 模型层加字段 + 全局过滤器
- <input type='checkbox' checked disabled> 删除接口改成软删除
- <input type='checkbox' disabled> 列表接口接上过滤(`apply_deleted_filter` 已写好,未接入)
- <input type='checkbox' disabled> 补测试
## 当前卡在哪
列表接口的 `get_users()` 有两处调用没走 `apply_deleted_filter`,需要改成统一走 `User.query.filter(User.deleted_at.is_(None))`。
## 关键决策
- 用 `deleted_at IS NULL` 判断,不用布尔字段(避免 NOT NULL 默认值迁移问题)
- 不加 `is_deleted` 索引,等真实数据量上来再看慢查询
## 验证命令
```bash
pytest server/tests/test_users.py -k soft_delete
```
这个文件的价值:
- 压缩不丢它——它在文件里,不在上下文里
- 换会话不丢它——新会话第一件事就是读它
- 人能看懂——出问题时人直接看这个文件判断进度
写进度文件的三条纪律:
- 只记「非显然」的事——做了什么可以直接看 git diff,不用记;为什么这么做必须记
- 记决策,不记流水账——「改了 3 个文件」没价值,「为什么不用布尔字段」有价值
- 每步更新——不是任务结束才写,是每完成一块就改,否则会写成事后总结
4.3 结构化状态:功能清单用 JSON
如果状态里有「很多个功能、各自什么进度」,用 JSON 不用 Markdown。Anthropic 的做法是用 JSON 追踪功能状态,理由很实在:Agent 不太容易乱改结构化数据。
{
"features": [
{ "id": "soft-delete-model", "status": "done", "notes": "deleted_at + 全局过滤器" },
{ "id": "soft-delete-api", "status": "wip", "notes": "删除接口已改,列表接口未接" },
{ "id": "soft-delete-tests", "status": "todo", "notes": "覆盖过滤与删除两条路径" }
],
"updated_at": "2026-10-21T18:40:00+08:00"
}
Markdown 适合人读,JSON 适合机器改。如果状态要由程序更新,就用 JSON;如果要由人审阅,就用 Markdown。
可以再加一个「推进清单」文件,让 Agent 每轮把待办勾掉:
# .agent/todo.md
- <input type='checkbox' disabled> 确认列表接口所有调用点(grep get_users)
- <input type='checkbox' disabled> 统一改成带过滤的查询
- <input type='checkbox' disabled> 补 pytest 用例
- <input type='checkbox' disabled> 跑全量测试确认没回归
4.4 子代理隔离:状态不串味
多 Agent 场景里,一个常见设计是主 Agent 负责规划与验收,子 Agent 负责执行。这样每个子 Agent 只带着一小段上下文干活,干完把结论交回主 Agent。
好处有三个:
| 好处 | 说明 |
|---|---|
| 上下文干净 | 子 Agent 只看到自己那摊事,不受主对话污染 |
| 能并行 | 多个子 Agent 同时跑不同的活儿 |
| 上下文利用率高 | 每个子 Agent 的上下文都很短,留在 Smart Zone |
代价是状态必须显式传递。主 Agent 不能假设子 Agent「知道」上下文,只能通过文件或结构化消息传。
主 Agent ──派任务──► 子 Agent A(改模型层)
│ │
│ 读 .agent/progress.md
│ 改完更新它
│ │
◄──────结果(文件 + 摘要)─┘
约定要先写下来:子 Agent 动手前读哪个文件、做完更新哪个文件、返回什么格式。这一条写进 AGENTS.md,比口头约定可靠。
4.5 上下文压缩的边界
第 2 章讲过压缩,这里补一条实务边界:什么该进摘要,什么必须留在文件里。
| 内容 | 压缩时 | 理由 |
|---|---|---|
| 工具调用原文 | 丢 | 日志占地方,细节可回查文件 |
| 已读文件的内容 | 丢 | 需要时重新读更便宜 |
| 试过什么、为什么放弃 | 保留 | 这是最贵的经验,别丢 |
| 关键决策与理由 | 保留(或落文件) | 决策丢了 Agent 会走回头路 |
| 当前任务状态 | 落文件 | 压缩不可靠,文件才可靠 |
一句话:经验压缩,状态落盘。
4.6 垃圾回收:熵不会自己消失
AI 生成的代码越多,低质量实现、重复逻辑、文档不一致也跟着变多。OpenAI 的团队一开始每周要花 20% 的时间手动清理这些生成物,后来把这事自动化了——后台 Agent 定期扫描文档不一致、架构违规和冗余代码,自动提交清理 PR。
这件事跟 Harness 的关系是:它属于 L4(状态与记忆),也是 L5(评估观测)。原因很简单——
生成速度高于清理速度时,重复逻辑和过期文档会持续进入仓库,后续 Agent 检索到的上下文也会随之变差。
也就是说,清理不是锦上添花,它直接决定后续 Agent 的上下文质量。
实操上可以先从低成本的开始:
- CI 里加检查:文档与代码不一致、目录里重复的 README,直接让流水线报错
- 定期扫一遍:每周跑一次脚本,列出「最近改动最多但引用最少」的文件,人工判断
- 再考虑自动化:等规则稳定了,再让后台 Agent 自动提 PR
4.7 L4 小结
- 分清对话历史 / 任务状态 / 长期记忆 / 工具日志四类东西
- 任务状态写成文件(
.agent/progress.md),记决策不记流水账 - 结构化状态用 JSON 追踪,人读的用 Markdown
- 子 Agent 靠文件交接状态,约定写进
AGENTS.md - 压缩时:经验留下、状态落盘、日志丢弃
- 清理是持续工作:生成速度超过清理速度,熵会污染后续上下文
🤝 记忆口诀:经验靠压缩,状态靠文件,熵靠定期清。
动手改
- 给你的项目建一个
.agent/progress.md,把当前正在做的事按第 4.2 节的模板写一遍(重点写「关键决策」那一段) - 检查一下你的工作流:任务状态现在存在哪?如果只在对话里,换个会话就断——这就是要修的地方
- 挑一个「Agent 反复做同一件事」的环节,把它固化成一个脚本或钩子,顺便看看能不能顺手清掉一批没人维护的旧文件
小结
- 四类东西要分清:对话历史(短期)、任务状态(随任务)、长期记忆(长期)、工具日志(副产品)
- 任务状态写文件,记决策和卡点,不记流水账
- 状态要程序改就用 JSON,要人审就用 Markdown
- 子 Agent 隔离上下文,靠文件交接状态,约定写进
AGENTS.md - 压缩的边界:经验压缩,状态落盘,日志丢弃
- 熵不会自己消失,清理 Agent 是 Harness 的常驻组件
下一章:约束、验证与恢复,它怎么保证不出事、做对了 🐸