Skip to content

第 6 章:错误与恢复 — 出错后仍能继续工作的代理系统 ​

出处

来源:原网页 · 状态:已读 · PDF:book1-claude-code.pdf p.49–56

一句话 ​

代理系统是否可靠,体现在错误发生后仍能维持可解释、可限界、可继续的执行秩序。 判断成熟不成熟,不是看它顺畅时多像人,而是看它出故障时像不像系统。工程世界最不值得相信的话,就是「正常情况下」。

核心概念 ​

模型会被截断、请求会超长、hook 会制造回环、工具会中断、fallback 会发生、恢复逻辑本身也会失手。错误属于主路径,恢复是必须提前设计好的运行机制。

prompt too long 是一种必然周期 ​

对长会话代理来说,PTL 是迟早会来的季节变化。当偶发异常处理,系统迟早会被它教育。query.ts 甚至会「暂时扣下」这类错误,不立刻原样抛给用户。流式阶段的 withheld 逻辑识别 recoverable errors:prompt too long、media size error、max output tokens。有些错误要先交给恢复系统试着处理,再决定是否展示。用户真正关心的通常是系统还能不能继续干活。

恢复分层:若启用了 context collapse,先 recoverFromOverflow() 把已经 staged 的 collapse 提交掉;还不行再 tryReactiveCompact()。先排空已知积压,再做更重的全文压缩,不会一上来就重建世界。真正好的恢复系统不会把所有错误都交给最重的一把锤子。

响应式 compact:别把自己逼进死循环 ​

一旦发现错误可恢复就不停重试,会把偶发事件升级成资源灾难。两处警惕:

  1. hasAttemptedReactiveCompact:已经试过,再遇同类问题不再装傻重来。compact 之后还不行,继续 compact 大概率只是把同一种失败换个姿势再演一次。
  2. stop hooks 防死循环。直白注释:PTL 之后还让 stop hooks 介入,可能出现 death spiral:错误 → hook blocking → retry → 错误 → hook blocking。无法恢复时直接 surface 并跳过 stop hooks——继续走形式流程,只会让坏事更有仪式感。

系统里最危险的错误,是失败分支和恢复分支彼此咬住,开始无限自我复制。

max_output_tokens:恢复要以续写为主 ​

产品坏习惯:输出截断就先来一段客气废话。Claude Code 更接近工程系统:

  1. 若当前是较保守 cap,把 maxOutputTokensOverride 提升到更高值,直接重跑同一请求。不插入 meta message,不让模型先寒暄,先给它一次把原任务做完的机会。
  2. 更高 cap 也不够,追加一条 meta user message:直接继续,不要道歉,不要 recap,若中断发生在半句就从半句接着写;剩余工作拆小一点。

每一次截断后的 recap 都会进一步消耗预算并增加语义漂移。最后系统做的不再是任务本身,而是一轮轮地回顾自己做任务。较好的恢复通常是续写。

auto compact 失败熔断:恢复系统自己也要受治理 ​

当恢复机制本身不断失败时:别一直试。consecutiveFailures 超过阈值,shouldAutoCompact 即便判断「按理说该 compact 了」也直接跳过。曾有大量 session 在连续 autocompact failure 上白白烧掉海量 API calls,所以必须加 circuit breaker。不会收手的恢复系统,和不会刹车的汽车差不多。

硬原则:任何自动恢复机制都必须可计数、可限次、可熔断。 否则恢复会从保险丝变成新的起火点。

不变式:可恢复集合明确(PTL / media_size / max_output_tokens);hasAttemptedReactiveCompact ⇒ 跳过进一步 reactive compact;连续失败 < 熔断阈值;用户 abort 的 compact ≠ summary 成功;每个 withheld 的可恢复错误在恢复耗尽时必须 surface。

事件下一步
PTL + 有 stagedCollapserecoverFromOverflow()
PTL + 无 stagedCollapsetryReactiveCompact()(每回合一次)
PTL + 已试过 reactive compact直接 surface,跳过 stop hooks
compact 自身 PTLtruncateHeadForPTLRetry(),成组丢早期 API round
MOT 且 cap < MAX提 cap 重跑
MOT 且 cap = MAX追加 meta user msg 续写,不 recap 不道歉
autocompact 连败 ≥ 3跳过 compact,surface
用户 Esc 且有悬空 tool_use消费剩余结果 + synthetic tool_result,账本必须闭合

Compact 自己也会爆 ​

系统为了缩短上下文去发摘要请求,结果摘要请求也因为上下文太长而失败。truncateHeadForPTLRetry():把更早的 API round 成组地从头部剥掉,再重试 compact,避免用户卡在「连压缩都压不动」里。这种修复有损、会丢历史,但优先保证用户不被完全锁死——last-resort escape hatch。系统快要窒息时,优先级是先恢复呼吸,再讨论信息保真度。

Abort 也属于错误恢复 ​

中断是必须被正确回收的失败态,不只是交互体验。两层:query.ts 流式打断先消费 getRemainingResults(),为悬空 tool_use 生成 synthetic tool_result;compact.ts 把 abort controller 传给 forked agent,处理 APIUserAbortError,防止「被 Esc 的 compact」误算成一次成功摘要。错误恢复只管异常不管中断,会留下大量语义半残的执行轨迹——短期无人查、长期都是祸根。

真正保护的是执行叙事的一致性 ​

系统还能不能说清楚:刚才试图做什么、为什么没做成、用了什么恢复路径、现在是继续 / 停止 / 换轨。transition.reason、maxOutputTokensRecoveryCount、hasAttemptedReactiveCompact、compact boundary、synthetic error message,都是防止叙事断裂的反失忆机制。没有叙事一致性的系统表面还能输出,但内部在散。错误恢复真正修补的是系统对自己行为的解释能力;解释能力一断,工程对象就退化成玄学对象。

关键洞察 ​

抽成工程原则:恢复分层,不用一把重锤;恢复逻辑必须防自回环;自动恢复要可计数可熔断;截断后优先续写而不是总结;中断也是需要语义收尾的失败态;可靠性最终体现在出错后系统还能不能把自己的行为讲明白。

个人思考 ​

封面那句「出了岔子以后谁来收拾残局」,这一章给了可执行的答案。对照自己写 agent:最常见的偷懒是 catch 后 retry,没有 hasAttempted*、没有熔断、没有续写指令。下一章问题从单线程秩序升级成组织问题:一群不稳定执行体如何彼此约束。

相关 ​

Copyright © 2026 openalon. All rights reserved.