MyPrototypeWhat

ContextChef (6):Snapshot & Restore——捕获决定下次编译的一切

English version: ContextChef (6): Snapshot & Restore — Capture Everything That Determines the Next Compile

Manus 在他们的博客里有一个反直觉的建议:保留错误记录,不要清理失败的工具调用。模型看到失败的操作和错误栈,会隐式更新它的内部判断,降低重复同一错误的概率——这是错误恢复能力的基础,也是 Manus 认为最能体现真正 agentic 行为的指标。

这是对的。但它解决的是一类问题:轻量失败——工具返回了错误,模型需要看到这个错误才能调整策略。

还有另一类问题:破坏性操作——工具执行了,副作用已经产生,但结果是错误的。这时候不是”让模型看到错误”就能解决的,你需要撤回整个上下文状态,从一个稳定点重新开始。

设计角度:捕获决定下次 compile() 的一切,不多不少

Snapshot 的设计角度是:chef.snapshot() 应该捕获所有决定下一次 compile() 输出的动态状态,不多也不少。

捕获什么:历史消息、动态状态、Janitor 的 token 计数游标、Core Memory 的当前键值对。这些加在一起,完整决定了 compile() 会产生什么样的 payload。

不捕获什么:工具注册信息、Janitor/VFS 等模块的静态配置、VFS 文件系统内容。这些是 Agent 的静态骨架,在 restore() 之后应该保持不变——否则回滚之后你的工具定义也消失了,Agent 就完全无法运行了。

这个边界的价值在于可预测性restore() 之后,你知道哪些东西回到了之前的状态(历史、任务状态、记忆),哪些东西没有变(工具定义、模块配置)。没有歧义,没有隐藏的副作用。

不可变快照的价值

ChefSnapshot 是只读对象。这个设计选择让快照可以安全地保存多个,互不干扰:

const snap1 = chef.snapshot("phase 1 complete");
// ... 执行 phase 2 ...
const snap2 = chef.snapshot("phase 2 complete");
// ... phase 3 失败 ...
chef.restore(snap2); // 回到 phase 2
// 或者
chef.restore(snap1); // 回到 phase 1,重试整个 phase 2

如果快照是可变的,你在 restore 之后操作会不会意外修改它,就需要时刻注意。不可变快照让这个问题不存在——快照创建之后永远是那一刻的状态,restore 只是把实例回到那个状态,不修改快照本身。

分支探索:一个实例做两条路的测试

Snapshot 的另一个高价值用法是策略对比。需要比较两种 prompt 策略或两种处理路径的效果时,不需要维护两个 ContextChef 实例——同一个实例通过 snapshot/restore 可以重复回到同一个起点:

先在稳定点打快照,走策略 A 到底,记录结果;restore 回稳定点,走策略 B 到底,记录结果;选最好的继续。每次 restore 之后,实例回到完全一致的起点,两次测试的变量被精确控制,排除了上下文差异对结果的干扰。

这个模式在某种程度上和 Anthropic 提到的 sub-agent 架构是互补的:sub-agent 适合大规模并行探索,Snapshot 适合单 agent 内的轻量分支对比。

与保留错误记录的关系

两者针对的是不同类别的失败,互不干扰:

  • 工具调用返回错误,Agent 状态没有被破坏 → 保留错误记录,让模型从失败中学习
  • 工具调用产生了破坏性副作用,需要重置状态 → restore() 到稳定快照

判断标准是:模型看到错误信息,能不能自己调整过来?能的话保留;不能的话 restore。在实践中,只读操作、可重试的查询不需要快照;数据写入、环境变更等高风险操作,在执行前打快照是标准做法。


最后一篇:Provider 适配层——为什么”写三套 prompt”是一个你应该逃离的陷阱。