@tangle-network/agent-bench 0.1.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.
Files changed (111) hide show
  1. package/README.md +15 -0
  2. package/package.json +35 -0
  3. package/src/adapters.ts +60 -0
  4. package/src/aec-gate.mts +217 -0
  5. package/src/atom-humaneval.mts +197 -0
  6. package/src/atom-mcp-e2e.mts +223 -0
  7. package/src/benchmarks/_harness.ts +206 -0
  8. package/src/benchmarks/aec-bench.test.mts +53 -0
  9. package/src/benchmarks/aec-bench.ts +319 -0
  10. package/src/benchmarks/appworld.test.mts +45 -0
  11. package/src/benchmarks/appworld.ts +426 -0
  12. package/src/benchmarks/cad-design.ts +429 -0
  13. package/src/benchmarks/cadbench.ts +135 -0
  14. package/src/benchmarks/cadgenbench.ts +121 -0
  15. package/src/benchmarks/commit0.test.mts +71 -0
  16. package/src/benchmarks/commit0.ts +266 -0
  17. package/src/benchmarks/enterpriseops-gym.test.mts +77 -0
  18. package/src/benchmarks/enterpriseops-gym.ts +343 -0
  19. package/src/benchmarks/finsearchcomp.ts +371 -0
  20. package/src/benchmarks/frames.ts +520 -0
  21. package/src/benchmarks/hotpotqa.ts +320 -0
  22. package/src/benchmarks/humaneval.ts +251 -0
  23. package/src/benchmarks/mind2web.ts +311 -0
  24. package/src/benchmarks/programbench.test.mts +60 -0
  25. package/src/benchmarks/programbench.ts +210 -0
  26. package/src/benchmarks/simpleqa.ts +390 -0
  27. package/src/benchmarks/swe-bench.ts +152 -0
  28. package/src/benchmarks/terminal-bench.ts +182 -0
  29. package/src/benchmarks/trata-hedge.ts +496 -0
  30. package/src/benchmarks/types.ts +58 -0
  31. package/src/browser/adapters/bad-design-audit.ts +153 -0
  32. package/src/browser/adapters/bad-design-audit.verify.ts +85 -0
  33. package/src/browser/adapters/bad.ts +165 -0
  34. package/src/browser/agent-adapter.ts +145 -0
  35. package/src/browser/process-adapter.ts +146 -0
  36. package/src/browser/run-to-spans.ts +105 -0
  37. package/src/browser/run-to-spans.verify.ts +89 -0
  38. package/src/browser/ui-reviewer.ts +200 -0
  39. package/src/browser/ui-reviewer.verify.ts +94 -0
  40. package/src/browser/verify.ts +96 -0
  41. package/src/clbench-codebase-gate.mts +314 -0
  42. package/src/clbench-context-gate.mts +305 -0
  43. package/src/cloud-loop.mts +138 -0
  44. package/src/coding-skills/minimal-diff.md +9 -0
  45. package/src/coding-skills/read-before-edit.md +9 -0
  46. package/src/coding-skills/reproduce-first.md +10 -0
  47. package/src/coding-skills/run-tests-after-edit.md +9 -0
  48. package/src/coding-skills/trace-the-failure.md +9 -0
  49. package/src/commit0-env-run.mts +59 -0
  50. package/src/commit0-env.ts +173 -0
  51. package/src/commit0-gate.mts +529 -0
  52. package/src/commit0-prereqs.sh +48 -0
  53. package/src/corpus-replay.mts +300 -0
  54. package/src/corpus-report.mts +504 -0
  55. package/src/corpus.test.mts +273 -0
  56. package/src/corpus.ts +325 -0
  57. package/src/decoder-live.mts +133 -0
  58. package/src/directives.ts +84 -0
  59. package/src/diverse-gate.mjs +112 -0
  60. package/src/egress-probe.mts +26 -0
  61. package/src/eops-skills/address-every-subtask.md +5 -0
  62. package/src/eops-skills/exact-tools-and-args.md +5 -0
  63. package/src/eops-skills/full-sequence-to-goal.md +5 -0
  64. package/src/eops-skills/ground-every-value.md +5 -0
  65. package/src/eops-skills/honor-the-policies.md +5 -0
  66. package/src/examples/README.md +58 -0
  67. package/src/examples/math-demo.mts +110 -0
  68. package/src/examples/strategy-demo.mts +119 -0
  69. package/src/fleet.mts +121 -0
  70. package/src/gate-cli.mts +101 -0
  71. package/src/gate.test.mts +129 -0
  72. package/src/gate.ts +460 -0
  73. package/src/generate-eval/certify.ts +178 -0
  74. package/src/generate-eval/schema.ts +78 -0
  75. package/src/humaneval-gate.mts +204 -0
  76. package/src/humaneval-repair-gate.mts +143 -0
  77. package/src/index.ts +19 -0
  78. package/src/mcp-mount-probe.mts +126 -0
  79. package/src/profile-coordinates.ts +134 -0
  80. package/src/profiles.ts +128 -0
  81. package/src/refine-loop.test.mts +106 -0
  82. package/src/refine-loop.ts +106 -0
  83. package/src/research-gate.mts +132 -0
  84. package/src/research-shot.ts +134 -0
  85. package/src/resolve-client.ts +58 -0
  86. package/src/router-executor.ts +51 -0
  87. package/src/run-pool.ts +48 -0
  88. package/src/runtime-hook-recorder.ts +137 -0
  89. package/src/sandbox-run.ts +125 -0
  90. package/src/search-bench/bridge.ts +124 -0
  91. package/src/search-bench/export.mts +0 -0
  92. package/src/search-bench/parametric-check.mts +63 -0
  93. package/src/search-bench/profiles.ts +98 -0
  94. package/src/search-bench/run.mts +287 -0
  95. package/src/search-bench/tasks-fresh.ts +688 -0
  96. package/src/search-bench/tasks.ts +129 -0
  97. package/src/search-tool.ts +95 -0
  98. package/src/selector.test.mts +189 -0
  99. package/src/selector.ts +366 -0
  100. package/src/skill-sandbox-smoke.mts +100 -0
  101. package/src/stats.mts +90 -0
  102. package/src/terminal-compare.ts +519 -0
  103. package/src/trajectory-assemble.mjs +130 -0
  104. package/src/trata-gate.mts +243 -0
  105. package/src/trata-gepa.mts +434 -0
  106. package/src/worker-blender.ts +230 -0
  107. package/src/worker-browser.ts +102 -0
  108. package/src/worker-build123d.ts +143 -0
  109. package/src/worker-cad.ts +451 -0
  110. package/src/worker.ts +136 -0
  111. package/src/workspace-loop.mts +133 -0
@@ -0,0 +1,343 @@
1
+ /**
2
+ * EnterpriseOps-Gym adapter (ServiceNow-AI/EnterpriseOps-Gym, Apache-2.0) —
3
+ * stateful agentic planning + tool use in enterprise settings. Each record is an
4
+ * enterprise-ops task (Customer Service, HR, ITSM, Calendar, Email, Drive, Teams,
5
+ * Hybrid) handed to the agent with a domain `system_prompt`, a `selected_tools`
6
+ * allow-list, and one or more containerized gym MCP servers (`gym_servers_config`).
7
+ * Worker artifact = the ordered tool-call transcript the agent would issue against
8
+ * those servers, emitted as a single fenced ```json block of
9
+ * `{ "calls": [ { "tool": ..., "arguments": {...}, "gym_name"?: ... } ] }`.
10
+ *
11
+ * Judge = the benchmark's OWN deterministic state-checker. The driver replays the
12
+ * transcript against a freshly-seeded gym server (mutating its database), then runs
13
+ * each task's `database_state` verifier — an SQL SELECT executed via the gym
14
+ * server's /api/sql-runner endpoint, compared to `expected_value` under
15
+ * `comparison_type` (equals/greater_than/less_than/contains). GRADED: score =
16
+ * (verifiers passing) / (total verifiers) = the bench's verifier_level_pass_rate;
17
+ * binary `resolved` = ALL verifiers pass = the bench's overall_success_rate. Fully
18
+ * deterministic — no LLM judge.
19
+ *
20
+ * loadTasks enumerates the real suite from the HF rows server (config = tool-set
21
+ * MODE oracle|plus_5_tools|plus_10_tools|plus_15_tools; split = DOMAIN); a committed
22
+ * sample (bench/fixtures/enterpriseops-gym.json) loads offline.
23
+ *
24
+ * Requires for a LIVE judge run: a Docker daemon with the domain gym images
25
+ * (`docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-<domain>:latest`) up
26
+ * on the ports in `gym_servers_config`, seeded from gym_dbs.zip. preflight + judge
27
+ * fail loud with the exact pull/run/unzip step when a server is unreachable — never
28
+ * a fabricated score. No portable gold artifact ships, so goldArtifact is undefined.
29
+ */
30
+
31
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
32
+ import { join } from 'node:path'
33
+ import type { OutputAdapter } from '@tangle-network/agent-runtime/loops'
34
+ import { benchRoot, runVenvScriptStdin } from './_harness'
35
+ import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
36
+
37
+ const FIXTURES = join(benchRoot, 'fixtures', 'enterpriseops-gym.json')
38
+ const JUDGE = join(benchRoot, 'scripts', 'enterpriseops_gym_judge.py')
39
+
40
+ /** Monotonic discriminator so concurrent judge calls stage distinct task-cache files. */
41
+ let judgeCallSeq = 0
42
+
43
+ const DATASET = 'ServiceNow-AI/EnterpriseOps-Gym'
44
+ /** Tool-set mode = HF config; oracle ships exact tools, plus_N adds N distractors. */
45
+ const DEFAULT_MODE = 'oracle'
46
+ /** Domain = HF split. */
47
+ const DEFAULT_DOMAIN = 'itsm'
48
+
49
+ const rowsApi = (mode: string, domain: string) =>
50
+ `https://datasets-server.huggingface.co/rows?dataset=${encodeURIComponent(DATASET)}&config=${encodeURIComponent(mode)}&split=${encodeURIComponent(domain)}`
51
+
52
+ interface GymServerConfig {
53
+ mcp_server_name: string
54
+ mcp_server_url: string
55
+ seed_database_file: string
56
+ context?: Record<string, string>
57
+ user_info?: Record<string, unknown>
58
+ }
59
+
60
+ interface Verifier {
61
+ verifier_type: string
62
+ name: string
63
+ description?: string
64
+ gym_name: string
65
+ validation_config: { query: string; expected_value: unknown; comparison_type: string }
66
+ }
67
+
68
+ interface EopsRow {
69
+ task_id: string
70
+ domain: string
71
+ system_prompt: string
72
+ user_prompt: string
73
+ selected_tools: string[]
74
+ restricted_tools: string[]
75
+ mcp_endpoint: string
76
+ number_of_runs: number
77
+ reset_database_between_runs: boolean
78
+ /** HF parquet stores these as JSON strings; fixtures store them as parsed arrays. */
79
+ gym_servers_config: string | GymServerConfig[]
80
+ verifiers: string | Verifier[]
81
+ }
82
+
83
+ interface EopsMeta {
84
+ taskId: string
85
+ domain: string
86
+ mode: string
87
+ selectedTools: string[]
88
+ servers: GymServerConfig[]
89
+ verifiers: Verifier[]
90
+ }
91
+
92
+ /** Worker transcript = the last fenced ```json block, else the raw text. */
93
+ export const enterpriseOpsTranscriptOutput: OutputAdapter<string> = {
94
+ parse(events) {
95
+ let text = ''
96
+ for (const ev of events) {
97
+ const d = (ev as { data?: Record<string, unknown> })?.data
98
+ const t = d?.finalText ?? d?.text ?? d?.result
99
+ if (typeof t === 'string' && t.length > 0) text = t
100
+ }
101
+ const fences = [...text.matchAll(/```(?:json)?\s*\n([\s\S]*?)```/g)]
102
+ return (fences.at(-1)?.[1] ?? text).trim()
103
+ },
104
+ }
105
+
106
+ function asArray<T>(v: string | T[]): T[] {
107
+ return typeof v === 'string' ? (JSON.parse(v) as T[]) : v
108
+ }
109
+
110
+ function workerContract(tools: string[]): string {
111
+ return [
112
+ '',
113
+ `You have exactly these tools available (call NO others): ${tools.join(', ')}.`,
114
+ 'Plan the full sequence of tool calls that brings the enterprise database to the required final state, honoring every policy in the role above.',
115
+ 'Emit your COMPLETE plan as the LAST thing in your reply, in a single fenced ```json block, as an object:',
116
+ '{ "calls": [ { "tool": "<tool_name>", "arguments": { ... } } ] }',
117
+ 'Include one entry per tool call in execution order. Nothing after the closing fence.',
118
+ ].join('\n')
119
+ }
120
+
121
+ function rowToTask(row: EopsRow, mode: string): BenchTask {
122
+ const servers = asArray<GymServerConfig>(row.gym_servers_config)
123
+ const verifiers = asArray<Verifier>(row.verifiers)
124
+ const meta: EopsMeta = {
125
+ taskId: row.task_id,
126
+ domain: row.domain,
127
+ mode,
128
+ selectedTools: row.selected_tools,
129
+ servers,
130
+ verifiers,
131
+ }
132
+ return {
133
+ id: row.task_id,
134
+ split: row.domain,
135
+ prompt: [row.system_prompt, '', row.user_prompt, workerContract(row.selected_tools)].join('\n'),
136
+ metadata: meta as unknown as Record<string, unknown>,
137
+ }
138
+ }
139
+
140
+ function readMeta(task: BenchTask): EopsMeta {
141
+ const md = task.metadata
142
+ if (
143
+ !md ||
144
+ typeof md.taskId !== 'string' ||
145
+ !Array.isArray(md.servers) ||
146
+ !Array.isArray(md.verifiers) ||
147
+ (md.verifiers as unknown[]).length === 0
148
+ ) {
149
+ throw new Error(`enterpriseops-gym task ${task.id} missing metadata — loadTasks did not populate it`)
150
+ }
151
+ return md as unknown as EopsMeta
152
+ }
153
+
154
+ function selectRows(rows: EopsRow[], mode: string, opts: LoadOptions): BenchTask[] {
155
+ let tasks = rows.map((r) => rowToTask(r, mode))
156
+ if (opts.ids) {
157
+ const want = new Set(opts.ids)
158
+ tasks = tasks.filter((t) => want.has(t.id))
159
+ } else if (opts.limit !== undefined) {
160
+ tasks = tasks.slice(0, opts.limit)
161
+ }
162
+ return tasks
163
+ }
164
+
165
+ async function loadFixtures(mode: string, opts: LoadOptions): Promise<BenchTask[]> {
166
+ const rows = JSON.parse(await readFile(FIXTURES, 'utf8')) as EopsRow[]
167
+ console.warn(
168
+ `[enterpriseops-gym] EOPS_FIXTURES=1 — loading ${rows.length} committed sample rows from ${FIXTURES} (no HF fetch)`,
169
+ )
170
+ const domain = opts.split
171
+ const scoped = domain ? rows.filter((r) => r.domain === domain) : rows
172
+ return selectRows(scoped, mode, opts)
173
+ }
174
+
175
+ /** Pull real rows from the HF rows server (paged) for one mode/domain. Throws loud on a non-OK response. */
176
+ async function fetchRows(mode: string, domain: string, opts: LoadOptions): Promise<EopsRow[]> {
177
+ const target = opts.ids ? opts.ids.length * 4 : (opts.limit ?? 16)
178
+ const rows: EopsRow[] = []
179
+ const want = opts.ids ? new Set(opts.ids) : null
180
+ const page = 100
181
+ const base = rowsApi(mode, domain)
182
+ for (let offset = 0; offset < 1024 && rows.length < target; offset += page) {
183
+ const res = await fetch(`${base}&offset=${offset}&length=${page}`)
184
+ if (!res.ok) {
185
+ throw new Error(`enterpriseops-gym rows HTTP ${res.status} (offset ${offset}): ${(await res.text()).slice(0, 200)}`)
186
+ }
187
+ const body = (await res.json()) as { rows?: Array<{ row: EopsRow }> }
188
+ const got = body.rows ?? []
189
+ if (got.length === 0) break
190
+ for (const r of got) {
191
+ if (want && !want.has(r.row.task_id)) continue
192
+ rows.push(r.row)
193
+ }
194
+ if (got.length < page) break
195
+ }
196
+ if (rows.length === 0) throw new Error(`enterpriseops-gym: no rows matched ${JSON.stringify(opts)} for ${mode}/${domain}`)
197
+ return rows
198
+ }
199
+
200
+ /**
201
+ * Run the benchmark's own state-checker for one task over the worker's transcript.
202
+ * The driver replays the tool calls against the live gym server, runs each
203
+ * database_state verifier's SQL via /api/sql-runner, and reports {passes,total}.
204
+ * Score = passes/total (verifier_level_pass_rate); resolved = all pass
205
+ * (overall_success_rate). This is the expensive Docker-backed boundary — delegated
206
+ * to the python driver, not reimplemented. The transcript is piped on stdin via the
207
+ * shared stdin-aware runner (execFile's `input` is not honored async and hangs the
208
+ * reader).
209
+ */
210
+ async function runJudge(meta: EopsMeta, artifact: string): Promise<BenchScore> {
211
+ // Stage the full task record (servers + verifiers) so the driver has the live
212
+ // server URLs/contexts and the SQL it must run. The path is UNIQUE per call: the gate
213
+ // runs k judges for one task concurrently, so a `${taskId}.json` shared path would race.
214
+ const taskJsonPath = join(
215
+ benchRoot,
216
+ '.eops-task-cache',
217
+ `${meta.taskId}-${process.pid}-${judgeCallSeq++}.json`,
218
+ )
219
+ await writeTaskCache(taskJsonPath, meta)
220
+ let stdout: string
221
+ try {
222
+ stdout = await runVenvScriptStdin(JUDGE, ['judge', '--task-json', taskJsonPath], artifact, { cwd: benchRoot })
223
+ } catch (err) {
224
+ const e = err as { message?: string }
225
+ throw new Error(
226
+ `enterpriseops-gym judge failed for ${meta.taskId}: ${(e.message || String(err)).slice(0, 1500)}\n` +
227
+ `Fix: (1) run the ${meta.domain} gym server — ` +
228
+ `docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-${meta.domain}:latest ; ` +
229
+ `docker run -d -p <host>:8005 shivakrishnareddyma225/enterpriseops-gym-mcp-${meta.domain}:latest ; ` +
230
+ `(2) unzip gym_dbs.zip (ServiceNow/EnterpriseOps-Gym) and export EOPS_GYM_DBS_DIR to its dir ` +
231
+ `(the judge seeds each task's database from seed_database_file).`,
232
+ )
233
+ } finally {
234
+ await rm(taskJsonPath, { force: true }).catch(() => {})
235
+ }
236
+ const report = JSON.parse(stdout.trim().split('\n').at(-1) ?? '{}') as {
237
+ success?: boolean
238
+ passes?: number
239
+ total?: number
240
+ error?: string
241
+ }
242
+ if (report.error) throw new Error(`enterpriseops-gym judge error for ${meta.taskId}: ${report.error}`)
243
+ if (typeof report.passes !== 'number' || typeof report.total !== 'number') {
244
+ throw new Error(`enterpriseops-gym judge returned no {passes,total}: ${stdout.slice(0, 400)}`)
245
+ }
246
+ const score = report.total > 0 ? report.passes / report.total : 0
247
+ return {
248
+ resolved: report.success === true && report.total > 0 && report.passes === report.total,
249
+ score,
250
+ detail: JSON.stringify({ taskId: meta.taskId, domain: meta.domain, passes: report.passes, total: report.total }),
251
+ }
252
+ }
253
+
254
+ async function writeTaskCache(path: string, meta: EopsMeta): Promise<void> {
255
+ await mkdir(join(path, '..'), { recursive: true })
256
+ await writeFile(
257
+ path,
258
+ JSON.stringify({ task_id: meta.taskId, domain: meta.domain, gym_servers_config: meta.servers, verifiers: meta.verifiers }),
259
+ )
260
+ }
261
+
262
+ /** Ping the configured gym servers' SQL endpoint; throw loud with the docker fix if any is unreachable. */
263
+ async function probeServers(servers: GymServerConfig[], domain: string): Promise<void> {
264
+ for (const s of servers) {
265
+ const url = `${s.mcp_server_url.replace(/\/$/, '')}/api/sql-runner`
266
+ try {
267
+ // A HEAD/empty POST just proves reachability; a real query runs in judge.
268
+ const res = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' })
269
+ // Any HTTP response (even an error status) proves the server is up. Only a
270
+ // transport failure (refused/ENOTFOUND) means the container is not running.
271
+ void res.status
272
+ } catch (err) {
273
+ throw new Error(
274
+ `enterpriseops-gym preflight: ${s.mcp_server_name} unreachable at ${url}: ${err instanceof Error ? err.message : err}\n` +
275
+ `Fix: docker pull shivakrishnareddyma225/enterpriseops-gym-mcp-${domain}:latest ; ` +
276
+ `docker run -d -p <port>:<port> shivakrishnareddyma225/enterpriseops-gym-mcp-${domain}:latest ; ` +
277
+ `seed from gym_dbs.zip. Set EOPS_FIXTURES=1 to load sample tasks offline (judge still needs a live server).`,
278
+ )
279
+ }
280
+ }
281
+ }
282
+
283
+ export function createEnterpriseOpsGymAdapter(): BenchmarkAdapter {
284
+ const fixturesMode = process.env.EOPS_FIXTURES === '1'
285
+ const mode = process.env.EOPS_MODE ?? DEFAULT_MODE
286
+
287
+ return {
288
+ name: 'enterpriseops-gym',
289
+ output: enterpriseOpsTranscriptOutput,
290
+
291
+ async preflight() {
292
+ // Fixtures mode proves only that the sample file is readable; a live judge
293
+ // still requires running gym servers (and fails loud there if absent).
294
+ if (fixturesMode) {
295
+ await readFile(FIXTURES, 'utf8').catch((err) => {
296
+ throw new Error(`EOPS_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`)
297
+ })
298
+ return
299
+ }
300
+ // Live mode: probe the gym servers for the default domain (the suite's tasks
301
+ // each carry their own server config; preflight verifies reachability up
302
+ // front so a batch fails fast with the docker fix rather than mid-run).
303
+ const sample = await loadFixtures(mode, { split: DEFAULT_DOMAIN, limit: 1 }).catch(() => [])
304
+ const servers = sample[0] ? readMeta(sample[0]).servers : []
305
+ if (servers.length === 0) {
306
+ throw new Error(
307
+ `enterpriseops-gym preflight: no gym_servers_config to probe. ` +
308
+ `Set EOPS_FIXTURES=1 to load offline, or ensure the dataset rows carry gym_servers_config.`,
309
+ )
310
+ }
311
+ await probeServers(servers, DEFAULT_DOMAIN)
312
+ },
313
+
314
+ async loadTasks(opts: LoadOptions = {}) {
315
+ const domain = opts.split ?? DEFAULT_DOMAIN
316
+ if (fixturesMode) return loadFixtures(mode, opts)
317
+ let rows: EopsRow[]
318
+ try {
319
+ rows = await fetchRows(mode, domain, opts)
320
+ } catch (err) {
321
+ console.warn(
322
+ `[enterpriseops-gym] live rows fetch failed (${err instanceof Error ? err.message : err}); falling back to committed sample at ${FIXTURES}`,
323
+ )
324
+ return loadFixtures(mode, opts)
325
+ }
326
+ return selectRows(rows, mode, opts)
327
+ },
328
+
329
+ async goldArtifact() {
330
+ // The benchmark ships no portable per-task oracle transcript — the reference
331
+ // is the seeded final DB state the verifiers check, not a tool-call script.
332
+ // Judge correctness is proven by replaying a real solve against the live gym
333
+ // server, not by a synthetic gold transcript. Returns undefined (documented,
334
+ // not faked).
335
+ return undefined
336
+ },
337
+
338
+ async judge(task: BenchTask, artifact: string): Promise<BenchScore> {
339
+ const meta = readMeta(task)
340
+ return runJudge(meta, artifact)
341
+ },
342
+ }
343
+ }