Plan 作为外部工作记忆——长对话中 Agent 如何不忘事

0 阅读17分钟

Plan 作为外部工作记忆——长对话中 Agent 如何不忘事

2026-10-05·编排与协作·Plan 系统

上下文窗口是 Agent 的短期记忆,Plan 是外部工作记忆。当上下文被压缩后,Plan 是 Agent 恢复完整决策链的唯一途径。这篇文章讲 Plan 的记忆功能——它如何被注入、如何被回读、压缩时如何保护、以及为什么'推'和'拉'两种模式各有取舍。

上下文窗口的真实寿命

一个"帮我重构这个模块的数据库访问层"的任务,典型的工具调用轮次可能是 150-300 轮。每轮包含一次 LLM 调用和若干工具调用结果。按每轮平均 2000 tokens 估算,150 轮就是 300K tokens——即使是 200K 上下文窗口的模型也装不下。

上下文压缩不是可选的——是必然发生的。

Cursor 的博客(2026 年 3 月)直接管这叫"lossy compression"——有损压缩。他们用 RL 训练把压缩错误降低了 50%,但 50% 意味着另一半仍然丢失。Claude Code 的 GitHub issues 中有大量关于上下文压缩丢失关键信息的报告(#75759, #55531, #10232)——用户报告 Agent 在压缩后"忘了"之前的决策、丢失了操作历史、或者重复已经做过的工作。

一项 EMNLP 2025 的研究(Du et al.)更从理论层面证实:即使上下文窗口足够大,长上下文本身也会降低模型的推理性能——信息在那里,但模型"注意不到"窗口早期的内容。这被称为"lost in the middle"效应。

Agent 在长任务中面临的核心问题不是"不会做"——是"忘了为什么这样做"。

"为什么 Agent 需要 Plan"一章的结论是:Plan 的第一角色不是"进度条",是"外部工作记忆"。这篇文章展开这个论点。


认知心理学的类比

在认知心理学中,人类的记忆系统分为:

  • 工作记忆(Working Memory):容量有限(7±2 项)、实时处理当前任务的信息
  • 长期记忆(Long-term Memory):容量几乎无限、存储持久知识
  • 外部记忆(External Memory):笔记、便签、文档——把信息"卸载"到环境中以释放工作记忆

LLM 的上下文窗口类比于工作记忆——有限、实时、过了就丢。Agent 引擎的持久记忆系统(记忆层)类比于长期记忆——跨会话持久化。

Plan 类比于外部记忆——写在纸上的备忘录。

当一个工程师处理复杂任务时,他不会把所有决策都记在脑子里。他会在笔记本或 wiki 上写下:

  • 目标是什么
  • 已经尝试了哪些方案、为什么否定了
  • 选择了什么方案、理由是什么
  • 接下来要做什么

当他被打断(去开会、下班、切到另一个紧急任务)后回来继续,他首先翻看自己的笔记恢复上下文。

Agent 的 Plan 文件起的是完全相同的作用。当上下文被压缩后——就像工程师被打断后回来——Plan 是 Agent 恢复决策链的途径。


推送 vs 拉取——Plan 注入上下文的两种方式

"推"和"拉":两种 Plan 注入模式

Plan 信息怎么进入模型的上下文?有两种模式,各有取舍。

"推"模式:引擎每轮注入 Plan 摘要

在这种模式中,引擎的上下文组装器(Context Assembler)每轮自动把当前 Plan 的摘要注入系统提示词。模型不需要主动请求——Plan 信息"推"给它。

典型的注入格式:

## 当前任务计划

### 概览
重构数据库访问层,迁移到 repository 模式

### 进度
- [x] 步骤 1: 分析现有 DAO 模式(完成)
- [x] 步骤 2: 设计 Repository 接口(完成)
- [ ] 步骤 3: 迁移 UserDAO(进行中)
- [ ] 步骤 4: 迁移 OrderDAO
- [ ] 步骤 5: 更新测试

优势:

  • 模型总是知道当前 Plan——不需要额外的工具调用去"查看 Plan"
  • 上下文被压缩后,Plan 摘要在下一轮自动注入——信息不会丢失
  • 实现简单——上下文组装器在固定位置插入固定格式

劣势:

  • 占用 token 预算:每轮注入都消耗系统提示词的 token 配额。一个有 14 步的 Plan 注入可能占 500-1000 tokens——在 token 紧张的场景下不可忽视
  • 预算降级时可能被丢弃:上下文组装器有预算优先级。当窗口极度紧张时(比如用户粘贴了一段 50K tokens 的代码),低优先级的 Plan 注入可能被裁剪掉——恰恰在最需要 Plan 的时候失去了它
  • 信息可能过时:推模式注入的是 Plan 的当前快照。如果模型在本轮修改了 Plan(追加步骤、跳过步骤),注入的内容和实际状态之间有一轮的延迟

"拉"模式:模型需要时主动回读 Plan 文件

在这种模式中,Plan 被写入文件系统(比如 .plan.md)。模型需要 Plan 信息时,通过 read 工具主动读取。

优势:

  • 不占用每轮的 token 预算——只在需要时才读取
  • 信息总是最新的——读取的是文件系统中的当前状态
  • 支持更丰富的内容——文件可以包含完整的分析文本(根因链、方案对比、mermaid 图),不像推模式那样被压缩为摘要
  • 天然跨会话——文件存在磁盘上,OpenAI Codex 的"清空上下文实施"就依赖这一点

劣势:

  • 模型可能忘了去读——上下文被压缩后,如果压缩摘要中没有提到"你有一份 Plan 文件可以参考",模型可能不知道去读它
  • 额外的工具调用开销——每次回读需要一次 read 工具调用,有延迟和 token 消耗
  • 不是所有环境都有文件系统——SaaS 场景下的 Agent 可能没有持久化的文件系统

两种模式在主流产品中的体现

产品主要模式辅助模式
Cursor拉(.plan.md 文件)推(Agent Mode 中 TodoWrite 组件始终可见)
Claude Code拉(Plan 子 Agent 返回值 + 可回读 .claude/plan.md)无
OpenAI Codex拉(Plan 文件 + 清空上下文实施)推(update_plan 工具在对话中渲染 checklist)
LangGraph Plan-and-Execute推(每步执行后 replanner 收到已完成步骤列表)无

我们的引擎使用的是推模式为主、拉模式为辅:

  • 推:上下文组装器每轮注入 Plan 的步骤列表(pending 步骤的描述 + 已完成步骤的简要状态)
  • 拉:Plan 同时作为 artifact 写入文件系统,注入时附带文件路径——模型在需要完整分析时可以回读
  • 预算降级保护:Plan 注入在上下文预算体系中属于 L3 优先级(高于一般的 Overlay、低于系统提示词核心段和用户消息),确保在大多数场景下不被裁剪

压缩是有损的——但 Plan 必须完整幸存

压缩时 Plan 的命运

上下文压缩是长对话中不可避免的操作。压缩发生时,Plan 信息的保全是一个关键的工程问题。

问题:压缩器不认识 Plan

上下文压缩通常由一次 LLM 调用完成——给模型完整的对话历史,让它产出一段摘要。这个压缩 LLM 调用不知道 Plan 的存在——它看到的只是一系列消息,其中包含 plan 工具调用和返回值。

如果压缩器把 Plan 的关键决策压缩掉了(比如"为什么选方案 B 而不是 A"),Agent 在后续轮次中就失去了这个决策上下文。它可能重新评估方案选择——浪费轮次重做已经做过的决策,甚至做出相反的选择导致工作矛盾。

认知结算:Run 结束时的"做笔记"

为了解决这个问题,引擎在每次 Run 结束时执行一个叫"认知结算"(Cognitive Settlement)的操作——由主对话模型产出一组认知产物:

  • SessionState:对话的核心认知状态摘要——"我们在做什么、做到哪了、关键决策是什么"
  • Memory 条目:值得长期保留的发现和偏好
  • Learnings:可跨会话复用的经验教训

SessionState 是下一轮上下文的唯一历史来源——系统提示词中的历史信息仅从 SessionState 注入,不从旧消息推断。这意味着即使旧消息被完全丢弃,SessionState 中记录的 Plan 决策信息仍然保留。

Plan 的持久化和认知结算形成双重保护:

  • 短期(单次 Run 内):Plan 注入保证每轮都有步骤信息
  • 中期(Run 之间):认知结算把关键决策写入 SessionState
  • 长期(跨 Session):Plan artifact 文件持久化在磁盘上

机械压缩的 fallback

认知结算是 LLM 调用——它可能失败(模型超时、返回格式错误)。失败时不能让 Plan 信息蒸发。

引擎有一个"机械 fallback"——在压缩发生时,从即将被丢弃的消息中提取工具证据(tool evidence),作为 Pinned system message 注入到压缩后的上下文中。这包括关键的 Plan 操作结果——哪些步骤完成了、完成时的发现是什么。

信息保全的首选是认知交班(LLM 产出的 SessionState),不可用时机械压缩提供确定性 fallback。 不是"纯靠容量、认知交班独占保全"——那样认知交班失败时证据蒸发。


TaskMemory:Plan 的五个兄弟

Plan 不是孤立的——它是 TaskMemory 系统五种认知制品之一。理解 TaskMemory 的全貌有助于理解 Plan 在长对话中的真正角色。

五种制品

制品角色写入时机Plan 的关系
Plan任务分解计划Planning Phase自身
Findings执行中发现的事实每步完成后Plan 的每步必须产出
Decisions关键决策及理由方向选择时触发 Plan 调整的原因
Checkpoints里程碑标记阶段性完成时Plan 的进度锚点
Learnings经验总结任务结束时整个任务的元认知产出

五种制品如何协作保全认知状态

一个长任务(比如"重构认证模块"200 轮)的认知生命周期:

第 1-10 轮:Planning Phase。模型分析代码库、生成 Plan。Plan 写入 TaskMemory。

第 11-50 轮:执行 Plan 前几步。每步完成后写入 Findings("UserDAO 使用了直接 SQL,没有参数化——需要同时修复注入漏洞")。遇到方向选择写入 Decision("选择 Repository 模式而非 Active Record 模式,因为现有代码已有 DAO 层")。

第 51-80 轮:上下文窗口即将满。压缩发生。

压缩后:上下文窗口中的旧消息被摘要替代。但 TaskMemory 中的 Plan、Findings、Decisions 仍在——它们存储在 Session 级的 TaskStore 中,不在对话消息里。

第 81 轮:模型通过注入的 Plan 步骤列表知道"接下来该做步骤 4"。如果需要回顾为什么选择 Repository 模式,可以读取 Decisions。如果需要回顾 UserDAO 的注入漏洞发现,可以读取 Findings。

任务结束:Learnings 写入——"这个代码库的 DAO 层没有参数化查询,下次遇到类似模式时先检查 SQL 注入"。这个 Learning 可能晋升为引擎的持久记忆,在未来的 Session 中被召回。

TaskMemory 和 Memory(记忆系统)的关系

TaskMemory 是 Session 内的认知状态——Plan 是它的骨架。Memory 是跨 Session 的持久知识——Learnings 可能从 TaskMemory 晋升为 Memory。

维度TaskMemoryMemory
生命周期Session 内跨 Session 持久化
写入者当前 Run 的模型模型 + 引擎行为信号
读取范围当前 Session按层+命名空间+Actor 可见性过滤
Run 中断后保留,恢复 Run 时续用独立于 Run
典型内容Plan 步骤、步骤发现、决策理由用户偏好、工作惯例、领域知识

Context 取消导致认知结算产出为零的真实事故

"上下文丢了"的真实场景

让我们用一个真实场景把以上机制串起来。

场景:200 轮代码重构任务

用户请求:"把引擎的所有 handler 迁移到新的 Handle 接口。"

第 1-5 轮(规划):模型分析代码库,发现 25 个 handler,生成 Plan——按子系统分组为 6 步。Plan 写入 TaskMemory。

第 6-80 轮(执行前三步):模型逐个迁移 handler。Findings 记录每步的发现——"Memory handler 有一个未文档化的 admin 路径需要特殊处理"。Decision 记录选择——"保留旧的兼容接口但标记 deprecated,而不是直接删除"。

第 81 轮(压缩触发):上下文接近 200K 限制。压缩摘要生成——但摘要可能丢失"admin 路径特殊处理"这个细节。

第 82 轮(压缩后首轮):模型的上下文中包含:

  • 压缩摘要(可能丢失了一些细节)
  • Plan 注入(自动推送,步骤 4-6 仍 pending)
  • SessionState(上一轮认知结算的产物,包含关键决策概要)

如果没有 Plan 注入:模型读到压缩摘要"正在迁移 handler"——但可能不知道还剩哪些步骤、已经做了哪些决策。它可能重新分析已经分析过的 handler,或者遗漏"admin 路径特殊处理"的决策而直接删除兼容接口。

有了 Plan 注入:模型看到"步骤 4:迁移 Observe handlers(pending)"——立即知道接下来该做什么。如果需要回顾之前的决策,read TaskMemory 的 Decisions 可以恢复完整的决策链。

这就是"外部工作记忆"的价值——它不是让模型更聪明,是让模型在遗忘不可避免时仍然能保持连贯。


行业中类似的挑战

Cursor 用户的上下文丢失报告

Cursor Blog(2026 年 3 月)在介绍他们的 self-summarization 训练时直接承认:"summarization is lossy"。他们训练了一个 RL 模型来学习"哪些信息在压缩时不能丢"——但结果是把错误率降低了 50%,不是消除了。

用户社区中关于上下文丢失的报告(Reddit r/cursor、GitHub issues)通常描述为:

  • "Agent 忘了我之前告诉它的约束"
  • "Agent 开始重复已经做过的工作"
  • "Agent 在压缩后做了和之前相反的决策"

这些症状都是"外部工作记忆"缺失的表现——如果 Plan 中记录了约束、已完成的工作、决策理由,模型可以通过回读 Plan 恢复这些信息。

Claude Code 的压缩与子 Agent

Claude Code 处理长对话的方式和 Plan 作为记忆的关系值得注意。根据源码分析,Claude Code 有三层压缩/保全机制:

  1. Session Memory Compaction(sessionMemoryCompact.ts):从会话记忆内容 + 保留的尾部消息重建紧凑结果。压缩后附件自动重注入。
  2. Plan 文件持久化(.claude/plan.md):Plan Mode 产出的文件在压缩后仍然可读。
  3. 子 Agent 上下文隔离:子 Agent 在独立的上下文窗口中工作。当子 Agent 完成后只返回摘要——相当于做了一次"人工压缩",但压缩的质量由子 Agent 自己控制(它知道什么是重要的)。

关键洞察:子 Agent 模式本身就是一种"记忆管理"策略——把大任务拆成子任务,每个子任务在独立窗口中完成,只传递摘要。这避免了在一个窗口中处理所有信息带来的压缩损失。

生产 Agent Runtime 的"静默失败"

arXiv:2606.14589("When Errors Become Narratives",2026 年 6 月)是一篇关于生产 Agent 系统的静默失败分类学研究。其中最危险的一类——D 类"Chained hallucination and fabrication"——与上下文丢失直接相关:

"The system does not merely fail to report an error — the LLM transforms it into fluent, plausible narrative delivered to the user. We term this fail-plausible: gray failure's differential observability escalated — the observer is not just blind, it is convincingly lied to by the failure itself."

当 Agent 在压缩后丢失了关键上下文,它不会说"我忘了"——它会编造一个听起来合理的解释继续执行。这比简单的遗忘更危险,因为用户无法从输出中判断 Agent 是在基于真实记忆执行还是在编造。

Plan 作为外部工作记忆的价值:它提供了一个可验证的"真相源"——模型声称的决策可以和 Plan + Decisions 中记录的进行比对。如果模型说"我们之前决定用方案 A",但 Plan 中记录的是方案 B,至少有一个信号表明出了问题。


设计选择总结

基于以上分析,Plan 作为外部工作记忆的设计选择可以总结为:

1. 同时支持推和拉

推模式保证每轮都有 Plan 信息——不依赖模型记得去读。拉模式支持完整内容回读——不被推模式的 token 预算限制。

2. Plan 的持久化不能依赖上下文

Plan 必须在上下文压缩后仍然可访问。这意味着它必须持久化到上下文之外的存储——TaskStore(推模式的来源)或文件系统(拉模式的来源)。

3. 认知结算是 Plan 的保险

Run 结束时的认知结算把 Plan 的关键决策写入 SessionState。即使 Plan 注入因为预算原因被裁剪,SessionState 中的决策概要仍然在。

4. 五种制品比单一 Plan 更抗遗忘

Plan 提供结构("接下来做什么"),Findings 提供事实("发现了什么"),Decisions 提供理由("为什么这样选择"),Checkpoints 提供锚点("到这里为止是确认完成的"),Learnings 提供元认知("这次学到了什么")。

五种制品覆盖认知的不同维度——任何一种丢失,其他四种仍然能部分恢复上下文。 这比把所有信息都放在 Plan 步骤列表里更健壮。

5. 承认信息丢失,设计降级路径

没有任何机制能保证零信息丢失。认知结算可能失败(模型超时),Plan 注入可能被裁剪(预算不足),文件可能被删除。

正确的设计不是追求"永不丢失"——是在每一层丢失时有下一层兜底:Plan 注入 → 认知结算 SessionState → 机械 fallback 工具证据 → 文件系统 artifact → 用户重新提供上下文。

多层冗余、逐级降级——这不是工程洁癖,是长对话场景中保持 Agent 连贯性的必要成本。


你现在能做什么

1. 长任务中让 Agent 把分析写进文件,不只是说出来。 在 prompt 中加:"请把你的分析和方案写入 .plan.md 文件,后续需要时可以回读。"这在任何支持文件操作的 Agent 中都能用——不需要专门的 Plan 工具。

2. 压缩后主动让 Agent 回读 Plan 文件。 如果你注意到 Agent 在长对话中开始"漫无目的"或重复工作——让它回读自己写的 Plan 文件:"请 read .plan.md 确认当前进度和下一步。"

3. 关键决策单独记录,不要埋在对话里。 让 Agent 在做重要决策时写入独立文件(decisions.md)——"选了方案 B 因为 X 和 Y"。对话历史可以被压缩,文件不会。

4. 上下文接近上限时主动开新会话。 不要等 Agent 开始忘事——在 100+ 轮后主动开新会话,把 Plan 文件路径带过去。新会话通过 read Plan 文件恢复上下文,比在已压缩的上下文中挣扎更高效。


引用来源