@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.
Files changed (155) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/HARNESS.md +2 -2
  3. package/dist/adapters.js +6 -2
  4. package/dist/adapters.js.map +1 -1
  5. package/dist/benchmarks/appworld.d.ts +61 -2
  6. package/dist/benchmarks/appworld.js +111 -40
  7. package/dist/benchmarks/appworld.js.map +1 -1
  8. package/dist/benchmarks/cadbench.js +1 -1
  9. package/dist/benchmarks/cadgenbench.js +1 -1
  10. package/dist/benchmarks/finresearchbench.js +16 -22
  11. package/dist/benchmarks/finresearchbench.js.map +1 -1
  12. package/dist/benchmarks/finsearchcomp.js +16 -22
  13. package/dist/benchmarks/finsearchcomp.js.map +1 -1
  14. package/dist/benchmarks/frames.js +18 -20
  15. package/dist/benchmarks/frames.js.map +1 -1
  16. package/dist/benchmarks/mcad-bench.d.ts +106 -0
  17. package/dist/benchmarks/mcad-bench.js +569 -0
  18. package/dist/benchmarks/mcad-bench.js.map +1 -0
  19. package/dist/benchmarks/mcad-cq-bench.d.ts +82 -0
  20. package/dist/benchmarks/mcad-cq-bench.js +339 -0
  21. package/dist/benchmarks/mcad-cq-bench.js.map +1 -0
  22. package/dist/benchmarks/mcad-cq-golds.d.ts +36 -0
  23. package/dist/benchmarks/mcad-cq-golds.js +342 -0
  24. package/dist/benchmarks/mcad-cq-golds.js.map +1 -0
  25. package/dist/benchmarks/mcad-golds.d.ts +20 -0
  26. package/dist/benchmarks/mcad-golds.js +318 -0
  27. package/dist/benchmarks/mcad-golds.js.map +1 -0
  28. package/dist/benchmarks/mcad-tasks.d.ts +66 -0
  29. package/dist/benchmarks/mcad-tasks.js +508 -0
  30. package/dist/benchmarks/mcad-tasks.js.map +1 -0
  31. package/dist/benchmarks/simpleqa.js +15 -19
  32. package/dist/benchmarks/simpleqa.js.map +1 -1
  33. package/dist/benchmarks/trata-hedge.js +20 -23
  34. package/dist/benchmarks/trata-hedge.js.map +1 -1
  35. package/dist/{cadbench-BrpwOU6A.js → cadbench-BLSyxR1N.js} +21 -20
  36. package/dist/cadbench-BLSyxR1N.js.map +1 -0
  37. package/dist/{cadgenbench-DF7hYHdl.js → cadgenbench-x2OFkf8y.js} +2 -2
  38. package/dist/cadgenbench-x2OFkf8y.js.map +1 -0
  39. package/dist/index.d.ts +0 -1
  40. package/dist/index.js +10 -9
  41. package/dist/index.js.map +1 -1
  42. package/dist/router-turn-C2wMiDoo.js +31 -0
  43. package/dist/router-turn-C2wMiDoo.js.map +1 -0
  44. package/package.json +7 -7
  45. package/scripts/appworld_driver.py +1 -170
  46. package/scripts/run-package-tests.mjs +56 -36
  47. package/scripts/run-package-tests.test.mjs +33 -0
  48. package/scripts/trata-hedge/README.md +3 -3
  49. package/scripts/trata-hedge/run.sh +6 -2
  50. package/src/adapters.ts +11 -0
  51. package/src/aec-gate.mts +30 -9
  52. package/src/agent-graphs-gen2.mts +523 -0
  53. package/src/agent-graphs-gen3.mts +660 -0
  54. package/src/agent-graphs-improve/offline-seams.mts +128 -0
  55. package/src/agent-graphs-improve.mts +747 -0
  56. package/src/atom-humaneval.mts +76 -55
  57. package/src/atom-mcp-e2e.mts +27 -13
  58. package/src/benchmarks/appworld.test.mts +119 -1
  59. package/src/benchmarks/appworld.ts +156 -33
  60. package/src/benchmarks/cadbench.ts +21 -8
  61. package/src/benchmarks/finresearchbench.ts +20 -16
  62. package/src/benchmarks/finsearchcomp.ts +20 -20
  63. package/src/benchmarks/frames.ts +19 -18
  64. package/src/benchmarks/mcad-bench.test.mts +455 -0
  65. package/src/benchmarks/mcad-bench.ts +561 -0
  66. package/src/benchmarks/mcad-cq-bench.ts +423 -0
  67. package/src/benchmarks/mcad-cq-golds.ts +374 -0
  68. package/src/benchmarks/mcad-cq.test.mts +386 -0
  69. package/src/benchmarks/mcad-golds.ts +359 -0
  70. package/src/benchmarks/mcad-tasks.ts +490 -0
  71. package/src/benchmarks/simpleqa.ts +19 -17
  72. package/src/benchmarks/trata-hedge.ts +26 -18
  73. package/src/browser/ui-reviewer.ts +1 -1
  74. package/src/clbench-context-gate.mts +45 -9
  75. package/src/cloud-loop.mts +24 -6
  76. package/src/commit0-env-run.mts +6 -3
  77. package/src/commit0-env.ts +2 -3
  78. package/src/commit0-gate.mts +53 -24
  79. package/src/corpus.ts +3 -3
  80. package/src/david-attribution.mts +28 -9
  81. package/src/david-goliath.mts +31 -12
  82. package/src/egress-probe.mts +3 -3
  83. package/src/examples/lean-proof-gate.mts +10 -3
  84. package/src/examples/math-demo.mts +10 -3
  85. package/src/examples/strategy-demo.mts +10 -4
  86. package/src/fleet.mts +30 -8
  87. package/src/gate-cli.mts +9 -4
  88. package/src/gate.test.mts +7 -2
  89. package/src/gate.ts +23 -75
  90. package/src/generate-eval/certify.ts +13 -5
  91. package/src/hev-eval.mts +39 -10
  92. package/src/hev-improve.mts +28 -16
  93. package/src/hev-structural.mts +28 -20
  94. package/src/humaneval-gate.mts +24 -8
  95. package/src/humaneval-object-ablation.mts +52 -14
  96. package/src/humaneval-repair-gate.mts +53 -14
  97. package/src/mbpp-structural.mts +28 -20
  98. package/src/mcp-mount-probe.mts +23 -11
  99. package/src/official-optimizer-config.mts +61 -12
  100. package/src/official-optimizer-config.test.mts +72 -3
  101. package/src/quant-arena/profiles/quant-researcher.profile.json +6 -1
  102. package/src/quant-arena/quant-loop.mts +88 -61
  103. package/src/research-shot.ts +21 -11
  104. package/src/resolve-client.ts +6 -8
  105. package/src/rollout-ledger/backfill-swe-arena.test.mts +7 -2
  106. package/src/router-executor.ts +1 -1
  107. package/src/router-turn.ts +135 -0
  108. package/src/run-benchmarks.ts +7 -2
  109. package/src/sandbox-run.ts +23 -41
  110. package/src/search-bench/bridge.ts +66 -8
  111. package/src/search-bench/parametric-check.mts +19 -4
  112. package/src/search-bench/profiles.ts +24 -9
  113. package/src/search-bench/run.mts +3 -4
  114. package/src/smoke-structural-rollout.mts +21 -5
  115. package/src/supervisor-arena.mts +28 -20
  116. package/src/swe-arena/arms.ts +73 -15
  117. package/src/swe-arena/capacity.ts +44 -41
  118. package/src/swe-arena/diagnosis-ensemble.ts +51 -48
  119. package/src/swe-arena/execution.test.mts +12 -12
  120. package/src/swe-arena/gepa-seat.mts +12 -5
  121. package/src/swe-arena/gepa-seat.test.mts +45 -33
  122. package/src/swe-arena/outer-loop.mts +94 -70
  123. package/src/swe-arena/premeasured-from-cells.mts +16 -1
  124. package/src/swe-arena/premeasured-from-cells.test.mts +21 -1
  125. package/src/swe-arena/profiles/deepseek-author.profile.json +12 -0
  126. package/src/swe-arena/profiles/default-author.profile.json +9 -1
  127. package/src/swe-arena/proposer-fanout.mts +52 -99
  128. package/src/swe-arena/proposer-fanout.test.mts +92 -57
  129. package/src/swe-arena/proposer-provenance.mts +1 -1
  130. package/src/swe-code-improve.mts +30 -52
  131. package/src/swe-emit-patch.mts +26 -5
  132. package/src/swe-improve.mts +27 -8
  133. package/src/swe-jail.test.ts +128 -0
  134. package/src/swe-jail.ts +106 -22
  135. package/src/swe-local-proof.mts +27 -5
  136. package/src/swe-repro-calibrate.mts +5 -0
  137. package/src/swe-self-improve.mts +45 -10
  138. package/src/swe-stream.mts +68 -6
  139. package/src/swe-structural.mts +46 -7
  140. package/src/tb-container-executor.mts +38 -6
  141. package/src/tb-container-executor.test.mts +11 -3
  142. package/src/tb-supervisor-sidecar.mts +11 -27
  143. package/src/terminal-compare.ts +38 -18
  144. package/src/trata-gate.mts +26 -26
  145. package/src/trata-gepa.mts +25 -16
  146. package/src/trata-hedge-solve.mts +80 -0
  147. package/src/worker-blender.ts +21 -11
  148. package/src/worker-browser.ts +18 -5
  149. package/src/worker-build123d.ts +17 -8
  150. package/src/worker-cad.ts +31 -12
  151. package/src/worker.ts +1 -1
  152. package/tb_agents/opencode_router_agent.py +84 -42
  153. package/dist/cadbench-BrpwOU6A.js.map +0 -1
  154. package/dist/cadgenbench-DF7hYHdl.js.map +0 -1
  155. package/scripts/trata-hedge/solve.py +0 -83
@@ -0,0 +1,31 @@
1
+ import { collectAgentTurn, createExecutor, streamAgentTurn } from "@tangle-network/agent-runtime/kernel";
2
+ import "@tangle-network/agent-interface";
3
+ //#region src/router-turn.ts
4
+ /**
5
+ * The benchmark-side entry to Runtime's canonical one-turn path.
6
+ * It is only an ergonomic composition: Runtime still parses the exact profile,
7
+ * materializes the executor, records identity/usage/result events, and refuses
8
+ * profile axes the direct Router backend cannot carry.
9
+ */
10
+ async function runBenchRouterTurn(config, input) {
11
+ if (!config.profile.model?.default) throw new Error("runBenchRouterTurn: profile.model.default is required");
12
+ const turn = await collectAgentTurn(streamAgentTurn({
13
+ kind: "executor",
14
+ factory: createExecutor({
15
+ backend: "router",
16
+ routerBaseUrl: config.routerBaseUrl,
17
+ routerKey: config.routerKey,
18
+ ...config.tools ? { tools: config.tools } : {}
19
+ }),
20
+ profile: config.profile
21
+ }, input, {
22
+ ...config.timeoutMs === void 0 ? {} : { timeoutMs: config.timeoutMs },
23
+ ...config.signal ? { signal: config.signal } : {}
24
+ }));
25
+ if (turn.status !== "completed") throw new Error(turn.error?.message ?? `Router turn ended with status ${turn.status}`);
26
+ return turn;
27
+ }
28
+ //#endregion
29
+ export { runBenchRouterTurn as t };
30
+
31
+ //# sourceMappingURL=router-turn-C2wMiDoo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-turn-C2wMiDoo.js","names":[],"sources":["../src/router-turn.ts"],"sourcesContent":["import {\n type AgentProfile,\n agentProfileSchema,\n type ReasoningEffort,\n} from '@tangle-network/agent-interface'\nimport {\n collectAgentTurn,\n createExecutor,\n streamAgentTurn,\n type CollectedAgentTurn,\n type ToolSpec,\n} from '@tangle-network/agent-runtime/kernel'\n\n/** Bench-local target shorthand; Runtime still executes only the exact profile below. */\nexport interface BenchRouterTarget {\n routerBaseUrl: string\n routerKey: string\n profile: AgentProfile\n}\n\nexport interface BenchRouterTurnConfig extends BenchRouterTarget {\n tools?: ReadonlyArray<ToolSpec>\n timeoutMs?: number\n signal?: AbortSignal\n}\n\nexport interface BenchProfileSettings {\n systemPrompt?: string\n temperature?: number\n maxTokens?: number\n retry?: {\n maxAttempts?: number\n initialBackoffMs?: number\n maxBackoffMs?: number\n jitter?: number\n retryStatuses?: ReadonlyArray<number>\n requestTimeoutMs?: number\n }\n maxTurns?: number\n seed?: number\n reasoningEffort?: ReasoningEffort\n extraBody?: Readonly<Record<string, unknown>>\n toolChoice?: 'auto' | 'required' | 'none'\n}\n\n/** Author an exact direct-Router profile for a benchmark. This is profile construction only;\n * execution still accepts no model or generation fields outside the returned AgentProfile. */\nexport function benchRouterProfile(\n name: string,\n model: string,\n settings: BenchProfileSettings = {},\n): AgentProfile {\n return withBenchProfile(\n {\n name,\n harness: 'cli-base',\n model: { provider: 'tangle-router', default: model },\n },\n settings,\n )\n}\n\n/** Derive another exact profile while preserving all untouched canonical axes. */\nexport function withBenchProfile(\n base: AgentProfile,\n settings: BenchProfileSettings & { name?: string },\n): AgentProfile {\n const metadata = {\n ...(base.model?.metadata ?? {}),\n ...(settings.temperature !== undefined ? { temperature: settings.temperature } : {}),\n ...(settings.maxTokens !== undefined ? { maxTokens: settings.maxTokens } : {}),\n ...(settings.retry !== undefined ? { retry: settings.retry } : {}),\n ...(settings.maxTurns !== undefined ? { maxTurns: settings.maxTurns } : {}),\n ...(settings.seed !== undefined ? { seed: settings.seed } : {}),\n ...(settings.extraBody !== undefined ? { extraBody: settings.extraBody } : {}),\n ...(settings.toolChoice !== undefined ? { toolChoice: settings.toolChoice } : {}),\n }\n return agentProfileSchema.parse({\n ...base,\n ...(settings.name ? { name: settings.name } : {}),\n model: {\n ...base.model,\n ...(settings.reasoningEffort !== undefined\n ? { reasoningEffort: settings.reasoningEffort }\n : {}),\n ...(Object.keys(metadata).length > 0 ? { metadata } : {}),\n },\n ...(settings.systemPrompt !== undefined\n ? { prompt: { ...base.prompt, systemPrompt: settings.systemPrompt } }\n : {}),\n })\n}\n\nexport function benchProfileModel(profile: AgentProfile): string {\n const model = profile.model?.default\n if (typeof model !== 'string' || model.length === 0 || model === 'runtime-selected') {\n throw new Error('benchmark AgentProfile.model.default must be concrete')\n }\n return model\n}\n\n/**\n * The benchmark-side entry to Runtime's canonical one-turn path.\n * It is only an ergonomic composition: Runtime still parses the exact profile,\n * materializes the executor, records identity/usage/result events, and refuses\n * profile axes the direct Router backend cannot carry.\n */\nexport async function runBenchRouterTurn(\n config: BenchRouterTurnConfig,\n input: string | { readonly messages: ReadonlyArray<Readonly<Record<string, unknown>>> },\n): Promise<CollectedAgentTurn> {\n if (!config.profile.model?.default) {\n throw new Error('runBenchRouterTurn: profile.model.default is required')\n }\n const factory = createExecutor({\n backend: 'router',\n routerBaseUrl: config.routerBaseUrl,\n routerKey: config.routerKey,\n ...(config.tools ? { tools: config.tools } : {}),\n })\n const turn = await collectAgentTurn(\n streamAgentTurn(\n { kind: 'executor', factory, profile: config.profile },\n input,\n {\n ...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),\n ...(config.signal ? { signal: config.signal } : {}),\n },\n ),\n )\n if (turn.status !== 'completed') {\n throw new Error(turn.error?.message ?? `Router turn ended with status ${turn.status}`)\n }\n return turn\n}\n"],"mappings":";;;;;;;;;AA2GA,eAAsB,mBACpB,QACA,OAC6B;CAC7B,IAAI,CAAC,OAAO,QAAQ,OAAO,SACzB,MAAM,IAAI,MAAM,uDAAuD;CAQzE,MAAM,OAAO,MAAM,iBACjB,gBACE;EAAE,MAAM;EAAY,SARR,eAAe;GAC7B,SAAS;GACT,eAAe,OAAO;GACtB,WAAW,OAAO;GAClB,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;EAChD,CAG8B;EAAG,SAAS,OAAO;CAAQ,GACrD,OACA;EACE,GAAI,OAAO,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,OAAO,UAAU;EACxE,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;CACnD,CACF,CACF;CACA,IAAI,KAAK,WAAW,aAClB,MAAM,IAAI,MAAM,KAAK,OAAO,WAAW,iCAAiC,KAAK,QAAQ;CAEvF,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-bench",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
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.143.0",
29
- "@tangle-network/agent-interface": "0.43.0",
30
- "@tangle-network/agent-knowledge": "7.0.8",
31
- "@tangle-network/sandbox": "0.17.2",
32
- "@tangle-network/agent-runtime": "0.126.0"
28
+ "@tangle-network/agent-eval": "0.144.8",
29
+ "@tangle-network/agent-interface": "0.46.1",
30
+ "@tangle-network/agent-knowledge": "7.1.3",
31
+ "@tangle-network/sandbox": "0.19.4",
32
+ "@tangle-network/agent-runtime": "0.131.6"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@arethetypeswrong/cli": "0.18.5",
@@ -69,7 +69,7 @@
69
69
  "run-benchmarks": "tsx src/run-benchmarks-cli.mts",
70
70
  "gate-report": "tsx src/corpus-report.mts corpus/finsearch.jsonl",
71
71
  "terminal-compare": "tsx src/terminal-compare.ts",
72
- "test": "node scripts/run-package-tests.mjs && node --test scripts/wait-for-published-dependencies.test.mjs",
72
+ "test": "node scripts/run-package-tests.mjs && node --test scripts/run-package-tests.test.mjs scripts/wait-for-published-dependencies.test.mjs",
73
73
  "typecheck:public": "tsc -p tsconfig.public.json",
74
74
  "verify:package": "pnpm run verify:package:static && node scripts/verify-packed-consumer.mjs",
75
75
  "verify:package:local-runtime": "pnpm run verify:package:static && node scripts/verify-packed-consumer.mjs --local-runtime",
@@ -9,10 +9,7 @@
9
9
 
10
10
  import argparse
11
11
  import json
12
- import os
13
- import re
14
12
  import sys
15
- import time
16
13
 
17
14
 
18
15
  def fail(msg: str) -> None:
@@ -20,169 +17,9 @@ def fail(msg: str) -> None:
20
17
  sys.exit(1)
21
18
 
22
19
 
23
- _CODE_RE = re.compile(r"```(?:python|py)?\s*\n(.*?)```", re.DOTALL)
24
-
25
-
26
- def _extract_code(text: str) -> str:
27
- blocks = _CODE_RE.findall(text or "")
28
- return (blocks[-1] if blocks else "").strip()
29
-
30
-
31
- def _router_chat(base: str, key: str, model: str, messages: list, timeout: float = 180.0):
32
- """One router chat-completion with retry on transient/429/5xx. Returns
33
- (content, input_tokens, output_tokens). Raises on exhausted retries."""
34
- import httpx
35
-
36
- url = base.rstrip("/") + "/chat/completions"
37
- last = None
38
- for attempt in range(4):
39
- try:
40
- r = httpx.post(
41
- url,
42
- headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
43
- json={"model": model, "messages": messages},
44
- timeout=timeout,
45
- )
46
- if r.status_code in (429, 500, 502, 503, 504):
47
- last = f"{r.status_code}: {r.text[:160]}"
48
- time.sleep(2**attempt)
49
- continue
50
- r.raise_for_status()
51
- d = r.json()
52
- content = (d["choices"][0]["message"].get("content") or "")
53
- usage = d.get("usage") or {}
54
- return content, int(usage.get("prompt_tokens", 0) or 0), int(usage.get("completion_tokens", 0) or 0)
55
- except Exception as e: # noqa: BLE001
56
- last = str(e)
57
- if attempt < 3:
58
- time.sleep(2**attempt)
59
- continue
60
- raise RuntimeError(f"router_chat failed after retries: {last}")
61
- raise RuntimeError(f"router_chat exhausted: {last}")
62
-
63
-
64
- def _build_system(directive: str, world) -> str:
65
- sup = world.task.supervisor
66
- apps = list(getattr(world.task, "allowed_apps", []) or [])
67
- descs = getattr(world.task, "app_descriptions", "")
68
- desc_str = json.dumps(descs) if isinstance(descs, (dict, list)) else str(descs)
69
- return (
70
- f"You are an AI agent completing a digital task for your supervisor "
71
- f"{getattr(sup, 'first_name', '')} {getattr(sup, 'last_name', '')} "
72
- f"(email {getattr(sup, 'email', '')}, phone {getattr(sup, 'phone_number', '')}) "
73
- "by WRITING PYTHON that calls app APIs (the apis.<app>.<function>(...) surface).\n\n"
74
- f"Available apps: {', '.join(apps)}.\n"
75
- f"App descriptions: {desc_str[:1500]}\n\n"
76
- "How to work, one step per turn:\n"
77
- "- Discover APIs with apis.api_docs.show_api_descriptions(app_name='<app>') and "
78
- "apis.api_docs.show_api_doc(app_name='<app>', api_name='<api>') BEFORE calling them.\n"
79
- "- Get the supervisor's app passwords with apis.supervisor.show_account_passwords(), then log in "
80
- "to each app you use to obtain its access_token.\n"
81
- "- Write ONE short Python code block per turn. After it runs you SEE its OUTPUT (or error "
82
- "traceback) — use that to decide the next step. Print intermediate values you need.\n"
83
- "- Iterate: inspect -> authenticate -> act -> verify. Do not guess API names or arguments.\n"
84
- "- When the task is fully done call apis.supervisor.complete_task(answer=<answer>) (include the "
85
- "answer if the task asks a question, otherwise apis.supervisor.complete_task()).\n"
86
- "- Reply with EXACTLY ONE fenced ```python block per turn and nothing else.\n\n"
87
- f"{directive}"
88
- )
89
-
90
-
91
- def cmd_react(args) -> None:
92
- """Multi-turn REPL agent: the model writes a python block, the engine executes
93
- it in the PERSISTENT world, the output is fed back, and it iterates until it
94
- completes the task or hits max-turns. Then AppWorld's own evaluator scores it.
95
- Config (directive, model, router creds, max_turns) arrives as JSON on stdin so
96
- the candidate directive can be arbitrarily long. The directive is the optimized
97
- surface; the loop + contract are fixed."""
98
- cfg = {}
99
- raw = sys.stdin.read()
100
- if raw.strip():
101
- try:
102
- cfg = json.loads(raw)
103
- except Exception as e: # noqa: BLE001
104
- fail(f"react config JSON parse failed: {e}")
105
- directive = str(cfg.get("directive", ""))
106
- model = str(cfg.get("model", "gpt-4o"))
107
- max_turns = int(cfg.get("max_turns", 8))
108
- router_base = str(cfg.get("router_base", "https://router.tangle.tools/v1"))
109
- router_key = str(cfg.get("router_key") or os.environ.get("TANGLE_API_KEY", ""))
110
- if not router_key:
111
- fail("react: router_key/TANGLE_API_KEY required")
112
-
113
- try:
114
- from appworld import AppWorld
115
- except Exception as e: # noqa: BLE001
116
- fail(f"appworld import failed: {e}")
117
-
118
- in_tok = 0
119
- out_tok = 0
120
- turns = 0
121
- turns_log: list = []
122
- try:
123
- with AppWorld(
124
- task_id=args.task_id,
125
- experiment_name="bench-react",
126
- raise_on_failure=False,
127
- ) as world:
128
- messages = [
129
- {"role": "system", "content": _build_system(directive, world)},
130
- {"role": "user", "content": f"Task: {world.task.instruction}"},
131
- ]
132
- for turn in range(max_turns):
133
- turns = turn + 1
134
- content, ui, uo = _router_chat(router_base, router_key, model, messages)
135
- in_tok += ui
136
- out_tok += uo
137
- code = _extract_code(content)
138
- messages.append({"role": "assistant", "content": content})
139
- if not code:
140
- messages.append({
141
- "role": "user",
142
- "content": "Reply with exactly one ```python block that makes progress, "
143
- "or call apis.supervisor.complete_task().",
144
- })
145
- continue
146
- output = world.execute(code)
147
- turns_log.append({"code": code[:600], "output": str(output)[:600]})
148
- messages.append({"role": "user", "content": "OUTPUT:\n" + str(output)[:4000]})
149
- if world.task_completed():
150
- break
151
- evaluation = world.evaluate().to_dict()
152
- except Exception as e: # noqa: BLE001
153
- fail(f"react of {args.task_id} failed: {e}")
154
-
155
- if "success" not in evaluation or "num_tests" not in evaluation:
156
- fail(f"evaluation dict missing success/num_tests keys: {sorted(evaluation.keys())}")
157
- passes = evaluation.get("passes", [])
158
- failures = evaluation.get("failures", [])
159
- n_pass = len(passes) if isinstance(passes, list) else int(passes or 0)
160
- n_fail = len(failures) if isinstance(failures, list) else int(failures or 0)
161
- print(
162
- json.dumps(
163
- {
164
- "success": bool(evaluation["success"]),
165
- "passes": n_pass,
166
- "fails": n_fail,
167
- "num_tests": int(evaluation["num_tests"]),
168
- # Failed sub-test names — the evidence a trace analyst steers on.
169
- "failure_names": [str(f)[:160] for f in failures][:8]
170
- if isinstance(failures, list)
171
- else [],
172
- "turns": turns,
173
- "input_tokens": in_tok,
174
- "output_tokens": out_tok,
175
- "transcript": "\n---\n".join(
176
- f"CODE:\n{t['code']}\nOUTPUT:\n{t['output']}" for t in turns_log[-3:]
177
- )[:1600],
178
- }
179
- )
180
- )
181
-
182
-
183
20
  def cmd_session(args) -> None:
184
21
  """Dumb world shim: a persistent AppWorld session driven over stdin JSONL.
185
- NO LLM calls here — the agent loop lives in the runtime (routerToolLoop);
22
+ NO LLM calls here — the profile-bound agent loop lives in Runtime;
186
23
  this process only owns world state. One JSON object per line, both ways:
187
24
  {"op":"execute","code":"..."} -> {"output":"...","task_completed":bool}
188
25
  {"op":"evaluate"} -> the evaluate verdict JSON (+failure_names)
@@ -336,10 +173,6 @@ def main() -> None:
336
173
  p_eval.add_argument("--task-id", required=True)
337
174
  p_eval.add_argument("--split", required=True)
338
175
 
339
- p_react = sub.add_parser("react")
340
- p_react.add_argument("--task-id", required=True)
341
- p_react.add_argument("--split", required=True)
342
-
343
176
  p_session = sub.add_parser("session")
344
177
  p_session.add_argument("--task-id", required=True)
345
178
  p_session.add_argument("--split", required=True)
@@ -349,8 +182,6 @@ def main() -> None:
349
182
  cmd_load(args)
350
183
  elif args.cmd == "evaluate":
351
184
  cmd_evaluate(args)
352
- elif args.cmd == "react":
353
- cmd_react(args)
354
185
  elif args.cmd == "session":
355
186
  cmd_session(args)
356
187
 
@@ -8,6 +8,19 @@ const execFileAsync = promisify(execFile)
8
8
  const benchDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
9
9
  const sourceDir = path.join(benchDir, 'src')
10
10
 
11
+ export function resolvePackageTestTimeoutMs(env = process.env) {
12
+ const raw = env.AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS
13
+ if (raw === undefined) return undefined
14
+ if (!/^(?:0|[1-9]\d*)$/.test(raw)) {
15
+ throw new Error('AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS must be a non-negative safe integer')
16
+ }
17
+ const timeoutMs = Number(raw)
18
+ if (!Number.isSafeInteger(timeoutMs)) {
19
+ throw new Error('AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS must be a non-negative safe integer')
20
+ }
21
+ return timeoutMs
22
+ }
23
+
11
24
  async function collectTests(dir) {
12
25
  const files = []
13
26
  for (const entry of await readdir(dir, { withFileTypes: true })) {
@@ -18,13 +31,14 @@ async function collectTests(dir) {
18
31
  return files.sort()
19
32
  }
20
33
 
21
- async function run(command, args, env = process.env) {
34
+ export async function run(command, args, env = process.env) {
35
+ const timeoutMs = resolvePackageTestTimeoutMs(env)
22
36
  try {
23
37
  await execFileAsync(command, args, {
24
38
  cwd: benchDir,
25
39
  env,
26
40
  maxBuffer: 10 * 1024 * 1024,
27
- timeout: 120_000,
41
+ ...(timeoutMs === undefined ? {} : { timeout: timeoutMs }),
28
42
  })
29
43
  } catch (error) {
30
44
  if (error?.stdout) process.stdout.write(error.stdout)
@@ -35,44 +49,50 @@ async function run(command, args, env = process.env) {
35
49
  }
36
50
  }
37
51
 
38
- const python = path.join(benchDir, '.venv', 'bin', 'python')
39
- try {
40
- await access(python)
41
- } catch {
42
- await run('python3', ['-m', 'venv', '.venv'])
43
- }
52
+ async function main() {
53
+ const python = path.join(benchDir, '.venv', 'bin', 'python')
54
+ try {
55
+ await access(python)
56
+ } catch {
57
+ await run('python3', ['-m', 'venv', '.venv'])
58
+ }
44
59
 
45
- const tests = await collectTests(sourceDir)
46
- if (tests.length === 0) throw new Error('no package tests found under src/')
60
+ const tests = await collectTests(sourceDir)
61
+ if (tests.length === 0) throw new Error('no package tests found under src/')
47
62
 
48
- // Two test runtimes coexist under src/: node:test files run under `node --test`;
49
- // vitest files (the swe-arena suite) crash there (`vitest` APIs need the vitest
50
- // worker), so partition by the framework each file actually imports.
51
- const nodeTests = []
52
- const vitestTests = []
53
- for (const file of tests) {
54
- const body = await readFile(file, 'utf8')
55
- if (/from\s+['"]vitest['"]/.test(body)) vitestTests.push(file)
56
- else nodeTests.push(file)
57
- }
63
+ // Two test runtimes coexist under src/: node:test files run under `node --test`;
64
+ // vitest files (the swe-arena suite) crash there (`vitest` APIs need the vitest
65
+ // worker), so partition by the framework each file actually imports.
66
+ const nodeTests = []
67
+ const vitestTests = []
68
+ for (const file of tests) {
69
+ const body = await readFile(file, 'utf8')
70
+ if (/from\s+['"]vitest['"]/.test(body)) vitestTests.push(file)
71
+ else nodeTests.push(file)
72
+ }
73
+
74
+ if (nodeTests.length > 0) {
75
+ await run(
76
+ process.execPath,
77
+ ['--test', '--import', 'tsx', ...nodeTests.map((file) => path.relative(benchDir, file))],
78
+ {
79
+ ...process.env,
80
+ TSX_TSCONFIG_PATH: 'tsconfig.public.json',
81
+ },
82
+ )
83
+ }
84
+
85
+ if (vitestTests.length > 0) {
86
+ await run('npx', ['vitest', 'run', ...vitestTests.map((file) => path.relative(benchDir, file))])
87
+ }
88
+
89
+ await run(python, ['-m', 'unittest', 'discover', '-s', 'pier_agents', '-p', '*_test.py'])
58
90
 
59
- if (nodeTests.length > 0) {
60
- await run(
61
- process.execPath,
62
- ['--test', '--import', 'tsx', ...nodeTests.map((file) => path.relative(benchDir, file))],
63
- {
64
- ...process.env,
65
- TSX_TSCONFIG_PATH: 'tsconfig.public.json',
66
- },
91
+ console.log(
92
+ `package tests passed: ${tests.length}/${tests.length} TypeScript files (${nodeTests.length} node:test + ${vitestTests.length} vitest) + Pier bridge`,
67
93
  )
68
94
  }
69
95
 
70
- if (vitestTests.length > 0) {
71
- await run('npx', ['vitest', 'run', ...vitestTests.map((file) => path.relative(benchDir, file))])
96
+ if (path.resolve(process.argv[1] ?? '') === fileURLToPath(import.meta.url)) {
97
+ await main()
72
98
  }
73
-
74
- await run(python, ['-m', 'unittest', 'discover', '-s', 'pier_agents', '-p', '*_test.py'])
75
-
76
- console.log(
77
- `package tests passed: ${tests.length}/${tests.length} TypeScript files (${nodeTests.length} node:test + ${vitestTests.length} vitest) + Pier bridge`,
78
- )
@@ -0,0 +1,33 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+ import { resolvePackageTestTimeoutMs, run } from './run-package-tests.mjs'
4
+
5
+ test('package test timeout is optional and caller-controlled', () => {
6
+ assert.equal(resolvePackageTestTimeoutMs({}), undefined)
7
+ assert.equal(resolvePackageTestTimeoutMs({ AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS: '0' }), 0)
8
+ assert.equal(resolvePackageTestTimeoutMs({ AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS: '900000' }), 900_000)
9
+
10
+ for (const value of ['', '-1', '1.5', 'Infinity', '9007199254740992']) {
11
+ assert.throws(
12
+ () => resolvePackageTestTimeoutMs({ AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS: value }),
13
+ /must be a non-negative safe integer/,
14
+ )
15
+ }
16
+ })
17
+
18
+ test('package test timeout reaches the child process', async () => {
19
+ const env = {
20
+ ...process.env,
21
+ AGENT_BENCH_PACKAGE_TEST_TIMEOUT_MS: '100',
22
+ }
23
+ const startedAt = Date.now()
24
+
25
+ await assert.rejects(
26
+ run(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], env),
27
+ (error) => {
28
+ assert.equal(error.cause?.killed, true)
29
+ return true
30
+ },
31
+ )
32
+ assert.ok(Date.now() - startedAt < 2_000)
33
+ })
@@ -21,8 +21,8 @@ sparse (1 iff all themes). No deployable ground-truth checker — it's an **orac
21
21
 
22
22
  ## Status (2026-06-06): pipeline PROVEN end-to-end
23
23
 
24
- Our solver → their **real** Gemini-3.1-pro judge → a genuine graded result. Every link
25
- works. The naive **single-shot** baseline (gpt-4o, ~3 of N corpus files in one context
24
+ Our Runtime-backed solver → their **real** Gemini-3.1-pro judge → a genuine graded result. Every link
25
+ works. The naive **single-shot** baseline (DeepSeek V4 Flash, ~3 of N corpus files in one context
26
26
  window) scores **0/4** — a floor: it hit only 1/3 moves on a few themes with
27
27
  hallucinations flagged, because it could not explore the full corpus. The bench is built
28
28
  for **agentic** exploration; a fair baseline needs our sandbox runtime as the solver
@@ -35,7 +35,7 @@ for **agentic** exploration; a fair baseline needs our sandbox runtime as the so
35
35
  git clone https://github.com/Trata-Inc/trata-hedge-bench /tmp/thb
36
36
 
37
37
  dotenvx run -f ~/company/devops/secrets/.env.keys -f ~/company/devops/secrets/agent-state.env -- \
38
- bash bench/scripts/trata-hedge/run.sh /tmp/thb/environments/<env-name> gpt-4o
38
+ bash bench/scripts/trata-hedge/run.sh /tmp/thb/environments/<env-name> deepseek-v4-flash
39
39
  ```
40
40
 
41
41
  ## Gotchas (each cost a debugging cycle)
@@ -17,14 +17,18 @@
17
17
  set -euo pipefail
18
18
 
19
19
  ENV="${1:?usage: run.sh <trata-env-dir> [model]}"
20
- MODEL="${2:-${WORKER_MODEL:-gpt-4o}}"
20
+ MODEL="${2:-${WORKER_MODEL:-}}"
21
+ if [[ -z "$MODEL" ]]; then
22
+ echo "WORKER_MODEL is required (or pass it as argument 2)" >&2
23
+ exit 2
24
+ fi
21
25
  HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
26
  ANS=/tmp/thb-answer.txt
23
27
  REWARD=/tmp/thb-reward.txt
24
28
  DETAILS=/tmp/thb-details.json
25
29
 
26
30
  echo "[trata] solve: $ENV (model=$MODEL)"
27
- WORKER_MODEL="$MODEL" python3 "$HERE/solve.py" "$ENV" "$ANS"
31
+ WORKER_MODEL="$MODEL" pnpm --dir "$HERE/../../.." exec tsx "$HERE/../../src/trata-hedge-solve.mts" "$ENV" "$ANS"
28
32
 
29
33
  sudo -n mkdir -p /app && sudo -n cp "$ANS" /app/answer.txt && sudo -n chmod 644 /app/answer.txt
30
34
 
package/src/adapters.ts CHANGED
@@ -21,6 +21,8 @@ import { createFinsearchcompAdapter } from './benchmarks/finsearchcomp'
21
21
  import { createFramesAdapter } from './benchmarks/frames'
22
22
  import { createHotpotqaAdapter } from './benchmarks/hotpotqa'
23
23
  import { createHumanEvalAdapter } from './benchmarks/humaneval'
24
+ import { createMcadBenchAdapter } from './benchmarks/mcad-bench'
25
+ import { createMcadCqAdapter } from './benchmarks/mcad-cq-bench'
24
26
  import { createMind2WebAdapter } from './benchmarks/mind2web'
25
27
  import { createNoMiraclAdapter } from './benchmarks/nomiracl'
26
28
  import { createOpenRagBenchAdapter } from './benchmarks/open-rag-bench'
@@ -59,6 +61,15 @@ export const ADAPTERS: Record<string, () => BenchmarkAdapter> = {
59
61
  'appworld-react': createAppWorldReactAdapter,
60
62
  'enterpriseops-gym': createEnterpriseOpsGymAdapter,
61
63
  'cad-design': createCadDesignAdapter,
64
+ // Dimensioned mechanical parts: same OpenSCAD+xvfb judge deps as cad-design, but
65
+ // the spec is metric (bbox / volume / body count / point-in-solid hole probes)
66
+ // rather than qualitative, and every task carries a verified gold.
67
+ mcad: createMcadBenchAdapter,
68
+ // The same 10 parts and the same spec assertions, but the worker writes a Python
69
+ // CadQuery script that must export STEP as well as the ASCII STL — which closes
70
+ // the format deviation `mcad-tasks.ts` documents (upstream asks for STEP;
71
+ // OpenSCAD cannot emit it). STEP delivery is one extra SCORED check.
72
+ 'mcad-cq': createMcadCqAdapter,
62
73
  cadbench: createCadBenchAdapter,
63
74
  cadgenbench: createCadGenBenchAdapter,
64
75
  frames: createFramesAdapter,
package/src/aec-gate.mts CHANGED
@@ -22,7 +22,13 @@ import { resolveAdapter } from './adapters'
22
22
  import type { BenchmarkAdapter, BenchTask } from './benchmarks/types'
23
23
  import { type AttemptRecord, appendRunRecord, buildRunRecordFromAttempts } from './corpus'
24
24
  import { composeStrategies } from './directives'
25
- import { type RouterConfig, routerChatWithUsage } from '@tangle-network/agent-runtime/kernel'
25
+ import {
26
+ benchProfileModel,
27
+ benchRouterProfile,
28
+ type BenchRouterTarget,
29
+ runBenchRouterTurn,
30
+ withBenchProfile,
31
+ } from './router-turn'
26
32
  import { pool } from './stats.mts'
27
33
 
28
34
  function must(name: string): string {
@@ -52,7 +58,7 @@ interface AttemptOutcome {
52
58
  }
53
59
 
54
60
  async function runAttempt(
55
- cfg: RouterConfig,
61
+ cfg: BenchRouterTarget,
56
62
  adapter: BenchmarkAdapter,
57
63
  task: BenchTask,
58
64
  prompt: string,
@@ -65,8 +71,15 @@ async function runAttempt(
65
71
  let lastErr: unknown
66
72
  for (let attempt = 0; attempt < 3; attempt += 1) {
67
73
  try {
68
- const res = await routerChatWithUsage(cfg, [{ role: 'user', content: prompt }])
69
- const content = typeof res.content === 'string' ? res.content : ''
74
+ const res = await runBenchRouterTurn(
75
+ {
76
+ routerBaseUrl: cfg.routerBaseUrl,
77
+ routerKey: cfg.routerKey,
78
+ profile: withBenchProfile(cfg.profile, { name: 'aec-worker' }),
79
+ },
80
+ prompt,
81
+ )
82
+ const content = res.finalText
70
83
  const verdict = await adapter.judge(task, content)
71
84
  return {
72
85
  prompt,
@@ -74,8 +87,10 @@ async function runAttempt(
74
87
  score: verdict.score,
75
88
  resolved: verdict.resolved,
76
89
  wallMs: Date.now() - startedAt,
77
- ...(res.costUsd !== undefined ? { costUsd: res.costUsd } : {}),
78
- ...(res.usage ? { tokensIn: res.usage.input, tokensOut: res.usage.output } : {}),
90
+ ...(res.usage.costUsd !== undefined ? { costUsd: res.usage.costUsd } : {}),
91
+ ...(res.usage.tokensKnown === false
92
+ ? {}
93
+ : { tokensIn: res.usage.input, tokensOut: res.usage.output }),
79
94
  }
80
95
  } catch (err) {
81
96
  lastErr = err
@@ -115,7 +130,7 @@ interface ArmResult {
115
130
 
116
131
  async function runArm(
117
132
  arm: ArmSpec,
118
- cfg: RouterConfig,
133
+ cfg: BenchRouterTarget,
119
134
  adapter: BenchmarkAdapter,
120
135
  tasks: BenchTask[],
121
136
  k: number,
@@ -144,7 +159,7 @@ async function runArm(
144
159
  benchmark: adapter.name,
145
160
  instanceId: task.id,
146
161
  condition: arm.condition,
147
- model: cfg.model,
162
+ model: benchProfileModel(cfg.profile),
148
163
  // k-attempt outcome = any usable attempt resolved (the oracle@k ceiling for
149
164
  // this run; the deployable selector is scored separately by corpus-replay).
150
165
  resolved: taskOutcomes.some((o) => o.resolved),
@@ -175,7 +190,13 @@ async function main(): Promise<void> {
175
190
  if (!Number.isFinite(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
176
191
  if (!Number.isFinite(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
177
192
 
178
- const cfg: RouterConfig = { routerBaseUrl, routerKey, model }
193
+ const cfg: BenchRouterTarget = {
194
+ routerBaseUrl,
195
+ routerKey,
196
+ profile: benchRouterProfile('aec-worker', model, {
197
+ retry: { maxAttempts: Number(process.env.MAX_ATTEMPTS ?? 3) },
198
+ }),
199
+ }
179
200
  const bench = process.env.BENCH ?? 'aec-bench'
180
201
  const adapter = resolveAdapter(bench)
181
202