05 · 多轮任务连续性
在 main.ts 的交互循环中持久化 ContextManager,让 agent 跨轮次记住之前的操作。
多轮对话的真实场景
用户使用 coding agent 时,很少一次对话就能完成任务。更常见的模式是:
第一轮: "这个项目用什么测试框架?" → agent 搜索并回答 "使用 vitest"
第二轮: "帮我添加一个新的测试文件" → agent 应该记得项目用 vitest,不需要再搜索
第三轮: "运行测试" → agent 应该知道测试命令是 pnpm test
如果每一轮都是独立的,agent 在第二轮还得重新搜索测试框架,第三轮还得重新查找测试命令。这就是"多轮任务连续性"要解决的问题。
持久化的关键:在循环外创建 ContextManager
看 main.ts 的结构:
const contextManager = new ContextManager(); // 在循环外创建
const projectRules = await rulesLoader.load(workingDir);
while (true) {
const input = await rl.question("> ");
const result = await runAgent(state, model, tools, {
permissionGuard,
emitter,
contextManager, // 每轮传入同一个实例
projectRules,
});
}contextManager 在 while 循环外创建,在整个会话期间持续存在。每一轮 runAgent 调用都接收同一个实例——agent 在第一轮记录的"读过 vitest.config.ts",第二轮还在。
这就是全部的"持久化"实现。没有数据库,没有文件存储,就是一个普通的 JavaScript 对象在循环外创建。
跨轮次的信息流动
用具体的例子追踪信息是怎么跨轮次流动的:
第一轮: 用户问 "项目用什么测试框架?"
- agent 调用
search搜索 "test framework" → ContextManager 记录search("test framework", "找到 vitest.config.ts") - agent 调用
read_file读取vitest.config.ts→ ContextManager 记录readFile("vitest.config.ts", "内容...") - agent 回答 "使用 vitest 框架"
第二轮: 用户说 "帮我写一个新测试"
runAgent构建系统提示词时,contextManager.formatForPrompt()返回:## 已读文件 - vitest.config.ts: import { defineConfig } from 'vitest/config' ## 最近搜索 - "test framework": 找到 vitest.config.ts- 模型看到了这些上下文,知道项目用 vitest,直接按 vitest 风格写测试
- 不需要再搜索测试框架——因为上下文里已经有了
RunAgentOptions 的完整变化
第 6 章为 RunAgentOptions 新增了两个字段:
export interface RunAgentOptions {
maxIterations?: number;
permissionGuard?: PermissionGuard;
emitter?: AgentEmitter;
/** 上下文管理器(第 6 章新增) */
contextManager?: ContextManager;
/** 项目规则内容(第 6 章新增) */
projectRules?: string;
}两个字段都是可选的。不传的话,agent 的行为和第 5 章完全一样——没有上下文跟踪,没有项目规则注入。这保证了向后兼容。
main.ts 的完整改动
main.ts 从 v0.5.0 升级到 v0.6.0,改动集中在三个地方:
1. 导入新模块:
import { ContextManager, ProjectRulesLoader } from "./memory";2. 在循环外创建实例:
const contextManager = new ContextManager();
const rulesLoader = new ProjectRulesLoader();
const projectRules = await rulesLoader.load(workingDir);
if (projectRules) {
console.log("已加载项目规则: AGENTS.md\n");
}3. 传入 runAgent:
const result = await runAgent(state, model, tools, {
permissionGuard,
emitter,
contextManager,
projectRules: projectRules ?? undefined,
});记忆的局限
这个实现的记忆有几个明显的局限,在后续章节中可以改进:
局限一:会话结束后记忆消失。 ContextManager 存在内存中,终端关闭就没了。如果需要跨会话记忆,需要持久化到文件(比如 .agent/memory.json)。
局限二:没有摘要机制。 如果会话很长,上下文会越来越大。理想情况下应该有"遗忘"机制——把旧的、不重要的信息压缩或丢弃。
局限三:没有智能选择。 当前是固定规则(记录 read_file、search、失败的 run_command)。更高级的做法是让模型自己决定哪些信息值得记住。
但这些局限不影响课程的核心目标——理解"agent 需要记忆"这个问题,以及"怎么把记忆注入到系统提示词中"这个解法。具体策略可以后续迭代。
本章回顾
这一章实现了 agent 的记忆能力,让它从"无状态脚本"进化为"有状态助手":
| 新增 | 文件 | 作用 |
|---|---|---|
| ContextManager | src/memory/context.ts | 跟踪已读文件、失败命令、最近搜索 |
| ProjectRulesLoader | src/memory/rules.ts | 从 AGENTS.md 加载项目规则 |
| 上下文注入 | src/agent.ts | 把记忆和规则注入系统提示词 |
| 会话持久化 | src/main.ts | ContextManager 跨轮次传递 |
加上之前章节的测试,整个项目现在有 142 个测试用例。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。