06 · 实战:记忆与多轮对话
用三轮连续对话展示 ContextManager 的效果:第一轮搜索的信息在后续轮次中被记录,上下文随着会话逐步积累。
无记忆 vs 有记忆
前三节实现了 ContextManager、ProjectRulesLoader 和会话持久化。这一节用真实的多轮对话来展示记忆的效果。
先看没有 ContextManager 时会发生什么:
> 这个项目用什么测试框架?
(搜索 vitest、jest、mocha...)
(读取 package.json...)
→ 使用 vitest
> 帮我写一个 search 工具的测试
(再次搜索测试框架...) ← 重复搜索
(再次读取 vitest.config...)← 重复读取
→ (按 vitest 风格写测试)第二轮任务明明跟第一轮相关,但 agent 毫无记忆,重新搜索了同样的信息。加上 ContextManager 后,每一轮积累的上下文会自动注入到后续轮次的系统提示词中。
三轮连续对话实录
用真实的任务序列跑一遍,观察上下文如何跨轮次积累:
第一轮:探索项目结构
> 这个项目有哪些源文件?
>> 开始处理: 这个项目有哪些源文件?
... 思考中
> glob: src/**/*.ts
+ glob: src/ui/events.ts
> 执行操作
> glob: src/*.ts
+ glob: src/agent.ts
> 执行操作
> glob: src/**/*.json
+ glob: 未找到匹配 "src/**/*.json" 的文件。
> 执行操作
= 完成 [4次模型调用, 4次工具调用]ContextManager 在这轮只记录了 glob 调用。由于 glob 不触发 recordFileRead(只有 read_file 才记录),readFiles 仍然为空。
第二轮:深入查看特定文件
> src/tools/search.ts 的执行逻辑是什么?
>> 开始处理: src/tools/search.ts 的执行逻辑是什么?
... 思考中
> read_file: src/tools/search.ts
+ read_file: 文件: src/tools/search.ts (94 行)
> 执行操作
= 完成 [2次模型调用, 1次工具调用]这一轮 read_file 被执行了,ContextManager 记录:
--- 当前上下文注入 ---
## 已读文件
- src/tools/search.ts: 文件: src/tools/search.ts (94 行)
--- 上下文结束 ---从现在开始,后续每一轮的系统提示词中都包含了"agent 已经读过 search.ts"这个信息。
第三轮:利用记忆回答问题
> search.ts 中有没有处理空查询的逻辑?
>> 开始处理: search.ts 中有没有处理空查询的逻辑?
... 思考中
> read_file: src/tools/search.ts
+ read_file: 文件: src/tools/search.ts (94 行)
> 执行操作
= 完成 [2次模型调用, 1次工具调用]观察: 第三轮模型仍然调用了 read_file 重新读取 search.ts。为什么?因为 ContextManager 的文件摘要只保留了前 3 行:
/** 只保留前 3 行作为摘要 */
const lines = content.split("\n").slice(0, 3);
const summary = lines.join("\n");
this.readFiles.set(path, summary);而 read_file 工具返回的第一行是 文件: src/tools/search.ts (94 行)——这是工具的格式化输出头部,不是文件内容。3 行摘要不够让模型判断空查询逻辑,所以模型选择重新读取。
这恰好展示了记忆系统的真实局限——摘要策略决定了记忆的有效性。如果摘要包含了足够的关键信息,模型就能跳过重复读取;如果不够,模型会自行补全。
formatForPrompt 输出追踪
三轮对话后,contextManager.formatForPrompt() 的完整输出保持不变:
## 已读文件
- src/tools/search.ts: 文件: src/tools/search.ts (94 行)这个文本被追加到系统提示词的末尾,模型在每一轮都能看到。这就是"记忆"的实现方式——不是数据库,不是文件系统,就是把之前的操作摘要塞进系统提示词。
记忆的边界:命令失败也记住
ContextManager 还记录失败的命令。如果 agent 尝试运行一个不存在的命令:
> 运行测试
> run_command: npm test
+ run_command: 错误: Command not found: npmContextManager 记录:
failedCommands: {
"npm test": "错误: Command not found: npm"
}下一轮如果 agent 需要运行测试,系统提示词中会出现:
## 失败过的命令
- `npm test`: 错误: Command not found: npm模型看到这个信息,会改用 pnpm test 或 npx vitest run——避免重蹈覆辙。
记忆的容量限制
ContextManager 有两个容量限制:
/** 文件摘要只保留前 3 行 */
const lines = content.split("\n").slice(0, 3);
/** 最近搜索最多保留 5 条 */
private static readonly MAX_RECENT_SEARCHES = 5;这些限制的目的是控制上下文长度。如果系统提示词太长,模型的有效上下文窗口会变小,响应质量下降。所以记忆是"精选摘要"而不是"完整记录"。
这一章做了什么
回顾第 6 章的成果:
- ContextManager:三类信息(已读文件、失败命令、最近搜索),每类有独立的容量策略
- formatForPrompt():把记忆格式化为可注入系统提示词的 markdown
- ProjectRulesLoader:从 AGENTS.md 加载项目规则
- 会话持久化:ContextManager 在 main.ts 的 while 循环外创建,跨轮次传递
- agent 集成:记忆自动注入系统提示词,不需要额外参数
agent 从第 5 章的"无状态工具"进化为"有状态助手"。下一章会给 agent 加上技能系统,让它能根据任务类型切换工作方式。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。