开发者复盘 agent 在多轮对话中遗忘早期上下文的问题,诊断根因是滑动窗口只保留最近几轮,解决方案是加 JSONL 日志回放定位断点。
上周二,我目睹了一个 Agent 做出令人恼火的事:它收集了需求、确认了计划,然后在实现阶段完全无视那个计划。它问了同一个已经回答过的问题,丢弃了重复过两次的预算约束,产出的代码与自己的 earlier 决策相矛盾。单独看输出没问题,这让失败更难被发现——我花了第一个小时以为是模型本身不行。并非如此。模型没问题,是我的上下文管理出了问题,而揭开这个问题面纱的调试过程值得分享,因为同样的陷阱也会绊住你。
调试 Agent 的第一原则是:别相信自己的记忆,开始相信一份 transcript。我一直在线观看 Agent 工作,这意味着我只能看到最后几轮对话,而 Agent 已经丢失的信息由我自己的 mental model 填补了。于是我加了一个简单的 logger,把每一次请求和响应都写入一个 JSONL 文件,包括 token 数量和时间戳,然后再从头回放整个会话。这一放就改变了认知:Agent 使用的是滑动窗口,只保留最近的对话轮次,而那些它 commit to the plan 的轮次,在实现开始时早已被丢弃。
为了验证这个假设,我写了一个极简的复现工具,模拟滑动窗口截断对对话的影响。脚本读取 transcript,给每轮对话分配 token 数量,然后只要累计总量超过预算就丢弃最早的轮次——这正是很多 Agent 脚手架默认使用的朴素策略。
# retention_check.py
import json
import sys
def simulate_truncation(turns, token_budget=8000):
kept = []
used = 0
for turn in turns:
used += turn["tokens"]
kept.append(turn)
while used > token_budget and len(kept) > 1:
dropped = kept.pop(0)
used -= dropped["tokens"]
return kept
def main(path):
turns = json.load(open(path))
kept = simulate_truncation(turns)
key_facts = ["budget under $50", "Python", "Docker"]
for fact in key_facts:
survived = any(fact in t["text"] for t in kept)
print(f"{fact}: {'survived' if survived else 'LOST'}")
if __name__ == "__main__":
main(sys.argv[1])
运行 python retention_check.py session.jsonl,你会看到和我一样的情况:预算约束在实现前三轮就丢失了,stack 决策纯属侥幸留存,而部署方案则完全消失了。整个复现用了大约十分钟,把「好像哪里不对」的模糊感觉变成了精确、可重复的事实——这就是猜测与调试的区别。
修复方案不是加大窗口,因为更大的窗口只是延迟了同样的失败,而且让每次请求都更贵。正确的修复是压缩决策而不是丢弃决策:每隔几轮,一个 summarizer 读取完整 transcript,写出一份简短的决策账本,作为 system message 钉在上下文里。Agent 仍然逐字收到最近的对话轮次,但同时也获得了一份稳定的记录,记录它已经决定了什么——这样即使原始轮次被丢弃,预算约束也能存活下来。
def compress_and_keep(turns, model, keep_recent=5):
full_text = "\n".join(t["text"] for t in turns)
summary = model.complete(
"List only the confirmed decisions and constraints so far."
)
return [{"role": "system", "text": summary}] + turns[-keep_recent:]
声明:本文是 MonkeyCode 产品推广的一部分。我在这个免费服务器上运行了完全相同的工具链,利用其免费模型访问权限,整个实验除了写脚本的时间外没有任何成本。免费服务器意味着我不需要为了验证一个假设而去配置一台虚拟机,而且该项目宣传的 1000 万 token 免费额度足以支撑数十次复现运行,不用盯着计量器看。这种便利性对调试很重要,因为这类复盘的目的就是让失败变得低成本可复现,而免费 tier 降低了运行实验而非在脑子里推理的门槛。
话虽如此,有一些真实的限制你需要遵守。免费服务器容量和 token 限额会变化,所以在围绕它们构建工作流之前请先检查仓库中的最新数字,永远不要把生产负载指向没有 SLA 的免费 tier。 summarization 修复方案本身也有其失败模式:summary 可能丢失细节,所以你应该保留最近对话轮次逐字不变,只压缩更早的轮次,而且你应该记录这些 summary 以便审计 Agent 认为它决定了什么。如果你的 Agent 只运行几轮,这些都不重要,不应该引入复杂性;但如果你的 Agent 运行得足够长以至于会遗忘,账本方案就是「一个可信赖的工具」与「只管用一次的 demo」之间的区别。
更宽泛的教训是:AI 辅助调试依然是调试:你形成假设,构建最小复现,修复根本原因而非症状。唯一的区别是,你调试的组件是 context window,失败模式是静默的内存丢失而非崩溃。如果你的 long-running agent 也撞上了同样的墙,先试试 transcript 回放,然后做 retention check——如果你想找个免费的地方跑这个实验,MonkeyCode 的服务器方案是一个合理的起点——只是依赖它们之前请核实当前的限额。