01 · 三类记忆
Agent 需要三种不同时间尺度的记忆:短期执行链路、会话任务历史、项目级规则约定。
为什么需要记忆
到第 5 章结束时,agent 已经有了事件流、权限系统和完整的工具箱。但有一个明显的问题——每次 runAgent 调用都是独立的,没有记忆。
这意味着:
- agent 读过文件 A,下一轮对话不记得读过 A,可能重复读取
- agent 执行
npm test失败了,下一轮可能再执行同样的命令 - agent 不知道项目的代码规范,每次都按通用方式写代码
- 用户第二轮说"继续",agent 不知道上一轮做了什么
Claude Code、Codex CLI 等产品都没有这个问题——它们都能在多轮对话中保持上下文连续性。
三类记忆
按照时间尺度和用途,agent 的记忆可以分为三层:
| 类型 | 持续时间 | 内容 | 类比 |
|---|---|---|---|
| 短期记忆 | 单次 runAgent 调用 | 对话历史、工具调用和结果 | 工作记忆 |
| 会话记忆 | 一次会话(多轮对话) | 读过的文件、失败的命令、最近搜索 | 短期笔记 |
| 项目记忆 | 跨会话 | 代码规范、目录约定、常见命令 | 项目文档 |
短期记忆已经存在了——allMessages 数组就是短期记忆,它包含了完整的对话历史。模型通过阅读这个数组来理解上下文。
会话记忆是这一章要实现的——一个 ContextManager,在 main.ts 的交互循环中持续存在,跨轮次记录 agent 的操作历史。
项目记忆通过加载项目根目录的 AGENTS.md 文件来实现——把项目的规则和约定注入到系统提示词中。
短期记忆 vs 会话记忆 vs 项目记忆
用一个具体例子区分三者:
场景: 用户让 agent "把测试框架从 Jest 迁移到 Vitest",agent 需要多轮操作。
短期记忆(单次调用): 模型看到了完整的对话历史——用户说"迁移到 Vitest",agent 搜索了配置文件,读取了 package.json,修改了配置。这些信息都在 allMessages 数组中。但这次调用结束后,下次调用不会保留这个数组。
会话记忆(多轮对话): 第二轮用户说"继续"。agent 知道第一轮读过 package.json、vitest.config.ts,也知道 npm test 曾经失败过。这些信息帮助 agent 不重复劳动,直接从上次断点继续。
项目记忆(跨会话): agent 读到了 AGENTS.md 中的规则:"使用 ESM 模块格式"、"测试文件放在 test/ 目录"。这些规则在任何一次会话中都适用。
实现策略
三类记忆的实现方式各不相同:
短期记忆 → allMessages 数组(已存在,不需要改动)
会话记忆 → ContextManager 类(新建 src/memory/context.ts)
项目记忆 → ProjectRulesLoader 类(新建 src/memory/rules.ts)ContextManager 是一个纯 JavaScript 对象,在 main.ts 中创建,通过 RunAgentOptions 传入 agent 循环。agent 在执行工具后,把关键信息记录到 ContextManager。下一轮调用时,ContextManager 的内容会被注入到系统提示词中。
ProjectRulesLoader 在 agent 启动时从工作目录读取 AGENTS.md,把内容注入到系统提示词中。整个会话期间保持不变。
这一章的重点是后两类——短期记忆已经通过对话历史自然实现了,不需要额外工作。
信息不是越多越好
有一个容易犯的错误:把所有工具调用结果都存到记忆里,越多越好。
实际上,太多信息反而有害:
- token 成本增加——每次调用模型都要把上下文发过去,信息越多越贵
- 噪音干扰——模型被大量无关信息淹没,可能忽略真正重要的线索
- 上下文窗口有限——信息太多会挤占对话历史的空间
所以会话记忆的设计原则是只记录关键信息的摘要:
- 读过的文件 → 只记路径和前几行,不存完整内容
- 失败的命令 → 只记命令和错误信息
- 最近的搜索 → 只记关键词和结果摘要,最多保留 5 条
下一节会详细讲解 ContextManager 的实现。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。