@tangle-network/agent-bench 0.7.1 → 0.8.1
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 +12 -0
- package/HARNESS.md +2 -2
- package/dist/adapters.js +6 -2
- package/dist/adapters.js.map +1 -1
- 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/mcad-bench.d.ts +106 -0
- package/dist/benchmarks/mcad-bench.js +569 -0
- package/dist/benchmarks/mcad-bench.js.map +1 -0
- package/dist/benchmarks/mcad-cq-bench.d.ts +82 -0
- package/dist/benchmarks/mcad-cq-bench.js +339 -0
- package/dist/benchmarks/mcad-cq-bench.js.map +1 -0
- package/dist/benchmarks/mcad-cq-golds.d.ts +36 -0
- package/dist/benchmarks/mcad-cq-golds.js +342 -0
- package/dist/benchmarks/mcad-cq-golds.js.map +1 -0
- package/dist/benchmarks/mcad-golds.d.ts +20 -0
- package/dist/benchmarks/mcad-golds.js +318 -0
- package/dist/benchmarks/mcad-golds.js.map +1 -0
- package/dist/benchmarks/mcad-tasks.d.ts +66 -0
- package/dist/benchmarks/mcad-tasks.js +508 -0
- package/dist/benchmarks/mcad-tasks.js.map +1 -0
- 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/adapters.ts +11 -0
- 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/mcad-bench.test.mts +455 -0
- package/src/benchmarks/mcad-bench.ts +561 -0
- package/src/benchmarks/mcad-cq-bench.ts +423 -0
- package/src/benchmarks/mcad-cq-golds.ts +374 -0
- package/src/benchmarks/mcad-cq.test.mts +386 -0
- package/src/benchmarks/mcad-golds.ts +359 -0
- package/src/benchmarks/mcad-tasks.ts +490 -0
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QUANT-ARENA campaign loop — the improvement loop embodied for trading
|
|
3
|
-
* strategies. One command runs: strategy authors (
|
|
3
|
+
* strategies. One command runs: strategy authors (Runtime profile-pinned)
|
|
4
4
|
* propose candidate strategies (v2 `onBar` contract, driven incrementally by
|
|
5
5
|
* driver.ts) -> every candidate passes a two-stage leak audit -> survivors
|
|
6
6
|
* are scored on K bootstrap in-sample windows against the pinned baselines
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* are never the acceptance currency.
|
|
16
16
|
*
|
|
17
17
|
* tsx src/quant-arena/quant-loop.mts --out <dir> [--candidates 2] [--seed 20260722]
|
|
18
|
-
* [--author-model
|
|
18
|
+
* [--author-model glm-5.2] [--audit-model glm-5.2] [--skip-llm-audit]
|
|
19
19
|
*
|
|
20
20
|
* Kernel reuse (import, not copy — see src/swe-arena/):
|
|
21
21
|
* - cost accounting: the lib's durable CostLedger (createRunCostLedger) +
|
|
@@ -38,12 +38,12 @@ import { join } from 'node:path'
|
|
|
38
38
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
39
39
|
import process from 'node:process'
|
|
40
40
|
import { createRunCostLedger, fsCampaignStorage } from '@tangle-network/agent-eval/campaign'
|
|
41
|
+
import { agentProfileSchema, type AgentProfile } from '@tangle-network/agent-interface'
|
|
42
|
+
import { collectAgentTurn, createExecutor, streamAgentTurn } from '@tangle-network/agent-runtime/kernel'
|
|
41
43
|
import { loadCampaignCells } from '../swe-arena/cell-evidence.mts'
|
|
42
44
|
import { reconcileCrashOrphansOnDisk } from '../swe-arena/ledger-orphans.mts'
|
|
43
45
|
import { loadLedgerReceipts } from '../swe-arena/manifest.mts'
|
|
44
|
-
import {
|
|
45
|
-
import { proposerShotEnv } from '../swe-arena/outer-loop.mts'
|
|
46
|
-
import { run } from '../swe-arena/proc.ts'
|
|
46
|
+
import { resolveAuthorProfile, type ProposerSpec } from '../swe-arena/proposer-fanout.mts'
|
|
47
47
|
import { runBacktest, statsForRange, type BacktestConfig, type RangeStats } from './backtest.ts'
|
|
48
48
|
import { loadInSample, type AlignedBars } from './data.ts'
|
|
49
49
|
import { loadStrategyFile } from './driver.ts'
|
|
@@ -57,6 +57,9 @@ import * as equalWeight from './strategies/equal-weight/strategy.ts'
|
|
|
57
57
|
import * as smaCrossover from './strategies/sma-crossover/strategy.ts'
|
|
58
58
|
|
|
59
59
|
export const QUANT_PROFILES_DIR = fileURLToPath(new URL('./profiles', import.meta.url))
|
|
60
|
+
const DEFAULT_AUTHOR_PROFILE = fileURLToPath(
|
|
61
|
+
new URL('../swe-arena/profiles/default-author.profile.json', import.meta.url),
|
|
62
|
+
)
|
|
60
63
|
|
|
61
64
|
// ---------------------------------------------------------------------------
|
|
62
65
|
// Config.
|
|
@@ -88,11 +91,17 @@ export const PINNED_BASELINES: Record<string, GenerateSignals> = {
|
|
|
88
91
|
/** The two demo author seats: the plain author and the quant lens. */
|
|
89
92
|
export function defaultQuantProposers(): ProposerSpec[] {
|
|
90
93
|
return [
|
|
91
|
-
{
|
|
94
|
+
{
|
|
95
|
+
name: 'default-author',
|
|
96
|
+
profile: DEFAULT_AUTHOR_PROFILE,
|
|
97
|
+
harness: 'pi',
|
|
98
|
+
model: 'glm-5.2',
|
|
99
|
+
},
|
|
92
100
|
{
|
|
93
101
|
name: 'quant-researcher',
|
|
94
102
|
profile: join(QUANT_PROFILES_DIR, 'quant-researcher.profile.json'),
|
|
95
|
-
harness: '
|
|
103
|
+
harness: 'pi',
|
|
104
|
+
model: 'glm-5.2',
|
|
96
105
|
lens:
|
|
97
106
|
'Favor ONE economically-motivated effect (trend, mean reversion, vol targeting) with few parameters. ' +
|
|
98
107
|
'State the regime in which it should work and keep turnover low enough that 15bps a side cannot eat the edge.',
|
|
@@ -110,8 +119,8 @@ export function defaultConfig(outDir: string): QuantLoopConfig {
|
|
|
110
119
|
warmupDays: 120,
|
|
111
120
|
costBps: 10,
|
|
112
121
|
slippageBps: 5,
|
|
113
|
-
authorModel: '
|
|
114
|
-
auditModel: '
|
|
122
|
+
authorModel: 'glm-5.2',
|
|
123
|
+
auditModel: 'glm-5.2',
|
|
115
124
|
skipLlmAudit: false,
|
|
116
125
|
authorTimeoutMs: 480_000,
|
|
117
126
|
auditTimeoutMs: 240_000,
|
|
@@ -183,10 +192,10 @@ export async function loadNotebookRows(notebookPath: string): Promise<Array<Reco
|
|
|
183
192
|
const log = (msg: string): void => console.log(`[${new Date().toISOString().slice(11, 19)}] ${msg}`)
|
|
184
193
|
|
|
185
194
|
// ---------------------------------------------------------------------------
|
|
186
|
-
//
|
|
195
|
+
// Exact-profile shots (author + auditor) — metered paid calls through Runtime + the run ledger.
|
|
187
196
|
// ---------------------------------------------------------------------------
|
|
188
197
|
|
|
189
|
-
interface
|
|
198
|
+
interface ProfileShotOutcome {
|
|
190
199
|
text: string
|
|
191
200
|
model: string
|
|
192
201
|
inputTokens: number
|
|
@@ -197,64 +206,81 @@ interface ClaudeShotOutcome {
|
|
|
197
206
|
|
|
198
207
|
type Ledger = ReturnType<typeof createRunCostLedger>
|
|
199
208
|
|
|
200
|
-
|
|
209
|
+
function withModel(profile: AgentProfile, model: string, name = profile.name): AgentProfile {
|
|
210
|
+
return agentProfileSchema.parse({
|
|
211
|
+
...profile,
|
|
212
|
+
...(name ? { name } : {}),
|
|
213
|
+
model: { ...profile.model, default: model },
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function quantAuditProfile(model: string): AgentProfile {
|
|
218
|
+
return agentProfileSchema.parse({
|
|
219
|
+
name: 'quant-leak-auditor',
|
|
220
|
+
harness: 'pi',
|
|
221
|
+
model: { provider: 'tangle-router', default: model },
|
|
222
|
+
prompt: {
|
|
223
|
+
systemPrompt:
|
|
224
|
+
'Audit the supplied trading strategy for look-ahead bias and nondeterminism. Follow the requested JSON response contract exactly.',
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function profileShot(opts: {
|
|
201
230
|
prompt: string
|
|
202
|
-
|
|
203
|
-
systemPrompt?: string
|
|
231
|
+
profile: AgentProfile
|
|
204
232
|
timeoutMs: number
|
|
205
233
|
cwd: string
|
|
206
|
-
}): Promise<
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
'
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const res = await run('claude', argv, {
|
|
219
|
-
stdin: opts.prompt,
|
|
234
|
+
}): Promise<ProfileShotOutcome> {
|
|
235
|
+
const bridgeUrl = process.env.CLI_BRIDGE_URL ?? process.env.BRIDGE_URL
|
|
236
|
+
const bridgeBearer = process.env.CLI_BRIDGE_BEARER ?? process.env.BRIDGE_BEARER
|
|
237
|
+
if (!bridgeUrl || !bridgeBearer) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
'quant profile shots require CLI_BRIDGE_URL/BRIDGE_URL and CLI_BRIDGE_BEARER/BRIDGE_BEARER',
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
const factory = createExecutor({
|
|
243
|
+
backend: 'bridge',
|
|
244
|
+
bridgeUrl,
|
|
245
|
+
bridgeBearer,
|
|
220
246
|
cwd: opts.cwd,
|
|
221
|
-
env: proposerShotEnv('claude-code'),
|
|
222
247
|
timeoutMs: opts.timeoutMs,
|
|
223
248
|
})
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
249
|
+
const turn = await collectAgentTurn(
|
|
250
|
+
streamAgentTurn(
|
|
251
|
+
{ kind: 'executor', factory, profile: opts.profile },
|
|
252
|
+
opts.prompt,
|
|
253
|
+
{ timeoutMs: opts.timeoutMs },
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
if (turn.status !== 'completed') {
|
|
257
|
+
throw new Error(turn.error?.message ?? `quant profile shot ended with ${turn.status}`)
|
|
232
258
|
}
|
|
233
|
-
|
|
234
|
-
const usage = (parsed.usage ?? {}) as Record<string, unknown>
|
|
235
|
-
const num = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0)
|
|
259
|
+
const cachedTokens = Number(turn.usage.promptCache?.readTokens ?? 0)
|
|
236
260
|
return {
|
|
237
|
-
text:
|
|
238
|
-
model:
|
|
239
|
-
inputTokens:
|
|
240
|
-
outputTokens:
|
|
241
|
-
cachedTokens:
|
|
242
|
-
costUsd:
|
|
261
|
+
text: turn.finalText,
|
|
262
|
+
model: turn.usage.model ?? opts.profile.model?.default ?? 'unknown',
|
|
263
|
+
inputTokens: turn.usage.input,
|
|
264
|
+
outputTokens: turn.usage.output,
|
|
265
|
+
cachedTokens: Number.isFinite(cachedTokens) ? cachedTokens : 0,
|
|
266
|
+
costUsd: turn.usage.costUsd ?? null,
|
|
243
267
|
}
|
|
244
268
|
}
|
|
245
269
|
|
|
246
|
-
async function
|
|
270
|
+
async function meteredProfileShot(
|
|
247
271
|
ledger: Ledger,
|
|
248
272
|
meta: { phase: string; actor: string; tags: Record<string, string> },
|
|
249
|
-
opts: Parameters<typeof
|
|
250
|
-
): Promise<{ outcome:
|
|
251
|
-
const
|
|
273
|
+
opts: Parameters<typeof profileShot>[0],
|
|
274
|
+
): Promise<{ outcome: ProfileShotOutcome; costUsd: number | null }> {
|
|
275
|
+
const model = opts.profile.model?.default
|
|
276
|
+
if (!model) throw new Error('meteredProfileShot: profile.model.default is required')
|
|
277
|
+
const paid = await ledger.runPaidCall<ProfileShotOutcome>({
|
|
252
278
|
channel: 'driver',
|
|
253
279
|
phase: meta.phase,
|
|
254
280
|
actor: meta.actor,
|
|
255
|
-
model
|
|
281
|
+
model,
|
|
256
282
|
tags: meta.tags,
|
|
257
|
-
execute: () =>
|
|
283
|
+
execute: () => profileShot(opts),
|
|
258
284
|
receipt: (v) => ({
|
|
259
285
|
model: v.model,
|
|
260
286
|
inputTokens: v.inputTokens,
|
|
@@ -379,12 +405,12 @@ async function llmLeakAudit(
|
|
|
379
405
|
code: string,
|
|
380
406
|
): Promise<{ verdict: 'clean' | 'leak' | 'inconclusive'; evidence: string }> {
|
|
381
407
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
382
|
-
const { outcome } = await
|
|
408
|
+
const { outcome } = await meteredProfileShot(
|
|
383
409
|
ledger,
|
|
384
|
-
{ phase: 'audit.leak', actor: 'leak-auditor:
|
|
410
|
+
{ phase: 'audit.leak', actor: 'leak-auditor:runtime', tags: { candidateId, attempt: String(attempt) } },
|
|
385
411
|
{
|
|
386
412
|
prompt: AUDIT_PROMPT_HEADER + '```ts\n' + code + '\n```',
|
|
387
|
-
|
|
413
|
+
profile: quantAuditProfile(config.auditModel),
|
|
388
414
|
timeoutMs: config.auditTimeoutMs,
|
|
389
415
|
cwd: config.outDir,
|
|
390
416
|
},
|
|
@@ -630,7 +656,9 @@ async function runQuantCampaignWithWorker(worker: VbtWorker, config: QuantLoopCo
|
|
|
630
656
|
const rows: CandidateRow[] = []
|
|
631
657
|
|
|
632
658
|
for (const proposer of config.proposers) {
|
|
633
|
-
const
|
|
659
|
+
const sourceProfile = resolveAuthorProfile(proposer)
|
|
660
|
+
if (!sourceProfile) throw new Error(`quant proposer ${proposer.name}: exact profile is required`)
|
|
661
|
+
const profile = withModel(sourceProfile, config.authorModel, `quant-${proposer.name}`)
|
|
634
662
|
for (let shot = 0; shot < config.candidatesPerProposer; shot++) {
|
|
635
663
|
nTried += 1
|
|
636
664
|
const candidateId = `cand-${String(nTried).padStart(3, '0')}-${proposer.name}`
|
|
@@ -642,7 +670,7 @@ async function runQuantCampaignWithWorker(worker: VbtWorker, config: QuantLoopCo
|
|
|
642
670
|
at: new Date().toISOString(),
|
|
643
671
|
candidateId,
|
|
644
672
|
proposer: proposer.name,
|
|
645
|
-
authorModel: config.authorModel,
|
|
673
|
+
authorModel: profile.model?.default ?? config.authorModel,
|
|
646
674
|
strategyPath: null,
|
|
647
675
|
sha256: null,
|
|
648
676
|
authoringCostUsd: null,
|
|
@@ -663,13 +691,12 @@ async function runQuantCampaignWithWorker(worker: VbtWorker, config: QuantLoopCo
|
|
|
663
691
|
nTried,
|
|
664
692
|
...(proposer.lens ? { lens: proposer.lens } : {}),
|
|
665
693
|
})
|
|
666
|
-
const { outcome, costUsd } = await
|
|
694
|
+
const { outcome, costUsd } = await meteredProfileShot(
|
|
667
695
|
ledger,
|
|
668
696
|
{ phase: 'search.proposal', actor: `proposer-shot:${proposer.name}`, tags: { candidateId } },
|
|
669
697
|
{
|
|
670
698
|
prompt,
|
|
671
|
-
|
|
672
|
-
...(profile?.prompt?.systemPrompt ? { systemPrompt: profile.prompt.systemPrompt } : {}),
|
|
699
|
+
profile,
|
|
673
700
|
timeoutMs: config.authorTimeoutMs,
|
|
674
701
|
cwd: config.outDir,
|
|
675
702
|
},
|
|
@@ -798,7 +825,7 @@ if (isMain) {
|
|
|
798
825
|
if (!outDir) {
|
|
799
826
|
console.error(
|
|
800
827
|
'usage: tsx src/quant-arena/quant-loop.mts --out <dir> [--candidates 2] [--seed 20260722] ' +
|
|
801
|
-
'[--author-model
|
|
828
|
+
'[--author-model glm-5.2] [--audit-model glm-5.2] [--skip-llm-audit] # SPENDS: author + audit shots',
|
|
802
829
|
)
|
|
803
830
|
process.exit(2)
|
|
804
831
|
}
|
package/src/research-shot.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One research rollout as a reusable primitive: 2-step RAG — (1) provider-pinned web
|
|
3
3
|
* search via the router's proven `/v1/search?provider=<id>` + `web_fetch` of the top-K
|
|
4
|
-
* result pages, (2) answer with that evidence
|
|
4
|
+
* result pages, (2) answer with that evidence through Runtime's profile-bound turn (no tools on the
|
|
5
5
|
* answer call → `content` always present, so a search arm differs from the parametric
|
|
6
6
|
* control ONLY by the evidence). Pure router HTTP (bearer `TANGLE_API_KEY`).
|
|
7
7
|
*
|
|
8
8
|
* Shared by the off-sandbox RAG leaderboard (`research-gate.mts`) and the router-backed
|
|
9
9
|
* loop executor (`router-executor.ts`), so both score the identical retrieve→answer body
|
|
10
10
|
* — the only difference is who drives the rounds (a flat best-of-k pool vs the real
|
|
11
|
-
* `
|
|
11
|
+
* `runAgentRounds` kernel with analyst steering).
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { runBenchRouterTurn } from './router-turn'
|
|
14
14
|
|
|
15
15
|
export interface ShotCfg {
|
|
16
16
|
model: string
|
|
@@ -109,15 +109,25 @@ export async function runResearchShot(prompt: string, taskId: string, attempt: n
|
|
|
109
109
|
: 'Answer from your own knowledge. ') +
|
|
110
110
|
'If you are not fully certain, still COMMIT to your single best estimate — never refuse, defer, or reply with a question.'
|
|
111
111
|
const userContent = useSearch && context ? `${prompt}\n\n=== WEB SEARCH RESULTS (provider: ${cfg.search}) ===\n${context}` : prompt
|
|
112
|
-
const
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
const turn = await runBenchRouterTurn(
|
|
113
|
+
{
|
|
114
|
+
routerBaseUrl: cfg.routerBaseUrl,
|
|
115
|
+
routerKey: cfg.routerKey,
|
|
116
|
+
profile: {
|
|
117
|
+
name: 'research-shot-answerer',
|
|
118
|
+
harness: 'cli-base',
|
|
119
|
+
model: {
|
|
120
|
+
provider: 'tangle-router',
|
|
121
|
+
default: cfg.model,
|
|
122
|
+
metadata: { temperature: cfg.temperature },
|
|
123
|
+
},
|
|
124
|
+
prompt: { systemPrompt: commit },
|
|
125
|
+
},
|
|
126
|
+
...(cfg.timeoutMs ? { timeoutMs: cfg.timeoutMs } : {}),
|
|
127
|
+
},
|
|
128
|
+
userContent,
|
|
119
129
|
)
|
|
120
|
-
const answer =
|
|
130
|
+
const answer = turn.finalText.trim()
|
|
121
131
|
const ok = answer.length > 0
|
|
122
132
|
return { taskId, attempt, answer, ok, searches, wallMs: Date.now() - startedAt, ...(ok ? {} : { detail: `empty answer (searches=${searches})` }) }
|
|
123
133
|
} catch (err) {
|
package/src/resolve-client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The ONE harness → worker-client mapping for the bench layer. Every bench entrypoint
|
|
3
|
-
* (rsi.ts, run.ts, …) derives the `SandboxClient` the kernel's `
|
|
3
|
+
* (rsi.ts, run.ts, …) derives the `SandboxClient` the kernel's `runAgentRounds` drives from a single
|
|
4
4
|
* selector instead of hand-rolling the branch:
|
|
5
5
|
*
|
|
6
6
|
* - `router` → OFF-BOX: a router chat-completion as the leaf executor, presented
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* - `bridge` → OFF-BOX: a local cli-bridge fronting a harness CLI
|
|
12
12
|
* (opencode/kimi-code/…) as the leaf executor. Same resumable
|
|
13
13
|
* `bridgeExecutor` the supervisor uses; harness+model ride the
|
|
14
|
-
*
|
|
14
|
+
* exact harness/provider/model come from the turn's AgentProfile.
|
|
15
15
|
* - anything else (`sandbox`/a BackendType) → IN-BOX: a real `Sandbox`. The in-box backend
|
|
16
16
|
* TYPE (opencode/codex/…) is set separately on the `AgentRunSpec`;
|
|
17
|
-
* this only decides off-box-vs-in-box transport for `
|
|
17
|
+
* this only decides off-box-vs-in-box transport for `runAgentRounds`.
|
|
18
18
|
*
|
|
19
19
|
* Centralizing it here means a new entrypoint gets the full off-box/in-box matrix for free and
|
|
20
20
|
* the mapping can't drift between callers.
|
|
@@ -33,7 +33,6 @@ export interface ResolveBenchClientOptions {
|
|
|
33
33
|
backend: string
|
|
34
34
|
routerBaseUrl: string
|
|
35
35
|
routerKey: string
|
|
36
|
-
model: string
|
|
37
36
|
/** When set on the `router` backend, the off-box worker becomes a `router-tools` agentic loop
|
|
38
37
|
* with a live `web_search` tool backed by this provider (`you`/`exa`/…). */
|
|
39
38
|
searchProvider?: string
|
|
@@ -47,7 +46,7 @@ export interface ResolveBenchClientOptions {
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
export function resolveBenchClient(opts: ResolveBenchClientOptions): SandboxClient {
|
|
50
|
-
const { backend, routerBaseUrl, routerKey,
|
|
49
|
+
const { backend, routerBaseUrl, routerKey, searchProvider } = opts
|
|
51
50
|
if (backend === 'router') {
|
|
52
51
|
if (searchProvider) {
|
|
53
52
|
return inlineSandboxClient(
|
|
@@ -55,13 +54,12 @@ export function resolveBenchClient(opts: ResolveBenchClientOptions): SandboxClie
|
|
|
55
54
|
backend: 'router-tools',
|
|
56
55
|
routerBaseUrl,
|
|
57
56
|
routerKey,
|
|
58
|
-
model,
|
|
59
57
|
tools: [webSearchTool],
|
|
60
58
|
executeToolCall: makeSearchExecutor({ routerBaseUrl, routerKey, provider: searchProvider }),
|
|
61
59
|
}),
|
|
62
60
|
)
|
|
63
61
|
}
|
|
64
|
-
return inlineSandboxClient(createExecutor({ backend: 'router', routerBaseUrl, routerKey
|
|
62
|
+
return inlineSandboxClient(createExecutor({ backend: 'router', routerBaseUrl, routerKey }))
|
|
65
63
|
}
|
|
66
64
|
if (backend === 'bridge') {
|
|
67
65
|
// bench's bearer fallback (`?? routerKey`) resolves first, then the shared
|
|
@@ -70,7 +68,7 @@ export function resolveBenchClient(opts: ResolveBenchClientOptions): SandboxClie
|
|
|
70
68
|
if (!bridgeBearer) throw new Error("resolveBenchClient: backend 'bridge' needs bridgeBearer or routerKey")
|
|
71
69
|
return resolveSandboxClient({
|
|
72
70
|
backend: 'bridge',
|
|
73
|
-
bridge: { url: opts.bridgeUrl, bearer: bridgeBearer,
|
|
71
|
+
bridge: { url: opts.bridgeUrl, bearer: bridgeBearer, timeoutMs: opts.timeoutMs },
|
|
74
72
|
})
|
|
75
73
|
}
|
|
76
74
|
return new Sandbox({
|
|
@@ -2,7 +2,12 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
|
2
2
|
import { tmpdir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
assertMintedLines,
|
|
7
|
+
assertRolloutLine,
|
|
8
|
+
claudeProjectSlug,
|
|
9
|
+
toSftRows,
|
|
10
|
+
} from '@tangle-network/agent-eval/rollout'
|
|
6
11
|
import { backfillSweArena } from './backfill-swe-arena.mts'
|
|
7
12
|
|
|
8
13
|
let dir: string
|
|
@@ -262,7 +267,7 @@ describe('backfillSweArena', () => {
|
|
|
262
267
|
|
|
263
268
|
// The dataset is immediately trainable: reward==1 trainable-split lines
|
|
264
269
|
// with transcripts → SFT rows (candidate worker + proposer here).
|
|
265
|
-
expect(toSftRows(lines)).toHaveLength(2)
|
|
270
|
+
expect(toSftRows(assertMintedLines(lines))).toHaveLength(2)
|
|
266
271
|
})
|
|
267
272
|
|
|
268
273
|
it('joins a respawned worker cwd once, not once per recovered entry', async () => {
|
package/src/router-executor.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Why off-sandbox: research is retrieval, not in-box code execution — it never
|
|
8
8
|
* needed a box, and a real sandbox box reaches only the router (egress allowlist,
|
|
9
9
|
* ops-board #976), so it cannot web-search natively anyway. Driving the loop with
|
|
10
|
-
* this executor instead of a box gives the REAL `
|
|
10
|
+
* this executor instead of a box gives the REAL `runAgentRounds` kernel full `rounds` +
|
|
11
11
|
* analyst steering (the depth regime), search working, no sandbox dependency.
|
|
12
12
|
*
|
|
13
13
|
* This is a BYO `Executor` over `runResearchShot`; `inlineSandboxClient` supplies
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AgentProfile,
|
|
3
|
+
agentProfileSchema,
|
|
4
|
+
type ReasoningEffort,
|
|
5
|
+
} from '@tangle-network/agent-interface'
|
|
6
|
+
import {
|
|
7
|
+
collectAgentTurn,
|
|
8
|
+
createExecutor,
|
|
9
|
+
streamAgentTurn,
|
|
10
|
+
type CollectedAgentTurn,
|
|
11
|
+
type ToolSpec,
|
|
12
|
+
} from '@tangle-network/agent-runtime/kernel'
|
|
13
|
+
|
|
14
|
+
/** Bench-local target shorthand; Runtime still executes only the exact profile below. */
|
|
15
|
+
export interface BenchRouterTarget {
|
|
16
|
+
routerBaseUrl: string
|
|
17
|
+
routerKey: string
|
|
18
|
+
profile: AgentProfile
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BenchRouterTurnConfig extends BenchRouterTarget {
|
|
22
|
+
tools?: ReadonlyArray<ToolSpec>
|
|
23
|
+
timeoutMs?: number
|
|
24
|
+
signal?: AbortSignal
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BenchProfileSettings {
|
|
28
|
+
systemPrompt?: string
|
|
29
|
+
temperature?: number
|
|
30
|
+
maxTokens?: number
|
|
31
|
+
retry?: {
|
|
32
|
+
maxAttempts?: number
|
|
33
|
+
initialBackoffMs?: number
|
|
34
|
+
maxBackoffMs?: number
|
|
35
|
+
jitter?: number
|
|
36
|
+
retryStatuses?: ReadonlyArray<number>
|
|
37
|
+
requestTimeoutMs?: number
|
|
38
|
+
}
|
|
39
|
+
maxTurns?: number
|
|
40
|
+
seed?: number
|
|
41
|
+
reasoningEffort?: ReasoningEffort
|
|
42
|
+
extraBody?: Readonly<Record<string, unknown>>
|
|
43
|
+
toolChoice?: 'auto' | 'required' | 'none'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Author an exact direct-Router profile for a benchmark. This is profile construction only;
|
|
47
|
+
* execution still accepts no model or generation fields outside the returned AgentProfile. */
|
|
48
|
+
export function benchRouterProfile(
|
|
49
|
+
name: string,
|
|
50
|
+
model: string,
|
|
51
|
+
settings: BenchProfileSettings = {},
|
|
52
|
+
): AgentProfile {
|
|
53
|
+
return withBenchProfile(
|
|
54
|
+
{
|
|
55
|
+
name,
|
|
56
|
+
harness: 'cli-base',
|
|
57
|
+
model: { provider: 'tangle-router', default: model },
|
|
58
|
+
},
|
|
59
|
+
settings,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Derive another exact profile while preserving all untouched canonical axes. */
|
|
64
|
+
export function withBenchProfile(
|
|
65
|
+
base: AgentProfile,
|
|
66
|
+
settings: BenchProfileSettings & { name?: string },
|
|
67
|
+
): AgentProfile {
|
|
68
|
+
const metadata = {
|
|
69
|
+
...(base.model?.metadata ?? {}),
|
|
70
|
+
...(settings.temperature !== undefined ? { temperature: settings.temperature } : {}),
|
|
71
|
+
...(settings.maxTokens !== undefined ? { maxTokens: settings.maxTokens } : {}),
|
|
72
|
+
...(settings.retry !== undefined ? { retry: settings.retry } : {}),
|
|
73
|
+
...(settings.maxTurns !== undefined ? { maxTurns: settings.maxTurns } : {}),
|
|
74
|
+
...(settings.seed !== undefined ? { seed: settings.seed } : {}),
|
|
75
|
+
...(settings.extraBody !== undefined ? { extraBody: settings.extraBody } : {}),
|
|
76
|
+
...(settings.toolChoice !== undefined ? { toolChoice: settings.toolChoice } : {}),
|
|
77
|
+
}
|
|
78
|
+
return agentProfileSchema.parse({
|
|
79
|
+
...base,
|
|
80
|
+
...(settings.name ? { name: settings.name } : {}),
|
|
81
|
+
model: {
|
|
82
|
+
...base.model,
|
|
83
|
+
...(settings.reasoningEffort !== undefined
|
|
84
|
+
? { reasoningEffort: settings.reasoningEffort }
|
|
85
|
+
: {}),
|
|
86
|
+
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
87
|
+
},
|
|
88
|
+
...(settings.systemPrompt !== undefined
|
|
89
|
+
? { prompt: { ...base.prompt, systemPrompt: settings.systemPrompt } }
|
|
90
|
+
: {}),
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function benchProfileModel(profile: AgentProfile): string {
|
|
95
|
+
const model = profile.model?.default
|
|
96
|
+
if (typeof model !== 'string' || model.length === 0 || model === 'runtime-selected') {
|
|
97
|
+
throw new Error('benchmark AgentProfile.model.default must be concrete')
|
|
98
|
+
}
|
|
99
|
+
return model
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The benchmark-side entry to Runtime's canonical one-turn path.
|
|
104
|
+
* It is only an ergonomic composition: Runtime still parses the exact profile,
|
|
105
|
+
* materializes the executor, records identity/usage/result events, and refuses
|
|
106
|
+
* profile axes the direct Router backend cannot carry.
|
|
107
|
+
*/
|
|
108
|
+
export async function runBenchRouterTurn(
|
|
109
|
+
config: BenchRouterTurnConfig,
|
|
110
|
+
input: string | { readonly messages: ReadonlyArray<Readonly<Record<string, unknown>>> },
|
|
111
|
+
): Promise<CollectedAgentTurn> {
|
|
112
|
+
if (!config.profile.model?.default) {
|
|
113
|
+
throw new Error('runBenchRouterTurn: profile.model.default is required')
|
|
114
|
+
}
|
|
115
|
+
const factory = createExecutor({
|
|
116
|
+
backend: 'router',
|
|
117
|
+
routerBaseUrl: config.routerBaseUrl,
|
|
118
|
+
routerKey: config.routerKey,
|
|
119
|
+
...(config.tools ? { tools: config.tools } : {}),
|
|
120
|
+
})
|
|
121
|
+
const turn = await collectAgentTurn(
|
|
122
|
+
streamAgentTurn(
|
|
123
|
+
{ kind: 'executor', factory, profile: config.profile },
|
|
124
|
+
input,
|
|
125
|
+
{
|
|
126
|
+
...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),
|
|
127
|
+
...(config.signal ? { signal: config.signal } : {}),
|
|
128
|
+
},
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
if (turn.status !== 'completed') {
|
|
132
|
+
throw new Error(turn.error?.message ?? `Router turn ended with status ${turn.status}`)
|
|
133
|
+
}
|
|
134
|
+
return turn
|
|
135
|
+
}
|
package/src/run-benchmarks.ts
CHANGED
|
@@ -178,7 +178,6 @@ const openSandboxShot: BenchShot = async ({ adapter, task, cell, prompt, routerB
|
|
|
178
178
|
backend: cell.backend ?? 'router',
|
|
179
179
|
routerBaseUrl,
|
|
180
180
|
routerKey,
|
|
181
|
-
model: cell.model,
|
|
182
181
|
...(bridgeUrl ? { bridgeUrl } : {}),
|
|
183
182
|
...(bridgeBearer ? { bridgeBearer } : {}),
|
|
184
183
|
...(sandboxBaseUrl ? { sandboxBaseUrl } : {}),
|
|
@@ -186,7 +185,13 @@ const openSandboxShot: BenchShot = async ({ adapter, task, cell, prompt, routerB
|
|
|
186
185
|
...(timeoutMs ? { timeoutMs } : {}),
|
|
187
186
|
})
|
|
188
187
|
const harness = cell.harness ?? (cell.profile?.metadata?.backendType as string | undefined) ?? 'opencode'
|
|
189
|
-
const profile: AgentProfile =
|
|
188
|
+
const profile: AgentProfile =
|
|
189
|
+
cell.profile ?? {
|
|
190
|
+
name: cell.label,
|
|
191
|
+
harness: harness as AgentProfile['harness'],
|
|
192
|
+
model: { provider: 'tangle-router', default: cell.model },
|
|
193
|
+
metadata: { backendType: harness },
|
|
194
|
+
}
|
|
190
195
|
// Unique per shot: the same (adapter, task) runs concurrently across cells and reps, so the box
|
|
191
196
|
// name and runId must not collide.
|
|
192
197
|
const uniq = Math.random().toString(36).slice(2, 8)
|