@tangle-network/agent-bench 0.7.1 → 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.
- package/CHANGELOG.md +8 -0
- package/HARNESS.md +1 -1
- package/dist/adapters.js +2 -2
- package/dist/benchmarks/appworld.d.ts +61 -2
- package/dist/benchmarks/appworld.js +111 -40
- package/dist/benchmarks/appworld.js.map +1 -1
- package/dist/benchmarks/cadbench.js +1 -1
- package/dist/benchmarks/cadgenbench.js +1 -1
- package/dist/benchmarks/finresearchbench.js +16 -22
- package/dist/benchmarks/finresearchbench.js.map +1 -1
- package/dist/benchmarks/finsearchcomp.js +16 -22
- package/dist/benchmarks/finsearchcomp.js.map +1 -1
- package/dist/benchmarks/frames.js +18 -20
- package/dist/benchmarks/frames.js.map +1 -1
- package/dist/benchmarks/simpleqa.js +15 -19
- package/dist/benchmarks/simpleqa.js.map +1 -1
- package/dist/benchmarks/trata-hedge.js +20 -23
- package/dist/benchmarks/trata-hedge.js.map +1 -1
- package/dist/{cadbench-BrpwOU6A.js → cadbench-BLSyxR1N.js} +21 -20
- package/dist/cadbench-BLSyxR1N.js.map +1 -0
- package/dist/{cadgenbench-DF7hYHdl.js → cadgenbench-x2OFkf8y.js} +2 -2
- package/dist/cadgenbench-x2OFkf8y.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/dist/router-turn-C2wMiDoo.js +31 -0
- package/dist/router-turn-C2wMiDoo.js.map +1 -0
- package/package.json +7 -7
- package/scripts/appworld_driver.py +1 -170
- package/scripts/run-package-tests.mjs +56 -36
- package/scripts/run-package-tests.test.mjs +33 -0
- package/scripts/trata-hedge/README.md +3 -3
- package/scripts/trata-hedge/run.sh +6 -2
- package/src/aec-gate.mts +30 -9
- package/src/agent-graphs-gen2.mts +523 -0
- package/src/agent-graphs-gen3.mts +660 -0
- package/src/agent-graphs-improve/offline-seams.mts +128 -0
- package/src/agent-graphs-improve.mts +747 -0
- package/src/atom-humaneval.mts +76 -55
- package/src/atom-mcp-e2e.mts +27 -13
- package/src/benchmarks/appworld.test.mts +119 -1
- package/src/benchmarks/appworld.ts +156 -33
- package/src/benchmarks/cadbench.ts +21 -8
- package/src/benchmarks/finresearchbench.ts +20 -16
- package/src/benchmarks/finsearchcomp.ts +20 -20
- package/src/benchmarks/frames.ts +19 -18
- package/src/benchmarks/simpleqa.ts +19 -17
- package/src/benchmarks/trata-hedge.ts +26 -18
- package/src/browser/ui-reviewer.ts +1 -1
- package/src/clbench-context-gate.mts +45 -9
- package/src/cloud-loop.mts +24 -6
- package/src/commit0-env-run.mts +6 -3
- package/src/commit0-env.ts +2 -3
- package/src/commit0-gate.mts +53 -24
- package/src/corpus.ts +3 -3
- package/src/david-attribution.mts +28 -9
- package/src/david-goliath.mts +31 -12
- package/src/egress-probe.mts +3 -3
- package/src/examples/lean-proof-gate.mts +10 -3
- package/src/examples/math-demo.mts +10 -3
- package/src/examples/strategy-demo.mts +10 -4
- package/src/fleet.mts +30 -8
- package/src/gate-cli.mts +9 -4
- package/src/gate.test.mts +7 -2
- package/src/gate.ts +23 -75
- package/src/generate-eval/certify.ts +13 -5
- package/src/hev-eval.mts +39 -10
- package/src/hev-improve.mts +28 -16
- package/src/hev-structural.mts +28 -20
- package/src/humaneval-gate.mts +24 -8
- package/src/humaneval-object-ablation.mts +52 -14
- package/src/humaneval-repair-gate.mts +53 -14
- package/src/mbpp-structural.mts +28 -20
- package/src/mcp-mount-probe.mts +23 -11
- package/src/official-optimizer-config.mts +61 -12
- package/src/official-optimizer-config.test.mts +72 -3
- package/src/quant-arena/profiles/quant-researcher.profile.json +6 -1
- package/src/quant-arena/quant-loop.mts +88 -61
- package/src/research-shot.ts +21 -11
- package/src/resolve-client.ts +6 -8
- package/src/rollout-ledger/backfill-swe-arena.test.mts +7 -2
- package/src/router-executor.ts +1 -1
- package/src/router-turn.ts +135 -0
- package/src/run-benchmarks.ts +7 -2
- package/src/sandbox-run.ts +23 -41
- package/src/search-bench/bridge.ts +66 -8
- package/src/search-bench/parametric-check.mts +19 -4
- package/src/search-bench/profiles.ts +24 -9
- package/src/search-bench/run.mts +3 -4
- package/src/smoke-structural-rollout.mts +21 -5
- package/src/supervisor-arena.mts +28 -20
- package/src/swe-arena/arms.ts +73 -15
- package/src/swe-arena/capacity.ts +44 -41
- package/src/swe-arena/diagnosis-ensemble.ts +51 -48
- package/src/swe-arena/execution.test.mts +12 -12
- package/src/swe-arena/gepa-seat.mts +12 -5
- package/src/swe-arena/gepa-seat.test.mts +45 -33
- package/src/swe-arena/outer-loop.mts +94 -70
- package/src/swe-arena/premeasured-from-cells.mts +16 -1
- package/src/swe-arena/premeasured-from-cells.test.mts +21 -1
- package/src/swe-arena/profiles/deepseek-author.profile.json +12 -0
- package/src/swe-arena/profiles/default-author.profile.json +9 -1
- package/src/swe-arena/proposer-fanout.mts +52 -99
- package/src/swe-arena/proposer-fanout.test.mts +92 -57
- package/src/swe-arena/proposer-provenance.mts +1 -1
- package/src/swe-code-improve.mts +30 -52
- package/src/swe-emit-patch.mts +26 -5
- package/src/swe-improve.mts +27 -8
- package/src/swe-jail.test.ts +128 -0
- package/src/swe-jail.ts +106 -22
- package/src/swe-local-proof.mts +27 -5
- package/src/swe-repro-calibrate.mts +5 -0
- package/src/swe-self-improve.mts +45 -10
- package/src/swe-stream.mts +68 -6
- package/src/swe-structural.mts +46 -7
- package/src/tb-container-executor.mts +38 -6
- package/src/tb-container-executor.test.mts +11 -3
- package/src/tb-supervisor-sidecar.mts +11 -27
- package/src/terminal-compare.ts +38 -18
- package/src/trata-gate.mts +26 -26
- package/src/trata-gepa.mts +25 -16
- package/src/trata-hedge-solve.mts +80 -0
- package/src/worker-blender.ts +21 -11
- package/src/worker-browser.ts +18 -5
- package/src/worker-build123d.ts +17 -8
- package/src/worker-cad.ts +31 -12
- package/src/worker.ts +1 -1
- package/tb_agents/opencode_router_agent.py +84 -42
- package/dist/cadbench-BrpwOU6A.js.map +0 -1
- package/dist/cadgenbench-DF7hYHdl.js.map +0 -1
- package/scripts/trata-hedge/solve.py +0 -83
|
@@ -21,14 +21,9 @@
|
|
|
21
21
|
* stage 3 — survivors proceed through the unchanged pinned-baseline
|
|
22
22
|
* reps-fail-closed gate.
|
|
23
23
|
*
|
|
24
|
-
* Proposer identity: each proposer
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `model.default` into the harness's `-m` flag — verified supported for the
|
|
28
|
-
* claude CLI; a bare profile is byte-identical to the legacy prompt-only
|
|
29
|
-
* invocation). Profile `resources` are only materialized on the reproducible
|
|
30
|
-
* Codex path, so a non-codex proposer with resources fails loud here instead
|
|
31
|
-
* of silently dropping them.
|
|
24
|
+
* Proposer identity: each harness-authored proposer supplies one complete
|
|
25
|
+
* `AgentProfile`. Runtime validates and materializes that exact profile, then
|
|
26
|
+
* runs it through the CLI bridge in the candidate worktree.
|
|
32
27
|
*
|
|
33
28
|
* NOTE on the import cycle with outer-loop.mts: outer-loop imports this
|
|
34
29
|
* module's generator factory; this module imports outer-loop's change-space +
|
|
@@ -47,13 +42,11 @@ import {
|
|
|
47
42
|
type AgenticGeneratorShotExecution,
|
|
48
43
|
type AgenticGeneratorShotReceipt,
|
|
49
44
|
} from '@tangle-network/agent-runtime'
|
|
50
|
-
import {
|
|
51
|
-
import type { AgentProfile } from '@tangle-network/agent-interface'
|
|
45
|
+
import { agentProfileSchema, type AgentProfile } from '@tangle-network/agent-interface'
|
|
52
46
|
import type { CostLedgerHandle, ProposalFinding } from '@tangle-network/agent-eval'
|
|
53
47
|
import {
|
|
54
48
|
changeSpaceViolations,
|
|
55
49
|
loopsCandidateVerifier,
|
|
56
|
-
proposerShotEnv,
|
|
57
50
|
purgeIgnoredArtifacts,
|
|
58
51
|
round4BuildPrompt,
|
|
59
52
|
type OuterLoopConfig,
|
|
@@ -86,17 +79,12 @@ import {
|
|
|
86
79
|
export interface ProposerSpec {
|
|
87
80
|
/** Unique short name — becomes the candidate's staircase `label`. */
|
|
88
81
|
name: string
|
|
89
|
-
/** Path to
|
|
90
|
-
* `profiles/` directory.
|
|
82
|
+
/** Path to the complete `AgentProfile` JSON. Absolute, or relative to this
|
|
83
|
+
* module's `profiles/` directory. Required by the Runtime author path. */
|
|
91
84
|
profile?: string
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
/** GEN-4 pinned model id, threaded to the harness CLI as `-m <model>` via
|
|
96
|
-
* the author profile's `model.default` (harnessInvocation maps it for all
|
|
97
|
-
* three harnesses). Unset = the CLI's own resolved model (its login/settings
|
|
98
|
-
* default) — recorded per run by the proposer-provenance capture, so the
|
|
99
|
-
* seat identity is pinned in provenance even when the flag is absent. */
|
|
85
|
+
/** Optional provenance mirror of `profile.harness`; a mismatch is rejected. */
|
|
86
|
+
harness?: 'claude-code' | 'codex' | 'opencode' | 'pi'
|
|
87
|
+
/** Optional provenance mirror of `profile.model.default`; a mismatch is rejected. */
|
|
100
88
|
model?: string
|
|
101
89
|
/** GEN-4 merge seat: this proposer's task is to MERGE the configured
|
|
102
90
|
* Pareto parents' diffs into one coherent surface (see
|
|
@@ -179,42 +167,40 @@ export interface PrefilterKill {
|
|
|
179
167
|
|
|
180
168
|
export const PROFILES_DIR = fileURLToPath(new URL('./profiles', import.meta.url))
|
|
181
169
|
|
|
182
|
-
/** Load
|
|
183
|
-
* non-codex harnesses: `agenticGenerator` only materializes profile resource
|
|
184
|
-
* files on the reproducible Codex path, so accepting them here would drop
|
|
185
|
-
* them silently. */
|
|
170
|
+
/** Load and schema-validate a proposer's exact `AgentProfile`. */
|
|
186
171
|
export function loadAuthorProfile(spec: ProposerSpec): AgentProfile | undefined {
|
|
187
172
|
if (!spec.profile) return undefined
|
|
188
173
|
const path = spec.profile.startsWith('/') ? spec.profile : join(PROFILES_DIR, spec.profile)
|
|
189
|
-
|
|
190
|
-
if (typeof profile.name !== 'string' || profile.name.length === 0) {
|
|
191
|
-
throw new Error(`proposer ${spec.name}: profile ${path} has no name`)
|
|
192
|
-
}
|
|
193
|
-
if (profile.resources && spec.harness !== 'codex') {
|
|
194
|
-
throw new Error(
|
|
195
|
-
`proposer ${spec.name}: profile ${path} declares resources, which only materialize on the ` +
|
|
196
|
-
`reproducible codex path — the ${spec.harness} harness would silently drop them`,
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
return profile
|
|
174
|
+
return agentProfileSchema.parse(JSON.parse(readFileSync(path, 'utf8')))
|
|
200
175
|
}
|
|
201
176
|
|
|
202
|
-
/**
|
|
177
|
+
/** Default author: Pi with the exact GLM profile committed beside this module. */
|
|
203
178
|
export function defaultProposers(): ProposerSpec[] {
|
|
204
|
-
return [
|
|
179
|
+
return [
|
|
180
|
+
{
|
|
181
|
+
name: 'default-author',
|
|
182
|
+
profile: 'default-author.profile.json',
|
|
183
|
+
harness: 'pi',
|
|
184
|
+
model: 'glm-5.2',
|
|
185
|
+
},
|
|
186
|
+
]
|
|
205
187
|
}
|
|
206
188
|
|
|
207
|
-
/**
|
|
208
|
-
* the spec's PINNED MODEL merged into `model.default` — harnessInvocation
|
|
209
|
-
* turns that into the CLI's `-m <model>` flag (claude/codex/opencode all map
|
|
210
|
-
* it). Without a pinned model this is byte-identical to `loadAuthorProfile`,
|
|
211
|
-
* so gen-3 seats keep their exact invocation. A pinned model with no profile
|
|
212
|
-
* path synthesizes a minimal named profile carrying only the pin. */
|
|
189
|
+
/** Resolve the one exact profile; duplicated provenance fields may only agree. */
|
|
213
190
|
export function resolveAuthorProfile(spec: ProposerSpec): AgentProfile | undefined {
|
|
214
191
|
const profile = loadAuthorProfile(spec)
|
|
215
|
-
if (!
|
|
216
|
-
|
|
217
|
-
|
|
192
|
+
if (!profile) return undefined
|
|
193
|
+
if (spec.harness !== undefined && profile.harness !== spec.harness) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`proposer ${spec.name}: harness ${spec.harness} conflicts with profile.harness ${profile.harness}`,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
if (spec.model !== undefined && profile.model?.default !== spec.model) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`proposer ${spec.name}: model ${spec.model} conflicts with profile.model.default ${profile.model?.default ?? 'missing'}`,
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
return profile
|
|
218
204
|
}
|
|
219
205
|
|
|
220
206
|
// ---------------------------------------------------------------------------
|
|
@@ -353,7 +339,7 @@ export function sliceFindings(
|
|
|
353
339
|
findings: ReadonlyArray<ProposalFinding>,
|
|
354
340
|
slice: ProposerSpec['diagnosisSlice'],
|
|
355
341
|
): ProposalFinding[] {
|
|
356
|
-
if (slice === undefined || slice === 'all') return findings
|
|
342
|
+
if (slice === undefined || slice === 'all') return [...findings]
|
|
357
343
|
return findings.filter((finding) => {
|
|
358
344
|
const f = finding as unknown as Record<string, unknown>
|
|
359
345
|
if (isSteeringOrRawTrace(f)) return true
|
|
@@ -402,47 +388,14 @@ export function proposerBuildPrompt(
|
|
|
402
388
|
|
|
403
389
|
export function proposerShotHooks(opts: {
|
|
404
390
|
shotDir: string
|
|
405
|
-
harness: ProposerSpec['harness']
|
|
406
|
-
ledger: () => CostLedgerHandle | undefined
|
|
407
|
-
phase: () => string | undefined
|
|
408
391
|
}): (receipt: AgenticGeneratorShotReceipt, execution: AgenticGeneratorShotExecution | null) => Promise<void> {
|
|
409
392
|
return async (receipt, execution) => {
|
|
410
|
-
const tail = (s: string | undefined): string | null =>
|
|
411
|
-
s === undefined ? null : s.length > 20_000 ? s.slice(-20_000) : s
|
|
412
393
|
await mkdir(opts.shotDir, { recursive: true })
|
|
413
394
|
const name = `gen${receipt.generation ?? 'x'}-cand${receipt.candidateIndex ?? 'x'}-shot${receipt.shot}.json`
|
|
414
395
|
await writeFile(
|
|
415
396
|
join(opts.shotDir, name),
|
|
416
|
-
JSON.stringify({ receipt,
|
|
397
|
+
JSON.stringify({ receipt, execution }, null, 2),
|
|
417
398
|
)
|
|
418
|
-
// Proposer-shot spend → the lib's run ledger. The generator only settles
|
|
419
|
-
// its own receipts on the codexReproducible path (costCallId non-null);
|
|
420
|
-
// the claude/opencode author path otherwise leaves every shot as $0 in
|
|
421
|
-
// the run's spend summary. Import the shot receipt's measured usage.
|
|
422
|
-
const ledger = opts.ledger()
|
|
423
|
-
if (ledger && receipt.costCallId === null && (receipt.usage || receipt.costUsdKnown)) {
|
|
424
|
-
const usage = receipt.usage
|
|
425
|
-
const paid = await ledger.runPaidCall({
|
|
426
|
-
channel: 'driver',
|
|
427
|
-
phase: opts.phase() ?? 'search.proposal',
|
|
428
|
-
actor: `proposer-shot:${opts.harness}`,
|
|
429
|
-
model: receipt.model ?? `${opts.harness}-cli`,
|
|
430
|
-
tags: {
|
|
431
|
-
generation: String(receipt.generation ?? -1),
|
|
432
|
-
candidateIndex: String(receipt.candidateIndex ?? -1),
|
|
433
|
-
shot: String(receipt.shot),
|
|
434
|
-
},
|
|
435
|
-
execute: async () => receipt,
|
|
436
|
-
receipt: () => ({
|
|
437
|
-
model: receipt.model ?? `${opts.harness}-cli`,
|
|
438
|
-
inputTokens: usage?.inputTokens ?? 0,
|
|
439
|
-
outputTokens: usage ? usage.outputTokens + usage.reasoningOutputTokens : 0,
|
|
440
|
-
...(usage ? { cachedTokens: usage.cachedInputTokens } : { usageUnknown: true }),
|
|
441
|
-
...(receipt.costUsdKnown && receipt.costUsd !== null ? { actualCostUsd: receipt.costUsd } : {}),
|
|
442
|
-
}),
|
|
443
|
-
})
|
|
444
|
-
if (!paid.succeeded) throw paid.error
|
|
445
|
-
}
|
|
446
399
|
}
|
|
447
400
|
}
|
|
448
401
|
|
|
@@ -512,7 +465,6 @@ function defaultAuthor(config: OuterLoopConfig, deps: FanOutDeps): AuthorFn {
|
|
|
512
465
|
// One agenticGenerator per proposer, created lazily and cached: each carries
|
|
513
466
|
// its own profile, lens prompt, and shot-receipt home.
|
|
514
467
|
const inners = new Map<string, CandidateGenerator>()
|
|
515
|
-
const ledgers = new Map<string, { ledger?: CostLedgerHandle; phase?: string }>()
|
|
516
468
|
// The GEPA seat authors through the agent-eval adapter, not a
|
|
517
469
|
// harness CLI. Its inner evaluator is the SAME injected smoke runner the
|
|
518
470
|
// pre-filter uses (presence enforced at generator construction).
|
|
@@ -531,20 +483,27 @@ function defaultAuthor(config: OuterLoopConfig, deps: FanOutDeps): AuthorFn {
|
|
|
531
483
|
})
|
|
532
484
|
return gepaAuthor(proposer, args)
|
|
533
485
|
}
|
|
534
|
-
const harness = proposer.harness
|
|
535
|
-
if (harness === undefined) {
|
|
536
|
-
throw new Error(`proposer ${proposer.name}: harness is required for a non-engine seat`)
|
|
537
|
-
}
|
|
538
486
|
let inner = inners.get(proposer.name)
|
|
539
|
-
const slot = ledgers.get(proposer.name) ?? {}
|
|
540
|
-
ledgers.set(proposer.name, slot)
|
|
541
487
|
if (!inner) {
|
|
542
|
-
// GEN-4: the resolved profile carries the spec's pinned model
|
|
543
|
-
// (`model.default` → the harness CLI's `-m` flag).
|
|
544
488
|
const profile = resolveAuthorProfile(proposer)
|
|
489
|
+
if (!profile) {
|
|
490
|
+
throw new Error(`proposer ${proposer.name}: profile is required for a Runtime author seat`)
|
|
491
|
+
}
|
|
492
|
+
const bridgeUrl = process.env.CLI_BRIDGE_URL ?? process.env.BRIDGE_URL
|
|
493
|
+
const bridgeBearer = process.env.CLI_BRIDGE_BEARER ?? process.env.BRIDGE_BEARER
|
|
494
|
+
if (!bridgeUrl || !bridgeBearer) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
'proposer authoring requires CLI_BRIDGE_URL/BRIDGE_URL and CLI_BRIDGE_BEARER/BRIDGE_BEARER',
|
|
497
|
+
)
|
|
498
|
+
}
|
|
545
499
|
inner = agenticGenerator({
|
|
546
|
-
|
|
547
|
-
|
|
500
|
+
profile,
|
|
501
|
+
executorForWorktree: (cwd) => ({
|
|
502
|
+
backend: 'bridge',
|
|
503
|
+
bridgeUrl,
|
|
504
|
+
bridgeBearer,
|
|
505
|
+
cwd,
|
|
506
|
+
}),
|
|
548
507
|
timeoutMs: config.proposerTimeoutMs,
|
|
549
508
|
buildPrompt: (args) =>
|
|
550
509
|
proposerBuildPrompt(args, proposer, deps.parents ?? [], {
|
|
@@ -552,18 +511,12 @@ function defaultAuthor(config: OuterLoopConfig, deps: FanOutDeps): AuthorFn {
|
|
|
552
511
|
...(config.activationGate === true ? { activationGate: true } : {}),
|
|
553
512
|
}),
|
|
554
513
|
verify: loopsCandidateVerifier(config.loopsRepo),
|
|
555
|
-
runHarness: (options) => runLocalHarness({ ...options, env: proposerShotEnv(harness) }),
|
|
556
514
|
onShotCompleted: proposerShotHooks({
|
|
557
515
|
shotDir: join(config.outDir, 'proposer-shots', sanitize(proposer.name)),
|
|
558
|
-
harness,
|
|
559
|
-
ledger: () => slot.ledger,
|
|
560
|
-
phase: () => slot.phase,
|
|
561
516
|
}),
|
|
562
517
|
})
|
|
563
518
|
inners.set(proposer.name, inner)
|
|
564
519
|
}
|
|
565
|
-
slot.ledger = args.costLedger
|
|
566
|
-
slot.phase = args.costPhase
|
|
567
520
|
return inner.generate(args)
|
|
568
521
|
}
|
|
569
522
|
}
|
|
@@ -73,7 +73,7 @@ describe('sliceFindings', () => {
|
|
|
73
73
|
|
|
74
74
|
describe('proposerBuildPrompt', () => {
|
|
75
75
|
it('appends the lens AFTER the shared protocol prompt, leaving the change-space text intact', () => {
|
|
76
|
-
const spec: ProposerSpec = { name: 'x', harness: '
|
|
76
|
+
const spec: ProposerSpec = { name: 'x', harness: 'pi', lens: 'Prefer code-path fixes.' }
|
|
77
77
|
const prompt = proposerBuildPrompt({ findings: [] }, spec)
|
|
78
78
|
expect(prompt).toContain('DECLARED CHANGE-SPACE')
|
|
79
79
|
expect(prompt.indexOf('DECLARED CHANGE-SPACE')).toBeLessThan(prompt.indexOf('YOUR AUTHORING LENS (x)'))
|
|
@@ -81,7 +81,7 @@ describe('proposerBuildPrompt', () => {
|
|
|
81
81
|
})
|
|
82
82
|
|
|
83
83
|
it('is the bare round prompt without a lens', () => {
|
|
84
|
-
const spec: ProposerSpec = { name: 'x', harness: '
|
|
84
|
+
const spec: ProposerSpec = { name: 'x', harness: 'pi' }
|
|
85
85
|
expect(proposerBuildPrompt({ findings: [] }, spec)).not.toContain('AUTHORING LENS')
|
|
86
86
|
})
|
|
87
87
|
})
|
|
@@ -106,35 +106,47 @@ const PARENTS: ParetoParentContext[] = [
|
|
|
106
106
|
},
|
|
107
107
|
]
|
|
108
108
|
|
|
109
|
-
describe('resolveAuthorProfile
|
|
110
|
-
it('
|
|
109
|
+
describe('resolveAuthorProfile', () => {
|
|
110
|
+
it('returns the exact committed profile when provenance mirrors agree', () => {
|
|
111
111
|
const spec: ProposerSpec = {
|
|
112
112
|
name: 'x',
|
|
113
113
|
profile: 'default-author.profile.json',
|
|
114
|
-
harness: '
|
|
115
|
-
model: '
|
|
114
|
+
harness: 'pi',
|
|
115
|
+
model: 'glm-5.2',
|
|
116
116
|
}
|
|
117
117
|
const profile = resolveAuthorProfile(spec)
|
|
118
118
|
expect(profile?.name).toBe('swe-arena-default-author')
|
|
119
|
-
expect(profile?.
|
|
119
|
+
expect(profile?.harness).toBe('pi')
|
|
120
|
+
expect(profile?.model?.default).toBe('glm-5.2')
|
|
120
121
|
})
|
|
121
122
|
|
|
122
|
-
it('
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
it('rejects duplicated harness or model fields that conflict with the profile', () => {
|
|
124
|
+
expect(() =>
|
|
125
|
+
resolveAuthorProfile({
|
|
126
|
+
name: 'bad-harness',
|
|
127
|
+
profile: 'default-author.profile.json',
|
|
128
|
+
harness: 'codex',
|
|
129
|
+
}),
|
|
130
|
+
).toThrow(/conflicts with profile\.harness/)
|
|
131
|
+
expect(() =>
|
|
132
|
+
resolveAuthorProfile({
|
|
133
|
+
name: 'bad-model',
|
|
134
|
+
profile: 'default-author.profile.json',
|
|
135
|
+
model: 'different-model',
|
|
136
|
+
}),
|
|
137
|
+
).toThrow(/conflicts with profile\.model\.default/)
|
|
126
138
|
})
|
|
127
139
|
|
|
128
|
-
it('
|
|
129
|
-
const spec: ProposerSpec = { name: 'x', profile: 'default-author.profile.json', harness: '
|
|
140
|
+
it('does not synthesize an ambient profile when the path is missing', () => {
|
|
141
|
+
const spec: ProposerSpec = { name: 'x', profile: 'default-author.profile.json', harness: 'pi' }
|
|
130
142
|
expect(resolveAuthorProfile(spec)).toEqual(loadAuthorProfile(spec))
|
|
131
|
-
expect(resolveAuthorProfile({ name: 'bare', harness: '
|
|
143
|
+
expect(resolveAuthorProfile({ name: 'bare', harness: 'pi' })).toBeUndefined()
|
|
132
144
|
})
|
|
133
145
|
})
|
|
134
146
|
|
|
135
147
|
describe('proposerBuildPrompt with pareto parents', () => {
|
|
136
148
|
it('appends the parents section (evidence + diffs) after the protocol prompt and lens', () => {
|
|
137
|
-
const spec: ProposerSpec = { name: 'x', harness: '
|
|
149
|
+
const spec: ProposerSpec = { name: 'x', harness: 'pi', lens: 'Prefer code-path fixes.' }
|
|
138
150
|
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
139
151
|
expect(prompt).toContain('DECLARED CHANGE-SPACE')
|
|
140
152
|
expect(prompt).toContain('PARETO PARENTS')
|
|
@@ -146,14 +158,14 @@ describe('proposerBuildPrompt with pareto parents', () => {
|
|
|
146
158
|
})
|
|
147
159
|
|
|
148
160
|
it('leaves the prompt untouched when no parents are seeded (gen-3 behavior)', () => {
|
|
149
|
-
const spec: ProposerSpec = { name: 'x', harness: '
|
|
161
|
+
const spec: ProposerSpec = { name: 'x', harness: 'pi' }
|
|
150
162
|
expect(proposerBuildPrompt({ findings: [] }, spec)).not.toContain('PARETO PARENTS')
|
|
151
163
|
expect(parentsPromptSection(PARENTS)).toContain('measured evidence')
|
|
152
164
|
})
|
|
153
165
|
})
|
|
154
166
|
|
|
155
167
|
describe('mergeAuthorPrompt', () => {
|
|
156
|
-
const spec: ProposerSpec = { name: 'merge-author', harness: '
|
|
168
|
+
const spec: ProposerSpec = { name: 'merge-author', harness: 'pi', merge: true }
|
|
157
169
|
|
|
158
170
|
it('keeps the change-space contract and presents BOTH parent diffs with the coherent-union task', () => {
|
|
159
171
|
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
@@ -178,21 +190,23 @@ describe('mergeAuthorPrompt', () => {
|
|
|
178
190
|
describe('defaultGen4Config', () => {
|
|
179
191
|
const config = defaultGen4Config()
|
|
180
192
|
|
|
181
|
-
it('seats
|
|
182
|
-
expect(config.proposers).toHaveLength(
|
|
183
|
-
expect(config.populationSize).toBe(
|
|
193
|
+
it('seats Pi authors with exact GLM and DeepSeek profiles', () => {
|
|
194
|
+
expect(config.proposers).toHaveLength(3)
|
|
195
|
+
expect(config.populationSize).toBe(3)
|
|
184
196
|
const byName = Object.fromEntries(config.proposers!.map((p) => [p.name, p]))
|
|
185
|
-
expect(byName['
|
|
186
|
-
expect(byName['
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
expect(byName['glm-author']).toMatchObject({ harness: 'pi', model: 'glm-5.2' })
|
|
198
|
+
expect(byName['deepseek-author']).toMatchObject({
|
|
199
|
+
harness: 'pi',
|
|
200
|
+
model: 'deepseek-v4-flash',
|
|
201
|
+
profile: 'deepseek-author.profile.json',
|
|
202
|
+
})
|
|
203
|
+
expect(byName['merge-author']).toMatchObject({ harness: 'pi', merge: true })
|
|
190
204
|
})
|
|
191
205
|
|
|
192
|
-
it('
|
|
193
|
-
const
|
|
194
|
-
expect(
|
|
195
|
-
expect(
|
|
206
|
+
it('can omit the DeepSeek seat while keeping population aligned', () => {
|
|
207
|
+
const noDeepseek = defaultGen4Config(undefined, { includeDeepseek: false })
|
|
208
|
+
expect(noDeepseek.proposers!.map((p) => p.name)).toEqual(['glm-author', 'merge-author'])
|
|
209
|
+
expect(noDeepseek.populationSize).toBe(2)
|
|
196
210
|
})
|
|
197
211
|
|
|
198
212
|
it('seeds the gen-3 frontier as pareto parents and points at a fresh gen4 outDir + baseline artifact', () => {
|
|
@@ -215,24 +229,40 @@ describe('defaultGen4Config', () => {
|
|
|
215
229
|
})
|
|
216
230
|
|
|
217
231
|
describe('loadAuthorProfile', () => {
|
|
218
|
-
it('loads the committed
|
|
219
|
-
const profile = loadAuthorProfile({
|
|
232
|
+
it('loads the complete committed Pi/GLM author profile', () => {
|
|
233
|
+
const profile = loadAuthorProfile({
|
|
234
|
+
name: 'a',
|
|
235
|
+
profile: 'default-author.profile.json',
|
|
236
|
+
harness: 'pi',
|
|
237
|
+
})
|
|
220
238
|
expect(profile?.name).toBe('swe-arena-default-author')
|
|
221
|
-
expect(profile?.
|
|
222
|
-
expect(profile?.
|
|
239
|
+
expect(profile?.harness).toBe('pi')
|
|
240
|
+
expect(profile?.prompt?.systemPrompt).toContain('measured failure evidence')
|
|
241
|
+
expect(profile?.model).toMatchObject({ provider: 'tangle-router', default: 'glm-5.2' })
|
|
223
242
|
expect(existsSync(join(PROFILES_DIR, 'default-author.profile.json'))).toBe(true)
|
|
224
243
|
})
|
|
225
244
|
|
|
226
245
|
it('returns undefined without a profile path', () => {
|
|
227
|
-
expect(loadAuthorProfile({ name: 'a', harness: '
|
|
246
|
+
expect(loadAuthorProfile({ name: 'a', harness: 'pi' })).toBeUndefined()
|
|
228
247
|
})
|
|
229
248
|
|
|
230
|
-
it('
|
|
249
|
+
it('preserves profile resources for Runtime materialization', async () => {
|
|
231
250
|
const dir = await mkdtemp(join(tmpdir(), 'profile-'))
|
|
232
251
|
try {
|
|
233
252
|
const path = join(dir, 'with-resources.json')
|
|
234
|
-
await writeFile(
|
|
235
|
-
|
|
253
|
+
await writeFile(
|
|
254
|
+
path,
|
|
255
|
+
JSON.stringify({
|
|
256
|
+
name: 'r',
|
|
257
|
+
harness: 'pi',
|
|
258
|
+
model: { provider: 'test', default: 'model' },
|
|
259
|
+
prompt: { systemPrompt: 'Author changes.' },
|
|
260
|
+
resources: { files: [] },
|
|
261
|
+
}),
|
|
262
|
+
)
|
|
263
|
+
expect(loadAuthorProfile({ name: 'a', profile: path, harness: 'pi' })?.resources).toEqual({
|
|
264
|
+
files: [],
|
|
265
|
+
})
|
|
236
266
|
} finally {
|
|
237
267
|
await rm(dir, { recursive: true, force: true })
|
|
238
268
|
}
|
|
@@ -300,9 +330,14 @@ describe('defaultGen3Config', () => {
|
|
|
300
330
|
}
|
|
301
331
|
})
|
|
302
332
|
|
|
303
|
-
it('defaultProposers
|
|
333
|
+
it('defaultProposers uses one exact Pi/GLM profile', () => {
|
|
304
334
|
expect(defaultProposers()).toEqual([
|
|
305
|
-
{
|
|
335
|
+
{
|
|
336
|
+
name: 'default-author',
|
|
337
|
+
profile: 'default-author.profile.json',
|
|
338
|
+
harness: 'pi',
|
|
339
|
+
model: 'glm-5.2',
|
|
340
|
+
},
|
|
306
341
|
])
|
|
307
342
|
})
|
|
308
343
|
})
|
|
@@ -357,8 +392,8 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
357
392
|
|
|
358
393
|
it('authors ALL proposers concurrently in separate worktrees and applies each patch to its candidate slot', async () => {
|
|
359
394
|
const proposers: ProposerSpec[] = [
|
|
360
|
-
{ name: 'alpha', harness: '
|
|
361
|
-
{ name: 'beta', harness: '
|
|
395
|
+
{ name: 'alpha', harness: 'pi' },
|
|
396
|
+
{ name: 'beta', harness: 'pi' },
|
|
362
397
|
]
|
|
363
398
|
let inFlight = 0
|
|
364
399
|
let maxInFlight = 0
|
|
@@ -396,7 +431,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
396
431
|
|
|
397
432
|
expect(first).toMatchObject({ applied: true, label: 'alpha' })
|
|
398
433
|
expect(second).toMatchObject({ applied: true, label: 'beta' })
|
|
399
|
-
expect(first.rationale).toContain('proposer alpha (
|
|
434
|
+
expect(first.rationale).toContain('proposer alpha (pi)')
|
|
400
435
|
// Both patches landed on the SAME driver worktree across the two calls —
|
|
401
436
|
// the driver discards/finalizes between calls in production; here we only
|
|
402
437
|
// assert each call applied its own proposer's file.
|
|
@@ -407,8 +442,8 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
407
442
|
|
|
408
443
|
it('kills a candidate at the smoke pre-filter: applied=false, no patch applied, kill recorded with reason', async () => {
|
|
409
444
|
const proposers: ProposerSpec[] = [
|
|
410
|
-
{ name: 'good', harness: '
|
|
411
|
-
{ name: 'bad', harness: '
|
|
445
|
+
{ name: 'good', harness: 'pi' },
|
|
446
|
+
{ name: 'bad', harness: 'pi' },
|
|
412
447
|
]
|
|
413
448
|
const config = baseConfig(proposers)
|
|
414
449
|
config.prefilter = { enabled: true, smokeInstance: 'cheapest-of-set' }
|
|
@@ -461,7 +496,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
461
496
|
})
|
|
462
497
|
|
|
463
498
|
it('kills an out-of-space diff at the change-space pre-filter before any smoke spend', async () => {
|
|
464
|
-
const proposers: ProposerSpec[] = [{ name: 'rogue', harness: '
|
|
499
|
+
const proposers: ProposerSpec[] = [{ name: 'rogue', harness: 'pi' }]
|
|
465
500
|
const config = baseConfig(proposers)
|
|
466
501
|
config.prefilter = { enabled: true, smokeInstance: 'cheapest-of-set' }
|
|
467
502
|
let smokeRan = false
|
|
@@ -488,7 +523,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
488
523
|
})
|
|
489
524
|
|
|
490
525
|
it('returns applied:false without a kill when a proposer authors nothing', async () => {
|
|
491
|
-
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'idle', harness: '
|
|
526
|
+
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'idle', harness: 'pi' }]), {
|
|
492
527
|
author: async () => ({ applied: false, summary: '' }),
|
|
493
528
|
})
|
|
494
529
|
expect((await gen.generate(generatorArgs(0))).applied).toBe(false)
|
|
@@ -496,7 +531,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
496
531
|
})
|
|
497
532
|
|
|
498
533
|
it('fails loud when candidateIndex exceeds the proposer list (populationSize drift)', async () => {
|
|
499
|
-
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'only', harness: '
|
|
534
|
+
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'only', harness: 'pi' }]), {
|
|
500
535
|
author: async () => ({ applied: false, summary: '' }),
|
|
501
536
|
})
|
|
502
537
|
await expect(gen.generate(generatorArgs(1))).rejects.toThrow(/populationSize must equal/)
|
|
@@ -520,7 +555,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
520
555
|
})
|
|
521
556
|
|
|
522
557
|
it('refuses a merge seat without >=2 materialized parents', () => {
|
|
523
|
-
const config = baseConfig([{ name: 'merge-author', harness: '
|
|
558
|
+
const config = baseConfig([{ name: 'merge-author', harness: 'pi', merge: true }])
|
|
524
559
|
expect(() => fanOutLoopsGenerator(config, { author: async () => ({ applied: false, summary: '' }) })).toThrow(
|
|
525
560
|
/merge proposer/,
|
|
526
561
|
)
|
|
@@ -539,8 +574,8 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
539
574
|
it('rejects duplicate proposer names and empty proposer lists', () => {
|
|
540
575
|
expect(() =>
|
|
541
576
|
fanOutLoopsGenerator(baseConfig([
|
|
542
|
-
{ name: 'dup', harness: '
|
|
543
|
-
{ name: 'dup', harness: '
|
|
577
|
+
{ name: 'dup', harness: 'pi' },
|
|
578
|
+
{ name: 'dup', harness: 'pi' },
|
|
544
579
|
])),
|
|
545
580
|
).toThrow(/duplicate/)
|
|
546
581
|
expect(() => fanOutLoopsGenerator({ ...defaultRound4Config(), loopsRepo, outDir })).toThrow(/empty/)
|
|
@@ -576,10 +611,10 @@ describe('defaultGen5Config', () => {
|
|
|
576
611
|
expect(config.priorEvidenceDirs!.some((d) => d.includes('gen4'))).toBe(true)
|
|
577
612
|
})
|
|
578
613
|
|
|
579
|
-
it('
|
|
580
|
-
const
|
|
581
|
-
expect(
|
|
582
|
-
expect(
|
|
614
|
+
it('can omit the DeepSeek seat while keeping population aligned', () => {
|
|
615
|
+
const noDeepseek = defaultGen5Config(undefined, { includeDeepseek: false })
|
|
616
|
+
expect(noDeepseek.proposers!.map((p) => p.name)).toEqual(['glm-author', 'merge-author'])
|
|
617
|
+
expect(noDeepseek.populationSize).toBe(2)
|
|
583
618
|
})
|
|
584
619
|
})
|
|
585
620
|
|
|
@@ -591,7 +626,7 @@ describe('gen-5 prompt sections', () => {
|
|
|
591
626
|
}
|
|
592
627
|
|
|
593
628
|
it('appends EVIDENCE MAP + briefing + activation contract after the protocol prompt', () => {
|
|
594
|
-
const spec: ProposerSpec = { name: 'x', harness: '
|
|
629
|
+
const spec: ProposerSpec = { name: 'x', harness: 'pi' }
|
|
595
630
|
const prompt = proposerBuildPrompt({ findings: [] }, spec, [], {
|
|
596
631
|
briefing,
|
|
597
632
|
activationGate: true,
|
|
@@ -604,7 +639,7 @@ describe('gen-5 prompt sections', () => {
|
|
|
604
639
|
})
|
|
605
640
|
|
|
606
641
|
it('the merge seat gets the gen-5 sections too', () => {
|
|
607
|
-
const spec: ProposerSpec = { name: 'merge-author', harness: '
|
|
642
|
+
const spec: ProposerSpec = { name: 'merge-author', harness: 'pi', merge: true }
|
|
608
643
|
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS, {
|
|
609
644
|
briefing,
|
|
610
645
|
activationGate: true,
|
|
@@ -615,7 +650,7 @@ describe('gen-5 prompt sections', () => {
|
|
|
615
650
|
})
|
|
616
651
|
|
|
617
652
|
it('leaves gen-3/gen-4 prompts byte-identical when no extras are passed', () => {
|
|
618
|
-
const spec: ProposerSpec = { name: 'x', harness: '
|
|
653
|
+
const spec: ProposerSpec = { name: 'x', harness: 'pi' }
|
|
619
654
|
const legacy = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
620
655
|
expect(legacy).not.toContain('EVIDENCE MAP')
|
|
621
656
|
expect(legacy).not.toContain('ACTIVATION PREDICATE')
|
|
@@ -120,7 +120,7 @@ export async function captureProposerProvenance(
|
|
|
120
120
|
for (const harness of harnesses) {
|
|
121
121
|
// The harness id is not the binary name (`claude-code` runs `claude`); read the executable
|
|
122
122
|
// from the runtime's harness table rather than spawning the id.
|
|
123
|
-
const executable = localHarnessExecutable(harness)
|
|
123
|
+
const executable = harness === 'pi' ? 'pi' : localHarnessExecutable(harness)
|
|
124
124
|
const res = await exec(executable, ['--version'])
|
|
125
125
|
if (res.code !== 0) {
|
|
126
126
|
throw new Error(
|