第 6 章 · 记忆与上下文

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,
  });
}

contextManagerwhile 循环外创建,在整个会话期间持续存在。每一轮 runAgent 调用都接收同一个实例——agent 在第一轮记录的"读过 vitest.config.ts",第二轮还在。

这就是全部的"持久化"实现。没有数据库,没有文件存储,就是一个普通的 JavaScript 对象在循环外创建。

跨轮次的信息流动

用具体的例子追踪信息是怎么跨轮次流动的:

第一轮: 用户问 "项目用什么测试框架?"

  1. agent 调用 search 搜索 "test framework" → ContextManager 记录 search("test framework", "找到 vitest.config.ts")
  2. agent 调用 read_file 读取 vitest.config.ts → ContextManager 记录 readFile("vitest.config.ts", "内容...")
  3. agent 回答 "使用 vitest 框架"

第二轮: 用户说 "帮我写一个新测试"

  1. runAgent 构建系统提示词时,contextManager.formatForPrompt() 返回:
    ## 已读文件
    
    - vitest.config.ts: import { defineConfig } from 'vitest/config'
    
    ## 最近搜索
    
    - "test framework": 找到 vitest.config.ts
  2. 模型看到了这些上下文,知道项目用 vitest,直接按 vitest 风格写测试
  3. 不需要再搜索测试框架——因为上下文里已经有了

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 的记忆能力,让它从"无状态脚本"进化为"有状态助手":

新增文件作用
ContextManagersrc/memory/context.ts跟踪已读文件、失败命令、最近搜索
ProjectRulesLoadersrc/memory/rules.ts从 AGENTS.md 加载项目规则
上下文注入src/agent.ts把记忆和规则注入系统提示词
会话持久化src/main.tsContextManager 跨轮次传递

加上之前章节的测试,整个项目现在有 142 个测试用例。

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page