上下文会退化。这已经确认了。问题是怎么办。答案简单得令人意外:把东西写下来。
Scratchpad 文件——一个代理在会话中读写的普通文件——把发现从对话上下文中外部化。一旦一个事实在文件中,它就不受上下文退化影响。代理可以在任何时候重新读取文件并恢复它记录的精确细节,不管此后积累了多少上下文。
为什么有效:上下文是易失的,文件不是
对话上下文是固定容量的缓冲区。新信息进来,旧信息被压缩或挤走。Scratchpad 文件完全在缓冲区之外。它不争夺上下文空间,不被摘要,不随时间丧失精度。
在一个研究探索代理上的实测效果:
| 指标 | 无 scratchpad | 有 scratchpad |
|---|---|---|
| 30 分钟时的具体性 | 75% | 88% |
| 60 分钟时的具体性 | 28% | 79% |
| 具体性下降 | 47 个百分点 | 9 个百分点 |
| 最终报告准确率 | 64% | 91% |
Scratchpad 代理每轮大约花 8% 的时间在文件 I/O 上。这对最终输出 27 个百分点的准确率提升来说是微不足道的代价。
记什么:具体事实,不是印象
Scratchpad 的价值完全取决于往里写什么。模糊的条目恰好复现了 scratchpad 本该防止的问题。
差的条目:
找到一篇关于 AI 医疗采用的相关论文——数据不错
好的条目:
Chen et al. (2025) "AI Adoption in Rural Healthcare" — 12 家诊所
诊断延迟减少 47%,n=3,400,发表于 JAMA Digital Health
代码探索同理。auth 模块看起来很标准 毫无价值。src/auth/jwt.ts:12 → verifyJWT(token: string): Promise<Claims> 恰恰是代理 45 分钟后需要的,到那时上下文已经把这个发现退化成了”认证中间件处理这个”。
规则很直接:如果条目放到任何项目都成立,那就太模糊了。Scratchpad 条目应该包含带行号的文件路径、函数签名、精确数值、具体标识符——这些正是上下文退化最先侵蚀的细节。
结构很重要:为检索而组织
随着探索推进,一个扁平的发现列表变得难以导航。两种结构模式效果好:
按功能区域分节 用于代码库探索:
## 认证
- src/auth/jwt.ts:12 → verifyJWT() 验证 token 签名 + 过期
- src/auth/middleware.ts:45 → authGuard() 包装路由处理器
## 数据库
- src/db/pool.ts:8 → 连接池 max=20, timeout=5000ms
按问题分行 用于多问题追踪(客服、bug 分流):
| 问题 | 订单 ID | 金额 | 状态 | 解决方案 |
|------|---------|------|------|---------|
| 1 | ORD-4421 | $89.99 | 已退款 | 政策例外 |
| 2 | ORD-4455 | $34.50 | 争议中 | 等待经理 |
结构化格式防止一种特定的失败模式:跨问题混淆。当客服代理处理 3 个订单和 2 个政策例外时,叙述式摘要会把细节混在一起。表格格式让每个问题的数据在自己的行里,代理永远不会把错误的退款金额套到错误的订单上。
持续更新,不是一次性
一个在会话开始时写一次然后再也不更新的 scratchpad 会过时。这是可度量的:
一个团队为他们的客服代理部署了只写一次的 scratchpad。总体错误率从 18% 降到 7%——显著改善。但剩余错误的 85% 发生在对话中途变化的事实上:客户更正订单号、添加新问题、更新物流偏好。
修复很明显:关键事实变化时更新 scratchpad。不只是创建时,不只是定时——每当代理遇到修改或补充已记录内容的信息时。
对于长时间运行的会话(多小时的代码库审计、延长的研究),更新周期是:
- 记录 发现时立即写入
- 更新 事实变化时修改条目
- 重读 引用早期工作时重新读取 scratchpad
这不是可选的。过时的 scratchpad 比没有 scratchpad 更糟,因为它给代理对过时数据的虚假信心。
Scratchpad + Compact 循环
对于反复填满上下文窗口的会话(500 文件审计每 30-45 分钟达到 90% 容量),scratchpad 与 /compact 结合成重复循环:
- 写入 探索期间持续把发现写到 scratchpad
- 压缩 上下文接近容量时——这释放空间但有损
- 重读 压缩后重读 scratchpad 恢复有损压缩可能移除的具体细节
- 继续 用恢复的具体信息和释放的上下文空间继续探索
这个循环可以在多小时会话中反复进行。关键洞察:没有 scratchpad 的压缩是有风险的,因为 /compact 可能把代理需要的精确文件路径和行号摘要掉。Scratchpad 确保这些细节在压缩后完好无损。
反模式是只在最后、生成最终报告之前才写 scratchpad。到那时候,代理的发现已经退化了。它只能记录几小时上下文退化后残留的模糊引用——scratchpad 捕获的是代理还记得的东西,而到那时,它已经记不住多少了。
跨域应用:测试驱动迭代
使用测试驱动迭代(写测试、实现、分享失败、修复、重复)的开发者每轮生成大量上下文。15 轮之后,第 1-5 轮建立的设计约束随着测试输出和修复代码的积累被压缩了。
结果:后期轮次的修复与早期约束冲突。不是因为模型的推理能力退化了,而是因为早期约束在上下文中不再有足够的具体性来指导决策。
一个保存关键约束的 scratchpad 文件(“每个端点最多 3 次重试——第 2 轮确定”、“请求路径中不允许同步 DB 调用——第 4 轮”)在上下文积累中存活下来,保持所有迭代的一致性。这就是 D3 会话管理(测试驱动工作流)遇上 D5 上下文保持(scratchpad)的地方——单独使用每种技术效果都会打折。
Scratchpad 不是什么
它不是对话副本。把完整对话转储到文件没有意义——代理已经在上下文中有这个对话了。Scratchpad 的价值在于提取和组织,不是复制。
它不是格式问题。JSON vs Markdown vs 纯文本,如果内容是模糊的就无所谓。{ "note": "good data" } 和纯文本文件中的 good data 一样没用。
它不是管理上下文的替代品。Scratchpad 补充压缩和子代理隔离;不替代它们。一个写了 scratchpad 但从不压缩的代理仍然会填满上下文窗口。
一句话总结: 发现时立即把具体信息写到文件里,事实变化时更新文件,压缩后重读——文件不会忘,上下文会。