OpenClaw源码解析(十一):上下文运行时恢复链
一个问题:
当上下文开始失控时,OpenClaw 到底怎么恢复,什么时候压缩,什么时候截断,什么时候放弃
1. 先区分 4 个动作
1.1 预处理
就是每轮固定会做的 sanitize / validate / limit / repair。
它的目标是让历史“结构合法”,不是恢复 overflow。
1.2 auto-compaction
发生在 attempt 内部。
可以理解成:
这一轮正在运行时,底层 session/runtime 自己先压缩了一次。
1.3 explicit compaction
发生在外层 run.ts。
可以理解成:
attempt 已经结束了,外层判断还是 overflow,就会主动发起一轮正式压缩。
1.4 truncation
不是压缩整个会话,而是定向砍掉局部超大的内容,最典型是 tool result。
2. 恢复链什么时候开始
不是每轮都会进入恢复链。
正常路径是:
组装上下文-> 调模型-> 正常返回-> afterTurn
只有外层 run.ts 看到错误像下面这些情况,才会进入恢复判断:
-
context overflow -
compaction failure -
timeout -
auth / overload / failover 问题
本篇只聚焦和上下文相关的主分支:
像 context overflow-> 看本轮是否已经 auto-compaction-> 必要时 explicit compaction-> 必要时 tool result truncation-> 仍失败才最终收敛成错误
3. auto-compaction 为什么和 explicit compaction 不是一回事
3.1 auto-compaction
它通过事件流暴露:
-
auto_compaction_start -
auto_compaction_end
而且 attempt 不会因为 prompt 返回就直接结束,还会:
-
waitForCompactionRetry()
意思是:
只要 compaction 还在内部收尾,这一轮的上下文状态就还不稳定。
3.2 explicit compaction
外层在 overflow 恢复分支里会调用:
-
contextEngine.compact(…)
当前默认 legacy 会桥接到老的 compaction 实现。
4. 为什么本轮已经 auto-compaction 过了,外层还会继续重试一次
这是源码里一个很重要的保守策略。
-
如果这一轮已经 auto-compaction -
但外层仍然看到 overflow 信号 -
外层先 retry 一轮 -
不马上 double-compact
原因是:
内部 compaction 已经可能改过 transcript,当前这个 overflow 信号可能只是本轮尾声的残留结果。这时立刻再压一次,可能是在重复压同一批内容。
所以系统先给“已经发生的压缩”一次重试的机会。
5. overflow 恢复主链
这条链是整个上下文恢复最重要的部分。
第一步:attempt 返回后,外层判断是不是 overflow
外层会看:
-
promptError -
assistant error 文本
只要错误特征是 context overflow,就进入这条链路。
第二步:看本轮有没有 in-attempt compaction
如果本轮已经 auto-compaction 过:
-
先 retry 一轮
如果本轮还没 auto-compaction:
-
再考虑 explicit compaction
第三步:做 explicit compaction
这时会调用:
-
contextEngine.compact(…)
而且压缩前并不是直接拿原始 transcript ,而是会先经过整理。
第四步:如果还是 overflow,就检查 oversized tool results
-
truncateOversizedToolResultsInSession(…)
这不是整体压缩,而是定向裁剪工具调用结果。
第五步:仍不收敛,才给最终错误
这时才会真正返回:
-
context_overflow -
或 compaction_failure
6. “head / tail 截断”是什么意思
truncateToolResultText(…) 的策略可以用最通俗的话总结:
默认只保留开头;如果结尾看起来也很重要,就保留开头和结尾,中间整块省略。
为什么有时要保 tail
会在尾部检查这些迹象:
-
error -
exception -
failed -
traceback -
panic -
exit code -
summary -
result -
finished -
JSON 收尾结构
7. pre-compaction snapshot 为什么重要
这也是恢复链里的关键点。
在 prompt 结束后、等待 compaction retry 前,attempt 会先抓一份当前消息快照。
但它不会盲信这份快照,而是会确认:
-
抓取前没有正在 compaction -
抓取后也没有正在 compaction
只有这样,才把它当作:
-
preCompactionSnapshot
它的作用是:
如果后来 timeout 恰好发生在 compaction 过程中,那当前 session 里的消息可能处在“压到一半”的过渡状态。这时优先退回到 compaction 前抓到的那份稳定快照。
这就是 selectCompactionTimeoutSnapshot(…) 的意义。
8. hidden lifecycle
这里只保留最关键的几个:
bootstrap(…)
旧 session 重新进入运行时,engine 有一次冷启动机会。
afterTurn(…)
这一轮结束后的正式生命周期钩子。
ingestBatch(…) / ingest(…)
如果 engine 不支持 afterTurn,还可以退回到更弱的消息级接入。
prompt-local 图片
图片不是普通历史文本,它们是本轮级输入,受单独检测和治理。
session file repair
上下文工程成立的前提, transcript 至少能被读出来。
9. 最终怎么收敛成“放弃”
真正报错之前,系统通常已经尝试过:
-
等待 auto-compaction 收尾 -
retry 一轮 -
explicit compaction -
tool result truncation
只有这些都没收住,才最终收敛成:
-
context_overflow -
compaction_failure
10. 总结
-
预处理、auto-compaction、explicit compaction、truncation 是四种不同动作。 -
overflow 恢复链不是单次 retry,而是一棵分支恢复树。 -
tool result truncation 是定向补救,不是整体上下文压缩。 -
head / tail 的本质就是“保留开头”和“保留结尾”,中间部分省略。 -
pre-compaction snapshot 是为了防止“压缩压到一半超时”时拿到半成品上下文。
夜雨聆风