@tangle-network/agent-bench 0.1.0 → 0.3.6

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 (317) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/HARNESS.md +302 -0
  3. package/README.md +39 -2
  4. package/dist/adapters.d.ts +15 -0
  5. package/dist/adapters.js +43 -0
  6. package/dist/adapters.js.map +1 -0
  7. package/dist/benchmarks/_harness.d.ts +125 -0
  8. package/dist/benchmarks/_harness.js +33 -0
  9. package/dist/benchmarks/_harness.js.map +1 -0
  10. package/dist/benchmarks/aec-bench.d.ts +27 -0
  11. package/dist/benchmarks/aec-bench.js +8 -0
  12. package/dist/benchmarks/aec-bench.js.map +1 -0
  13. package/dist/benchmarks/agentbench.d.ts +16 -0
  14. package/dist/benchmarks/agentbench.js +10 -0
  15. package/dist/benchmarks/agentbench.js.map +1 -0
  16. package/dist/benchmarks/appworld.d.ts +37 -0
  17. package/dist/benchmarks/appworld.js +14 -0
  18. package/dist/benchmarks/appworld.js.map +1 -0
  19. package/dist/benchmarks/bfcl.d.ts +18 -0
  20. package/dist/benchmarks/bfcl.js +10 -0
  21. package/dist/benchmarks/bfcl.js.map +1 -0
  22. package/dist/benchmarks/cad-design.d.ts +45 -0
  23. package/dist/benchmarks/cad-design.js +7 -0
  24. package/dist/benchmarks/cad-design.js.map +1 -0
  25. package/dist/benchmarks/cadbench.d.ts +19 -0
  26. package/dist/benchmarks/cadbench.js +8 -0
  27. package/dist/benchmarks/cadbench.js.map +1 -0
  28. package/dist/benchmarks/cadgenbench.d.ts +22 -0
  29. package/dist/benchmarks/cadgenbench.js +8 -0
  30. package/dist/benchmarks/cadgenbench.js.map +1 -0
  31. package/dist/benchmarks/commit0.d.ts +31 -0
  32. package/dist/benchmarks/commit0.js +10 -0
  33. package/dist/benchmarks/commit0.js.map +1 -0
  34. package/dist/benchmarks/crag.d.ts +14 -0
  35. package/dist/benchmarks/crag.js +9 -0
  36. package/dist/benchmarks/crag.js.map +1 -0
  37. package/dist/benchmarks/dabstep.d.ts +18 -0
  38. package/dist/benchmarks/dabstep.js +10 -0
  39. package/dist/benchmarks/dabstep.js.map +1 -0
  40. package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
  41. package/dist/benchmarks/enterpriseops-gym.js +10 -0
  42. package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
  43. package/dist/benchmarks/finresearchbench.d.ts +15 -0
  44. package/dist/benchmarks/finresearchbench.js +8 -0
  45. package/dist/benchmarks/finresearchbench.js.map +1 -0
  46. package/dist/benchmarks/finsearchcomp.d.ts +49 -0
  47. package/dist/benchmarks/finsearchcomp.js +7 -0
  48. package/dist/benchmarks/finsearchcomp.js.map +1 -0
  49. package/dist/benchmarks/frames.d.ts +59 -0
  50. package/dist/benchmarks/frames.js +13 -0
  51. package/dist/benchmarks/frames.js.map +1 -0
  52. package/dist/benchmarks/hotpotqa.d.ts +48 -0
  53. package/dist/benchmarks/hotpotqa.js +15 -0
  54. package/dist/benchmarks/hotpotqa.js.map +1 -0
  55. package/dist/benchmarks/humaneval.d.ts +53 -0
  56. package/dist/benchmarks/humaneval.js +15 -0
  57. package/dist/benchmarks/humaneval.js.map +1 -0
  58. package/dist/benchmarks/mind2web.d.ts +41 -0
  59. package/dist/benchmarks/mind2web.js +9 -0
  60. package/dist/benchmarks/mind2web.js.map +1 -0
  61. package/dist/benchmarks/nomiracl.d.ts +15 -0
  62. package/dist/benchmarks/nomiracl.js +9 -0
  63. package/dist/benchmarks/nomiracl.js.map +1 -0
  64. package/dist/benchmarks/open-rag-bench.d.ts +14 -0
  65. package/dist/benchmarks/open-rag-bench.js +9 -0
  66. package/dist/benchmarks/open-rag-bench.js.map +1 -0
  67. package/dist/benchmarks/programbench.d.ts +38 -0
  68. package/dist/benchmarks/programbench.js +10 -0
  69. package/dist/benchmarks/programbench.js.map +1 -0
  70. package/dist/benchmarks/rag-shared.d.ts +42 -0
  71. package/dist/benchmarks/rag-shared.js +39 -0
  72. package/dist/benchmarks/rag-shared.js.map +1 -0
  73. package/dist/benchmarks/ragbench.d.ts +16 -0
  74. package/dist/benchmarks/ragbench.js +9 -0
  75. package/dist/benchmarks/ragbench.js.map +1 -0
  76. package/dist/benchmarks/simpleqa.d.ts +64 -0
  77. package/dist/benchmarks/simpleqa.js +11 -0
  78. package/dist/benchmarks/simpleqa.js.map +1 -0
  79. package/dist/benchmarks/swe-bench.d.ts +56 -0
  80. package/dist/benchmarks/swe-bench.js +14 -0
  81. package/dist/benchmarks/swe-bench.js.map +1 -0
  82. package/dist/benchmarks/t2-ragbench.d.ts +14 -0
  83. package/dist/benchmarks/t2-ragbench.js +9 -0
  84. package/dist/benchmarks/t2-ragbench.js.map +1 -0
  85. package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
  86. package/dist/benchmarks/tau-bench-shared.js +10 -0
  87. package/dist/benchmarks/tau-bench-shared.js.map +1 -0
  88. package/dist/benchmarks/tau2-bench.d.ts +7 -0
  89. package/dist/benchmarks/tau2-bench.js +11 -0
  90. package/dist/benchmarks/tau2-bench.js.map +1 -0
  91. package/dist/benchmarks/tau3-banking.d.ts +15 -0
  92. package/dist/benchmarks/tau3-banking.js +9 -0
  93. package/dist/benchmarks/tau3-banking.js.map +1 -0
  94. package/dist/benchmarks/terminal-bench.d.ts +24 -0
  95. package/dist/benchmarks/terminal-bench.js +8 -0
  96. package/dist/benchmarks/terminal-bench.js.map +1 -0
  97. package/dist/benchmarks/toollm.d.ts +16 -0
  98. package/dist/benchmarks/toollm.js +10 -0
  99. package/dist/benchmarks/toollm.js.map +1 -0
  100. package/dist/benchmarks/trata-hedge.d.ts +32 -0
  101. package/dist/benchmarks/trata-hedge.js +7 -0
  102. package/dist/benchmarks/trata-hedge.js.map +1 -0
  103. package/dist/benchmarks/types.d.ts +107 -0
  104. package/dist/benchmarks/types.js +1 -0
  105. package/dist/benchmarks/types.js.map +1 -0
  106. package/dist/benchmarks/webarena-verified.d.ts +16 -0
  107. package/dist/benchmarks/webarena-verified.js +10 -0
  108. package/dist/benchmarks/webarena-verified.js.map +1 -0
  109. package/dist/chunk-2PVVP7GN.js +197 -0
  110. package/dist/chunk-2PVVP7GN.js.map +1 -0
  111. package/dist/chunk-2XU6OGEN.js +170 -0
  112. package/dist/chunk-2XU6OGEN.js.map +1 -0
  113. package/dist/chunk-53UPUNBZ.js +325 -0
  114. package/dist/chunk-53UPUNBZ.js.map +1 -0
  115. package/dist/chunk-5SBJCB6W.js +144 -0
  116. package/dist/chunk-5SBJCB6W.js.map +1 -0
  117. package/dist/chunk-7WSD27QQ.js +118 -0
  118. package/dist/chunk-7WSD27QQ.js.map +1 -0
  119. package/dist/chunk-C7T7WEK2.js +103 -0
  120. package/dist/chunk-C7T7WEK2.js.map +1 -0
  121. package/dist/chunk-CKUVRZ2T.js +251 -0
  122. package/dist/chunk-CKUVRZ2T.js.map +1 -0
  123. package/dist/chunk-HBSWHQNJ.js +30 -0
  124. package/dist/chunk-HBSWHQNJ.js.map +1 -0
  125. package/dist/chunk-HHXFIHXC.js +116 -0
  126. package/dist/chunk-HHXFIHXC.js.map +1 -0
  127. package/dist/chunk-IFAV6KEM.js +276 -0
  128. package/dist/chunk-IFAV6KEM.js.map +1 -0
  129. package/dist/chunk-INNOYXCP.js +387 -0
  130. package/dist/chunk-INNOYXCP.js.map +1 -0
  131. package/dist/chunk-J3KDJNX2.js +182 -0
  132. package/dist/chunk-J3KDJNX2.js.map +1 -0
  133. package/dist/chunk-JRWWGMK7.js +148 -0
  134. package/dist/chunk-JRWWGMK7.js.map +1 -0
  135. package/dist/chunk-JTHWEDEW.js +32 -0
  136. package/dist/chunk-JTHWEDEW.js.map +1 -0
  137. package/dist/chunk-KDIKRJGB.js +120 -0
  138. package/dist/chunk-KDIKRJGB.js.map +1 -0
  139. package/dist/chunk-LRRD7NAG.js +301 -0
  140. package/dist/chunk-LRRD7NAG.js.map +1 -0
  141. package/dist/chunk-ODT47UAY.js +221 -0
  142. package/dist/chunk-ODT47UAY.js.map +1 -0
  143. package/dist/chunk-PA2ZKHJC.js +230 -0
  144. package/dist/chunk-PA2ZKHJC.js.map +1 -0
  145. package/dist/chunk-PPYSEKFM.js +182 -0
  146. package/dist/chunk-PPYSEKFM.js.map +1 -0
  147. package/dist/chunk-PUIRNYI7.js +189 -0
  148. package/dist/chunk-PUIRNYI7.js.map +1 -0
  149. package/dist/chunk-R36V2VP7.js +169 -0
  150. package/dist/chunk-R36V2VP7.js.map +1 -0
  151. package/dist/chunk-R67DFVLO.js +142 -0
  152. package/dist/chunk-R67DFVLO.js.map +1 -0
  153. package/dist/chunk-SEVJPLZC.js +260 -0
  154. package/dist/chunk-SEVJPLZC.js.map +1 -0
  155. package/dist/chunk-SYDW647C.js +318 -0
  156. package/dist/chunk-SYDW647C.js.map +1 -0
  157. package/dist/chunk-TBKU5XQI.js +228 -0
  158. package/dist/chunk-TBKU5XQI.js.map +1 -0
  159. package/dist/chunk-TSWPNOYM.js +147 -0
  160. package/dist/chunk-TSWPNOYM.js.map +1 -0
  161. package/dist/chunk-UAIOHCUK.js +27 -0
  162. package/dist/chunk-UAIOHCUK.js.map +1 -0
  163. package/dist/chunk-UPAMRDX4.js +233 -0
  164. package/dist/chunk-UPAMRDX4.js.map +1 -0
  165. package/dist/chunk-VQRS7VUC.js +342 -0
  166. package/dist/chunk-VQRS7VUC.js.map +1 -0
  167. package/dist/chunk-X3BTXCJ4.js +262 -0
  168. package/dist/chunk-X3BTXCJ4.js.map +1 -0
  169. package/dist/chunk-X5YKXC6V.js +211 -0
  170. package/dist/chunk-X5YKXC6V.js.map +1 -0
  171. package/dist/chunk-Y6O2OCUO.js +130 -0
  172. package/dist/chunk-Y6O2OCUO.js.map +1 -0
  173. package/dist/chunk-YCGY7UIZ.js +208 -0
  174. package/dist/chunk-YCGY7UIZ.js.map +1 -0
  175. package/dist/chunk-Z7ML6L77.js +162 -0
  176. package/dist/chunk-Z7ML6L77.js.map +1 -0
  177. package/dist/chunk-ZEWMTR5M.js +136 -0
  178. package/dist/chunk-ZEWMTR5M.js.map +1 -0
  179. package/dist/index.d.ts +355 -0
  180. package/dist/index.js +1908 -0
  181. package/dist/index.js.map +1 -0
  182. package/fixtures/aec-bench.json +18 -0
  183. package/fixtures/agentbench-dbbench.json +22 -0
  184. package/fixtures/bfcl.json +45 -0
  185. package/fixtures/commit0.json +72 -0
  186. package/fixtures/crag.json +10 -0
  187. package/fixtures/dabstep.json +22 -0
  188. package/fixtures/enterpriseops-gym.json +103 -0
  189. package/fixtures/finresearchbench.json +21 -0
  190. package/fixtures/finsearchcomp.json +66 -0
  191. package/fixtures/frames.json +26 -0
  192. package/fixtures/hotpotqa.json +182 -0
  193. package/fixtures/nomiracl.json +26 -0
  194. package/fixtures/open-rag-bench.json +16 -0
  195. package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
  196. package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
  197. package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
  198. package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
  199. package/fixtures/pier-agent/no-model-task/task.toml +35 -0
  200. package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
  201. package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
  202. package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
  203. package/fixtures/programbench.json +17 -0
  204. package/fixtures/ragbench.json +21 -0
  205. package/fixtures/simpleqa.json +121 -0
  206. package/fixtures/t2-ragbench.json +13 -0
  207. package/fixtures/tau2-bench.json +16 -0
  208. package/fixtures/tau3-banking.json +16 -0
  209. package/fixtures/toollm.json +28 -0
  210. package/fixtures/webarena-verified.json +20 -0
  211. package/package.json +60 -20
  212. package/pier_agents/__init__.py +18 -0
  213. package/pier_agents/candidate_contract.py +755 -0
  214. package/pier_agents/process_boundary.py +321 -0
  215. package/pier_agents/tangle_candidate.py +907 -0
  216. package/pier_agents/workspace_boundary.py +368 -0
  217. package/scripts/appworld_driver.py +359 -0
  218. package/scripts/cadbench_prepare.py +22 -0
  219. package/scripts/cadgenbench_hard_parts.py +48 -0
  220. package/scripts/clbench_codebase_judge.py +73 -0
  221. package/scripts/commit0_judge.py +170 -0
  222. package/scripts/dabstep_judge.py +42 -0
  223. package/scripts/enterpriseops_gym_judge.py +281 -0
  224. package/scripts/programbench_judge.py +120 -0
  225. package/scripts/render-gate-chart.mjs +176 -0
  226. package/scripts/run-package-tests.mjs +56 -0
  227. package/scripts/terminate-pier-trial.mts +66 -0
  228. package/scripts/trata-hedge/README.md +56 -0
  229. package/scripts/trata-hedge/run.sh +60 -0
  230. package/scripts/trata-hedge/solve.py +83 -0
  231. package/scripts/verify-packed-consumer.mjs +235 -0
  232. package/scripts/verify-pier-agent.mts +715 -0
  233. package/scripts/verify-pier-pair.mts +74 -0
  234. package/scripts/verify-pier-recovery.mts +139 -0
  235. package/src/adapters.ts +26 -0
  236. package/src/benchmarks/_harness.test.mts +178 -0
  237. package/src/benchmarks/_harness.ts +239 -16
  238. package/src/benchmarks/agentbench.ts +163 -0
  239. package/src/benchmarks/appworld.test.mts +15 -9
  240. package/src/benchmarks/bfcl.ts +346 -0
  241. package/src/benchmarks/crag.ts +137 -0
  242. package/src/benchmarks/dabstep.test.mts +70 -0
  243. package/src/benchmarks/dabstep.ts +212 -0
  244. package/src/benchmarks/external-adapters.test.mts +150 -0
  245. package/src/benchmarks/finresearchbench.ts +269 -0
  246. package/src/benchmarks/humaneval.ts +20 -8
  247. package/src/benchmarks/nomiracl.ts +180 -0
  248. package/src/benchmarks/open-rag-bench.ts +153 -0
  249. package/src/benchmarks/rag-benchmarks.test.mts +138 -0
  250. package/src/benchmarks/rag-shared.ts +327 -0
  251. package/src/benchmarks/ragbench.ts +171 -0
  252. package/src/benchmarks/swe-bench.test.mts +61 -0
  253. package/src/benchmarks/swe-bench.ts +201 -19
  254. package/src/benchmarks/t2-ragbench.ts +166 -0
  255. package/src/benchmarks/tau-bench-shared.ts +214 -0
  256. package/src/benchmarks/tau2-bench.ts +30 -0
  257. package/src/benchmarks/tau3-banking.ts +29 -0
  258. package/src/benchmarks/terminal-bench.test.mts +33 -0
  259. package/src/benchmarks/terminal-bench.ts +23 -8
  260. package/src/benchmarks/toollm.ts +254 -0
  261. package/src/benchmarks/types.ts +42 -0
  262. package/src/benchmarks/webarena-verified.ts +200 -0
  263. package/src/commit0-prereqs.sh +0 -0
  264. package/src/coordination-mcp-container-reach.mts +181 -0
  265. package/src/decoder-live.mts +1 -1
  266. package/src/examples/README.md +103 -39
  267. package/src/examples/benchmark-matrix.mts +101 -0
  268. package/src/examples/lean-proof-gate.README.md +77 -0
  269. package/src/examples/lean-proof-gate.mts +162 -0
  270. package/src/examples/lean-verify.ts +95 -0
  271. package/src/examples/lean.Dockerfile +12 -0
  272. package/src/examples/math-demo.mts +9 -7
  273. package/src/examples/strategy-demo.mts +10 -12
  274. package/src/gate.ts +3 -2
  275. package/src/hev-eval.mts +69 -0
  276. package/src/hev-improve.mts +169 -0
  277. package/src/hev-structural.mts +688 -0
  278. package/src/index.ts +73 -0
  279. package/src/mbpp-structural.mts +662 -0
  280. package/src/pier-agent.test-fixtures.mts +19 -0
  281. package/src/pier-agent.test.mts +363 -0
  282. package/src/pier-agent.ts +657 -0
  283. package/src/pier-result-grader.mjs +30 -0
  284. package/src/pier-result-grader.test.mts +62 -0
  285. package/src/pier-result-grader.ts +108 -0
  286. package/src/pier-task-outcome.test.mts +117 -0
  287. package/src/pier-task-outcome.ts +240 -0
  288. package/src/pier-trial-controller.test.mts +412 -0
  289. package/src/pier-trial-controller.ts +858 -0
  290. package/src/pier-trial-supervisor.mjs +352 -0
  291. package/src/resolve-client.ts +25 -2
  292. package/src/run-benchmarks-cli.mts +72 -0
  293. package/src/run-benchmarks-report.ts +66 -0
  294. package/src/run-benchmarks.test.mts +231 -0
  295. package/src/run-benchmarks.ts +589 -0
  296. package/src/smoke-structural-rollout.mts +393 -0
  297. package/src/swe-bench-env.test.ts +207 -0
  298. package/src/swe-bench-env.ts +554 -0
  299. package/src/swe-jail.ts +293 -0
  300. package/src/swe-self-improve.mts +84 -0
  301. package/src/swe-structural-judge-policy.test.ts +117 -0
  302. package/src/swe-structural-judge-policy.ts +133 -0
  303. package/src/swe-structural-policy.test.ts +124 -0
  304. package/src/swe-structural-policy.ts +132 -0
  305. package/src/swe-structural-provenance.test.ts +93 -0
  306. package/src/swe-structural-provenance.ts +138 -0
  307. package/src/swe-structural.mts +1260 -0
  308. package/src/swe-temp.ts +14 -0
  309. package/src/tb-container-executor.mts +234 -0
  310. package/src/tb-container-executor.test.mts +99 -0
  311. package/src/tb-supervisor-sidecar.mts +222 -0
  312. package/src/trata-gepa.mts +1 -1
  313. package/steerers/eops-itsm-population.json +1 -0
  314. package/tb_agents/opencode_refine_agent.py +117 -0
  315. package/tb_agents/opencode_router_agent.py +406 -0
  316. package/tb_agents/opencode_supervisor_agent.py +239 -0
  317. package/tb_agents/script_agent.py +66 -0
@@ -0,0 +1,14 @@
1
+ import { tmpdir } from 'node:os'
2
+ import { isAbsolute } from 'node:path'
3
+
4
+ /** Docker bind mounts and mkdtemp parents must never inherit a relative operating-system temp root. */
5
+ export function absoluteSweTempDir(): string {
6
+ const dir = tmpdir()
7
+ if (!isAbsolute(dir)) {
8
+ throw new Error(
9
+ `SWE temporary directory must be absolute, got "${dir}"; ` +
10
+ 'TEMP configures the operating-system temp root, not model temperature. Use TEMPERATURE instead.',
11
+ )
12
+ }
13
+ return dir
14
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Executor for workers that must mutate the same Terminal-Bench container the grader inspects.
3
+ *
4
+ * The container lifecycle stays owned by Terminal-Bench. This executor only runs a command in the
5
+ * provided container id and reports the captured process artifact back through the runtime.
6
+ */
7
+ import { spawn } from 'node:child_process'
8
+ import type {
9
+ Agent,
10
+ AgentProfile,
11
+ AgentSpec,
12
+ Executor,
13
+ ExecutorContext,
14
+ ExecutorFactory,
15
+ ExecutorResult,
16
+ MakeWorkerAgent,
17
+ Runtime,
18
+ Spend,
19
+ } from '../../src/runtime/index'
20
+
21
+ export interface TbExecOutput {
22
+ /** Primary artifact consumed by drivers that read `{ content }`. */
23
+ readonly content: string
24
+ readonly stdout: string
25
+ readonly stderr: string
26
+ readonly exitCode: number | null
27
+ readonly containerId: string
28
+ readonly command: string
29
+ }
30
+
31
+ export type ParseUsage = (io: {
32
+ stdout: string
33
+ stderr: string
34
+ exitCode: number | null
35
+ }) => { input: number; output: number; usd?: number } | undefined
36
+
37
+ export interface TbContainerConfig {
38
+ readonly containerId?: string
39
+ readonly containerEnvVar?: string
40
+ readonly workdir?: string
41
+ readonly shell?: string
42
+ readonly env?: Readonly<Record<string, string>>
43
+ readonly wrapCommand?: (task: unknown) => string
44
+ readonly parseUsage?: ParseUsage
45
+ readonly budgetExempt?: boolean
46
+ readonly dockerBin?: string
47
+ readonly failOnNonZeroExit?: boolean
48
+ readonly runtime?: Runtime
49
+ }
50
+
51
+ const DEFAULT_ENV_VAR = 'TB_TARGET_CONTAINER'
52
+
53
+ function resolveContainerId(config: TbContainerConfig): string {
54
+ const envVar = config.containerEnvVar ?? DEFAULT_ENV_VAR
55
+ const id = config.containerId ?? process.env[envVar]
56
+ if (!id || id.trim().length === 0) {
57
+ throw new Error(
58
+ `tbContainerExecutor: no target container - set config.containerId or the ${envVar} env var ` +
59
+ `to the id of the terminal-bench container the verifier grades.`,
60
+ )
61
+ }
62
+ return id.trim()
63
+ }
64
+
65
+ function taskToCommand(task: unknown): string {
66
+ if (typeof task === 'string') return task
67
+ if (task && typeof task === 'object') {
68
+ const obj = task as Record<string, unknown>
69
+ for (const k of ['command', 'prompt', 'content', 'task', 'message']) {
70
+ if (typeof obj[k] === 'string') return obj[k] as string
71
+ }
72
+ }
73
+ return JSON.stringify(task)
74
+ }
75
+
76
+ /**
77
+ * Compose the `docker exec` argv that runs `command` inside `containerId` via `<shell> -c`.
78
+ * Exported so tests can verify the container path without a live Docker daemon.
79
+ */
80
+ export function buildTbDockerExecArgs(
81
+ containerId: string,
82
+ command: string,
83
+ opts: { shell?: string; workdir?: string; env?: Readonly<Record<string, string>> } = {},
84
+ ): string[] {
85
+ const out: string[] = ['exec', '-i']
86
+ if (opts.workdir) out.push('--workdir', opts.workdir)
87
+ if (opts.env) {
88
+ for (const [k, v] of Object.entries(opts.env)) {
89
+ if (typeof v !== 'string' || v.length === 0) continue
90
+ out.push('-e', `${k}=${v}`)
91
+ }
92
+ }
93
+ out.push(containerId, opts.shell ?? '/bin/sh', '-c', command)
94
+ return out
95
+ }
96
+
97
+ function contentRef(prefix: string, value: unknown): string {
98
+ let str: string
99
+ try {
100
+ str = JSON.stringify(value) ?? String(value)
101
+ } catch {
102
+ str = String(value)
103
+ }
104
+ let h = 0x811c9dc5
105
+ for (let i = 0; i < str.length; i += 1) {
106
+ h ^= str.charCodeAt(i)
107
+ h = Math.imul(h, 0x01000193)
108
+ }
109
+ return `${prefix}:${(h >>> 0).toString(16).padStart(8, '0')}`
110
+ }
111
+
112
+ export function createTbContainerExecutor(
113
+ config: TbContainerConfig = {},
114
+ ): ExecutorFactory<TbExecOutput> {
115
+ return (_spec: AgentSpec, ctx: ExecutorContext): Executor<TbExecOutput> => {
116
+ const containerId = resolveContainerId(config)
117
+ const dockerBin = config.dockerBin ?? 'docker'
118
+ const metered = config.parseUsage !== undefined
119
+ const budgetExempt = config.budgetExempt ?? !metered
120
+ const runtime: Runtime = config.runtime ?? 'tb-container'
121
+
122
+ const controller = new AbortController()
123
+ const abortIfSignalled = () => {
124
+ if (ctx.signal.aborted) controller.abort()
125
+ }
126
+ abortIfSignalled()
127
+ if (!ctx.signal.aborted) ctx.signal.addEventListener('abort', abortIfSignalled, { once: true })
128
+
129
+ let proc: ReturnType<typeof spawn> | undefined
130
+ let artifact: ExecutorResult<TbExecOutput> | undefined
131
+
132
+ return {
133
+ runtime,
134
+ budgetExempt,
135
+ execute(task, signal): Promise<ExecutorResult<TbExecOutput>> {
136
+ const command = (config.wrapCommand ?? taskToCommand)(task)
137
+ const args = buildTbDockerExecArgs(containerId, command, {
138
+ ...(config.shell ? { shell: config.shell } : {}),
139
+ ...(config.workdir ? { workdir: config.workdir } : {}),
140
+ ...(config.env ? { env: config.env } : {}),
141
+ })
142
+ const started = Date.now()
143
+
144
+ return new Promise<ExecutorResult<TbExecOutput>>((resolve, reject) => {
145
+ const child = spawn(dockerBin, args, { stdio: ['ignore', 'pipe', 'pipe'] })
146
+ proc = child
147
+
148
+ const kill = () => {
149
+ if (child.exitCode === null && !child.killed) child.kill('SIGKILL')
150
+ }
151
+ if (signal.aborted || controller.signal.aborted) kill()
152
+ else {
153
+ signal.addEventListener('abort', kill, { once: true })
154
+ controller.signal.addEventListener('abort', kill, { once: true })
155
+ }
156
+
157
+ const outChunks: string[] = []
158
+ const errChunks: string[] = []
159
+ child.stdout?.on('data', (d: Buffer) => outChunks.push(d.toString('utf8')))
160
+ child.stderr?.on('data', (d: Buffer) => errChunks.push(d.toString('utf8')))
161
+
162
+ child.once('error', (err) => {
163
+ signal.removeEventListener('abort', kill)
164
+ controller.signal.removeEventListener('abort', kill)
165
+ reject(
166
+ new Error(
167
+ `tbContainerExecutor: failed to spawn '${dockerBin} exec' on ${containerId}: ${err.message}`,
168
+ { cause: err },
169
+ ),
170
+ )
171
+ })
172
+
173
+ child.once('close', (code) => {
174
+ signal.removeEventListener('abort', kill)
175
+ controller.signal.removeEventListener('abort', kill)
176
+ const stdout = outChunks.join('')
177
+ const stderr = errChunks.join('')
178
+ const exitCode = code
179
+
180
+ if (config.failOnNonZeroExit && exitCode !== 0) {
181
+ reject(
182
+ new Error(
183
+ `tbContainerExecutor: command exited ${exitCode} in ${containerId}: ${stderr.slice(0, 200)}`,
184
+ ),
185
+ )
186
+ return
187
+ }
188
+
189
+ const out: TbExecOutput = { content: stdout, stdout, stderr, exitCode, containerId, command }
190
+ const usage = config.parseUsage?.({ stdout, stderr, exitCode })
191
+ const spent: Spend = metered
192
+ ? {
193
+ iterations: 1,
194
+ tokens: { input: usage?.input ?? 0, output: usage?.output ?? 0 },
195
+ usd: usage?.usd ?? 0,
196
+ ms: Date.now() - started,
197
+ }
198
+ : // Budget-exempt commands report no conserved token, dollar, or iteration spend.
199
+ { iterations: 0, tokens: { input: 0, output: 0 }, usd: 0, ms: Date.now() - started }
200
+
201
+ artifact = { outRef: contentRef(String(runtime), out), out, spent }
202
+ resolve(artifact)
203
+ })
204
+ })
205
+ },
206
+ teardown(_grace): Promise<{ destroyed: boolean }> {
207
+ controller.abort()
208
+ if (proc && proc.exitCode === null && !proc.killed) proc.kill('SIGKILL')
209
+ return Promise.resolve({ destroyed: true })
210
+ },
211
+ resultArtifact() {
212
+ if (!artifact) {
213
+ throw new Error('tbContainerExecutor: resultArtifact() read before execute() settled')
214
+ }
215
+ return artifact
216
+ },
217
+ }
218
+ }
219
+ }
220
+
221
+ export function makeTbContainerWorkerAgent(config: TbContainerConfig = {}): MakeWorkerAgent {
222
+ const factory = createTbContainerExecutor(config)
223
+ return (rawProfile) => {
224
+ const p = (rawProfile ?? {}) as { name?: unknown }
225
+ const name = typeof p.name === 'string' && p.name.length > 0 ? p.name : 'tb-worker'
226
+ const spec: AgentSpec = { profile: rawProfile as AgentProfile, harness: null }
227
+ const ctx: ExecutorContext = { signal: new AbortController().signal, seams: {} }
228
+ const executor = factory(spec, ctx)
229
+ return { name, act: async () => '', executorSpec: { ...spec, executor } } as Agent<
230
+ unknown,
231
+ unknown
232
+ > & { executorSpec: AgentSpec }
233
+ }
234
+ }
@@ -0,0 +1,99 @@
1
+ import assert from 'node:assert/strict'
2
+ import { chmod, mkdtemp, writeFile } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import path from 'node:path'
5
+ import type { AgentSpec, ExecutorContext } from '@tangle-network/agent-runtime/loops'
6
+ import { buildTbDockerExecArgs, createTbContainerExecutor } from './tb-container-executor.mts'
7
+
8
+ const spec: AgentSpec = { profile: { name: 'tb-test-worker' }, harness: null }
9
+
10
+ function context(): ExecutorContext {
11
+ return { signal: new AbortController().signal, seams: {} }
12
+ }
13
+
14
+ async function executable(name: string, body: string): Promise<string> {
15
+ const dir = await mkdtemp(path.join(tmpdir(), 'tb-container-executor-'))
16
+ const file = path.join(dir, name)
17
+ await writeFile(file, body)
18
+ await chmod(file, 0o755)
19
+ return file
20
+ }
21
+
22
+ async function main(): Promise<void> {
23
+ assert.deepEqual(
24
+ buildTbDockerExecArgs('cid', 'echo ok', {
25
+ workdir: '/app',
26
+ env: { A: 'B', EMPTY: '' },
27
+ shell: '/bin/bash',
28
+ }),
29
+ ['exec', '-i', '--workdir', '/app', '-e', 'A=B', 'cid', '/bin/bash', '-c', 'echo ok'],
30
+ 'docker exec argv is deterministic and skips empty env values',
31
+ )
32
+
33
+ assert.throws(
34
+ () => createTbContainerExecutor({ containerEnvVar: 'TB_CONTAINER_EXECUTOR_TEST_MISSING' })(spec, context()),
35
+ /no target container/,
36
+ 'missing target container fails before spawning',
37
+ )
38
+
39
+ const fakeDocker = await executable(
40
+ 'fake-docker',
41
+ `#!/bin/sh
42
+ printf 'argv:%s\\n' "$*"
43
+ `,
44
+ )
45
+
46
+ const metered = createTbContainerExecutor({
47
+ containerId: 'cid',
48
+ dockerBin: fakeDocker,
49
+ workdir: '/work',
50
+ env: { OPENAI_BASE_URL: 'http://router.test' },
51
+ parseUsage: () => ({ input: 7, output: 11, usd: 0.004 }),
52
+ })(spec, context())
53
+
54
+ assert.equal(metered.budgetExempt, false, 'usage parser makes the executor metered')
55
+ const meteredResult = await metered.execute({ command: 'echo hello' }, new AbortController().signal)
56
+ assert.equal(meteredResult.out.containerId, 'cid')
57
+ assert.equal(meteredResult.out.command, 'echo hello')
58
+ assert.match(
59
+ meteredResult.out.stdout,
60
+ /argv:exec -i --workdir \/work -e OPENAI_BASE_URL=http:\/\/router\.test cid \/bin\/sh -c echo hello/,
61
+ )
62
+ assert.deepEqual(meteredResult.spent.tokens, { input: 7, output: 11 })
63
+ assert.equal(meteredResult.spent.usd, 0.004)
64
+ assert.equal(metered.resultArtifact().out, meteredResult.out)
65
+
66
+ const free = createTbContainerExecutor({ containerId: 'cid', dockerBin: fakeDocker })(spec, context())
67
+ assert.equal(free.budgetExempt, true, 'unmetered shell commands are explicit budget-exempt work')
68
+ const freeResult = await free.execute('printf ok', new AbortController().signal)
69
+ assert.equal(freeResult.spent.iterations, 0)
70
+ assert.deepEqual(freeResult.spent.tokens, { input: 0, output: 0 })
71
+ assert.equal(freeResult.spent.usd, 0)
72
+
73
+ const failingDocker = await executable(
74
+ 'failing-docker',
75
+ `#!/bin/sh
76
+ echo failed >&2
77
+ exit 7
78
+ `,
79
+ )
80
+ const strict = createTbContainerExecutor({
81
+ containerId: 'cid',
82
+ dockerBin: failingDocker,
83
+ failOnNonZeroExit: true,
84
+ })(spec, context())
85
+ await assert.rejects(
86
+ strict.execute('do work', new AbortController().signal),
87
+ /command exited 7/,
88
+ 'strict mode treats non-zero command exit as infrastructure failure',
89
+ )
90
+
91
+ const lenient = createTbContainerExecutor({ containerId: 'cid', dockerBin: failingDocker })(spec, context())
92
+ const lenientResult = await lenient.execute('do work', new AbortController().signal)
93
+ assert.equal(lenientResult.out.exitCode, 7, 'default mode returns non-zero exits as task artifacts')
94
+ assert.match(lenientResult.out.stderr, /failed/)
95
+
96
+ console.log('tb-container-executor.test: OK')
97
+ }
98
+
99
+ void main()
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Host process that lets an in-container Terminal-Bench agent delegate work to child agents.
3
+ *
4
+ * It exposes the coordination MCP on a container-reachable host address, then runs every spawned
5
+ * worker in the same Docker container the benchmark grader inspects.
6
+ */
7
+
8
+ import { appendFileSync, writeFileSync } from 'node:fs'
9
+ import {
10
+ type Agent,
11
+ createExecutorRegistry,
12
+ createSupervisor,
13
+ InMemoryResultBlobStore,
14
+ InMemorySpawnJournal,
15
+ type Scope,
16
+ } from '../../src/runtime/index'
17
+ import { serveCoordinationMcp } from '../../src/runtime/supervise/coordination-mcp'
18
+ import { makeTbContainerWorkerAgent, type ParseUsage } from './tb-container-executor.mts'
19
+
20
+ const CONTAINER_ID = (process.env.TB_TARGET_CONTAINER ?? '').trim()
21
+ const ROUTER_KEY = process.env.OPENAI_API_KEY ?? ''
22
+ const ROUTER_BASE = process.env.OPENAI_BASE_URL ?? 'https://router.tangle.tools/v1'
23
+ const WORKER_MODEL = process.env.WORKER_MODEL ?? 'zai-coding-plan/glm-5.2'
24
+ const DOCKER_BRIDGE_GATEWAY = process.env.DOCKER_BRIDGE_GATEWAY ?? '172.17.0.1'
25
+ const PORT_FILE = process.env.TB_SIDECAR_PORT_FILE ?? '.tb-sidecar-port'
26
+ const LOG_FILE = process.env.TB_SIDECAR_LOG ?? '.tb-sidecar-events.jsonl'
27
+ const WORKER_WORKDIR = process.env.TB_WORKER_WORKDIR ?? '/app'
28
+ const WORKER_CONFIG_PATH = '/installed-agent/opencode-worker.json'
29
+
30
+ if (!CONTAINER_ID) {
31
+ console.error('[sidecar] FATAL: TB_TARGET_CONTAINER not set')
32
+ process.exit(2)
33
+ }
34
+
35
+ function logEvent(kind: string, payload: unknown): void {
36
+ const line = JSON.stringify({ ts: new Date().toISOString(), kind, payload }) + '\n'
37
+ try {
38
+ appendFileSync(LOG_FILE, line)
39
+ } catch {
40
+ /* logging is never fatal */
41
+ }
42
+ console.error(`[sidecar] ${kind} ${JSON.stringify(payload).slice(0, 300)}`)
43
+ }
44
+
45
+ function workerWrapCommand(task: unknown): string {
46
+ const instr =
47
+ typeof task === 'string'
48
+ ? task
49
+ : task && typeof task === 'object'
50
+ ? ((): string => {
51
+ const o = task as Record<string, unknown>
52
+ for (const k of ['task', 'prompt', 'content', 'command', 'message', 'instruction']) {
53
+ if (typeof o[k] === 'string') return o[k] as string
54
+ }
55
+ return JSON.stringify(task)
56
+ })()
57
+ : String(task)
58
+ const q = `'${instr.replace(/'/g, `'\\''`)}'`
59
+ return [
60
+ 'export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"',
61
+ '{ [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; } 2>/dev/null || true',
62
+ '{ [ -s /root/.nvm/nvm.sh ] && . /root/.nvm/nvm.sh; } 2>/dev/null || true',
63
+ `export OPENCODE_CONFIG=${WORKER_CONFIG_PATH}`,
64
+ `cd ${WORKER_WORKDIR} 2>/dev/null || true`,
65
+ `opencode --model ${WORKER_MODEL} --format json run ${q}`,
66
+ ].join('; ')
67
+ }
68
+
69
+ const parseWorkerUsage: ParseUsage = ({ stdout }) => {
70
+ let input = 0
71
+ let output = 0
72
+ let saw = false
73
+ for (const raw of stdout.split('\n')) {
74
+ const line = raw.trim()
75
+ if (!line || !(line.startsWith('{') || line.startsWith('['))) continue
76
+ let ev: unknown
77
+ try {
78
+ ev = JSON.parse(line)
79
+ } catch {
80
+ continue
81
+ }
82
+ const events = Array.isArray(ev) ? ev : [ev]
83
+ for (const e of events) {
84
+ if (!e || typeof e !== 'object') continue
85
+ const dicts: Array<Record<string, unknown>> = [e as Record<string, unknown>]
86
+ for (const k of ['usage', 'tokens', 'part', 'info', 'message']) {
87
+ const sub = (e as Record<string, unknown>)[k]
88
+ if (sub && typeof sub === 'object') {
89
+ dicts.push(sub as Record<string, unknown>)
90
+ for (const k2 of ['usage', 'tokens']) {
91
+ const sub2 = (sub as Record<string, unknown>)[k2]
92
+ if (sub2 && typeof sub2 === 'object') dicts.push(sub2 as Record<string, unknown>)
93
+ }
94
+ }
95
+ }
96
+ for (const d of dicts) {
97
+ const i = (d.input_tokens ?? d.prompt_tokens ?? d.input) as unknown
98
+ const o = (d.output_tokens ?? d.completion_tokens ?? d.output) as unknown
99
+ if (typeof i === 'number' || typeof o === 'number') {
100
+ input += typeof i === 'number' ? i : 0
101
+ output += typeof o === 'number' ? o : 0
102
+ saw = true
103
+ break
104
+ }
105
+ }
106
+ }
107
+ }
108
+ return saw ? { input, output } : undefined
109
+ }
110
+
111
+ async function main(): Promise<void> {
112
+ writeFileSync(LOG_FILE, '')
113
+ const blobs = new InMemoryResultBlobStore()
114
+
115
+ const makeWorkerAgent = makeTbContainerWorkerAgent({
116
+ containerId: CONTAINER_ID,
117
+ workdir: WORKER_WORKDIR,
118
+ wrapCommand: workerWrapCommand,
119
+ parseUsage: parseWorkerUsage,
120
+ env: { OPENAI_API_KEY: ROUTER_KEY, OPENAI_BASE_URL: ROUTER_BASE, HOME: '/root' },
121
+ runtime: 'tb-container',
122
+ })
123
+
124
+ const root: Agent<unknown, unknown> = {
125
+ name: 'tb-supervisor-sidecar',
126
+ async act(_t, scope: Scope<unknown>) {
127
+ const mcp = await serveCoordinationMcp({
128
+ scope,
129
+ blobs,
130
+ makeWorkerAgent,
131
+ perWorker: { maxIterations: 40, maxTokens: 200_000 },
132
+ host: '0.0.0.0',
133
+ onEvent: (event) => logEvent('bus', event),
134
+ })
135
+ const containerUrl = `http://${DOCKER_BRIDGE_GATEWAY}:${mcp.port}/mcp`
136
+ writeFileSync(PORT_FILE, String(mcp.port))
137
+ logEvent('ready', { port: mcp.port, url: mcp.url, containerUrl, containerId: CONTAINER_ID })
138
+
139
+ await new Promise<void>((resolve) => {
140
+ const stop = (sig: string) => {
141
+ logEvent('stop', { signal: sig })
142
+ resolve()
143
+ }
144
+ process.on('SIGTERM', () => stop('SIGTERM'))
145
+ process.on('SIGINT', () => stop('SIGINT'))
146
+ })
147
+
148
+ const drained = mcp.drainResolved()
149
+ const settled = mcp.settled()
150
+ const history = mcp.history()
151
+ const workerOutputs: Array<Record<string, unknown>> = []
152
+ let workerInput = 0
153
+ let workerOutput = 0
154
+ for (const w of settled) {
155
+ if (!w.outRef) continue
156
+ try {
157
+ const out = (await blobs.get(w.outRef)) as
158
+ | { command?: string; stdout?: string; stderr?: string; exitCode?: number | null; containerId?: string }
159
+ | undefined
160
+ if (out) {
161
+ const usage = out.stdout ? parseWorkerUsage({ stdout: out.stdout, stderr: '', exitCode: null }) : undefined
162
+ if (usage) {
163
+ workerInput += usage.input
164
+ workerOutput += usage.output
165
+ }
166
+ workerOutputs.push({
167
+ id: w.id,
168
+ status: w.status,
169
+ command: out.command,
170
+ exitCode: out.exitCode ?? null,
171
+ containerId: out.containerId,
172
+ stdoutBytes: out.stdout?.length ?? 0,
173
+ stderrTail: (out.stderr ?? '').slice(-500),
174
+ stdoutTail: (out.stdout ?? '').slice(-500),
175
+ usage: usage ?? null,
176
+ })
177
+ }
178
+ } catch {
179
+ /* blob fetch is best-effort */
180
+ }
181
+ }
182
+ logEvent('final', {
183
+ drained,
184
+ settled,
185
+ settledCount: settled.length,
186
+ historyLen: history.length,
187
+ workerTokens: { input: workerInput, output: workerOutput },
188
+ stats: mcp.stats(),
189
+ })
190
+ try {
191
+ writeFileSync(
192
+ `${LOG_FILE}.final.json`,
193
+ JSON.stringify(
194
+ { settled, drained, history, workerOutputs, workerTokens: { input: workerInput, output: workerOutput }, stats: mcp.stats() },
195
+ null,
196
+ 2,
197
+ ),
198
+ )
199
+ } catch {
200
+ /* never fatal */
201
+ }
202
+ await mcp.close()
203
+ return { settledCount: settled.length }
204
+ },
205
+ }
206
+
207
+ await createSupervisor<unknown, unknown>().run(root, 'tb-supervisor', {
208
+ budget: { maxIterations: 500, maxTokens: 2_000_000 },
209
+ runId: 'tb-supervisor-sidecar',
210
+ journal: new InMemorySpawnJournal(),
211
+ blobs,
212
+ executors: createExecutorRegistry(),
213
+ maxDepth: 4,
214
+ now: () => Date.now(),
215
+ })
216
+ process.exit(0)
217
+ }
218
+
219
+ main().catch((e) => {
220
+ console.error('[sidecar] FATAL', e instanceof Error ? (e.stack ?? e.message) : String(e))
221
+ process.exit(1)
222
+ })
@@ -367,7 +367,7 @@ async function main(): Promise<void> {
367
367
  apiKey: routerKey,
368
368
  model: reflectModel,
369
369
  },
370
- driverTarget:
370
+ proposerTarget:
371
371
  'a FINANCIAL ANALYST SYSTEM INSTRUCTION: the directive given to an agent that produces an investment memo from embedded earnings call transcripts, SEC filings, financial statements, and investor presentations. ' +
372
372
  'The memo is scored by a rubric with 4-6 analytical themes, each requiring 2-4 specific analytical moves (quantitative claims, strategic conclusions, peer comparisons, or explicit calculations). ' +
373
373
  'A theme is "hit" only when the agent makes the SPECIFIC move — not just gestures at the theme. ' +
@@ -0,0 +1 @@
1
+ [{"id": "precision-gap-locator", "systemPrompt": "You are a precision-diagnostic steerer for an agent working an IT-service-management task against a LIVE database across sequential shots. The database state PERSISTS between shots. Your job is to emit ONE surgical correction for the next shot.\n\nYou see only the task and the agent's own tool-call trace (the tools it called and the results it observed). You do NOT see the grader, the verifiers, or any expected/ground-truth values. Diagnose only from the task's explicit requirements and the observable record state in the trace. If a value cannot be derived from the task text or an observed record, you do not know it \u2014 do not invent it.\n\nCORE METHOD \u2014 locate the single remaining gap:\n1. Enumerate the discrete state requirements the task asks for, each as a tuple: (record identity, field, required value). Derive required values only from the task text or from values already observed in the trace; never guess.\n2. For each tuple, check the LATEST observed value of that field for that record in the trace. Mark it SATISFIED (latest observed value matches the requirement) or OUTSTANDING (no write observed, or latest observed value differs).\n3. Pick exactly ONE outstanding tuple to fix next \u2014 the most specific, lowest-scope one whose required value you can state concretely. Prefer a record already read this run (its identity is confirmed) over one that must first be located.\n\nANTI-OVER-ACTION (as important as the diagnosis \u2014 the agent's main failure mode is undoing correct work):\n- Every record/field your enumeration marked SATISFIED is FROZEN. Name them and instruct the agent to NOT read-then-rewrite, re-touch, re-open, re-validate, or \"clean up\" any frozen record. Correct records that get re-touched tend to get broken.\n- The next shot must change exactly ONE field on ONE record (the single outstanding tuple), or \u2014 if that field's required value is not yet derivable \u2014 perform exactly ONE READ to obtain it, and nothing else.\n- Forbid any write the task did not explicitly require. No cascading edits, no status sweeps, no defensive re-saves.\n- If your enumeration finds ZERO outstanding tuples (every required tuple is SATISFIED at its latest observed value), the instruction is to STOP and make no further writes \u2014 the artifact is complete; any additional action can only regress it.\n\nOUTPUT CONTRACT:\n- Emit ONE imperative instruction (at most a short ordered list of 1-3 atomic steps for that single fix). No summary, no praise, no restating progress, no explanation of your reasoning.\n- The instruction must name the EXACT target: the record identifier (e.g. ticket/incident/CI sys_id or number), the field name, and the concrete value to set it to. Specificity is the product \u2014 \"set incident INC0012345.priority = 1 (Critical)\" not \"update the incident priority\".\n- End the instruction with the explicit freeze list: \"Do not modify: <records confirmed satisfied>. Make no other writes.\" If nothing is outstanding, the entire instruction is the STOP directive plus the freeze list.", "userTemplate": "TASK (the agent's objective):\n{task}\n\nAGENT TOOL-CALL TRACE (tools called and results observed; this is all you may diagnose from \u2014 no grader, no expected values):\n{trace}\n\nDo the enumeration silently: list every (record, field, required value) the TASK demands, mark each SATISFIED or OUTSTANDING by its LATEST observed value in the TRACE, then emit ONE surgical instruction for the single outstanding tuple \u2014 naming the exact record id, field, and concrete target value \u2014 followed by the explicit freeze list of satisfied records the next shot must not touch. If nothing is outstanding, emit the STOP directive plus the freeze list. Output only the instruction."}, {"id": "checkpoint-restore-and-lock", "systemPrompt": "You are a regression-recovery steerer for an agent working ONE IT-service-management task against a LIVE, PERSISTENT database across sequential shots. The dominant failure here is OVER-WRITING: the agent reaches a correct value for a field, then a LATER shot changes that same field to something worse. So the current (latest) value of a field is NOT trustworthy evidence \u2014 an earlier value may have been the right one. Your unique job is to read the trace as a TIMELINE and recover the best state that already existed.\n\nFIREWALL: you see only the task text and the agent's tool-call trace (calls, arguments, observed results). You NEVER see the grader, verifiers, or expected values. A target value is legitimate only if the task text states it or a record the agent read shows it. Never invent a value.\n\nMETHOD \u2014 per-field timeline reconstruction:\n1. For each (record, field) the task cares about, list its value HISTORY in order: every observed value from reads and the values written by writes, oldest to newest. This exposes oscillation (A -> B -> A), silent overwrites, and writes that moved a field AWAY from what the task asks.\n2. For each (record, field), decide which point in its history best matches the task requirement \u2014 call this its BEST-KNOWN value. The best-known value is frequently NOT the latest one.\n3. Compare BEST-KNOWN to the CURRENT (latest) value:\n - If current already equals best-known and best-known satisfies the task -> this field is DONE and FROZEN.\n - If current differs from a best-known value that DID satisfy the task -> this field was REGRESSED; the fix is to RESTORE it to that earlier value, derived from the trace (you have seen the exact value).\n - If no point in the history ever satisfied the task -> this field is a genuine GAP; the fix is the single write to the task-required value.\n4. Detect active thrash: if the same field was written 3+ times or oscillated, flag it \u2014 the agent is fighting itself and must be told to set it ONCE and stop.\n\nDECISION \u2014 emit exactly one move, in this priority:\n (a) If any field was REGRESSED from a previously-correct value, the move is to RESTORE the highest-priority regressed field to its earlier correct value, then lock.\n (b) Else if a genuine GAP remains, the move is the single write to close it.\n (c) Else (every task field is at a correct value now), the move is STOP: make no further writes; the database is already in its best state and any write risks regressing it.\nWhen a needed value is unobservable, order ONE targeted READ instead of guessing.\n\nANTI-UNDO LAW: never order a write to a field already at a correct value, not even to \"confirm\" \u2014 confirm by READ only. Every write you order must restore or close a specific, evidenced discrepancy. Name the frozen fields so the agent leaves them alone.\n\nOUTPUT CONTRACT: ONE imperative instruction (or up to 3 ordered atomic steps). Name exact record id, field, and the concrete value (a restore value taken verbatim from the trace history, or a task-derived target). End with \"Do not modify: <frozen records/fields>. No other writes.\" No summary, no praise, no reasoning, no trace recap.", "userTemplate": "TASK (the only source of required end-state and its values):\n{task}\n\nAGENT TOOL-CALL TRACE (calls + observed results, oldest first \u2014 read this as a TIMELINE; later values may have overwritten earlier correct ones):\n{trace}\n\nSilently build the per-field value history for every (record, field) the task targets, pick each field's BEST-KNOWN value across its history, and compare to its current value. Then emit ONE instruction in priority order: (a) RESTORE a field that was regressed from a previously-correct value to that earlier value (taken verbatim from the trace); else (b) the single write that closes a never-satisfied GAP; else (c) STOP and make no further writes because every targeted field is already at its best value. If a value is unobservable, order one targeted READ. End with the explicit freeze list of done fields. Output only the instruction."}, {"id": "assumption-falsifier-steerer", "systemPrompt": "You are a SKEPTIC who audits a tool-using IT-service-management (ITSM) agent between attempts. The agent works one task against a LIVE database that PERSISTS across attempts. You read only its tool-call trace and observable state. You do NOT see grading criteria, expected answers, or any verifier \u2014 and you must never ask for them or guess at them. You diagnose strictly from the agent's BEHAVIOR and the tool results it actually saw.\n\nYour job is not to grade the agent and not to praise it. Your job is to find the FALSE BELIEF behind its actions, because in this environment the agent's biggest failure mode is acting confidently on a wrong mental model: a stale read it never refreshed, an entity it resolved to the wrong id (a name/email/number it matched loosely or assumed was unique), or an implicit mapping it invented (assuming a status string, field name, priority code, group, or relationship without ever confirming it exists in this DB).\n\nHow to think (in order):\n1. Reconstruct the agent's IMPLICIT MODEL: what must it believe to be true for its writes to make sense? List the specific assumed facts (this id IS that record; this field accepts this value; this record still looks the way I first read it).\n2. Stress each assumed fact against the trace: Was it ever READ, or just assumed? Was it read BEFORE a mutation that could have changed it (its own or the task's)? Did it resolve an entity by an ambiguous key (name, partial match, \"the first result\") and then never confirm uniqueness? Did it write a value/field/status it never saw the schema or an example of?\n3. Pick the SINGLE assumption whose being-wrong would most explain the gap between what the agent intended and what the DB now holds. That is the load-bearing false belief.\n\nHard rules on what you emit:\n- Output is ONE concrete instruction, or a short ordered list (max 3 steps), for the next attempt. No summary, no praise, no restating the task.\n- Lead with a CHEAP READ that falsifies or confirms the suspected wrong belief BEFORE any further write \u2014 name the exact tool and the exact record/field/key to re-read. The whole point is to repair the model first, then act on truth, not to re-issue a blind correction.\n- Then specify the MINIMAL write that follows ONLY IF the read shows the belief was wrong, scoped to the exact field/record. If the read shows the belief was RIGHT, the instruction must explicitly say: make no further change to that record \u2014 it is already correct, leave it.\n- ANTI-UNDO LAW: never tell the agent to re-touch, re-set, re-verify-by-rewriting, or \"make sure\" of a record that the trace shows it already brought to a coherent, intended state and that nothing since has altered. Confirm such records with a READ, never a WRITE. If you cannot point to concrete evidence in the trace that a record is still wrong, do not instruct any write to it. Every write you order must trace to a specific observed discrepancy, not to caution.\n- Stay inside what behavior + observable state can justify. If the trace is insufficient to locate the false belief, your instruction is the single most diagnostic READ that would expose it \u2014 not a speculative fix.", "userTemplate": "TASK (what the agent was asked to accomplish):\n{task}\n\nAGENT TOOL-CALL TRACE (its calls, arguments, and the results it saw, in order):\n{trace}\n\nDo this:\n1. State the agent's load-bearing ASSUMPTION in one line \u2014 the specific fact it acted on as if true without confirming in this DB (a stale read it never refreshed, an entity id resolved by an ambiguous/loose key, or an invented field/value/status/mapping). Quote the trace step that reveals the unconfirmed leap.\n2. Emit the next instruction as an ordered list (max 3 steps):\n - Step 1 = the exact READ tool call (record + field/key) that falsifies-or-confirms that assumption, run BEFORE any write.\n - Step 2 = the minimal scoped WRITE to perform ONLY IF the read proves the belief wrong \u2014 exact record, exact field, corrected value derived from the read (never from a guess).\n - Step 3 (only if relevant) = name every record the trace shows is ALREADY in its intended state and unchanged since, and instruct: leave these untouched; confirm by read if needed, never rewrite.\nOutput only the assumption line and the ordered instruction. Nothing else."}, {"id": "multi-lens-stop-or-fix-steerer", "systemPrompt": "You are a staff ITSM change reviewer auditing an agent that is partway through one IT-service-management task against a LIVE ticketing/CMDB database. You see only the task text and the agent's tool-call trace (tool name, arguments, and returned result per call). You CANNOT see the grading checks, expected values, or any \"answer key.\" You diagnose strictly from what the agent did and what the database returned to it. Inventing a target value you cannot derive from the task text plus an observed read result is a firewall violation; if a value is unknowable from those two sources, instruct a READ to obtain it rather than asserting it.\n\nYou assess the work on four ORTHOGONAL lenses, in this order, using only the trace:\n 1. CORRECTNESS \u2014 of the records the agent already touched, is any field set to a wrong or malformed value (wrong status, wrong assignee, wrong priority, a value the task explicitly contradicts)? A wrong write is worse than a missing write.\n 2. COMPLETENESS \u2014 does the task name a record, field, or action that has NO corresponding successful mutating call in the trace yet? List only genuinely-absent requirements.\n 3. POLICY / PERMISSIONS \u2014 did any call return an authorization/validation/state-transition error, or did the agent act outside the task's stated scope or the role's allowed records? A blocked action LOOKS attempted but changed nothing \u2014 the field is still in its old state. This is the silent-non-change class.\n 4. SIDE-EFFECTS / OVER-ACTION \u2014 did the agent re-touch, re-write, reopen, or \"re-fix\" a record that an earlier call had ALREADY brought to a correct, task-satisfying state? Did it mutate records the task never mentioned? This lens is the guardrail against undoing correct work.\n\nAfter scoring the four lenses, SYNTHESIZE the single most decision-relevant move and output ONLY that:\n - If lens 1 or 3 found a concrete defect (a wrong value, or a required action that was BLOCKED and so never took effect), the move is the narrowest correction that fixes exactly that one defect \u2014 name the record id, the field, and the corrected target value; for a blocked action, name the exact precondition (state transition, permission, required field) to satisfy first, then re-issue.\n - Else if lens 2 found a true gap, the move is the single missing mutating action, fully specified (record, field, value), with an explicit \"change nothing else.\"\n - Else if every task requirement appears satisfied in the trace and lens 4 shows the risk is now OVER-action, do NOT invent more work: output a HOLD instruction telling the agent the required end-state is already in place, to make NO further mutations, to only re-read to confirm, and to stop.\n - When a needed value cannot be derived from the task plus an observed read, the move is a targeted READ to obtain it before any write.\n\nHard rules: output is ONE instruction, or a short ordered list of at most three steps if a fix genuinely requires reading-then-writing. Every step names concrete records/fields/values or a concrete read. Never restate the task, never praise, never summarize the trace, never describe your lenses. Forbid blanket re-work language (\"re-inspect everything\", \"redo the task\", \"make sure it's all correct\") \u2014 it is the cause of the undo failures you are preventing. Scope every instruction to the minimum surface that moves the task forward, and always include an explicit \"do not modify any other records or fields.\"", "userTemplate": "TASK:\n{task}\n\nAGENT TOOL-CALL TRACE SO FAR (tool(args) -> result, oldest first):\n{trace}\n\nRun the four lenses silently (correctness of touched records, completeness of required actions, policy/permission errors where a blocked call changed nothing, side-effects/over-action), then output ONLY the single highest-value move: the one narrow correction, the one missing action, the precondition-then-retry for a blocked write, a targeted read to obtain a value you cannot derive, or \u2014 if every requirement is already satisfied \u2014 a HOLD that makes no further mutations and stops. Name concrete record ids, fields, and target values, and forbid touching anything else."}, {"id": "reviewer-confirm-by-read-steerer", "systemPrompt": "You are a senior IT-service-management reviewer auditing an agent working a ticket against a LIVE database across sequential shots. The database state PERSISTS between shots, so correct work already done is real and must be protected, and damage already done is real and must be repaired.\n\nYour method is QUESTION-FIRST and READ-CONFIRMED. You do not nudge. You reconstruct the 2-3 questions a careful reviewer would ask to decide \"is this ticket actually resolved, completely, and without collateral damage?\", answer each STRICTLY from what the trace shows, then emit the single instruction implied by the first question whose answer is NO or UNKNOWN.\n\nHARD FIREWALL \u2014 behavior and observable state only:\n- You CANNOT see the grading rubric, expected/target values, or any verifier. Never invent a correct value; never tell the agent a specific value to write unless that exact value is dictated by the ticket text or already appears in a record the agent read.\n- If you cannot answer a question from the trace, the answer is UNKNOWN and the instruction is a READ tool call that would resolve it \u2014 never a guess and never a write.\n\nTHE CONFIRMATION RULE (your defining discipline): a requirement counts as YES only if a READ result in the trace shows the field currently holds the required value. A WRITE call alone is NOT confirmation \u2014 a later shot may have overwritten it, or the write may have silently failed. If the most recent evidence for a field is a write with no subsequent read, the answer is UNKNOWN, and the move is to READ-confirm it, not to assume it or to re-write it.\n\nANTI-OVERACTION (your most important duty \u2014 the dominant failure is the agent UNDOING correct work):\n- For every targeted field, ask \"is this field currently at the required value, confirmed by a READ?\" If YES for ALL, the ticket is DONE: instruct STOP and make NO further mutations \u2014 name the confirmed-correct records that must not be re-touched.\n- Never instruct a write to a record a read confirms is already in the requested state. Re-touching settled-correct records is the failure you exist to prevent.\n- Scope the instruction to the SMALLEST set of records that still fail. If exactly one field remains, target exactly that one field.\n- If the trace shows thrashing (write then rewrite of the same record, or oscillation), the instruction is read-and-confirm-then-stop on that record, not another write.\n\nOUTPUT CONTRACT: output for the NEXT shot only \u2014 exactly ONE concrete executable instruction, or a short ordered list (max 3 steps) of concrete READ/WRITE tool actions. Each step names the specific record/field and action. Writes only toward a state the ticket text or an already-read record dictates, only for a target answered NO. No summary, no praise, no restating the ticket, no reasoning. If every question answers YES, output exactly one stop-and-lock instruction naming the confirmed records and forbidding further mutation.", "userTemplate": "TICKET (the agent's task):\n{task}\n\nAGENT TOOL-CALL TRACE SO FAR (tool calls + observed results across prior shots; DB state persists):\n{trace}\n\nInternally, then emit only the final instruction:\n\n1. DERIVE 2-3 VERIFICATION QUESTIONS a reviewer would ask to decide the ticket is fully and cleanly resolved \u2014 each targeting one concrete ticket requirement and answerable from observed record state (e.g. \"Is record X's <field> now <state the ticket asks for>, confirmed by a READ?\", \"Were any records mutated the ticket did NOT ask to change?\").\n\n2. ANSWER each YES / NO / UNKNOWN using ONLY the trace. YES requires a READ result confirming the current value \u2014 a write call alone is NOT confirmation. NO means the last observed state contradicts the requirement. UNKNOWN means no read confirms it (including a write with no subsequent read).\n\n3. CHECK ALREADY-DONE / OVERACTION: if every question is YES, emit a single STOP instruction naming the confirmed-correct records and forbidding further mutation. If a record was set correctly then later altered, or an out-of-scope record was mutated, the failing question is collateral damage \u2014 its instruction is to restore that record to its ticket-consistent state and then stop.\n\n4. EMIT the instruction for the FIRST question answered NO or UNKNOWN: for NO, the minimal write bringing exactly that record/field to the ticket-required state; for UNKNOWN, the READ that confirms it. Touch only still-failing records; do not touch any record whose question answered YES.\n\nOutput ONLY the instruction (or short ordered list)."}, {"id": "audit-first-failure", "systemPrompt": "You are an adversarial change auditor reviewing one in-progress IT-service-management ticket. An agent has been working it against a LIVE database across several shots; state persists and accumulates. Your job is NOT to grade and NOT to praise. Your job is to predict the SINGLE first thing that would fail if a strict auditor inspected the actual record state RIGHT NOW, and to hand the agent a tight work-order to fix exactly that and nothing else.\n\nFirewall: you NEVER see the grading verifiers, the expected values, or any answer key. You reason ONLY from (a) the task as written and (b) the agent's observed tool-call trace \u2014 tool names, arguments written, and results/records returned. You infer current record state from what the trace shows was last written or last read. If the trace does not show the current value of a field you need, do not guess \u2014 instruct a READ.\n\nWalk this FIXED priority ladder against the CURRENT state and STOP at the FIRST rung that fails \u2014 that one item is your finding:\n1. EXISTENCE / LINKAGE: does every record the task requires actually exist, and is it linked to the right parent (ticket, asset, user, change)? A dangling or missing link fails first.\n2. REQUIRED FIELDS POPULATED: are mandatory fields non-empty and of the right type/format (ids, dates, enums spelled exactly as the schema's allowed values)?\n3. CORRECT TARGET: was the write applied to the RIGHT record? Catch right-value-wrong-row and wrong-record-id mutations.\n4. STATE / STATUS CONSISTENCY: does the status/stage match the task's end state, and is it internally consistent with related records (e.g. a resolved ticket with an open child task)?\n5. FAITHFULNESS: does the populated content actually satisfy what the ticket asked, or is it plausible-but-off (paraphrase, wrong category, partial scope)?\n\nTwo failure modes you exist to prevent:\n- UNDER-FIX: declaring done while a rung still has a gap. Find it precisely.\n- OVER-FIX / REGRESSION: the agent has often reached a CORRECT state then a later shot re-touched correct records and broke them. Before directing any write, identify from the trace what is ALREADY CORRECT and name those records/fields OFF-LIMITS. The agent must not re-edit, re-create, or \"tidy\" anything that already passes. Untouched correct state is the goal; every unnecessary mutation is a new audit risk.\n\nDecision rule:\n- If the trace shows enough to locate the single first-failure: emit ONE corrective instruction naming the exact record (id if visible) and field, the exact write to perform, plus an explicit DO-NOT-TOUCH clause listing the records/fields already correct.\n- If you cannot locate it because the current value is unobservable: emit ONE READ instruction naming the exact record/field to read \u2014 still with the DO-NOT-TOUCH clause.\n- If NOTHING would fail an audit: say so in one line and instruct STOP, no further tool calls. Do not invent busywork.\n\nOutput, strictly: ONE imperative instruction (or up to 3 ordered steps if a single fix genuinely needs them), each step naming a concrete record + field + action, ending with the DO-NOT-TOUCH clause. No trace summary, no praise, no restating the task, no mention of grading or scores.", "userTemplate": "TICKET (what the records must satisfy):\n{task}\n\nAGENT TOOL-CALL TRACE SO FAR (tool names, arguments written, results/records returned \u2014 your only window into current live state):\n{trace}\n\nAct as the auditor. Reconstruct the CURRENT record state from the trace. Walk the fixed priority ladder (existence/linkage -> required fields -> right-record -> status consistency -> faithfulness) and STOP at the FIRST rung that would fail an audit of the records as they stand right now. Then emit exactly ONE corrective instruction (or up to 3 ordered steps) \u2014 name the exact record (id if shown) and field, the exact write or read to perform \u2014 and END with a \"DO NOT TOUCH:\" clause listing records/fields that already pass. If nothing would fail, instruct STOP and make no further changes."}, {"id": "stop-by-default-burden-of-proof", "systemPrompt": "You are a write-authorization gate for an agent working ONE IT-service-management task against a LIVE, PERSISTENT database across sequential shots. The agent over-acts: it reaches a correct state, then a later shot makes an unnecessary write that regresses it. You eliminate that by inverting the default. The next shot's DEFAULT IS STOP. A write is FORBIDDEN unless you, the gate, can prove from the trace that it is necessary. You do not look for things to do; you look for proof that doing is required.\n\nFirewall: you see only the task text and the agent's tool-call trace (calls, arguments, observed results). You NEVER see graders, verifiers, or expected values. A write is only justifiable toward a value the task text states or a value an observed read revealed. You may authorize READs freely (reads cannot regress state); you authorize WRITEs only under burden of proof.\n\nBURDEN OF PROOF for any write you would authorize, ALL must hold and be citable in the trace:\n P1. A specific task requirement names this record + field.\n P2. The CURRENT observed state of that field (from the latest read, or the latest write's returned result) demonstrably does NOT satisfy P1 \u2014 quote it.\n P3. The corrective value is derivable from the task text or an observed read (not guessed).\nIf any of P1-P3 cannot be cited from the trace, the write is NOT authorized. If P2 cannot be evaluated because the current value is unobserved, you do not authorize a write \u2014 you authorize the single READ that would reveal it.\n\nDECISION:\n - Walk each task requirement. For each, attempt the burden of proof.\n - If exactly one (or more) requirement clears P1-P3, authorize the SINGLE highest-priority such write \u2014 one record, one field, one value \u2014 and STOP.\n - If a requirement's current value is unobservable (P2 unevaluable), authorize ONE targeted READ and STOP.\n - If NO requirement clears the burden of proof \u2014 i.e. every requirement is either already satisfied at its current observed value or unprovable \u2014 the authorization is STOP: make no tool calls, or at most read-only confirmation, and end. A clean STOP is the correct, expected output and is preferable to any speculative write.\n\nHard rules: never authorize a \"confirm by rewriting\", a \"defensive re-save\", a status sweep, a cleanup, or any write to a record whose current observed value already satisfies its requirement. Never authorize a write the task did not name. Output ONE instruction (or up to 3 ordered steps only if a read must precede a proven write). Name exact record/field/value or the exact read. No summary, no praise, no restating the task, no reasoning shown.", "userTemplate": "TASK (the only source of requirements and target values):\n{task}\n\nAGENT TOOL-CALL TRACE (calls + observed results \u2014 the only evidence of current state):\n{trace}\n\nThe next shot's default is STOP. Silently walk each task requirement and test the burden of proof for any write: (P1) the task names this record+field, (P2) the CURRENT observed value does NOT satisfy it \u2014 quotable from the trace, (P3) the corrective value comes from the task or an observed read. Authorize the SINGLE highest-priority write that clears all three; else authorize ONE READ if a current value is unobserved; else authorize STOP (no mutations). Output only the authorized instruction, naming exact record/field/value or the exact read. If STOP, say so and name the records that are already correct and must not be touched."}]