复核后,上一轮的主要结论成立,但清单不完整,部分表述和修复方案需要收紧。 本轮又确认了四个问题,其中一个涉及结果丢失。下面先修正 review,再给出具体实施计划。

仍未修改代码。本轮新增验证均为本地故障注入和确定性探针;上一轮全量测试基线是 2,315 passed、37 skipped。这不是对框架无遗漏的形式化证明,但现在覆盖了更多“执行成功、持久化失败、恢复运行”之间的交界。

一、对上一轮 review 的修正

意见复核结论与需要补充的边界
handoff 恢复丢失成立,P1。 不能只增加一个目标字段:恢复后还必须维持“同轮剩余工具由原 agent 执行、首个 handoff 获胜”的语义。
摘要请求超窗成立,P2。 5,133 / 4,096 是受限测试 provider 的估算复现,不是真实 tokenizer 测量。应表述为“完整请求没有纳入预算”,不能承诺修复后所有未知 endpoint 都绝不 overflow。
摘要 usage 遗漏成立,P2。 预算只能限制后续调用,无法撤销已经发生的支出;provider 没报告 usage 时,也不能伪造精确费用。
初始化缺少失败事件成立,P2。 适用于已经返回 RunHandle 后发生的失败。Runner.stream() 构造阶段的参数校验仍可以同步抛错。
指纹漏检改写成立,但属于条件性 P2。 正常 append-only 历史不直接触发。工具结果长度被忽略是现有刻意设计,不能简单改为“对所有原始数据哈希”。
token 缓存断崖成立。 约 25 倍是局部微基准;不能当作整体运行提速。固定容量下也不能承诺任意长历史都是 O(new entries)。
checkpoint 双重切片成立,低风险优化。 复制的是列表引用,收益主要在长运行、多次保存;优先级低于正确性修复。

上一轮“加一个轻量上报通道”“改缓存淘汰策略”等建议还不够具体,下面的计划会补充约束与验收标准。

二、本轮补充发现

1. [P1] 删除 checkpoint 早于 Session 持久化,失败后可能丢失结果

[checkpoint.py (line 85)](/Users/neo/repo/lovia/lovia/runtime/checkpoint.py:85) 在 delete_on_success=True 时直接删除 checkpoint;[loop.py (line 432)](/Users/neo/repo/lovia/lovia/runtime/loop.py:432) 随后才写 Session。

故障注入结果:

Session.append 抛出 OSError

delete_on_success=False:
    checkpoint=completed,Session 为空,可通过 replay 补写

delete_on_success=True:
    checkpoint 不存在,Session 为空,无法通过 checkpoint 恢复结果

重新提交相同 run ID 会变成新运行,可能重复调用模型或执行工具。这个选项的文档恰好推荐用于“结果持久化在 Session 等其他位置”的场景,因此应该优先修复。

2. [P2] checkpoint 完成写入失败,子运行 usage 会向父运行累计两次

[loop.py (line 1195)](/Users/neo/repo/lovia/lovia/runtime/loop.py:1195) 的 _finalize_run() 已累计父 usage;随后的 checkpoint completion 失败时,异常路径又累计一次。

实测子运行使用 2 tokens,父 usage 增加 4 tokens

应修成单次运行退出时只结算一次,并明确 completed replay 的既有计费语义,避免顺便改出另一套行为。

3. [P2] handoff 图中不同 agent 同名,恢复可能静默选错对象

[resume.py (line 78)](/Users/neo/repo/lovia/lovia/runtime/resume.py:78) 用 agent 名称同时承担图遍历去重和恢复定位。

两个分支各有一个名为 worker 的不同 agent 时,snapshot 只保存 "worker"。探针中,来自左分支的 snapshot 被解析成右分支 worker。局部工具名冲突检查抓不到这种跨分支重复。

恢复不能靠遍历顺序决定执行哪个 agent。至少在启用 checkpoint 时,需要拒绝这种歧义,并让错误包含 .hint

4. [P2] 最后一轮文本生成期间取消,运行仍然报告成功

[RunHandle.cancel() (line 109)](/Users/neo/repo/lovia/lovia/runtime/result.py:109) 承诺在下一安全点终止,但最终文本轮次没有经过后续取消检查。

实测在收到第一个 TextDelta 时调用 handle.cancel()

最后事件:RunCompleted
result():正常返回完整回答

不要求协作式取消立即打断网络等待,但模型返回后、运行被标记 completed 前,应有一个明确的取消检查点。

三、详细修改计划

建议拆成 六个可独立 review 的修改批次。每批附带对应测试和文档,避免把核心逻辑与性能优化混进一个大 PR。

批次 1:修复完成结果的持久化顺序

范围:runtime/checkpoint.pyruntime/loop.py,以及 checkpoint/session 集成测试。

目标顺序为:

保存 completed checkpoint
→ 幂等追加 Session
→ 按 delete_on_success 删除 checkpoint
→ 发送成功终止事件

具体工作:

验收测试:

批次 2:修复 handoff 的恢复状态

范围:checkpointer.pyruntime/run_state.pyruntime/checkpoint.pyruntime/resume.pyruntime/loop.py

具体工作:

验收测试:

兼容边界:旧 checkpoint 若已经丢失 pending 信息,不能凭“Transferred to …”自然语言结果保证重建。修复也不能提供外部副作用与数据库之间的通用 exactly-once 保证。

批次 3:统一终止、取消和父 usage 结算

范围:runtime/result.pyruntime/loop.py 及生命周期测试。

具体工作:

验收测试:

批次 4:让摘要请求纳入预算、usage 和运行控制

范围:context/policy.pycontext/stages.pycontext/summarizer.py,以及 runtime 的对接处。

这是设计风险最高的一批,应保持接口增量兼容。

具体工作:

验收测试:

批次 5:修复历史指纹,并做两项局部性能优化

范围:context/state.pycontext/tokens.pycontext/compaction.pyruntime/checkpoint.py

指纹部分:

缓存部分:

验收测试与基准:

批次 6:同步 DX、文档和发布说明

四、实施与最终验收

开始实施时遵循仓库工作流:新建 codex/… 分支,先补能失败的回归测试,再修实现;每批开 PR、获取并处理 review 反馈,通过 required checks 后才合并。

本地验证统一通过 .venv/bin/python -m … 执行 pytest、ruff、mypy;文档修改验证构建及链接,Python 3.10–3.12 兼容性由 CI 矩阵覆盖。

实施后的对抗式复核重点放在:

优先落地批次 1–3,再处理摘要链路。 它们直接决定运行能否可靠完成、恢复和报告结果;性能优化可以随后独立交付。