@tangle-network/agent-bench 0.3.6 → 0.3.8
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 +11 -0
- package/HARNESS.md +43 -0
- package/dist/adapters.js +24 -24
- package/dist/benchmarks/_harness.d.ts +1 -1
- package/dist/benchmarks/_harness.js +1 -1
- package/dist/benchmarks/aec-bench.js +2 -2
- package/dist/benchmarks/agentbench.js +2 -2
- package/dist/benchmarks/appworld.js +2 -2
- package/dist/benchmarks/bfcl.js +2 -2
- package/dist/benchmarks/commit0.js +2 -2
- package/dist/benchmarks/crag.js +2 -2
- package/dist/benchmarks/dabstep.js +2 -2
- package/dist/benchmarks/enterpriseops-gym.js +2 -2
- package/dist/benchmarks/finresearchbench.js +2 -2
- package/dist/benchmarks/humaneval.d.ts +10 -1
- package/dist/benchmarks/humaneval.js +5 -3
- package/dist/benchmarks/nomiracl.js +2 -2
- package/dist/benchmarks/open-rag-bench.js +2 -2
- package/dist/benchmarks/programbench.js +2 -2
- package/dist/benchmarks/ragbench.js +2 -2
- package/dist/benchmarks/swe-bench.js +2 -2
- package/dist/benchmarks/t2-ragbench.js +2 -2
- package/dist/benchmarks/tau-bench-shared.js +2 -2
- package/dist/benchmarks/tau2-bench.js +3 -3
- package/dist/benchmarks/tau3-banking.js +3 -3
- package/dist/benchmarks/terminal-bench.js +2 -2
- package/dist/benchmarks/toollm.js +2 -2
- package/dist/benchmarks/webarena-verified.js +2 -2
- package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
- package/dist/{chunk-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
- package/dist/chunk-5H5XV76F.js.map +1 -0
- package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
- package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
- package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
- package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
- package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
- package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
- package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
- package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
- package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
- package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
- package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
- package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
- package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
- package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
- package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
- package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
- package/dist/{chunk-5SBJCB6W.js → chunk-V7AEBY6U.js} +22 -22
- package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
- package/dist/chunk-WSKWVEQB.js.map +1 -0
- package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
- package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
- package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
- package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
- package/dist/index.js +24 -24
- package/package.json +6 -5
- package/scripts/run-package-tests.mjs +30 -8
- package/scripts/verify-packed-consumer.mjs +1 -1
- package/scripts/verify-pier-agent.mts +1 -0
- package/src/benchmarks/_harness.ts +20 -2
- package/src/benchmarks/humaneval.test.mts +122 -0
- package/src/benchmarks/humaneval.ts +100 -27
- package/src/david-attribution.mts +78 -0
- package/src/david-goliath.mts +149 -0
- package/src/hev-improve.mts +25 -6
- package/src/humaneval-object-ablation.mts +201 -0
- package/src/live-improve-campaign-mbpp.mts +641 -0
- package/src/live-improve-campaign.mts +500 -0
- package/src/mbpp-structural.mts +12 -7
- package/src/quant-arena/README.md +144 -0
- package/src/quant-arena/backtest.test.mts +135 -0
- package/src/quant-arena/backtest.ts +218 -0
- package/src/quant-arena/data.test.mts +44 -0
- package/src/quant-arena/data.ts +141 -0
- package/src/quant-arena/driver.test.mts +253 -0
- package/src/quant-arena/driver.ts +219 -0
- package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
- package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
- package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
- package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
- package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
- package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
- package/src/quant-arena/holdout-certify.mts +206 -0
- package/src/quant-arena/holdout-certify.test.mts +82 -0
- package/src/quant-arena/leak-audit.test.mts +79 -0
- package/src/quant-arena/leak-audit.ts +95 -0
- package/src/quant-arena/make-fixtures.mts +161 -0
- package/src/quant-arena/multiplicity.test.mts +68 -0
- package/src/quant-arena/multiplicity.ts +87 -0
- package/src/quant-arena/nautilus-certify.ts +31 -0
- package/src/quant-arena/oms.ts +90 -0
- package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
- package/src/quant-arena/python/pyproject.toml +8 -0
- package/src/quant-arena/python/uv.lock +1297 -0
- package/src/quant-arena/python/vbt-worker.py +192 -0
- package/src/quant-arena/quant-loop.mts +813 -0
- package/src/quant-arena/quant-loop.test.mts +75 -0
- package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
- package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
- package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
- package/src/quant-arena/types.ts +133 -0
- package/src/quant-arena/vbt-client.ts +321 -0
- package/src/quant-arena/vbt-parity.test.mts +183 -0
- package/src/quant-arena/windows.test.mts +45 -0
- package/src/quant-arena/windows.ts +54 -0
- package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
- package/src/rollout-ledger/backfill-swe-arena.test.mts +338 -0
- package/src/rollout-ledger/settle-capture.mts +442 -0
- package/src/rollout-ledger/settle-capture.test.mts +270 -0
- package/src/stream-observe.py +45 -0
- package/src/stream-observe.tpl.html +247 -0
- package/src/supervisor-arena.mts +816 -0
- package/src/swe-arena/activation.mts +228 -0
- package/src/swe-arena/activation.test.mts +303 -0
- package/src/swe-arena/analyze.ts +211 -0
- package/src/swe-arena/arms.ts +804 -0
- package/src/swe-arena/bootstrap-meta.mts +188 -0
- package/src/swe-arena/bootstrap-meta.test.mts +51 -0
- package/src/swe-arena/briefing.mts +217 -0
- package/src/swe-arena/briefing.test.mts +178 -0
- package/src/swe-arena/calibrate.ts +217 -0
- package/src/swe-arena/capabilities.mts +76 -0
- package/src/swe-arena/capabilities.test.mts +57 -0
- package/src/swe-arena/capacity.ts +194 -0
- package/src/swe-arena/cell-evidence.mts +437 -0
- package/src/swe-arena/cell-evidence.test.mts +248 -0
- package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
- package/src/swe-arena/diagnosis-ensemble.ts +520 -0
- package/src/swe-arena/execution.test.mts +1170 -0
- package/src/swe-arena/factory-command-container.ts +284 -0
- package/src/swe-arena/factory-judge-child.mts +228 -0
- package/src/swe-arena/factory.test.mts +643 -0
- package/src/swe-arena/fixtures/analyze.py +80 -0
- package/src/swe-arena/fixtures/excludes.txt +8 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
- package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
- package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
- package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
- package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
- package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
- package/src/swe-arena/fixtures/holdout.json +44 -0
- package/src/swe-arena/fixtures/instances.json +146 -0
- package/src/swe-arena/fixtures/ledger.jsonl +12 -0
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
- package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
- package/src/swe-arena/fixtures/rematch.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
- package/src/swe-arena/fixtures/run-report/README.md +43 -0
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
- package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
- package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
- package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
- package/src/swe-arena/fixtures/worker-tokens.json +42 -0
- package/src/swe-arena/fixtures.ts +237 -0
- package/src/swe-arena/gepa-seat.mts +583 -0
- package/src/swe-arena/gepa-seat.test.mts +635 -0
- package/src/swe-arena/holdout-certify.mts +408 -0
- package/src/swe-arena/holdout-certify.test.mts +160 -0
- package/src/swe-arena/judge-child.mts +37 -0
- package/src/swe-arena/ledger-orphans.mts +77 -0
- package/src/swe-arena/ledger-orphans.test.mts +147 -0
- package/src/swe-arena/lineage-record.mts +164 -0
- package/src/swe-arena/lineage-record.test.mts +115 -0
- package/src/swe-arena/manifest.mts +293 -0
- package/src/swe-arena/manifest.test.mts +169 -0
- package/src/swe-arena/materialize.ts +142 -0
- package/src/swe-arena/outer-loop.mts +2795 -0
- package/src/swe-arena/outer-loop.test.mts +696 -0
- package/src/swe-arena/parity.test.mts +87 -0
- package/src/swe-arena/premeasured-from-cells.mts +281 -0
- package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
- package/src/swe-arena/proc.test.mts +174 -0
- package/src/swe-arena/proc.ts +260 -0
- package/src/swe-arena/profiles/default-author.profile.json +4 -0
- package/src/swe-arena/proposer-fanout.mts +770 -0
- package/src/swe-arena/proposer-fanout.test.mts +619 -0
- package/src/swe-arena/proposer-provenance.mts +177 -0
- package/src/swe-arena/proposer-provenance.test.mts +106 -0
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +183 -0
- package/src/swe-arena/replay.test.mts +300 -0
- package/src/swe-arena/run-experiment.mts +727 -0
- package/src/swe-arena/run-report.mts +75 -0
- package/src/swe-arena/run-supervisor.mjs +297 -0
- package/src/swe-arena/run-supervisor.test.mts +500 -0
- package/src/swe-arena/score-split.mts +140 -0
- package/src/swe-arena/score-split.test.mts +123 -0
- package/src/swe-arena/serialized-judge.ts +414 -0
- package/src/swe-arena/types.ts +218 -0
- package/src/swe-code-improve.mts +328 -0
- package/src/swe-emit-patch.mts +104 -0
- package/src/swe-improve.mts +232 -0
- package/src/swe-jail.ts +2 -2
- package/src/swe-local-proof.mts +169 -0
- package/src/swe-repro-calibrate.mts +446 -0
- package/src/swe-stream.mts +1497 -0
- package/src/swe-structural.mts +245 -837
- package/dist/chunk-LRRD7NAG.js.map +0 -1
- package/dist/chunk-PPYSEKFM.js.map +0 -1
- /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
- /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
- /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
- /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
- /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
- /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
- /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
- /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
- /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
- /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
- /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
- /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
- /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
- /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
- /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
- /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
- /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
- /package/dist/{chunk-5SBJCB6W.js.map → chunk-V7AEBY6U.js.map} +0 -0
- /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
- /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
- /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
- /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settle-time rollout capture with LABEL v2 — `tangle.rollout.v1` lines
|
|
3
|
+
* emitted LIVE, right after each campaign cell is judged, while the harness
|
|
4
|
+
* stores still hold the transcripts (the whole reason settle-time capture
|
|
5
|
+
* beats backfill: the opencode sqlite db and claude project transcripts are
|
|
6
|
+
* mutable/garbage-collected).
|
|
7
|
+
*
|
|
8
|
+
* LABEL v2 semantics (reward_source tags distinguish them from the v1
|
|
9
|
+
* backfill's inherited labels — both coexist in one ledger):
|
|
10
|
+
*
|
|
11
|
+
* - WORKERS get CONTRIBUTION-AWARE reward: reward=1 only when the cell
|
|
12
|
+
* resolved AND that worker's patch was the delivered one (delivered-patch
|
|
13
|
+
* identity from the supervisor run dir's per-worker evidence,
|
|
14
|
+
* workers/<label>.patch); sibling bystanders in a resolved cell get
|
|
15
|
+
* reward=0 with metrics.bystander=true. In an unresolved cell every worker
|
|
16
|
+
* gets reward=0. A resolved cell whose delivered patch matches NO worker
|
|
17
|
+
* patch is a labeled identity gap: reward=null,
|
|
18
|
+
* metrics.delivered_match='unknown' — never a fabricated credit.
|
|
19
|
+
* - The SUPERVISOR line keeps the official-judge verdict as its reward
|
|
20
|
+
* (unchanged semantics; brain transcripts remain honest gap lines until
|
|
21
|
+
* the loops brain persists message content — journal + stats are joined
|
|
22
|
+
* into metrics).
|
|
23
|
+
* - PROPOSERS get BASELINE-RELATIVE reward: candidate_score −
|
|
24
|
+
* baseline_score (resolved fractions), so an improvement is positive and
|
|
25
|
+
* a regression negative — emitted once per candidate when the round's
|
|
26
|
+
* scores are final.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { randomUUID } from 'node:crypto'
|
|
30
|
+
import { existsSync } from 'node:fs'
|
|
31
|
+
import { readFile, readdir } from 'node:fs/promises'
|
|
32
|
+
import { join } from 'node:path'
|
|
33
|
+
import type { DatabaseSync } from 'node:sqlite'
|
|
34
|
+
import {
|
|
35
|
+
appendRolloutLines,
|
|
36
|
+
DEFAULT_OPENCODE_DB,
|
|
37
|
+
findOpencodeSessionsByDirectory,
|
|
38
|
+
openOpencodeDb,
|
|
39
|
+
readOpencodeSessionMessages,
|
|
40
|
+
ROLLOUT_SCHEMA,
|
|
41
|
+
type ChatMessage,
|
|
42
|
+
type OpencodeSessionRow,
|
|
43
|
+
type RolloutLine,
|
|
44
|
+
} from '@tangle-network/agent-eval/rollout'
|
|
45
|
+
|
|
46
|
+
export const OFFICIAL_JUDGE = 'swe-arena-official-judge'
|
|
47
|
+
export const WORKER_REWARD_SOURCE_V2 = `${OFFICIAL_JUDGE}/contribution-v2`
|
|
48
|
+
export const PROPOSER_REWARD_SOURCE_V2 = `${OFFICIAL_JUDGE}/baseline-relative-v2`
|
|
49
|
+
|
|
50
|
+
/** Stable candidate identity from the improvement-loop coordinates. */
|
|
51
|
+
export function candidateId(generation: number, candidateIndex: number): string {
|
|
52
|
+
return candidateIndex === -1 ? 'baseline' : `gen${generation}-cand${candidateIndex}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Worker evidence — the supervisor run dir's per-worker record.
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
export interface WorkerEvidenceRecord {
|
|
60
|
+
label: string
|
|
61
|
+
/** Worker clone cwd from the ndjson `started` event (opencode join key). */
|
|
62
|
+
cwd: string | null
|
|
63
|
+
/** Patch persisted at settle time (workers/<label>.patch); null = none. */
|
|
64
|
+
patch: string | null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Read workers/<label>.{ndjson,patch} under one supervisor run dir. */
|
|
68
|
+
export async function readWorkerEvidence(supRunDir: string): Promise<WorkerEvidenceRecord[]> {
|
|
69
|
+
const workersDir = join(supRunDir, 'workers')
|
|
70
|
+
const names = await readdir(workersDir).catch(() => [])
|
|
71
|
+
const labels = new Set<string>()
|
|
72
|
+
for (const name of names) {
|
|
73
|
+
if (name.endsWith('.ndjson') && !name.endsWith('.inbox.ndjson')) labels.add(name.slice(0, -'.ndjson'.length))
|
|
74
|
+
else if (name.endsWith('.patch')) labels.add(name.slice(0, -'.patch'.length))
|
|
75
|
+
}
|
|
76
|
+
const records: WorkerEvidenceRecord[] = []
|
|
77
|
+
for (const label of [...labels].sort()) {
|
|
78
|
+
let cwd: string | null = null
|
|
79
|
+
const nd = await readFile(join(workersDir, `${label}.ndjson`), 'utf8').catch(() => '')
|
|
80
|
+
for (const line of nd.split('\n')) {
|
|
81
|
+
if (!line.trim()) continue
|
|
82
|
+
try {
|
|
83
|
+
const o = JSON.parse(line) as Record<string, unknown>
|
|
84
|
+
if (o.kind === 'started' && typeof o.cwd === 'string') cwd = o.cwd
|
|
85
|
+
} catch {
|
|
86
|
+
// A corrupt event line cannot invalidate the rest of the evidence.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const patch = await readFile(join(workersDir, `${label}.patch`), 'utf8').catch(() => null)
|
|
90
|
+
records.push({ label, cwd, patch: patch !== null && patch.trim().length > 0 ? patch : null })
|
|
91
|
+
}
|
|
92
|
+
return records
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Labels whose persisted patch IS the delivered patch (trimmed equality —
|
|
96
|
+
* the supervisor delivers a settled worker's patch verbatim). */
|
|
97
|
+
export function deliveredWorkerLabels(deliveredPatch: string, workers: readonly WorkerEvidenceRecord[]): string[] {
|
|
98
|
+
const delivered = deliveredPatch.trim()
|
|
99
|
+
if (delivered.length === 0) return []
|
|
100
|
+
return workers.filter((w) => w.patch !== null && w.patch.trim() === delivered).map((w) => w.label)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface WorkerRewardV2 {
|
|
104
|
+
reward: number | null
|
|
105
|
+
bystander: boolean
|
|
106
|
+
/** 'delivered' | 'bystander' | 'unresolved' | 'unknown' (identity gap). */
|
|
107
|
+
deliveredMatch: 'delivered' | 'bystander' | 'unresolved' | 'unknown'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The v2 contribution rule for one worker in one cell. */
|
|
111
|
+
export function workerRewardV2(input: {
|
|
112
|
+
/** Official judge verdict for the cell; null = inconclusive. */
|
|
113
|
+
resolved: boolean | null
|
|
114
|
+
/** This worker's label is among the delivered-patch matches. */
|
|
115
|
+
isDelivered: boolean
|
|
116
|
+
/** At least one worker matched the delivered patch (identity known). */
|
|
117
|
+
identityKnown: boolean
|
|
118
|
+
}): WorkerRewardV2 {
|
|
119
|
+
if (input.resolved !== true) {
|
|
120
|
+
return { reward: input.resolved === false ? 0 : null, bystander: false, deliveredMatch: 'unresolved' }
|
|
121
|
+
}
|
|
122
|
+
if (!input.identityKnown) {
|
|
123
|
+
return { reward: null, bystander: false, deliveredMatch: 'unknown' }
|
|
124
|
+
}
|
|
125
|
+
if (input.isDelivered) return { reward: 1, bystander: false, deliveredMatch: 'delivered' }
|
|
126
|
+
return { reward: 0, bystander: true, deliveredMatch: 'bystander' }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Baseline-relative proposer reward (resolved fractions; improvement > 0). */
|
|
130
|
+
export function proposerRewardV2(candResolved: number, baselineResolved: number, instanceCount: number): number {
|
|
131
|
+
if (!(instanceCount > 0)) throw new Error('proposerRewardV2: instanceCount must be > 0')
|
|
132
|
+
return (candResolved - baselineResolved) / instanceCount
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// The live capture handle.
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
export interface SettleCaptureOptions {
|
|
140
|
+
/** Ledger JSONL destination (appended live, one flush per cell). */
|
|
141
|
+
ledgerPath: string
|
|
142
|
+
runId: string
|
|
143
|
+
instanceCount: number
|
|
144
|
+
opencodeDb?: string
|
|
145
|
+
/** Injected clock for deterministic tests. */
|
|
146
|
+
now?: () => Date
|
|
147
|
+
log?: (msg: string) => void
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface CellCaptureArgs {
|
|
151
|
+
generation: number
|
|
152
|
+
candidateIndex: number
|
|
153
|
+
iid: string
|
|
154
|
+
rep: number
|
|
155
|
+
seed: number | null
|
|
156
|
+
/** Our public/private sub-split of the train instances (metrics label). */
|
|
157
|
+
splitVisibility: 'public' | 'private' | null
|
|
158
|
+
commit: string | null
|
|
159
|
+
/** Official judge verdict (null = inconclusive). */
|
|
160
|
+
resolved: boolean | null
|
|
161
|
+
/** Raw judge verdict record, verbatim. */
|
|
162
|
+
judgeVerdict: unknown
|
|
163
|
+
runDir: string | null
|
|
164
|
+
patchPath: string | null
|
|
165
|
+
/** Supervisor run dir (<ws>/.loops/supervisor/<id>); null = unlocatable. */
|
|
166
|
+
supRunDir: string | null
|
|
167
|
+
/** The delivered patch text (empty = no delivery). */
|
|
168
|
+
deliveredPatch: string
|
|
169
|
+
workerModel: string | null
|
|
170
|
+
metrics: Record<string, unknown>
|
|
171
|
+
cost: { usd: number | null; wallS: number | null; spentTokens: number | null }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface ProposerCaptureArgs {
|
|
175
|
+
generation: number
|
|
176
|
+
candidateIndex: number
|
|
177
|
+
proposer: string
|
|
178
|
+
harness: string | null
|
|
179
|
+
commit: string | null
|
|
180
|
+
candResolved: number
|
|
181
|
+
baselineResolved: number
|
|
182
|
+
shotReceiptPaths: string[]
|
|
183
|
+
diffPath: string | null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface SettleCapture {
|
|
187
|
+
readonly path: string
|
|
188
|
+
captureCell(args: CellCaptureArgs): Promise<{ lines: number }>
|
|
189
|
+
captureProposer(args: ProposerCaptureArgs): Promise<{ lines: number }>
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function createSettleCapture(opts: SettleCaptureOptions): SettleCapture {
|
|
193
|
+
const now = opts.now ?? (() => new Date())
|
|
194
|
+
const log = opts.log ?? (() => {})
|
|
195
|
+
const dbPath = opts.opencodeDb ?? DEFAULT_OPENCODE_DB
|
|
196
|
+
|
|
197
|
+
const base = (capturedAt: string): Pick<RolloutLine, 'schema' | 'run_id'> & { provenance: RolloutLine['provenance'] } => ({
|
|
198
|
+
schema: ROLLOUT_SCHEMA,
|
|
199
|
+
run_id: opts.runId,
|
|
200
|
+
provenance: { captured_at: capturedAt, capture: 'settle-time' },
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
path: opts.ledgerPath,
|
|
205
|
+
|
|
206
|
+
async captureCell(args: CellCaptureArgs): Promise<{ lines: number }> {
|
|
207
|
+
const capturedAt = now().toISOString()
|
|
208
|
+
const supervisorId = randomUUID()
|
|
209
|
+
const reward = args.resolved === null ? null : args.resolved ? 1 : 0
|
|
210
|
+
const lines: RolloutLine[] = []
|
|
211
|
+
|
|
212
|
+
lines.push({
|
|
213
|
+
...base(capturedAt),
|
|
214
|
+
rollout_id: supervisorId,
|
|
215
|
+
parent_rollout_id: null,
|
|
216
|
+
candidate_id: candidateId(args.generation, args.candidateIndex),
|
|
217
|
+
generation: args.generation,
|
|
218
|
+
candidate_index: args.candidateIndex,
|
|
219
|
+
role: 'supervisor',
|
|
220
|
+
task: { suite: 'swe-bench-verified', instance_id: args.iid, split: 'search', seed: args.seed, rep: args.rep },
|
|
221
|
+
policy: {
|
|
222
|
+
harness: 'pi-loops',
|
|
223
|
+
harness_version: null,
|
|
224
|
+
model: args.workerModel,
|
|
225
|
+
provider: null,
|
|
226
|
+
profile_commit: args.commit,
|
|
227
|
+
sampling: null,
|
|
228
|
+
},
|
|
229
|
+
messages: [],
|
|
230
|
+
tool_defs: [],
|
|
231
|
+
outcome: {
|
|
232
|
+
reward,
|
|
233
|
+
reward_source: reward === null ? null : OFFICIAL_JUDGE,
|
|
234
|
+
verdict: args.judgeVerdict,
|
|
235
|
+
metrics: {
|
|
236
|
+
...args.metrics,
|
|
237
|
+
...(args.splitVisibility !== null ? { split_visibility: args.splitVisibility } : {}),
|
|
238
|
+
},
|
|
239
|
+
is_completed: args.resolved !== null,
|
|
240
|
+
is_truncated: false,
|
|
241
|
+
error: null,
|
|
242
|
+
},
|
|
243
|
+
cost: {
|
|
244
|
+
usd: args.cost.usd,
|
|
245
|
+
tokens_in: null,
|
|
246
|
+
tokens_out: null,
|
|
247
|
+
tokens_reasoning: null,
|
|
248
|
+
cache_read: null,
|
|
249
|
+
cache_write: null,
|
|
250
|
+
wall_s: args.cost.wallS,
|
|
251
|
+
},
|
|
252
|
+
artifacts: {
|
|
253
|
+
patch_path: args.patchPath,
|
|
254
|
+
run_dir: args.runDir,
|
|
255
|
+
transcript_ref: args.runDir !== null ? join(args.runDir, 'brain.jsonl') : null,
|
|
256
|
+
},
|
|
257
|
+
provenance: {
|
|
258
|
+
captured_at: capturedAt,
|
|
259
|
+
capture: 'settle-time',
|
|
260
|
+
gap: 'supervisor brain transcript not persisted (brain.jsonl carries per-request stats only)',
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// Worker lines with v2 contribution labels.
|
|
265
|
+
const workers = args.supRunDir !== null ? await readWorkerEvidence(args.supRunDir) : []
|
|
266
|
+
if (workers.length > 0) {
|
|
267
|
+
const delivered = new Set(deliveredWorkerLabels(args.deliveredPatch, workers))
|
|
268
|
+
const identityKnown = delivered.size > 0
|
|
269
|
+
let db: DatabaseSync | null = null
|
|
270
|
+
try {
|
|
271
|
+
db = await openOpencodeDb(dbPath)
|
|
272
|
+
for (const worker of workers) {
|
|
273
|
+
const v2 = workerRewardV2({
|
|
274
|
+
resolved: args.resolved,
|
|
275
|
+
isDelivered: delivered.has(worker.label),
|
|
276
|
+
identityKnown,
|
|
277
|
+
})
|
|
278
|
+
let session: OpencodeSessionRow | null = null
|
|
279
|
+
let messages: ChatMessage[] = []
|
|
280
|
+
if (db !== null && worker.cwd !== null) {
|
|
281
|
+
const sessions = findOpencodeSessionsByDirectory(db, worker.cwd)
|
|
282
|
+
session = sessions[0] ?? null
|
|
283
|
+
if (session !== null) messages = readOpencodeSessionMessages(db, session.id)
|
|
284
|
+
}
|
|
285
|
+
lines.push({
|
|
286
|
+
...base(capturedAt),
|
|
287
|
+
rollout_id: randomUUID(),
|
|
288
|
+
parent_rollout_id: supervisorId,
|
|
289
|
+
candidate_id: candidateId(args.generation, args.candidateIndex),
|
|
290
|
+
generation: args.generation,
|
|
291
|
+
candidate_index: args.candidateIndex,
|
|
292
|
+
role: 'worker',
|
|
293
|
+
task: {
|
|
294
|
+
suite: 'swe-bench-verified',
|
|
295
|
+
instance_id: args.iid,
|
|
296
|
+
split: 'search',
|
|
297
|
+
seed: args.seed,
|
|
298
|
+
rep: args.rep,
|
|
299
|
+
},
|
|
300
|
+
policy: {
|
|
301
|
+
harness: 'opencode',
|
|
302
|
+
harness_version: null,
|
|
303
|
+
model: session?.model?.id ?? null,
|
|
304
|
+
provider: session?.model?.providerID ?? null,
|
|
305
|
+
profile_commit: args.commit,
|
|
306
|
+
sampling: null,
|
|
307
|
+
},
|
|
308
|
+
messages,
|
|
309
|
+
tool_defs: [],
|
|
310
|
+
outcome: {
|
|
311
|
+
reward: v2.reward,
|
|
312
|
+
reward_source: v2.reward === null && v2.deliveredMatch !== 'unknown' ? null : WORKER_REWARD_SOURCE_V2,
|
|
313
|
+
verdict: null,
|
|
314
|
+
metrics: {
|
|
315
|
+
worker_label: worker.label,
|
|
316
|
+
worker_cwd: worker.cwd,
|
|
317
|
+
bystander: v2.bystander,
|
|
318
|
+
delivered_match: v2.deliveredMatch,
|
|
319
|
+
has_patch: worker.patch !== null,
|
|
320
|
+
has_session: session !== null,
|
|
321
|
+
...(args.splitVisibility !== null ? { split_visibility: args.splitVisibility } : {}),
|
|
322
|
+
},
|
|
323
|
+
// Matches the backfill rule: a session with no readable parts is
|
|
324
|
+
// a gap line, not a completed invocation.
|
|
325
|
+
is_completed: session !== null && messages.length > 0,
|
|
326
|
+
is_truncated: false,
|
|
327
|
+
error: null,
|
|
328
|
+
},
|
|
329
|
+
cost: {
|
|
330
|
+
usd: session?.costUsd ?? null,
|
|
331
|
+
tokens_in: session?.tokensInput ?? null,
|
|
332
|
+
tokens_out: session?.tokensOutput ?? null,
|
|
333
|
+
tokens_reasoning: session?.tokensReasoning ?? null,
|
|
334
|
+
cache_read: session?.tokensCacheRead ?? null,
|
|
335
|
+
cache_write: session?.tokensCacheWrite ?? null,
|
|
336
|
+
wall_s: session !== null ? Math.round((session.timeUpdated - session.timeCreated) / 1000) : null,
|
|
337
|
+
},
|
|
338
|
+
artifacts: {
|
|
339
|
+
patch_path: null,
|
|
340
|
+
run_dir: args.runDir,
|
|
341
|
+
transcript_ref: session !== null ? `opencode:${session.id}` : null,
|
|
342
|
+
},
|
|
343
|
+
provenance: {
|
|
344
|
+
captured_at: capturedAt,
|
|
345
|
+
capture: 'settle-time',
|
|
346
|
+
...(messages.length === 0
|
|
347
|
+
? {
|
|
348
|
+
gap:
|
|
349
|
+
worker.cwd === null
|
|
350
|
+
? `worker ${worker.label} recorded no clone cwd in its ndjson`
|
|
351
|
+
: session === null
|
|
352
|
+
? db === null
|
|
353
|
+
? `opencode store unavailable; worker cwd ${worker.cwd} unrecoverable`
|
|
354
|
+
: `no opencode session found for worker cwd ${worker.cwd}`
|
|
355
|
+
: `opencode session ${session.id} has no readable message parts`,
|
|
356
|
+
}
|
|
357
|
+
: {}),
|
|
358
|
+
},
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
} finally {
|
|
362
|
+
db?.close()
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
await appendRolloutLines(opts.ledgerPath, lines)
|
|
367
|
+
log(`rollout-ledger: +${lines.length} line(s) (cell ${args.iid} r${args.rep}) → ${opts.ledgerPath}`)
|
|
368
|
+
return { lines: lines.length }
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
async captureProposer(args: ProposerCaptureArgs): Promise<{ lines: number }> {
|
|
372
|
+
const capturedAt = now().toISOString()
|
|
373
|
+
const reward = proposerRewardV2(args.candResolved, args.baselineResolved, opts.instanceCount)
|
|
374
|
+
const line: RolloutLine = {
|
|
375
|
+
...base(capturedAt),
|
|
376
|
+
rollout_id: randomUUID(),
|
|
377
|
+
parent_rollout_id: null,
|
|
378
|
+
candidate_id: candidateId(args.generation, args.candidateIndex),
|
|
379
|
+
generation: args.generation,
|
|
380
|
+
candidate_index: args.candidateIndex,
|
|
381
|
+
role: 'proposer',
|
|
382
|
+
task: {
|
|
383
|
+
suite: 'swe-arena-proposer',
|
|
384
|
+
instance_id: `${candidateId(args.generation, args.candidateIndex)}-${args.proposer}`,
|
|
385
|
+
split: 'search',
|
|
386
|
+
seed: null,
|
|
387
|
+
rep: 0,
|
|
388
|
+
},
|
|
389
|
+
policy: {
|
|
390
|
+
harness: args.harness,
|
|
391
|
+
harness_version: null,
|
|
392
|
+
model: null,
|
|
393
|
+
provider: null,
|
|
394
|
+
profile_commit: args.commit,
|
|
395
|
+
sampling: null,
|
|
396
|
+
},
|
|
397
|
+
messages: [],
|
|
398
|
+
tool_defs: [],
|
|
399
|
+
outcome: {
|
|
400
|
+
reward,
|
|
401
|
+
reward_source: PROPOSER_REWARD_SOURCE_V2,
|
|
402
|
+
verdict: null,
|
|
403
|
+
metrics: {
|
|
404
|
+
resolved_count: args.candResolved,
|
|
405
|
+
baseline_resolved_count: args.baselineResolved,
|
|
406
|
+
instance_count: opts.instanceCount,
|
|
407
|
+
proposer: args.proposer,
|
|
408
|
+
shot_receipts: args.shotReceiptPaths,
|
|
409
|
+
},
|
|
410
|
+
is_completed: true,
|
|
411
|
+
is_truncated: false,
|
|
412
|
+
error: null,
|
|
413
|
+
},
|
|
414
|
+
cost: { usd: null, tokens_in: null, tokens_out: null, tokens_reasoning: null, cache_read: null, cache_write: null, wall_s: null },
|
|
415
|
+
artifacts: {
|
|
416
|
+
patch_path: args.diffPath !== null && existsSync(args.diffPath) ? args.diffPath : null,
|
|
417
|
+
run_dir: null,
|
|
418
|
+
transcript_ref: args.shotReceiptPaths[0] ?? null,
|
|
419
|
+
},
|
|
420
|
+
provenance: {
|
|
421
|
+
captured_at: capturedAt,
|
|
422
|
+
capture: 'settle-time',
|
|
423
|
+
gap: 'proposer transcript joined at backfill time (settle-time line carries the v2 reward label)',
|
|
424
|
+
},
|
|
425
|
+
}
|
|
426
|
+
await appendRolloutLines(opts.ledgerPath, [line])
|
|
427
|
+
log(`rollout-ledger: +1 proposer line (${args.proposer}, reward ${reward.toFixed(3)}) → ${opts.ledgerPath}`)
|
|
428
|
+
return { lines: 1 }
|
|
429
|
+
},
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Parse (generation, candidateIndex) off a campaign cell artifact path — the
|
|
434
|
+
* campaign DIRECTORY is the blessed attribution source (never dispatch
|
|
435
|
+
* order): `<improveRunDir>/gen-<g>/candidate-<k>/<cell>/…` for candidates,
|
|
436
|
+
* `<improveRunDir>/baseline/<cell>/…` (→ −1/−1) for the baseline. */
|
|
437
|
+
export function campaignCoordsFromCellPath(path: string): { generation: number; candidateIndex: number } | null {
|
|
438
|
+
const cand = /\/gen-(\d+)\/candidate-(\d+)\//.exec(path)
|
|
439
|
+
if (cand !== null) return { generation: Number(cand[1]), candidateIndex: Number(cand[2]) }
|
|
440
|
+
if (/\/baseline\//.test(path)) return { generation: -1, candidateIndex: -1 }
|
|
441
|
+
return null
|
|
442
|
+
}
|