我先看一下代码,逐条核对这些结论,而不是凭印象评价。
我把 GPT 提到的每一处都对照代码核了一遍(没有跑探针,只读源码),结论先说:问题清单基本靠谱,9 条里 7 条是真 bug,1 条是文档/契约缺口,1 条是刻意的设计取舍被夸大了;但它的修复计划明显过度工程化,尤其批次 4 和批次 5。
逐条核对
| # | GPT 的说法 | 我的核对 | 我的评级 |
|---|---|---|---|
| handoff 恢复丢失 | P1 | 成立。 save_running 在 handoff 工具结果后立刻落盘(loop.py:1020),此时 RunHead 只有 agent_name=a,没有 pending 字段(checkpoint.py:159)。而且 _apply_handoff 在 activate() 之前就把 pending_handoff 清空了(loop.py:1129),目标 agent 插件 setup 失败时同样丢。现有测试 test_resume_drains_pending_handoff_call 只覆盖"call 无 result"的情况,恰好没覆盖"有 result 但未切换"。 | P1,同意 |
| delete_on_success 删早了 | P1 | 成立,而且比 GPT 说的更值得优先。 complete() 直接 delete(checkpoint.py:90),Session 写入在后。有意思的是 completed-replay 路径(loop.py:288-295)已经是"先 session 后 delete"的正确顺序,注释还写着 "mirroring the normal completion order" —— 但正常路径并不是这个顺序。web supervisor 两处都用了 delete_on_success=True,所以这是 web 实际跑的路径。 | P1,最先修 |
| 父 usage 重复累计 | P2 | 成立。 _finalize_run 加一次(loop.py:1210),checkpoints.complete() 抛错时 run_completed 仍为 False,异常路径再加一次(loop.py:488)。窗口很窄,修法一行。 | P2,顺手修 |
| 初始化失败无 RunFailed | P2 | 成立。 _bootstrap/_resolve_resume 在 try 之外;RunHandle._iter 的 except Exception 注释说 "The loop already emitted RunFailed",对这条路径不成立。result() 语义是对的,只是流契约不完整。 | P3,小修 |
| 摘要 usage 不计入 | P2 | 成立。 lovia/context/ 里没有任何 usage 字样。默认 summarizer 用的是 run 自己的模型,一次 burst 多次 fold 的开销确实完全隐形,budget 也管不到。 | P2,同意 |
| 摘要分块没扣旧摘要 | P2 | 成立但被高估。 cap = usable // 2(stages.py:411),确实没扣 running 和模板。但探针场景是 4K 窗口 + 15K 字符旧摘要——真正的根因是 max_summary_chars=16_000 没有相对窗口设上限,4K 模型下摘要自己就把窗口占满了。128K+ 窗口下这个问题不会出现。另外 GPT 说注释声称"分块保证不 overflow",实际注释明确写了 oversized chunk 的存在,这里它有点断章取义。 | P3,小修(扣 count_text(running) + 模板开销,并给 max_summary_chars 加相对窗口的上限) |
| 最后一轮取消仍 completed | P2 | 是契约缺口,不算 bug。 文档说 "next safe point",最后一轮文本没有 safe point 是事实。但 GPT 建议的"记录 usage 后、标 completed 前检查"会产生一个尴尬状态:interrupted 快照里带着完整的最终回答但没有 output,resume 时会再跑一轮模型。要么在 transcript.extend 之前检查(丢弃这一轮),要么就在文档里写明"落在最后一轮的 cancel 让这一轮跑完"。我倾向后者——用户已经付费的答案没必要扔。 | P4,改文档 |
| 同名 agent 恢复选错 | P2 | 成立但已知。 reachable_agents 的 docstring 明确写了 "first reached wins"。加一个 found[name] is not agent → UserError 是几行的事,可以做。 | P3 |
| 指纹漏检同长度改写 | P2(条件性) | 这是刻意的取舍,不是 bug。 docstring 写明是 "cheap structural digest",还专门解释为什么排除 tool result 长度。同长度替换 user 消息在 append-only 的 Session 里基本不会发生。要改也很简单——对 Input/Assistant/Call 的内容做 hash(sha1 几 MB 也就毫秒级,GPT "别每轮重 hash 大文本"的顾虑是多余的),而且不需要它说的"指纹版本号":现在 mismatch 就只重置 summary、保留 clear/offload,天然兼容。 | P4,可做可不做 |
| TokenCounter 缓存断崖 | 性能 | 成立但影响小。 FIFO 淘汰 + 顺序扫描确实会 thrash。但默认没有 provider 实现 estimate_tokens,走的是 chars/4,thrash 后也就 17ms/轮,对比模型调用可忽略。8192 条 entry 的 transcript 本身也罕见。修法几行(按 entries 长度自动扩容,或 memo 淘汰时跳过本轮已访问的)。 | P4 |
| checkpoint 双重切片 | 性能 | 成立,是指针复制,微秒级。不值得单独 PR。 | 顺手 |
| 文档漂移 | DX | 成立。 architecture.md:196 写 0.75/0.50,实际 0.85/0.60;"按 session_id 缓存"也已过时。context was …% full 用的是压缩后的 tokens,确实误导。 | 应修 |
对修复计划的意见
批次 1、2、3 方向对,但每一批的验收清单都膨胀了。 例如批次 1 列的"不可序列化 output 兼容路径",complete() 现在已经处理了。批次 2 说"不能凭 Transferred to … 自然语言重建"——其实工具名 + handoff 图就足够推导,不过我同意直接在 RunHead 加一个可选的 pending_handoff: str | None 更干净。恢复顺序现有代码已经对:先 drain 原 agent 的 pending 工具,再 _apply_handoff,只需要在 drain 前把 state.pending_handoff 从 head 还原。
批次 4 是过度设计。 "提取请求构造逻辑、摘要投影分块、span、不静默截断、每次 fold 前检查取消预算"——这是把摘要改造成半个 Runner。实际需要的只有三件事:
cap扣掉count_text(running)和模板开销;max_summary_chars加相对窗口的上限。CompactionRequest加一个可选的 usage sink(或直接一个Usage字段),LLMSummarizer._generate消费UsageDelta累加进去,runtime 在compact()返回后并入run_ctx.usage——不碰last_input_tokens。可选:fold 之间检查 cancel token,同时让
except Exception放过RunCancelled。
批次 5 的"指纹版本化"没必要,理由上面说了。
批次 6 有一条和仓库习惯冲突:"不在每个修复 PR 中机械递增版本"——这个仓库每个 PR 都 bump 版本,CI 靠版本变化触发发布,这是既定流程,不该改。
我建议的顺序
delete_on_success 顺序(web 在用,修法:
complete()总是写 completed 快照 → session → 再 delete)+ 父 usage 双计 + RunFailed 补发。三个都是几行的改动,可以一个 PR。handoff pending 持久化(一个字段 + resume 还原 +
_apply_handoff成功后再清空)+ 同名 agent 检查。摘要 usage + cap 修正,按上面缩小后的范围。
文档漂移 +
% full提示语义。缓存/切片/指纹:随手为之,不单独立项。
整体上 GPT 第二轮的自我修正是有价值的(把几个 P2 的边界说清楚了),它对"做得好的部分"的判断我也同意——view/transcript 分离、preflight 串行 + 执行并行、pair 保护这些不该动。要不要我按上面第 1 批开始做?