@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,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crash-orphan ledger reconciliation: a synthetic durable ledger with one
|
|
3
|
+
* orphaned pending call (crashed process — call opened, no receipt) and one
|
|
4
|
+
* resolved call. Reconciliation must settle ONLY the orphan, as a $0
|
|
5
|
+
* failure receipt tagged 'process-crash-orphan', after which the ledger's
|
|
6
|
+
* fail-closed admission guard passes new paid work again — the exact resume
|
|
7
|
+
* path improve() rides.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
|
|
11
|
+
import { tmpdir } from 'node:os'
|
|
12
|
+
import { join } from 'node:path'
|
|
13
|
+
import { describe, expect, it } from 'vitest'
|
|
14
|
+
import type { CostReceipt, PendingCostCallView } from '@tangle-network/agent-eval'
|
|
15
|
+
import { createRunCostLedger, fsCampaignStorage } from '@tangle-network/agent-eval/campaign'
|
|
16
|
+
import {
|
|
17
|
+
CRASH_ORPHAN_REASON,
|
|
18
|
+
reconcileCrashOrphanCalls,
|
|
19
|
+
reconcileCrashOrphansOnDisk,
|
|
20
|
+
type OrphanReconcilableLedger,
|
|
21
|
+
} from './ledger-orphans.mts'
|
|
22
|
+
|
|
23
|
+
const ATTRIBUTION = { channel: 'agent', phase: 'search.baseline', model: 'm' }
|
|
24
|
+
|
|
25
|
+
/** A durable event line in the exact shape cost-ledger.jsonl persists. */
|
|
26
|
+
const line = (record: Record<string, unknown>): string =>
|
|
27
|
+
`${JSON.stringify({ version: 1, record })}\n`
|
|
28
|
+
|
|
29
|
+
/** Write a synthetic ledger: one settled pair + one crash-orphaned pending. */
|
|
30
|
+
function writeCrashedLedger(dir: string): void {
|
|
31
|
+
const settledPending = {
|
|
32
|
+
status: 'pending',
|
|
33
|
+
callId: 'settled-1',
|
|
34
|
+
...ATTRIBUTION,
|
|
35
|
+
actor: 'worker:astropy#r0',
|
|
36
|
+
timestamp: 1_000,
|
|
37
|
+
}
|
|
38
|
+
const settledReceipt = {
|
|
39
|
+
...settledPending,
|
|
40
|
+
status: 'settled',
|
|
41
|
+
inputTokens: 10,
|
|
42
|
+
outputTokens: 5,
|
|
43
|
+
costUsd: 0.01,
|
|
44
|
+
costUnknown: false,
|
|
45
|
+
actualCostUsd: 0.01,
|
|
46
|
+
}
|
|
47
|
+
const orphanPending = {
|
|
48
|
+
status: 'pending',
|
|
49
|
+
callId: 'orphan-1',
|
|
50
|
+
...ATTRIBUTION,
|
|
51
|
+
actor: 'worker:xarray#r0',
|
|
52
|
+
timestamp: 2_000,
|
|
53
|
+
tags: { cellId: 'pydata__xarray-4687:0' },
|
|
54
|
+
}
|
|
55
|
+
writeFileSync(
|
|
56
|
+
join(dir, 'cost-ledger.jsonl'),
|
|
57
|
+
line(settledPending) + line(settledReceipt) + line(orphanPending),
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const openLedger = (dir: string) =>
|
|
62
|
+
createRunCostLedger({ storage: fsCampaignStorage(), runDir: dir })
|
|
63
|
+
|
|
64
|
+
describe('reconcileCrashOrphansOnDisk', () => {
|
|
65
|
+
it('settles only the orphan, and the admission guard passes afterwards', async () => {
|
|
66
|
+
const dir = mkdtempSync(join(tmpdir(), 'ledger-orphans-'))
|
|
67
|
+
writeCrashedLedger(dir)
|
|
68
|
+
|
|
69
|
+
// The bug being fixed: with the orphan unresolved, ALL new paid work is refused.
|
|
70
|
+
const blocked = await openLedger(dir).runPaidCall({
|
|
71
|
+
...ATTRIBUTION,
|
|
72
|
+
actor: 'worker:next',
|
|
73
|
+
execute: async () => 'ok',
|
|
74
|
+
receipt: () => ({ model: 'm', inputTokens: 1, outputTokens: 1, actualCostUsd: 0 }),
|
|
75
|
+
})
|
|
76
|
+
expect(blocked.succeeded).toBe(false)
|
|
77
|
+
if (!blocked.succeeded) {
|
|
78
|
+
expect(blocked.error.message).toContain('unresolved call(s) must be reconciled')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const receipts = reconcileCrashOrphansOnDisk(dir)
|
|
82
|
+
expect(receipts.map((r) => r.callId)).toEqual(['orphan-1'])
|
|
83
|
+
expect(receipts[0]).toMatchObject({
|
|
84
|
+
status: 'settled',
|
|
85
|
+
error: CRASH_ORPHAN_REASON,
|
|
86
|
+
inputTokens: 0,
|
|
87
|
+
outputTokens: 0,
|
|
88
|
+
costUsd: 0,
|
|
89
|
+
costUnknown: false,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Durable: a fresh ledger instance sees zero unresolved calls, the
|
|
93
|
+
// settled call untouched, and admits new paid work again.
|
|
94
|
+
const reopened = openLedger(dir)
|
|
95
|
+
const summary = reopened.summary()
|
|
96
|
+
expect(summary.unresolvedCalls).toBe(0)
|
|
97
|
+
expect(summary.pendingCalls).toBe(0)
|
|
98
|
+
expect(summary.accountingComplete).toBe(true)
|
|
99
|
+
const settled = reopened.list().find((r) => r.callId === 'settled-1')
|
|
100
|
+
expect(settled?.costUsd).toBe(0.01)
|
|
101
|
+
expect(settled?.error).toBeUndefined()
|
|
102
|
+
const admitted = await reopened.runPaidCall({
|
|
103
|
+
...ATTRIBUTION,
|
|
104
|
+
actor: 'worker:next',
|
|
105
|
+
execute: async () => 'ok',
|
|
106
|
+
receipt: () => ({ model: 'm', inputTokens: 1, outputTokens: 1, actualCostUsd: 0 }),
|
|
107
|
+
})
|
|
108
|
+
expect(admitted.succeeded).toBe(true)
|
|
109
|
+
|
|
110
|
+
// The durable log carries the orphan's failure receipt verbatim.
|
|
111
|
+
const persisted = readFileSync(join(dir, 'cost-ledger.jsonl'), 'utf8')
|
|
112
|
+
expect(persisted).toContain(CRASH_ORPHAN_REASON)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('is idempotent — a second startup pass finds nothing to settle', () => {
|
|
116
|
+
const dir = mkdtempSync(join(tmpdir(), 'ledger-orphans-'))
|
|
117
|
+
writeCrashedLedger(dir)
|
|
118
|
+
expect(reconcileCrashOrphansOnDisk(dir)).toHaveLength(1)
|
|
119
|
+
expect(reconcileCrashOrphansOnDisk(dir)).toHaveLength(0)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('is a no-op on a fresh run dir with no ledger file', () => {
|
|
123
|
+
const dir = mkdtempSync(join(tmpdir(), 'ledger-orphans-'))
|
|
124
|
+
expect(reconcileCrashOrphansOnDisk(dir)).toEqual([])
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('reconcileCrashOrphanCalls', () => {
|
|
129
|
+
it("never settles a live in-process call — only 'interrupted' pendings", () => {
|
|
130
|
+
const base = { ...ATTRIBUTION, status: 'pending' as const, timestamp: 1 }
|
|
131
|
+
const pendings: PendingCostCallView[] = [
|
|
132
|
+
{ ...base, callId: 'live-1', actor: 'a', state: 'active' },
|
|
133
|
+
{ ...base, callId: 'late-1', actor: 'b', state: 'late' },
|
|
134
|
+
{ ...base, callId: 'orphan-1', actor: 'c', state: 'interrupted' },
|
|
135
|
+
]
|
|
136
|
+
const settled: string[] = []
|
|
137
|
+
const ledger: OrphanReconcilableLedger = {
|
|
138
|
+
listPending: () => pendings,
|
|
139
|
+
reconcile: (callId) => {
|
|
140
|
+
settled.push(callId)
|
|
141
|
+
return { callId } as CostReceipt
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
reconcileCrashOrphanCalls(ledger)
|
|
145
|
+
expect(settled).toEqual(['orphan-1'])
|
|
146
|
+
})
|
|
147
|
+
})
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gen-5 lineage DAG wiring (SOTA adoption #3) — the hand-rolled parent fields
|
|
3
|
+
* stop being the only ancestry record: every candidate becomes a
|
|
4
|
+
* `LineageNode` in agent-eval's Lineage DAG (campaign/lineage.ts —
|
|
5
|
+
* multi-parent merges, tracks, deterministic content-hash ids, append-only
|
|
6
|
+
* fsLineageStore), every generation appends, and the library's
|
|
7
|
+
* `heuristicGovernor` reads the graph to decide per-generation continuation
|
|
8
|
+
* (extend the winner / branch on stall / merge frontier tips / prune). The
|
|
9
|
+
* governor's decision is RECORDED in the round summary — the operator (or a
|
|
10
|
+
* future automated outer loop) acts on it; this run never acts on it itself.
|
|
11
|
+
*
|
|
12
|
+
* Store location: `<outDir>/.evolve/lineage.jsonl` (an .evolve-compatible
|
|
13
|
+
* path). The existing staircase rows are UNCHANGED and still written — the
|
|
14
|
+
* observatory depends on them; the DAG is additive.
|
|
15
|
+
*
|
|
16
|
+
* Node semantics:
|
|
17
|
+
* - the BASELINE incumbent is the root (track 'baseline', proposer
|
|
18
|
+
* 'baseline'),
|
|
19
|
+
* - configured Pareto parents (prior-run frontier commits) become nodes off
|
|
20
|
+
* the root under their own tracks, so a merge can name them as parents,
|
|
21
|
+
* - each evaluated candidate is a node under its proposer-named track:
|
|
22
|
+
* single-parent (root) for regular authors, MULTI-PARENT (the Pareto
|
|
23
|
+
* parent nodes) for the merge seat,
|
|
24
|
+
* - score = combined resolved fraction; scoreVector = per-instance
|
|
25
|
+
* fail-closed AND-verdicts over the lexicographically sorted instance ids.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { join } from 'node:path'
|
|
29
|
+
import {
|
|
30
|
+
Lineage,
|
|
31
|
+
fsLineageStore,
|
|
32
|
+
heuristicGovernor,
|
|
33
|
+
lineageNodeId,
|
|
34
|
+
type GovernorOp,
|
|
35
|
+
type LineageNode,
|
|
36
|
+
} from '@tangle-network/agent-eval/campaign'
|
|
37
|
+
|
|
38
|
+
export const LINEAGE_STORE_RELPATH = join('.evolve', 'lineage.jsonl')
|
|
39
|
+
|
|
40
|
+
export interface LineageCandidateInput {
|
|
41
|
+
/** Proposer name — the node's track and proposer. */
|
|
42
|
+
label: string
|
|
43
|
+
/** Loops commit; null (never-committed candidate) is skipped with a note. */
|
|
44
|
+
commit: string | null
|
|
45
|
+
resolvedCount: number
|
|
46
|
+
/** Per-instance AND-verdicts (missing instance = fail-closed false). */
|
|
47
|
+
verdicts: Record<string, boolean>
|
|
48
|
+
merge: boolean
|
|
49
|
+
/** Staircase verdict — recorded as the node's rationale. */
|
|
50
|
+
verdict: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LineageRecordArgs {
|
|
54
|
+
outDir: string
|
|
55
|
+
runId: string
|
|
56
|
+
instances: readonly string[]
|
|
57
|
+
baseline: { commit: string; resolvedCount: number; verdicts: Record<string, boolean> }
|
|
58
|
+
paretoParents: Array<{ label: string; commit: string; resolvedInstances: string[] }>
|
|
59
|
+
candidates: LineageCandidateInput[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface LineageRecordResult {
|
|
63
|
+
path: string
|
|
64
|
+
/** Node ids appended by THIS call (idempotent re-runs append zero). */
|
|
65
|
+
appended: string[]
|
|
66
|
+
/** Nodes skipped because the candidate never became a commit. */
|
|
67
|
+
skipped: string[]
|
|
68
|
+
governor: GovernorOp
|
|
69
|
+
nodesTotal: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const scoreVectorOf = (instances: readonly string[], verdicts: Record<string, boolean>): number[] =>
|
|
73
|
+
[...instances].sort().map((iid) => (verdicts[iid] === true ? 1 : 0))
|
|
74
|
+
|
|
75
|
+
const scoreOf = (resolvedCount: number, instanceCount: number): number =>
|
|
76
|
+
instanceCount > 0 ? resolvedCount / instanceCount : 0
|
|
77
|
+
|
|
78
|
+
/** Record one generation into the lineage DAG and ask the governor for the
|
|
79
|
+
* continuation decision. Append-only + idempotent: node ids are content
|
|
80
|
+
* hashes, so re-recording an identical generation appends nothing. */
|
|
81
|
+
export async function recordLineageGeneration(args: LineageRecordArgs): Promise<LineageRecordResult> {
|
|
82
|
+
const path = join(args.outDir, LINEAGE_STORE_RELPATH)
|
|
83
|
+
const store = fsLineageStore(path)
|
|
84
|
+
const lineage = await store.load()
|
|
85
|
+
const appended: string[] = []
|
|
86
|
+
const skipped: string[] = []
|
|
87
|
+
|
|
88
|
+
const ensure = async (input: Parameters<Lineage['addNode']>[0]): Promise<LineageNode> => {
|
|
89
|
+
const id = lineageNodeId({
|
|
90
|
+
parentIds: input.parentIds,
|
|
91
|
+
track: input.track,
|
|
92
|
+
surface: input.surface,
|
|
93
|
+
proposer: input.proposer,
|
|
94
|
+
})
|
|
95
|
+
const existed = lineage.all().some((n) => n.id === id)
|
|
96
|
+
const node = lineage.addNode(input)
|
|
97
|
+
if (!existed) {
|
|
98
|
+
await store.append(node)
|
|
99
|
+
appended.push(node.id)
|
|
100
|
+
}
|
|
101
|
+
return node
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const n = args.instances.length
|
|
105
|
+
const root = await ensure({
|
|
106
|
+
parentIds: [],
|
|
107
|
+
track: 'baseline',
|
|
108
|
+
surface: `loops@${args.baseline.commit}`,
|
|
109
|
+
score: scoreOf(args.baseline.resolvedCount, n),
|
|
110
|
+
scoreVector: scoreVectorOf(args.instances, args.baseline.verdicts),
|
|
111
|
+
proposer: 'baseline',
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Prior-run frontier commits become referenceable parent nodes.
|
|
115
|
+
const parentNodeByLabel = new Map<string, LineageNode>()
|
|
116
|
+
for (const parent of args.paretoParents) {
|
|
117
|
+
const verdicts: Record<string, boolean> = {}
|
|
118
|
+
for (const iid of parent.resolvedInstances) verdicts[iid] = true
|
|
119
|
+
const node = await ensure({
|
|
120
|
+
parentIds: [root.id],
|
|
121
|
+
track: parent.label,
|
|
122
|
+
surface: `loops@${parent.commit}`,
|
|
123
|
+
score: scoreOf(parent.resolvedInstances.length, n),
|
|
124
|
+
scoreVector: scoreVectorOf(args.instances, verdicts),
|
|
125
|
+
proposer: parent.label,
|
|
126
|
+
})
|
|
127
|
+
parentNodeByLabel.set(parent.label, node)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const cand of args.candidates) {
|
|
131
|
+
if (cand.commit === null) {
|
|
132
|
+
skipped.push(cand.label)
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
const parentIds = cand.merge
|
|
136
|
+
? [...parentNodeByLabel.values()].map((p) => p.id)
|
|
137
|
+
: [root.id]
|
|
138
|
+
if (cand.merge && parentIds.length < 2) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`lineage: merge candidate ${cand.label} needs >=2 pareto parent nodes, got ${parentIds.length}`,
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
await ensure({
|
|
144
|
+
parentIds,
|
|
145
|
+
track: cand.label,
|
|
146
|
+
surface: `loops@${cand.commit}`,
|
|
147
|
+
score: scoreOf(cand.resolvedCount, n),
|
|
148
|
+
scoreVector: scoreVectorOf(args.instances, cand.verdicts),
|
|
149
|
+
proposer: cand.merge ? 'merge' : cand.label,
|
|
150
|
+
rationale: `run ${args.runId}: ${cand.verdict}`,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// The continuation decision — recorded, not acted on here. budgetRemaining=1:
|
|
155
|
+
// the operator approves one next generation at a time.
|
|
156
|
+
const governor = await heuristicGovernor().decide({
|
|
157
|
+
lineage,
|
|
158
|
+
step: lineage.all().length,
|
|
159
|
+
budgetRemaining: 1,
|
|
160
|
+
prunedTracks: [],
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
return { path, appended, skipped, governor, nodesTotal: lineage.all().length }
|
|
164
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gen-5 lineage DAG wiring: baseline root + pareto-parent nodes + candidate
|
|
3
|
+
* nodes (multi-parent merge), append-only idempotence at the
|
|
4
|
+
* .evolve-compatible store, and the recorded governor decision.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFile, rm } from 'node:fs/promises'
|
|
8
|
+
import { mkdtemp } from 'node:fs/promises'
|
|
9
|
+
import { tmpdir } from 'node:os'
|
|
10
|
+
import { join } from 'node:path'
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
12
|
+
import { Lineage } from '@tangle-network/agent-eval/campaign'
|
|
13
|
+
import { LINEAGE_STORE_RELPATH, recordLineageGeneration, type LineageRecordArgs } from './lineage-record.mts'
|
|
14
|
+
|
|
15
|
+
const SIX = ['a-1', 'b-2', 'c-3', 'd-4', 'e-5', 'f-6']
|
|
16
|
+
|
|
17
|
+
describe('recordLineageGeneration', () => {
|
|
18
|
+
let outDir: string
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
outDir = await mkdtemp(join(tmpdir(), 'lineage-rec-'))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
await rm(outDir, { recursive: true, force: true })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const args = (): LineageRecordArgs => ({
|
|
29
|
+
outDir,
|
|
30
|
+
runId: 'r4-test',
|
|
31
|
+
instances: SIX,
|
|
32
|
+
baseline: { commit: 'base'.repeat(10), resolvedCount: 1, verdicts: { 'a-1': true } },
|
|
33
|
+
paretoParents: [
|
|
34
|
+
{ label: 'default-author', commit: 'p1p1'.repeat(10), resolvedInstances: ['d-4', 'f-6'] },
|
|
35
|
+
{ label: 'prompts-author', commit: 'p2p2'.repeat(10), resolvedInstances: ['b-2', 'd-4'] },
|
|
36
|
+
],
|
|
37
|
+
candidates: [
|
|
38
|
+
{
|
|
39
|
+
label: 'claude-author',
|
|
40
|
+
commit: 'c1c1'.repeat(10),
|
|
41
|
+
resolvedCount: 2,
|
|
42
|
+
verdicts: { 'a-1': true, 'd-4': true },
|
|
43
|
+
merge: false,
|
|
44
|
+
verdict: 'accepted',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'merge-author',
|
|
48
|
+
commit: 'c2c2'.repeat(10),
|
|
49
|
+
resolvedCount: 3,
|
|
50
|
+
verdicts: { 'a-1': true, 'b-2': true, 'd-4': true },
|
|
51
|
+
merge: true,
|
|
52
|
+
verdict: 'accepted',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: 'killed-author',
|
|
56
|
+
commit: null,
|
|
57
|
+
resolvedCount: 0,
|
|
58
|
+
verdicts: {},
|
|
59
|
+
merge: false,
|
|
60
|
+
verdict: 'rejected-prefilter',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('records root + parents + candidates at the .evolve-compatible store; merge is MULTI-PARENT', async () => {
|
|
66
|
+
const result = await recordLineageGeneration(args())
|
|
67
|
+
expect(result.path).toBe(join(outDir, LINEAGE_STORE_RELPATH))
|
|
68
|
+
expect(result.path).toContain('.evolve')
|
|
69
|
+
// root + 2 parents + 2 committed candidates (killed-author skipped).
|
|
70
|
+
expect(result.nodesTotal).toBe(5)
|
|
71
|
+
expect(result.appended).toHaveLength(5)
|
|
72
|
+
expect(result.skipped).toEqual(['killed-author'])
|
|
73
|
+
|
|
74
|
+
const raw = await readFile(result.path, 'utf8')
|
|
75
|
+
const lineage = Lineage.fromJSONL(raw)
|
|
76
|
+
const root = lineage.roots()
|
|
77
|
+
expect(root).toHaveLength(1)
|
|
78
|
+
expect(root[0]!.track).toBe('baseline')
|
|
79
|
+
expect(root[0]!.score).toBeCloseTo(1 / 6)
|
|
80
|
+
|
|
81
|
+
const merge = lineage.all().find((n) => n.track === 'merge-author')!
|
|
82
|
+
expect(merge.parentIds).toHaveLength(2)
|
|
83
|
+
const parentTracks = merge.parentIds.map((id) => lineage.all().find((n) => n.id === id)!.track).sort()
|
|
84
|
+
expect(parentTracks).toEqual(['default-author', 'prompts-author'])
|
|
85
|
+
expect(merge.proposer).toBe('merge')
|
|
86
|
+
expect(merge.score).toBeCloseTo(3 / 6)
|
|
87
|
+
|
|
88
|
+
const single = lineage.all().find((n) => n.track === 'claude-author')!
|
|
89
|
+
expect(single.parentIds).toEqual([root[0]!.id])
|
|
90
|
+
expect(single.rationale).toContain('accepted')
|
|
91
|
+
// scoreVector: sorted instance order, fail-closed 0 for missing verdicts.
|
|
92
|
+
expect(single.scoreVector).toEqual([1, 0, 0, 1, 0, 0])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('is idempotent: re-recording the identical generation appends ZERO nodes', async () => {
|
|
96
|
+
const first = await recordLineageGeneration(args())
|
|
97
|
+
const second = await recordLineageGeneration(args())
|
|
98
|
+
expect(first.appended).toHaveLength(5)
|
|
99
|
+
expect(second.appended).toHaveLength(0)
|
|
100
|
+
expect(second.nodesTotal).toBe(5)
|
|
101
|
+
const raw = await readFile(second.path, 'utf8')
|
|
102
|
+
expect(raw.trim().split('\n')).toHaveLength(5)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('returns a governor continuation decision (recorded, never acted on here)', async () => {
|
|
106
|
+
const result = await recordLineageGeneration(args())
|
|
107
|
+
expect(['extend', 'branch', 'merge', 'prune', 'stop']).toContain(result.governor.op)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('fails loud when a merge candidate has fewer than 2 parent nodes', async () => {
|
|
111
|
+
const bad = args()
|
|
112
|
+
bad.paretoParents = bad.paretoParents.slice(0, 1)
|
|
113
|
+
await expect(recordLineageGeneration(bad)).rejects.toThrow(/needs >=2/)
|
|
114
|
+
})
|
|
115
|
+
})
|