toolbench 0.1.0__tar.gz → 0.2.0__tar.gz

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 (161) hide show
  1. {toolbench-0.1.0 → toolbench-0.2.0}/PKG-INFO +23 -7
  2. {toolbench-0.1.0 → toolbench-0.2.0}/README.md +22 -6
  3. toolbench-0.2.0/harness_templates/README.md +91 -0
  4. toolbench-0.2.0/harness_templates/claude-code/README.md +54 -0
  5. toolbench-0.2.0/harness_templates/claude-code/default.yaml +20 -0
  6. toolbench-0.2.0/harness_templates/codex/README.md +43 -0
  7. toolbench-0.2.0/harness_templates/codex/default.yaml +18 -0
  8. toolbench-0.2.0/harness_templates/orchestral/README.md +49 -0
  9. toolbench-0.2.0/harness_templates/orchestral/anthropic.yaml +22 -0
  10. toolbench-0.2.0/harness_templates/orchestral/google.yaml +22 -0
  11. toolbench-0.2.0/harness_templates/orchestral/groq.yaml +28 -0
  12. toolbench-0.2.0/harness_templates/orchestral/litellm.yaml +32 -0
  13. toolbench-0.2.0/harness_templates/orchestral/openai.yaml +22 -0
  14. {toolbench-0.1.0 → toolbench-0.2.0}/pyproject.toml +1 -1
  15. toolbench-0.2.0/tests/test_benchmark_extends.py +250 -0
  16. toolbench-0.2.0/tests/test_cli_runtime_stderr_drain.py +89 -0
  17. toolbench-0.2.0/tests/test_codex_runtime.py +136 -0
  18. toolbench-0.2.0/tests/test_cost_extraction.py +92 -0
  19. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_crash_classifier.py +43 -0
  20. toolbench-0.2.0/tests/test_integrity.py +80 -0
  21. toolbench-0.2.0/tests/test_json_repair.py +113 -0
  22. toolbench-0.2.0/tests/test_judge_score.py +157 -0
  23. toolbench-0.2.0/tests/test_judge_select.py +137 -0
  24. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_metrics.py +28 -0
  25. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_numeric_checks.py +6 -5
  26. toolbench-0.2.0/tests/test_resume.py +129 -0
  27. toolbench-0.2.0/tests/test_runner_llm_judge.py +126 -0
  28. toolbench-0.2.0/tests/test_runner_transient.py +108 -0
  29. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_runtime_registry.py +5 -3
  30. toolbench-0.2.0/tests/test_runtime_version_label.py +11 -0
  31. toolbench-0.2.0/tests/test_summary_text_cost.py +36 -0
  32. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_trial_loop.py +1 -1
  33. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/__init__.py +1 -1
  34. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/cli.py +667 -47
  35. toolbench-0.2.0/toolbench/core/benchmark.py +190 -0
  36. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/budget.py +10 -0
  37. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/checks.py +35 -9
  38. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/content_checks.py +161 -2
  39. toolbench-0.2.0/toolbench/core/crash_classifier.py +280 -0
  40. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/failure_modes.py +8 -1
  41. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/harness.py +57 -16
  42. toolbench-0.2.0/toolbench/core/integrity.py +94 -0
  43. toolbench-0.2.0/toolbench/core/json_repair.py +201 -0
  44. toolbench-0.2.0/toolbench/core/judge.py +426 -0
  45. toolbench-0.2.0/toolbench/core/judge_select.py +252 -0
  46. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/llm_factory.py +115 -4
  47. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/loadout.py +35 -14
  48. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/metrics.py +85 -14
  49. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/numeric_checks.py +24 -24
  50. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/runner.py +441 -40
  51. toolbench-0.2.0/toolbench/core/runtime.py +1011 -0
  52. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/task.py +42 -1
  53. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/tool_resolver.py +100 -6
  54. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/trajectory.py +4 -1
  55. toolbench-0.2.0/toolbench/reporting/_output.py +53 -0
  56. toolbench-0.2.0/toolbench/reporting/_shared.py +135 -0
  57. toolbench-0.2.0/toolbench/reporting/audit_log.py +250 -0
  58. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/reporting/k_sweep.py +58 -13
  59. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/reporting/parallel_coords.py +55 -13
  60. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/reporting/per_stage_k.py +59 -11
  61. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/reporting/plot_overview.py +3 -1
  62. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/reporting/summary_text.py +157 -7
  63. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/reporting/transcript.py +67 -9
  64. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/authoring/custom-tools.md +6 -2
  65. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/authoring/define-a-benchmark.md +15 -1
  66. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/authoring/overview.md +32 -0
  67. toolbench-0.2.0/userdocs/authoring/rubrics-and-checks.md +240 -0
  68. toolbench-0.2.0/userdocs/explanation.md +106 -0
  69. toolbench-0.2.0/userdocs/guides/harnesses.md +147 -0
  70. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/guides/install-and-quickstart.md +4 -0
  71. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/guides/loadouts.md +3 -1
  72. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/guides/reading-results.md +82 -2
  73. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/guides/running-a-benchmark.md +39 -1
  74. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/guides/toolbase.md +14 -0
  75. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/index.md +6 -2
  76. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/reference/commands.md +24 -5
  77. toolbench-0.2.0/userdocs/reference/files-and-layout.md +122 -0
  78. toolbench-0.2.0/userdocs/reference/metrics.md +164 -0
  79. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/reference/schemas.md +88 -2
  80. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/reference/vocabulary.md +46 -7
  81. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/troubleshooting.md +43 -0
  82. toolbench-0.1.0/tests/test_judge_score.py +0 -85
  83. toolbench-0.1.0/toolbench/core/benchmark.py +0 -100
  84. toolbench-0.1.0/toolbench/core/crash_classifier.py +0 -163
  85. toolbench-0.1.0/toolbench/core/judge.py +0 -115
  86. toolbench-0.1.0/toolbench/core/runtime.py +0 -145
  87. toolbench-0.1.0/toolbench/reporting/_shared.py +0 -73
  88. toolbench-0.1.0/userdocs/authoring/rubrics-and-checks.md +0 -112
  89. toolbench-0.1.0/userdocs/explanation.md +0 -61
  90. toolbench-0.1.0/userdocs/guides/harnesses.md +0 -67
  91. toolbench-0.1.0/userdocs/reference/files-and-layout.md +0 -72
  92. toolbench-0.1.0/userdocs/reference/metrics.md +0 -112
  93. {toolbench-0.1.0 → toolbench-0.2.0}/.gitignore +0 -0
  94. {toolbench-0.1.0 → toolbench-0.2.0}/LICENSE +0 -0
  95. {toolbench-0.1.0 → toolbench-0.2.0}/examples/calculator/requirements.txt +0 -0
  96. {toolbench-0.1.0 → toolbench-0.2.0}/examples/calculator/toolkit.yaml +0 -0
  97. {toolbench-0.1.0 → toolbench-0.2.0}/examples/calculator/tools/__init__.py +0 -0
  98. {toolbench-0.1.0 → toolbench-0.2.0}/examples/calculator/tools/basic.py +0 -0
  99. {toolbench-0.1.0 → toolbench-0.2.0}/examples/calculator/tools/scientific.py +0 -0
  100. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/benchmark.yaml +0 -0
  101. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/ground_truth/answer.json +0 -0
  102. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/harnesses/orchestral/anthropic.yaml +0 -0
  103. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/harnesses/orchestral/groq.yaml +0 -0
  104. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/harnesses/orchestral/litellm.yaml +0 -0
  105. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/harnesses/orchestral/openai.yaml +0 -0
  106. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/additive_only.yaml +0 -0
  107. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/all_metrics.yaml +0 -0
  108. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/calc_toolbase.yaml +0 -0
  109. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/core_only.yaml +0 -0
  110. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/full_local.yaml +0 -0
  111. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/full_mixed.yaml +0 -0
  112. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/full_toolbase.yaml +0 -0
  113. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/guided.yaml +0 -0
  114. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/loadouts/primitives_only.yaml +0 -0
  115. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/skills/distance_recipe.md +0 -0
  116. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/tools/chebyshev.py +0 -0
  117. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/tools/dunderkit.py +0 -0
  118. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/tools/euclid.py +0 -0
  119. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/tools/extras.py +0 -0
  120. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/tools/taxicab.py +0 -0
  121. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/derived/prompts/system.md +0 -0
  122. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/derived/prompts/user.md +0 -0
  123. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/derived/variant.yaml +0 -0
  124. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/direct/prompts/system.md +0 -0
  125. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/direct/prompts/user.md +0 -0
  126. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/direct/sandbox/template/points.json +0 -0
  127. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/direct/variant.yaml +0 -0
  128. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/polar/prompts/system.md +0 -0
  129. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/polar/prompts/user.md +0 -0
  130. {toolbench-0.1.0 → toolbench-0.2.0}/examples/geometry/variants/polar/variant.yaml +0 -0
  131. {toolbench-0.1.0 → toolbench-0.2.0}/mkdocs.yml +0 -0
  132. {toolbench-0.1.0 → toolbench-0.2.0}/tests/__init__.py +0 -0
  133. {toolbench-0.1.0 → toolbench-0.2.0}/tests/helpers.py +0 -0
  134. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_artifact_cleanup.py +0 -0
  135. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_artifact_policy.py +0 -0
  136. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_checks.py +0 -0
  137. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_content_checks.py +0 -0
  138. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_geometry.py +0 -0
  139. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_mcp_source.py +0 -0
  140. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_paired_deltas.py +0 -0
  141. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_rubric_loader.py +0 -0
  142. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_runner_rate_limit.py +0 -0
  143. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_runner_request_params.py +0 -0
  144. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_runner_retry.py +0 -0
  145. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_schema.py +0 -0
  146. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_skills.py +0 -0
  147. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_tool_error_visibility.py +0 -0
  148. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_tool_resolver.py +0 -0
  149. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_toolbase_sandbox_scoping.py +0 -0
  150. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_toolbase_select_provenance.py +0 -0
  151. {toolbench-0.1.0 → toolbench-0.2.0}/tests/test_variant.py +0 -0
  152. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/__init__.py +0 -0
  153. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/artifact_policy.py +0 -0
  154. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/litellm_pricing.py +0 -0
  155. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/skills.py +0 -0
  156. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/store.py +0 -0
  157. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/tool_policy.py +0 -0
  158. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/core/variant.py +0 -0
  159. {toolbench-0.1.0 → toolbench-0.2.0}/toolbench/reporting/__init__.py +0 -0
  160. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/guides/variants.md +0 -0
  161. {toolbench-0.1.0 → toolbench-0.2.0}/userdocs/stylesheets/extra.css +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolbench
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A platform and CLI for building benchmarks for agentic tools and harnesses.
5
5
  Project-URL: Homepage, https://github.com/tonymenzo/toolbench
6
6
  Project-URL: Repository, https://github.com/tonymenzo/toolbench
@@ -88,10 +88,10 @@ axes — vary any of them on the command line to run an ablation:
88
88
  | Concept | What it is | Where it lives |
89
89
  |---------------|----------------------------------------------------------------------|-------------------------|
90
90
  | **Benchmark** | The task + grading rubric + ground truth. | `benchmark.yaml` |
91
- | **Harness** | The agent runtime, provider, core tools, and loop policy. | `harnesses/*.yaml` |
91
+ | **Harness** | The agent runtime (`orchestral`, `claude_code`, `codex`), provider, core tools, and loop policy. | `harnesses/*.yaml` |
92
92
  | **Loadout** | The domain tools the agent gets (beyond the harness core). | `loadouts/*.yaml` |
93
93
  | **Variant** | The prompt + sandbox seed (scaffolding axis), orthogonal to tools. | `variants/<name>/` |
94
- | **Rubric** | Ordered, weighted stages of checks; trial score = prefix product. | inside `benchmark.yaml` |
94
+ | **Rubric** | Ordered, weighted stages of checks; trial score = weighted reach. | inside `benchmark.yaml` |
95
95
 
96
96
  A **loadout source** is one of:
97
97
 
@@ -113,11 +113,14 @@ A **loadout source** is one of:
113
113
 
114
114
  For each (model × condition) cell over `k` trials:
115
115
 
116
- - **reach̄ₖ** — mean rubric-weighted reach (how far through the staged pipeline the agent gets).
117
- - **pass@k** — probability at least one of k trials passes every stage (best-of-k).
118
- - **pass^k** — probability all k trials pass (worst-of-k).
116
+ - $\overline{\text{reach}}_k$ — mean rubric-weighted reach: how far through the task the agent gets, on average.
117
+ - $\text{pass@}k$ — probability that at least one of $k$ trials passes (best-of-$k$).
118
+ - $\text{pass}^{k}$ — probability that all $k$ trials pass (worst-of-$k$).
119
119
 
120
- with bootstrap 95% confidence intervals and a metric-correlation matrix.
120
+ with bootstrap 95% confidence intervals and a metric-correlation matrix. A trial *passes*
121
+ when it clears the rubric's pass criterion — every stage by default, or reach ≥ a
122
+ `pass_threshold` once the rubric uses partial-credit stages. See
123
+ [Metrics](https://toolbench-ai.com/docs/reference/metrics/) for the exact estimators.
121
124
 
122
125
  ## Commands
123
126
 
@@ -129,6 +132,19 @@ with bootstrap 95% confidence intervals and a metric-correlation matrix.
129
132
 
130
133
  Run `toolbench --help` (or `tbe --help`) for the full reference.
131
134
 
135
+ ## Also
136
+
137
+ - **Runtimes** — besides the API-driven `orchestral` runtime, toolbench drives the
138
+ `claude_code` and `codex` CLIs directly (subscription auth, no API key), with per-turn
139
+ token accounting and a filesystem sandbox. Copy-paste starting points live in
140
+ [`harness_templates/`](harness_templates/).
141
+ - **Judges** — grade with the deterministic rule judge (default) or add an LLM second
142
+ opinion (`--judge rule+llm`); the rule grade always stays authoritative. Any judge can be
143
+ applied after the fact with `toolbench regrade --judge …`.
144
+ - **Safeguards** — trials that read the ground-truth answer key are quarantined
145
+ (`INTEGRITY_LEAK`, scored 0) so a leak can't inflate the headline; every trial also gets a
146
+ readable `audit.txt` of its full trajectory.
147
+
132
148
  ## License
133
149
 
134
150
  MIT
@@ -46,10 +46,10 @@ axes — vary any of them on the command line to run an ablation:
46
46
  | Concept | What it is | Where it lives |
47
47
  |---------------|----------------------------------------------------------------------|-------------------------|
48
48
  | **Benchmark** | The task + grading rubric + ground truth. | `benchmark.yaml` |
49
- | **Harness** | The agent runtime, provider, core tools, and loop policy. | `harnesses/*.yaml` |
49
+ | **Harness** | The agent runtime (`orchestral`, `claude_code`, `codex`), provider, core tools, and loop policy. | `harnesses/*.yaml` |
50
50
  | **Loadout** | The domain tools the agent gets (beyond the harness core). | `loadouts/*.yaml` |
51
51
  | **Variant** | The prompt + sandbox seed (scaffolding axis), orthogonal to tools. | `variants/<name>/` |
52
- | **Rubric** | Ordered, weighted stages of checks; trial score = prefix product. | inside `benchmark.yaml` |
52
+ | **Rubric** | Ordered, weighted stages of checks; trial score = weighted reach. | inside `benchmark.yaml` |
53
53
 
54
54
  A **loadout source** is one of:
55
55
 
@@ -71,11 +71,14 @@ A **loadout source** is one of:
71
71
 
72
72
  For each (model × condition) cell over `k` trials:
73
73
 
74
- - **reach̄ₖ** — mean rubric-weighted reach (how far through the staged pipeline the agent gets).
75
- - **pass@k** — probability at least one of k trials passes every stage (best-of-k).
76
- - **pass^k** — probability all k trials pass (worst-of-k).
74
+ - $\overline{\text{reach}}_k$ — mean rubric-weighted reach: how far through the task the agent gets, on average.
75
+ - $\text{pass@}k$ — probability that at least one of $k$ trials passes (best-of-$k$).
76
+ - $\text{pass}^{k}$ — probability that all $k$ trials pass (worst-of-$k$).
77
77
 
78
- with bootstrap 95% confidence intervals and a metric-correlation matrix.
78
+ with bootstrap 95% confidence intervals and a metric-correlation matrix. A trial *passes*
79
+ when it clears the rubric's pass criterion — every stage by default, or reach ≥ a
80
+ `pass_threshold` once the rubric uses partial-credit stages. See
81
+ [Metrics](https://toolbench-ai.com/docs/reference/metrics/) for the exact estimators.
79
82
 
80
83
  ## Commands
81
84
 
@@ -87,6 +90,19 @@ with bootstrap 95% confidence intervals and a metric-correlation matrix.
87
90
 
88
91
  Run `toolbench --help` (or `tbe --help`) for the full reference.
89
92
 
93
+ ## Also
94
+
95
+ - **Runtimes** — besides the API-driven `orchestral` runtime, toolbench drives the
96
+ `claude_code` and `codex` CLIs directly (subscription auth, no API key), with per-turn
97
+ token accounting and a filesystem sandbox. Copy-paste starting points live in
98
+ [`harness_templates/`](harness_templates/).
99
+ - **Judges** — grade with the deterministic rule judge (default) or add an LLM second
100
+ opinion (`--judge rule+llm`); the rule grade always stays authoritative. Any judge can be
101
+ applied after the fact with `toolbench regrade --judge …`.
102
+ - **Safeguards** — trials that read the ground-truth answer key are quarantined
103
+ (`INTEGRITY_LEAK`, scored 0) so a leak can't inflate the headline; every trial also gets a
104
+ readable `audit.txt` of its full trajectory.
105
+
90
106
  ## License
91
107
 
92
108
  MIT
@@ -0,0 +1,91 @@
1
+ # Harness templates
2
+
3
+ A harness answers one question: **what drives the model, and how does the
4
+ loop behave?** It is the `harness` axis of a run
5
+ (`benchmark × harness × loadout × variant × model`).
6
+
7
+ These are copy-paste starting points, not a registry — nothing in toolbench
8
+ reads this directory. Copy a file into a benchmark and edit it:
9
+
10
+ ```
11
+ <benchmark>/harnesses/<runtime>/<name>.yaml
12
+ ```
13
+
14
+ The path is the harness id: `harnesses/orchestral/groq.yaml` is referred to
15
+ as `--harness orchestral/groq`.
16
+
17
+ ## Layout
18
+
19
+ One directory per **runtime**. Each has a `README.md` documenting every key
20
+ that runtime reads, plus runnable templates.
21
+
22
+ ```
23
+ claude-code/ default.yaml provider-locked (subscription)
24
+ codex/ default.yaml provider-locked (subscription)
25
+ orchestral/ anthropic.yaml openai.yaml provider-agnostic:
26
+ google.yaml groq.yaml one template per provider
27
+ litellm.yaml
28
+ ```
29
+
30
+ ## Runtime vs. provider
31
+
32
+ The distinction drives everything else:
33
+
34
+ - **CLI runtimes** (`claude_code`, `codex`) shell out to a coding agent that
35
+ owns the model request. `provider:` is a credential-free placeholder and
36
+ only `{name, model}` apply — request params like `max_tokens` are inert.
37
+ `core: {builtin: true}` uses the agent's own tools.
38
+ - **In-process runtimes** (`orchestral`) call a provider SDK or proxy
39
+ directly. `provider:` is fully live: `name` selects the backend and every
40
+ other key is forwarded as a model-request parameter on each call. `core:`
41
+ is an explicit tool list you curate per arm.
42
+
43
+ Because orchestral is the provider-agnostic one, it gets a template per
44
+ provider. The filename is the **provider key**, not the model family — Gemini
45
+ lives in `google.yaml`. Registered providers: `anthropic`, `openai`,
46
+ `google`, `groq`, `litellm`, `subscription`. Others (ollama, vllm, custom
47
+ routes) register themselves via `register_provider`; see
48
+ `toolbench/core/llm_factory.py`.
49
+
50
+ ## Two things that bite
51
+
52
+ **`cache_bust` on proxy routes.** `provider:` keys are forwarded to the model
53
+ *except* the control keys `name` and `cache_bust`. A proxy with response-level
54
+ caching hands every trial in a cell the same completion — k trials stop being
55
+ independent samples, and pass@k / pass^k silently collapse into reach. Set
56
+ `cache_bust: true` on any proxy route (it's on in `litellm.yaml`). Direct
57
+ providers don't need it: their prompt/KV caches don't affect sampling.
58
+
59
+ **`--max-cost-usd` binds only when *something* prices the model.** Cost
60
+ resolves through a three-step cascade (`runner.py:_extract_usage`):
61
+
62
+ 1. `usage.cost` on the Response — orchestral's own per-provider pricing model.
63
+ This is where `anthropic`, `openai`, `google` and `groq` all get priced.
64
+ 2. The LiteLLM proxy's pricing snapshot, fetched at run start — proxy routes
65
+ only, and only if the proxy is reachable at that moment.
66
+ 3. `toolbench.core.metrics.PRICING_TABLE` — a static last resort.
67
+
68
+ Step 1 is an **exact model-id string match with no `default` rate**, so an
69
+ unrecognized id costs `0.0` rather than raising. Combined with steps 2 and 3
70
+ missing, the run reports `$0.00 spent` and the cap never trips — silently.
71
+ Always pass the fully-qualified id the provider's pricing table uses
72
+ (`openai/gpt-oss-120b`, not `gpt-oss-120b`), and sanity-check the reported
73
+ spend on the first run of any new route.
74
+
75
+ Note that step 3 is reached through a provider *guess* from the model name
76
+ (`"claude" -> anthropic`, `"gpt" -> openai`), so `PRICING_TABLE` can never be
77
+ consulted for a `groq` or `google` key — adding entries there would not help.
78
+ Price a new provider in orchestral's pricing model instead.
79
+
80
+ ## Curating the tool set
81
+
82
+ `core.tools` is where a harness enforces arm policy, and it is the natural
83
+ place to drop tools that would function as an oracle:
84
+
85
+ - `WebSearchTool` — remove when the answer is published anywhere.
86
+ - `RunCommandTool` — a shell is the natural way to `cat`/`find` the ground
87
+ truth; remove it when the task never needs one.
88
+
89
+ A "closed-book" arm is just a harness whose `core.tools` has no execution
90
+ tools at all — file tools only, so the model must work in-context and can
91
+ still deposit its answer.
@@ -0,0 +1,54 @@
1
+ # `claude-code` harness templates
2
+
3
+ The `claude_code` runtime shells out to `claude -p ... --output-format
4
+ stream-json` under a Claude subscription. It is **provider-locked**: the
5
+ `provider:` block is a credential-free placeholder and only `{name, model}`
6
+ apply — the CLI owns the model request, so `max_tokens` and friends are
7
+ inert here (cap output with `env: CLAUDE_CODE_MAX_OUTPUT_TOKENS`).
8
+
9
+ Copy `default.yaml` to `<benchmark>/harnesses/claude-code/<name>.yaml`.
10
+
11
+ ## Every key this runtime reads
12
+
13
+ ```yaml
14
+ # TEMPLATE — claude_code runtime (drives `claude -p` under a subscription).
15
+ # Copy into a benchmark's harnesses/claude-code/<name>.yaml and keep only what
16
+ # you override. The keys below are the ONLY ones this runtime reads; anything
17
+ # else is ignored. Correctness-critical flags are hardcoded (see bottom).
18
+ name: claude-code/<name>
19
+ runtime:
20
+ name: claude_code # selects this runtime (required)
21
+ call_timeout_s: 3600 # per-MCP-call timeout (seconds)
22
+ disallowed_tools: [WebSearch, WebFetch] # --disallowedTools
23
+ # effort: high # --effort: low|medium|high|xhigh|max
24
+ # fallback_model: claude-sonnet-5 # --fallback-model when the model is overloaded
25
+ # max_budget_usd: 5 # --max-budget-usd: per-session spend cap
26
+ # add_dir: [../shared] # --add-dir: extra dirs the tools may access
27
+ env: # any var, set on the claude subprocess
28
+ ENABLE_TOOL_SEARCH: "false" # eager-load MCP tools; no ToolSearch deferral
29
+ # MAX_THINKING_TOKENS: "31999" # extended-thinking token budget
30
+ # CLAUDE_CODE_MAX_OUTPUT_TOKENS: "8192" # per-response output-token cap
31
+ provider: # CLI runtime: only {name, model} apply
32
+ name: subscription # credential-free placeholder llm (never called)
33
+ model: claude-haiku-4-5 # --model default; --models CLI arg overrides
34
+ # provider.max_tokens and other request params are INERT here — the claude CLI
35
+ # owns the model request. Cap output via env CLAUDE_CODE_MAX_OUTPUT_TOKENS.
36
+ core:
37
+ builtin: true # use Claude Code's own core tools
38
+ loop: # consumed by the runner (not the CLI)
39
+ max_iterations: 150 # agent.run() round-trip cap
40
+ max_format_retries: 2 # resume on MODEL_FORMAT_CRASH (bad tool-call JSON)
41
+ continue_nudges: 0 # presence-gated resumes on early stop (0 = strict)
42
+ max_rate_limit_retries: 3 # resume on provider 429/529 with backoff
43
+ max_transient_retries: 4 # resume on transient API/transport errors
44
+ ux_feedback: false # post-run unscored tool-UX critique; false |
45
+ # true | graded (graded shows the agent its
46
+ # score + stage pass/fail first, no evidence)
47
+
48
+ # HARDCODED (not overridable from this file):
49
+ # -p, --output-format stream-json, --verbose trajectory capture
50
+ # --permission-mode acceptEdits non-interactive
51
+ # --allowedTools (builtins + mcp__<server>__*) oracle hygiene + loadout
52
+ # --mcp-config, --strict-mcp-config, --resume toolbase wiring + session resume
53
+ # env MCP_TOOL_TIMEOUT, MCP_TIMEOUT; ANTHROPIC_API_KEY stripped
54
+ ```
@@ -0,0 +1,20 @@
1
+ # claude_code runtime: drives `claude -p` under a subscription.
2
+ # See README.md in this directory for every key the runtime reads.
3
+ name: claude-code/default
4
+ runtime:
5
+ name: claude_code
6
+ call_timeout_s: 3600 # per-MCP-call timeout; raise for long tool calls
7
+ disallowed_tools: [WebSearch, WebFetch]
8
+ env:
9
+ ENABLE_TOOL_SEARCH: "false" # eager-load MCP tools; no ToolSearch deferral
10
+ provider:
11
+ name: subscription # credential-free placeholder llm (never called)
12
+ model: claude-haiku-4-5 # --model default; --models overrides
13
+ core:
14
+ builtin: true # use Claude Code's own core tools
15
+ loop:
16
+ max_iterations: 150
17
+ max_format_retries: 2
18
+ continue_nudges: 0
19
+ max_rate_limit_retries: 3
20
+ max_transient_retries: 4
@@ -0,0 +1,43 @@
1
+ # `codex` harness templates
2
+
3
+ The `codex` runtime shells out to `codex exec --json` under a ChatGPT
4
+ subscription. Like `claude-code` it is **provider-locked**: only
5
+ `name` identifies subscription auth; the model comes from `toolbench run
6
+ --models`. Request params are inert.
7
+
8
+ Not plumbed for codex: `disallowed_tools`, `effort`, `fallback_model`,
9
+ `max_budget_usd`, `add_dir` — those are `claude_code`-only curated flags.
10
+
11
+ Copy `default.yaml` to `<benchmark>/harnesses/codex/<name>.yaml`.
12
+
13
+ ## Every key this runtime reads
14
+
15
+ ```yaml
16
+ # TEMPLATE — codex runtime (drives `codex exec --json` under a ChatGPT
17
+ # subscription). Copy into a benchmark's harnesses/codex/<name>.yaml and keep
18
+ # only what you override. The keys below are the ONLY ones this runtime reads.
19
+ name: codex/<name>
20
+ runtime:
21
+ name: codex # selects this runtime (required)
22
+ call_timeout_s: 3600 # per-MCP-call timeout (seconds)
23
+ sandbox: workspace-write # -s/--sandbox: read-only|workspace-write|danger-full-access
24
+ reasoning_effort: medium # low|medium|high|xhigh|max; explicit for reproducibility
25
+ env: # any var, set on the codex subprocess
26
+ # SOME_VAR: "value"
27
+ provider:
28
+ name: subscription # credential-free placeholder llm (never called)
29
+ # Model comes from `toolbench run --models`; request params are not accepted.
30
+ core:
31
+ builtin: true # use Codex's own core tools
32
+ loop: # consumed by the runner (same knobs as claude_code)
33
+ max_format_retries: 2
34
+ continue_nudges: 0
35
+ max_rate_limit_retries: 3
36
+ max_transient_retries: 4
37
+ ux_feedback: false # post-run unscored tool-UX critique (dev aid)
38
+
39
+ # NOT plumbed for codex: disallowed_tools, fallback_model, max_budget_usd,
40
+ # add_dir (these are claude_code-only curated flags).
41
+ # HARDCODED: --ignore-user-config; codex exec --json; MCP via
42
+ # -c mcp_servers.*; codex exec resume <id>.
43
+ ```
@@ -0,0 +1,18 @@
1
+ # codex runtime: drives `codex exec --json` under a ChatGPT subscription.
2
+ # See README.md in this directory for every key the runtime reads.
3
+ name: codex/default
4
+ runtime:
5
+ name: codex
6
+ call_timeout_s: 3600 # per-MCP-call timeout; raise for long tool calls
7
+ sandbox: workspace-write # read-only | workspace-write | danger-full-access
8
+ reasoning_effort: medium # explicit; never inherit the user's Codex config
9
+ provider:
10
+ name: subscription # credential-free placeholder llm (never called)
11
+ # The model always comes from toolbench run --models.
12
+ core:
13
+ builtin: true # use Codex's own core tools
14
+ loop:
15
+ max_format_retries: 2
16
+ continue_nudges: 0
17
+ max_rate_limit_retries: 3
18
+ max_transient_retries: 4
@@ -0,0 +1,49 @@
1
+ # `orchestral` harness templates
2
+
3
+ The orchestral runtime runs the model **in-process** through a provider
4
+ SDK or proxy, so unlike the CLI runtimes the `provider:` block is fully
5
+ live: `name` selects the backend and every other key is forwarded as a
6
+ model-request parameter on each call.
7
+
8
+ One file per **provider** — the filename is the provider key, not the
9
+ model family (Gemini lives in `google.yaml`). Copy the one you need to
10
+ `<benchmark>/harnesses/orchestral/<provider>.yaml` and trim `core.tools`
11
+ to the arm's tool policy.
12
+
13
+ Registered providers are `anthropic`, `openai`, `google`, `groq`,
14
+ `litellm`, `subscription`. Others (ollama, vllm, custom routes) register
15
+ themselves via `register_provider` in an adapter module — see
16
+ `toolbench/core/llm_factory.py`.
17
+
18
+ ## Every key this runtime reads
19
+
20
+ ```yaml
21
+ # TEMPLATE — orchestral runtime (in-process model via a provider SDK/proxy).
22
+ # Copy into a benchmark's harnesses/orchestral/<name>.yaml. Unlike the CLI
23
+ # runtimes, provider is FULLY live here: name + model + any request param
24
+ # (max_tokens, temperature, ...) are forwarded on every model call.
25
+ name: orchestral/<name>
26
+ runtime:
27
+ name: orchestral # selects this runtime (required)
28
+ version: ">=1.3" # enforced against the installed orchestral
29
+ provider:
30
+ name: anthropic # provider backend: anthropic|openai|litellm|... (control key)
31
+ model: claude-opus-4-8 # request model; --models CLI arg overrides
32
+ max_tokens: 8192 # forwarded on every model call
33
+ # temperature: 1.0 # any provider request param is forwarded
34
+ # cache_bust: false # toolbench control key: per-request cache nonce
35
+ core:
36
+ # Explicit in-process core toolset (no toolbase builtin). Drop tools an arm bans.
37
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, EditFileTool,
38
+ FindFilesTool, FileSearchTool, RunPythonTool, TodoRead, TodoWrite]
39
+ loop: # consumed by the runner
40
+ max_iterations: 150
41
+ max_format_retries: 2
42
+ continue_nudges: 0
43
+ max_rate_limit_retries: 3
44
+ max_transient_retries: 4
45
+ ux_feedback: false # post-run unscored tool-UX critique (dev aid)
46
+
47
+ # provider control keys (configure toolbench, NOT sent to the model): name, cache_bust.
48
+ # All other provider keys are forwarded verbatim as model-request params.
49
+ ```
@@ -0,0 +1,22 @@
1
+ # Anthropic direct (ANTHROPIC_API_KEY). Priced in toolbench's PRICING_TABLE,
2
+ # so --max-cost-usd binds on this route.
3
+ name: orchestral/anthropic
4
+ runtime:
5
+ name: orchestral
6
+ version: ">=1.3"
7
+ provider:
8
+ name: anthropic
9
+ model: claude-haiku-4-5 # default; --models overrides
10
+ max_tokens: 8192
11
+ core:
12
+ # Explicit in-process core toolset. Drop tools an arm bans (e.g. remove
13
+ # WebSearchTool when the answer is published, RunCommandTool when a shell
14
+ # would reach the ground truth).
15
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, EditFileTool,
16
+ FindFilesTool, FileSearchTool, RunPythonTool, TodoRead, TodoWrite]
17
+ loop:
18
+ max_iterations: 150 # agent.run() round-trip cap
19
+ max_format_retries: 2 # resume on MODEL_FORMAT_CRASH (bad tool-call JSON)
20
+ continue_nudges: 0 # presence-gated resumes on early stop (0 = strict)
21
+ max_rate_limit_retries: 3 # resume on provider 429/529 with backoff
22
+ max_transient_retries: 4 # resume on transient API/transport errors
@@ -0,0 +1,22 @@
1
+ # Google / Gemini (GOOGLE_API_KEY). The provider key is `google`; the file is
2
+ # named for the provider, not the model family.
3
+ name: orchestral/google
4
+ runtime:
5
+ name: orchestral
6
+ version: ">=1.3"
7
+ provider:
8
+ name: google
9
+ model: gemini-2.0-flash # default; --models overrides
10
+ max_tokens: 8192
11
+ core:
12
+ # Explicit in-process core toolset. Drop tools an arm bans (e.g. remove
13
+ # WebSearchTool when the answer is published, RunCommandTool when a shell
14
+ # would reach the ground truth).
15
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, EditFileTool,
16
+ FindFilesTool, FileSearchTool, RunPythonTool, TodoRead, TodoWrite]
17
+ loop:
18
+ max_iterations: 150 # agent.run() round-trip cap
19
+ max_format_retries: 2 # resume on MODEL_FORMAT_CRASH (bad tool-call JSON)
20
+ continue_nudges: 0 # presence-gated resumes on early stop (0 = strict)
21
+ max_rate_limit_retries: 3 # resume on provider 429/529 with backoff
22
+ max_transient_retries: 4 # resume on transient API/transport errors
@@ -0,0 +1,28 @@
1
+ # Groq direct (GROQ_API_KEY) -- fast inference for open-weight models.
2
+ # COST: orchestral prices groq itself (orchestral/llm/groq/pricing_model.py),
3
+ # so usage.cost is populated and --max-cost-usd DOES bind. The lookup is an
4
+ # EXACT string match with no 'default' fallback -- an unrecognized id silently
5
+ # costs 0.00 and the budget cap never trips. Use the fully-qualified id
6
+ # (`openai/gpt-oss-120b`, not `gpt-oss-120b`).
7
+ # The orchestral Groq client hardcodes a 30s HTTP timeout: fine for short
8
+ # tasks, too tight for harnesses whose tools make long calls.
9
+ name: orchestral/groq
10
+ runtime:
11
+ name: orchestral
12
+ version: ">=1.3"
13
+ provider:
14
+ name: groq
15
+ model: openai/gpt-oss-120b # default; --models overrides
16
+ max_tokens: 8192
17
+ core:
18
+ # Explicit in-process core toolset. Drop tools an arm bans (e.g. remove
19
+ # WebSearchTool when the answer is published, RunCommandTool when a shell
20
+ # would reach the ground truth).
21
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, EditFileTool,
22
+ FindFilesTool, FileSearchTool, RunPythonTool, TodoRead, TodoWrite]
23
+ loop:
24
+ max_iterations: 150 # agent.run() round-trip cap
25
+ max_format_retries: 2 # resume on MODEL_FORMAT_CRASH (bad tool-call JSON)
26
+ continue_nudges: 0 # presence-gated resumes on early stop (0 = strict)
27
+ max_rate_limit_retries: 3 # resume on provider 429/529 with backoff
28
+ max_transient_retries: 4 # resume on transient API/transport errors
@@ -0,0 +1,32 @@
1
+ # LiteLLM proxy (LITELLM_HOST incl. /v1, LITELLM_API_KEY) -- one route to many
2
+ # backends. COST: orchestral's openai pricing model doesn't know proxied
3
+ # open-weight ids, so cost resolves from the PRICING SNAPSHOT toolbench fetches
4
+ # from the proxy at run start. If the proxy is unreachable then, or has no
5
+ # pricing configured for the model, the run reports $0.00 and --max-cost-usd
6
+ # does not bind -- bound it with --n and cell count instead.
7
+ name: orchestral/litellm
8
+ runtime:
9
+ name: orchestral
10
+ version: ">=1.3"
11
+ provider:
12
+ name: litellm
13
+ model: gpt-oss-120b # default; --models overrides
14
+ max_tokens: 8192
15
+ # A proxy may have RESPONSE-level caching on, which would hand every trial
16
+ # of a cell the same completion -- k trials stop being independent samples
17
+ # and pass@k / pass^k collapse into reach. cache_bust appends a per-trial
18
+ # nonce to defeat it. Leave it on for any proxy route; direct providers
19
+ # don't need it (their prompt/KV caches don't affect sampling).
20
+ cache_bust: true
21
+ core:
22
+ # Explicit in-process core toolset. Drop tools an arm bans (e.g. remove
23
+ # WebSearchTool when the answer is published, RunCommandTool when a shell
24
+ # would reach the ground truth).
25
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, EditFileTool,
26
+ FindFilesTool, FileSearchTool, RunPythonTool, TodoRead, TodoWrite]
27
+ loop:
28
+ max_iterations: 150 # agent.run() round-trip cap
29
+ max_format_retries: 2 # resume on MODEL_FORMAT_CRASH (bad tool-call JSON)
30
+ continue_nudges: 0 # presence-gated resumes on early stop (0 = strict)
31
+ max_rate_limit_retries: 3 # resume on provider 429/529 with backoff
32
+ max_transient_retries: 4 # resume on transient API/transport errors
@@ -0,0 +1,22 @@
1
+ # OpenAI direct (OPENAI_API_KEY). Priced for gpt-4o / gpt-4o-mini; other
2
+ # model ids fall outside PRICING_TABLE and --max-cost-usd will not bind.
3
+ name: orchestral/openai
4
+ runtime:
5
+ name: orchestral
6
+ version: ">=1.3"
7
+ provider:
8
+ name: openai
9
+ model: gpt-4o # default; --models overrides
10
+ max_tokens: 8192
11
+ core:
12
+ # Explicit in-process core toolset. Drop tools an arm bans (e.g. remove
13
+ # WebSearchTool when the answer is published, RunCommandTool when a shell
14
+ # would reach the ground truth).
15
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, EditFileTool,
16
+ FindFilesTool, FileSearchTool, RunPythonTool, TodoRead, TodoWrite]
17
+ loop:
18
+ max_iterations: 150 # agent.run() round-trip cap
19
+ max_format_retries: 2 # resume on MODEL_FORMAT_CRASH (bad tool-call JSON)
20
+ continue_nudges: 0 # presence-gated resumes on early stop (0 = strict)
21
+ max_rate_limit_retries: 3 # resume on provider 429/529 with backoff
22
+ max_transient_retries: 4 # resume on transient API/transport errors
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "toolbench"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "A platform and CLI for building benchmarks for agentic tools and harnesses."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"