@tangle-network/agent-eval 0.217.1 → 0.219.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -1
- package/dist/adapters/http.d.ts +3 -3
- package/dist/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +1 -0
- package/dist/adapters/http.js.map +1 -1
- package/dist/{agent-profile-DTt8uGP9.d.ts → agent-profile-D01Io2Xw.d.ts} +4 -4
- package/dist/{agent-profile-DTt8uGP9.d.ts.map → agent-profile-D01Io2Xw.d.ts.map} +1 -1
- package/dist/analyst/index.d.ts +13 -13
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +10 -10
- package/dist/analyst/index.js.map +1 -1
- package/dist/{analyze-runs-DyeNcenW.d.ts → analyze-runs-BWAHBDAw.d.ts} +4 -4
- package/dist/{analyze-runs-DyeNcenW.d.ts.map → analyze-runs-BWAHBDAw.d.ts.map} +1 -1
- package/dist/{benchmark-command-xQK8nOI6.js → benchmark-command-BzmMTnvC.js} +664 -565
- package/dist/benchmark-command-BzmMTnvC.js.map +1 -0
- package/dist/benchmarks/index.d.ts +5 -5
- package/dist/benchmarks/index.js +3 -3
- package/dist/benchmarks/index.js.map +1 -1
- package/dist/campaign/index.d.ts +8 -8
- package/dist/campaign/index.js +11 -11
- package/dist/{campaign-CJGkxAQ6.js → campaign-BVPC6ODQ.js} +452 -282
- package/dist/campaign-BVPC6ODQ.js.map +1 -0
- package/dist/{campaign-evidence-CQYw8_0H.js → campaign-evidence-DunzcJGv.js} +2 -2
- package/dist/campaign-evidence-DunzcJGv.js.map +1 -0
- package/dist/{chat-client-ldqxYYTN.js → chat-client-CMQ8_h4J.js} +2 -2
- package/dist/{chat-client-ldqxYYTN.js.map → chat-client-CMQ8_h4J.js.map} +1 -1
- package/dist/{chat-json-call-DavzexSp.js → chat-json-call-BrZKn0L3.js} +2 -2
- package/dist/{chat-json-call-DavzexSp.js.map → chat-json-call-BrZKn0L3.js.map} +1 -1
- package/dist/cli.js +10 -10
- package/dist/{completion-verifier-B_jw6C9p.js → completion-verifier-Cigap8CG.js} +5 -5
- package/dist/{completion-verifier-B_jw6C9p.js.map → completion-verifier-Cigap8CG.js.map} +1 -1
- package/dist/contract/index.d.ts +11 -11
- package/dist/contract/index.js +91 -11
- package/dist/contract/index.js.map +1 -1
- package/dist/{cost-ledger-D0352Z4d.js → cost-ledger-BGZB6yY2.js} +75 -2
- package/dist/cost-ledger-BGZB6yY2.js.map +1 -0
- package/dist/{cost-ledger-Jk62OBfF.d.ts → cost-ledger-C1W3ICmP.d.ts} +26 -2
- package/dist/cost-ledger-C1W3ICmP.d.ts.map +1 -0
- package/dist/{default-registry-ClYjq3QE.js → default-registry-BrKmT01n.js} +310 -278
- package/dist/{default-registry-ClYjq3QE.js.map → default-registry-BrKmT01n.js.map} +1 -1
- package/dist/{default-registry-DXgLmoDI.d.ts → default-registry-Cwg_QN3d.d.ts} +6 -6
- package/dist/{default-registry-DXgLmoDI.d.ts.map → default-registry-Cwg_QN3d.d.ts.map} +1 -1
- package/dist/{define-agent-eval-BIkH_gAR.d.ts → define-agent-eval-C2nFhHPu.d.ts} +8 -8
- package/dist/{define-agent-eval-BIkH_gAR.d.ts.map → define-agent-eval-C2nFhHPu.d.ts.map} +1 -1
- package/dist/{define-agent-eval-BDxNlP3D.js → define-agent-eval-UQY3VbJ2.js} +165 -105
- package/dist/define-agent-eval-UQY3VbJ2.js.map +1 -0
- package/dist/diagnosis/index.d.ts +2 -2
- package/dist/{dspy-rlm-engine-DhdCr1Dc.js → dspy-rlm-engine-Bn_Mt_LW.js} +3 -3
- package/dist/{dspy-rlm-engine-DhdCr1Dc.js.map → dspy-rlm-engine-Bn_Mt_LW.js.map} +1 -1
- package/dist/{engine-BvZ0R-36.d.ts → engine-BpGGw6cd.d.ts} +3 -3
- package/dist/{engine-BvZ0R-36.d.ts.map → engine-BpGGw6cd.d.ts.map} +1 -1
- package/dist/eval-campaign-BUMIxKTE.js +393 -0
- package/dist/eval-campaign-BUMIxKTE.js.map +1 -0
- package/dist/{evaluation-jTjC_QQD.d.ts → evaluation-CBdQt_kr.d.ts} +4 -4
- package/dist/{evaluation-jTjC_QQD.d.ts.map → evaluation-CBdQt_kr.d.ts.map} +1 -1
- package/dist/evaluation.d.ts +1 -1
- package/dist/evaluation.js +1 -1
- package/dist/{exact-types-CflbJ9f3.d.ts → exact-types-BYFmYq44.d.ts} +2 -2
- package/dist/{exact-types-CflbJ9f3.d.ts.map → exact-types-BYFmYq44.d.ts.map} +1 -1
- package/dist/experiment/index.d.ts +24 -1
- package/dist/experiment/index.d.ts.map +1 -1
- package/dist/experiment/index.js +110 -71
- package/dist/experiment/index.js.map +1 -1
- package/dist/{exporters-Cl6IeNAu.js → exporters-DWvM16LE.js} +2 -2
- package/dist/{exporters-Cl6IeNAu.js.map → exporters-DWvM16LE.js.map} +1 -1
- package/dist/{external-optimizer-process-KVFGvYX1.js → external-optimizer-process-wOLfzsCC.js} +2 -2
- package/dist/{external-optimizer-process-KVFGvYX1.js.map → external-optimizer-process-wOLfzsCC.js.map} +1 -1
- package/dist/{external-optimizer-subprocess-XIHBOSKt.js → external-optimizer-subprocess-D670Oxf2.js} +4 -4
- package/dist/{external-optimizer-subprocess-XIHBOSKt.js.map → external-optimizer-subprocess-D670Oxf2.js.map} +1 -1
- package/dist/{feedback-trajectory-BWmhRBGu.d.ts → feedback-trajectory-DhB9WzYg.d.ts} +3 -3
- package/dist/{feedback-trajectory-BWmhRBGu.d.ts.map → feedback-trajectory-DhB9WzYg.d.ts.map} +1 -1
- package/dist/{findings-store-BMVQtEsm.js → findings-store-CvNo712L.js} +6 -6
- package/dist/{findings-store-BMVQtEsm.js.map → findings-store-CvNo712L.js.map} +1 -1
- package/dist/fuzz.d.ts +1 -1
- package/dist/fuzz.js +1 -1
- package/dist/{hf-dataset-D9f7-oX7.js → hf-dataset-CKxIJQba.js} +3 -3
- package/dist/{hf-dataset-D9f7-oX7.js.map → hf-dataset-CKxIJQba.js.map} +1 -1
- package/dist/hosted/index.js +1 -1
- package/dist/{index-CX-GAUpw.d.ts → index-CHxwfW5g.d.ts} +2 -2
- package/dist/{index-CX-GAUpw.d.ts.map → index-CHxwfW5g.d.ts.map} +1 -1
- package/dist/{index-BLGqZ81N.d.ts → index-CVFStrQD.d.ts} +8 -8
- package/dist/{index-BLGqZ81N.d.ts.map → index-CVFStrQD.d.ts.map} +1 -1
- package/dist/{index-9jJ2fKSX.d.ts → index-CalAsXMR.d.ts} +7 -5
- package/dist/index-CalAsXMR.d.ts.map +1 -0
- package/dist/{index-CiRwLv2R.d.ts → index-D7fmC-1K.d.ts} +3 -4
- package/dist/index-D7fmC-1K.d.ts.map +1 -0
- package/dist/{index-BiT6RtNH.d.ts → index-DAXfpVf7.d.ts} +10 -10
- package/dist/{index-BiT6RtNH.d.ts.map → index-DAXfpVf7.d.ts.map} +1 -1
- package/dist/{index-ZuWx52WG.d.ts → index-Q6DvdcwI.d.ts} +67 -24
- package/dist/index-Q6DvdcwI.d.ts.map +1 -0
- package/dist/index.d.ts +26 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +615 -692
- package/dist/index.js.map +1 -1
- package/dist/{integrity-DOd9Q86O.js → integrity-C8oUeB0g.js} +322 -197
- package/dist/integrity-C8oUeB0g.js.map +1 -0
- package/dist/{integrity-_qgZLA5p.d.ts → integrity-CCwvezWS.d.ts} +2 -2
- package/dist/{integrity-_qgZLA5p.d.ts.map → integrity-CCwvezWS.d.ts.map} +1 -1
- package/dist/{journal-CR4QikYp.js → journal-C7om8Gum.js} +48 -38
- package/dist/journal-C7om8Gum.js.map +1 -0
- package/dist/judge-gate-Co5tuh57.js.map +1 -1
- package/dist/{judge-gate-DsL0yFcb.d.ts → judge-gate-Dc_mXQsL.d.ts} +2 -2
- package/dist/judge-gate-Dc_mXQsL.d.ts.map +1 -0
- package/dist/{kind-factory-Gy4fXixd.js → kind-factory-BhxbtUsQ.js} +2 -2
- package/dist/{kind-factory-Gy4fXixd.js.map → kind-factory-BhxbtUsQ.js.map} +1 -1
- package/dist/ledger-core/index.d.ts +1 -1
- package/dist/ledger-core/index.js +2 -2
- package/dist/ledger-core/lock-holder.d.ts +2 -0
- package/dist/ledger-core/lock-holder.js +181 -0
- package/dist/ledger-core/lock-holder.js.map +1 -0
- package/dist/{ledger-core-DrJhbms9.js → ledger-core-CAxUdDk0.js} +2 -2
- package/dist/{ledger-core-DrJhbms9.js.map → ledger-core-CAxUdDk0.js.map} +1 -1
- package/dist/{llm-client-BshCREn6.js → llm-client-DXuOTseU.js} +239 -249
- package/dist/llm-client-DXuOTseU.js.map +1 -0
- package/dist/{llm-judge-C8jMTNNG.js → llm-judge-Cc8vDJh_.js} +648 -459
- package/dist/llm-judge-Cc8vDJh_.js.map +1 -0
- package/dist/lock-holder-DaJbfpci.d.ts +86 -0
- package/dist/lock-holder-DaJbfpci.d.ts.map +1 -0
- package/dist/matrix/index.d.ts +2 -2
- package/dist/{matrix-DHe-dwVJ.d.ts → matrix-Cf-AGxGh.d.ts} +4 -4
- package/dist/{matrix-DHe-dwVJ.d.ts.map → matrix-Cf-AGxGh.d.ts.map} +1 -1
- package/dist/meta-eval/index.d.ts +2 -2
- package/dist/{mint-zszyYlpA.js → mint-CbdwjTPW.js} +2 -2
- package/dist/{mint-zszyYlpA.js.map → mint-CbdwjTPW.js.map} +1 -1
- package/dist/{mint-BRZXuwRi.d.ts → mint-CkzpVPdz.d.ts} +3 -3
- package/dist/{mint-BRZXuwRi.d.ts.map → mint-CkzpVPdz.d.ts.map} +1 -1
- package/dist/multishot/golden/index.d.ts +1 -1
- package/dist/multishot/index.d.ts +2 -2
- package/dist/multishot/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/{pareto-BzsxidFD.js → pareto-BiBnQXAE.js} +306 -212
- package/dist/pareto-BiBnQXAE.js.map +1 -0
- package/dist/pipelines/index.d.ts.map +1 -1
- package/dist/pipelines/index.js.map +1 -1
- package/dist/{prime-protocol-BWG2DQIk.d.ts → prime-protocol-CckHBxGu.d.ts} +3 -3
- package/dist/{prime-protocol-BWG2DQIk.d.ts.map → prime-protocol-CckHBxGu.d.ts.map} +1 -1
- package/dist/{promotion-gate-DaY95yU6.d.ts → promotion-gate-CkVZHagC.d.ts} +3 -3
- package/dist/{promotion-gate-DaY95yU6.d.ts.map → promotion-gate-CkVZHagC.d.ts.map} +1 -1
- package/dist/{proposal-findings-BbWidvUk.js → proposal-findings-E1aSC_cU.js} +2 -2
- package/dist/{proposal-findings-BbWidvUk.js.map → proposal-findings-E1aSC_cU.js.map} +1 -1
- package/dist/{registry-ssNoHzwD.d.ts → registry-B_yG-1jh.d.ts} +5 -5
- package/dist/{registry-ssNoHzwD.d.ts.map → registry-B_yG-1jh.d.ts.map} +1 -1
- package/dist/{render-C-w2EZ6p.js → render-Cs34y0iX.js} +404 -208
- package/dist/render-Cs34y0iX.js.map +1 -0
- package/dist/reporting.d.ts +3 -3
- package/dist/{researcher-0ABVIdGa.d.ts → researcher-H0p44OYC.d.ts} +5 -5
- package/dist/{researcher-0ABVIdGa.d.ts.map → researcher-H0p44OYC.d.ts.map} +1 -1
- package/dist/{reward-DrZ71vnf.d.ts → reward-BeUgVVC_.d.ts} +2 -2
- package/dist/{reward-DrZ71vnf.d.ts.map → reward-BeUgVVC_.d.ts.map} +1 -1
- package/dist/{reward-hacking-E52wq86n.d.ts → reward-hacking-WTxUbtbb.d.ts} +2 -2
- package/dist/{reward-hacking-E52wq86n.d.ts.map → reward-hacking-WTxUbtbb.d.ts.map} +1 -1
- package/dist/rl.d.ts +8 -8
- package/dist/rl.js +4 -4
- package/dist/rollout/index.d.ts +3 -3
- package/dist/rollout/index.js +5 -5
- package/dist/{rollout-D6C50mfA.js → rollout-Dd1ucT_M.js} +5 -5
- package/dist/{rollout-D6C50mfA.js.map → rollout-Dd1ucT_M.js.map} +1 -1
- package/dist/{rubric-predictive-validity-DiwT8PbL.d.ts → rubric-predictive-validity-noY7uE03.d.ts} +2 -2
- package/dist/{rubric-predictive-validity-DiwT8PbL.d.ts.map → rubric-predictive-validity-noY7uE03.d.ts.map} +1 -1
- package/dist/{run-record-CaxbLq6Y.d.ts → run-record-C3awiXPf.d.ts} +2 -2
- package/dist/{run-record-CaxbLq6Y.d.ts.map → run-record-C3awiXPf.d.ts.map} +1 -1
- package/dist/schema-C-ziyJD7.d.ts.map +1 -1
- package/dist/{schema-DxcKTzbi.js → schema-CvhfLjtZ.js} +77 -47
- package/dist/schema-CvhfLjtZ.js.map +1 -0
- package/dist/search/index.d.ts +2 -2
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +2 -2
- package/dist/{search-kernel-DxqjBxA-.d.ts → search-kernel-Dloxd6gs.d.ts} +5 -5
- package/dist/{search-kernel-DxqjBxA-.d.ts.map → search-kernel-Dloxd6gs.d.ts.map} +1 -1
- package/dist/{search-ledger-TUvB5gWH.js → search-ledger-CZRqtFbW.js} +252 -245
- package/dist/search-ledger-CZRqtFbW.js.map +1 -0
- package/dist/{search-ledger-file-UKCO2r8a.js → search-ledger-file-Bd5Tioq6.js} +3 -3
- package/dist/{search-ledger-file-UKCO2r8a.js.map → search-ledger-file-Bd5Tioq6.js.map} +1 -1
- package/dist/{search-shipper-Cw2z3-td.js → search-shipper-Dn3NLsuH.js} +3 -3
- package/dist/{search-shipper-Cw2z3-td.js.map → search-shipper-Dn3NLsuH.js.map} +1 -1
- package/dist/{search-summary-DQIvJsSB.js → search-summary-D4bwYnAE.js} +3 -3
- package/dist/{search-summary-DQIvJsSB.js.map → search-summary-D4bwYnAE.js.map} +1 -1
- package/dist/{server-DoVbqqtJ.js → server-8TS8XCvg.js} +3 -3
- package/dist/{server-DoVbqqtJ.js.map → server-8TS8XCvg.js.map} +1 -1
- package/dist/{single-run-lock-U58yf8N6.js → single-run-lock-Dekd8_Wv.js} +7 -7
- package/dist/single-run-lock-Dekd8_Wv.js.map +1 -0
- package/dist/{skillopt-optimization-method-kyL94-ri.js → skillopt-optimization-method-DLKqdhcR.js} +1384 -1059
- package/dist/skillopt-optimization-method-DLKqdhcR.js.map +1 -0
- package/dist/{statistical-heldout-C9AbgREN.d.ts → statistical-heldout-3z3I-kJN.d.ts} +3 -3
- package/dist/{statistical-heldout-C9AbgREN.d.ts.map → statistical-heldout-3z3I-kJN.d.ts.map} +1 -1
- package/dist/{store-otlp-BteDAbHK.js → store-otlp-dyWE3wAG.js} +2 -2
- package/dist/{store-otlp-BteDAbHK.js.map → store-otlp-dyWE3wAG.js.map} +1 -1
- package/dist/{store-tool-spans-D5l_OfTJ.js → store-tool-spans-DIUgZbzu.js} +3 -3
- package/dist/{store-tool-spans-D5l_OfTJ.js.map → store-tool-spans-DIUgZbzu.js.map} +1 -1
- package/dist/{store-tool-spans-DZS0HWVf.d.ts → store-tool-spans-DPwXHQkH.d.ts} +7 -7
- package/dist/{store-tool-spans-DZS0HWVf.d.ts.map → store-tool-spans-DPwXHQkH.d.ts.map} +1 -1
- package/dist/{summary-report-DM7tlml1.d.ts → summary-report-C8V49Jt3.d.ts} +18 -3
- package/dist/summary-report-C8V49Jt3.d.ts.map +1 -0
- package/dist/supervisor-run/index.d.ts.map +1 -1
- package/dist/supervisor-run/index.js +3 -3
- package/dist/{systemone-Bp_Ac0p1.d.ts → systemone-DVA7t76V.d.ts} +6 -6
- package/dist/{systemone-Bp_Ac0p1.d.ts.map → systemone-DVA7t76V.d.ts.map} +1 -1
- package/dist/{systemone-review-CwE6JGIp.d.ts → systemone-review-CUBMuTLi.d.ts} +2 -2
- package/dist/{systemone-review-CwE6JGIp.d.ts.map → systemone-review-CUBMuTLi.d.ts.map} +1 -1
- package/dist/systemone.d.ts +3 -3
- package/dist/{terminal-record-78g6E4vh.js → terminal-record-D4Rv-j1W.js} +241 -193
- package/dist/{terminal-record-78g6E4vh.js.map → terminal-record-D4Rv-j1W.js.map} +1 -1
- package/dist/{tool-groups-DyNz2JZ_.d.ts → tool-groups-LISbBWCg.d.ts} +3 -3
- package/dist/tool-groups-LISbBWCg.d.ts.map +1 -0
- package/dist/{trace-questions-xxwgsuMi.d.ts → trace-questions-C42fYEE4.d.ts} +4 -4
- package/dist/{trace-questions-xxwgsuMi.d.ts.map → trace-questions-C42fYEE4.d.ts.map} +1 -1
- package/dist/trace-repair/index.d.ts +3 -3
- package/dist/traces.d.ts +6 -6
- package/dist/traces.js +3 -3
- package/dist/trajectory-replay/index.d.ts +4 -1
- package/dist/trajectory-replay/index.d.ts.map +1 -1
- package/dist/trajectory-replay/index.js +854 -823
- package/dist/trajectory-replay/index.js.map +1 -1
- package/dist/{tree-BGRglkKd.js → tree-DSqwJpL9.js} +122 -14
- package/dist/tree-DSqwJpL9.js.map +1 -0
- package/dist/{types-CW8ewATZ.d.ts → types-8yQzNm8u.d.ts} +4 -4
- package/dist/{types-CW8ewATZ.d.ts.map → types-8yQzNm8u.d.ts.map} +1 -1
- package/dist/{types-D8GhJoHk.d.ts → types-CHfn1zg5.d.ts} +70 -72
- package/dist/types-CHfn1zg5.d.ts.map +1 -0
- package/dist/{types-DtMbFw6U.d.ts → types-D8GXK9Z5.d.ts} +3 -3
- package/dist/{types-DtMbFw6U.d.ts.map → types-D8GXK9Z5.d.ts.map} +1 -1
- package/dist/{types-D-mgRzlh.d.ts → types-eMhdacVp.d.ts} +2 -2
- package/dist/{types-D-mgRzlh.d.ts.map → types-eMhdacVp.d.ts.map} +1 -1
- package/dist/wire/index.d.ts +3 -3
- package/dist/wire/index.js +1 -1
- package/docs/campaign-proposers.md +16 -0
- package/docs/concepts.md +31 -0
- package/docs/design/statistics-decisions.md +1 -1
- package/docs/eval-surface-map.md +1 -0
- package/docs/profile-json-identity.md +1 -2
- package/docs/public-api.md +100 -88
- package/package.json +8 -6
- package/dist/benchmark-command-xQK8nOI6.js.map +0 -1
- package/dist/campaign-CJGkxAQ6.js.map +0 -1
- package/dist/campaign-evidence-CQYw8_0H.js.map +0 -1
- package/dist/cost-ledger-D0352Z4d.js.map +0 -1
- package/dist/cost-ledger-Jk62OBfF.d.ts.map +0 -1
- package/dist/define-agent-eval-BDxNlP3D.js.map +0 -1
- package/dist/eval-campaign-BLEfBGbA.js +0 -343
- package/dist/eval-campaign-BLEfBGbA.js.map +0 -1
- package/dist/index-9jJ2fKSX.d.ts.map +0 -1
- package/dist/index-CiRwLv2R.d.ts.map +0 -1
- package/dist/index-ZuWx52WG.d.ts.map +0 -1
- package/dist/integrity-DOd9Q86O.js.map +0 -1
- package/dist/journal-CR4QikYp.js.map +0 -1
- package/dist/judge-gate-DsL0yFcb.d.ts.map +0 -1
- package/dist/llm-client-BshCREn6.js.map +0 -1
- package/dist/llm-judge-C8jMTNNG.js.map +0 -1
- package/dist/pareto-BzsxidFD.js.map +0 -1
- package/dist/render-C-w2EZ6p.js.map +0 -1
- package/dist/schema-DxcKTzbi.js.map +0 -1
- package/dist/search-ledger-TUvB5gWH.js.map +0 -1
- package/dist/single-run-lock-U58yf8N6.js.map +0 -1
- package/dist/skillopt-optimization-method-kyL94-ri.js.map +0 -1
- package/dist/summary-report-DM7tlml1.d.ts.map +0 -1
- package/dist/tool-groups-DyNz2JZ_.d.ts.map +0 -1
- package/dist/tree-BGRglkKd.js.map +0 -1
- package/dist/types-D8GhJoHk.d.ts.map +0 -1
|
@@ -104,229 +104,6 @@ function applyMutation(step, mutation) {
|
|
|
104
104
|
return step;
|
|
105
105
|
}
|
|
106
106
|
//#endregion
|
|
107
|
-
//#region src/trajectory-replay/corpus.ts
|
|
108
|
-
/**
|
|
109
|
-
* Corpus enumeration for batch replay verification.
|
|
110
|
-
*
|
|
111
|
-
* A labeled trajectory corpus is a gold label file (array of trajectory
|
|
112
|
-
* entries with `incorrect_stages[].incorrect_step_ids`) plus a prepared
|
|
113
|
-
* directory:
|
|
114
|
-
* <prepared>/normalized/<traj_id>/steps.json normalized steps
|
|
115
|
-
* <prepared>/normalized/<traj_id>/task.md optional task statement
|
|
116
|
-
* <prepared>/extracted/<traj_id>/swe_raw/** raw mini-SWE trajectory
|
|
117
|
-
*
|
|
118
|
-
* Only trajectories that record their environment are replayable: the raw
|
|
119
|
-
* trajectory must carry `info.docker_config.base_image`. Every excluded case
|
|
120
|
-
* is returned with a machine-readable reason — the batch report surfaces the
|
|
121
|
-
* full exclusion table, never a silently shrunk denominator.
|
|
122
|
-
*/
|
|
123
|
-
function readLabelEntries(labelsPath) {
|
|
124
|
-
const parsed = JSON.parse(readFileSync(labelsPath, "utf8"));
|
|
125
|
-
if (!Array.isArray(parsed)) throw new Error(`${labelsPath} is not a JSON array of label entries`);
|
|
126
|
-
for (const entry of parsed) if (typeof entry.traj_id !== "string") throw new Error(`${labelsPath} has an entry without a string traj_id`);
|
|
127
|
-
return parsed;
|
|
128
|
-
}
|
|
129
|
-
function goldIncorrectSteps(entry) {
|
|
130
|
-
const ids = /* @__PURE__ */ new Set();
|
|
131
|
-
for (const stage of entry.incorrect_stages ?? []) for (const id of stage.incorrect_step_ids ?? []) ids.add(id);
|
|
132
|
-
return [...ids].sort((a, b) => a - b);
|
|
133
|
-
}
|
|
134
|
-
function findRawTrajFiles(sweRawDir) {
|
|
135
|
-
if (!existsSync(sweRawDir)) return [];
|
|
136
|
-
return readdirSync(sweRawDir, {
|
|
137
|
-
recursive: true,
|
|
138
|
-
encoding: "utf8"
|
|
139
|
-
}).filter((relative) => relative.endsWith(".traj.json")).map((relative) => join(sweRawDir, relative)).sort();
|
|
140
|
-
}
|
|
141
|
-
/** First non-empty output line of the trajectory's first bare `pwd` step. */
|
|
142
|
-
function cwdFromPwdObservation(steps) {
|
|
143
|
-
for (const step of steps) {
|
|
144
|
-
if (step.action.trim() !== "pwd") continue;
|
|
145
|
-
const line = parseObservationOutput(step.observation).split("\n").map((l) => l.trim()).find((l) => l.startsWith("/"));
|
|
146
|
-
if (line) return line;
|
|
147
|
-
}
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
function taskStatementOf(preparedDir, trajId, raw) {
|
|
151
|
-
const taskPath = join(preparedDir, "normalized", trajId, "task.md");
|
|
152
|
-
if (existsSync(taskPath)) {
|
|
153
|
-
const text = readFileSync(taskPath, "utf8").trim();
|
|
154
|
-
if (text.length > 0) return text;
|
|
155
|
-
}
|
|
156
|
-
const userMessage = raw.messages?.find((m) => m.role === "user");
|
|
157
|
-
if (typeof userMessage?.content === "string" && userMessage.content.trim().length > 0) return userMessage.content.trim();
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Resolves the replay resources for one trajectory, independent of gold
|
|
162
|
-
* labels — the finding wire uses this with a finding-supplied step instead.
|
|
163
|
-
*/
|
|
164
|
-
function resolveCaseResources(corpus, trajId) {
|
|
165
|
-
const sweRawDir = join(corpus.preparedDir, "extracted", trajId, "swe_raw");
|
|
166
|
-
const rawFiles = findRawTrajFiles(sweRawDir);
|
|
167
|
-
if (rawFiles.length === 0) return {
|
|
168
|
-
resolved: false,
|
|
169
|
-
reason: "no-swe-raw-trajectory",
|
|
170
|
-
detail: sweRawDir
|
|
171
|
-
};
|
|
172
|
-
if (rawFiles.length > 1) return {
|
|
173
|
-
resolved: false,
|
|
174
|
-
reason: "ambiguous-swe-raw-trajectory",
|
|
175
|
-
detail: rawFiles.join(", ")
|
|
176
|
-
};
|
|
177
|
-
let raw;
|
|
178
|
-
try {
|
|
179
|
-
raw = JSON.parse(readFileSync(rawFiles[0], "utf8"));
|
|
180
|
-
} catch (err) {
|
|
181
|
-
return {
|
|
182
|
-
resolved: false,
|
|
183
|
-
reason: "unreadable-raw-trajectory",
|
|
184
|
-
detail: `${rawFiles[0]}: ${err instanceof Error ? err.message : String(err)}`
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
const dockerConfig = raw.info?.docker_config;
|
|
188
|
-
const image = dockerConfig?.base_image;
|
|
189
|
-
if (typeof image !== "string" || image.length === 0) return {
|
|
190
|
-
resolved: false,
|
|
191
|
-
reason: "no-docker-image",
|
|
192
|
-
detail: rawFiles[0]
|
|
193
|
-
};
|
|
194
|
-
const stepsPath = join(corpus.preparedDir, "normalized", trajId, "steps.json");
|
|
195
|
-
if (!existsSync(stepsPath)) return {
|
|
196
|
-
resolved: false,
|
|
197
|
-
reason: "missing-steps-json",
|
|
198
|
-
detail: stepsPath
|
|
199
|
-
};
|
|
200
|
-
const steps = JSON.parse(readFileSync(stepsPath, "utf8"));
|
|
201
|
-
const runConfigCwd = raw.info?.config?.environment?.cwd;
|
|
202
|
-
const dockerCwd = dockerConfig?.cwd;
|
|
203
|
-
let cwd;
|
|
204
|
-
let cwdSource;
|
|
205
|
-
if (typeof runConfigCwd === "string" && runConfigCwd.length > 0) {
|
|
206
|
-
cwd = runConfigCwd;
|
|
207
|
-
cwdSource = "run-config";
|
|
208
|
-
} else if (typeof dockerCwd === "string" && dockerCwd.length > 0) {
|
|
209
|
-
cwd = dockerCwd;
|
|
210
|
-
cwdSource = "docker-config";
|
|
211
|
-
} else {
|
|
212
|
-
const observed = cwdFromPwdObservation(steps);
|
|
213
|
-
if (!observed) return {
|
|
214
|
-
resolved: false,
|
|
215
|
-
reason: "cwd-underivable"
|
|
216
|
-
};
|
|
217
|
-
cwd = observed;
|
|
218
|
-
cwdSource = "pwd-observation";
|
|
219
|
-
}
|
|
220
|
-
const recordedTimeout = raw.info?.config?.environment?.timeout;
|
|
221
|
-
return {
|
|
222
|
-
resolved: true,
|
|
223
|
-
resources: {
|
|
224
|
-
corpus: corpus.name,
|
|
225
|
-
trajId,
|
|
226
|
-
stepsPath,
|
|
227
|
-
steps,
|
|
228
|
-
taskStatement: taskStatementOf(corpus.preparedDir, trajId, raw),
|
|
229
|
-
image,
|
|
230
|
-
cwd,
|
|
231
|
-
cwdSource,
|
|
232
|
-
recordedStepTimeoutMs: typeof recordedTimeout === "number" && recordedTimeout > 0 ? recordedTimeout * 1e3 : null
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Replayable = a raw trajectory with a recorded image AND at least one gold
|
|
238
|
-
* incorrect step that is a real mid-trajectory action. Gold steps whose action
|
|
239
|
-
* is the submit command are skipped when choosing k (counted per case); a case
|
|
240
|
-
* whose golds are ALL submit steps is excluded as gold-only-submit-step.
|
|
241
|
-
* Exclusion reasons are reported in resolution order:
|
|
242
|
-
* raw trajectory → image → gold labels → steps.json → k range → cwd.
|
|
243
|
-
*/
|
|
244
|
-
function enumerateReplayableCases(corpora) {
|
|
245
|
-
const replayable = [];
|
|
246
|
-
const excluded = [];
|
|
247
|
-
let labelEntryCount = 0;
|
|
248
|
-
for (const corpus of corpora) for (const entry of readLabelEntries(corpus.labelsPath)) {
|
|
249
|
-
labelEntryCount += 1;
|
|
250
|
-
const trajId = entry.traj_id;
|
|
251
|
-
const resolution = resolveCaseResources(corpus, trajId);
|
|
252
|
-
if (!resolution.resolved) {
|
|
253
|
-
excluded.push({
|
|
254
|
-
corpus: corpus.name,
|
|
255
|
-
trajId,
|
|
256
|
-
reason: resolution.reason,
|
|
257
|
-
detail: resolution.detail
|
|
258
|
-
});
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
const gold = goldIncorrectSteps(entry);
|
|
262
|
-
if (gold.length === 0) {
|
|
263
|
-
excluded.push({
|
|
264
|
-
corpus: corpus.name,
|
|
265
|
-
trajId,
|
|
266
|
-
reason: "no-gold-incorrect-step"
|
|
267
|
-
});
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
const resources = resolution.resources;
|
|
271
|
-
let submitGoldsSkipped = 0;
|
|
272
|
-
let target = null;
|
|
273
|
-
let missingGoldId = null;
|
|
274
|
-
for (const goldId of gold) {
|
|
275
|
-
const step = resources.steps.find((s) => s.step_id === goldId);
|
|
276
|
-
if (!step) {
|
|
277
|
-
missingGoldId = goldId;
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
if (step.action.trim() === "submit" || isSubmitOnlyAction(step.action)) {
|
|
281
|
-
submitGoldsSkipped += 1;
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
target = step;
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
if (missingGoldId !== null) {
|
|
288
|
-
excluded.push({
|
|
289
|
-
corpus: corpus.name,
|
|
290
|
-
trajId,
|
|
291
|
-
reason: "gold-step-outside-steps",
|
|
292
|
-
detail: `k=${missingGoldId}, steps=${resources.steps.length}`
|
|
293
|
-
});
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
if (!target) {
|
|
297
|
-
excluded.push({
|
|
298
|
-
corpus: corpus.name,
|
|
299
|
-
trajId,
|
|
300
|
-
reason: "gold-only-submit-step",
|
|
301
|
-
detail: `${submitGoldsSkipped} gold step(s), all submit commands`
|
|
302
|
-
});
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
const recordedReturncodeAtK = parseRecordedReturncode(target.observation);
|
|
306
|
-
if (recordedReturncodeAtK === null) {
|
|
307
|
-
excluded.push({
|
|
308
|
-
corpus: corpus.name,
|
|
309
|
-
trajId,
|
|
310
|
-
reason: "no-recorded-returncode-at-k",
|
|
311
|
-
detail: `k=${target.step_id}; arm A cannot confirm execution without a recorded returncode`
|
|
312
|
-
});
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
replayable.push({
|
|
316
|
-
...resources,
|
|
317
|
-
goldIncorrectSteps: gold,
|
|
318
|
-
k: target.step_id,
|
|
319
|
-
submitGoldsSkipped,
|
|
320
|
-
recordedReturncodeAtK
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
return {
|
|
324
|
-
replayable,
|
|
325
|
-
excluded,
|
|
326
|
-
labelEntryCount
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
//#endregion
|
|
330
107
|
//#region src/trajectory-replay/fix.ts
|
|
331
108
|
/**
|
|
332
109
|
* Counterfactual patch synthesis: turn a recorded incorrect step into a
|
|
@@ -604,129 +381,9 @@ async function runFixLoop(caller, input, executor, options) {
|
|
|
604
381
|
};
|
|
605
382
|
}
|
|
606
383
|
//#endregion
|
|
607
|
-
//#region src/trajectory-replay/
|
|
384
|
+
//#region src/trajectory-replay/verify.ts
|
|
608
385
|
/**
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
* A recorded trajectory names the image it ran in, but the image is not always
|
|
612
|
-
* runnable as-is: sandbox platforms pin customer commands to a non-root
|
|
613
|
-
* identity, so a root-owned working tree must be chowned before the replay can
|
|
614
|
-
* write to it. `ImagePreparer` is that step, injectable so a consumer whose
|
|
615
|
-
* images are already replay-ready supplies its own no-op or none at all.
|
|
616
|
-
*/
|
|
617
|
-
const execFileAsync = promisify(execFile);
|
|
618
|
-
function derivedImageTag(image, cwd) {
|
|
619
|
-
return `ctb-replay:${createHash("sha256").update(`${image}\n${cwd}`).digest("hex").slice(0, 12)}-uid1000`;
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Pulls the base image when absent and builds `FROM <base>; RUN chown -R
|
|
623
|
-
* 1000:1000 <cwd>` tagged by content hash, so repeated batches reuse both
|
|
624
|
-
* the pull and the build. cwd `/` skips the chown (never chown -R /) and
|
|
625
|
-
* replays on the base image directly.
|
|
626
|
-
*/
|
|
627
|
-
function dockerImagePreparer(options = {}) {
|
|
628
|
-
const pullTimeoutMs = options.pullTimeoutMs ?? 12e5;
|
|
629
|
-
const buildTimeoutMs = options.buildTimeoutMs ?? 9e5;
|
|
630
|
-
const imageExists = async (tag) => {
|
|
631
|
-
try {
|
|
632
|
-
await execFileAsync("docker", [
|
|
633
|
-
"image",
|
|
634
|
-
"inspect",
|
|
635
|
-
tag
|
|
636
|
-
], { maxBuffer: 8388608 });
|
|
637
|
-
return true;
|
|
638
|
-
} catch {
|
|
639
|
-
return false;
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
const errorTail = (err) => {
|
|
643
|
-
return (err && typeof err === "object" && "stderr" in err && typeof err.stderr === "string" && err.stderr.length > 0 ? err.stderr : err instanceof Error ? err.message : String(err)).trim().split("\n").slice(-3).join(" | ").slice(0, 400);
|
|
644
|
-
};
|
|
645
|
-
return { async ensure(image, cwd) {
|
|
646
|
-
if (cwd === "/") {
|
|
647
|
-
if (await imageExists(image)) return {
|
|
648
|
-
succeeded: true,
|
|
649
|
-
value: {
|
|
650
|
-
derivedImage: image,
|
|
651
|
-
pulled: false,
|
|
652
|
-
built: false
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
try {
|
|
656
|
-
await execFileAsync("docker", ["pull", image], {
|
|
657
|
-
timeout: pullTimeoutMs,
|
|
658
|
-
maxBuffer: 33554432
|
|
659
|
-
});
|
|
660
|
-
} catch (err) {
|
|
661
|
-
return {
|
|
662
|
-
succeeded: false,
|
|
663
|
-
error: `pull ${image}: ${errorTail(err)}`
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
return {
|
|
667
|
-
succeeded: true,
|
|
668
|
-
value: {
|
|
669
|
-
derivedImage: image,
|
|
670
|
-
pulled: true,
|
|
671
|
-
built: false
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
const derived = derivedImageTag(image, cwd);
|
|
676
|
-
if (await imageExists(derived)) return {
|
|
677
|
-
succeeded: true,
|
|
678
|
-
value: {
|
|
679
|
-
derivedImage: derived,
|
|
680
|
-
pulled: false,
|
|
681
|
-
built: false
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
let pulled = false;
|
|
685
|
-
if (!await imageExists(image)) try {
|
|
686
|
-
await execFileAsync("docker", ["pull", image], {
|
|
687
|
-
timeout: pullTimeoutMs,
|
|
688
|
-
maxBuffer: 33554432
|
|
689
|
-
});
|
|
690
|
-
pulled = true;
|
|
691
|
-
} catch (err) {
|
|
692
|
-
return {
|
|
693
|
-
succeeded: false,
|
|
694
|
-
error: `pull ${image}: ${errorTail(err)}`
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
const contextDir = mkdtempSync(join(tmpdir(), "ctb-replay-image-"));
|
|
698
|
-
const quotedCwd = `'${cwd.replaceAll("'", `'\\''`)}'`;
|
|
699
|
-
writeFileSync(join(contextDir, "Dockerfile"), `FROM ${image}\nRUN chown -R 1000:1000 ${quotedCwd}\n`);
|
|
700
|
-
try {
|
|
701
|
-
await execFileAsync("docker", [
|
|
702
|
-
"build",
|
|
703
|
-
"-t",
|
|
704
|
-
derived,
|
|
705
|
-
contextDir
|
|
706
|
-
], {
|
|
707
|
-
timeout: buildTimeoutMs,
|
|
708
|
-
maxBuffer: 33554432
|
|
709
|
-
});
|
|
710
|
-
} catch (err) {
|
|
711
|
-
return {
|
|
712
|
-
succeeded: false,
|
|
713
|
-
error: `build ${derived} from ${image}: ${errorTail(err)}`
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
return {
|
|
717
|
-
succeeded: true,
|
|
718
|
-
value: {
|
|
719
|
-
derivedImage: derived,
|
|
720
|
-
pulled,
|
|
721
|
-
built: true
|
|
722
|
-
}
|
|
723
|
-
};
|
|
724
|
-
} };
|
|
725
|
-
}
|
|
726
|
-
//#endregion
|
|
727
|
-
//#region src/trajectory-replay/verify.ts
|
|
728
|
-
/**
|
|
729
|
-
* Execution-replay verification for recorded shell trajectories.
|
|
386
|
+
* Execution-replay verification for recorded shell trajectories.
|
|
730
387
|
*
|
|
731
388
|
* Turns a cited claim ("step k is the error-critical step") into an executed
|
|
732
389
|
* proof: replay steps 1..k-1 inside the trajectory's own image, then
|
|
@@ -1116,33 +773,8 @@ function renderReport(verdict, target, armA, armB) {
|
|
|
1116
773
|
return lines.join("\n");
|
|
1117
774
|
}
|
|
1118
775
|
//#endregion
|
|
1119
|
-
//#region src/trajectory-replay/batch.ts
|
|
1120
|
-
/**
|
|
1121
|
-
* Batch replay verification across gold-labeled trajectory corpora.
|
|
1122
|
-
*
|
|
1123
|
-
* For every replayable case (a raw trajectory with a recorded image AND at
|
|
1124
|
-
* least one gold incorrect step) the batch:
|
|
1125
|
-
* 1. derives the replay-ready image through the injected `ImagePreparer`,
|
|
1126
|
-
* 2. replays the prefix and runs arm A — the recorded gold step k, and
|
|
1127
|
-
* 3. optionally generates a corrected command with one LLM call and runs
|
|
1128
|
-
* arm B in its own fresh session.
|
|
1129
|
-
*
|
|
1130
|
-
* Headline metrics:
|
|
1131
|
-
* replayability rate — fraction of replayable cases where the prefix
|
|
1132
|
-
* replays within the divergence tolerance AND arm A reproduces a
|
|
1133
|
-
* recorded failure at k; a zero exit without an error signature is not
|
|
1134
|
-
* evidence of failure;
|
|
1135
|
-
* prefix fidelity — executed prefix steps and the share of them that did
|
|
1136
|
-
* not confirm the recording, split by kind. A corpus whose recordings
|
|
1137
|
-
* carry no returncodes shows up here as unknown-expectation steps, never
|
|
1138
|
-
* as a clean replay;
|
|
1139
|
-
* fix-flip rate — fraction of arm-B-executed cases where the failure
|
|
1140
|
-
* vanished (exit 0, signature absent).
|
|
1141
|
-
*
|
|
1142
|
-
* Image pulls and execs run strictly serially: pulls contend on disk and
|
|
1143
|
-
* registry bandwidth, and serial cases keep wall-time attribution per case
|
|
1144
|
-
* honest. Pull failures are report rows, never silent skips.
|
|
1145
|
-
*/
|
|
776
|
+
//#region src/trajectory-replay/batch-fix.ts
|
|
777
|
+
/** Arm B and the counterfactual fix phase of a replay batch. */
|
|
1146
778
|
function seededSample(items, size, seed) {
|
|
1147
779
|
const pool = [...items];
|
|
1148
780
|
const random = mulberry32(seed);
|
|
@@ -1195,471 +827,228 @@ async function executeArmB(replayCase, fixCommand, backend, signature, stepTimeo
|
|
|
1195
827
|
stderr: exec.stderr
|
|
1196
828
|
};
|
|
1197
829
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
830
|
+
/** A fix outcome with nothing measured; callers name what they did measure. */
|
|
831
|
+
function fixResult(measured) {
|
|
832
|
+
return {
|
|
833
|
+
attempted: true,
|
|
834
|
+
sampledOut: false,
|
|
835
|
+
command: null,
|
|
836
|
+
llmError: null,
|
|
837
|
+
usage: null,
|
|
838
|
+
armBExit: null,
|
|
839
|
+
armBPrefixExecuted: null,
|
|
840
|
+
armBPrefixDivergences: null,
|
|
841
|
+
armBPrefixDivergencePct: null,
|
|
842
|
+
failureVanished: null,
|
|
843
|
+
armBError: null,
|
|
844
|
+
attempts: null,
|
|
845
|
+
flippedAtAttempt: null,
|
|
846
|
+
...measured
|
|
847
|
+
};
|
|
1200
848
|
}
|
|
1201
|
-
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
849
|
+
function runArmB(run, fixCase, command, label) {
|
|
850
|
+
const { options } = run;
|
|
851
|
+
return executeArmB(fixCase.replayCase, command, options.backendFactory(fixCase.row.derivedImage), fixCase.signature, fixCase.stepTimeoutMs, options.prefixLimit, (message) => run.onProgress(`${label}: ${message}`));
|
|
852
|
+
}
|
|
853
|
+
/** The fix summary a finished loop reports: the flipping attempt, else the last executed one, else why it stopped. */
|
|
854
|
+
function loopFixResult(result, usage) {
|
|
855
|
+
const summary = (result.flippedAtAttempt !== null ? result.attempts.find((a) => a.attempt === result.flippedAtAttempt) : null) ?? [...result.attempts].reverse().find((a) => a.executed) ?? null;
|
|
856
|
+
const lastRecord = result.attempts.at(-1) ?? null;
|
|
857
|
+
if (summary) return fixResult({
|
|
858
|
+
command: summary.command,
|
|
859
|
+
usage,
|
|
860
|
+
armBExit: summary.exitCode,
|
|
861
|
+
armBPrefixExecuted: summary.prefixExecuted,
|
|
862
|
+
armBPrefixDivergences: summary.prefixDivergences,
|
|
863
|
+
armBPrefixDivergencePct: summary.prefixDivergencePct,
|
|
864
|
+
failureVanished: summary.failureVanished,
|
|
865
|
+
attempts: result.attempts,
|
|
866
|
+
flippedAtAttempt: result.flippedAtAttempt
|
|
867
|
+
});
|
|
868
|
+
if (result.aborted) return fixResult({
|
|
869
|
+
command: lastRecord?.command ?? null,
|
|
870
|
+
usage,
|
|
871
|
+
armBError: lastRecord?.armBError ?? "sandbox error",
|
|
872
|
+
attempts: result.attempts
|
|
873
|
+
});
|
|
874
|
+
return fixResult({
|
|
875
|
+
llmError: lastRecord?.llmError ?? "no attempt produced a runnable fix",
|
|
876
|
+
usage,
|
|
877
|
+
attempts: result.attempts
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
/** Iterative fix: failed arms feed their real output into up to `fixAttempts` prompts. */
|
|
881
|
+
async function fixByLoop(run, fixCase, caller, tally) {
|
|
882
|
+
const { options, onProgress } = run;
|
|
883
|
+
const { row, label, caseOut } = fixCase;
|
|
884
|
+
const fixAttempts = options.fixAttempts ?? 3;
|
|
885
|
+
onProgress(`${label}: fix loop (budget ${fixAttempts} attempts)`);
|
|
886
|
+
const result = await runFixLoop(caller, fixCase.promptInput, async (command, attempt) => {
|
|
887
|
+
onProgress(`${label}: attempt ${attempt} arm B — ${command.split("\n")[0].slice(0, 120)}`);
|
|
888
|
+
const armB = await runArmB(run, fixCase, command, `${label}: attempt ${attempt}`);
|
|
889
|
+
mkdirSync(caseOut, { recursive: true });
|
|
890
|
+
writeFileSync(join(caseOut, `armB-attempt${attempt}-result.json`), `${JSON.stringify({
|
|
891
|
+
command,
|
|
892
|
+
...armB
|
|
893
|
+
}, null, 2)}\n`);
|
|
894
|
+
writeFileSync(join(caseOut, "armB-result.json"), `${JSON.stringify({
|
|
895
|
+
command,
|
|
896
|
+
attempt,
|
|
897
|
+
...armB
|
|
898
|
+
}, null, 2)}\n`);
|
|
899
|
+
return armB;
|
|
900
|
+
}, {
|
|
901
|
+
maxAttempts: fixAttempts,
|
|
902
|
+
onProgress: (message) => onProgress(`${label}: ${message}`)
|
|
903
|
+
});
|
|
904
|
+
tally.calls += result.llmCalls;
|
|
905
|
+
tally.failures += result.llmFailures;
|
|
906
|
+
tally.promptTokens += result.promptTokens;
|
|
907
|
+
tally.completionTokens += result.completionTokens;
|
|
908
|
+
tally.callsWithoutUsage += result.callsWithoutUsage;
|
|
909
|
+
const usage = result.promptTokens + result.completionTokens > 0 ? {
|
|
910
|
+
promptTokens: result.promptTokens,
|
|
911
|
+
completionTokens: result.completionTokens
|
|
912
|
+
} : null;
|
|
913
|
+
onProgress(`${label}: loop done — flipped=${result.flipped}` + (result.flippedAtAttempt !== null ? ` at attempt ${result.flippedAtAttempt}` : "") + ` (${result.llmCalls} calls, ${result.attempts.filter((a) => a.executed).length} arms)`);
|
|
914
|
+
return {
|
|
915
|
+
...row,
|
|
916
|
+
fix: loopFixResult(result, usage)
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
/** One-shot fix: a single generated command, one arm B. */
|
|
920
|
+
async function fixByOneShot(run, fixCase, caller, tally) {
|
|
921
|
+
const { onProgress } = run;
|
|
922
|
+
const { row, label, caseOut } = fixCase;
|
|
923
|
+
onProgress(`${label}: generating corrected command`);
|
|
924
|
+
tally.calls += 1;
|
|
925
|
+
const generated = await generateFixCommand(caller, fixCase.promptInput);
|
|
926
|
+
if (!generated.succeeded) {
|
|
927
|
+
tally.failures += 1;
|
|
928
|
+
onProgress(`${label}: LLM failed — ${generated.error.slice(0, 200)}`);
|
|
929
|
+
return {
|
|
930
|
+
...row,
|
|
931
|
+
fix: fixResult({ llmError: generated.error })
|
|
1231
932
|
};
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
933
|
+
}
|
|
934
|
+
if (generated.value.usage === null || generated.value.usage === void 0) tally.callsWithoutUsage += 1;
|
|
935
|
+
tally.promptTokens += generated.value.usage?.promptTokens ?? 0;
|
|
936
|
+
tally.completionTokens += generated.value.usage?.completionTokens ?? 0;
|
|
937
|
+
onProgress(`${label}: arm B — ${generated.value.command.split("\n")[0].slice(0, 120)}`);
|
|
938
|
+
try {
|
|
939
|
+
const armB = await runArmB(run, fixCase, generated.value.command, label);
|
|
940
|
+
const fix = fixResult({
|
|
941
|
+
command: generated.value.command,
|
|
942
|
+
usage: generated.value.usage,
|
|
943
|
+
armBExit: armB.exitCode,
|
|
944
|
+
armBPrefixExecuted: armB.prefixExecuted,
|
|
945
|
+
armBPrefixDivergences: armB.prefixDivergences,
|
|
946
|
+
armBPrefixDivergencePct: armB.prefixDivergencePct,
|
|
947
|
+
failureVanished: armB.failureVanished,
|
|
948
|
+
flippedAtAttempt: armB.failureVanished ? 1 : null
|
|
949
|
+
});
|
|
950
|
+
mkdirSync(caseOut, { recursive: true });
|
|
951
|
+
writeFileSync(join(caseOut, "armB-result.json"), `${JSON.stringify({
|
|
952
|
+
command: generated.value.command,
|
|
953
|
+
...armB
|
|
954
|
+
}, null, 2)}\n`);
|
|
955
|
+
onProgress(`${label}: armB exit=${armB.exitCode} failureVanished=${armB.failureVanished}`);
|
|
956
|
+
return {
|
|
957
|
+
...row,
|
|
958
|
+
fix
|
|
959
|
+
};
|
|
960
|
+
} catch (err) {
|
|
961
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
962
|
+
onProgress(`${label}: arm B error — ${message.slice(0, 200)}`);
|
|
963
|
+
return {
|
|
964
|
+
...row,
|
|
965
|
+
fix: fixResult({
|
|
966
|
+
command: generated.value.command,
|
|
967
|
+
usage: generated.value.usage,
|
|
968
|
+
armBError: message.slice(0, 500)
|
|
969
|
+
})
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/** Phase 2: counterfactual fixes for the cases where arm A reproduced, capped and seeded-sampled. */
|
|
974
|
+
async function runFixPhase(run, selected, rows, verdicts) {
|
|
975
|
+
const { options, onProgress } = run;
|
|
976
|
+
const caller = options.fixCaller;
|
|
977
|
+
if (!caller) throw new Error(`trajectory-replay: fix=${options.fix} requires a fixCaller`);
|
|
978
|
+
const maxFixCases = options.maxFixCases ?? 30;
|
|
979
|
+
const seed = options.seed ?? 17;
|
|
980
|
+
const eligible = rows.filter((r) => r.status === "ok" && r.replayed);
|
|
981
|
+
const sampled = eligible.length > maxFixCases ? seededSample(eligible, maxFixCases, seed) : new Set(eligible);
|
|
982
|
+
if (eligible.length > maxFixCases) onProgress(`fix phase: ${eligible.length} eligible > cap ${maxFixCases}; seeded sample (seed=${seed})`);
|
|
983
|
+
const tally = {
|
|
984
|
+
calls: 0,
|
|
985
|
+
failures: 0,
|
|
986
|
+
promptTokens: 0,
|
|
987
|
+
completionTokens: 0,
|
|
988
|
+
callsWithoutUsage: 0
|
|
989
|
+
};
|
|
990
|
+
for (const row of eligible) {
|
|
991
|
+
const index = rows.indexOf(row);
|
|
992
|
+
if (!sampled.has(row)) {
|
|
993
|
+
rows[index] = {
|
|
994
|
+
...row,
|
|
995
|
+
fix: fixResult({
|
|
996
|
+
attempted: false,
|
|
997
|
+
sampledOut: true
|
|
998
|
+
})
|
|
1259
999
|
};
|
|
1260
|
-
rows.push(row);
|
|
1261
|
-
appendFileSync(progressPath, `${JSON.stringify(row)}\n`);
|
|
1262
|
-
onProgress(`${label}: image unavailable — ${preparation.error}`);
|
|
1263
1000
|
continue;
|
|
1264
1001
|
}
|
|
1265
|
-
const
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
cwd: replayCase.cwd,
|
|
1275
|
-
out: caseOut,
|
|
1276
|
-
caseId: replayCase.trajId,
|
|
1277
|
-
stepTimeoutMs,
|
|
1278
|
-
prefixLimit: options.prefixLimit,
|
|
1279
|
-
backend: backendFactory(derivedImage),
|
|
1280
|
-
onProgress: (message) => onProgress(`${label}: ${message}`)
|
|
1281
|
-
});
|
|
1282
|
-
verdictByTraj.set(replayCase.trajId, verdict);
|
|
1283
|
-
const returncodeMatch = verdict.recordedReturncode !== null && verdict.armA.exitCode === verdict.recordedReturncode;
|
|
1284
|
-
const row = {
|
|
1285
|
-
...base,
|
|
1286
|
-
derivedImage,
|
|
1287
|
-
signature: verdict.signature,
|
|
1288
|
-
status: "ok",
|
|
1289
|
-
error: null,
|
|
1290
|
-
imagePulled: pulled,
|
|
1291
|
-
imageBuilt: built,
|
|
1292
|
-
prefixExecuted: verdict.prefixExecuted,
|
|
1293
|
-
prefixDivergences: verdict.prefixDivergences.length,
|
|
1294
|
-
prefixDivergencePct: verdict.prefixDivergencePct,
|
|
1295
|
-
prefixConfirmed: verdict.prefixConfirmed,
|
|
1296
|
-
prefixReturncodeMismatches: verdict.prefixReturncodeMismatches,
|
|
1297
|
-
prefixUnknownExpectations: verdict.prefixUnknownExpectations,
|
|
1298
|
-
armAExit: verdict.armA.exitCode,
|
|
1299
|
-
armAReturncodeMatch: returncodeMatch,
|
|
1300
|
-
armASignatureMatch: verdict.armA.failureSignatureMatch,
|
|
1301
|
-
replayed: verdict.valid,
|
|
1302
|
-
fix: null,
|
|
1303
|
-
wallMs: Date.now() - caseStart
|
|
1304
|
-
};
|
|
1305
|
-
rows.push(row);
|
|
1306
|
-
appendFileSync(progressPath, `${JSON.stringify(row)}\n`);
|
|
1307
|
-
onProgress(`${label}: armA exit=${verdict.armA.exitCode} rcMatch=${returncodeMatch} divergences=${verdict.prefixDivergences.length}/${verdict.prefixExecuted} (${verdict.prefixDivergencePct}%, ${verdict.prefixUnknownExpectations} unknown-expectation)`);
|
|
1308
|
-
} catch (err) {
|
|
1309
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1310
|
-
const row = {
|
|
1311
|
-
...base,
|
|
1312
|
-
derivedImage,
|
|
1313
|
-
signature: null,
|
|
1314
|
-
status: "replay-error",
|
|
1315
|
-
error: message.slice(0, 500),
|
|
1316
|
-
imagePulled: pulled,
|
|
1317
|
-
imageBuilt: built,
|
|
1318
|
-
prefixExecuted: null,
|
|
1319
|
-
prefixDivergences: null,
|
|
1320
|
-
prefixDivergencePct: null,
|
|
1321
|
-
prefixConfirmed: null,
|
|
1322
|
-
prefixReturncodeMismatches: null,
|
|
1323
|
-
prefixUnknownExpectations: null,
|
|
1324
|
-
armAExit: null,
|
|
1325
|
-
armAReturncodeMatch: false,
|
|
1326
|
-
armASignatureMatch: false,
|
|
1327
|
-
replayed: false,
|
|
1328
|
-
fix: null,
|
|
1329
|
-
wallMs: Date.now() - caseStart
|
|
1330
|
-
};
|
|
1331
|
-
rows.push(row);
|
|
1332
|
-
appendFileSync(progressPath, `${JSON.stringify(row)}\n`);
|
|
1333
|
-
onProgress(`${label}: replay error — ${message.slice(0, 200)}`);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
let llm = null;
|
|
1337
|
-
if (options.fix !== "none") {
|
|
1338
|
-
const caller = options.fixCaller;
|
|
1339
|
-
if (!caller) throw new Error(`trajectory-replay: fix=${options.fix} requires a fixCaller`);
|
|
1340
|
-
const fixAttempts = options.fixAttempts ?? 3;
|
|
1341
|
-
const maxFixCases = options.maxFixCases ?? 30;
|
|
1342
|
-
const seed = options.seed ?? 17;
|
|
1343
|
-
const eligible = rows.filter((r) => r.status === "ok" && r.replayed);
|
|
1344
|
-
const sampled = eligible.length > maxFixCases ? seededSample(eligible, maxFixCases, seed) : new Set(eligible);
|
|
1345
|
-
if (eligible.length > maxFixCases) onProgress(`fix phase: ${eligible.length} eligible > cap ${maxFixCases}; seeded sample (seed=${seed})`);
|
|
1346
|
-
let calls = 0;
|
|
1347
|
-
let failures = 0;
|
|
1348
|
-
let promptTokens = 0;
|
|
1349
|
-
let completionTokens = 0;
|
|
1350
|
-
let callsWithoutUsage = 0;
|
|
1351
|
-
for (const row of eligible) {
|
|
1352
|
-
const index = rows.indexOf(row);
|
|
1353
|
-
if (!sampled.has(row)) {
|
|
1354
|
-
rows[index] = {
|
|
1355
|
-
...row,
|
|
1356
|
-
fix: {
|
|
1357
|
-
attempted: false,
|
|
1358
|
-
sampledOut: true,
|
|
1359
|
-
command: null,
|
|
1360
|
-
llmError: null,
|
|
1361
|
-
usage: null,
|
|
1362
|
-
armBExit: null,
|
|
1363
|
-
armBPrefixExecuted: null,
|
|
1364
|
-
armBPrefixDivergences: null,
|
|
1365
|
-
armBPrefixDivergencePct: null,
|
|
1366
|
-
failureVanished: null,
|
|
1367
|
-
armBError: null,
|
|
1368
|
-
attempts: null,
|
|
1369
|
-
flippedAtAttempt: null
|
|
1370
|
-
}
|
|
1371
|
-
};
|
|
1372
|
-
continue;
|
|
1373
|
-
}
|
|
1374
|
-
const replayCase = selected.find((c) => c.trajId === row.trajId && c.corpus === row.corpus);
|
|
1375
|
-
const label = `fix ${row.corpus}/${row.trajId}`;
|
|
1376
|
-
const signature = verdictByTraj.get(row.trajId)?.signature ?? null;
|
|
1377
|
-
const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
|
|
1378
|
-
const promptInput = {
|
|
1002
|
+
const replayCase = selected.find((c) => c.trajId === row.trajId && c.corpus === row.corpus);
|
|
1003
|
+
const fixCase = {
|
|
1004
|
+
row,
|
|
1005
|
+
replayCase,
|
|
1006
|
+
label: `fix ${row.corpus}/${row.trajId}`,
|
|
1007
|
+
signature: verdicts.get(row.trajId)?.signature ?? null,
|
|
1008
|
+
stepTimeoutMs: options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4,
|
|
1009
|
+
caseOut: join(options.out, caseOutDirName(row)),
|
|
1010
|
+
promptInput: {
|
|
1379
1011
|
taskStatement: replayCase.taskStatement,
|
|
1380
1012
|
steps: replayCase.steps,
|
|
1381
1013
|
k: replayCase.k
|
|
1382
|
-
};
|
|
1383
|
-
const caseOut = join(options.out, caseOutDirName(row));
|
|
1384
|
-
if (options.fix === "loop") {
|
|
1385
|
-
onProgress(`${label}: fix loop (budget ${fixAttempts} attempts)`);
|
|
1386
|
-
const result = await runFixLoop(caller, promptInput, async (command, attempt) => {
|
|
1387
|
-
onProgress(`${label}: attempt ${attempt} arm B — ${command.split("\n")[0].slice(0, 120)}`);
|
|
1388
|
-
const armB = await executeArmB(replayCase, command, backendFactory(row.derivedImage), signature, stepTimeoutMs, options.prefixLimit, (message) => onProgress(`${label}: attempt ${attempt}: ${message}`));
|
|
1389
|
-
mkdirSync(caseOut, { recursive: true });
|
|
1390
|
-
writeFileSync(join(caseOut, `armB-attempt${attempt}-result.json`), `${JSON.stringify({
|
|
1391
|
-
command,
|
|
1392
|
-
...armB
|
|
1393
|
-
}, null, 2)}\n`);
|
|
1394
|
-
writeFileSync(join(caseOut, "armB-result.json"), `${JSON.stringify({
|
|
1395
|
-
command,
|
|
1396
|
-
attempt,
|
|
1397
|
-
...armB
|
|
1398
|
-
}, null, 2)}\n`);
|
|
1399
|
-
return armB;
|
|
1400
|
-
}, {
|
|
1401
|
-
maxAttempts: fixAttempts,
|
|
1402
|
-
onProgress: (message) => onProgress(`${label}: ${message}`)
|
|
1403
|
-
});
|
|
1404
|
-
calls += result.llmCalls;
|
|
1405
|
-
failures += result.llmFailures;
|
|
1406
|
-
promptTokens += result.promptTokens;
|
|
1407
|
-
completionTokens += result.completionTokens;
|
|
1408
|
-
callsWithoutUsage += result.callsWithoutUsage;
|
|
1409
|
-
const usage = result.promptTokens + result.completionTokens > 0 ? {
|
|
1410
|
-
promptTokens: result.promptTokens,
|
|
1411
|
-
completionTokens: result.completionTokens
|
|
1412
|
-
} : null;
|
|
1413
|
-
const summary = (result.flippedAtAttempt !== null ? result.attempts.find((a) => a.attempt === result.flippedAtAttempt) : null) ?? [...result.attempts].reverse().find((a) => a.executed) ?? null;
|
|
1414
|
-
const lastRecord = result.attempts.at(-1) ?? null;
|
|
1415
|
-
rows[index] = {
|
|
1416
|
-
...row,
|
|
1417
|
-
fix: summary ? {
|
|
1418
|
-
attempted: true,
|
|
1419
|
-
sampledOut: false,
|
|
1420
|
-
command: summary.command,
|
|
1421
|
-
llmError: null,
|
|
1422
|
-
usage,
|
|
1423
|
-
armBExit: summary.exitCode,
|
|
1424
|
-
armBPrefixExecuted: summary.prefixExecuted,
|
|
1425
|
-
armBPrefixDivergences: summary.prefixDivergences,
|
|
1426
|
-
armBPrefixDivergencePct: summary.prefixDivergencePct,
|
|
1427
|
-
failureVanished: summary.failureVanished,
|
|
1428
|
-
armBError: null,
|
|
1429
|
-
attempts: result.attempts,
|
|
1430
|
-
flippedAtAttempt: result.flippedAtAttempt
|
|
1431
|
-
} : result.aborted ? {
|
|
1432
|
-
attempted: true,
|
|
1433
|
-
sampledOut: false,
|
|
1434
|
-
command: lastRecord?.command ?? null,
|
|
1435
|
-
llmError: null,
|
|
1436
|
-
usage,
|
|
1437
|
-
armBExit: null,
|
|
1438
|
-
armBPrefixExecuted: null,
|
|
1439
|
-
armBPrefixDivergences: null,
|
|
1440
|
-
armBPrefixDivergencePct: null,
|
|
1441
|
-
failureVanished: null,
|
|
1442
|
-
armBError: lastRecord?.armBError ?? "sandbox error",
|
|
1443
|
-
attempts: result.attempts,
|
|
1444
|
-
flippedAtAttempt: null
|
|
1445
|
-
} : {
|
|
1446
|
-
attempted: true,
|
|
1447
|
-
sampledOut: false,
|
|
1448
|
-
command: null,
|
|
1449
|
-
llmError: lastRecord?.llmError ?? "no attempt produced a runnable fix",
|
|
1450
|
-
usage,
|
|
1451
|
-
armBExit: null,
|
|
1452
|
-
armBPrefixExecuted: null,
|
|
1453
|
-
armBPrefixDivergences: null,
|
|
1454
|
-
armBPrefixDivergencePct: null,
|
|
1455
|
-
failureVanished: null,
|
|
1456
|
-
armBError: null,
|
|
1457
|
-
attempts: result.attempts,
|
|
1458
|
-
flippedAtAttempt: null
|
|
1459
|
-
}
|
|
1460
|
-
};
|
|
1461
|
-
onProgress(`${label}: loop done — flipped=${result.flipped}` + (result.flippedAtAttempt !== null ? ` at attempt ${result.flippedAtAttempt}` : "") + ` (${result.llmCalls} calls, ${result.attempts.filter((a) => a.executed).length} arms)`);
|
|
1462
|
-
continue;
|
|
1463
|
-
}
|
|
1464
|
-
onProgress(`${label}: generating corrected command`);
|
|
1465
|
-
calls += 1;
|
|
1466
|
-
const generated = await generateFixCommand(caller, promptInput);
|
|
1467
|
-
if (!generated.succeeded) {
|
|
1468
|
-
failures += 1;
|
|
1469
|
-
rows[index] = {
|
|
1470
|
-
...row,
|
|
1471
|
-
fix: {
|
|
1472
|
-
attempted: true,
|
|
1473
|
-
sampledOut: false,
|
|
1474
|
-
command: null,
|
|
1475
|
-
llmError: generated.error,
|
|
1476
|
-
usage: null,
|
|
1477
|
-
armBExit: null,
|
|
1478
|
-
armBPrefixExecuted: null,
|
|
1479
|
-
armBPrefixDivergences: null,
|
|
1480
|
-
armBPrefixDivergencePct: null,
|
|
1481
|
-
failureVanished: null,
|
|
1482
|
-
armBError: null,
|
|
1483
|
-
attempts: null,
|
|
1484
|
-
flippedAtAttempt: null
|
|
1485
|
-
}
|
|
1486
|
-
};
|
|
1487
|
-
onProgress(`${label}: LLM failed — ${generated.error.slice(0, 200)}`);
|
|
1488
|
-
continue;
|
|
1489
|
-
}
|
|
1490
|
-
if (generated.value.usage === null || generated.value.usage === void 0) callsWithoutUsage += 1;
|
|
1491
|
-
promptTokens += generated.value.usage?.promptTokens ?? 0;
|
|
1492
|
-
completionTokens += generated.value.usage?.completionTokens ?? 0;
|
|
1493
|
-
onProgress(`${label}: arm B — ${generated.value.command.split("\n")[0].slice(0, 120)}`);
|
|
1494
|
-
try {
|
|
1495
|
-
const armB = await executeArmB(replayCase, generated.value.command, backendFactory(row.derivedImage), signature, stepTimeoutMs, options.prefixLimit, (message) => onProgress(`${label}: ${message}`));
|
|
1496
|
-
rows[index] = {
|
|
1497
|
-
...row,
|
|
1498
|
-
fix: {
|
|
1499
|
-
attempted: true,
|
|
1500
|
-
sampledOut: false,
|
|
1501
|
-
command: generated.value.command,
|
|
1502
|
-
llmError: null,
|
|
1503
|
-
usage: generated.value.usage,
|
|
1504
|
-
armBExit: armB.exitCode,
|
|
1505
|
-
armBPrefixExecuted: armB.prefixExecuted,
|
|
1506
|
-
armBPrefixDivergences: armB.prefixDivergences,
|
|
1507
|
-
armBPrefixDivergencePct: armB.prefixDivergencePct,
|
|
1508
|
-
failureVanished: armB.failureVanished,
|
|
1509
|
-
armBError: null,
|
|
1510
|
-
attempts: null,
|
|
1511
|
-
flippedAtAttempt: armB.failureVanished ? 1 : null
|
|
1512
|
-
}
|
|
1513
|
-
};
|
|
1514
|
-
mkdirSync(caseOut, { recursive: true });
|
|
1515
|
-
writeFileSync(join(caseOut, "armB-result.json"), `${JSON.stringify({
|
|
1516
|
-
command: generated.value.command,
|
|
1517
|
-
...armB
|
|
1518
|
-
}, null, 2)}\n`);
|
|
1519
|
-
onProgress(`${label}: armB exit=${armB.exitCode} failureVanished=${armB.failureVanished}`);
|
|
1520
|
-
} catch (err) {
|
|
1521
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1522
|
-
rows[index] = {
|
|
1523
|
-
...row,
|
|
1524
|
-
fix: {
|
|
1525
|
-
attempted: true,
|
|
1526
|
-
sampledOut: false,
|
|
1527
|
-
command: generated.value.command,
|
|
1528
|
-
llmError: null,
|
|
1529
|
-
usage: generated.value.usage,
|
|
1530
|
-
armBExit: null,
|
|
1531
|
-
armBPrefixExecuted: null,
|
|
1532
|
-
armBPrefixDivergences: null,
|
|
1533
|
-
armBPrefixDivergencePct: null,
|
|
1534
|
-
failureVanished: null,
|
|
1535
|
-
armBError: message.slice(0, 500),
|
|
1536
|
-
attempts: null,
|
|
1537
|
-
flippedAtAttempt: null
|
|
1538
|
-
}
|
|
1539
|
-
};
|
|
1540
|
-
onProgress(`${label}: arm B error — ${message.slice(0, 200)}`);
|
|
1541
1014
|
}
|
|
1542
|
-
}
|
|
1543
|
-
llm = {
|
|
1544
|
-
model: options.fixModelLabel ?? "unknown",
|
|
1545
|
-
calls,
|
|
1546
|
-
failures,
|
|
1547
|
-
promptTokens,
|
|
1548
|
-
completionTokens,
|
|
1549
|
-
callsWithoutUsage
|
|
1550
1015
|
};
|
|
1016
|
+
rows[index] = options.fix === "loop" ? await fixByLoop(run, fixCase, caller, tally) : await fixByOneShot(run, fixCase, caller, tally);
|
|
1551
1017
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
const executedSteps = sumOver((r) => r.prefixExecuted);
|
|
1556
|
-
const divergentSteps = sumOver((r) => r.prefixDivergences);
|
|
1557
|
-
const prefixFidelity = {
|
|
1558
|
-
executedSteps,
|
|
1559
|
-
divergentSteps,
|
|
1560
|
-
returncodeMismatches: sumOver((r) => r.prefixReturncodeMismatches),
|
|
1561
|
-
unknownExpectations: sumOver((r) => r.prefixUnknownExpectations),
|
|
1562
|
-
divergencePct: executedSteps > 0 ? Number((divergentSteps / executedSteps * 100).toFixed(1)) : null,
|
|
1563
|
-
tolerancePct: 10,
|
|
1564
|
-
casesWithinTolerance: executedRows.filter((r) => r.prefixDivergencePct !== null && r.prefixDivergencePct <= 10).length,
|
|
1565
|
-
casesExecuted: executedRows.length
|
|
1018
|
+
return {
|
|
1019
|
+
model: options.fixModelLabel ?? "unknown",
|
|
1020
|
+
...tally
|
|
1566
1021
|
};
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1022
|
+
}
|
|
1023
|
+
//#endregion
|
|
1024
|
+
//#region src/trajectory-replay/batch-report.ts
|
|
1025
|
+
function pct(value) {
|
|
1026
|
+
return value === null ? "—" : `${(value * 100).toFixed(1)}%`;
|
|
1027
|
+
}
|
|
1028
|
+
/** The title and the headline rates, prefix divergence and fix-flip numbers. */
|
|
1029
|
+
function pushHeadline(report, lines) {
|
|
1030
|
+
const { headline } = report;
|
|
1031
|
+
lines.push("# Replay-verify batch report");
|
|
1032
|
+
lines.push("");
|
|
1033
|
+
lines.push(`Generated ${report.generatedAt}.`);
|
|
1034
|
+
lines.push("");
|
|
1035
|
+
lines.push("## Headline");
|
|
1036
|
+
lines.push("");
|
|
1037
|
+
const fidelity = headline.prefixFidelity;
|
|
1038
|
+
lines.push(`- **Replayability rate: ${pct(headline.replayabilityRate.value)}** (${headline.replayabilityRate.numerator}/${headline.replayabilityRate.denominator} replayable cases where the prefix replayed within ${fidelity.tolerancePct}% divergence AND arm A reproduced an observed failure at the gold step k).`);
|
|
1039
|
+
lines.push(`- Signature-strict rate: ${pct(headline.signatureStrictRate.value)} (${headline.signatureStrictRate.numerator}/${headline.signatureStrictRate.denominator}; additionally requires the recorded error substring in arm A output).`);
|
|
1040
|
+
lines.push(`- **Prefix divergence: ${fidelity.divergencePct === null ? "—" : `${fidelity.divergencePct}%`}** (${fidelity.divergentSteps}/${fidelity.executedSteps} executed prefix steps did not confirm the recording: ${fidelity.returncodeMismatches} returncode mismatches, ${fidelity.unknownExpectations} with no recorded returncode to check). ${fidelity.casesWithinTolerance}/${fidelity.casesExecuted} executed cases are within the ${fidelity.tolerancePct}% tolerance.`);
|
|
1041
|
+
if (headline.fixFlipRate) lines.push(`- **Fix-flip rate: ${pct(headline.fixFlipRate.value)}** (${headline.fixFlipRate.numerator}/${headline.fixFlipRate.denominator} arm-B-executed cases where the generated fix made the failure vanish).`);
|
|
1042
|
+
if (headline.fixFlipRateNonzeroRc) lines.push(`- Fix-flip rate on recorded-rc≠0 cases: ${pct(headline.fixFlipRateNonzeroRc.value)} (${headline.fixFlipRateNonzeroRc.numerator}/${headline.fixFlipRateNonzeroRc.denominator}; real recorded failures — a gold step recorded with rc 0 flips vacuously).`);
|
|
1043
|
+
if (headline.fixFlipAttempt1) lines.push(`- Fix-flip@1: ${pct(headline.fixFlipAttempt1.value)} (${headline.fixFlipAttempt1.numerator}/${headline.fixFlipAttempt1.denominator} cases whose attempt 1 executed — the one-shot-comparable number).`);
|
|
1044
|
+
if (headline.flipsByAttempt && Object.keys(headline.flipsByAttempt).length > 0) {
|
|
1045
|
+
const parts = Object.entries(headline.flipsByAttempt).sort((a, b) => Number(a[0]) - Number(b[0])).map(([attempt, count]) => `attempt ${attempt}: ${count}`);
|
|
1046
|
+
lines.push(`- Flips by attempt: ${parts.join(", ")}.`);
|
|
1578
1047
|
}
|
|
1579
|
-
const excludedByReason = {};
|
|
1580
|
-
for (const excluded of enumeration.excluded) excludedByReason[excluded.reason] = (excludedByReason[excluded.reason] ?? 0) + 1;
|
|
1581
|
-
const submitGoldsByCorpus = {};
|
|
1582
|
-
const submitEntry = (corpus) => submitGoldsByCorpus[corpus] ??= {
|
|
1583
|
-
submitOnlyCases: 0,
|
|
1584
|
-
goldsSkippedWithinReplayable: 0
|
|
1585
|
-
};
|
|
1586
|
-
for (const excluded of enumeration.excluded) if (excluded.reason === "gold-only-submit-step") submitEntry(excluded.corpus).submitOnlyCases += 1;
|
|
1587
|
-
for (const replayCase of enumeration.replayable) if (replayCase.submitGoldsSkipped > 0) submitEntry(replayCase.corpus).goldsSkippedWithinReplayable += replayCase.submitGoldsSkipped;
|
|
1588
|
-
const report = {
|
|
1589
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1590
|
-
corpora: options.corpora.map((c) => ({
|
|
1591
|
-
name: c.name,
|
|
1592
|
-
labelsPath: c.labelsPath,
|
|
1593
|
-
preparedDir: c.preparedDir
|
|
1594
|
-
})),
|
|
1595
|
-
totals: {
|
|
1596
|
-
labelEntries: enumeration.labelEntryCount,
|
|
1597
|
-
replayable: enumeration.replayable.length,
|
|
1598
|
-
executed: selected.length,
|
|
1599
|
-
excludedByReason,
|
|
1600
|
-
submitGoldsByCorpus
|
|
1601
|
-
},
|
|
1602
|
-
headline: {
|
|
1603
|
-
replayabilityRate: {
|
|
1604
|
-
numerator: replayed.length,
|
|
1605
|
-
denominator: selected.length,
|
|
1606
|
-
value: rate(replayed.length, selected.length)
|
|
1607
|
-
},
|
|
1608
|
-
signatureStrictRate: {
|
|
1609
|
-
numerator: signatureStrict.length,
|
|
1610
|
-
denominator: selected.length,
|
|
1611
|
-
value: rate(signatureStrict.length, selected.length)
|
|
1612
|
-
},
|
|
1613
|
-
prefixFidelity,
|
|
1614
|
-
fixFlipRate: options.fix !== "none" ? {
|
|
1615
|
-
numerator: flipped.length,
|
|
1616
|
-
denominator: armBExecuted.length,
|
|
1617
|
-
value: rate(flipped.length, armBExecuted.length)
|
|
1618
|
-
} : null,
|
|
1619
|
-
fixFlipRateNonzeroRc: options.fix !== "none" ? {
|
|
1620
|
-
numerator: flippedNonzeroRc.length,
|
|
1621
|
-
denominator: armBNonzeroRc.length,
|
|
1622
|
-
value: rate(flippedNonzeroRc.length, armBNonzeroRc.length)
|
|
1623
|
-
} : null,
|
|
1624
|
-
fixFlipAttempt1: options.fix === "loop" ? {
|
|
1625
|
-
numerator: flippedAt1.length,
|
|
1626
|
-
denominator: attempt1Executed.length,
|
|
1627
|
-
value: rate(flippedAt1.length, attempt1Executed.length)
|
|
1628
|
-
} : null,
|
|
1629
|
-
flipsByAttempt: options.fix === "loop" ? flipsByAttempt : null
|
|
1630
|
-
},
|
|
1631
|
-
llm,
|
|
1632
|
-
excluded: enumeration.excluded,
|
|
1633
|
-
pullFailures,
|
|
1634
|
-
cases: rows
|
|
1635
|
-
};
|
|
1636
|
-
writeFileSync(join(options.out, "batch-report.json"), `${JSON.stringify(report, null, 2)}\n`);
|
|
1637
|
-
writeFileSync(join(options.out, "batch-report.md"), renderBatchReport(report));
|
|
1638
|
-
return report;
|
|
1639
|
-
}
|
|
1640
|
-
function pct(value) {
|
|
1641
|
-
return value === null ? "—" : `${(value * 100).toFixed(1)}%`;
|
|
1642
1048
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
const {
|
|
1646
|
-
lines.push("# Replay-verify batch report");
|
|
1647
|
-
lines.push("");
|
|
1648
|
-
lines.push(`Generated ${report.generatedAt}.`);
|
|
1649
|
-
lines.push("");
|
|
1650
|
-
lines.push("## Headline");
|
|
1651
|
-
lines.push("");
|
|
1652
|
-
const fidelity = headline.prefixFidelity;
|
|
1653
|
-
lines.push(`- **Replayability rate: ${pct(headline.replayabilityRate.value)}** (${headline.replayabilityRate.numerator}/${headline.replayabilityRate.denominator} replayable cases where the prefix replayed within ${fidelity.tolerancePct}% divergence AND arm A reproduced an observed failure at the gold step k).`);
|
|
1654
|
-
lines.push(`- Signature-strict rate: ${pct(headline.signatureStrictRate.value)} (${headline.signatureStrictRate.numerator}/${headline.signatureStrictRate.denominator}; additionally requires the recorded error substring in arm A output).`);
|
|
1655
|
-
lines.push(`- **Prefix divergence: ${fidelity.divergencePct === null ? "—" : `${fidelity.divergencePct}%`}** (${fidelity.divergentSteps}/${fidelity.executedSteps} executed prefix steps did not confirm the recording: ${fidelity.returncodeMismatches} returncode mismatches, ${fidelity.unknownExpectations} with no recorded returncode to check). ${fidelity.casesWithinTolerance}/${fidelity.casesExecuted} executed cases are within the ${fidelity.tolerancePct}% tolerance.`);
|
|
1656
|
-
if (headline.fixFlipRate) lines.push(`- **Fix-flip rate: ${pct(headline.fixFlipRate.value)}** (${headline.fixFlipRate.numerator}/${headline.fixFlipRate.denominator} arm-B-executed cases where the generated fix made the failure vanish).`);
|
|
1657
|
-
if (headline.fixFlipRateNonzeroRc) lines.push(`- Fix-flip rate on recorded-rc≠0 cases: ${pct(headline.fixFlipRateNonzeroRc.value)} (${headline.fixFlipRateNonzeroRc.numerator}/${headline.fixFlipRateNonzeroRc.denominator}; real recorded failures — a gold step recorded with rc 0 flips vacuously).`);
|
|
1658
|
-
if (headline.fixFlipAttempt1) lines.push(`- Fix-flip@1: ${pct(headline.fixFlipAttempt1.value)} (${headline.fixFlipAttempt1.numerator}/${headline.fixFlipAttempt1.denominator} cases whose attempt 1 executed — the one-shot-comparable number).`);
|
|
1659
|
-
if (headline.flipsByAttempt && Object.keys(headline.flipsByAttempt).length > 0) {
|
|
1660
|
-
const parts = Object.entries(headline.flipsByAttempt).sort((a, b) => Number(a[0]) - Number(b[0])).map(([attempt, count]) => `attempt ${attempt}: ${count}`);
|
|
1661
|
-
lines.push(`- Flips by attempt: ${parts.join(", ")}.`);
|
|
1662
|
-
}
|
|
1049
|
+
/** How many label entries were replayable, executed and excluded, and why. */
|
|
1050
|
+
function pushEnumeration(report, lines) {
|
|
1051
|
+
const { totals } = report;
|
|
1663
1052
|
lines.push("");
|
|
1664
1053
|
lines.push("## Enumeration");
|
|
1665
1054
|
lines.push("");
|
|
@@ -1677,6 +1066,9 @@ function renderBatchReport(report) {
|
|
|
1677
1066
|
lines.push("| --- | --- | --- |");
|
|
1678
1067
|
for (const [corpus, stats] of submitEntries.sort((a, b) => a[0].localeCompare(b[0]))) lines.push(`| ${corpus} | ${stats.submitOnlyCases} | ${stats.goldsSkippedWithinReplayable} |`);
|
|
1679
1068
|
}
|
|
1069
|
+
}
|
|
1070
|
+
/** The images that could not be pulled or built. */
|
|
1071
|
+
function pushPullFailures(report, lines) {
|
|
1680
1072
|
if (report.pullFailures.length > 0) {
|
|
1681
1073
|
lines.push("");
|
|
1682
1074
|
lines.push("## Image pull/build failures");
|
|
@@ -1685,6 +1077,9 @@ function renderBatchReport(report) {
|
|
|
1685
1077
|
lines.push("| --- | --- | --- | --- |");
|
|
1686
1078
|
for (const failure of report.pullFailures) lines.push(`| ${failure.corpus} | ${failure.trajId} | \`${failure.image}\` | ${failure.error.replaceAll("|", "\\|")} |`);
|
|
1687
1079
|
}
|
|
1080
|
+
}
|
|
1081
|
+
/** One row per replayed case. */
|
|
1082
|
+
function pushCaseResults(report, lines) {
|
|
1688
1083
|
lines.push("");
|
|
1689
1084
|
lines.push("## Per-case results");
|
|
1690
1085
|
lines.push("");
|
|
@@ -1715,16 +1110,652 @@ function renderBatchReport(report) {
|
|
|
1715
1110
|
(row.wallMs / 1e3).toFixed(1)
|
|
1716
1111
|
].join(" | "));
|
|
1717
1112
|
}
|
|
1113
|
+
}
|
|
1114
|
+
/** The model, calls and tokens spent generating fixes. */
|
|
1115
|
+
function pushLlmUsage(report, lines) {
|
|
1718
1116
|
if (report.llm) {
|
|
1719
1117
|
lines.push("");
|
|
1720
1118
|
lines.push("## LLM fix generation");
|
|
1721
1119
|
lines.push("");
|
|
1722
1120
|
lines.push(`Model ${report.llm.model}: ${report.llm.calls} calls (${report.llm.failures} failed), ${report.llm.promptTokens} prompt + ${report.llm.completionTokens} completion tokens.`);
|
|
1723
1121
|
}
|
|
1122
|
+
}
|
|
1123
|
+
function renderBatchReport(report) {
|
|
1124
|
+
const lines = [];
|
|
1125
|
+
pushHeadline(report, lines);
|
|
1126
|
+
pushEnumeration(report, lines);
|
|
1127
|
+
pushPullFailures(report, lines);
|
|
1128
|
+
pushCaseResults(report, lines);
|
|
1129
|
+
pushLlmUsage(report, lines);
|
|
1724
1130
|
lines.push("");
|
|
1725
1131
|
return lines.join("\n");
|
|
1726
1132
|
}
|
|
1727
1133
|
//#endregion
|
|
1134
|
+
//#region src/trajectory-replay/corpus.ts
|
|
1135
|
+
/**
|
|
1136
|
+
* Corpus enumeration for batch replay verification.
|
|
1137
|
+
*
|
|
1138
|
+
* A labeled trajectory corpus is a gold label file (array of trajectory
|
|
1139
|
+
* entries with `incorrect_stages[].incorrect_step_ids`) plus a prepared
|
|
1140
|
+
* directory:
|
|
1141
|
+
* <prepared>/normalized/<traj_id>/steps.json normalized steps
|
|
1142
|
+
* <prepared>/normalized/<traj_id>/task.md optional task statement
|
|
1143
|
+
* <prepared>/extracted/<traj_id>/swe_raw/** raw mini-SWE trajectory
|
|
1144
|
+
*
|
|
1145
|
+
* Only trajectories that record their environment are replayable: the raw
|
|
1146
|
+
* trajectory must carry `info.docker_config.base_image`. Every excluded case
|
|
1147
|
+
* is returned with a machine-readable reason — the batch report surfaces the
|
|
1148
|
+
* full exclusion table, never a silently shrunk denominator.
|
|
1149
|
+
*/
|
|
1150
|
+
function readLabelEntries(labelsPath) {
|
|
1151
|
+
const parsed = JSON.parse(readFileSync(labelsPath, "utf8"));
|
|
1152
|
+
if (!Array.isArray(parsed)) throw new Error(`${labelsPath} is not a JSON array of label entries`);
|
|
1153
|
+
for (const entry of parsed) if (typeof entry.traj_id !== "string") throw new Error(`${labelsPath} has an entry without a string traj_id`);
|
|
1154
|
+
return parsed;
|
|
1155
|
+
}
|
|
1156
|
+
function goldIncorrectSteps(entry) {
|
|
1157
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1158
|
+
for (const stage of entry.incorrect_stages ?? []) for (const id of stage.incorrect_step_ids ?? []) ids.add(id);
|
|
1159
|
+
return [...ids].sort((a, b) => a - b);
|
|
1160
|
+
}
|
|
1161
|
+
function findRawTrajFiles(sweRawDir) {
|
|
1162
|
+
if (!existsSync(sweRawDir)) return [];
|
|
1163
|
+
return readdirSync(sweRawDir, {
|
|
1164
|
+
recursive: true,
|
|
1165
|
+
encoding: "utf8"
|
|
1166
|
+
}).filter((relative) => relative.endsWith(".traj.json")).map((relative) => join(sweRawDir, relative)).sort();
|
|
1167
|
+
}
|
|
1168
|
+
/** First non-empty output line of the trajectory's first bare `pwd` step. */
|
|
1169
|
+
function cwdFromPwdObservation(steps) {
|
|
1170
|
+
for (const step of steps) {
|
|
1171
|
+
if (step.action.trim() !== "pwd") continue;
|
|
1172
|
+
const line = parseObservationOutput(step.observation).split("\n").map((l) => l.trim()).find((l) => l.startsWith("/"));
|
|
1173
|
+
if (line) return line;
|
|
1174
|
+
}
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
function taskStatementOf(preparedDir, trajId, raw) {
|
|
1178
|
+
const taskPath = join(preparedDir, "normalized", trajId, "task.md");
|
|
1179
|
+
if (existsSync(taskPath)) {
|
|
1180
|
+
const text = readFileSync(taskPath, "utf8").trim();
|
|
1181
|
+
if (text.length > 0) return text;
|
|
1182
|
+
}
|
|
1183
|
+
const userMessage = raw.messages?.find((m) => m.role === "user");
|
|
1184
|
+
if (typeof userMessage?.content === "string" && userMessage.content.trim().length > 0) return userMessage.content.trim();
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Resolves the replay resources for one trajectory, independent of gold
|
|
1189
|
+
* labels — the finding wire uses this with a finding-supplied step instead.
|
|
1190
|
+
*/
|
|
1191
|
+
function resolveCaseResources(corpus, trajId) {
|
|
1192
|
+
const sweRawDir = join(corpus.preparedDir, "extracted", trajId, "swe_raw");
|
|
1193
|
+
const rawFiles = findRawTrajFiles(sweRawDir);
|
|
1194
|
+
if (rawFiles.length === 0) return {
|
|
1195
|
+
resolved: false,
|
|
1196
|
+
reason: "no-swe-raw-trajectory",
|
|
1197
|
+
detail: sweRawDir
|
|
1198
|
+
};
|
|
1199
|
+
if (rawFiles.length > 1) return {
|
|
1200
|
+
resolved: false,
|
|
1201
|
+
reason: "ambiguous-swe-raw-trajectory",
|
|
1202
|
+
detail: rawFiles.join(", ")
|
|
1203
|
+
};
|
|
1204
|
+
let raw;
|
|
1205
|
+
try {
|
|
1206
|
+
raw = JSON.parse(readFileSync(rawFiles[0], "utf8"));
|
|
1207
|
+
} catch (err) {
|
|
1208
|
+
return {
|
|
1209
|
+
resolved: false,
|
|
1210
|
+
reason: "unreadable-raw-trajectory",
|
|
1211
|
+
detail: `${rawFiles[0]}: ${err instanceof Error ? err.message : String(err)}`
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
const dockerConfig = raw.info?.docker_config;
|
|
1215
|
+
const image = dockerConfig?.base_image;
|
|
1216
|
+
if (typeof image !== "string" || image.length === 0) return {
|
|
1217
|
+
resolved: false,
|
|
1218
|
+
reason: "no-docker-image",
|
|
1219
|
+
detail: rawFiles[0]
|
|
1220
|
+
};
|
|
1221
|
+
const stepsPath = join(corpus.preparedDir, "normalized", trajId, "steps.json");
|
|
1222
|
+
if (!existsSync(stepsPath)) return {
|
|
1223
|
+
resolved: false,
|
|
1224
|
+
reason: "missing-steps-json",
|
|
1225
|
+
detail: stepsPath
|
|
1226
|
+
};
|
|
1227
|
+
const steps = JSON.parse(readFileSync(stepsPath, "utf8"));
|
|
1228
|
+
const runConfigCwd = raw.info?.config?.environment?.cwd;
|
|
1229
|
+
const dockerCwd = dockerConfig?.cwd;
|
|
1230
|
+
let cwd;
|
|
1231
|
+
let cwdSource;
|
|
1232
|
+
if (typeof runConfigCwd === "string" && runConfigCwd.length > 0) {
|
|
1233
|
+
cwd = runConfigCwd;
|
|
1234
|
+
cwdSource = "run-config";
|
|
1235
|
+
} else if (typeof dockerCwd === "string" && dockerCwd.length > 0) {
|
|
1236
|
+
cwd = dockerCwd;
|
|
1237
|
+
cwdSource = "docker-config";
|
|
1238
|
+
} else {
|
|
1239
|
+
const observed = cwdFromPwdObservation(steps);
|
|
1240
|
+
if (!observed) return {
|
|
1241
|
+
resolved: false,
|
|
1242
|
+
reason: "cwd-underivable"
|
|
1243
|
+
};
|
|
1244
|
+
cwd = observed;
|
|
1245
|
+
cwdSource = "pwd-observation";
|
|
1246
|
+
}
|
|
1247
|
+
const recordedTimeout = raw.info?.config?.environment?.timeout;
|
|
1248
|
+
return {
|
|
1249
|
+
resolved: true,
|
|
1250
|
+
resources: {
|
|
1251
|
+
corpus: corpus.name,
|
|
1252
|
+
trajId,
|
|
1253
|
+
stepsPath,
|
|
1254
|
+
steps,
|
|
1255
|
+
taskStatement: taskStatementOf(corpus.preparedDir, trajId, raw),
|
|
1256
|
+
image,
|
|
1257
|
+
cwd,
|
|
1258
|
+
cwdSource,
|
|
1259
|
+
recordedStepTimeoutMs: typeof recordedTimeout === "number" && recordedTimeout > 0 ? recordedTimeout * 1e3 : null
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Replayable = a raw trajectory with a recorded image AND at least one gold
|
|
1265
|
+
* incorrect step that is a real mid-trajectory action. Gold steps whose action
|
|
1266
|
+
* is the submit command are skipped when choosing k (counted per case); a case
|
|
1267
|
+
* whose golds are ALL submit steps is excluded as gold-only-submit-step.
|
|
1268
|
+
* Exclusion reasons are reported in resolution order:
|
|
1269
|
+
* raw trajectory → image → gold labels → steps.json → k range → cwd.
|
|
1270
|
+
*/
|
|
1271
|
+
function enumerateReplayableCases(corpora) {
|
|
1272
|
+
const replayable = [];
|
|
1273
|
+
const excluded = [];
|
|
1274
|
+
let labelEntryCount = 0;
|
|
1275
|
+
for (const corpus of corpora) for (const entry of readLabelEntries(corpus.labelsPath)) {
|
|
1276
|
+
labelEntryCount += 1;
|
|
1277
|
+
const trajId = entry.traj_id;
|
|
1278
|
+
const resolution = resolveCaseResources(corpus, trajId);
|
|
1279
|
+
if (!resolution.resolved) {
|
|
1280
|
+
excluded.push({
|
|
1281
|
+
corpus: corpus.name,
|
|
1282
|
+
trajId,
|
|
1283
|
+
reason: resolution.reason,
|
|
1284
|
+
detail: resolution.detail
|
|
1285
|
+
});
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
const gold = goldIncorrectSteps(entry);
|
|
1289
|
+
if (gold.length === 0) {
|
|
1290
|
+
excluded.push({
|
|
1291
|
+
corpus: corpus.name,
|
|
1292
|
+
trajId,
|
|
1293
|
+
reason: "no-gold-incorrect-step"
|
|
1294
|
+
});
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
const resources = resolution.resources;
|
|
1298
|
+
let submitGoldsSkipped = 0;
|
|
1299
|
+
let target = null;
|
|
1300
|
+
let missingGoldId = null;
|
|
1301
|
+
for (const goldId of gold) {
|
|
1302
|
+
const step = resources.steps.find((s) => s.step_id === goldId);
|
|
1303
|
+
if (!step) {
|
|
1304
|
+
missingGoldId = goldId;
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
if (step.action.trim() === "submit" || isSubmitOnlyAction(step.action)) {
|
|
1308
|
+
submitGoldsSkipped += 1;
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
target = step;
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
if (missingGoldId !== null) {
|
|
1315
|
+
excluded.push({
|
|
1316
|
+
corpus: corpus.name,
|
|
1317
|
+
trajId,
|
|
1318
|
+
reason: "gold-step-outside-steps",
|
|
1319
|
+
detail: `k=${missingGoldId}, steps=${resources.steps.length}`
|
|
1320
|
+
});
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
if (!target) {
|
|
1324
|
+
excluded.push({
|
|
1325
|
+
corpus: corpus.name,
|
|
1326
|
+
trajId,
|
|
1327
|
+
reason: "gold-only-submit-step",
|
|
1328
|
+
detail: `${submitGoldsSkipped} gold step(s), all submit commands`
|
|
1329
|
+
});
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
const recordedReturncodeAtK = parseRecordedReturncode(target.observation);
|
|
1333
|
+
if (recordedReturncodeAtK === null) {
|
|
1334
|
+
excluded.push({
|
|
1335
|
+
corpus: corpus.name,
|
|
1336
|
+
trajId,
|
|
1337
|
+
reason: "no-recorded-returncode-at-k",
|
|
1338
|
+
detail: `k=${target.step_id}; arm A cannot confirm execution without a recorded returncode`
|
|
1339
|
+
});
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
replayable.push({
|
|
1343
|
+
...resources,
|
|
1344
|
+
goldIncorrectSteps: gold,
|
|
1345
|
+
k: target.step_id,
|
|
1346
|
+
submitGoldsSkipped,
|
|
1347
|
+
recordedReturncodeAtK
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
replayable,
|
|
1352
|
+
excluded,
|
|
1353
|
+
labelEntryCount
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
//#endregion
|
|
1357
|
+
//#region src/trajectory-replay/image-preparer.ts
|
|
1358
|
+
/**
|
|
1359
|
+
* Replay-ready image derivation.
|
|
1360
|
+
*
|
|
1361
|
+
* A recorded trajectory names the image it ran in, but the image is not always
|
|
1362
|
+
* runnable as-is: sandbox platforms pin customer commands to a non-root
|
|
1363
|
+
* identity, so a root-owned working tree must be chowned before the replay can
|
|
1364
|
+
* write to it. `ImagePreparer` is that step, injectable so a consumer whose
|
|
1365
|
+
* images are already replay-ready supplies its own no-op or none at all.
|
|
1366
|
+
*/
|
|
1367
|
+
const execFileAsync = promisify(execFile);
|
|
1368
|
+
function derivedImageTag(image, cwd) {
|
|
1369
|
+
return `ctb-replay:${createHash("sha256").update(`${image}\n${cwd}`).digest("hex").slice(0, 12)}-uid1000`;
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Pulls the base image when absent and builds `FROM <base>; RUN chown -R
|
|
1373
|
+
* 1000:1000 <cwd>` tagged by content hash, so repeated batches reuse both
|
|
1374
|
+
* the pull and the build. cwd `/` skips the chown (never chown -R /) and
|
|
1375
|
+
* replays on the base image directly.
|
|
1376
|
+
*/
|
|
1377
|
+
function dockerImagePreparer(options = {}) {
|
|
1378
|
+
const pullTimeoutMs = options.pullTimeoutMs ?? 12e5;
|
|
1379
|
+
const buildTimeoutMs = options.buildTimeoutMs ?? 9e5;
|
|
1380
|
+
const imageExists = async (tag) => {
|
|
1381
|
+
try {
|
|
1382
|
+
await execFileAsync("docker", [
|
|
1383
|
+
"image",
|
|
1384
|
+
"inspect",
|
|
1385
|
+
tag
|
|
1386
|
+
], { maxBuffer: 8388608 });
|
|
1387
|
+
return true;
|
|
1388
|
+
} catch {
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
const errorTail = (err) => {
|
|
1393
|
+
return (err && typeof err === "object" && "stderr" in err && typeof err.stderr === "string" && err.stderr.length > 0 ? err.stderr : err instanceof Error ? err.message : String(err)).trim().split("\n").slice(-3).join(" | ").slice(0, 400);
|
|
1394
|
+
};
|
|
1395
|
+
return { async ensure(image, cwd) {
|
|
1396
|
+
if (cwd === "/") {
|
|
1397
|
+
if (await imageExists(image)) return {
|
|
1398
|
+
succeeded: true,
|
|
1399
|
+
value: {
|
|
1400
|
+
derivedImage: image,
|
|
1401
|
+
pulled: false,
|
|
1402
|
+
built: false
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
try {
|
|
1406
|
+
await execFileAsync("docker", ["pull", image], {
|
|
1407
|
+
timeout: pullTimeoutMs,
|
|
1408
|
+
maxBuffer: 33554432
|
|
1409
|
+
});
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
return {
|
|
1412
|
+
succeeded: false,
|
|
1413
|
+
error: `pull ${image}: ${errorTail(err)}`
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
return {
|
|
1417
|
+
succeeded: true,
|
|
1418
|
+
value: {
|
|
1419
|
+
derivedImage: image,
|
|
1420
|
+
pulled: true,
|
|
1421
|
+
built: false
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
const derived = derivedImageTag(image, cwd);
|
|
1426
|
+
if (await imageExists(derived)) return {
|
|
1427
|
+
succeeded: true,
|
|
1428
|
+
value: {
|
|
1429
|
+
derivedImage: derived,
|
|
1430
|
+
pulled: false,
|
|
1431
|
+
built: false
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
let pulled = false;
|
|
1435
|
+
if (!await imageExists(image)) try {
|
|
1436
|
+
await execFileAsync("docker", ["pull", image], {
|
|
1437
|
+
timeout: pullTimeoutMs,
|
|
1438
|
+
maxBuffer: 33554432
|
|
1439
|
+
});
|
|
1440
|
+
pulled = true;
|
|
1441
|
+
} catch (err) {
|
|
1442
|
+
return {
|
|
1443
|
+
succeeded: false,
|
|
1444
|
+
error: `pull ${image}: ${errorTail(err)}`
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
const contextDir = mkdtempSync(join(tmpdir(), "ctb-replay-image-"));
|
|
1448
|
+
const quotedCwd = `'${cwd.replaceAll("'", `'\\''`)}'`;
|
|
1449
|
+
writeFileSync(join(contextDir, "Dockerfile"), `FROM ${image}\nRUN chown -R 1000:1000 ${quotedCwd}\n`);
|
|
1450
|
+
try {
|
|
1451
|
+
await execFileAsync("docker", [
|
|
1452
|
+
"build",
|
|
1453
|
+
"-t",
|
|
1454
|
+
derived,
|
|
1455
|
+
contextDir
|
|
1456
|
+
], {
|
|
1457
|
+
timeout: buildTimeoutMs,
|
|
1458
|
+
maxBuffer: 33554432
|
|
1459
|
+
});
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
return {
|
|
1462
|
+
succeeded: false,
|
|
1463
|
+
error: `build ${derived} from ${image}: ${errorTail(err)}`
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
return {
|
|
1467
|
+
succeeded: true,
|
|
1468
|
+
value: {
|
|
1469
|
+
derivedImage: derived,
|
|
1470
|
+
pulled,
|
|
1471
|
+
built: true
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
} };
|
|
1475
|
+
}
|
|
1476
|
+
//#endregion
|
|
1477
|
+
//#region src/trajectory-replay/batch.ts
|
|
1478
|
+
/**
|
|
1479
|
+
* Batch replay verification across gold-labeled trajectory corpora.
|
|
1480
|
+
*
|
|
1481
|
+
* For every replayable case (a raw trajectory with a recorded image AND at
|
|
1482
|
+
* least one gold incorrect step) the batch:
|
|
1483
|
+
* 1. derives the replay-ready image through the injected `ImagePreparer`,
|
|
1484
|
+
* 2. replays the prefix and runs arm A — the recorded gold step k, and
|
|
1485
|
+
* 3. optionally generates a corrected command with one LLM call and runs
|
|
1486
|
+
* arm B in its own fresh session.
|
|
1487
|
+
*
|
|
1488
|
+
* Headline metrics:
|
|
1489
|
+
* replayability rate — fraction of replayable cases where the prefix
|
|
1490
|
+
* replays within the divergence tolerance AND arm A reproduces a
|
|
1491
|
+
* recorded failure at k; a zero exit without an error signature is not
|
|
1492
|
+
* evidence of failure;
|
|
1493
|
+
* prefix fidelity — executed prefix steps and the share of them that did
|
|
1494
|
+
* not confirm the recording, split by kind. A corpus whose recordings
|
|
1495
|
+
* carry no returncodes shows up here as unknown-expectation steps, never
|
|
1496
|
+
* as a clean replay;
|
|
1497
|
+
* fix-flip rate — fraction of arm-B-executed cases where the failure
|
|
1498
|
+
* vanished (exit 0, signature absent).
|
|
1499
|
+
*
|
|
1500
|
+
* Image pulls and execs run strictly serially: pulls contend on disk and
|
|
1501
|
+
* registry bandwidth, and serial cases keep wall-time attribution per case
|
|
1502
|
+
* honest. Pull failures are report rows, never silent skips.
|
|
1503
|
+
*/
|
|
1504
|
+
function rate(numerator, denominator) {
|
|
1505
|
+
return denominator > 0 ? numerator / denominator : null;
|
|
1506
|
+
}
|
|
1507
|
+
function caseIdentity(replayCase) {
|
|
1508
|
+
return {
|
|
1509
|
+
corpus: replayCase.corpus,
|
|
1510
|
+
trajId: replayCase.trajId,
|
|
1511
|
+
image: replayCase.image,
|
|
1512
|
+
cwd: replayCase.cwd,
|
|
1513
|
+
cwdSource: replayCase.cwdSource,
|
|
1514
|
+
k: replayCase.k,
|
|
1515
|
+
stepCount: replayCase.steps.length,
|
|
1516
|
+
goldIncorrectSteps: replayCase.goldIncorrectSteps,
|
|
1517
|
+
submitGoldsSkipped: replayCase.submitGoldsSkipped,
|
|
1518
|
+
recordedReturncodeAtK: replayCase.recordedReturncodeAtK
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
/** The row of a case that never produced an arm A verdict. */
|
|
1522
|
+
function unreplayedRow(replayCase, caseStart, failure) {
|
|
1523
|
+
return {
|
|
1524
|
+
...caseIdentity(replayCase),
|
|
1525
|
+
derivedImage: failure.derivedImage,
|
|
1526
|
+
signature: null,
|
|
1527
|
+
status: failure.status,
|
|
1528
|
+
error: failure.error,
|
|
1529
|
+
imagePulled: failure.imagePulled,
|
|
1530
|
+
imageBuilt: failure.imageBuilt,
|
|
1531
|
+
prefixExecuted: null,
|
|
1532
|
+
prefixDivergences: null,
|
|
1533
|
+
prefixDivergencePct: null,
|
|
1534
|
+
prefixConfirmed: null,
|
|
1535
|
+
prefixReturncodeMismatches: null,
|
|
1536
|
+
prefixUnknownExpectations: null,
|
|
1537
|
+
armAExit: null,
|
|
1538
|
+
armAReturncodeMatch: false,
|
|
1539
|
+
armASignatureMatch: false,
|
|
1540
|
+
replayed: false,
|
|
1541
|
+
fix: null,
|
|
1542
|
+
wallMs: Date.now() - caseStart
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
function verdictRow(replayCase, caseStart, image, verdict, returncodeMatch) {
|
|
1546
|
+
return {
|
|
1547
|
+
...caseIdentity(replayCase),
|
|
1548
|
+
derivedImage: image.derivedImage,
|
|
1549
|
+
signature: verdict.signature,
|
|
1550
|
+
status: "ok",
|
|
1551
|
+
error: null,
|
|
1552
|
+
imagePulled: image.pulled,
|
|
1553
|
+
imageBuilt: image.built,
|
|
1554
|
+
prefixExecuted: verdict.prefixExecuted,
|
|
1555
|
+
prefixDivergences: verdict.prefixDivergences.length,
|
|
1556
|
+
prefixDivergencePct: verdict.prefixDivergencePct,
|
|
1557
|
+
prefixConfirmed: verdict.prefixConfirmed,
|
|
1558
|
+
prefixReturncodeMismatches: verdict.prefixReturncodeMismatches,
|
|
1559
|
+
prefixUnknownExpectations: verdict.prefixUnknownExpectations,
|
|
1560
|
+
armAExit: verdict.armA.exitCode,
|
|
1561
|
+
armAReturncodeMatch: returncodeMatch,
|
|
1562
|
+
armASignatureMatch: verdict.armA.failureSignatureMatch,
|
|
1563
|
+
replayed: verdict.valid,
|
|
1564
|
+
fix: null,
|
|
1565
|
+
wallMs: Date.now() - caseStart
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
/** Phase 1 for one case: prepare its image and run arm A. Every outcome is a row, never a skip. */
|
|
1569
|
+
async function replayArmA(run, replayCase, label, verdicts, pullFailures) {
|
|
1570
|
+
const { options, onProgress } = run;
|
|
1571
|
+
const caseStart = Date.now();
|
|
1572
|
+
onProgress(`${label}: preparing image ${replayCase.image}`);
|
|
1573
|
+
const preparation = await run.preparer.ensure(replayCase.image, replayCase.cwd);
|
|
1574
|
+
if (!preparation.succeeded) {
|
|
1575
|
+
pullFailures.push({
|
|
1576
|
+
corpus: replayCase.corpus,
|
|
1577
|
+
trajId: replayCase.trajId,
|
|
1578
|
+
image: replayCase.image,
|
|
1579
|
+
error: preparation.error
|
|
1580
|
+
});
|
|
1581
|
+
const row = unreplayedRow(replayCase, caseStart, {
|
|
1582
|
+
derivedImage: null,
|
|
1583
|
+
status: "image-unavailable",
|
|
1584
|
+
error: preparation.error,
|
|
1585
|
+
imagePulled: false,
|
|
1586
|
+
imageBuilt: false
|
|
1587
|
+
});
|
|
1588
|
+
appendFileSync(run.progressPath, `${JSON.stringify(row)}\n`);
|
|
1589
|
+
onProgress(`${label}: image unavailable — ${preparation.error}`);
|
|
1590
|
+
return row;
|
|
1591
|
+
}
|
|
1592
|
+
const { derivedImage, pulled, built } = preparation.value;
|
|
1593
|
+
const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
|
|
1594
|
+
const caseOut = join(options.out, caseOutDirName(replayCase));
|
|
1595
|
+
onProgress(`${label}: arm A on ${derivedImage} (k=${replayCase.k}, timeout ${stepTimeoutMs}ms)`);
|
|
1596
|
+
try {
|
|
1597
|
+
const verdict = await replayVerify({
|
|
1598
|
+
stepsPath: replayCase.stepsPath,
|
|
1599
|
+
image: derivedImage,
|
|
1600
|
+
at: replayCase.k,
|
|
1601
|
+
cwd: replayCase.cwd,
|
|
1602
|
+
out: caseOut,
|
|
1603
|
+
caseId: replayCase.trajId,
|
|
1604
|
+
stepTimeoutMs,
|
|
1605
|
+
prefixLimit: options.prefixLimit,
|
|
1606
|
+
backend: options.backendFactory(derivedImage),
|
|
1607
|
+
onProgress: (message) => onProgress(`${label}: ${message}`)
|
|
1608
|
+
});
|
|
1609
|
+
verdicts.set(replayCase.trajId, verdict);
|
|
1610
|
+
const returncodeMatch = verdict.recordedReturncode !== null && verdict.armA.exitCode === verdict.recordedReturncode;
|
|
1611
|
+
const row = verdictRow(replayCase, caseStart, {
|
|
1612
|
+
derivedImage,
|
|
1613
|
+
pulled,
|
|
1614
|
+
built
|
|
1615
|
+
}, verdict, returncodeMatch);
|
|
1616
|
+
appendFileSync(run.progressPath, `${JSON.stringify(row)}\n`);
|
|
1617
|
+
onProgress(`${label}: armA exit=${verdict.armA.exitCode} rcMatch=${returncodeMatch} divergences=${verdict.prefixDivergences.length}/${verdict.prefixExecuted} (${verdict.prefixDivergencePct}%, ${verdict.prefixUnknownExpectations} unknown-expectation)`);
|
|
1618
|
+
return row;
|
|
1619
|
+
} catch (err) {
|
|
1620
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1621
|
+
const row = unreplayedRow(replayCase, caseStart, {
|
|
1622
|
+
derivedImage,
|
|
1623
|
+
status: "replay-error",
|
|
1624
|
+
error: message.slice(0, 500),
|
|
1625
|
+
imagePulled: pulled,
|
|
1626
|
+
imageBuilt: built
|
|
1627
|
+
});
|
|
1628
|
+
appendFileSync(run.progressPath, `${JSON.stringify(row)}\n`);
|
|
1629
|
+
onProgress(`${label}: replay error — ${message.slice(0, 200)}`);
|
|
1630
|
+
return row;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
function prefixFidelityOf(executedRows) {
|
|
1634
|
+
const sumOver = (pick) => executedRows.reduce((total, row) => total + (pick(row) ?? 0), 0);
|
|
1635
|
+
const executedSteps = sumOver((r) => r.prefixExecuted);
|
|
1636
|
+
const divergentSteps = sumOver((r) => r.prefixDivergences);
|
|
1637
|
+
return {
|
|
1638
|
+
executedSteps,
|
|
1639
|
+
divergentSteps,
|
|
1640
|
+
returncodeMismatches: sumOver((r) => r.prefixReturncodeMismatches),
|
|
1641
|
+
unknownExpectations: sumOver((r) => r.prefixUnknownExpectations),
|
|
1642
|
+
divergencePct: executedSteps > 0 ? Number((divergentSteps / executedSteps * 100).toFixed(1)) : null,
|
|
1643
|
+
tolerancePct: 10,
|
|
1644
|
+
casesWithinTolerance: executedRows.filter((r) => r.prefixDivergencePct !== null && r.prefixDivergencePct <= 10).length,
|
|
1645
|
+
casesExecuted: executedRows.length
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
/** Fix-flip headline rates over the cases whose arm B executed. */
|
|
1649
|
+
function fixFlipHeadline(rows, fix) {
|
|
1650
|
+
const armBExecuted = rows.filter((r) => r.fix?.attempted && r.fix.failureVanished !== null);
|
|
1651
|
+
const flipped = armBExecuted.filter((r) => r.fix.failureVanished === true);
|
|
1652
|
+
const armBNonzeroRc = armBExecuted.filter((r) => r.recordedReturncodeAtK !== null && r.recordedReturncodeAtK !== 0);
|
|
1653
|
+
const flippedNonzeroRc = armBNonzeroRc.filter((r) => r.fix.failureVanished === true);
|
|
1654
|
+
const attempt1Executed = rows.filter((r) => r.fix?.attempts?.find((a) => a.attempt === 1)?.executed === true);
|
|
1655
|
+
const flippedAt1 = rows.filter((r) => r.fix?.flippedAtAttempt === 1);
|
|
1656
|
+
const flipsByAttempt = {};
|
|
1657
|
+
for (const row of rows) {
|
|
1658
|
+
const at = row.fix?.flippedAtAttempt;
|
|
1659
|
+
if (typeof at === "number") flipsByAttempt[String(at)] = (flipsByAttempt[String(at)] ?? 0) + 1;
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
fixFlipRate: fix !== "none" ? {
|
|
1663
|
+
numerator: flipped.length,
|
|
1664
|
+
denominator: armBExecuted.length,
|
|
1665
|
+
value: rate(flipped.length, armBExecuted.length)
|
|
1666
|
+
} : null,
|
|
1667
|
+
fixFlipRateNonzeroRc: fix !== "none" ? {
|
|
1668
|
+
numerator: flippedNonzeroRc.length,
|
|
1669
|
+
denominator: armBNonzeroRc.length,
|
|
1670
|
+
value: rate(flippedNonzeroRc.length, armBNonzeroRc.length)
|
|
1671
|
+
} : null,
|
|
1672
|
+
fixFlipAttempt1: fix === "loop" ? {
|
|
1673
|
+
numerator: flippedAt1.length,
|
|
1674
|
+
denominator: attempt1Executed.length,
|
|
1675
|
+
value: rate(flippedAt1.length, attempt1Executed.length)
|
|
1676
|
+
} : null,
|
|
1677
|
+
flipsByAttempt: fix === "loop" ? flipsByAttempt : null
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
/** Per-corpus submit-gold accounting: cases dropped for pure-submit golds, and submit golds skipped inside replayable cases. */
|
|
1681
|
+
function submitGoldsOf(enumeration) {
|
|
1682
|
+
const byCorpus = {};
|
|
1683
|
+
const entry = (corpus) => byCorpus[corpus] ??= {
|
|
1684
|
+
submitOnlyCases: 0,
|
|
1685
|
+
goldsSkippedWithinReplayable: 0
|
|
1686
|
+
};
|
|
1687
|
+
for (const excluded of enumeration.excluded) if (excluded.reason === "gold-only-submit-step") entry(excluded.corpus).submitOnlyCases += 1;
|
|
1688
|
+
for (const replayCase of enumeration.replayable) if (replayCase.submitGoldsSkipped > 0) entry(replayCase.corpus).goldsSkippedWithinReplayable += replayCase.submitGoldsSkipped;
|
|
1689
|
+
return byCorpus;
|
|
1690
|
+
}
|
|
1691
|
+
function buildBatchReport(options, enumeration, selected, rows, llm, pullFailures) {
|
|
1692
|
+
const executedRows = rows.filter((r) => r.status === "ok");
|
|
1693
|
+
const replayed = executedRows.filter((r) => r.replayed);
|
|
1694
|
+
const signatureStrict = executedRows.filter((r) => r.replayed && r.armASignatureMatch);
|
|
1695
|
+
const excludedByReason = {};
|
|
1696
|
+
for (const excluded of enumeration.excluded) excludedByReason[excluded.reason] = (excludedByReason[excluded.reason] ?? 0) + 1;
|
|
1697
|
+
return {
|
|
1698
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1699
|
+
corpora: options.corpora.map((c) => ({
|
|
1700
|
+
name: c.name,
|
|
1701
|
+
labelsPath: c.labelsPath,
|
|
1702
|
+
preparedDir: c.preparedDir
|
|
1703
|
+
})),
|
|
1704
|
+
totals: {
|
|
1705
|
+
labelEntries: enumeration.labelEntryCount,
|
|
1706
|
+
replayable: enumeration.replayable.length,
|
|
1707
|
+
executed: selected.length,
|
|
1708
|
+
excludedByReason,
|
|
1709
|
+
submitGoldsByCorpus: submitGoldsOf(enumeration)
|
|
1710
|
+
},
|
|
1711
|
+
headline: {
|
|
1712
|
+
replayabilityRate: {
|
|
1713
|
+
numerator: replayed.length,
|
|
1714
|
+
denominator: selected.length,
|
|
1715
|
+
value: rate(replayed.length, selected.length)
|
|
1716
|
+
},
|
|
1717
|
+
signatureStrictRate: {
|
|
1718
|
+
numerator: signatureStrict.length,
|
|
1719
|
+
denominator: selected.length,
|
|
1720
|
+
value: rate(signatureStrict.length, selected.length)
|
|
1721
|
+
},
|
|
1722
|
+
prefixFidelity: prefixFidelityOf(executedRows),
|
|
1723
|
+
...fixFlipHeadline(rows, options.fix)
|
|
1724
|
+
},
|
|
1725
|
+
llm,
|
|
1726
|
+
excluded: enumeration.excluded,
|
|
1727
|
+
pullFailures,
|
|
1728
|
+
cases: rows
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
async function runReplayBatch(options) {
|
|
1732
|
+
const onProgress = options.onProgress ?? (() => {});
|
|
1733
|
+
const enumeration = enumerateReplayableCases(options.corpora);
|
|
1734
|
+
let selected = enumeration.replayable;
|
|
1735
|
+
if (options.caseFilter) selected = selected.filter((c) => c.trajId.includes(options.caseFilter));
|
|
1736
|
+
if (options.caseLimit !== void 0) selected = selected.slice(0, options.caseLimit);
|
|
1737
|
+
onProgress(`enumerated ${enumeration.labelEntryCount} label entries → ${enumeration.replayable.length} replayable, ${enumeration.excluded.length} excluded; executing ${selected.length}`);
|
|
1738
|
+
mkdirSync(options.out, { recursive: true });
|
|
1739
|
+
const run = {
|
|
1740
|
+
options,
|
|
1741
|
+
onProgress,
|
|
1742
|
+
progressPath: join(options.out, "cases.jsonl"),
|
|
1743
|
+
preparer: options.preparer ?? dockerImagePreparer()
|
|
1744
|
+
};
|
|
1745
|
+
const rows = [];
|
|
1746
|
+
const pullFailures = [];
|
|
1747
|
+
const verdictByTraj = /* @__PURE__ */ new Map();
|
|
1748
|
+
for (const [index, replayCase] of selected.entries()) {
|
|
1749
|
+
const label = `[${index + 1}/${selected.length}] ${replayCase.corpus}/${replayCase.trajId}`;
|
|
1750
|
+
rows.push(await replayArmA(run, replayCase, label, verdictByTraj, pullFailures));
|
|
1751
|
+
}
|
|
1752
|
+
const llm = options.fix !== "none" ? await runFixPhase(run, selected, rows, verdictByTraj) : null;
|
|
1753
|
+
const report = buildBatchReport(options, enumeration, selected, rows, llm, pullFailures);
|
|
1754
|
+
writeFileSync(join(options.out, "batch-report.json"), `${JSON.stringify(report, null, 2)}\n`);
|
|
1755
|
+
writeFileSync(join(options.out, "batch-report.md"), renderBatchReport(report));
|
|
1756
|
+
return report;
|
|
1757
|
+
}
|
|
1758
|
+
//#endregion
|
|
1728
1759
|
//#region src/trajectory-replay/wire.ts
|
|
1729
1760
|
/**
|
|
1730
1761
|
* Finding-to-replay wire: the entry point that turns a cited incorrect-steps
|