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 恢复决策链的途径。
"推"和"拉":两种 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
上下文压缩通常由一次 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。
| 维度 | TaskMemory | Memory |
|---|---|---|
| 生命周期 | Session 内 | 跨 Session 持久化 |
| 写入者 | 当前 Run 的模型 | 模型 + 引擎行为信号 |
| 读取范围 | 当前 Session | 按层+命名空间+Actor 可见性过滤 |
| Run 中断后 | 保留,恢复 Run 时续用 | 独立于 Run |
| 典型内容 | Plan 步骤、步骤发现、决策理由 | 用户偏好、工作惯例、领域知识 |
"上下文丢了"的真实场景
让我们用一个真实场景把以上机制串起来。
场景: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 有三层压缩/保全机制:
- Session Memory Compaction(
sessionMemoryCompact.ts):从会话记忆内容 + 保留的尾部消息重建紧凑结果。压缩后附件自动重注入。 - Plan 文件持久化(
.claude/plan.md):Plan Mode 产出的文件在压缩后仍然可读。 - 子 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 文件恢复上下文,比在已压缩的上下文中挣扎更高效。
引用来源
- Cursor Blog, "Training Composer for longer horizons" (Mar 2026) — cursor.com/blog/self-s…
- Claude Code GitHub Issues: #10232, #55531, #75759 — 压缩丢失报告
- Du et al., "Lost in the Middle" (EMNLP 2025) — 长上下文降低推理性能
- Inside Claude Code, "19 – Plan Mode" — sessionMemoryCompact.ts + plan 持久化
- Boulea7/ClaudeCode-Source-DeepDive, "02-planning-compaction-and-assistant" — 三层压缩/保全机制
- arXiv:2606.14589, "When Errors Become Narratives: A Longitudinal Taxonomy of Silent Failures in a Production LLM Agent Runtime"
- Orchestrik, "AI Agent State Management: Why Stateless Agents Fail" — AI Agent State Management: Why Stateless Agents Fail — Orchestrik
- eClips Tech, "State Management in Agentic Workflows" — State Management in Agentic Workflows: The Problem Nobody Designs For | eClips Tech