连续聊到第 30 轮,它开始忘事
利益声明:本文作者参与了 wescode 的开发。文中涉及 wescode 的技术描述基于内部测试数据;涉及其他工具的描述基于各家公开文档和社区反馈。
你和 AI 聊了 30 轮。前 10 轮你告诉它项目架构,中间 10 轮它帮你改了几个文件,最后 10 轮你让它接着改另一个模块。
然后你发现——它给你写了一段和第 5 轮你明确否决过的方案一模一样的代码。
你说:"我之前说了不要用 moment.js。"
它说:"抱歉,让我改成 dayjs。"
你心想:你之前不是已经知道了吗?
上下文窗口是一块固定大小的黑板——精确到 token

LLM 的上下文窗口是一块大小固定的黑板。每次你说一句、它说一句、调用一个工具、返回一段代码——黑板上就多一段内容。黑板写满了,最早的内容就被擦掉。
各模型的黑板大小:
| 模型 | 上下文窗口 | 实际有效区间 |
|---|---|---|
| Claude Sonnet 4 | 200K token | ~160K(扣除系统提示后) |
| GPT-4o | 128K token | ~110K |
| DeepSeek V3 | 128K token | ~110K |
| Gemini 2.5 Pro | 1M token | 窗口大但注意力精度下降更快 |
"200K 听起来很大"——但一段编程对话消耗 token 的速度远超你的想象:
一段 30 轮对话的 token 消耗拆解
| 组成部分 | token 估算 | 说明 |
|---|---|---|
| 系统提示词 | 5,000-10,000 | 包含:规则文件内容、工具定义(每个工具 schema ~200-500 token)、项目上下文注入 |
| 每轮用户消息 | 200-800 | 你的提问、代码片段粘贴、需求描述 |
| 每轮 AI 回复 | 500-2,000 | 代码生成 + 解释说明 |
| 每轮工具调用结果 | 1,000-5,000 | read 一个 200 行文件 ≈ 2-3K token;grep 搜索结果 ≈ 1-2K token |
| 每轮合计 | 2,000-8,000 | 简单问答 ~2K,涉及读写文件的改动 ~5-8K |
30 轮对话的总消耗:
- 下限(纯问答,少工具调用):5K 系统提示 + 30 × 2K = 65K token
- 上限(频繁读写文件):10K 系统提示 + 30 × 8K = 250K token
- 典型场景(混合):8K 系统提示 + 30 × 4K = 128K token
一个典型的 30 轮编程对话,消耗约 100-150K token——已经接近 Claude 200K 上限的 50-75%、GPT-4o 128K 上限的 80-100%。
最大的 token 消费者:工具调用结果
很多人以为对话里最占 token 的是 AI 的长回复。实际上,工具调用结果才是窗口空间的最大消费者。
来看一个具体的对话片段:
你(第 12 轮):"帮我重构 UserService,把认证逻辑抽出来"
AI 的行动序列:
1. read("src/services/UserService.ts") → 返回 350 行代码 ≈ 4,200 token
2. read("src/services/AuthService.ts") → 返回 120 行代码 ≈ 1,500 token
3. grep("UserService", "src/") → 返回 28 个匹配 ≈ 1,800 token
4. read("src/controllers/UserController.ts") → 返回 200 行代码 ≈ 2,400 token
5. AI 分析 + 给出重构方案 → AI 回复 ≈ 1,500 token
6. edit("src/services/UserService.ts", ...) → 工具结果 ≈ 800 token
7. write("src/services/AuthExtractor.ts", ...) → 工具结果 ≈ 600 token
这一轮的 token 消耗:
├─ 你的消息:~50 token
├─ 工具调用结果:4,200 + 1,500 + 1,800 + 2,400 + 800 + 600 = 11,300 token
├─ AI 回复:~1,500 token
└─ 合计:~12,850 token
一轮对话就消耗了 12,850 token——其中 88% 是工具调用结果。而这些结果里的大部分(读取的文件内容)在 AI 完成改动后就不再需要了,但它们仍然占着窗口空间。
这就是为什么涉及大量代码文件的长对话比纯文字对话更容易撞到上限:不是因为你说了太多话,而是因为 AI 读了太多文件。
"忘事"的三种机制——不只是"被丢掉了"
长对话退化不是一种原因,而是三种独立的机制共同作用:
机制一:上下文截断——物理擦除
当 token 总量接近窗口上限时,最早的几轮对话被物理移出窗口。你在第 5 轮说的"不要用 moment.js"被丢掉了——AI 不是忘了,是根本看不到了。
这是最容易理解的,也是最致命的——被截断的信息 100% 丢失,没有任何降级,直接消失。
机制二:Lost in the Middle——注意力盲区
学术界研究已证实的现象(Liu et al., 2023, "Lost in the Middle: How Language Models Use Long Contexts"):LLM 对上下文中间部分的注意力显著低于开头和结尾。
注意力分布(示意):
开头 ████████████ (高)← 系统提示 + 最初几轮
中间 ████ (低)← 你的偏好、否决、约定
结尾 ██████████ (高)← 最近几轮对话
这意味着即使你的信息还在窗口里,如果它在中间位置,AI 的注意力也可能跳过它。你第 10 轮否决的方案、第 12 轮强调的命名约定——到第 25 轮时可能已经落在注意力最弱的区间。
论文的关键数据:在一个需要从长文档中检索信息的任务里,当目标信息放在文档的前 10% 或后 10% 位置时,模型准确率约 70-80%;放在正中间时,准确率降到 40-50%。这不是模型的 bug——是 Transformer 注意力机制的内在特性。
最危险的区间:研究表明 Lost in the Middle 效应在 50K-100K token 范围内最严重。一段 20-30 轮的编程对话恰好落在这个区间——长到足以产生注意力盲区,又短到不会触发截断提示。
机制三:token 竞争——有用信息被无用信息挤占
上下文窗口的容量是固定的。工具调用结果(代码文件内容、搜索结果、执行输出)占的 token 越多,留给对话历史的空间就越少。
一个具体的场景:
轮次 1-10:你建立了项目理解(~15K token 的对话)
轮次 11-20:AI 读了 8 个文件做重构(~40K token 的工具调用结果)
轮次 21-25:AI 读了 5 个测试文件(~25K token)
此时上下文已有 ~88K token(含系统提示)
轮次 1-5 的内容已经被截断——
你在第 3 轮说的"错误处理用 AppError 不用原生 Error"消失了
轮次 6-8 的内容进入了 Lost in the Middle 区间
工具调用结果是一次性的——AI 读一个文件是为了当时改它,改完之后那段文件内容就是"已消费"的 token。但它仍然占着窗口空间,挤走你更有价值的对话历史。
各工具的压缩策略——不只是"丢掉旧内容"
面对窗口上限,各工具采取了不同的策略:
Cursor:滑动窗口 + 提示开新对话
Cursor 的策略相对简单——当上下文接近上限时,丢弃最早的对话轮次(滑动窗口),并在某些模式下提示用户开新对话。
- 优点:简单可靠,最近的几轮总是完整的
- 局限:被丢掉的就是彻底丢了——你在前面建立的项目理解全部消失;新对话从零开始,没有任何连续性
Claude Code:自动紧凑化 + 提示上下文快满
Claude Code 在上下文接近限制时自动紧凑化(compact)历史——把旧轮次总结成更短的摘要。同时提示用户"上下文即将达到限制"。
- 优点:Claude 的单次长上下文能力是六款工具里最强的,200K 窗口比 128K 多出 56%;紧凑化比直接截断保留更多信息
- 局限:总结仍然有信息损失;一旦上下文满了,新对话还是从零开始
wescode:两段式管理——对话中压缩 + 对话后结算

wescode 有两个独立的机制分别解决"当次对话不忘事"和"下次对话不从零":
机制一:MidRunCompression(对话进行中)
当上下文接近窗口上限时,wescode 自动触发对话中压缩。但它不是简单地丢弃或总结——它做的是分类保留:
| 内容类型 | 处理方式 | 原因 |
|---|---|---|
| 工具调用的关键证据(代码改动、文件内容) | 提取后注入固定位置(Pinned system message) | 这些是事实,压缩后仍需可查 |
| 对话历史("我们讨论了 A 然后讨论了 B") | 压缩成摘要 | 流程可以概括,事实不能 |
| 系统提示词 | 不动 | 规则和工具定义必须完整 |
MidRunCompression 与简单截断的核心区别:截断是"先进先出"——不管内容重不重要,最早的一律丢掉。MidRunCompression 是按内容类型分类——事实证据保留,流程叙述压缩。这让 30 轮的对话不会在第 25 轮开始犯前面犯过的错。
一个 30 轮对话的压缩过程走一遍
假设你在做一个 API 模块的重构,用 Claude Sonnet(200K 窗口)。来看上下文怎么增长、什么时候触发压缩、压缩后留下了什么:
轮次 1-5:建立项目理解(对话式)
├─ 你:描述项目结构、命名约定、错误处理规范
├─ AI:确认理解
├─ 累计 token:~12K(系统提示 8K + 对话 4K)
└─ 状态:安全区间,无任何压力
轮次 6-10:读文件 + 初步改动
├─ AI 读了 4 个文件(~12K token 的工具结果)
├─ AI 做了 2 次 edit(~2K token 的工具结果)
├─ 累计 token:~40K
└─ 状态:仍然安全
轮次 11-15:大范围重构
├─ AI 读了 6 个文件(~18K token 的工具结果)
├─ AI 做了 5 次 edit + 2 次 write(~6K token)
├─ 你在第 13 轮否决了一个方案:
│ "不要把校验逻辑放在 Controller 里,放在 Service 层"
├─ 累计 token:~85K
└─ 状态:接近阈值(通常设在窗口的 70-80%)
───── MidRunCompression 触发 ─────
压缩前(~85K token):
├─ 系统提示词(8K)→ 保留,不动
├─ 轮次 1-5 的对话历史(4K)→ 压缩成摘要(~800 token)
│ 摘要内容:"用户项目使用 TypeScript + Express,错误处理用
│ AppError 类,命名约定为 camelCase,否决了 moment.js"
├─ 轮次 6-10 的工具结果(14K)→ 提取关键证据(~2K token)
│ 保留的证据:文件修改 diff(事实),丢弃的:读取的完整文件内容
├─ 轮次 11-15 的内容(~59K)→ 最近的,保持完整
└─ Pinned message 注入(证据 + 摘要 ≈ 3K token)
压缩后(~70K token → 释放了 ~15K 空间)
轮次 16-25:继续工作
├─ AI 在第 18 轮需要用到第 5 轮的否决信息
│ → 在 Pinned message 里找到"否决了 moment.js" ✅
├─ AI 在第 22 轮需要用到第 13 轮的否决信息
│ → 第 13 轮仍在窗口内 ✅
├─ 累计 token:再次接近阈值
└─ 第二次 MidRunCompression 触发...
轮次 26-30:收尾
├─ 经过两次压缩,所有关键事实仍在 Pinned message 中
├─ AI 一直知道"不用 moment.js"、"校验放 Service 层"
└─ 对话质量保持稳定
关键点:每次压缩不是"砍掉前面的",而是"把前面的事实提炼出来钉在固定位置"。这就是为什么第 30 轮 AI 仍然知道你第 5 轮说的事。

机制二:CognitiveSettlement(对话结束后)
对话结束时,主对话模型回顾整段对话,提炼关键信息存入长期记忆。下次新对话自动注入——不占上下文窗口(因为它是预处理好的结构化记忆,不是原始对话历史)。
两个机制的协作关系:
一段 30 轮对话的生命周期:
轮次 1-15:正常对话,上下文逐渐增长
轮次 16-20:接近窗口上限,MidRunCompression 触发
→ 提取关键代码证据,压缩早期对话历史
→ 释放出 ~15-30K token 空间
轮次 21-30:继续对话,AI 仍能参考早期的代码证据
对话结束:CognitiveSettlement 触发
→ 提炼:"用户否决了 moment.js、API 格式是 { data, total, page }、
错误处理从 null 改成了 AppError 异常"
→ 存入长期记忆
下一次新对话开始:
→ 长期记忆自动注入系统提示词
→ AI 已知上次的关键决策,不用重新解释
→ 上下文窗口几乎全部可用(记忆只占 ~1-2K token)
为什么"短对话 + 认知结算"比"一段长对话"效果好
这不是直觉——是上下文窗口物理机制的直接推论。
同一个重构任务的两种做法
做法 A:1 段 30 轮长对话
token 使用:~128K(接近 GPT-4o 上限)
前 10 轮信息:已被截断或在 Lost in the Middle 区间
AI 在第 25 轮的注意力质量:显著下降
错误率:第 20 轮开始明显上升(重复已否决方案、遗忘约定)
做法 B:3 段 10 轮短对话(每段结束后认知结算)
每段 token 使用:~45K(远离危险区间)
每段的信息:全部在窗口内且注意力高
每段结束后:认知结算保留关键信息
下一段开始时:上下文几乎全部可用 + 前面的记忆注入(~1-2K token)
AI 在每段的注意力质量:始终处于最佳区间
错误率:保持稳定
两种做法的量化对比:
| 指标 | 1 × 30 轮 | 3 × 10 轮 + 认知结算 |
|---|---|---|
| 单段最大 token | ~128K | ~45K |
| Lost in the Middle 风险 | 高(50-100K 区间) | 低(每段 <50K) |
| 前期信息保留率 | ~30%(截断 + 注意力衰减)(内部测试数据) | ~85%(认知结算提炼)(内部测试数据) |
| 第 25-30 轮的代码质量 | 明显下降 | 保持稳定 |
核心逻辑:短对话让你始终处于 LLM 注意力最佳的区间(<50K token),而认知结算让短对话之间不断联——你不用牺牲连续性来换取注意力质量。
怎么拆、每段多长、什么时候开新对话
拆对话的实操指南:
| 信号 | 含义 | 建议操作 |
|---|---|---|
| AI 开始重复你否决过的方案 | 上下文截断或注意力衰减 | 立刻开新对话 |
| AI 的代码风格突然变了 | 早期的风格约定被挤出窗口 | 开新对话,开头重申风格规范 |
| AI 问你已经回答过的问题 | 那轮问答被截断了 | 开新对话 |
| 一轮里 AI 读了 5+ 个文件 | 窗口空间被工具结果大量消耗 | 当前任务做完就开新对话 |
| 对话超过 15 轮 | 接近 Lost in the Middle 危险区间 | 找一个自然断点开新对话 |
每段的最佳长度:8-15 轮。这个范围让 token 总量保持在 30-60K——远离截断阈值,也远离 Lost in the Middle 的最严重区间。
自然断点的选择:
- 一个功能模块做完 → 开新对话做下一个模块
- 代码写完 → 开新对话写测试(而不是在同一段对话里又写又测)
- 讨论完设计方案 → 开新对话做实现(设计讨论的对话历史对实现没有价值,反而占窗口)
- Bug 修完 → 开新对话做下一个 bug(每个 bug 的上下文是独立的)
认知结算的能力边界——它不是完美的
诚实讲,CognitiveSettlement 有几个已知的局限:
1. 结算本身会丢信息。 一段 30 轮对话包含大量细节——代码的具体行号、中间的推理过程、某个方案被否决的完整理由。结算提炼的是"关键结论",不是"完整过程"。如果你后续需要回溯"当时为什么否决方案 B",结算可能只保留了"否决了方案 B",而没有保留"因为方案 B 在并发场景下有竞态条件"。
2. 提炼质量取决于对话质量。 如果你的对话本身很混乱——需求反复变更、多个话题交织——结算提炼出来的东西也会混乱。"用户偏好 X"和"用户否决了 X"可能在同一段对话里都出现过,结算需要判断哪个是最终决定。大多数时候它判断对了,但不是 100%。
3. 跨领域的知识不能跨对话。 如果你第一段对话聊的是后端重构,第二段聊的是前端组件——结算只保留各自领域的事实。它不会自动建立"后端改了 API 格式 → 前端需要跟着改"这种跨领域关联。这种关联需要你在新对话开头显式提一句。
怎么判断结算是否丢了关键信息:新对话开始时,如果 AI 的第一轮回复表现出对上段对话结论的了解(比如主动提到你的命名约定、之前的技术选择),说明结算有效。如果 AI 表现得像完全没有上下文,可能是结算提炼不够——这时手动在第一条消息里补充关键约定即可。
各工具完整对比
| 工具 | 窗口大小 | 对话中压缩 | 对话后记忆 | 长对话效果 |
|---|---|---|---|---|
| Cursor | 取决于模型(128-200K) | 滑动窗口截断 | 无(开新对话从零) | 前期信息逐渐丢失 |
| Claude Code | 200K(最大) | 自动紧凑化(摘要) | 可追加到 CLAUDE.md | 紧凑化有信息损失,新对话需手动 |
| Copilot | 较短 | 截断 | 无 | 设计为短交互,长对话非核心场景 |
| wescode | 取决于模型(128-200K) | MidRunCompression(分类保留证据) | CognitiveSettlement(自动长期记忆) | 对话中保留事实,对话后保留认知 |
| 通义灵码 / Trae | 取决于模型 | 标准截断 | 无 | 与 Cursor 类似 |
wescode 的代价/局限:认知结算依赖主对话模型(不用辅助模型),每次 Run 结束额外消耗一次 LLM 调用;MidRunCompression 在高频短对话场景下可能不触发(对话太短无需压缩);长期记忆的召回质量取决于写入时的提炼质量,极端情况下可能丢失细节。
你现在能做什么
不管用什么工具:
1. 长对话拆成多段短对话。 一个需要 30 轮的重构任务,拆成 3 段:第一段搭框架(10 轮),第二段实现核心逻辑(10 轮),第三段写测试和收尾(10 轮)。每段开始时把上一段的关键结论粘贴进去——手动版的认知结算。
2. 关键约定放在对话开头。 上下文窗口对开头的注意力最高。把"不要用 moment.js""错误处理用 AppError"这类约定写在第一条消息里——比在中间某一轮提到不容易被忘。
3. 注意 AI 开始"忘事"的信号。 当 AI 的代码风格突然变了、当它建议你之前否决过的方案、当它问你已经回答过的问题——这些都说明上下文开始退化。这时候开新对话比继续纠正更高效——继续纠正只是在已经拥挤的窗口里加入更多内容。
4. 减少不必要的工具调用结果。 如果你贴了一段代码在消息里,AI 就不需要再 read 那个文件。每次少读一个文件,就少消耗 2-3K token 的窗口空间。
5. 用规则文件(.cursorrules、AGENTS.md)固化关键约定。 规则文件在系统提示词位置——窗口开头,注意力最高的区间。把项目的关键约定(命名规范、技术选型、禁止事项)写进规则文件,比每次开对话手动重复可靠得多。
如果你不想手动做"拆对话 + 粘总结"这件事——wescode 的 MidRunCompression + CognitiveSettlement 帮你自动完成:对话中分类压缩保留关键事实,对话结束后提炼到长期记忆,下次自动带入、不占窗口。