@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
package/src/swe-self-improve.mts
CHANGED
|
@@ -9,17 +9,42 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { mkdtempSync, rmSync } from 'node:fs'
|
|
11
11
|
import { join } from 'node:path'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import type { AgentProfile } from '@tangle-network/agent-interface'
|
|
13
|
+
import {
|
|
14
|
+
refine,
|
|
15
|
+
runAgentic,
|
|
16
|
+
runStrategyEvolution,
|
|
17
|
+
sample,
|
|
18
|
+
strategyAuthorSystemPrompt,
|
|
19
|
+
} from '@tangle-network/agent-runtime/kernel'
|
|
14
20
|
import { createSweBenchEnvironment } from './swe-bench-env'
|
|
15
21
|
|
|
16
22
|
async function main(): Promise<void> {
|
|
17
23
|
const routerKey = process.env.TANGLE_API_KEY
|
|
18
24
|
if (!routerKey) throw new Error('TANGLE_API_KEY required (worker + author call the router)')
|
|
19
25
|
const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
20
|
-
const workerModel = process.env.WORKER_MODEL ?? '
|
|
21
|
-
const authorModel = process.env.AUTHOR_MODEL ?? '
|
|
26
|
+
const workerModel = process.env.WORKER_MODEL ?? 'deepseek-v4-flash'
|
|
27
|
+
const authorModel = process.env.AUTHOR_MODEL ?? 'deepseek-v4-flash'
|
|
22
28
|
const innerTurns = Number(process.env.INNER_TURNS ?? 40)
|
|
29
|
+
const workerProfile: AgentProfile = {
|
|
30
|
+
name: 'swe-worker',
|
|
31
|
+
harness: 'cli-base',
|
|
32
|
+
model: {
|
|
33
|
+
provider: 'tangle-router',
|
|
34
|
+
default: workerModel,
|
|
35
|
+
metadata: { maxTokens: 8000, maxTurns: innerTurns },
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
const authorProfile = (model: string, name: string): AgentProfile => ({
|
|
39
|
+
name,
|
|
40
|
+
harness: 'cli-base',
|
|
41
|
+
model: {
|
|
42
|
+
provider: 'tangle-router',
|
|
43
|
+
default: model,
|
|
44
|
+
metadata: { maxTokens: 8000 },
|
|
45
|
+
},
|
|
46
|
+
prompt: { systemPrompt: strategyAuthorSystemPrompt },
|
|
47
|
+
})
|
|
23
48
|
const { environment, tasks } = await createSweBenchEnvironment(Number(process.env.POOL_N ?? 80))
|
|
24
49
|
|
|
25
50
|
if (process.env.CALIBRATE === '1') {
|
|
@@ -29,7 +54,15 @@ async function main(): Promise<void> {
|
|
|
29
54
|
let resolved = 0
|
|
30
55
|
for (const t of ts) {
|
|
31
56
|
const t0 = Date.now()
|
|
32
|
-
const r = await runAgentic({
|
|
57
|
+
const r = await runAgentic({
|
|
58
|
+
surface: environment,
|
|
59
|
+
task: t,
|
|
60
|
+
strategy: refine,
|
|
61
|
+
routerBaseUrl,
|
|
62
|
+
routerKey,
|
|
63
|
+
workerProfile,
|
|
64
|
+
budget: 1,
|
|
65
|
+
})
|
|
33
66
|
if (r.resolved) resolved++
|
|
34
67
|
console.log(` ${t.id.padEnd(32)} resolved=${r.resolved} completions=${r.completions} shots=${r.shots} (${Math.round((Date.now() - t0) / 1000)}s)`)
|
|
35
68
|
}
|
|
@@ -46,12 +79,14 @@ async function main(): Promise<void> {
|
|
|
46
79
|
tasks,
|
|
47
80
|
trainN: Number(process.env.TRAIN_N ?? 6),
|
|
48
81
|
holdoutN: Number(process.env.HOLDOUT_N ?? 8),
|
|
49
|
-
worker: { routerBaseUrl, routerKey,
|
|
82
|
+
worker: { routerBaseUrl, routerKey, workerProfile },
|
|
50
83
|
author: {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
84
|
+
profile: authorProfile(authorModel, 'swe-strategy-author'),
|
|
85
|
+
executor: { backend: 'router', routerBaseUrl, routerKey },
|
|
86
|
+
fallbackProfile: authorProfile(
|
|
87
|
+
process.env.AUTHOR_FALLBACK ?? 'deepseek-v4-flash',
|
|
88
|
+
'swe-strategy-author-fallback',
|
|
89
|
+
),
|
|
55
90
|
},
|
|
56
91
|
baselines: [sample, refine],
|
|
57
92
|
budget: Number(process.env.BUDGET ?? 2),
|
package/src/swe-stream.mts
CHANGED
|
@@ -66,7 +66,7 @@ import { join } from 'node:path'
|
|
|
66
66
|
import { promisify } from 'node:util'
|
|
67
67
|
import { applyKnowledgeWriteBlocks, buildKnowledgeIndex, initKnowledgeBase, searchKnowledge } from '@tangle-network/agent-knowledge'
|
|
68
68
|
import type { AgenticSurface, AgenticTask, ArtifactHandle, SurfaceScore } from '@tangle-network/agent-runtime/kernel'
|
|
69
|
-
import { refine, runAgentic } from '@tangle-network/agent-runtime/kernel'
|
|
69
|
+
import { defaultAnalystInstruction, refine, runAgentic } from '@tangle-network/agent-runtime/kernel'
|
|
70
70
|
import type { BenchTask } from './benchmarks/types'
|
|
71
71
|
import { createSweBenchEnvironment, resolveImageForMetadata, SWE_SEED_PROMPT } from './swe-bench-env'
|
|
72
72
|
import {
|
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
tail,
|
|
83
83
|
zaiChatRaw,
|
|
84
84
|
} from './swe-jail'
|
|
85
|
+
import { withBenchProfile } from './router-turn'
|
|
85
86
|
|
|
86
87
|
const exec = promisify(execFile)
|
|
87
88
|
|
|
@@ -274,9 +275,29 @@ const makeTransport =
|
|
|
274
275
|
counter.guardedMsgs += assertNoHiddenLeak(marks, msgs)
|
|
275
276
|
// Inject the honored reasoning-budget knob (thinking) here at the single shared worker
|
|
276
277
|
// chokepoint: makeTransport is byte-identical across arms F and L, so the budget is symmetric.
|
|
278
|
+
const model = String(body.model ?? '')
|
|
279
|
+
const systemPrompt = msgs.find((message) => message.role === 'system')?.content
|
|
280
|
+
const toolNames = Array.isArray(body.tools)
|
|
281
|
+
? (body.tools as Array<{ function?: { name?: unknown } }>).flatMap((tool) =>
|
|
282
|
+
typeof tool.function?.name === 'string' ? [tool.function.name] : [],
|
|
283
|
+
)
|
|
284
|
+
: []
|
|
277
285
|
const { json, attempts } = await zaiChatRaw(
|
|
278
286
|
{ base: ZAI_BASE, key: ZAI_KEY, timeoutMs: LLM_TIMEOUT_MS, deadlineAt: guard.deadlineAt },
|
|
279
287
|
{ ...body, ...WORKER_REASONING },
|
|
288
|
+
{
|
|
289
|
+
name: 'swe-stream-worker',
|
|
290
|
+
harness: 'cli-base',
|
|
291
|
+
model: {
|
|
292
|
+
provider: 'zai',
|
|
293
|
+
default: model,
|
|
294
|
+
reasoningEffort: REASONING_ON ? 'high' : 'none',
|
|
295
|
+
},
|
|
296
|
+
...(typeof systemPrompt === 'string' ? { prompt: { systemPrompt } } : {}),
|
|
297
|
+
...(toolNames.length > 0
|
|
298
|
+
? { tools: Object.fromEntries(toolNames.map((name) => [name, true])) }
|
|
299
|
+
: {}),
|
|
300
|
+
},
|
|
280
301
|
)
|
|
281
302
|
counter.calls += 1
|
|
282
303
|
counter.httpAttempts += attempts
|
|
@@ -345,10 +366,44 @@ async function emitAttempt(
|
|
|
345
366
|
}
|
|
346
367
|
const task: AgenticTask = {
|
|
347
368
|
id: bt.id,
|
|
348
|
-
systemPrompt: SWE_SEED_PROMPT,
|
|
349
369
|
userPrompt: cfg.promptAppendix ? `${bt.prompt}\n\n${cfg.promptAppendix}` : bt.prompt,
|
|
350
370
|
meta: { instanceId: bt.id },
|
|
351
371
|
}
|
|
372
|
+
const workerProfile = withBenchProfile(
|
|
373
|
+
{
|
|
374
|
+
name: 'swe-stream-worker',
|
|
375
|
+
harness: 'cli-base',
|
|
376
|
+
model: {
|
|
377
|
+
provider: 'zai',
|
|
378
|
+
default: WORKER_MODEL,
|
|
379
|
+
reasoningEffort: REASONING_ON ? 'high' : 'none',
|
|
380
|
+
},
|
|
381
|
+
tools: { list_files: true, read_file: true, edit_file: true },
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
systemPrompt: SWE_SEED_PROMPT,
|
|
385
|
+
maxTokens: MAX_TOKENS,
|
|
386
|
+
maxTurns: INNER_TURNS,
|
|
387
|
+
temperature: cfg.temperature,
|
|
388
|
+
extraBody: WORKER_REASONING,
|
|
389
|
+
},
|
|
390
|
+
)
|
|
391
|
+
const analystProfile = withBenchProfile(
|
|
392
|
+
{
|
|
393
|
+
name: 'swe-stream-analyst',
|
|
394
|
+
harness: 'cli-base',
|
|
395
|
+
model: {
|
|
396
|
+
provider: 'zai',
|
|
397
|
+
default: WORKER_MODEL,
|
|
398
|
+
reasoningEffort: REASONING_ON ? 'high' : 'none',
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
systemPrompt: defaultAnalystInstruction,
|
|
403
|
+
maxTokens: MAX_TOKENS,
|
|
404
|
+
extraBody: WORKER_REASONING,
|
|
405
|
+
},
|
|
406
|
+
)
|
|
352
407
|
let error: string | undefined
|
|
353
408
|
try {
|
|
354
409
|
const r = await runAgentic({
|
|
@@ -357,10 +412,8 @@ async function emitAttempt(
|
|
|
357
412
|
strategy: refine,
|
|
358
413
|
routerBaseUrl: 'zai-direct', // unused: the `complete` transport short-circuits the router
|
|
359
414
|
routerKey: 'zai-direct',
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
temperature: cfg.temperature,
|
|
363
|
-
innerTurns: INNER_TURNS,
|
|
415
|
+
workerProfile,
|
|
416
|
+
analystProfile,
|
|
364
417
|
budget: 1,
|
|
365
418
|
complete: makeTransport(cfg.marks, counter, guard),
|
|
366
419
|
})
|
|
@@ -643,6 +696,11 @@ async function acquireRepro(
|
|
|
643
696
|
const { json } = await zaiChatRaw(
|
|
644
697
|
{ base: ZAI_BASE, key: ZAI_KEY, timeoutMs: LLM_TIMEOUT_MS, deadlineAt },
|
|
645
698
|
{ model: REPRO_MODEL, max_tokens: MAX_TOKENS, temperature: 0.2, messages },
|
|
699
|
+
{
|
|
700
|
+
name: 'swe-reproduction-author',
|
|
701
|
+
model: { provider: 'zai', default: REPRO_MODEL, reasoningEffort: 'high' },
|
|
702
|
+
prompt: { systemPrompt: reproAuthorSystem(REPRO_TIMEOUT_S) },
|
|
703
|
+
},
|
|
646
704
|
)
|
|
647
705
|
const d = json as { choices?: Array<{ message?: { content?: string } }>; usage?: { prompt_tokens?: number; completion_tokens?: number } }
|
|
648
706
|
out.authorCalls += 1
|
|
@@ -810,6 +868,10 @@ async function superviseRepair(
|
|
|
810
868
|
const { json, attempts } = await zaiChatRaw(
|
|
811
869
|
{ base: ZAI_BASE, key: ZAI_KEY, timeoutMs: LLM_TIMEOUT_MS, deadlineAt },
|
|
812
870
|
{ model: SUPERVISOR_MODEL, max_tokens: SUPERVISOR_MAX_TOKENS, temperature: 0.2, messages },
|
|
871
|
+
{
|
|
872
|
+
name: 'swe-repair-supervisor',
|
|
873
|
+
model: { provider: 'zai', default: SUPERVISOR_MODEL, reasoningEffort: 'high' },
|
|
874
|
+
},
|
|
813
875
|
)
|
|
814
876
|
const d = json as { choices?: Array<{ message?: { content?: string } }>; usage?: { prompt_tokens?: number; completion_tokens?: number } }
|
|
815
877
|
const planRaw = d.choices?.[0]?.message?.content ?? ''
|
package/src/swe-structural.mts
CHANGED
|
@@ -50,7 +50,7 @@ import { appendFileSync, existsSync, readFileSync, rmSync, writeFileSync } from
|
|
|
50
50
|
import { join } from 'node:path'
|
|
51
51
|
import { promisify } from 'node:util'
|
|
52
52
|
import type { AgenticSurface, AgenticTask, ArtifactHandle, SurfaceScore } from '@tangle-network/agent-runtime/kernel'
|
|
53
|
-
import { refine, runAgentic } from '@tangle-network/agent-runtime/kernel'
|
|
53
|
+
import { defaultAnalystInstruction, refine, runAgentic } from '@tangle-network/agent-runtime/kernel'
|
|
54
54
|
import type { BenchTask } from './benchmarks/types'
|
|
55
55
|
import { createSweBenchEnvironment, resolveImageForMetadata, SWE_SEED_PROMPT } from './swe-bench-env'
|
|
56
56
|
import {
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
tail,
|
|
64
64
|
zaiChatRaw,
|
|
65
65
|
} from './swe-jail'
|
|
66
|
+
import { withBenchProfile } from './router-turn'
|
|
66
67
|
|
|
67
68
|
const exec = promisify(execFile)
|
|
68
69
|
|
|
@@ -138,7 +139,26 @@ const makeTransport =
|
|
|
138
139
|
async (body: Record<string, unknown>): Promise<unknown> => {
|
|
139
140
|
const msgs = (body.messages ?? []) as Array<{ role?: string; content?: unknown }>
|
|
140
141
|
counter.guardedMsgs += assertNoHiddenLeak(marks, msgs)
|
|
141
|
-
const
|
|
142
|
+
const model = String(body.model ?? '')
|
|
143
|
+
const systemPrompt = msgs.find((message) => message.role === 'system')?.content
|
|
144
|
+
const toolNames = Array.isArray(body.tools)
|
|
145
|
+
? (body.tools as Array<{ function?: { name?: unknown } }>).flatMap((tool) =>
|
|
146
|
+
typeof tool.function?.name === 'string' ? [tool.function.name] : [],
|
|
147
|
+
)
|
|
148
|
+
: []
|
|
149
|
+
const { json, attempts } = await zaiChatRaw(
|
|
150
|
+
{ base: ZAI_BASE, key: ZAI_KEY, timeoutMs: LLM_TIMEOUT_MS },
|
|
151
|
+
body,
|
|
152
|
+
{
|
|
153
|
+
name: 'swe-structural-worker',
|
|
154
|
+
harness: 'cli-base',
|
|
155
|
+
model: { provider: 'zai', default: model, reasoningEffort: 'high' },
|
|
156
|
+
...(typeof systemPrompt === 'string' ? { prompt: { systemPrompt } } : {}),
|
|
157
|
+
...(toolNames.length > 0
|
|
158
|
+
? { tools: Object.fromEntries(toolNames.map((name) => [name, true])) }
|
|
159
|
+
: {}),
|
|
160
|
+
},
|
|
161
|
+
)
|
|
142
162
|
counter.calls += 1
|
|
143
163
|
counter.httpAttempts += attempts
|
|
144
164
|
const u = (json as { usage?: { prompt_tokens?: number; completion_tokens?: number } }).usage
|
|
@@ -204,10 +224,31 @@ async function emitAttempt(
|
|
|
204
224
|
}
|
|
205
225
|
const task: AgenticTask = {
|
|
206
226
|
id: bt.id,
|
|
207
|
-
systemPrompt: SWE_SEED_PROMPT,
|
|
208
227
|
userPrompt: cfg.promptAppendix ? `${bt.prompt}\n\n${cfg.promptAppendix}` : bt.prompt,
|
|
209
228
|
meta: { instanceId: bt.id },
|
|
210
229
|
}
|
|
230
|
+
const workerProfile = withBenchProfile(
|
|
231
|
+
{
|
|
232
|
+
name: 'swe-structural-worker',
|
|
233
|
+
harness: 'cli-base',
|
|
234
|
+
model: { provider: 'zai', default: MODEL, reasoningEffort: 'high' },
|
|
235
|
+
tools: { list_files: true, read_file: true, edit_file: true },
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
systemPrompt: SWE_SEED_PROMPT,
|
|
239
|
+
maxTokens: MAX_TOKENS,
|
|
240
|
+
maxTurns: INNER_TURNS,
|
|
241
|
+
temperature: cfg.temperature,
|
|
242
|
+
},
|
|
243
|
+
)
|
|
244
|
+
const analystProfile = withBenchProfile(
|
|
245
|
+
{
|
|
246
|
+
name: 'swe-structural-analyst',
|
|
247
|
+
harness: 'cli-base',
|
|
248
|
+
model: { provider: 'zai', default: MODEL, reasoningEffort: 'high' },
|
|
249
|
+
},
|
|
250
|
+
{ systemPrompt: defaultAnalystInstruction, maxTokens: MAX_TOKENS },
|
|
251
|
+
)
|
|
211
252
|
let error: string | undefined
|
|
212
253
|
try {
|
|
213
254
|
const r = await runAgentic({
|
|
@@ -216,10 +257,8 @@ async function emitAttempt(
|
|
|
216
257
|
strategy: refine,
|
|
217
258
|
routerBaseUrl: 'zai-direct', // unused: the `complete` transport short-circuits the router
|
|
218
259
|
routerKey: 'zai-direct',
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
temperature: cfg.temperature,
|
|
222
|
-
innerTurns: INNER_TURNS,
|
|
260
|
+
workerProfile,
|
|
261
|
+
analystProfile,
|
|
223
262
|
budget: 1,
|
|
224
263
|
complete: makeTransport(cfg.marks, counter),
|
|
225
264
|
})
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* provided container id and reports the captured process artifact back through the runtime.
|
|
6
6
|
*/
|
|
7
7
|
import { spawn } from 'node:child_process'
|
|
8
|
+
import { agentProfileSchema } from '@tangle-network/agent-interface'
|
|
8
9
|
import type {
|
|
9
10
|
Agent,
|
|
10
11
|
AgentProfile,
|
|
@@ -17,6 +18,8 @@ import type {
|
|
|
17
18
|
Runtime,
|
|
18
19
|
Spend,
|
|
19
20
|
} from '../../src/runtime/index'
|
|
21
|
+
import { harnessInvocation, type LocalHarness } from '../../src/mcp/local-harness'
|
|
22
|
+
import { assertExecutableAgentProfile } from '../../src/runtime/supervise/model-policy'
|
|
20
23
|
|
|
21
24
|
export interface TbExecOutput {
|
|
22
25
|
/** Primary artifact consumed by drivers that read `{ content }`. */
|
|
@@ -40,7 +43,8 @@ export interface TbContainerConfig {
|
|
|
40
43
|
readonly workdir?: string
|
|
41
44
|
readonly shell?: string
|
|
42
45
|
readonly env?: Readonly<Record<string, string>>
|
|
43
|
-
|
|
46
|
+
/** Shell setup required inside the existing task container before the canonical invocation. */
|
|
47
|
+
readonly commandPrefix?: ReadonlyArray<string>
|
|
44
48
|
readonly parseUsage?: ParseUsage
|
|
45
49
|
readonly budgetExempt?: boolean
|
|
46
50
|
readonly dockerBin?: string
|
|
@@ -62,7 +66,7 @@ function resolveContainerId(config: TbContainerConfig): string {
|
|
|
62
66
|
return id.trim()
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
function
|
|
69
|
+
function taskToPrompt(task: unknown): string {
|
|
66
70
|
if (typeof task === 'string') return task
|
|
67
71
|
if (task && typeof task === 'object') {
|
|
68
72
|
const obj = task as Record<string, unknown>
|
|
@@ -73,6 +77,31 @@ function taskToCommand(task: unknown): string {
|
|
|
73
77
|
return JSON.stringify(task)
|
|
74
78
|
}
|
|
75
79
|
|
|
80
|
+
function shellQuote(value: string): string {
|
|
81
|
+
return `'${value.replace(/'/g, `'\\''`)}'`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function profileCommand(
|
|
85
|
+
profile: AgentProfile,
|
|
86
|
+
task: unknown,
|
|
87
|
+
prefix: ReadonlyArray<string> = [],
|
|
88
|
+
): string {
|
|
89
|
+
const harness = profile.harness
|
|
90
|
+
if (harness !== 'claude-code' && harness !== 'codex' && harness !== 'opencode') {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`tbContainerExecutor: profile harness ${JSON.stringify(harness)} is not available in the task container`,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
const invocation = harnessInvocation(
|
|
96
|
+
harness as LocalHarness,
|
|
97
|
+
profile,
|
|
98
|
+
taskToPrompt(task),
|
|
99
|
+
{ dangerouslySkipPermissions: true },
|
|
100
|
+
)
|
|
101
|
+
const run = [invocation.command, ...invocation.args].map(shellQuote).join(' ')
|
|
102
|
+
return [...prefix, `exec ${run}`].join('; ')
|
|
103
|
+
}
|
|
104
|
+
|
|
76
105
|
/**
|
|
77
106
|
* Compose the `docker exec` argv that runs `command` inside `containerId` via `<shell> -c`.
|
|
78
107
|
* Exported so tests can verify the container path without a live Docker daemon.
|
|
@@ -113,6 +142,8 @@ export function createTbContainerExecutor(
|
|
|
113
142
|
config: TbContainerConfig = {},
|
|
114
143
|
): ExecutorFactory<TbExecOutput> {
|
|
115
144
|
return (_spec: AgentSpec, ctx: ExecutorContext): Executor<TbExecOutput> => {
|
|
145
|
+
const profile = agentProfileSchema.parse(_spec.profile)
|
|
146
|
+
assertExecutableAgentProfile(profile, 'tbContainerExecutor')
|
|
116
147
|
const containerId = resolveContainerId(config)
|
|
117
148
|
const dockerBin = config.dockerBin ?? 'docker'
|
|
118
149
|
const metered = config.parseUsage !== undefined
|
|
@@ -133,7 +164,7 @@ export function createTbContainerExecutor(
|
|
|
133
164
|
runtime,
|
|
134
165
|
budgetExempt,
|
|
135
166
|
execute(task, signal): Promise<ExecutorResult<TbExecOutput>> {
|
|
136
|
-
const command = (config.
|
|
167
|
+
const command = profileCommand(profile, task, config.commandPrefix)
|
|
137
168
|
const args = buildTbDockerExecArgs(containerId, command, {
|
|
138
169
|
...(config.shell ? { shell: config.shell } : {}),
|
|
139
170
|
...(config.workdir ? { workdir: config.workdir } : {}),
|
|
@@ -221,9 +252,10 @@ export function createTbContainerExecutor(
|
|
|
221
252
|
export function makeTbContainerWorkerAgent(config: TbContainerConfig = {}): MakeWorkerAgent {
|
|
222
253
|
const factory = createTbContainerExecutor(config)
|
|
223
254
|
return (rawProfile) => {
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
const
|
|
255
|
+
const profile = agentProfileSchema.parse(rawProfile)
|
|
256
|
+
assertExecutableAgentProfile(profile, 'tbContainerWorkerAgent')
|
|
257
|
+
const name = profile.name ?? 'tb-worker'
|
|
258
|
+
const spec: AgentSpec = { profile, harness: null }
|
|
227
259
|
const ctx: ExecutorContext = { signal: new AbortController().signal, seams: {} }
|
|
228
260
|
const executor = factory(spec, ctx)
|
|
229
261
|
return { name, act: async () => '', executorSpec: { ...spec, executor } } as Agent<
|
|
@@ -14,7 +14,14 @@ import {
|
|
|
14
14
|
type TbExecOutput,
|
|
15
15
|
} from './tb-container-executor.mts'
|
|
16
16
|
|
|
17
|
-
const spec: AgentSpec = {
|
|
17
|
+
const spec: AgentSpec = {
|
|
18
|
+
profile: {
|
|
19
|
+
name: 'tb-test-worker',
|
|
20
|
+
harness: 'opencode',
|
|
21
|
+
model: { provider: 'tangle-router', default: 'deepseek-v4-flash' },
|
|
22
|
+
},
|
|
23
|
+
harness: null,
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
function context(): ExecutorContext {
|
|
20
27
|
return { signal: new AbortController().signal, seams: {} }
|
|
@@ -78,10 +85,11 @@ printf 'argv:%s\\n' "$*"
|
|
|
78
85
|
assert.equal(metered.budgetExempt, false, 'usage parser makes the executor metered')
|
|
79
86
|
const meteredResult = await executeOneShot(metered, { command: 'echo hello' })
|
|
80
87
|
assert.equal(meteredResult.out.containerId, 'cid')
|
|
81
|
-
assert.
|
|
88
|
+
assert.match(meteredResult.out.command, /'echo hello'/)
|
|
89
|
+
assert.match(meteredResult.out.command, /'tangle-router\/deepseek-v4-flash'/)
|
|
82
90
|
assert.match(
|
|
83
91
|
meteredResult.out.stdout,
|
|
84
|
-
/argv:exec -i --workdir \/work -e OPENAI_BASE_URL=http:\/\/router\.test cid \/bin\/sh -c echo hello/,
|
|
92
|
+
/argv:exec -i --workdir \/work -e OPENAI_BASE_URL=http:\/\/router\.test cid \/bin\/sh -c exec 'opencode' 'run' 'echo hello' '-m' 'tangle-router\/deepseek-v4-flash'/,
|
|
85
93
|
)
|
|
86
94
|
assert.deepEqual(meteredResult.spent.tokens, { input: 7, output: 11 })
|
|
87
95
|
assert.equal(meteredResult.spent.usd, 0.004)
|
|
@@ -20,7 +20,6 @@ import { makeTbContainerWorkerAgent, type ParseUsage } from './tb-container-exec
|
|
|
20
20
|
const CONTAINER_ID = (process.env.TB_TARGET_CONTAINER ?? '').trim()
|
|
21
21
|
const ROUTER_KEY = process.env.OPENAI_API_KEY ?? ''
|
|
22
22
|
const ROUTER_BASE = process.env.OPENAI_BASE_URL ?? 'https://router.tangle.tools/v1'
|
|
23
|
-
const WORKER_MODEL = process.env.WORKER_MODEL ?? 'zai-coding-plan/glm-5.2'
|
|
24
23
|
const DOCKER_BRIDGE_GATEWAY = process.env.DOCKER_BRIDGE_GATEWAY ?? '172.17.0.1'
|
|
25
24
|
const PORT_FILE = process.env.TB_SIDECAR_PORT_FILE ?? '.tb-sidecar-port'
|
|
26
25
|
const LOG_FILE = process.env.TB_SIDECAR_LOG ?? '.tb-sidecar-events.jsonl'
|
|
@@ -42,30 +41,6 @@ function logEvent(kind: string, payload: unknown): void {
|
|
|
42
41
|
console.error(`[sidecar] ${kind} ${JSON.stringify(payload).slice(0, 300)}`)
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
function workerWrapCommand(task: unknown): string {
|
|
46
|
-
const instr =
|
|
47
|
-
typeof task === 'string'
|
|
48
|
-
? task
|
|
49
|
-
: task && typeof task === 'object'
|
|
50
|
-
? ((): string => {
|
|
51
|
-
const o = task as Record<string, unknown>
|
|
52
|
-
for (const k of ['task', 'prompt', 'content', 'command', 'message', 'instruction']) {
|
|
53
|
-
if (typeof o[k] === 'string') return o[k] as string
|
|
54
|
-
}
|
|
55
|
-
return JSON.stringify(task)
|
|
56
|
-
})()
|
|
57
|
-
: String(task)
|
|
58
|
-
const q = `'${instr.replace(/'/g, `'\\''`)}'`
|
|
59
|
-
return [
|
|
60
|
-
'export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"',
|
|
61
|
-
'{ [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; } 2>/dev/null || true',
|
|
62
|
-
'{ [ -s /root/.nvm/nvm.sh ] && . /root/.nvm/nvm.sh; } 2>/dev/null || true',
|
|
63
|
-
`export OPENCODE_CONFIG=${WORKER_CONFIG_PATH}`,
|
|
64
|
-
`cd ${WORKER_WORKDIR} 2>/dev/null || true`,
|
|
65
|
-
`opencode --model ${WORKER_MODEL} --format json run ${q}`,
|
|
66
|
-
].join('; ')
|
|
67
|
-
}
|
|
68
|
-
|
|
69
44
|
const parseWorkerUsage: ParseUsage = ({ stdout }) => {
|
|
70
45
|
let input = 0
|
|
71
46
|
let output = 0
|
|
@@ -115,9 +90,18 @@ async function main(): Promise<void> {
|
|
|
115
90
|
const makeWorkerAgent = makeTbContainerWorkerAgent({
|
|
116
91
|
containerId: CONTAINER_ID,
|
|
117
92
|
workdir: WORKER_WORKDIR,
|
|
118
|
-
|
|
93
|
+
commandPrefix: [
|
|
94
|
+
'export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"',
|
|
95
|
+
'{ [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; } 2>/dev/null || true',
|
|
96
|
+
'{ [ -s /root/.nvm/nvm.sh ] && . /root/.nvm/nvm.sh; } 2>/dev/null || true',
|
|
97
|
+
],
|
|
119
98
|
parseUsage: parseWorkerUsage,
|
|
120
|
-
env: {
|
|
99
|
+
env: {
|
|
100
|
+
OPENAI_API_KEY: ROUTER_KEY,
|
|
101
|
+
OPENAI_BASE_URL: ROUTER_BASE,
|
|
102
|
+
OPENCODE_CONFIG: WORKER_CONFIG_PATH,
|
|
103
|
+
HOME: '/root',
|
|
104
|
+
},
|
|
121
105
|
runtime: 'tb-container',
|
|
122
106
|
})
|
|
123
107
|
|
package/src/terminal-compare.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* judge here.
|
|
8
8
|
*
|
|
9
9
|
* Per task:
|
|
10
|
-
* ROUND 1 (blind) —
|
|
10
|
+
* ROUND 1 (blind) — Terminal-Bench runs one exact AgentProfile → parse results.json →
|
|
11
11
|
* resolved_1. This IS blind pass@1.
|
|
12
12
|
* ROUND r (refine, only if blind failed AND rounds>1) — extract a compact
|
|
13
13
|
* summary of round 1's commands + terminal state + the
|
|
@@ -26,11 +26,12 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import { execFile } from 'node:child_process'
|
|
29
|
-
import { mkdir, readFile, readdir, stat } from 'node:fs/promises'
|
|
29
|
+
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'
|
|
30
30
|
import { join } from 'node:path'
|
|
31
31
|
import { fileURLToPath } from 'node:url'
|
|
32
32
|
|
|
33
33
|
import { homedir } from 'node:os'
|
|
34
|
+
import { agentProfileSchema } from '@tangle-network/agent-interface'
|
|
34
35
|
|
|
35
36
|
import { appendRunRecord, type AttemptRecord, type RunRecord } from './corpus'
|
|
36
37
|
import { runPool } from './run-pool'
|
|
@@ -38,16 +39,36 @@ import { runPool } from './run-pool'
|
|
|
38
39
|
const BENCH_ROOT = fileURLToPath(new URL('..', import.meta.url))
|
|
39
40
|
const TB = join(BENCH_ROOT, '.venv', 'bin', 'tb')
|
|
40
41
|
const RUNS_DIR = join(BENCH_ROOT, 'runs')
|
|
41
|
-
const
|
|
42
|
+
const PROFILE_AGENT_IMPORT = 'tb_agents.opencode_router_agent:OpenCodeRouterAgent'
|
|
42
43
|
// The durable learning-flywheel corpus (docs/learning-flywheel.md). terminal-bench
|
|
43
44
|
// is bench-orchestrated (tb owns the containers) so it cannot use buildRunRecord,
|
|
44
|
-
// which consumes
|
|
45
|
+
// which consumes runAgentRounds Iterations; instead each task's per-round tb artifacts are
|
|
45
46
|
// folded into one RunRecord here so the corpus is genuinely cross-benchmark
|
|
46
|
-
// (finsearch-loop.ts writes the same store from the
|
|
47
|
+
// (finsearch-loop.ts writes the same store from the runAgentRounds path).
|
|
47
48
|
const CORPUS = process.env.CORPUS ?? join(BENCH_ROOT, 'corpus', 'terminal.jsonl')
|
|
48
49
|
|
|
49
50
|
const DATASET = process.env.TB_DATASET ?? 'terminal-bench-core==0.1.1'
|
|
50
|
-
const MODEL = process.env.TB_MODEL ?? 'deepseek
|
|
51
|
+
const MODEL = process.env.TB_MODEL ?? 'deepseek-v4-flash'
|
|
52
|
+
const PROVIDER = process.env.TB_PROVIDER ?? 'tangle-router'
|
|
53
|
+
const PROFILE = agentProfileSchema.parse({
|
|
54
|
+
name: 'terminal-compare-worker',
|
|
55
|
+
harness: 'opencode',
|
|
56
|
+
model: { provider: PROVIDER, default: MODEL },
|
|
57
|
+
prompt: {
|
|
58
|
+
systemPrompt:
|
|
59
|
+
'Solve the Terminal-Bench task completely in the provided container and verify the result before finishing.',
|
|
60
|
+
},
|
|
61
|
+
permission: {
|
|
62
|
+
edit: 'allow',
|
|
63
|
+
bash: 'allow',
|
|
64
|
+
webfetch: 'allow',
|
|
65
|
+
read: 'allow',
|
|
66
|
+
write: 'allow',
|
|
67
|
+
external_directory: 'allow',
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
const PROFILE_MODEL = MODEL.includes('/') ? MODEL : `${PROVIDER}/${MODEL}`
|
|
71
|
+
const PROFILE_PATH = join(RUNS_DIR, 'terminal-compare.profile.json')
|
|
51
72
|
const ROUNDS = Math.max(1, Number(process.env.ROUNDS ?? 2))
|
|
52
73
|
const CONCURRENCY = Math.max(1, Number(process.env.CONCURRENCY ?? 2))
|
|
53
74
|
// Per-round wall-clock cap (ms). tb itself enforces task timeouts; this is the
|
|
@@ -307,8 +328,8 @@ function informativeFailureMode(mode: string | undefined): string | undefined {
|
|
|
307
328
|
|
|
308
329
|
/**
|
|
309
330
|
* Build and persist one flywheel RunRecord per task from terminal-compare's own
|
|
310
|
-
* round data — no buildRunRecord (that consumes
|
|
311
|
-
*
|
|
331
|
+
* round data — no buildRunRecord (that consumes runAgentRounds Iterations; tb is not
|
|
332
|
+
* runAgentRounds-shaped). condition = 'refine@k' when a refine budget was available,
|
|
312
333
|
* 'blind' when only the single blind round can run (ROUNDS===1). Appended once
|
|
313
334
|
* per task; never throws into the run (corpus capture must not fail the bench).
|
|
314
335
|
*/
|
|
@@ -330,7 +351,7 @@ async function captureRunRecord(
|
|
|
330
351
|
benchmark: 'terminal-bench',
|
|
331
352
|
instanceId: taskId,
|
|
332
353
|
condition: ROUNDS > 1 ? `refine@${ROUNDS}` : 'blind',
|
|
333
|
-
model: MODEL,
|
|
354
|
+
model: PROFILE.model?.default ?? MODEL,
|
|
334
355
|
blindResolved,
|
|
335
356
|
resolved: last ? last.resolved : blindResolved,
|
|
336
357
|
attempts,
|
|
@@ -392,7 +413,7 @@ async function runRound(taskId: string, round: number, prior: string): Promise<R
|
|
|
392
413
|
'run',
|
|
393
414
|
'-d', DATASET,
|
|
394
415
|
'--task-id', taskId,
|
|
395
|
-
'-m',
|
|
416
|
+
'-m', PROFILE_MODEL,
|
|
396
417
|
'--output-path', RUNS_DIR,
|
|
397
418
|
'--run-id', runId,
|
|
398
419
|
'--n-concurrent', '1',
|
|
@@ -404,14 +425,12 @@ async function runRound(taskId: string, round: number, prior: string): Promise<R
|
|
|
404
425
|
// literals in values. Hex-encode the prior with an 'h' sentinel so it survives
|
|
405
426
|
// the split and is kept as a string; the refine agent decodes it.
|
|
406
427
|
const priorHex = `h${Buffer.from(prior, 'utf8').toString('hex')}`
|
|
407
|
-
const args =
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
'--agent-kwarg', `prior_attempt_hex=${priorHex}`,
|
|
414
|
-
]
|
|
428
|
+
const args = [
|
|
429
|
+
...baseArgs,
|
|
430
|
+
'--agent-import-path', PROFILE_AGENT_IMPORT,
|
|
431
|
+
'--agent-kwarg', `profile_path=${PROFILE_PATH}`,
|
|
432
|
+
...(round > 1 ? ['--agent-kwarg', `prior_attempt_hex=${priorHex}`] : []),
|
|
433
|
+
]
|
|
415
434
|
await runTb(args, runId)
|
|
416
435
|
const resolved = await readResolved(outputDir, taskId)
|
|
417
436
|
const trialDir = await findTrialDir(outputDir, taskId)
|
|
@@ -457,6 +476,7 @@ async function solveTask(taskId: string): Promise<{
|
|
|
457
476
|
|
|
458
477
|
async function main(): Promise<void> {
|
|
459
478
|
await mkdir(RUNS_DIR, { recursive: true })
|
|
479
|
+
await writeFile(PROFILE_PATH, JSON.stringify(PROFILE, null, 2) + '\n')
|
|
460
480
|
// Validate auth up front — fail loud before spending a single container.
|
|
461
481
|
must('OPENAI_API_KEY')
|
|
462
482
|
|