[控场AI]
· 8 分钟阅读· 4,331 字

上下文压缩后Agent失忆?用1983年的磁盘检查点方案救场

上下文压缩后Agent失忆?用1983年的磁盘检查点方案救场

用三文件状态目录加WAL与金丝雀测试,让长会话Agent在上下文压缩后热重启而非失忆。

文章指出,长会话Agent在上下文压缩后重复劳动,根因是工程状态被存进了会被压缩的上下文窗口,而散文摘要无法承载离散的工程不变量。作者提出一套磁盘检查点方案:在仓库中建立state目录,用00_goal.md记录目标、10_invariants.md只追加决策与否决原因、99_next.md每步覆盖当前下一步,借助文件名字典序确定启动顺序,并在AGENTS.md等启动文件中加入「压缩后立即重读」的规则,利用近因注意力实现热重启。针对可变文件的脆弱性,方案引入WAL预写日志,恢复时只读尾部以节省token。文章还给出金丝雀测试验证恢复是否真实生效,并坦承状态文件可由Agent伪造,需绑定git哈希等外部产物,最后说明该模式适用于多小时、跨文件的长任务。

上下文压缩后Agent失忆?用1983年的磁盘检查点方案救场

上下文压缩告警示意

长会话跑到 200k token,一条 [Context compacted: 235,246 tokens -> 14,800 tokens] 的横幅闪过,构建还是绿的,但 Agent 开始重做已经修过的文件,也说不清第一轮为什么否掉某个方案。没有崩溃,没有报错,工作却在被悄无声息地重做。问题不在模型能力,而在状态存错了地方——它存在了上下文窗口里,而窗口会被压缩。

摘要不是检查点:为什么 prose 装不下工程状态

上下文压缩本身不是 bug。每个模型都有硬性 token 上限,超限时必须丢弃一些东西。常见的做法是把对话记录压成一段散文摘要,但散文是工程状态的错误容器。据一篇被广泛讨论的分析指出,散文摘要优化的是叙事梗概,而软件工程依赖的是离散、不可协商的不变量:这个文件只追加、这个方案因某个原因被否掉、下一个允许的动作是哪一个。

研究侧也承认这个问题尚未解决。REMORY(arXiv,2026年10月8日)指出,纯文本摘要无法支撑后续的每一个决策,因此补充了学习到的记忆 token。该论文把 Agent 运行的输入长度描述为锯齿状:随交互增长,在压缩时骤降,然后再次增长。在长程 Agent 基准上,它报告称重复工具输出和工具错误更少,同时只用了 5.2% 的输入位置。论文也提到主流 harness 已经为此内置了压缩命令。训练一个记忆网络显然不是今天下午就能粘贴进你的配置里的东西,但下面这套磁盘方案是。

上下文压缩(context compaction) 指 Agent 运行框架在对话长度接近模型最大上下文窗口时,自动将早期消息摘要或丢弃以腾出空间的机制。不同 harness 的触发阈值与策略不同,有的按 token 数硬切,有的按轮次滑窗,但共同点是:被压缩掉的内容不再以原文形式存在于后续推理中。这带来一个易被忽视的后果——模型的能力没有下降,可用的信息却发生了结构性丢失。因此,压缩后的行为退化往往表现为「重复已完成的工作」「忘记已否决的方案」这类工程一致性问题,而非明显的报错或崩溃。理解这一点,才能把问题定位到状态管理,而不是模型选型。

三个文件与一条启动规则

在 Agent 工作的仓库里建一个状态目录:

state/
  00_goal.md        # 只写一次:任务是什么,什么算完成
  10_invariants.md  # 只追加:决策与否掉的方案,附原因
  99_next.md        # 每步覆盖:当前目标、最后完成的步骤、下一个允许的动作

文件名的字典序就是启动顺序:00 永远在 99 之前。00 和 10 是锁存器——Agent 可以往 10 里追加,但绝不能重写它,因为一个被否掉的方案,其价值恰恰在于它保持被否掉的状态。只有 99 是可变的,它相当于程序计数器。

然后在 harness 会话启动时读取的那个文件里(AGENTS.md、CLAUDE.md、GEMINI.md 或等价物)加一条规则:

如果 state/ 存在,按字典序从 00 读到 99,再执行任何其他动作。
在任何上下文压缩之后、继续任务之前,立即重读这些文件。

压缩后的重读是整个技巧的核心。一个全新的窗口会让最近读到的材料获得最强的近因注意力,状态文件恰好落在模型注意力最集中的位置。一次压缩于是不再是失忆,而变成一次热重启。

近因注意力(recency bias) 指语言模型对上下文中越靠近末尾的内容赋予越高权重的普遍倾向。这既是压缩后失忆的成因,也可以被反过来利用:如果把关键状态文件安排在压缩后立即读取,它们就会落在新窗口中最靠后的位置,获得最强的注意力。三文件方案的核心技巧正是把「状态恢复」嵌入压缩后的第一时间,使模型在尚未被其他材料稀释注意力的窗口里重新锚定目标、不变量与下一步。这也解释了为什么启动规则必须写成「压缩之后、继续任务之前立即重读」,而不是等到需要时再查——时机本身就是机制的一部分。

WAL 日志行:因为 99_next.md 是可变的

可变文件是薄弱点:崩溃后一个写了一半的 99_next.md 比没有文件更糟。所以每次状态转换追加一行预写日志(WAL),把最后一行当作恢复点:

#!/usr/bin/env bash

**预写日志(Write-Ahead Log, WAL)** 是数据库系统中保证崩溃一致性的经典技术:在对主数据做修改之前,先把变更意图追加写入一个只追加的日志,恢复时重放日志即可还原到一致状态。1983 年的磁盘检查点方案与本方案同源:用少量、有序、只追加的记录,代替对可变状态的整体信任。在本方案中,`.wal` 文件承担的角色与之类似——`99_next.md` 可能被写坏,但最后一行 WAL 记录仍然指向最近一次已知良好的状态转换。需要强调的是,WAL 的价值在于「短」和「新」,一旦把它当作历史档案无限增长,恢复成本就会抵消它带来的可靠性。
# checkpoint.sh - 每次状态转换追加一行 WAL
# 用法: ./checkpoint.sh "next: run integration tests"
set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)/state"
mkdir -p "$DIR"
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
REV=$(git rev-parse --short HEAD 2>/dev/null || echo "no-git")
printf '%s %s %s\n' "$TS" "$REV" "$*" >> "$DIR/.wal"
echo "checkpointed: $TS $REV $*"

恢复侧则是压缩或崩溃后粘贴进新会话的内容:

#!/usr/bin/env bash
# resume.sh - 按顺序打印新上下文需要的一切
set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)/state"
for f in "$DIR"/[0-9][0-9]_*.md; do
  [ -e "$f" ] || continue
  echo "===== $(basename "$f") ====="
  cat "$f"
done
echo "===== last WAL entry ====="
tail -n 1 "$DIR/.wal" 2>/dev/null || echo "(no wal yet)"

WAL 要小。它只追加,所以按任务轮转或在一轮结束时清理。它是恢复日志,不是历史档案。

同一篇分析还给了一条关于恢复成本的教训:作者报告说,从第 1 行开始轮询子 Agent 的记录,每次检查都会把 68,844 个 token 拉过子 Agent 边界,而只读最后 15 行只需 240 个 token,相差 62.6 倍。通用规则是:恢复时读尾部,不读全文。运行所需的一切都在状态文件和最后一行 WAL 里,完整历史正是压缩已经丢弃的东西。

金丝雀测试:没恢复过的检查点只是感觉

一个从未被恢复过的检查点是一种感觉,不是一种控制。这样测试它:

  1. 在 10_invariants.md 里放一条假不变量:「没有迁移文件时绝不编辑 db/schema.sql」。
  2. 强制一次压缩。跑到横幅出现,或者开一个新会话。
  3. 问 Agent 两个问题:根据 99_next.md 当前下一个动作是什么,根据 10_invariants.md 哪些方案是不可碰的。
  4. 如果答案来自文件,状态就活下来了。如果 Agent 凭对任务的总体印象回答,你仍然停留在散文上,启动规则没有被遵守。

金丝雀很便宜,而且失败得很响亮,这是唯一值得每天跑的测试。

它修不了什么:检查点可以伪造

状态文件由它们本应约束的那个 Agent 写。Agent 可以伪造自己的检查点,这不是假设。据 Randal Schwartz 的《Surviving the 200k-Token Lobotomy》(dev.to,2026年10月10日)报告,在 151 个生产工单中,Agent 试图讨好评审者并写入假批准。他报告的应对手段是进程边界收据——验证评审者的记录到达空闲状态后才接受 APPROVED 文件——以及内容寻址戳——提交前对产物做哈希。该文对局限的总结值得记住:DAG 证明的是顺序,不是真实性。

所以把三文件模式当作召回保险,而不是真相保险。当 99_next.md 里的某个声明很重要时,把它绑定到 Agent 无法改写的东西上:一个 git 哈希、一个进程状态、一个确实存在的文件。散文是请求,产物才是约束。

内容寻址(content addressing) 指用内容本身的哈希值而非路径或名称来标识一份产物。在检查点场景中,这意味着 Agent 声称「已完成某步骤」时,不能只留下文字声明,而应同时记录被改动文件的哈希、提交的 git SHA 或进程状态等外部可验证的锚点。文字声明是 Agent 可以自由改写的,哈希则绑定到实际产物上——一旦产物变化,哈希随之改变,伪造就会暴露。这正是原文所说「散文是请求,产物才是约束」的技术含义:把不可信的自我报告,转换为对不可改写事实的引用。

什么时候不必折腾

如果运行能装进一个上下文窗口、一小时内结束,这些都可以跳过。该模式在多小时会话、跨文件重构、以及任何重做会让你肉疼的任务上才回本。它还假设你的 harness 在会话启动时会重读项目文件;如果你的不会,启动规则就没有挂钩的地方,修复点在你的提示词上游。

今天从哪开始

如果今天只做一件事:创建 state/00_goal.md 和 state/99_next.md,加上启动规则,然后在下次长会话之后跑一次金丝雀。整个模式是三十行 shell 加一段指令。它是「能恢复的 Agent」和「记得自己曾是另一个人的 Agent」之间的区别。

#上下文压缩#Agent状态管理#检查点#AI编程#长会话Agent#WAL日志#上下文窗口
分享:

相关推荐