toolbench 0.0.1__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 (154) hide show
  1. {toolbench-0.0.1 → toolbench-0.2.0}/.gitignore +13 -0
  2. toolbench-0.2.0/PKG-INFO +150 -0
  3. toolbench-0.2.0/README.md +108 -0
  4. toolbench-0.2.0/examples/calculator/requirements.txt +1 -0
  5. toolbench-0.2.0/examples/calculator/toolkit.yaml +37 -0
  6. toolbench-0.2.0/examples/calculator/tools/__init__.py +4 -0
  7. toolbench-0.2.0/examples/calculator/tools/basic.py +29 -0
  8. toolbench-0.2.0/examples/calculator/tools/scientific.py +18 -0
  9. toolbench-0.2.0/examples/geometry/benchmark.yaml +47 -0
  10. toolbench-0.2.0/examples/geometry/ground_truth/answer.json +1 -0
  11. toolbench-0.2.0/examples/geometry/harnesses/orchestral/anthropic.yaml +13 -0
  12. toolbench-0.2.0/examples/geometry/harnesses/orchestral/groq.yaml +13 -0
  13. toolbench-0.2.0/examples/geometry/harnesses/orchestral/litellm.yaml +19 -0
  14. toolbench-0.2.0/examples/geometry/harnesses/orchestral/openai.yaml +13 -0
  15. toolbench-0.2.0/examples/geometry/loadouts/additive_only.yaml +6 -0
  16. toolbench-0.2.0/examples/geometry/loadouts/all_metrics.yaml +8 -0
  17. toolbench-0.2.0/examples/geometry/loadouts/calc_toolbase.yaml +8 -0
  18. toolbench-0.2.0/examples/geometry/loadouts/core_only.yaml +4 -0
  19. toolbench-0.2.0/examples/geometry/loadouts/full_local.yaml +6 -0
  20. toolbench-0.2.0/examples/geometry/loadouts/full_mixed.yaml +9 -0
  21. toolbench-0.2.0/examples/geometry/loadouts/full_toolbase.yaml +11 -0
  22. toolbench-0.2.0/examples/geometry/loadouts/guided.yaml +8 -0
  23. toolbench-0.2.0/examples/geometry/loadouts/primitives_only.yaml +5 -0
  24. toolbench-0.2.0/examples/geometry/skills/distance_recipe.md +9 -0
  25. toolbench-0.2.0/examples/geometry/tools/chebyshev.py +14 -0
  26. toolbench-0.2.0/examples/geometry/tools/dunderkit.py +43 -0
  27. toolbench-0.2.0/examples/geometry/tools/euclid.py +15 -0
  28. toolbench-0.2.0/examples/geometry/tools/extras.py +14 -0
  29. toolbench-0.2.0/examples/geometry/tools/taxicab.py +14 -0
  30. toolbench-0.2.0/examples/geometry/variants/derived/prompts/system.md +3 -0
  31. toolbench-0.2.0/examples/geometry/variants/derived/prompts/user.md +17 -0
  32. toolbench-0.2.0/examples/geometry/variants/derived/variant.yaml +17 -0
  33. toolbench-0.2.0/examples/geometry/variants/direct/prompts/system.md +3 -0
  34. toolbench-0.2.0/examples/geometry/variants/direct/prompts/user.md +12 -0
  35. toolbench-0.2.0/examples/geometry/variants/direct/sandbox/template/points.json +1 -0
  36. toolbench-0.2.0/examples/geometry/variants/direct/variant.yaml +16 -0
  37. toolbench-0.2.0/examples/geometry/variants/polar/prompts/system.md +3 -0
  38. toolbench-0.2.0/examples/geometry/variants/polar/prompts/user.md +21 -0
  39. toolbench-0.2.0/examples/geometry/variants/polar/variant.yaml +24 -0
  40. toolbench-0.2.0/harness_templates/README.md +91 -0
  41. toolbench-0.2.0/harness_templates/claude-code/README.md +54 -0
  42. toolbench-0.2.0/harness_templates/claude-code/default.yaml +20 -0
  43. toolbench-0.2.0/harness_templates/codex/README.md +43 -0
  44. toolbench-0.2.0/harness_templates/codex/default.yaml +18 -0
  45. toolbench-0.2.0/harness_templates/orchestral/README.md +49 -0
  46. toolbench-0.2.0/harness_templates/orchestral/anthropic.yaml +22 -0
  47. toolbench-0.2.0/harness_templates/orchestral/google.yaml +22 -0
  48. toolbench-0.2.0/harness_templates/orchestral/groq.yaml +28 -0
  49. toolbench-0.2.0/harness_templates/orchestral/litellm.yaml +32 -0
  50. toolbench-0.2.0/harness_templates/orchestral/openai.yaml +22 -0
  51. toolbench-0.2.0/mkdocs.yml +97 -0
  52. toolbench-0.2.0/pyproject.toml +72 -0
  53. toolbench-0.2.0/tests/__init__.py +0 -0
  54. toolbench-0.2.0/tests/helpers.py +12 -0
  55. toolbench-0.2.0/tests/test_artifact_cleanup.py +82 -0
  56. toolbench-0.2.0/tests/test_artifact_policy.py +86 -0
  57. toolbench-0.2.0/tests/test_benchmark_extends.py +250 -0
  58. toolbench-0.2.0/tests/test_checks.py +158 -0
  59. toolbench-0.2.0/tests/test_cli_runtime_stderr_drain.py +89 -0
  60. toolbench-0.2.0/tests/test_codex_runtime.py +136 -0
  61. toolbench-0.2.0/tests/test_content_checks.py +309 -0
  62. toolbench-0.2.0/tests/test_cost_extraction.py +92 -0
  63. toolbench-0.2.0/tests/test_crash_classifier.py +219 -0
  64. toolbench-0.2.0/tests/test_geometry.py +71 -0
  65. toolbench-0.2.0/tests/test_integrity.py +80 -0
  66. toolbench-0.2.0/tests/test_json_repair.py +113 -0
  67. toolbench-0.2.0/tests/test_judge_score.py +157 -0
  68. toolbench-0.2.0/tests/test_judge_select.py +137 -0
  69. toolbench-0.2.0/tests/test_mcp_source.py +134 -0
  70. toolbench-0.2.0/tests/test_metrics.py +262 -0
  71. toolbench-0.2.0/tests/test_numeric_checks.py +151 -0
  72. toolbench-0.2.0/tests/test_paired_deltas.py +82 -0
  73. toolbench-0.2.0/tests/test_resume.py +129 -0
  74. toolbench-0.2.0/tests/test_rubric_loader.py +32 -0
  75. toolbench-0.2.0/tests/test_runner_llm_judge.py +126 -0
  76. toolbench-0.2.0/tests/test_runner_rate_limit.py +106 -0
  77. toolbench-0.2.0/tests/test_runner_request_params.py +125 -0
  78. toolbench-0.2.0/tests/test_runner_retry.py +221 -0
  79. toolbench-0.2.0/tests/test_runner_transient.py +108 -0
  80. toolbench-0.2.0/tests/test_runtime_registry.py +95 -0
  81. toolbench-0.2.0/tests/test_runtime_version_label.py +11 -0
  82. toolbench-0.2.0/tests/test_schema.py +82 -0
  83. toolbench-0.2.0/tests/test_skills.py +136 -0
  84. toolbench-0.2.0/tests/test_summary_text_cost.py +36 -0
  85. toolbench-0.2.0/tests/test_tool_error_visibility.py +80 -0
  86. toolbench-0.2.0/tests/test_tool_resolver.py +83 -0
  87. toolbench-0.2.0/tests/test_toolbase_sandbox_scoping.py +105 -0
  88. toolbench-0.2.0/tests/test_toolbase_select_provenance.py +117 -0
  89. toolbench-0.2.0/tests/test_trial_loop.py +140 -0
  90. toolbench-0.2.0/tests/test_variant.py +39 -0
  91. toolbench-0.2.0/toolbench/__init__.py +15 -0
  92. toolbench-0.2.0/toolbench/cli.py +2010 -0
  93. toolbench-0.2.0/toolbench/core/__init__.py +0 -0
  94. toolbench-0.2.0/toolbench/core/artifact_policy.py +109 -0
  95. toolbench-0.2.0/toolbench/core/benchmark.py +190 -0
  96. toolbench-0.2.0/toolbench/core/budget.py +52 -0
  97. toolbench-0.2.0/toolbench/core/checks.py +211 -0
  98. toolbench-0.2.0/toolbench/core/content_checks.py +395 -0
  99. toolbench-0.2.0/toolbench/core/crash_classifier.py +280 -0
  100. toolbench-0.2.0/toolbench/core/failure_modes.py +94 -0
  101. toolbench-0.2.0/toolbench/core/harness.py +152 -0
  102. toolbench-0.2.0/toolbench/core/integrity.py +94 -0
  103. toolbench-0.2.0/toolbench/core/json_repair.py +201 -0
  104. toolbench-0.2.0/toolbench/core/judge.py +426 -0
  105. toolbench-0.2.0/toolbench/core/judge_select.py +252 -0
  106. toolbench-0.2.0/toolbench/core/litellm_pricing.py +126 -0
  107. toolbench-0.2.0/toolbench/core/llm_factory.py +289 -0
  108. toolbench-0.2.0/toolbench/core/loadout.py +140 -0
  109. toolbench-0.2.0/toolbench/core/metrics.py +345 -0
  110. toolbench-0.2.0/toolbench/core/numeric_checks.py +195 -0
  111. toolbench-0.2.0/toolbench/core/runner.py +1177 -0
  112. toolbench-0.2.0/toolbench/core/runtime.py +1011 -0
  113. toolbench-0.2.0/toolbench/core/skills.py +110 -0
  114. toolbench-0.2.0/toolbench/core/store.py +52 -0
  115. toolbench-0.2.0/toolbench/core/task.py +148 -0
  116. toolbench-0.2.0/toolbench/core/tool_policy.py +44 -0
  117. toolbench-0.2.0/toolbench/core/tool_resolver.py +622 -0
  118. toolbench-0.2.0/toolbench/core/trajectory.py +316 -0
  119. toolbench-0.2.0/toolbench/core/variant.py +147 -0
  120. toolbench-0.2.0/toolbench/reporting/__init__.py +0 -0
  121. toolbench-0.2.0/toolbench/reporting/_output.py +53 -0
  122. toolbench-0.2.0/toolbench/reporting/_shared.py +135 -0
  123. toolbench-0.2.0/toolbench/reporting/audit_log.py +250 -0
  124. toolbench-0.2.0/toolbench/reporting/k_sweep.py +299 -0
  125. toolbench-0.2.0/toolbench/reporting/parallel_coords.py +319 -0
  126. toolbench-0.2.0/toolbench/reporting/per_stage_k.py +352 -0
  127. toolbench-0.2.0/toolbench/reporting/plot_overview.py +363 -0
  128. toolbench-0.2.0/toolbench/reporting/summary_text.py +375 -0
  129. toolbench-0.2.0/toolbench/reporting/transcript.py +406 -0
  130. toolbench-0.2.0/userdocs/authoring/custom-tools.md +94 -0
  131. toolbench-0.2.0/userdocs/authoring/define-a-benchmark.md +122 -0
  132. toolbench-0.2.0/userdocs/authoring/overview.md +98 -0
  133. toolbench-0.2.0/userdocs/authoring/rubrics-and-checks.md +240 -0
  134. toolbench-0.2.0/userdocs/explanation.md +106 -0
  135. toolbench-0.2.0/userdocs/guides/harnesses.md +147 -0
  136. toolbench-0.2.0/userdocs/guides/install-and-quickstart.md +70 -0
  137. toolbench-0.2.0/userdocs/guides/loadouts.md +150 -0
  138. toolbench-0.2.0/userdocs/guides/reading-results.md +189 -0
  139. toolbench-0.2.0/userdocs/guides/running-a-benchmark.md +151 -0
  140. toolbench-0.2.0/userdocs/guides/toolbase.md +118 -0
  141. toolbench-0.2.0/userdocs/guides/variants.md +67 -0
  142. toolbench-0.2.0/userdocs/index.md +88 -0
  143. toolbench-0.2.0/userdocs/reference/commands.md +88 -0
  144. toolbench-0.2.0/userdocs/reference/files-and-layout.md +122 -0
  145. toolbench-0.2.0/userdocs/reference/metrics.md +164 -0
  146. toolbench-0.2.0/userdocs/reference/schemas.md +247 -0
  147. toolbench-0.2.0/userdocs/reference/vocabulary.md +145 -0
  148. toolbench-0.2.0/userdocs/stylesheets/extra.css +78 -0
  149. toolbench-0.2.0/userdocs/troubleshooting.md +118 -0
  150. toolbench-0.0.1/PKG-INFO +0 -46
  151. toolbench-0.0.1/README.md +0 -22
  152. toolbench-0.0.1/pyproject.toml +0 -35
  153. toolbench-0.0.1/src/toolbench/__init__.py +0 -5
  154. {toolbench-0.0.1 → toolbench-0.2.0}/LICENSE +0 -0
@@ -41,9 +41,22 @@ ENV/
41
41
  htmlcov/
42
42
  .hypothesis/
43
43
 
44
+ # Claude Code local state
45
+ .claude/
46
+
44
47
  # IDE
45
48
  .vscode/
46
49
  .idea/
47
50
  *.swp
48
51
  *.swo
49
52
  .DS_Store
53
+
54
+ # local handoff / scratch — not for commit
55
+ PORT_STATUS.md
56
+ *.orig
57
+
58
+ # built docs
59
+ site/
60
+
61
+ # run output
62
+ runs/
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: toolbench
3
+ Version: 0.2.0
4
+ Summary: A platform and CLI for building benchmarks for agentic tools and harnesses.
5
+ Project-URL: Homepage, https://github.com/tonymenzo/toolbench
6
+ Project-URL: Repository, https://github.com/tonymenzo/toolbench
7
+ Project-URL: Issues, https://github.com/tonymenzo/toolbench/issues
8
+ Author: Tony Menzo
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,ai,benchmark,evaluation,llm,orchestral,tools
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: click>=8.0
23
+ Requires-Dist: matplotlib>=3.10.8
24
+ Requires-Dist: numpy>=2.3.5
25
+ Requires-Dist: orchestral-ai>=1.6.3
26
+ Requires-Dist: pyyaml>=6.0.3
27
+ Requires-Dist: requests>=2.32.0
28
+ Requires-Dist: rich>=14.2.0
29
+ Requires-Dist: scipy>=1.16.3
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.6; extra == 'dev'
33
+ Provides-Extra: docs
34
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
35
+ Provides-Extra: dotenv
36
+ Requires-Dist: python-dotenv>=1.2.1; extra == 'dotenv'
37
+ Provides-Extra: mcp
38
+ Requires-Dist: mcp>=1.0; extra == 'mcp'
39
+ Provides-Extra: toolbase
40
+ Requires-Dist: toolbase>=0.3.0; extra == 'toolbase'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # toolbench
44
+
45
+ **toolbench is a platform and CLI for building benchmarks for agentic tools and harnesses.**
46
+ Compose a benchmark (a task + a grading rubric) with a *harness* (the agent runtime), a
47
+ *loadout* (the tools the agent is given), and a *variant* (the prompt + sandbox), run N
48
+ trials per cell against a model, and get reach / pass@k / pass^k metrics with plots.
49
+
50
+ It is the benchmarking sibling of [toolbase](https://github.com/alexr314/toolbase) — the
51
+ package manager and runtime for agent tools. toolbase serves the tools; toolbench measures
52
+ how well an agent uses them. Together they close the loop for agentic tool and harness
53
+ development.
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install toolbench # core framework
59
+ pip install 'toolbench[toolbase]' # + resolve tools from toolbase profiles
60
+ ```
61
+
62
+ Requires Python ≥ 3.12. The CLI is available as both `toolbench` and the short alias `tbe`.
63
+
64
+ ## Quickstart
65
+
66
+ The example `geometry` benchmark (Euclidean distance + midpoint between two 2-D points) is a
67
+ self-contained, dependency-free example that exercises the whole framework.
68
+
69
+ ```bash
70
+ # Validate the wiring with no LLM calls or cost:
71
+ toolbench run --benchmark examples/geometry --model stub \
72
+ --loadouts full_local --n 1 --max-cost-usd 0 --dry-run
73
+
74
+ # A real run (a few cheap trials):
75
+ toolbench run --benchmark examples/geometry --model claude-haiku-4-5 \
76
+ --loadouts core_only,full_local --n 3 --max-cost-usd 0.50
77
+ ```
78
+
79
+ Each run writes a directory under `runs/<run_id>/` with a `manifest.json`, per-trial
80
+ transcripts and artifacts, an aggregated `summary.json` / `summary.txt`, and headline plots
81
+ (k-sweep, parallel-coordinates, per-stage breakdown).
82
+
83
+ ## Concepts
84
+
85
+ A benchmark lives in `examples/<name>/` and is composed from four declarative
86
+ axes — vary any of them on the command line to run an ablation:
87
+
88
+ | Concept | What it is | Where it lives |
89
+ |---------------|----------------------------------------------------------------------|-------------------------|
90
+ | **Benchmark** | The task + grading rubric + ground truth. | `benchmark.yaml` |
91
+ | **Harness** | The agent runtime (`orchestral`, `claude_code`, `codex`), provider, core tools, and loop policy. | `harnesses/*.yaml` |
92
+ | **Loadout** | The domain tools the agent gets (beyond the harness core). | `loadouts/*.yaml` |
93
+ | **Variant** | The prompt + sandbox seed (scaffolding axis), orthogonal to tools. | `variants/<name>/` |
94
+ | **Rubric** | Ordered, weighted stages of checks; trial score = weighted reach. | inside `benchmark.yaml` |
95
+
96
+ A **loadout source** is one of:
97
+
98
+ - `python:` — import a module exposing `TOOLS` / `make_tools()` (the no-dependency escape hatch),
99
+ - `toolbase:` — resolve tools from a [toolbase](https://github.com/alexr314/toolbase) profile,
100
+ in-process (requires `toolbench[toolbase]`); served toolkit versions are recorded in the
101
+ run manifest as reproducibility provenance, or
102
+ - `mcp:` — serve any MCP server's tools, stdio or HTTP (requires `toolbench[mcp]`):
103
+
104
+ ```yaml
105
+ tools:
106
+ sources:
107
+ - toolbase: { profile: my-profile }
108
+ select: [calculator__add] # optional: ablate within the profile
109
+ - mcp: { url: "https://host/mcp", headers: { Authorization: "Bearer ${TOK}" } }
110
+ ```
111
+
112
+ ## Metrics
113
+
114
+ For each (model × condition) cell over `k` trials:
115
+
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
+
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.
124
+
125
+ ## Commands
126
+
127
+ | Command | What it does |
128
+ |--------------------|----------------------------------------------------------------------|
129
+ | `toolbench run` | Run a benchmark across the harness × loadout × variant × model grid. |
130
+ | `toolbench resume` | Resume an interrupted run; run only the seeds not yet completed. |
131
+ | `toolbench regrade`| Re-judge a finished run's preserved artifacts after a rubric change. |
132
+
133
+ Run `toolbench --help` (or `tbe --help`) for the full reference.
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
+
148
+ ## License
149
+
150
+ MIT
@@ -0,0 +1,108 @@
1
+ # toolbench
2
+
3
+ **toolbench is a platform and CLI for building benchmarks for agentic tools and harnesses.**
4
+ Compose a benchmark (a task + a grading rubric) with a *harness* (the agent runtime), a
5
+ *loadout* (the tools the agent is given), and a *variant* (the prompt + sandbox), run N
6
+ trials per cell against a model, and get reach / pass@k / pass^k metrics with plots.
7
+
8
+ It is the benchmarking sibling of [toolbase](https://github.com/alexr314/toolbase) — the
9
+ package manager and runtime for agent tools. toolbase serves the tools; toolbench measures
10
+ how well an agent uses them. Together they close the loop for agentic tool and harness
11
+ development.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install toolbench # core framework
17
+ pip install 'toolbench[toolbase]' # + resolve tools from toolbase profiles
18
+ ```
19
+
20
+ Requires Python ≥ 3.12. The CLI is available as both `toolbench` and the short alias `tbe`.
21
+
22
+ ## Quickstart
23
+
24
+ The example `geometry` benchmark (Euclidean distance + midpoint between two 2-D points) is a
25
+ self-contained, dependency-free example that exercises the whole framework.
26
+
27
+ ```bash
28
+ # Validate the wiring with no LLM calls or cost:
29
+ toolbench run --benchmark examples/geometry --model stub \
30
+ --loadouts full_local --n 1 --max-cost-usd 0 --dry-run
31
+
32
+ # A real run (a few cheap trials):
33
+ toolbench run --benchmark examples/geometry --model claude-haiku-4-5 \
34
+ --loadouts core_only,full_local --n 3 --max-cost-usd 0.50
35
+ ```
36
+
37
+ Each run writes a directory under `runs/<run_id>/` with a `manifest.json`, per-trial
38
+ transcripts and artifacts, an aggregated `summary.json` / `summary.txt`, and headline plots
39
+ (k-sweep, parallel-coordinates, per-stage breakdown).
40
+
41
+ ## Concepts
42
+
43
+ A benchmark lives in `examples/<name>/` and is composed from four declarative
44
+ axes — vary any of them on the command line to run an ablation:
45
+
46
+ | Concept | What it is | Where it lives |
47
+ |---------------|----------------------------------------------------------------------|-------------------------|
48
+ | **Benchmark** | The task + grading rubric + ground truth. | `benchmark.yaml` |
49
+ | **Harness** | The agent runtime (`orchestral`, `claude_code`, `codex`), provider, core tools, and loop policy. | `harnesses/*.yaml` |
50
+ | **Loadout** | The domain tools the agent gets (beyond the harness core). | `loadouts/*.yaml` |
51
+ | **Variant** | The prompt + sandbox seed (scaffolding axis), orthogonal to tools. | `variants/<name>/` |
52
+ | **Rubric** | Ordered, weighted stages of checks; trial score = weighted reach. | inside `benchmark.yaml` |
53
+
54
+ A **loadout source** is one of:
55
+
56
+ - `python:` — import a module exposing `TOOLS` / `make_tools()` (the no-dependency escape hatch),
57
+ - `toolbase:` — resolve tools from a [toolbase](https://github.com/alexr314/toolbase) profile,
58
+ in-process (requires `toolbench[toolbase]`); served toolkit versions are recorded in the
59
+ run manifest as reproducibility provenance, or
60
+ - `mcp:` — serve any MCP server's tools, stdio or HTTP (requires `toolbench[mcp]`):
61
+
62
+ ```yaml
63
+ tools:
64
+ sources:
65
+ - toolbase: { profile: my-profile }
66
+ select: [calculator__add] # optional: ablate within the profile
67
+ - mcp: { url: "https://host/mcp", headers: { Authorization: "Bearer ${TOK}" } }
68
+ ```
69
+
70
+ ## Metrics
71
+
72
+ For each (model × condition) cell over `k` trials:
73
+
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
+
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.
82
+
83
+ ## Commands
84
+
85
+ | Command | What it does |
86
+ |--------------------|----------------------------------------------------------------------|
87
+ | `toolbench run` | Run a benchmark across the harness × loadout × variant × model grid. |
88
+ | `toolbench resume` | Resume an interrupted run; run only the seeds not yet completed. |
89
+ | `toolbench regrade`| Re-judge a finished run's preserved artifacts after a rubric change. |
90
+
91
+ Run `toolbench --help` (or `tbe --help`) for the full reference.
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
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1 @@
1
+ orchestral-ai>=1.3.0
@@ -0,0 +1,37 @@
1
+ name: calculator
2
+ version: 1.0.0
3
+ description: A small calculator toolkit — basic arithmetic plus a scientific bundle.
4
+ author: toolbench examples
5
+ license: MIT
6
+ category: utils
7
+ python_version: "3.12"
8
+
9
+ bundles:
10
+ basic: {}
11
+ scientific: {}
12
+
13
+ tools:
14
+ - name: add
15
+ function: tools.basic.add
16
+ description: Add two numbers.
17
+ bundle: basic
18
+ - name: subtract
19
+ function: tools.basic.subtract
20
+ description: Subtract b from a.
21
+ bundle: basic
22
+ - name: multiply
23
+ function: tools.basic.multiply
24
+ description: Multiply two numbers.
25
+ bundle: basic
26
+ - name: divide
27
+ function: tools.basic.divide
28
+ description: Divide a by b.
29
+ bundle: basic
30
+ - name: power
31
+ function: tools.scientific.power
32
+ description: Raise base to the given exponent (use exp=0.5 for square root).
33
+ bundle: scientific
34
+ - name: sqrt
35
+ function: tools.scientific.sqrt
36
+ description: Square root of a number.
37
+ bundle: scientific
@@ -0,0 +1,4 @@
1
+ from .basic import add, subtract, multiply, divide
2
+ from .scientific import power, sqrt
3
+
4
+ __all__ = ["add", "subtract", "multiply", "divide", "power", "sqrt"]
@@ -0,0 +1,29 @@
1
+ """Basic arithmetic tools for the calculator example toolkit."""
2
+
3
+ import json
4
+
5
+ from orchestral import define_tool
6
+
7
+
8
+ @define_tool
9
+ def add(a: float, b: float) -> str:
10
+ """Add two numbers."""
11
+ return json.dumps({"result": float(a) + float(b)})
12
+
13
+
14
+ @define_tool
15
+ def subtract(a: float, b: float) -> str:
16
+ """Subtract b from a."""
17
+ return json.dumps({"result": float(a) - float(b)})
18
+
19
+
20
+ @define_tool
21
+ def multiply(a: float, b: float) -> str:
22
+ """Multiply two numbers."""
23
+ return json.dumps({"result": float(a) * float(b)})
24
+
25
+
26
+ @define_tool
27
+ def divide(a: float, b: float) -> str:
28
+ """Divide a by b."""
29
+ return json.dumps({"result": float(a) / float(b)})
@@ -0,0 +1,18 @@
1
+ """Scientific tools for the calculator example toolkit."""
2
+
3
+ import json
4
+ import math
5
+
6
+ from orchestral import define_tool
7
+
8
+
9
+ @define_tool
10
+ def power(base: float, exp: float) -> str:
11
+ """Raise base to the given exponent (use exp=0.5 for a square root)."""
12
+ return json.dumps({"result": float(base) ** float(exp)})
13
+
14
+
15
+ @define_tool
16
+ def sqrt(x: float) -> str:
17
+ """Square root of a number."""
18
+ return json.dumps({"result": math.sqrt(float(x))})
@@ -0,0 +1,47 @@
1
+ name: geometry
2
+ version: 0.1.0
3
+ description: Euclidean distance and midpoint between two 2-D points.
4
+
5
+ default_harness: orchestral/anthropic
6
+ default_loadout: full_local
7
+ # Three variants (direct/derived/polar) — see variants/. All three
8
+ # share the same ground truth (distance 5.0, midpoint [1.5, 2.0]) so
9
+ # the family-level rubric grades them identically and cross-variant
10
+ # reach deltas are meaningful (and not contaminated by reference-value
11
+ # differences). Names describe what each variant supplies, not a
12
+ # presumed difficulty ordering.
13
+ default_variant: direct
14
+
15
+ ground_truth:
16
+ dir: ./ground_truth
17
+
18
+ rubric:
19
+ type: stagewise # ordered stages; trial score = prefix product
20
+ stages:
21
+ - id: answer_written
22
+ description: output/answer.json exists with the required keys
23
+ weight: 0.2
24
+ checks:
25
+ - json_with_keys:
26
+ file: output/answer.json
27
+ required_keys: [distance, midpoint]
28
+ - id: midpoint_correct
29
+ description: midpoint is within 1% of ground truth
30
+ weight: 0.3
31
+ checks:
32
+ - close_to:
33
+ file: output/answer.json
34
+ field: midpoint
35
+ reference: ./ground_truth/answer.json
36
+ tolerance_frac: 0.01
37
+ expected_tool_calls: [add, divide] # optional diagnostic, not graded
38
+ - id: distance_correct
39
+ description: Euclidean distance is within 1% of ground truth
40
+ weight: 0.5
41
+ checks:
42
+ - close_to:
43
+ file: output/answer.json
44
+ field: distance
45
+ reference: ./ground_truth/answer.json
46
+ tolerance_frac: 0.01
47
+ expected_tool_calls: [euclidean_distance]
@@ -0,0 +1 @@
1
+ {"distance": 5.0, "midpoint": [1.5, 2.0]}
@@ -0,0 +1,13 @@
1
+ name: orchestral/anthropic
2
+ runtime:
3
+ name: orchestral
4
+ version: ">=1.3"
5
+ provider:
6
+ name: anthropic
7
+ max_tokens: 8192
8
+ core:
9
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, RunPythonTool, TodoWrite]
10
+ loop:
11
+ max_iterations: 150 # agent.run() round-trip cap (consumed by the runner)
12
+ max_format_retries: 2 # resume the session on a MODEL_FORMAT_CRASH (bad tool-call JSON)
13
+ continue_nudges: 0 # presence-gated resumes when the model stops early (0 = strict autonomy)
@@ -0,0 +1,13 @@
1
+ name: orchestral/groq
2
+ runtime:
3
+ name: orchestral
4
+ version: ">=1.3"
5
+ provider:
6
+ name: groq
7
+ max_tokens: 8192
8
+ core:
9
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, RunPythonTool, TodoWrite]
10
+ loop:
11
+ max_iterations: 150 # agent.run() round-trip cap (consumed by the runner)
12
+ max_format_retries: 2 # resume the session on a MODEL_FORMAT_CRASH (bad tool-call JSON)
13
+ continue_nudges: 0 # presence-gated resumes when the model stops early (0 = strict autonomy)
@@ -0,0 +1,19 @@
1
+ name: orchestral/litellm
2
+ runtime:
3
+ name: orchestral
4
+ version: ">=1.3"
5
+ provider:
6
+ name: litellm # routes via the LiteLLM proxy (LITELLM_HOST / LITELLM_API_KEY)
7
+ max_tokens: 8192
8
+ # A LiteLLM proxy may have *response-level* caching enabled, which would
9
+ # hand every trial of a cell the same completion (k trials stop being
10
+ # independent samples). cache_bust appends a per-trial nonce to the
11
+ # prompt to defeat it. Off by default for direct providers: their
12
+ # prompt/KV caches don't affect sampling independence.
13
+ cache_bust: true
14
+ core:
15
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, RunPythonTool, TodoWrite]
16
+ loop:
17
+ max_iterations: 150 # agent.run() round-trip cap (consumed by the runner)
18
+ max_format_retries: 2 # resume the session on a MODEL_FORMAT_CRASH (bad tool-call JSON)
19
+ continue_nudges: 0 # presence-gated resumes when the model stops early (0 = strict autonomy)
@@ -0,0 +1,13 @@
1
+ name: orchestral/openai
2
+ runtime:
3
+ name: orchestral
4
+ version: ">=1.3"
5
+ provider:
6
+ name: openai
7
+ max_tokens: 8192
8
+ core:
9
+ tools: [RunCommandTool, WriteFileTool, ReadFileTool, RunPythonTool, TodoWrite]
10
+ loop:
11
+ max_iterations: 150 # agent.run() round-trip cap (consumed by the runner)
12
+ max_format_retries: 2 # resume the session on a MODEL_FORMAT_CRASH (bad tool-call JSON)
13
+ continue_nudges: 0 # presence-gated resumes when the model stops early (0 = strict autonomy)
@@ -0,0 +1,6 @@
1
+ name: additive_only
2
+ tools:
3
+ sources:
4
+ - python: tools/dunderkit.py
5
+ select: [additive] # only add + subtract (no divide, no power)
6
+ skills: []
@@ -0,0 +1,8 @@
1
+ name: all_metrics
2
+ tools:
3
+ sources:
4
+ - python: tools/dunderkit.py # for the midpoint
5
+ - python: tools/euclid.py # correct metric for this task -> 5.0
6
+ - python: tools/taxicab.py # L1 -> 7.0 (wrong)
7
+ - python: tools/chebyshev.py # L-inf -> 4.0 (wrong)
8
+ skills: []
@@ -0,0 +1,8 @@
1
+ name: calc_toolbase
2
+ # Resolve tools from a toolbase profile (the `calculator` example toolkit).
3
+ # Requires: toolbench[toolbase] installed, and the calculator toolkit installed
4
+ # + activated into the named profile (see docs/guides/toolbase.md).
5
+ tools:
6
+ sources:
7
+ - toolbase: { profile: default }
8
+ skills: []
@@ -0,0 +1,4 @@
1
+ name: core_only
2
+ tools:
3
+ sources: [] # empty toolkit; the agent has only the harness's core tools
4
+ skills: []
@@ -0,0 +1,6 @@
1
+ name: full_local
2
+ tools:
3
+ sources:
4
+ - python: tools/dunderkit.py # all bundles (no `select:` = all)
5
+ - python: tools/euclid.py
6
+ skills: []
@@ -0,0 +1,9 @@
1
+ name: full_mixed
2
+ tools:
3
+ sources:
4
+ - python: tools/dunderkit.py # local (escape hatch)
5
+ - toolbase: # frozen, via toolbase
6
+ toolsets:
7
+ euclid:
8
+ version: "==1.0.0"
9
+ skills: []
@@ -0,0 +1,11 @@
1
+ name: full_toolbase
2
+ tools:
3
+ sources:
4
+ - toolbase: # mirrors a serve.yaml toolkit block
5
+ toolsets:
6
+ dunderkit:
7
+ version: ">=1.0,<2.0"
8
+ bundles: [additive, multiplicative, powers]
9
+ euclid:
10
+ version: "==1.0.0"
11
+ skills: []
@@ -0,0 +1,8 @@
1
+ name: guided
2
+ tools:
3
+ sources:
4
+ - python: tools/dunderkit.py # NOTE: no euclid toolset; the skill teaches assembly
5
+ skills:
6
+ - name: distance_recipe
7
+ file: ./skills/distance_recipe.md
8
+ mode: on_demand
@@ -0,0 +1,5 @@
1
+ name: primitives_only
2
+ tools:
3
+ sources:
4
+ - python: tools/dunderkit.py # all dunder primitives, no metric tool
5
+ skills: []
@@ -0,0 +1,9 @@
1
+ # Distance + midpoint
2
+
3
+ Given two points `p1 = [x1, y1]` and `p2 = [x2, y2]`:
4
+
5
+ 1. `dx = subtract(x2, x1)`, `dy = subtract(y2, y1)`
6
+ 2. `distance = power( add(power(dx, 2), power(dy, 2)), 0.5 )`
7
+ 3. `midpoint = [ divide(add(x1, x2), 2), divide(add(y1, y2), 2) ]`
8
+
9
+ Write the result to `output/answer.json` as `{"distance": ..., "midpoint": [...]}`.
@@ -0,0 +1,14 @@
1
+ """The Chebyshev (L-infinity) distance metric as its own one-tool toolset."""
2
+
3
+ from orchestral.tools import define_tool
4
+
5
+
6
+ @define_tool
7
+ def chebyshev_distance(p1: list[float], p2: list[float]) -> dict:
8
+ """L-infinity distance: max |a_i - b_i|."""
9
+ return {"result": max(abs(float.__sub__(float(a), float(b)))
10
+ for a, b in zip(p1, p2))}
11
+
12
+
13
+ TOOLS = [chebyshev_distance]
14
+ BUNDLES = {"metric": [chebyshev_distance]}
@@ -0,0 +1,43 @@
1
+ """Arithmetic primitives, each tool's body a single Python dunder call."""
2
+
3
+ from orchestral.tools import define_tool
4
+
5
+
6
+ @define_tool
7
+ def add(a: float, b: float) -> dict:
8
+ """a + b, via float.__add__."""
9
+ return {"result": float.__add__(float(a), float(b))}
10
+
11
+
12
+ @define_tool
13
+ def subtract(a: float, b: float) -> dict:
14
+ """a - b, via float.__sub__."""
15
+ return {"result": float.__sub__(float(a), float(b))}
16
+
17
+
18
+ @define_tool
19
+ def multiply(a: float, b: float) -> dict:
20
+ """a * b, via float.__mul__."""
21
+ return {"result": float.__mul__(float(a), float(b))}
22
+
23
+
24
+ @define_tool
25
+ def divide(a: float, b: float) -> dict:
26
+ """a / b, via float.__truediv__."""
27
+ return {"result": float.__truediv__(float(a), float(b))}
28
+
29
+
30
+ @define_tool
31
+ def power(base: float, exp: float) -> dict:
32
+ """base ** exp, via float.__pow__. (sqrt = power(x, 0.5))"""
33
+ return {"result": float.__pow__(float(base), float(exp))}
34
+
35
+
36
+ TOOLS = [add, subtract, multiply, divide, power] # discovery entrypoint (all tools)
37
+
38
+ # BUNDLES — author-declared groupings within this toolset (a toolbase "bundle").
39
+ BUNDLES = {
40
+ "additive": [add, subtract],
41
+ "multiplicative": [multiply, divide],
42
+ "powers": [power],
43
+ }
@@ -0,0 +1,15 @@
1
+ """The Euclidean (L2) distance metric as its own one-tool toolset."""
2
+
3
+ from orchestral.tools import define_tool
4
+
5
+
6
+ @define_tool
7
+ def euclidean_distance(p1: list[float], p2: list[float]) -> dict:
8
+ """L2 distance: sqrt(sum (a_i - b_i)^2)."""
9
+ s = sum(float.__pow__(float.__sub__(float(a), float(b)), 2.0)
10
+ for a, b in zip(p1, p2))
11
+ return {"result": float.__pow__(s, 0.5)}
12
+
13
+
14
+ TOOLS = [euclidean_distance]
15
+ BUNDLES = {"metric": [euclidean_distance]}