Repository navigation
fix(chat): recover committed replies after lost event acknowledgments - #7449
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; effort=max.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机 / Motivation
使用受管会话执行周期任务、等待原会话返回答案的用户。任务和额度结算已完成,结果事件也已写入,但写入确认报错时,旧流程把聊天标成失败;新流程在同一调用读回唯一完整匹配的事件后返回原答案。这会影响一次已经完成的工作,也会让后续周期停在未确认状态,用户无法从原对话拿到结果。
两种存储均能读到一份完整答案,同周期不重复执行,下一独立周期重新计算。不重新执行历史失败回合,不重放模型或结算,不新增调度器,也不交付自然语言创建周期任务。自然语言创建、统一Agent设置完整旅程和真实外部投递仍由原有周期任务工作继续验收。
这是有用且可单独验收的增量:恢复已有任务的原答案返回链路。没有把完整周期任务产品缩减为本次错误处理,也没有把原生任务完成直接当作用户已经收到答复。
改动思路 / Approach
在原结果返回处复用既有存储读回,是解决确认丢失的最小边界;不把提供方I/O恢复移入原生结算或新增恢复状态。本PR只保证同一调用在已提交且唯一完整事件可读时继续返回原答案;其余情况保留失败。
原生驱动继续拥有执行、独立验收和额度结算。Chat 宿主在这些步骤之后追加结果事件,只在该追加抛出 OSError 时检查原生结果和原事件。正常追加成功的路径不增加读取。读回仍使用原 ChatStore.events_after,包括它已有的文件锁、顺序与稳定追加身份处理;这不是纯粹绕开存储的缓存判断,也不保证完全没有提供方I/O重试。
| 追加确认失败后的事实 | 本次行为 |
|---|---|
| 原生已提交,唯一事件与完整结果相等 | 同一调用继续原答案返回 |
| 事件缺失、冲突、重复或不可读 | 保留失败,交给原恢复负责人核对 |
| 原生尚未提交 | 保留失败,不凭事件猜测完成 |
不处理这个错误会继续丢失已经完成的答案;重新调用原生驱动会引入重复业务风险;新增恢复服务则重复现有状态所有者。这里沿用原宿主与存储边界更直接。
具体改动 / Changes
评审准确版本为 acf7df934c9c34449bda38988c1de4132a1e2700。完整差异为 3 个文件、增加 115 行、删除 6 行;其中生产代码增加 13 行、删除 1 行,测试增加 96 行、删除 5 行,说明增加 6 行。
先读取的既有规范是 docs/product/periodic-reports.md,规范版本为 bee2f29b34f8f53a4dcdd31e1e00a5e2bc10a43a,见该不可变版本的周期任务约定。按规范中的实际要求映射:session-task-native-readback 为已验收任务、原生提交和原答案共同确认;session-task-no-model-replay 为同周期重复观察不再次执行;session-task-unknown-retention 为未知、部分或中断结果保留;这三项在本次边界内均有实现和正反例。session-task-parent-journey 对应规范已明确排除的自然语言创建及统一Agent完整旅程,继续留在原任务范围,没有宣称本PR完成。
关键代码讲解
- StewardTurnHost.execute:必须同时满足
ok is True、status == committed、恰好一条native_loopx.result和完整载荷相等。没有调用第二次模型、原生执行或结算;通过后仍沿原来的报告或普通受管答案返回分支。 - 已提交事件确认丢失的完整用例:通过真实 HTTP 配置、原 Chat worker、File/SQLite 原生任务和独立产物校验执行。随后通过 HTTP 读回完整原答案,确认同周期两次观察没有额外模型调用、消息或 Bot 写入;把旧产物改成错误值后,下一独立周期重新计算出 42。
- 拒绝错误结果证据的用例:覆盖冲突、重复、不可读和未提交四类失败。原有未知结果用例改为在 Chat 结果事件写入前失败;原生事务虽已完成,但缺少原事件时仍不能生成完成回执。已经写入的情况成为单独正例,没有把历史失败改称成功。
语义与 CI 对齐
复用已有结果事件、committed 状态、原会话和原生任务,不创建词汇、授权或持久化字段。SessionCalendarTasks._completion 仍要求原任务、原生提交、完成的 Chat Turn 和完整 Core 答案共同读回,Task 的 done 本身仍不够。维护性与语义漂移预检均通过;本 Goal 的现行策略为 wait_for_ci=false,没有查询、轮询或等待远端 CI。
对主干的风险 / Risks
实际生产调用来自 chat_runtime.py 的原生工作分支,既有普通受管任务、monitor 和报告共享这个宿主。主风险是把附近或冲突的事件当成成功,或者把提供方确认失败误当成可以重跑业务。精确原会话/Turn、完整载荷相等和唯一性约束缩小了这个边界;缺失、冲突、重复、不可读及未提交均保留失败,后续观察也没有额外执行。
PASS:相同最终测试文件在不可变主干模块上运行,两种存储的正例都在 failed != completed 断言失败,两个未知结果控制通过;准确新版本的最终 8 项聚焦用例通过。最初增加 HTTP 读回时,测试服务器缺少 runtime_root,已补齐真实运行时字段后完整重跑;保留了该次失败记录和所有原断言,没有通过删除 HTTP 检查消除失败。
PASS:标准预检 6 项直接检查、11 项选中检查,Ruff、编译和公开边界扫描通过,没有人工 hold。335 项相邻原生回归通过。该运行在最后补充 HTTP 与下一周期产物断言前收集,生产逻辑已相同;最终补充由上述 8 项聚焦验证覆盖,测试夹具修正也已单独重跑。
模型输出与确认故障是合成的,HTTP、Chat 存储、原生 Task/Turn、独立验收和 File/SQLite 后端是真实实现。这些证据不证明真实外部消息送达、自然生产断电恢复或模型对新自然语言任务的理解。旧失败回合仍不自动重开;回滚也只撤销这个返回边界,不清除历史。页面、配置结构、权限和正常成功路径没有新增迁移成本。
我的整体评价 / Verdict
结论为 APPROVE,这是一段有实际用户价值的恢复增量。长期执行能力得到改善:原答案确认后,下一独立周期可以继续,重复观察不会重复消耗执行;用户体验得到改善:原会话完整答案可见且不增加确认轮次;原有配置入口和权限保持。仅修改StewardTurnHost结果返回与测试、说明;没有改变页面、样式或呈现层。真实外部消息投递和统一设置页面全旅程不在本次证据范围。
12 行净生产改动沿用已有所有者,不需要新的服务、状态或配置。未知效果的保守停止仍有明确归属,不以减少用户步骤为由移除必要的验证。完整周期任务产品、自然语言和外部投递继续按原有范围验收。本结论属于作者自行审查记录;正式自我批准受 GitHub 限制,合并还需要当前准确版本的原生 readiness 和既有维护授权。
English verdict: APPROVE — acf7df9 restores the original committed answer after a proved lost event acknowledgment, with File/SQLite failure controls, no business replay, and independent next-period execution.
原生受管任务已完成验收和结算后,Chat 的结果事件可能已经写入,却在返回写入确认时抛出 I/O 错误。原流程随即把聊天标为失败,原答案无法返回,后续日历周期也停在未确认状态。
现在由同一调用读回该 Turn 的结果事件:只有原生结果已提交,且存在唯一、完整匹配的事件时,才继续返回原答案。缺失、冲突、重复、不可读和未提交状态仍保留失败;不重新执行模型、结算或历史失败回合。复用原 Chat store、受管 Turn 和日历回执,没有新增调度器、配置或界面。
验证:
本修复覆盖本轮结果事件确认丢失;自然语言创建新周期任务、旧失败回合恢复和真实外部送达保持各自的验收边界。