@tangle-network/agent-bench 0.4.9 → 0.6.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 +11 -0
- package/package.json +6 -6
- package/src/quant-arena/quant-loop.mts +3 -3
- package/src/rollout-ledger/backfill-swe-arena.test.mts +2 -2
- package/src/swe-arena/activation.test.mts +4 -4
- package/src/swe-arena/factory.test.mts +3 -3
- package/src/swe-arena/gepa-seat.test.mts +4 -4
- package/src/swe-arena/outer-loop.mts +8 -8
- package/src/swe-arena/proc.test.mts +1 -1
- package/src/swe-arena/proposer-fanout.mts +2 -2
- package/src/swe-arena/proposer-fanout.test.mts +36 -30
- package/src/swe-arena/proposer-provenance.mts +7 -3
- package/src/swe-arena/proposer-provenance.test.mts +3 -3
- package/src/swe-code-improve.mts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
- Consume Runtime 0.121.0 with Sandbox 0.16.0 so a clean install no longer requires the superseded Sandbox 0.15 line.
|
|
6
|
+
- Release under a new minor because Sandbox is a pre-1.0 dependency moving across a minor boundary.
|
|
7
|
+
|
|
8
|
+
## 0.5.0
|
|
9
|
+
|
|
10
|
+
- Consume Runtime 0.120.0 with Eval 0.140.1, Interface 0.40.0, Knowledge 7.0.4, and Sandbox 0.15.2.
|
|
11
|
+
- Released under a new minor, not a patch, because Knowledge crosses a major (6.1.11 -> 7.0.4) and Runtime crosses eleven minors. A `^0.4.9` range admits only patches, so shipping this alignment as 0.4.10 would hand every such consumer a major dependency move without a range change.
|
|
12
|
+
- Resolve the supervisor tree from the `{kind:'event', root, event}` journal envelope, and take the optional `controlAdapter` on the DSPy RLM trace engine, both by way of Eval 0.140.1. Bench calls neither surface directly, so no adapter code changed.
|
|
13
|
+
|
|
3
14
|
## 0.4.9
|
|
4
15
|
|
|
5
16
|
- Consume Runtime 0.109.2 with Eval 0.135.2 and Knowledge 6.1.11 so benchmark runs use the corrected paired promotion decisions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-bench",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Benchmark adapters and execution for agent-runtime across coding, tool-use, RAG, memory, browser, and terminal tasks.",
|
|
6
6
|
"repository": {
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@tangle-network/agent-eval": "0.
|
|
29
|
-
"@tangle-network/agent-interface": "0.
|
|
30
|
-
"@tangle-network/agent-knowledge": "
|
|
31
|
-
"@tangle-network/sandbox": "0.
|
|
32
|
-
"@tangle-network/agent-runtime": "0.
|
|
28
|
+
"@tangle-network/agent-eval": "0.140.1",
|
|
29
|
+
"@tangle-network/agent-interface": "0.40.0",
|
|
30
|
+
"@tangle-network/agent-knowledge": "7.0.4",
|
|
31
|
+
"@tangle-network/sandbox": "0.16.0",
|
|
32
|
+
"@tangle-network/agent-runtime": "0.121.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@arethetypeswrong/cli": "0.18.5",
|
|
@@ -88,11 +88,11 @@ export const PINNED_BASELINES: Record<string, GenerateSignals> = {
|
|
|
88
88
|
/** The two demo author seats: the plain author and the quant lens. */
|
|
89
89
|
export function defaultQuantProposers(): ProposerSpec[] {
|
|
90
90
|
return [
|
|
91
|
-
{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude' },
|
|
91
|
+
{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude-code' },
|
|
92
92
|
{
|
|
93
93
|
name: 'quant-researcher',
|
|
94
94
|
profile: join(QUANT_PROFILES_DIR, 'quant-researcher.profile.json'),
|
|
95
|
-
harness: 'claude',
|
|
95
|
+
harness: 'claude-code',
|
|
96
96
|
lens:
|
|
97
97
|
'Favor ONE economically-motivated effect (trend, mean reversion, vol targeting) with few parameters. ' +
|
|
98
98
|
'State the regime in which it should work and keep turnover low enough that 15bps a side cannot eat the edge.',
|
|
@@ -218,7 +218,7 @@ async function claudeShot(opts: {
|
|
|
218
218
|
const res = await run('claude', argv, {
|
|
219
219
|
stdin: opts.prompt,
|
|
220
220
|
cwd: opts.cwd,
|
|
221
|
-
env: proposerShotEnv('claude'),
|
|
221
|
+
env: proposerShotEnv('claude-code'),
|
|
222
222
|
timeoutMs: opts.timeoutMs,
|
|
223
223
|
})
|
|
224
224
|
if (res.code !== 0) {
|
|
@@ -158,7 +158,7 @@ async function buildFixtureTree(): Promise<void> {
|
|
|
158
158
|
candidateIndex: 0,
|
|
159
159
|
shot: 1,
|
|
160
160
|
maxShots: 3,
|
|
161
|
-
harness: 'claude',
|
|
161
|
+
harness: 'claude-code',
|
|
162
162
|
model: null,
|
|
163
163
|
promptSha256: 'sha256:abc',
|
|
164
164
|
startedAt: '2026-07-22T19:27:18.350Z',
|
|
@@ -256,7 +256,7 @@ describe('backfillSweArena', () => {
|
|
|
256
256
|
expect(proposer?.messages).toHaveLength(2)
|
|
257
257
|
expect(proposer?.outcome.reward).toBe(1)
|
|
258
258
|
expect(proposer?.outcome.reward_source).toBe('swe-arena-official-judge/candidate-resolved-fraction')
|
|
259
|
-
expect(proposer?.policy).toMatchObject({ harness: 'claude', model: 'claude-fable-5' })
|
|
259
|
+
expect(proposer?.policy).toMatchObject({ harness: 'claude-code', model: 'claude-fable-5' })
|
|
260
260
|
expect(proposer?.cost.tokens_out).toBe(462)
|
|
261
261
|
expect(proposer?.task).toMatchObject({ suite: 'swe-arena-proposer', rep: 1 })
|
|
262
262
|
|
|
@@ -245,9 +245,9 @@ describe('activation-predicate prefilter', () => {
|
|
|
245
245
|
|
|
246
246
|
it('kills a candidate without .improve/activation.json (stage activation-predicate) and passes one WITH it', async () => {
|
|
247
247
|
const proposers: ProposerSpec[] = [
|
|
248
|
-
{ name: 'with-predicate', harness: 'claude' },
|
|
249
|
-
{ name: 'without-predicate', harness: 'claude' },
|
|
250
|
-
{ name: 'invalid-predicate', harness: 'claude' },
|
|
248
|
+
{ name: 'with-predicate', harness: 'claude-code' },
|
|
249
|
+
{ name: 'without-predicate', harness: 'claude-code' },
|
|
250
|
+
{ name: 'invalid-predicate', harness: 'claude-code' },
|
|
251
251
|
]
|
|
252
252
|
const gen = fanOutLoopsGenerator(config(proposers), {
|
|
253
253
|
author: async (proposer, args) => {
|
|
@@ -284,7 +284,7 @@ describe('activation-predicate prefilter', () => {
|
|
|
284
284
|
})
|
|
285
285
|
|
|
286
286
|
it('does not require a predicate when the gate is off (gen-4 behavior unchanged)', async () => {
|
|
287
|
-
const cfg = config([{ name: 'legacy', harness: 'claude' }])
|
|
287
|
+
const cfg = config([{ name: 'legacy', harness: 'claude-code' }])
|
|
288
288
|
cfg.activationGate = false
|
|
289
289
|
const gen = fanOutLoopsGenerator(cfg, {
|
|
290
290
|
author: async (_p, args) => {
|
|
@@ -510,19 +510,19 @@ describe('judgeFactoryPatch', () => {
|
|
|
510
510
|
it('gold (impl-only PR diff) resolves with full score', async () => {
|
|
511
511
|
const { result } = await judgeFactoryPatch(goodInst, await goldImplPatch(goodInst))
|
|
512
512
|
expect(result).toMatchObject({ resolved: true, score: 1, passed: 2, total: 2 })
|
|
513
|
-
})
|
|
513
|
+
}, 60_000)
|
|
514
514
|
|
|
515
515
|
it('empty patch (bare base) fails: judge tests cannot even collect', async () => {
|
|
516
516
|
const { result } = await judgeFactoryPatch(goodInst, '')
|
|
517
517
|
expect(result).toMatchObject({ resolved: false, score: 0, passed: 0, total: 2 })
|
|
518
|
-
})
|
|
518
|
+
}, 60_000)
|
|
519
519
|
|
|
520
520
|
it('partial impl earns partial credit, never resolved', async () => {
|
|
521
521
|
// add correct, mul wrong → 1/2.
|
|
522
522
|
const patch = await makePatch(goodInst, 'export const add = (a, b) => a + b\nexport const mul = (a, b) => a + b\n')
|
|
523
523
|
const { result } = await judgeFactoryPatch(goodInst, patch)
|
|
524
524
|
expect(result).toMatchObject({ resolved: false, score: 0.5, passed: 1, total: 2 })
|
|
525
|
-
})
|
|
525
|
+
}, 60_000)
|
|
526
526
|
|
|
527
527
|
it('an unappliable patch is the candidate\'s failure (resolved false), not infra', async () => {
|
|
528
528
|
const garbage = 'diff --git a/nope.txt b/nope.txt\n--- a/nope.txt\n+++ b/nope.txt\n@@ -1 +1 @@\n-x\n+y\n'
|
|
@@ -58,7 +58,7 @@ describe('validateGepaSeat', () => {
|
|
|
58
58
|
expect(() => validateGepaSeat(seat())).not.toThrow()
|
|
59
59
|
expect(() => validateGepaSeat(seat({ engine: 'omni', maxMetricCalls: 8 }))).not.toThrow()
|
|
60
60
|
expect(isGepaSeat(seat())).toBe(true)
|
|
61
|
-
expect(isGepaSeat({ name: 'x', harness: 'claude' })).toBe(false)
|
|
61
|
+
expect(isGepaSeat({ name: 'x', harness: 'claude-code' })).toBe(false)
|
|
62
62
|
})
|
|
63
63
|
|
|
64
64
|
it('requires a surface inside the declared change-space', () => {
|
|
@@ -68,7 +68,7 @@ describe('validateGepaSeat', () => {
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
it('rejects harness-seat fields on an engine seat instead of silently ignoring them', () => {
|
|
71
|
-
expect(() => validateGepaSeat(seat({ harness: 'claude' }))).toThrow(/'harness' belongs to harness-authored/)
|
|
71
|
+
expect(() => validateGepaSeat(seat({ harness: 'claude-code' }))).toThrow(/'harness' belongs to harness-authored/)
|
|
72
72
|
expect(() => validateGepaSeat(seat({ merge: true }))).toThrow(/'merge'/)
|
|
73
73
|
expect(() => validateGepaSeat(seat({ model: 'x' }))).toThrow(/'model'/)
|
|
74
74
|
expect(() => validateGepaSeat(seat({ profile: 'p.json' }))).toThrow(/'profile'/)
|
|
@@ -234,7 +234,7 @@ describe('captureProposerProvenance with a gepa seat', () => {
|
|
|
234
234
|
return { code: 0, stdout: 'source', stderr: '' }
|
|
235
235
|
}
|
|
236
236
|
it('records engine, surface, gepa version, bridge module, and the python runtime as harnessVersion', async () => {
|
|
237
|
-
const record = await captureProposerProvenance([{ name: 'claude-author', harness: 'claude' }, seat()], {
|
|
237
|
+
const record = await captureProposerProvenance([{ name: 'claude-author', harness: 'claude-code' }, seat()], {
|
|
238
238
|
exec: okExec,
|
|
239
239
|
readSettingsModel: () => 'settings-model',
|
|
240
240
|
})
|
|
@@ -251,7 +251,7 @@ describe('captureProposerProvenance with a gepa seat', () => {
|
|
|
251
251
|
expect(gepa.harness).toBeUndefined()
|
|
252
252
|
// The claude seat is untouched by the gepa capture path.
|
|
253
253
|
expect(record.proposers.find((p) => p.name === 'claude-author')).toMatchObject({
|
|
254
|
-
harness: 'claude',
|
|
254
|
+
harness: 'claude-code',
|
|
255
255
|
settingsModel: 'settings-model',
|
|
256
256
|
})
|
|
257
257
|
})
|
|
@@ -646,7 +646,7 @@ 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' | 'codex' | 'opencode'
|
|
649
|
+
proposerHarness: 'claude-code' | 'codex' | 'opencode'
|
|
650
650
|
proposerTimeoutMs: number
|
|
651
651
|
/** GEN-3 proposer fan-out: N proposers author candidates CONCURRENTLY, each
|
|
652
652
|
* an AgentProfile-pinned harness invocation (see proposer-fanout.mts).
|
|
@@ -759,7 +759,7 @@ export function defaultRound4Config(
|
|
|
759
759
|
// bootstrap run writes it; the lib validates it on every consumption.
|
|
760
760
|
premeasuredBaselinePath: join(hh, 'r4', 'premeasured-baseline.json'),
|
|
761
761
|
maxShots: 3,
|
|
762
|
-
proposerHarness: 'claude',
|
|
762
|
+
proposerHarness: 'claude-code',
|
|
763
763
|
// Per author SHOT (agenticGenerator timeoutMs). 20 min timed out 3× under
|
|
764
764
|
// degraded capacity in gen-1 ("author shot timed out") — doubled to 40 min.
|
|
765
765
|
proposerTimeoutMs: 2_400_000,
|
|
@@ -858,18 +858,18 @@ export function defaultGen3Config(
|
|
|
858
858
|
const base = defaultRound4Config(hh, opts)
|
|
859
859
|
const outDirName = opts.outDirName ?? 'gen3'
|
|
860
860
|
const proposers: ProposerSpec[] = [
|
|
861
|
-
{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude' },
|
|
861
|
+
{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude-code' },
|
|
862
862
|
{
|
|
863
863
|
name: 'mechanics-author',
|
|
864
864
|
profile: 'default-author.profile.json',
|
|
865
|
-
harness: 'claude',
|
|
865
|
+
harness: 'claude-code',
|
|
866
866
|
diagnosisSlice: 'mechanics',
|
|
867
867
|
lens: 'Focus on MECHANICS: worker lifecycle, sandbox/clone contracts, settlement and delivery paths. Prefer code-path fixes over prompt wording.',
|
|
868
868
|
},
|
|
869
869
|
{
|
|
870
870
|
name: 'prompts-author',
|
|
871
871
|
profile: 'default-author.profile.json',
|
|
872
|
-
harness: 'claude',
|
|
872
|
+
harness: 'claude-code',
|
|
873
873
|
diagnosisSlice: 'prompts',
|
|
874
874
|
lens: 'Focus on PROMPTS: worker/brain instruction wording, placement guidance, self-check discipline. Prefer prompt/instruction changes over code-path rewrites.',
|
|
875
875
|
},
|
|
@@ -945,10 +945,10 @@ export function defaultGen4Config(
|
|
|
945
945
|
): OuterLoopConfig {
|
|
946
946
|
const base = defaultGen3Config(hh, { outDirName: opts.outDirName ?? 'gen4' })
|
|
947
947
|
const proposers: ProposerSpec[] = [
|
|
948
|
-
{ name: 'claude-author', profile: 'default-author.profile.json', harness: 'claude' },
|
|
948
|
+
{ name: 'claude-author', profile: 'default-author.profile.json', harness: 'claude-code' },
|
|
949
949
|
{ name: 'glm-author', harness: 'opencode', model: 'zai-coding-plan/glm-5.2' },
|
|
950
950
|
...(opts.includeCodex === false ? [] : [{ name: 'codex-author', harness: 'codex' } satisfies ProposerSpec]),
|
|
951
|
-
{ name: 'merge-author', profile: 'default-author.profile.json', harness: 'claude', merge: true },
|
|
951
|
+
{ name: 'merge-author', profile: 'default-author.profile.json', harness: 'claude-code', merge: true },
|
|
952
952
|
]
|
|
953
953
|
return {
|
|
954
954
|
...base,
|
|
@@ -1263,7 +1263,7 @@ const CODEX_AMBIENT_AUTH_VARS = ['OPENAI_API_KEY', 'OPENAI_BASE_URL'] as const
|
|
|
1263
1263
|
|
|
1264
1264
|
export function proposerShotEnv(harness: OuterLoopConfig['proposerHarness']): NodeJS.ProcessEnv {
|
|
1265
1265
|
const env: NodeJS.ProcessEnv = { ...process.env }
|
|
1266
|
-
if (harness === 'claude') {
|
|
1266
|
+
if (harness === 'claude-code') {
|
|
1267
1267
|
for (const name of CLAUDE_AMBIENT_AUTH_VARS) delete env[name]
|
|
1268
1268
|
}
|
|
1269
1269
|
if (harness === 'codex') {
|
|
@@ -36,7 +36,7 @@ describe('run process-group timeout', () => {
|
|
|
36
36
|
'-e',
|
|
37
37
|
`const fs=require('node:fs'); const marker=process.argv[1]; process.on('SIGTERM',()=>{fs.writeFileSync(marker,'settled'); setTimeout(()=>process.exit(0),25)}); setInterval(()=>{},1000)`,
|
|
38
38
|
marker,
|
|
39
|
-
], { timeoutMs:
|
|
39
|
+
], { timeoutMs: 1_000, killGraceMs: 500 })
|
|
40
40
|
|
|
41
41
|
expect(result.code).toBe(TIMEOUT_RC)
|
|
42
42
|
expect(result.timedOut).toBe(true)
|
|
@@ -91,7 +91,7 @@ export interface ProposerSpec {
|
|
|
91
91
|
profile?: string
|
|
92
92
|
/** Required for harness-authored seats. Absent on an engine seat
|
|
93
93
|
* (`engine` set) — enforced both ways at generator construction. */
|
|
94
|
-
harness?: 'claude' | 'codex' | 'opencode'
|
|
94
|
+
harness?: 'claude-code' | 'codex' | 'opencode'
|
|
95
95
|
/** GEN-4 pinned model id, threaded to the harness CLI as `-m <model>` via
|
|
96
96
|
* the author profile's `model.default` (harnessInvocation maps it for all
|
|
97
97
|
* three harnesses). Unset = the CLI's own resolved model (its login/settings
|
|
@@ -201,7 +201,7 @@ export function loadAuthorProfile(spec: ProposerSpec): AgentProfile | undefined
|
|
|
201
201
|
|
|
202
202
|
/** The gen-2 author, codified: one bare-profile claude proposer. */
|
|
203
203
|
export function defaultProposers(): ProposerSpec[] {
|
|
204
|
-
return [{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude' }]
|
|
204
|
+
return [{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude-code' }]
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
/** The profile the author shot actually runs: the loaded profile (if any) with
|
|
@@ -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: 'claude', lens: 'Prefer code-path fixes.' }
|
|
76
|
+
const spec: ProposerSpec = { name: 'x', harness: 'claude-code', 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: 'claude' }
|
|
84
|
+
const spec: ProposerSpec = { name: 'x', harness: 'claude-code' }
|
|
85
85
|
expect(proposerBuildPrompt({ findings: [] }, spec)).not.toContain('AUTHORING LENS')
|
|
86
86
|
})
|
|
87
87
|
})
|
|
@@ -111,7 +111,7 @@ describe('resolveAuthorProfile (pinned models)', () => {
|
|
|
111
111
|
const spec: ProposerSpec = {
|
|
112
112
|
name: 'x',
|
|
113
113
|
profile: 'default-author.profile.json',
|
|
114
|
-
harness: 'claude',
|
|
114
|
+
harness: 'claude-code',
|
|
115
115
|
model: 'claude-fable-5',
|
|
116
116
|
}
|
|
117
117
|
const profile = resolveAuthorProfile(spec)
|
|
@@ -126,15 +126,15 @@ describe('resolveAuthorProfile (pinned models)', () => {
|
|
|
126
126
|
})
|
|
127
127
|
|
|
128
128
|
it('is byte-identical to loadAuthorProfile without a pin (gen-3 seats unchanged)', () => {
|
|
129
|
-
const spec: ProposerSpec = { name: 'x', profile: 'default-author.profile.json', harness: 'claude' }
|
|
129
|
+
const spec: ProposerSpec = { name: 'x', profile: 'default-author.profile.json', harness: 'claude-code' }
|
|
130
130
|
expect(resolveAuthorProfile(spec)).toEqual(loadAuthorProfile(spec))
|
|
131
|
-
expect(resolveAuthorProfile({ name: 'bare', harness: 'claude' })).toBeUndefined()
|
|
131
|
+
expect(resolveAuthorProfile({ name: 'bare', harness: 'claude-code' })).toBeUndefined()
|
|
132
132
|
})
|
|
133
133
|
})
|
|
134
134
|
|
|
135
135
|
describe('proposerBuildPrompt with pareto parents', () => {
|
|
136
136
|
it('appends the parents section (evidence + diffs) after the protocol prompt and lens', () => {
|
|
137
|
-
const spec: ProposerSpec = { name: 'x', harness: 'claude', lens: 'Prefer code-path fixes.' }
|
|
137
|
+
const spec: ProposerSpec = { name: 'x', harness: 'claude-code', lens: 'Prefer code-path fixes.' }
|
|
138
138
|
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
139
139
|
expect(prompt).toContain('DECLARED CHANGE-SPACE')
|
|
140
140
|
expect(prompt).toContain('PARETO PARENTS')
|
|
@@ -146,14 +146,14 @@ describe('proposerBuildPrompt with pareto parents', () => {
|
|
|
146
146
|
})
|
|
147
147
|
|
|
148
148
|
it('leaves the prompt untouched when no parents are seeded (gen-3 behavior)', () => {
|
|
149
|
-
const spec: ProposerSpec = { name: 'x', harness: 'claude' }
|
|
149
|
+
const spec: ProposerSpec = { name: 'x', harness: 'claude-code' }
|
|
150
150
|
expect(proposerBuildPrompt({ findings: [] }, spec)).not.toContain('PARETO PARENTS')
|
|
151
151
|
expect(parentsPromptSection(PARENTS)).toContain('measured evidence')
|
|
152
152
|
})
|
|
153
153
|
})
|
|
154
154
|
|
|
155
155
|
describe('mergeAuthorPrompt', () => {
|
|
156
|
-
const spec: ProposerSpec = { name: 'merge-author', harness: 'claude', merge: true }
|
|
156
|
+
const spec: ProposerSpec = { name: 'merge-author', harness: 'claude-code', merge: true }
|
|
157
157
|
|
|
158
158
|
it('keeps the change-space contract and presents BOTH parent diffs with the coherent-union task', () => {
|
|
159
159
|
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
@@ -182,11 +182,11 @@ describe('defaultGen4Config', () => {
|
|
|
182
182
|
expect(config.proposers).toHaveLength(4)
|
|
183
183
|
expect(config.populationSize).toBe(4)
|
|
184
184
|
const byName = Object.fromEntries(config.proposers!.map((p) => [p.name, p]))
|
|
185
|
-
expect(byName['claude-author']).toMatchObject({ harness: 'claude', profile: 'default-author.profile.json' })
|
|
185
|
+
expect(byName['claude-author']).toMatchObject({ harness: 'claude-code', profile: 'default-author.profile.json' })
|
|
186
186
|
expect(byName['claude-author']!.model).toBeUndefined()
|
|
187
187
|
expect(byName['glm-author']).toMatchObject({ harness: 'opencode', model: 'zai-coding-plan/glm-5.2' })
|
|
188
188
|
expect(byName['codex-author']).toMatchObject({ harness: 'codex' })
|
|
189
|
-
expect(byName['merge-author']).toMatchObject({ harness: 'claude', merge: true })
|
|
189
|
+
expect(byName['merge-author']).toMatchObject({ harness: 'claude-code', merge: true })
|
|
190
190
|
})
|
|
191
191
|
|
|
192
192
|
it('drops the codex seat (and shrinks the population) when includeCodex is false', () => {
|
|
@@ -216,7 +216,7 @@ describe('defaultGen4Config', () => {
|
|
|
216
216
|
|
|
217
217
|
describe('loadAuthorProfile', () => {
|
|
218
218
|
it('loads the committed default-author profile (bare: no prompt, no model)', () => {
|
|
219
|
-
const profile = loadAuthorProfile({ name: 'a', profile: 'default-author.profile.json', harness: 'claude' })
|
|
219
|
+
const profile = loadAuthorProfile({ name: 'a', profile: 'default-author.profile.json', harness: 'claude-code' })
|
|
220
220
|
expect(profile?.name).toBe('swe-arena-default-author')
|
|
221
221
|
expect(profile?.prompt).toBeUndefined()
|
|
222
222
|
expect(profile?.model).toBeUndefined()
|
|
@@ -224,7 +224,7 @@ describe('loadAuthorProfile', () => {
|
|
|
224
224
|
})
|
|
225
225
|
|
|
226
226
|
it('returns undefined without a profile path', () => {
|
|
227
|
-
expect(loadAuthorProfile({ name: 'a', harness: 'claude' })).toBeUndefined()
|
|
227
|
+
expect(loadAuthorProfile({ name: 'a', harness: 'claude-code' })).toBeUndefined()
|
|
228
228
|
})
|
|
229
229
|
|
|
230
230
|
it('fails loud when a non-codex proposer declares profile resources (they would be dropped)', async () => {
|
|
@@ -232,7 +232,7 @@ describe('loadAuthorProfile', () => {
|
|
|
232
232
|
try {
|
|
233
233
|
const path = join(dir, 'with-resources.json')
|
|
234
234
|
await writeFile(path, JSON.stringify({ name: 'r', resources: { files: [] } }))
|
|
235
|
-
expect(() => loadAuthorProfile({ name: 'a', profile: path, harness: 'claude' })).toThrow(/silently drop/)
|
|
235
|
+
expect(() => loadAuthorProfile({ name: 'a', profile: path, harness: 'claude-code' })).toThrow(/silently drop/)
|
|
236
236
|
} finally {
|
|
237
237
|
await rm(dir, { recursive: true, force: true })
|
|
238
238
|
}
|
|
@@ -302,7 +302,7 @@ describe('defaultGen3Config', () => {
|
|
|
302
302
|
|
|
303
303
|
it('defaultProposers codifies the gen-2 author: one bare-profile claude entry', () => {
|
|
304
304
|
expect(defaultProposers()).toEqual([
|
|
305
|
-
{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude' },
|
|
305
|
+
{ name: 'default-author', profile: 'default-author.profile.json', harness: 'claude-code' },
|
|
306
306
|
])
|
|
307
307
|
})
|
|
308
308
|
})
|
|
@@ -357,19 +357,25 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
357
357
|
|
|
358
358
|
it('authors ALL proposers concurrently in separate worktrees and applies each patch to its candidate slot', async () => {
|
|
359
359
|
const proposers: ProposerSpec[] = [
|
|
360
|
-
{ name: 'alpha', harness: 'claude' },
|
|
361
|
-
{ name: 'beta', harness: 'claude' },
|
|
360
|
+
{ name: 'alpha', harness: 'claude-code' },
|
|
361
|
+
{ name: 'beta', harness: 'claude-code' },
|
|
362
362
|
]
|
|
363
363
|
let inFlight = 0
|
|
364
364
|
let maxInFlight = 0
|
|
365
365
|
const seenWorktrees: string[] = []
|
|
366
|
+
let releaseAuthors!: () => void
|
|
367
|
+
const bothAuthorsStarted = new Promise<void>((resolve) => {
|
|
368
|
+
releaseAuthors = resolve
|
|
369
|
+
})
|
|
366
370
|
const gen = fanOutLoopsGenerator(baseConfig(proposers), {
|
|
367
371
|
author: async (proposer, args) => {
|
|
368
372
|
inFlight++
|
|
369
373
|
maxInFlight = Math.max(maxInFlight, inFlight)
|
|
370
374
|
seenWorktrees.push(args.worktreePath)
|
|
371
|
-
|
|
372
|
-
|
|
375
|
+
if (inFlight === proposers.length) releaseAuthors()
|
|
376
|
+
// Neither author may finish before both have started. A serial implementation
|
|
377
|
+
// therefore fails this test instead of passing or failing with machine speed.
|
|
378
|
+
await bothAuthorsStarted
|
|
373
379
|
// Inside the declared change-space (extensions/pi/**) so the always-on
|
|
374
380
|
// change-space pre-filter does not kill the candidate.
|
|
375
381
|
await mkdir(join(args.worktreePath, 'extensions', 'pi'), { recursive: true })
|
|
@@ -390,7 +396,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
390
396
|
|
|
391
397
|
expect(first).toMatchObject({ applied: true, label: 'alpha' })
|
|
392
398
|
expect(second).toMatchObject({ applied: true, label: 'beta' })
|
|
393
|
-
expect(first.rationale).toContain('proposer alpha (claude)')
|
|
399
|
+
expect(first.rationale).toContain('proposer alpha (claude-code)')
|
|
394
400
|
// Both patches landed on the SAME driver worktree across the two calls —
|
|
395
401
|
// the driver discards/finalizes between calls in production; here we only
|
|
396
402
|
// assert each call applied its own proposer's file.
|
|
@@ -401,8 +407,8 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
401
407
|
|
|
402
408
|
it('kills a candidate at the smoke pre-filter: applied=false, no patch applied, kill recorded with reason', async () => {
|
|
403
409
|
const proposers: ProposerSpec[] = [
|
|
404
|
-
{ name: 'good', harness: 'claude' },
|
|
405
|
-
{ name: 'bad', harness: 'claude' },
|
|
410
|
+
{ name: 'good', harness: 'claude-code' },
|
|
411
|
+
{ name: 'bad', harness: 'claude-code' },
|
|
406
412
|
]
|
|
407
413
|
const config = baseConfig(proposers)
|
|
408
414
|
config.prefilter = { enabled: true, smokeInstance: 'cheapest-of-set' }
|
|
@@ -455,7 +461,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
455
461
|
})
|
|
456
462
|
|
|
457
463
|
it('kills an out-of-space diff at the change-space pre-filter before any smoke spend', async () => {
|
|
458
|
-
const proposers: ProposerSpec[] = [{ name: 'rogue', harness: 'claude' }]
|
|
464
|
+
const proposers: ProposerSpec[] = [{ name: 'rogue', harness: 'claude-code' }]
|
|
459
465
|
const config = baseConfig(proposers)
|
|
460
466
|
config.prefilter = { enabled: true, smokeInstance: 'cheapest-of-set' }
|
|
461
467
|
let smokeRan = false
|
|
@@ -482,7 +488,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
482
488
|
})
|
|
483
489
|
|
|
484
490
|
it('returns applied:false without a kill when a proposer authors nothing', async () => {
|
|
485
|
-
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'idle', harness: 'claude' }]), {
|
|
491
|
+
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'idle', harness: 'claude-code' }]), {
|
|
486
492
|
author: async () => ({ applied: false, summary: '' }),
|
|
487
493
|
})
|
|
488
494
|
expect((await gen.generate(generatorArgs(0))).applied).toBe(false)
|
|
@@ -490,7 +496,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
490
496
|
})
|
|
491
497
|
|
|
492
498
|
it('fails loud when candidateIndex exceeds the proposer list (populationSize drift)', async () => {
|
|
493
|
-
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'only', harness: 'claude' }]), {
|
|
499
|
+
const gen = fanOutLoopsGenerator(baseConfig([{ name: 'only', harness: 'claude-code' }]), {
|
|
494
500
|
author: async () => ({ applied: false, summary: '' }),
|
|
495
501
|
})
|
|
496
502
|
await expect(gen.generate(generatorArgs(1))).rejects.toThrow(/populationSize must equal/)
|
|
@@ -514,7 +520,7 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
514
520
|
})
|
|
515
521
|
|
|
516
522
|
it('refuses a merge seat without >=2 materialized parents', () => {
|
|
517
|
-
const config = baseConfig([{ name: 'merge-author', harness: 'claude', merge: true }])
|
|
523
|
+
const config = baseConfig([{ name: 'merge-author', harness: 'claude-code', merge: true }])
|
|
518
524
|
expect(() => fanOutLoopsGenerator(config, { author: async () => ({ applied: false, summary: '' }) })).toThrow(
|
|
519
525
|
/merge proposer/,
|
|
520
526
|
)
|
|
@@ -533,8 +539,8 @@ describe('fanOutLoopsGenerator', () => {
|
|
|
533
539
|
it('rejects duplicate proposer names and empty proposer lists', () => {
|
|
534
540
|
expect(() =>
|
|
535
541
|
fanOutLoopsGenerator(baseConfig([
|
|
536
|
-
{ name: 'dup', harness: 'claude' },
|
|
537
|
-
{ name: 'dup', harness: 'claude' },
|
|
542
|
+
{ name: 'dup', harness: 'claude-code' },
|
|
543
|
+
{ name: 'dup', harness: 'claude-code' },
|
|
538
544
|
])),
|
|
539
545
|
).toThrow(/duplicate/)
|
|
540
546
|
expect(() => fanOutLoopsGenerator({ ...defaultRound4Config(), loopsRepo, outDir })).toThrow(/empty/)
|
|
@@ -585,7 +591,7 @@ describe('gen-5 prompt sections', () => {
|
|
|
585
591
|
}
|
|
586
592
|
|
|
587
593
|
it('appends EVIDENCE MAP + briefing + activation contract after the protocol prompt', () => {
|
|
588
|
-
const spec: ProposerSpec = { name: 'x', harness: 'claude' }
|
|
594
|
+
const spec: ProposerSpec = { name: 'x', harness: 'claude-code' }
|
|
589
595
|
const prompt = proposerBuildPrompt({ findings: [] }, spec, [], {
|
|
590
596
|
briefing,
|
|
591
597
|
activationGate: true,
|
|
@@ -598,7 +604,7 @@ describe('gen-5 prompt sections', () => {
|
|
|
598
604
|
})
|
|
599
605
|
|
|
600
606
|
it('the merge seat gets the gen-5 sections too', () => {
|
|
601
|
-
const spec: ProposerSpec = { name: 'merge-author', harness: 'claude', merge: true }
|
|
607
|
+
const spec: ProposerSpec = { name: 'merge-author', harness: 'claude-code', merge: true }
|
|
602
608
|
const prompt = proposerBuildPrompt({ findings: [] }, spec, PARENTS, {
|
|
603
609
|
briefing,
|
|
604
610
|
activationGate: true,
|
|
@@ -609,7 +615,7 @@ describe('gen-5 prompt sections', () => {
|
|
|
609
615
|
})
|
|
610
616
|
|
|
611
617
|
it('leaves gen-3/gen-4 prompts byte-identical when no extras are passed', () => {
|
|
612
|
-
const spec: ProposerSpec = { name: 'x', harness: 'claude' }
|
|
618
|
+
const spec: ProposerSpec = { name: 'x', harness: 'claude-code' }
|
|
613
619
|
const legacy = proposerBuildPrompt({ findings: [] }, spec, PARENTS)
|
|
614
620
|
expect(legacy).not.toContain('EVIDENCE MAP')
|
|
615
621
|
expect(legacy).not.toContain('ACTIVATION PREDICATE')
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import { readFileSync } from 'node:fs'
|
|
25
25
|
import { homedir } from 'node:os'
|
|
26
26
|
import { join } from 'node:path'
|
|
27
|
+
import { localHarnessExecutable } from '@tangle-network/agent-runtime/mcp'
|
|
27
28
|
import {
|
|
28
29
|
DEFAULT_GEPA_PYTHON,
|
|
29
30
|
isGepaSeat,
|
|
@@ -117,10 +118,13 @@ export async function captureProposerProvenance(
|
|
|
117
118
|
(h): h is NonNullable<ProposerSpec['harness']> => h !== undefined,
|
|
118
119
|
)
|
|
119
120
|
for (const harness of harnesses) {
|
|
120
|
-
|
|
121
|
+
// The harness id is not the binary name (`claude-code` runs `claude`); read the executable
|
|
122
|
+
// from the runtime's harness table rather than spawning the id.
|
|
123
|
+
const executable = localHarnessExecutable(harness)
|
|
124
|
+
const res = await exec(executable, ['--version'])
|
|
121
125
|
if (res.code !== 0) {
|
|
122
126
|
throw new Error(
|
|
123
|
-
`proposer provenance: '${
|
|
127
|
+
`proposer provenance: '${executable} --version' failed (rc=${res.code}) — the ${harness} seat cannot author. ` +
|
|
124
128
|
`stderr: ${res.stderr.slice(0, 300)}`,
|
|
125
129
|
)
|
|
126
130
|
}
|
|
@@ -163,7 +167,7 @@ export async function captureProposerProvenance(
|
|
|
163
167
|
harness: spec.harness,
|
|
164
168
|
pinnedModel: spec.model ?? null,
|
|
165
169
|
harnessVersion: versionByHarness.get(spec.harness!)!,
|
|
166
|
-
settingsModel: spec.harness === 'claude' && !spec.model ? readSettingsModel() : null,
|
|
170
|
+
settingsModel: spec.harness === 'claude-code' && !spec.model ? readSettingsModel() : null,
|
|
167
171
|
authStatus: (spec.harness !== undefined ? authByHarness.get(spec.harness) : undefined) ?? null,
|
|
168
172
|
merge: spec.merge === true,
|
|
169
173
|
}
|
|
@@ -9,10 +9,10 @@ import type { ProposerSpec } from './proposer-fanout.mts'
|
|
|
9
9
|
const ok = (stdout: string) => ({ code: 0, stdout, stderr: '' })
|
|
10
10
|
|
|
11
11
|
const gen4ish: ProposerSpec[] = [
|
|
12
|
-
{ name: 'claude-author', profile: 'default-author.profile.json', harness: 'claude' },
|
|
12
|
+
{ name: 'claude-author', profile: 'default-author.profile.json', harness: 'claude-code' },
|
|
13
13
|
{ name: 'glm-author', harness: 'opencode', model: 'zai-coding-plan/glm-5.2' },
|
|
14
14
|
{ name: 'codex-author', harness: 'codex' },
|
|
15
|
-
{ name: 'merge-author', profile: 'default-author.profile.json', harness: 'claude', merge: true },
|
|
15
|
+
{ name: 'merge-author', profile: 'default-author.profile.json', harness: 'claude-code', merge: true },
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
describe('captureProposerProvenance', () => {
|
|
@@ -31,7 +31,7 @@ describe('captureProposerProvenance', () => {
|
|
|
31
31
|
const byName = Object.fromEntries(record.proposers.map((p) => [p.name, p]))
|
|
32
32
|
// Claude seat: no pin — the CLI's resolved settings model is the record.
|
|
33
33
|
expect(byName['claude-author']).toMatchObject({
|
|
34
|
-
harness: 'claude',
|
|
34
|
+
harness: 'claude-code',
|
|
35
35
|
pinnedModel: null,
|
|
36
36
|
settingsModel: 'claude-fable-5',
|
|
37
37
|
harnessVersion: 'claude-version 9.9.9',
|
package/src/swe-code-improve.mts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Wiring (all verified in this worktree):
|
|
11
11
|
* - improve()/codeProposerFor + rawTraceContext come from the LOCAL agent-runtime build, linked into
|
|
12
12
|
* this bench's node_modules (bench/node_modules/@tangle-network/agent-runtime -> /home/drew/code/agent-runtime).
|
|
13
|
-
* - The candidate proposer is agenticGenerator(harness:'claude'), BUT the shipped runLocalHarness
|
|
13
|
+
* - The candidate proposer is agenticGenerator(harness:'claude-code'), BUT the shipped runLocalHarness
|
|
14
14
|
* spawns `claude --headless -p` and --headless is an unknown option on the current CLI (exit 1, no
|
|
15
15
|
* edits ever). We pass code.generator with a corrected runHarness that spawns
|
|
16
16
|
* `claude -p <prompt> --dangerously-skip-permissions` so the coding agent can actually edit the
|
|
@@ -286,7 +286,7 @@ async function main(): Promise<void> {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
const generator = agenticGenerator({
|
|
289
|
-
harness: 'claude',
|
|
289
|
+
harness: 'claude-code',
|
|
290
290
|
verify,
|
|
291
291
|
timeoutMs: harnessTimeoutMs,
|
|
292
292
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|