@tangle-network/agent-bench 0.1.0 → 0.3.5

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 (139) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/HARNESS.md +302 -0
  3. package/README.md +26 -1
  4. package/fixtures/aec-bench.json +18 -0
  5. package/fixtures/agentbench-dbbench.json +22 -0
  6. package/fixtures/bfcl.json +45 -0
  7. package/fixtures/commit0.json +72 -0
  8. package/fixtures/crag.json +10 -0
  9. package/fixtures/dabstep.json +22 -0
  10. package/fixtures/enterpriseops-gym.json +103 -0
  11. package/fixtures/finresearchbench.json +21 -0
  12. package/fixtures/finsearchcomp.json +66 -0
  13. package/fixtures/frames.json +26 -0
  14. package/fixtures/hotpotqa.json +182 -0
  15. package/fixtures/nomiracl.json +26 -0
  16. package/fixtures/open-rag-bench.json +16 -0
  17. package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
  18. package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
  19. package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
  20. package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
  21. package/fixtures/pier-agent/no-model-task/task.toml +35 -0
  22. package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
  23. package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
  24. package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
  25. package/fixtures/programbench.json +17 -0
  26. package/fixtures/ragbench.json +21 -0
  27. package/fixtures/simpleqa.json +121 -0
  28. package/fixtures/t2-ragbench.json +13 -0
  29. package/fixtures/tau2-bench.json +16 -0
  30. package/fixtures/tau3-banking.json +16 -0
  31. package/fixtures/toollm.json +28 -0
  32. package/fixtures/webarena-verified.json +20 -0
  33. package/package.json +39 -15
  34. package/pier_agents/__init__.py +18 -0
  35. package/pier_agents/candidate_contract.py +755 -0
  36. package/pier_agents/process_boundary.py +321 -0
  37. package/pier_agents/tangle_candidate.py +907 -0
  38. package/pier_agents/workspace_boundary.py +368 -0
  39. package/scripts/appworld_driver.py +359 -0
  40. package/scripts/cadbench_prepare.py +22 -0
  41. package/scripts/cadgenbench_hard_parts.py +48 -0
  42. package/scripts/clbench_codebase_judge.py +73 -0
  43. package/scripts/commit0_judge.py +170 -0
  44. package/scripts/dabstep_judge.py +42 -0
  45. package/scripts/enterpriseops_gym_judge.py +281 -0
  46. package/scripts/programbench_judge.py +120 -0
  47. package/scripts/render-gate-chart.mjs +176 -0
  48. package/scripts/run-package-tests.mjs +56 -0
  49. package/scripts/terminate-pier-trial.mts +66 -0
  50. package/scripts/trata-hedge/README.md +56 -0
  51. package/scripts/trata-hedge/run.sh +60 -0
  52. package/scripts/trata-hedge/solve.py +83 -0
  53. package/scripts/verify-packed-consumer.mjs +224 -0
  54. package/scripts/verify-pier-agent.mts +715 -0
  55. package/scripts/verify-pier-pair.mts +74 -0
  56. package/scripts/verify-pier-recovery.mts +139 -0
  57. package/src/adapters.ts +26 -0
  58. package/src/benchmarks/_harness.test.mts +178 -0
  59. package/src/benchmarks/_harness.ts +239 -16
  60. package/src/benchmarks/agentbench.ts +163 -0
  61. package/src/benchmarks/appworld.test.mts +15 -9
  62. package/src/benchmarks/bfcl.ts +346 -0
  63. package/src/benchmarks/crag.ts +137 -0
  64. package/src/benchmarks/dabstep.test.mts +70 -0
  65. package/src/benchmarks/dabstep.ts +212 -0
  66. package/src/benchmarks/external-adapters.test.mts +150 -0
  67. package/src/benchmarks/finresearchbench.ts +269 -0
  68. package/src/benchmarks/humaneval.ts +20 -8
  69. package/src/benchmarks/nomiracl.ts +180 -0
  70. package/src/benchmarks/open-rag-bench.ts +153 -0
  71. package/src/benchmarks/rag-benchmarks.test.mts +138 -0
  72. package/src/benchmarks/rag-shared.ts +327 -0
  73. package/src/benchmarks/ragbench.ts +171 -0
  74. package/src/benchmarks/swe-bench.test.mts +61 -0
  75. package/src/benchmarks/swe-bench.ts +201 -19
  76. package/src/benchmarks/t2-ragbench.ts +166 -0
  77. package/src/benchmarks/tau-bench-shared.ts +214 -0
  78. package/src/benchmarks/tau2-bench.ts +30 -0
  79. package/src/benchmarks/tau3-banking.ts +29 -0
  80. package/src/benchmarks/terminal-bench.test.mts +33 -0
  81. package/src/benchmarks/terminal-bench.ts +23 -8
  82. package/src/benchmarks/toollm.ts +254 -0
  83. package/src/benchmarks/types.ts +42 -0
  84. package/src/benchmarks/webarena-verified.ts +200 -0
  85. package/src/commit0-prereqs.sh +0 -0
  86. package/src/coordination-mcp-container-reach.mts +181 -0
  87. package/src/decoder-live.mts +1 -1
  88. package/src/examples/README.md +103 -39
  89. package/src/examples/benchmark-matrix.mts +101 -0
  90. package/src/examples/lean-proof-gate.README.md +77 -0
  91. package/src/examples/lean-proof-gate.mts +162 -0
  92. package/src/examples/lean-verify.ts +95 -0
  93. package/src/examples/lean.Dockerfile +12 -0
  94. package/src/examples/math-demo.mts +9 -7
  95. package/src/examples/strategy-demo.mts +10 -12
  96. package/src/gate.ts +3 -2
  97. package/src/hev-eval.mts +69 -0
  98. package/src/hev-improve.mts +169 -0
  99. package/src/hev-structural.mts +688 -0
  100. package/src/index.ts +73 -0
  101. package/src/mbpp-structural.mts +662 -0
  102. package/src/pier-agent.test-fixtures.mts +19 -0
  103. package/src/pier-agent.test.mts +363 -0
  104. package/src/pier-agent.ts +657 -0
  105. package/src/pier-result-grader.mjs +30 -0
  106. package/src/pier-result-grader.test.mts +62 -0
  107. package/src/pier-result-grader.ts +108 -0
  108. package/src/pier-task-outcome.test.mts +117 -0
  109. package/src/pier-task-outcome.ts +240 -0
  110. package/src/pier-trial-controller.test.mts +412 -0
  111. package/src/pier-trial-controller.ts +858 -0
  112. package/src/pier-trial-supervisor.mjs +352 -0
  113. package/src/resolve-client.ts +25 -2
  114. package/src/run-benchmarks-cli.mts +72 -0
  115. package/src/run-benchmarks-report.ts +66 -0
  116. package/src/run-benchmarks.test.mts +231 -0
  117. package/src/run-benchmarks.ts +589 -0
  118. package/src/smoke-structural-rollout.mts +393 -0
  119. package/src/swe-bench-env.test.ts +207 -0
  120. package/src/swe-bench-env.ts +554 -0
  121. package/src/swe-jail.ts +293 -0
  122. package/src/swe-self-improve.mts +84 -0
  123. package/src/swe-structural-judge-policy.test.ts +117 -0
  124. package/src/swe-structural-judge-policy.ts +133 -0
  125. package/src/swe-structural-policy.test.ts +124 -0
  126. package/src/swe-structural-policy.ts +132 -0
  127. package/src/swe-structural-provenance.test.ts +93 -0
  128. package/src/swe-structural-provenance.ts +138 -0
  129. package/src/swe-structural.mts +1260 -0
  130. package/src/swe-temp.ts +14 -0
  131. package/src/tb-container-executor.mts +234 -0
  132. package/src/tb-container-executor.test.mts +99 -0
  133. package/src/tb-supervisor-sidecar.mts +222 -0
  134. package/src/trata-gepa.mts +1 -1
  135. package/steerers/eops-itsm-population.json +1 -0
  136. package/tb_agents/opencode_refine_agent.py +117 -0
  137. package/tb_agents/opencode_router_agent.py +406 -0
  138. package/tb_agents/opencode_supervisor_agent.py +239 -0
  139. package/tb_agents/script_agent.py +66 -0
@@ -0,0 +1,239 @@
1
+ """Terminal-Bench opencode agent with a coordination MCP mounted into the task container."""
2
+
3
+ import json
4
+ import os
5
+ import shlex
6
+ import subprocess
7
+ import time
8
+ from pathlib import Path
9
+
10
+ from terminal_bench.agents.base_agent import AgentResult
11
+ from terminal_bench.agents.failure_mode import FailureMode
12
+ from terminal_bench.terminal.tmux_session import TmuxSession
13
+
14
+ from tb_agents.opencode_router_agent import (
15
+ _CONTAINER_DIR,
16
+ OpenCodeRouterAgent,
17
+ )
18
+
19
+ DOCKER_BRIDGE_GATEWAY = os.environ.get("DOCKER_BRIDGE_GATEWAY", "172.17.0.1")
20
+ _WORKER_CONFIG_PATH = f"{_CONTAINER_DIR}/opencode-worker.json"
21
+ # The sidecar entrypoint, resolved relative to the bench dir (this file is bench/tb_agents/...).
22
+ _BENCH_DIR = Path(__file__).resolve().parent.parent
23
+ _REPO_DIR = _BENCH_DIR.parent
24
+ _SIDECAR_TS = _BENCH_DIR / "src" / "tb-supervisor-sidecar.mts"
25
+
26
+
27
+ def _resolve_tsx() -> list[str]:
28
+ """Prefer the pinned local tsx binary (no npx download / no shell hook rewrite)."""
29
+ for cand in (
30
+ _BENCH_DIR / "node_modules" / ".bin" / "tsx",
31
+ _REPO_DIR / "node_modules" / ".bin" / "tsx",
32
+ ):
33
+ if cand.exists():
34
+ return [str(cand)]
35
+ return ["npx", "tsx"]
36
+
37
+
38
+ class OpenCodeSupervisorAgent(OpenCodeRouterAgent):
39
+ """`OpenCodeRouterAgent` + a coordination MCP mounted into the in-container opencode.
40
+
41
+ Adds a host orchestration sidecar and the `mcp.coordination` config block; otherwise
42
+ identical to Arm A. Worker model defaults to the same model as the supervisor.
43
+ """
44
+
45
+ def __init__(self, model_name: str, *args, **kwargs):
46
+ super().__init__(model_name, *args, **kwargs)
47
+ self._mcp_port: int | None = None
48
+ self._sidecar: subprocess.Popen | None = None
49
+ self._sidecar_stdout = None
50
+ self._sidecar_port_file: Path | None = None
51
+ self._sidecar_log_file: Path | None = None
52
+
53
+ @staticmethod
54
+ def name() -> str:
55
+ return "agent-runtime-opencode-supervisor"
56
+
57
+ def _mcp_url(self) -> str:
58
+ return f"http://{DOCKER_BRIDGE_GATEWAY}:{self._mcp_port}/mcp"
59
+
60
+ def _build_router_config(self) -> str:
61
+ """Base router config plus the coordination MCP block."""
62
+ config = json.loads(super()._build_router_config())
63
+ if self._mcp_port is not None:
64
+ config["mcp"] = {
65
+ "coordination": {
66
+ "type": "remote",
67
+ "url": self._mcp_url(),
68
+ "enabled": True,
69
+ }
70
+ }
71
+ return json.dumps(config)
72
+
73
+ def _write_config_to_container(self, session: TmuxSession) -> None:
74
+ super()._write_config_to_container(session)
75
+ worker_config = OpenCodeRouterAgent._build_router_config(self)
76
+ session.container.exec_run(
77
+ [
78
+ "sh",
79
+ "-c",
80
+ f"printf '%s' {shlex.quote(worker_config)} > {_WORKER_CONFIG_PATH}",
81
+ ]
82
+ )
83
+
84
+ def _resolve_container_id(self, session: TmuxSession) -> str | None:
85
+ return getattr(session.container, "id", None) or getattr(
86
+ session.container, "short_id", None
87
+ )
88
+
89
+ def _start_sidecar(self, container_id: str, logging_dir: Path | None) -> None:
90
+ base = logging_dir if logging_dir is not None else Path.cwd()
91
+ base.mkdir(parents=True, exist_ok=True)
92
+ self._sidecar_port_file = base / ".tb-sidecar-port"
93
+ self._sidecar_log_file = base / "tb-sidecar-events.jsonl"
94
+ self._sidecar_port_file.unlink(missing_ok=True)
95
+
96
+ env = dict(os.environ)
97
+ env.update(
98
+ {
99
+ "TB_TARGET_CONTAINER": container_id,
100
+ "OPENAI_API_KEY": self._router_api_key,
101
+ "OPENAI_BASE_URL": self._router_base_url,
102
+ "WORKER_MODEL": self._model_name,
103
+ "TB_SIDECAR_PORT_FILE": str(self._sidecar_port_file),
104
+ "TB_SIDECAR_LOG": str(self._sidecar_log_file),
105
+ "DOCKER_BRIDGE_GATEWAY": DOCKER_BRIDGE_GATEWAY,
106
+ }
107
+ )
108
+ self._sidecar_stdout = open(base / "tb-sidecar.stdout.log", "w")
109
+ self._logger.info("Starting orchestration sidecar for container %s", container_id)
110
+ self._sidecar = subprocess.Popen(
111
+ [*_resolve_tsx(), str(_SIDECAR_TS)],
112
+ cwd=str(_BENCH_DIR),
113
+ env=env,
114
+ stdout=self._sidecar_stdout,
115
+ stderr=subprocess.STDOUT,
116
+ )
117
+ deadline = time.time() + 60.0
118
+ while time.time() < deadline:
119
+ if self._sidecar.poll() is not None:
120
+ raise RuntimeError(
121
+ f"orchestration sidecar exited early with code {self._sidecar.returncode} "
122
+ f"(see {base / 'tb-sidecar.stdout.log'})"
123
+ )
124
+ if self._sidecar_port_file.exists():
125
+ txt = self._sidecar_port_file.read_text().strip()
126
+ if txt.isdigit():
127
+ self._mcp_port = int(txt)
128
+ self._logger.info(
129
+ "Sidecar ready: mcp port=%d url=%s", self._mcp_port, self._mcp_url()
130
+ )
131
+ return
132
+ time.sleep(0.5)
133
+ raise RuntimeError("orchestration sidecar did not publish a port within 60s")
134
+
135
+ def _stop_sidecar(self) -> None:
136
+ if self._sidecar is None:
137
+ return
138
+ try:
139
+ self._sidecar.terminate()
140
+ try:
141
+ self._sidecar.wait(timeout=30)
142
+ except subprocess.TimeoutExpired:
143
+ self._sidecar.kill()
144
+ except Exception as e: # noqa: BLE001 - teardown is never fatal
145
+ self._logger.warning("Could not stop sidecar cleanly: %s", e)
146
+ finally:
147
+ if self._sidecar_stdout is not None:
148
+ try:
149
+ self._sidecar_stdout.close()
150
+ except Exception: # noqa: BLE001
151
+ pass
152
+
153
+ def _orchestration_summary(self) -> dict:
154
+ summary: dict = {
155
+ "settled": [],
156
+ "worker_outputs": [],
157
+ "history_len": 0,
158
+ "worker_ran": False,
159
+ "worker_tokens": {"input": 0, "output": 0},
160
+ }
161
+ if self._sidecar_log_file is None:
162
+ return summary
163
+ final = Path(str(self._sidecar_log_file) + ".final.json")
164
+ try:
165
+ if final.exists():
166
+ data = json.loads(final.read_text())
167
+ settled = data.get("settled", []) or []
168
+ summary["settled"] = settled
169
+ summary["worker_outputs"] = data.get("workerOutputs", []) or []
170
+ summary["history_len"] = len(data.get("history", []) or [])
171
+ summary["worker_tokens"] = data.get(
172
+ "workerTokens", {"input": 0, "output": 0}
173
+ )
174
+ summary["worker_ran"] = any(
175
+ isinstance(w, dict) and w.get("containerId")
176
+ for w in summary["worker_outputs"]
177
+ ) or len(settled) > 0
178
+ except Exception as e: # noqa: BLE001
179
+ self._logger.warning("Could not read orchestration summary: %s", e)
180
+ return summary
181
+
182
+ def perform_task(
183
+ self,
184
+ instruction: str,
185
+ session: TmuxSession,
186
+ logging_dir: Path | None = None,
187
+ ) -> AgentResult:
188
+ container_id = self._resolve_container_id(session)
189
+ if not container_id:
190
+ self._logger.error("Could not resolve container id for supervisor sidecar.")
191
+ return AgentResult(failure_mode=FailureMode.UNKNOWN_AGENT_ERROR)
192
+
193
+ try:
194
+ self._start_sidecar(container_id, logging_dir)
195
+ except Exception as e: # noqa: BLE001
196
+ self._logger.error("Sidecar failed to start: %s", e)
197
+ self._mcp_port = None
198
+ return AgentResult(failure_mode=FailureMode.UNKNOWN_AGENT_ERROR)
199
+
200
+ try:
201
+ supervised_instruction = (
202
+ "You are the supervisor. You have an MCP server named \"coordination\" with "
203
+ "tools including spawn_agent(profile, task), observe_agent(workerId), "
204
+ "await_event(), and stop(). A spawned worker runs a full coding agent INSIDE "
205
+ "THIS SAME container and its file changes persist here, so you may delegate "
206
+ "concrete sub-tasks (e.g. \"install perl and 7z\", \"crack the hash with john\") "
207
+ "to workers via spawn_agent, then observe_agent/await_event for their results. "
208
+ "Delegate independent or heavy sub-tasks to workers when useful; do the rest "
209
+ "yourself. Complete the task fully.\n\n"
210
+ f"TASK:\n{instruction}"
211
+ )
212
+ result = super().perform_task(supervised_instruction, session, logging_dir)
213
+ finally:
214
+ self._stop_sidecar()
215
+
216
+ summary = self._orchestration_summary()
217
+ if logging_dir is not None:
218
+ try:
219
+ (logging_dir / "orchestration-summary.json").write_text(
220
+ json.dumps(summary, indent=2)
221
+ )
222
+ except Exception: # noqa: BLE001
223
+ pass
224
+ self._logger.info(
225
+ "Arm B orchestration: workers_settled=%d history_len=%d worker_ran=%s",
226
+ len(summary.get("settled", [])),
227
+ summary.get("history_len", 0),
228
+ summary.get("worker_ran"),
229
+ )
230
+ wt = summary.get("worker_tokens") or {}
231
+ worker_in = int(wt.get("input") or 0)
232
+ worker_out = int(wt.get("output") or 0)
233
+ if result.failure_mode == FailureMode.AGENT_INSTALLATION_FAILED:
234
+ return result
235
+ return AgentResult(
236
+ total_input_tokens=result.total_input_tokens + worker_in,
237
+ total_output_tokens=result.total_output_tokens + worker_out,
238
+ failure_mode=result.failure_mode,
239
+ )
@@ -0,0 +1,66 @@
1
+ """Custom Terminal-Bench agent that executes a precomputed shell script in the
2
+ task container, then returns control to the harness so the task's own verifier
3
+ scores the resulting container state.
4
+
5
+ This is the deterministic-judge seam for agent-runtime/bench: our worker produces
6
+ an *artifact* (the commands it ran to attempt the task) and the judge replays that
7
+ artifact in a fresh task container via `tb run --agent-import-path
8
+ tb_agents.script_agent:ScriptAgent --agent-kwarg script_path=<file>`. The harness
9
+ then runs the per-task tests exactly as it does for the oracle agent, so the score
10
+ comes from Terminal-Bench's published verifier — never a self-authored judge.
11
+
12
+ Mechanism mirrors terminal_bench.agents.oracle_agent.OracleAgent: copy the script
13
+ into the container and run it with `block=True`, so the agent step completes only
14
+ after the script finishes (or the harness agent-timeout fires).
15
+ """
16
+
17
+ from pathlib import Path
18
+
19
+ from terminal_bench.agents.base_agent import AgentResult, BaseAgent
20
+ from terminal_bench.agents.failure_mode import FailureMode
21
+ from terminal_bench.terminal.tmux_session import TmuxSession
22
+
23
+
24
+ class ScriptAgent(BaseAgent):
25
+ def __init__(self, script_path: str | None = None, **kwargs):
26
+ super().__init__(**kwargs)
27
+ if not script_path:
28
+ raise ValueError(
29
+ "ScriptAgent requires --agent-kwarg script_path=<path to the "
30
+ "artifact script>. Got none."
31
+ )
32
+ path = Path(script_path)
33
+ if not path.is_file():
34
+ raise FileNotFoundError(f"ScriptAgent script_path not found: {path}")
35
+ self._script_path = path
36
+
37
+ @staticmethod
38
+ def name() -> str:
39
+ return "agent-runtime-script"
40
+
41
+ def perform_task(
42
+ self,
43
+ instruction: str,
44
+ session: TmuxSession,
45
+ logging_dir: Path | None = None,
46
+ ) -> AgentResult:
47
+ # An empty artifact = no-op attempt (the must-fail control). Skip execution
48
+ # so the verifier scores the untouched container — equivalent to the nop
49
+ # agent — instead of running an empty script.
50
+ if self._script_path.read_text().strip():
51
+ session.copy_to_container(
52
+ self._script_path,
53
+ container_dir="/agent-runtime",
54
+ container_filename="attempt.sh",
55
+ )
56
+ session.send_keys(
57
+ ["bash /agent-runtime/attempt.sh", "Enter"],
58
+ max_timeout_sec=float("inf"),
59
+ block=True,
60
+ )
61
+
62
+ return AgentResult(
63
+ total_input_tokens=0,
64
+ total_output_tokens=0,
65
+ failure_mode=FailureMode.NONE,
66
+ )