Repository navigation
fix(trajectory): preserve attempt receipts across rejected reuse and retry preparation - #8368
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, gpt-6.1-sol, OpenAI, runtime_reported, reasoning_effort=max
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
当前 head c0cbebc2dfabe7cccde99aa07ca7a6894128fcfa,目标 base 5090afc2a3516ad678f29d2328d8b3295dc2653d。
动机 / Motivation
研究人员从 CLI 或 Goal 研究面板重新打开同一份轨迹分析时,会复核已保存的输出。base 上若输出损坏,复核失败会把原成功回执改成失败,修复输出后仍不能直接复用。原结果的状态与耗时因此失真,用户还可能为同一次分析再发起 provider 调用。
当前 head 拒绝损坏输出时保留原成功回执,恢复有效输出后能直接复用。这批改动限于既有结果的复核与尝试记录,模型输出资格由原有校验器决定。这个修复也承诺保留显式重试前的上一轮回执。归档后的准备失败仍会使当前回执从正常读写入口消失。
改动思路 / Approach
CLI 与研究面板的 HTTP 调用共用 trajectory_assessment.execute。它持有同一 analysis_key 的锁,从当前回执决定复用、拒绝或显式重试,再调用 provider。把写失败回执的条件收敛到本次 running 回执已经持久化,能保护复用资格检查中的历史状态。重试需要把同一提交边界延伸到归档与当前回执的切换,防止仅有归档时被识别为全新任务。现有 job 目录与锁足以承载这条规则。
具体改动 / Changes
4 个文件共 +134/-1。执行器 +5/-1,文档补充中英行为说明,测试覆盖 CLI、HTTP 及 provider 启动失败。目标分支共同祖先之后的改动未触及这些文件。
关键代码讲解
execute在旧结果资格检查失败时跳过回执重写。原结果恢复后,普通执行复用同一 attempt,provider 启动数维持 1。显式重试会先把receipt.json和产物移至attempt-N,随后才写输入和新的running回执。执行路径read_job从 job 根目录读取receipt.json,HTTP 的inspect_pack在该文件缺失时也没有当前 job。单独存在的attempt-N归档不会挡住普通执行。回执 reader · HTTP 读回
按 base 已接受的规则,validated-reuse 已实现,explicit-retry-preserves-prior 在下述故障路径未满足。规范同时要求保留实际观察到的用量,并以显式 --retry 进入后续尝试。原用量与归档规则 · 原重试与状态规则
对主干的风险 / Risks
[P2] 重试准备失败后,普通执行会绕过 --retry。 从一次已记录用量的失败 attempt 开始,在临时 job 目录把 input.tmp 设为目录,让显式重试在完成归档后写输入失败。真实 CLI 在 base 与 head 都返回写入错误,归档回执字节完好,但 trajectory-assessment-status 已读不到当前回执。清除故障后,普通 --execute 将同一 job 作为 attempt 1 启动合成 provider,重复占用 attempt 编号。此顺序在 base 已存在,且直接影响本 PR 承诺的重试保留边界。归档和输入写入顺序
最小修复是在当前 job 仍可读时完成准备,再切换上一轮归档与新的 running 回执,或在切换失败后恢复可读的上一轮状态。回归测试应在第一次归档写入后注入失败,验证状态可读、原用量和耗时可取、普通执行拒绝、显式重试才进入 attempt 2。运行 uv run --extra test pytest -q tests/capabilities/test_trajectory_assessment.py tests/presentation/test_research_analysis_api.py,并用实际 CLI 读回这条负例。
语义与 CI 对齐
本地两套定向测试分别为 base 42 PASS、head 50 PASS。标准 premerge 的 6 项直接检查和 15 项选中检查无失败,另有 benchmark 人工检查门。归档后失败的 CLI 反例两边均为 FAIL。已有重试准备测试触发的是归档目录冲突,发生在任何回执移动之前。远程 CI 未查询,provider 启动和用量来自合成子进程。
我的整体评价 / Verdict
这是 justified_increment。成功回执的复用修复已有实际读回,用户恢复原输出后可继续使用结果。长程用量与历史连续性仍受重试故障影响。本次改动涉及 CLI 和现有 HTTP 读写结果,diff 无界面布局或样式变化。未观察已安装界面与真实模型。应保留现有 trajectory_assessment_job_v1 状态和显式重试规则,在同一执行器内完成回执切换;复核下一 head 时以归档后失败的读回和调用次数为准。
English verdict: REQUEST_CHANGES. Rejected reuse preserves the successful receipt. Retry preparation can still remove the active receipt and let ordinary execution relaunch without explicit retry. Preserve the prior attempt across that failure.
…pair-20261110 Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>
There was a problem hiding this comment.
Reviewer: model_agent, gpt-6.1-sol, OpenAI, runtime_reported, reasoning effort max
动机 / Motivation
通过 CLI 或研究分析 API 读取、复用和重试轨迹评估的用户。 已有失败或成功评估被复用校验拒绝时,旧代码会把历史回执改成新的失败;重试准备阶段又可能把唯一回执移走。现在历史 attempt 持续可读,准备失败后恢复原状态,只有真正开始的新 attempt 才拥有新的失败。
实际 CLI 的 status 保留原失败及 usage;普通 execute 不会偷偷重跑,明确 retry 后得到 attempt 2,原回执字节和历史附件仍在。
本 PR 不改变评估评分、模型选择或显式 retry 权限,不自动清除崩溃留下的 execution lock。
改动思路 / Approach
继续使用单一 execution.lock、原子 receipt 写入和现有 attempt 编号。把“准备操作失败”与“新 attempt 已开始”分开;归档时复制回执而非移走,其他产物在未开始时可逆归位。最小修复不是吞掉异常,而是让 status 始终能解释最后一次已发生的执行。
本次只修复评估执行的回执与归档生命周期,保留明确重试和原有崩溃锁检查,不增加新的持久化状态机。
入口为 trajectory assessment execute/status 与研究分析 HTTP API;权威输入来自 analysis key、execution.lock、receipt.json 和 attempt 归档,由 既有 trajectory assessment execute 决定,结果交给 CLI status、API analysis readback 与原 usage。attempt_started 与 prior_archive 是单次执行的局部控制信息;权威事实仍为原子写入的 receipt 和独立归档,无新增人工标注。 相同领域的可逆精简已经在本次完成;没有把职责搬成另一套平行规则。
具体改动 / Changes
关键代码讲解
execute:attempt_started 区分历史复用拒绝、准备失败与真实新执行;复制回执并在未开始时恢复归档。 输入来自analysis key、execution.lock、receipt.json 和 attempt 归档,下游为CLI status、API analysis readback 与原 usage;失败由准备失败恢复旧附件;运行后失败属于新 attempt;崩溃锁仍人工核对处理。
read_job:status 只读取当前持久回执,不依靠调用方临时对象推断上次执行是否存在。 输入来自analysis key、execution.lock、receipt.json 和 attempt 归档,下游为CLI status、API analysis readback 与原 usage;失败由准备失败恢复旧附件;运行后失败属于新 attempt;崩溃锁仍人工核对处理。
规格依据:docs/reference/trajectory-assessment.md,固定修复前版本 e7ae4b077c0a2da0791753c9be3f7e4ff0b59b6e。映射标签 attempt-history:失败需要显式 retry,旧 attempt、错误与实际 observed usage 必须保留,status 可读。 —— implemented,验证来自公开测试入口 uv run --extra test python -m pytest -q tests/capabilities/test_trajectory_assessment.py tests/presentation/test_research_analysis_api.py。该标签是本次评审的映射名,不伪称规格原有编号。
差异形态:production: 1 files +22/-2; tests_or_fixtures: 2 files +152/-0; docs: 1 files +12/-0; generated: 0 files +0/-0; mechanical_moves: 0 files +0/-0。不使用 retry 的已有成功复用与失败拒绝路径都保留;未开始不增加 attempt,真实新运行失败仍增加 attempt。
对主干的风险 / Risks
跨多个附件的归档不是一个文件系统事务,突然进程退出可能留下需要核对的 execution lock;本修复保留回执并处理普通异常,不虚称完整 crash recovery。历史 usage 和失败原因不能因为后续入口拒绝而被覆盖。
正向:首次失败保留 observed usage;显式 retry 成功提交 attempt 2,同时可回读 attempt 1。 负向与恢复:input.tmp 是目录导致归档后的准备写入失败;status 仍读到原 attempt,普通 execute 拒绝隐式重试,移除障碍后显式 retry 正常运行。
验证:51 passed;覆盖真实子进程、CLI status/execute/retry 及实际 HTTP API,模型提供者是隔离的确定性夹具。 Ruff/diff hygiene 与 exact-head canary direct/run checks 无失败;manual_hold_count=1。手工 benchmark/authority 判断由本次完整评审执行,不把该 hold 写成自动通过。 历史失败保留为反例,不把新 head 的通过反写成历史通过。GitHub CI 与合并策略是单独的 readiness 判断;本结论不从红绿状态推导代码正确性。
未调用付费模型,未宣称解决 SIGKILL 后的锁恢复或跨附件崩溃原子性。
合并读回:该精确 head 已合入,merge commit 为 ed4dd993cbcd15d7d5f36ebe6ddb69b144192943。合并前 readiness 为 ready=false,GitHub 检查汇总 {"pending": 23, "success": 11};review conclusion 有效、线程完整且 unresolved=0。 依据维护者对本批“自修复自合并”的明确授权使用 admin bypass 处理策略/CI 等待;必需本地验证、已修复发现的核对与精确版本评审单独完成。
我的整体评价 / Verdict
最终评审版本为 058f8ae731c0b8628348910a1e60e17a5bd51604,检查覆盖整个剩余 diff 与当前调用方,不仅是最后一条 comment。实际 CLI 的 status 保留原失败及 usage;普通 execute 不会偷偷重跑,明确 retry 后得到 attempt 2,原回执字节和历史附件仍在。 本次只修复评估执行的回执与归档生命周期,保留明确重试和原有崩溃锁检查,不增加新的持久化状态机。 上述已有缺陷均有入口级反例与恢复读回,未发现仍需阻止本次交付的具体问题。
已检查 loopx/capabilities/benchmark_toolkit/trajectory_assessment.py 及现有消费者,本 PR 不改变渲染布局或内容层级;本次验证集中于入口、状态与恢复。 没有对已安装 App、真实个人账号或付费模型作额外观测。
本 PR 不改变评估评分、模型选择或显式 retry 权限,不自动清除崩溃留下的 execution lock。 合并仍需该版本的远端 head、未解决线程、旧阻塞评审与 readiness 读回;本评审不自行赋予合并或付费执行权限。
English verdict: APPROVE. Head 058f8ae. The bounded fixes preserve existing authority and recovery contracts; the stated untested boundaries are not claimed as verified.
Problem and outcome
通过 CLI 或研究分析 API 读取、复用和重试轨迹评估的用户。 已有失败或成功评估被复用校验拒绝时,旧代码会把历史回执改成新的失败;重试准备阶段又可能把唯一回执移走。现在历史 attempt 持续可读,准备失败后恢复原状态,只有真正开始的新 attempt 才拥有新的失败。
实际 CLI 的 status 保留原失败及 usage;普通 execute 不会偷偷重跑,明确 retry 后得到 attempt 2,原回执字节和历史附件仍在。
Design and scope
继续使用单一 execution.lock、原子 receipt 写入和现有 attempt 编号。把“准备操作失败”与“新 attempt 已开始”分开;归档时复制回执而非移走,其他产物在未开始时可逆归位。最小修复不是吞掉异常,而是让 status 始终能解释最后一次已发生的执行。
本次只修复评估执行的回执与归档生命周期,保留明确重试和原有崩溃锁检查,不增加新的持久化状态机。
本 PR 不改变评估评分、模型选择或显式 retry 权限,不自动清除崩溃留下的 execution lock。
Validation
51 passed;覆盖真实子进程、CLI status/execute/retry 及实际 HTTP API,模型提供者是隔离的确定性夹具。
Reproduction:
uv run --extra test python -m pytest -q tests/capabilities/test_trajectory_assessment.py tests/presentation/test_research_analysis_api.pyRuff/diff hygiene 与 exact-head canary direct/run checks 无失败;manual_hold_count=1。手工 benchmark/authority 判断由本次完整评审执行,不把该 hold 写成自动通过。
未调用付费模型,未宣称解决 SIGKILL 后的锁恢复或跨附件崩溃原子性。
Reviewed head:
058f8ae731c0b8628348910a1e60e17a5bd51604. Historical timing claims or failures are not reused as evidence for an unmeasured final implementation.