@tangle-network/agent-bench 0.7.0 → 0.8.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.
Files changed (131) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/HARNESS.md +1 -1
  3. package/dist/adapters.js +2 -2
  4. package/dist/benchmarks/appworld.d.ts +61 -2
  5. package/dist/benchmarks/appworld.js +111 -40
  6. package/dist/benchmarks/appworld.js.map +1 -1
  7. package/dist/benchmarks/cadbench.js +1 -1
  8. package/dist/benchmarks/cadgenbench.js +1 -1
  9. package/dist/benchmarks/finresearchbench.js +16 -22
  10. package/dist/benchmarks/finresearchbench.js.map +1 -1
  11. package/dist/benchmarks/finsearchcomp.js +16 -22
  12. package/dist/benchmarks/finsearchcomp.js.map +1 -1
  13. package/dist/benchmarks/frames.js +18 -20
  14. package/dist/benchmarks/frames.js.map +1 -1
  15. package/dist/benchmarks/simpleqa.js +15 -19
  16. package/dist/benchmarks/simpleqa.js.map +1 -1
  17. package/dist/benchmarks/trata-hedge.js +20 -23
  18. package/dist/benchmarks/trata-hedge.js.map +1 -1
  19. package/dist/{cadbench-BrpwOU6A.js → cadbench-BLSyxR1N.js} +21 -20
  20. package/dist/cadbench-BLSyxR1N.js.map +1 -0
  21. package/dist/{cadgenbench-DF7hYHdl.js → cadgenbench-x2OFkf8y.js} +2 -2
  22. package/dist/cadgenbench-x2OFkf8y.js.map +1 -0
  23. package/dist/index.d.ts +0 -1
  24. package/dist/index.js +10 -9
  25. package/dist/index.js.map +1 -1
  26. package/dist/router-turn-C2wMiDoo.js +31 -0
  27. package/dist/router-turn-C2wMiDoo.js.map +1 -0
  28. package/package.json +7 -7
  29. package/scripts/appworld_driver.py +1 -170
  30. package/scripts/run-package-tests.mjs +56 -36
  31. package/scripts/run-package-tests.test.mjs +33 -0
  32. package/scripts/trata-hedge/README.md +3 -3
  33. package/scripts/trata-hedge/run.sh +6 -2
  34. package/src/aec-gate.mts +30 -9
  35. package/src/agent-graphs-gen2.mts +523 -0
  36. package/src/agent-graphs-gen3.mts +660 -0
  37. package/src/agent-graphs-improve/offline-seams.mts +128 -0
  38. package/src/agent-graphs-improve.mts +747 -0
  39. package/src/atom-humaneval.mts +76 -55
  40. package/src/atom-mcp-e2e.mts +27 -13
  41. package/src/benchmarks/appworld.test.mts +119 -1
  42. package/src/benchmarks/appworld.ts +156 -33
  43. package/src/benchmarks/cadbench.ts +21 -8
  44. package/src/benchmarks/finresearchbench.ts +20 -16
  45. package/src/benchmarks/finsearchcomp.ts +20 -20
  46. package/src/benchmarks/frames.ts +19 -18
  47. package/src/benchmarks/simpleqa.ts +19 -17
  48. package/src/benchmarks/trata-hedge.ts +26 -18
  49. package/src/browser/ui-reviewer.ts +1 -1
  50. package/src/clbench-context-gate.mts +45 -9
  51. package/src/cloud-loop.mts +24 -6
  52. package/src/commit0-env-run.mts +6 -3
  53. package/src/commit0-env.ts +2 -3
  54. package/src/commit0-gate.mts +53 -24
  55. package/src/corpus.ts +3 -3
  56. package/src/david-attribution.mts +28 -9
  57. package/src/david-goliath.mts +31 -12
  58. package/src/egress-probe.mts +3 -3
  59. package/src/examples/lean-proof-gate.mts +10 -3
  60. package/src/examples/math-demo.mts +10 -3
  61. package/src/examples/strategy-demo.mts +10 -4
  62. package/src/fleet.mts +30 -8
  63. package/src/gate-cli.mts +9 -4
  64. package/src/gate.test.mts +7 -2
  65. package/src/gate.ts +23 -75
  66. package/src/generate-eval/certify.ts +13 -5
  67. package/src/hev-eval.mts +39 -10
  68. package/src/hev-improve.mts +28 -16
  69. package/src/hev-structural.mts +28 -20
  70. package/src/humaneval-gate.mts +24 -8
  71. package/src/humaneval-object-ablation.mts +52 -14
  72. package/src/humaneval-repair-gate.mts +53 -14
  73. package/src/mbpp-structural.mts +28 -20
  74. package/src/mcp-mount-probe.mts +23 -11
  75. package/src/official-optimizer-config.mts +61 -12
  76. package/src/official-optimizer-config.test.mts +72 -3
  77. package/src/quant-arena/profiles/quant-researcher.profile.json +6 -1
  78. package/src/quant-arena/quant-loop.mts +88 -61
  79. package/src/research-shot.ts +21 -11
  80. package/src/resolve-client.ts +6 -8
  81. package/src/rollout-ledger/backfill-swe-arena.test.mts +7 -2
  82. package/src/router-executor.ts +1 -1
  83. package/src/router-turn.ts +135 -0
  84. package/src/run-benchmarks.ts +7 -2
  85. package/src/sandbox-run.ts +23 -41
  86. package/src/search-bench/bridge.ts +66 -8
  87. package/src/search-bench/parametric-check.mts +19 -4
  88. package/src/search-bench/profiles.ts +24 -9
  89. package/src/search-bench/run.mts +3 -4
  90. package/src/smoke-structural-rollout.mts +21 -5
  91. package/src/supervisor-arena.mts +28 -20
  92. package/src/swe-arena/arms.ts +73 -15
  93. package/src/swe-arena/capacity.ts +44 -41
  94. package/src/swe-arena/diagnosis-ensemble.ts +51 -48
  95. package/src/swe-arena/execution.test.mts +12 -12
  96. package/src/swe-arena/gepa-seat.mts +12 -5
  97. package/src/swe-arena/gepa-seat.test.mts +45 -33
  98. package/src/swe-arena/outer-loop.mts +94 -70
  99. package/src/swe-arena/premeasured-from-cells.mts +16 -1
  100. package/src/swe-arena/premeasured-from-cells.test.mts +21 -1
  101. package/src/swe-arena/profiles/deepseek-author.profile.json +12 -0
  102. package/src/swe-arena/profiles/default-author.profile.json +9 -1
  103. package/src/swe-arena/proposer-fanout.mts +52 -99
  104. package/src/swe-arena/proposer-fanout.test.mts +92 -57
  105. package/src/swe-arena/proposer-provenance.mts +1 -1
  106. package/src/swe-code-improve.mts +30 -52
  107. package/src/swe-emit-patch.mts +26 -5
  108. package/src/swe-improve.mts +27 -8
  109. package/src/swe-jail.test.ts +128 -0
  110. package/src/swe-jail.ts +106 -22
  111. package/src/swe-local-proof.mts +27 -5
  112. package/src/swe-repro-calibrate.mts +5 -0
  113. package/src/swe-self-improve.mts +45 -10
  114. package/src/swe-stream.mts +68 -6
  115. package/src/swe-structural.mts +46 -7
  116. package/src/tb-container-executor.mts +38 -6
  117. package/src/tb-container-executor.test.mts +11 -3
  118. package/src/tb-supervisor-sidecar.mts +11 -27
  119. package/src/terminal-compare.ts +38 -18
  120. package/src/trata-gate.mts +26 -26
  121. package/src/trata-gepa.mts +25 -16
  122. package/src/trata-hedge-solve.mts +80 -0
  123. package/src/worker-blender.ts +21 -11
  124. package/src/worker-browser.ts +18 -5
  125. package/src/worker-build123d.ts +17 -8
  126. package/src/worker-cad.ts +31 -12
  127. package/src/worker.ts +1 -1
  128. package/tb_agents/opencode_router_agent.py +84 -42
  129. package/dist/cadbench-BrpwOU6A.js.map +0 -1
  130. package/dist/cadgenbench-DF7hYHdl.js.map +0 -1
  131. package/scripts/trata-hedge/solve.py +0 -83
@@ -69,10 +69,10 @@ import {
69
69
  improve,
70
70
  rawTraceDistiller,
71
71
  type CandidateGenerator,
72
+ type ImproveCodeRunOptions,
72
73
  type Verifier,
73
74
  } from '@tangle-network/agent-runtime'
74
- import { runLocalHarness } from '@tangle-network/agent-runtime/mcp'
75
- import { canonicalCandidateDigest } from '@tangle-network/agent-interface'
75
+ import { canonicalCandidateDigest, type AgentProfile } from '@tangle-network/agent-interface'
76
76
  import {
77
77
  makeProposalFinding,
78
78
  type AnalystFinding,
@@ -89,7 +89,6 @@ import {
89
89
  type PremeasuredOptimizationBaseline,
90
90
  type Scenario,
91
91
  } from '@tangle-network/agent-eval/campaign'
92
- import type { CostLedgerHandle } from '@tangle-network/agent-eval'
93
92
  import { runVenvPython } from '../benchmarks/_harness.ts'
94
93
  import { createSweBenchAdapter } from '../benchmarks/swe-bench.ts'
95
94
  import {
@@ -134,6 +133,7 @@ import {
134
133
  fanOutLoopsGenerator,
135
134
  materializeParetoParents,
136
135
  proposerShotHooks,
136
+ resolveAuthorProfile,
137
137
  type ParetoParentContext,
138
138
  type ParetoParentSeed,
139
139
  type PrefilterConfig,
@@ -646,7 +646,9 @@ export interface OuterLoopConfig {
646
646
  * budget.maxImprovementShots; the LIB owns the dial (capabilities.mts
647
647
  * fails loud on a substrate that would drop it). */
648
648
  maxShots: number
649
- proposerHarness: 'claude-code' | 'codex' | 'opencode'
649
+ proposerHarness: 'pi'
650
+ /** Complete profile used by the single-author path and as the run's admitted author identity. */
651
+ proposerProfile: string
650
652
  proposerTimeoutMs: number
651
653
  /** GEN-3 proposer fan-out: N proposers author candidates CONCURRENTLY, each
652
654
  * an AgentProfile-pinned harness invocation (see proposer-fanout.mts).
@@ -759,7 +761,8 @@ export function defaultRound4Config(
759
761
  // bootstrap run writes it; the lib validates it on every consumption.
760
762
  premeasuredBaselinePath: join(hh, 'r4', 'premeasured-baseline.json'),
761
763
  maxShots: 3,
762
- proposerHarness: 'claude-code',
764
+ proposerHarness: 'pi',
765
+ proposerProfile: 'default-author.profile.json',
763
766
  // Per author SHOT (agenticGenerator timeoutMs). 20 min timed out 3× under
764
767
  // degraded capacity in gen-1 ("author shot timed out") — doubled to 40 min.
765
768
  proposerTimeoutMs: 2_400_000,
@@ -836,7 +839,7 @@ export function resolveSmokeInstance(
836
839
  * The gen-3 config: protocol round 4 continues (frozen arm, same holdout
837
840
  * registry, same roundsDir staircase) with the gen-3 machinery on:
838
841
  *
839
- * - THREE parallel proposers (all claude, bare default-author profile) that
842
+ * - THREE parallel Pi proposers using the exact GLM author profile that
840
843
  * differ by diagnosis slice/lens — fan-out diversity without unproven
841
844
  * harness seats; `populationSize` = `proposers.length`.
842
845
  * - Pre-filter enabled at the mechanism bar on the cheapest-of-set smoke
@@ -858,18 +861,25 @@ export function defaultGen3Config(
858
861
  const base = defaultRound4Config(hh, opts)
859
862
  const outDirName = opts.outDirName ?? 'gen3'
860
863
  const proposers: ProposerSpec[] = [
861
- { name: 'default-author', profile: 'default-author.profile.json', harness: 'claude-code' },
864
+ {
865
+ name: 'default-author',
866
+ profile: 'default-author.profile.json',
867
+ harness: 'pi',
868
+ model: 'glm-5.2',
869
+ },
862
870
  {
863
871
  name: 'mechanics-author',
864
872
  profile: 'default-author.profile.json',
865
- harness: 'claude-code',
873
+ harness: 'pi',
874
+ model: 'glm-5.2',
866
875
  diagnosisSlice: 'mechanics',
867
876
  lens: 'Focus on MECHANICS: worker lifecycle, sandbox/clone contracts, settlement and delivery paths. Prefer code-path fixes over prompt wording.',
868
877
  },
869
878
  {
870
879
  name: 'prompts-author',
871
880
  profile: 'default-author.profile.json',
872
- harness: 'claude-code',
881
+ harness: 'pi',
882
+ model: 'glm-5.2',
873
883
  diagnosisSlice: 'prompts',
874
884
  lens: 'Focus on PROMPTS: worker/brain instruction wording, placement guidance, self-check discipline. Prefer prompt/instruction changes over code-path rewrites.',
875
885
  },
@@ -921,13 +931,8 @@ export const GEN3_PARETO_PARENTS: ParetoParentSeed[] = [
921
931
  * The gen-4 config: protocol round 4 continues (frozen arm, same holdout
922
932
  * registry, same roundsDir staircase) with three changes as a unit:
923
933
  *
924
- * 1. PINNED PER-PROPOSER MODELS — four seats: claude-author (claude CLI on
925
- * its own login; the resolved model + CLI version are captured into
926
- * `<outDir>/proposer-provenance.json` at t=0), glm-author (opencode
927
- * pinned to zai-coding-plan/glm-5.2 via `-m`), codex-author (codex CLI on
928
- * its ChatGPT login, auth provenance-gated at launch; drop the seat via
929
- * `includeCodex: false` when the CLI is absent), and merge-author (claude,
930
- * merge seat).
934
+ * 1. PINNED PER-PROPOSER MODELS — GLM 5.2 and DeepSeek V4 Flash run through
935
+ * Pi and Tangle Router; the merge seat uses the same exact GLM profile.
931
936
  * 2. PARETO PARENTS — the gen-3 winner + runner-up diffs and their measured
932
937
  * per-instance results seed every author's prompt; the merge seat's task
933
938
  * is their coherent union. Seeded at the buildPrompt seam (our seam): the
@@ -941,14 +946,33 @@ export const GEN3_PARETO_PARENTS: ParetoParentSeed[] = [
941
946
  */
942
947
  export function defaultGen4Config(
943
948
  hh = DEFAULT_HH_SCRATCHPAD,
944
- opts: { outDirName?: string; includeCodex?: boolean } = {},
949
+ opts: { outDirName?: string; includeDeepseek?: boolean } = {},
945
950
  ): OuterLoopConfig {
946
951
  const base = defaultGen3Config(hh, { outDirName: opts.outDirName ?? 'gen4' })
947
952
  const proposers: ProposerSpec[] = [
948
- { name: 'claude-author', profile: 'default-author.profile.json', harness: 'claude-code' },
949
- { name: 'glm-author', harness: 'opencode', model: 'zai-coding-plan/glm-5.2' },
950
- ...(opts.includeCodex === false ? [] : [{ name: 'codex-author', harness: 'codex' } satisfies ProposerSpec]),
951
- { name: 'merge-author', profile: 'default-author.profile.json', harness: 'claude-code', merge: true },
953
+ {
954
+ name: 'glm-author',
955
+ profile: 'default-author.profile.json',
956
+ harness: 'pi',
957
+ model: 'glm-5.2',
958
+ },
959
+ ...(opts.includeDeepseek === false
960
+ ? []
961
+ : [
962
+ {
963
+ name: 'deepseek-author',
964
+ profile: 'deepseek-author.profile.json',
965
+ harness: 'pi',
966
+ model: 'deepseek-v4-flash',
967
+ } satisfies ProposerSpec,
968
+ ]),
969
+ {
970
+ name: 'merge-author',
971
+ profile: 'default-author.profile.json',
972
+ harness: 'pi',
973
+ model: 'glm-5.2',
974
+ merge: true,
975
+ },
952
976
  ]
953
977
  return {
954
978
  ...base,
@@ -979,11 +1003,11 @@ export function defaultGen4Config(
979
1003
 
980
1004
  export function defaultGen5Config(
981
1005
  hh = DEFAULT_HH_SCRATCHPAD,
982
- opts: { outDirName?: string; includeCodex?: boolean } = {},
1006
+ opts: { outDirName?: string; includeDeepseek?: boolean } = {},
983
1007
  ): OuterLoopConfig {
984
1008
  const base = defaultGen4Config(hh, {
985
1009
  outDirName: opts.outDirName ?? 'gen5',
986
- ...(opts.includeCodex !== undefined ? { includeCodex: opts.includeCodex } : {}),
1010
+ ...(opts.includeDeepseek !== undefined ? { includeDeepseek: opts.includeDeepseek } : {}),
987
1011
  })
988
1012
  return {
989
1013
  ...base,
@@ -1261,7 +1285,7 @@ const CLAUDE_AMBIENT_AUTH_VARS = ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', '
1261
1285
  * leaked auth is stripped for the shot subprocess only. */
1262
1286
  const CODEX_AMBIENT_AUTH_VARS = ['OPENAI_API_KEY', 'OPENAI_BASE_URL'] as const
1263
1287
 
1264
- export function proposerShotEnv(harness: OuterLoopConfig['proposerHarness']): NodeJS.ProcessEnv {
1288
+ export function proposerShotEnv(harness: NonNullable<ProposerSpec['harness']>): NodeJS.ProcessEnv {
1265
1289
  const env: NodeJS.ProcessEnv = { ...process.env }
1266
1290
  if (harness === 'claude-code') {
1267
1291
  for (const name of CLAUDE_AMBIENT_AUTH_VARS) delete env[name]
@@ -1272,43 +1296,51 @@ export function proposerShotEnv(harness: OuterLoopConfig['proposerHarness']): No
1272
1296
  return env
1273
1297
  }
1274
1298
 
1299
+ function configuredAuthorProfile(config: OuterLoopConfig): AgentProfile {
1300
+ const spec =
1301
+ config.proposers?.find((proposer) => proposer.engine === undefined) ??
1302
+ ({
1303
+ name: 'single-author',
1304
+ profile: config.proposerProfile,
1305
+ harness: config.proposerHarness,
1306
+ } satisfies ProposerSpec)
1307
+ const profile = resolveAuthorProfile(spec)
1308
+ if (!profile) throw new Error(`author ${spec.name}: an exact AgentProfile is required`)
1309
+ return profile
1310
+ }
1311
+
1312
+ function authorExecutorForWorktree(worktreePath: string) {
1313
+ const bridgeUrl = process.env.CLI_BRIDGE_URL ?? process.env.BRIDGE_URL
1314
+ const bridgeBearer = process.env.CLI_BRIDGE_BEARER ?? process.env.BRIDGE_BEARER
1315
+ if (!bridgeUrl || !bridgeBearer) {
1316
+ throw new Error(
1317
+ 'authoring requires CLI_BRIDGE_URL/BRIDGE_URL and CLI_BRIDGE_BEARER/BRIDGE_BEARER',
1318
+ )
1319
+ }
1320
+ return {
1321
+ backend: 'bridge' as const,
1322
+ bridgeUrl,
1323
+ bridgeBearer,
1324
+ cwd: worktreePath,
1325
+ }
1326
+ }
1327
+
1275
1328
  export function constrainedLoopsGenerator(config: OuterLoopConfig): CandidateGenerator {
1276
1329
  const shotDir = join(config.outDir, 'proposer-shots')
1277
- // The run-wide CostLedger the current generate() call rides — captured so
1278
- // onShotCompleted can settle each shot's spend into it. maxConcurrency is 1
1279
- // and shots run inside generate(), so a single slot cannot interleave.
1280
- let activeLedger: CostLedgerHandle | undefined
1281
- let activePhase: string | undefined
1282
1330
  const inner = agenticGenerator({
1283
- harness: config.proposerHarness,
1331
+ profile: configuredAuthorProfile(config),
1332
+ executorForWorktree: authorExecutorForWorktree,
1284
1333
  timeoutMs: config.proposerTimeoutMs,
1285
1334
  buildPrompt: round4BuildPrompt,
1286
1335
  verify: loopsCandidateVerifier(config.loopsRepo),
1287
- runHarness: (options) => runLocalHarness({ ...options, env: proposerShotEnv(config.proposerHarness) }),
1288
- // Three runs died as "author shot exited with code 1" with the shot's
1289
- // stderr lost (nothing wires receipt persistence by default). Persist every
1290
- // attempted shot — receipt plus bounded stream tails — so the NEXT failure
1291
- // names its cause from disk. Shared implementation with the gen-3 fan-out
1292
- // authors (proposer-fanout.mts): receipt persistence + spend settlement
1293
- // into the run ledger for the claude/opencode paths whose shots would
1294
- // otherwise read $0.
1295
1336
  onShotCompleted: proposerShotHooks({
1296
1337
  shotDir,
1297
- harness: config.proposerHarness,
1298
- ledger: () => activeLedger,
1299
- phase: () => activePhase,
1300
1338
  }),
1301
1339
  })
1302
1340
  return {
1303
1341
  kind: `round4-constrained:${inner.kind}`,
1304
1342
  proposesWithoutFindings: true,
1305
- generate: (args) => {
1306
- activeLedger = args.costLedger
1307
- activePhase = args.costPhase
1308
- // args.maxShots is the LIB's dial (budget.maxImprovementShots → the
1309
- // improvement driver); capabilities.mts guarantees it is threaded.
1310
- return inner.generate(args)
1311
- },
1343
+ generate: (args) => inner.generate(args),
1312
1344
  }
1313
1345
  }
1314
1346
 
@@ -2001,12 +2033,9 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2001
2033
  evidence_refs: [],
2002
2034
  proposal_origin: 'search',
2003
2035
  })
2004
- const analyzeGeneration = async (input: {
2005
- generation: number
2006
- runDir: string
2007
- candidates: Array<{ surfaceHash: string; composite: number; campaign: unknown }>
2008
- history: unknown[]
2009
- }): Promise<ProposalFinding[]> => {
2036
+ const analyzeGeneration: NonNullable<
2037
+ ImproveCodeRunOptions<Scenario, R4Artifact>['analyzeGeneration']
2038
+ > = async (input): Promise<ProposalFinding[]> => {
2010
2039
  signal?.throwIfAborted()
2011
2040
  const runs: SupRunArtifacts[] = []
2012
2041
  if (input.generation === -1) {
@@ -2030,9 +2059,11 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2030
2059
  // Candidate failure artifacts come from the LIB's campaign cells (the
2031
2060
  // artifacts name their own runDir/patch) — resume-replayed cells included,
2032
2061
  // which the old recorder-based lookup silently dropped.
2033
- const worstFirst = [...input.candidates].sort((a, b) => a.composite - b.composite).slice(0, 4)
2062
+ const worstFirst = [...input.candidates]
2063
+ .sort((a, b) => (a.composite ?? -Infinity) - (b.composite ?? -Infinity))
2064
+ .slice(0, 4)
2034
2065
  for (const cand of worstFirst) {
2035
- const cells = cellsFromCampaign(cand.campaign as CampaignResult<R4Artifact, Scenario>)
2066
+ const cells = cellsFromCampaign(cand.campaign)
2036
2067
  for (const cell of cells) {
2037
2068
  const a = cell.artifact
2038
2069
  if (a === null || a.kind !== 'swe-arm' || !a.runDir) continue
@@ -2083,16 +2114,17 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2083
2114
  : {
2084
2115
  ...input,
2085
2116
  candidates: input.candidates.map((cand) => {
2086
- const campaign = cand.campaign as { cells?: Array<{ scenarioId: string }> } | null
2087
- if (campaign === null || typeof campaign !== 'object' || !Array.isArray(campaign.cells)) return cand
2088
2117
  return {
2089
2118
  ...cand,
2090
- campaign: { ...campaign, cells: campaign.cells.filter((c) => !privateIids.has(c.scenarioId)) },
2119
+ campaign: {
2120
+ ...cand.campaign,
2121
+ cells: cand.campaign.cells.filter((cell) => !privateIids.has(cell.scenarioId)),
2122
+ },
2091
2123
  }
2092
2124
  }),
2093
2125
  }
2094
2126
  signal?.throwIfAborted()
2095
- const rawFindings = await rawTrace(censoredInput as Parameters<typeof rawTrace>[0])
2127
+ const rawFindings = await rawTrace(censoredInput)
2096
2128
  signal?.throwIfAborted()
2097
2129
  return [steeringFinding, ...ensembleFindings, ...rawFindings]
2098
2130
  }
@@ -2161,6 +2193,7 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2161
2193
  repoRoot: config.loopsRepo,
2162
2194
  baseRef: config.loopsBaseRef,
2163
2195
  worktreeDir: join(config.outDir, 'loops-worktrees'),
2196
+ profile: configuredAuthorProfile(config),
2164
2197
  generator,
2165
2198
  },
2166
2199
  scenarios: sweScenarios,
@@ -2764,17 +2797,8 @@ if (isMain) {
2764
2797
  let config: OuterLoopConfig
2765
2798
  let flavor: string
2766
2799
  if (gen4 || gen5) {
2767
- // The codex seat rides only when the CLI is actually present — a config
2768
- // naming a missing harness would fail the whole launch at t=0.
2769
- const codexProbe = await run('codex', ['--version'])
2770
- const includeCodex = codexProbe.code === 0
2771
- if (!includeCodex) {
2772
- console.log(
2773
- `codex CLI unavailable (rc=${codexProbe.code}) — ${gen5 ? 'gen-5' : 'gen-4'} config written WITHOUT the codex-author seat`,
2774
- )
2775
- }
2776
2800
  const make = gen5 ? defaultGen5Config : defaultGen4Config
2777
- config = make(undefined, { ...(outDirName ? { outDirName } : {}), includeCodex })
2801
+ config = make(undefined, { ...(outDirName ? { outDirName } : {}) })
2778
2802
  flavor = gen5 ? 'gen-5' : 'gen-4'
2779
2803
  } else {
2780
2804
  const make = gen3 ? defaultGen3Config : defaultRound4Config
@@ -153,6 +153,16 @@ export function buildPremeasuredFromCells(input: {
153
153
  if (cell.artifact === null || cell.artifact.kind !== 'swe-arm') {
154
154
  throw new Error(`premeasured-from-cells: cell ${cell.cellId} has no swe-arm artifact`)
155
155
  }
156
+ if (cell.costProvenance.kind === 'uncaptured') {
157
+ throw new Error(`premeasured-from-cells: cell ${cell.cellId} has uncaptured cost`)
158
+ }
159
+ if (
160
+ cell.costProvenance.usd !== cell.costUsd
161
+ ) {
162
+ throw new Error(
163
+ `premeasured-from-cells: cell ${cell.cellId} cost provenance does not match costUsd`,
164
+ )
165
+ }
156
166
  }
157
167
 
158
168
  const scenarios: Scenario[] = instances.map((iid) => ({ id: iid, kind: 'swe-instance' }))
@@ -180,6 +190,11 @@ export function buildPremeasuredFromCells(input: {
180
190
  }),
181
191
  )
182
192
  const totalCostUsd = sorted.reduce((s, c) => s + c.costUsd, 0)
193
+ const costProvenance: CampaignCellResult<R4Artifact>['costProvenance'] = sorted.some(
194
+ (cell) => cell.costProvenance.kind === 'estimated',
195
+ )
196
+ ? { kind: 'estimated', usd: totalCostUsd }
197
+ : { kind: 'observed', usd: totalCostUsd }
183
198
  const inputTokens = sorted.reduce((s, c) => s + c.tokenUsage.input, 0)
184
199
  const outputTokens = sorted.reduce((s, c) => s + c.tokenUsage.output, 0)
185
200
  const totalCalls = sorted.reduce((s, c) => s + (c.costCallIds?.length ?? 0), 0)
@@ -213,6 +228,7 @@ export function buildPremeasuredFromCells(input: {
213
228
  outputTokens,
214
229
  cachedTokens: 0,
215
230
  totalCostUsd,
231
+ costProvenance,
216
232
  byChannel: [
217
233
  {
218
234
  channel: 'agent',
@@ -231,7 +247,6 @@ export function buildPremeasuredFromCells(input: {
231
247
  accountingComplete: true,
232
248
  incompleteReasons: [],
233
249
  },
234
- totalCostUsd,
235
250
  cellsExecuted: sorted.length,
236
251
  cellsSkipped: 0,
237
252
  cellsCached: sorted.length,
@@ -49,6 +49,7 @@ const cell = (iid: string, rep: number, resolved: boolean, over: Partial<FullCel
49
49
  },
50
50
  },
51
51
  costUsd: 0.05,
52
+ costProvenance: { kind: 'observed', usd: 0.05 },
52
53
  costCallIds: [`call-${iid}-${rep}`],
53
54
  tokenUsage: { input: 100, output: 50 },
54
55
  resolvedModel: 'zai-coding-plan/glm-5.2',
@@ -85,7 +86,11 @@ describe('buildPremeasuredFromCells', () => {
85
86
  expect(out.campaign.cells.every((c) => c.cached)).toBe(true)
86
87
  expect(out.campaign.cells.every((c) => !('mtimeMs' in c))).toBe(true)
87
88
  // Honest spend rollup from the real cells.
88
- expect(out.campaign.aggregates.totalCostUsd).toBeCloseTo(0.2)
89
+ expect(out.campaign.aggregates.cost.totalCostUsd).toBeCloseTo(0.2)
90
+ expect(out.campaign.aggregates.cost.costProvenance).toEqual({
91
+ kind: 'observed',
92
+ usd: 0.2,
93
+ })
89
94
  expect(out.campaign.aggregates.cost.inputTokens).toBe(400)
90
95
  expect(out.campaign.runDir).toBe('/prior/baseline')
91
96
  // Window reconstructed from mtimes: ends at the last cell write.
@@ -126,6 +131,21 @@ describe('buildPremeasuredFromCells', () => {
126
131
  expect(() =>
127
132
  buildPremeasuredFromCells({ cells: errored, instances: INSTANCES, reps: 2, surfaceHash: 'x', sourceDir: '/p' }),
128
133
  ).toThrow(/carries an error/)
134
+ const uncapturedCost = [
135
+ cell('inst-a', 0, false, { costProvenance: { kind: 'uncaptured', usd: null } }),
136
+ cell('inst-a', 1, true),
137
+ cell('inst-b', 0, true),
138
+ cell('inst-b', 1, true),
139
+ ]
140
+ expect(() =>
141
+ buildPremeasuredFromCells({
142
+ cells: uncapturedCost,
143
+ instances: INSTANCES,
144
+ reps: 2,
145
+ surfaceHash: 'x',
146
+ sourceDir: '/p',
147
+ }),
148
+ ).toThrow(/has uncaptured cost/)
129
149
  })
130
150
  })
131
151
 
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "swe-arena-deepseek-author",
3
+ "description": "Pi author using DeepSeek V4 Flash through Tangle Router.",
4
+ "harness": "pi",
5
+ "model": {
6
+ "provider": "tangle-router",
7
+ "default": "deepseek-v4-flash"
8
+ },
9
+ "prompt": {
10
+ "systemPrompt": "Improve the candidate worktree from measured failure evidence. Make the smallest coherent change, verify it, and leave the edit uncommitted."
11
+ }
12
+ }
@@ -1,4 +1,12 @@
1
1
  {
2
2
  "name": "swe-arena-default-author",
3
- "description": "The gen-2 winning author seat, codified: bare claude CLI invocation. No systemPrompt, no instructions, no model override — harnessInvocation() with this profile is byte-identical to the legacy prompt-only buildArgs path, so provenance pins the author identity without changing gen-2 behavior."
3
+ "description": "Pi author for measured SWE arena code improvement.",
4
+ "harness": "pi",
5
+ "model": {
6
+ "provider": "tangle-router",
7
+ "default": "glm-5.2"
8
+ },
9
+ "prompt": {
10
+ "systemPrompt": "Improve the candidate worktree from measured failure evidence. Make the smallest coherent change, verify it, and leave the edit uncommitted."
11
+ }
4
12
  }