This guide starts Park Bench from a clean checkout with an empty application SQLite database.
- Docker and Docker Compose.
- Credentials for the coding provider you want to benchmark.
- Credentials for the judge provider required by that coding provider.
- A pullable Docker task image containing
/task/task-manifest.json.
For local development outside Docker you also need Python 3.12+, uv, and
Node.js 22+ for frontend work.
git clone <repository-url>
cd park-bench
cp .env.example .envEdit .env with the credentials needed by your selected provider. Common pairs:
| Coding provider | Coding credential | Judge credential |
|---|---|---|
anthropic-api |
ANTHROPIC_API_KEY |
OPENAI_API_KEY |
anthropic-max |
CLAUDE_CODE_OAUTH_TOKEN |
OPENAI_API_KEY |
openai / openai-api |
OPENAI_API_KEY |
ANTHROPIC_API_KEY |
openai-plus |
Pi auth.json entry openai-codex |
ANTHROPIC_API_KEY |
google |
GEMINI_API_KEY |
ANTHROPIC_API_KEY |
openrouter |
OPENROUTER_API_KEY |
ANTHROPIC_API_KEY |
See LLM Clients for the full provider registry and auth-json mount options.
docker compose up -d --buildOpen:
On first startup, bootstrap_database() creates schema version 14. The task-image
catalog starts empty.
Older Park Bench application schemas are not migrated by this release. Start with
a fresh data/results.db when upgrading from an older schema family.
The image must already be available to the Docker daemon used by the server. Register it in the catalog:
curl -X POST http://localhost:8080/api/task-images \
-H "Content-Type: application/json" \
-d '{
"name": "oas-generation",
"catalog_image_ref": "ghcr.io/danballance/park-bench-task-exercise-oas-generation:v1",
"enabled": true
}'Response shape:
{
"id": 1,
"name": "oas-generation",
"catalog_image_ref": "ghcr.io/danballance/park-bench-task-exercise-oas-generation:v1",
"effective_image_ref": "ghcr.io/danballance/park-bench-task-exercise-oas-generation:v1",
"enabled": true,
"created_at": "2026-04-28T12:00:00",
"updated_at": "2026-04-28T12:00:00"
}catalog_image_ref is the stored value. effective_image_ref is the value after
an optional local-registry override.
A task configuration is the immutable benchmark identity used by ad hoc runs, suites, and collections.
curl -X POST http://localhost:8080/api/task-configurations \
-H "Content-Type: application/json" \
-d '{
"name": "oas-generation-pytest",
"task_image_id": 1,
"scorers": ["pytest", "complexity"]
}'The backend checks that the task image exists and is enabled, and that the scorer list is non-empty, unique, registered, and dependency-complete. The scorer set is stored in canonical sorted order for uniqueness.
curl -X POST http://localhost:8080/api/runs/benchmark \
-H "Content-Type: application/json" \
-d '{
"task_configuration_id": 1,
"iterations": 3,
"provider": "anthropic-api",
"model": "claude-sonnet-4-6",
"llm_timeout": 7200,
"container_timeout": 7200,
"max_workers": 1
}'The endpoint returns 202 Accepted when the run is accepted for background
execution. RunLock allows only one active benchmark or suite run at a time.
Ad hoc benchmark requests do not include a reasoning field; the runtime uses
the default Pi thinking level xhigh.
A suite runs one task configuration across one or more provider/model lanes.
Provider entries can include reasoning; when omitted, Pydantic defaults it to
xhigh.
curl -X POST http://localhost:8080/api/suites \
-H "Content-Type: application/json" \
-d '{
"name": "nightly-oas-generation",
"task_configuration_id": 1,
"iterations": 3,
"max_workers": 1,
"providers": [
{"provider": "anthropic-api", "model": "claude-sonnet-4-6", "reasoning": "xhigh"},
{"provider": "openai-api", "model": "gpt-5.4"}
]
}'Trigger the suite:
curl -X POST http://localhost:8080/api/runs/suite \
-H "Content-Type: application/json" \
-d '{"suite_id": 1}'Use the UI or these API endpoints:
GET /api/batchesGET /api/batches/{batch_id}GET /api/batches/{batch_id}/iterationsGET /api/iterations/{iteration_id}GET /api/iterations/{iteration_id}/artefactsGET /api/iterations/{iteration_id}/codeGET /api/iterations/{iteration_id}/code/content?path=...
Iteration payloads include normalized scores rows of the form
{score_id, value, detail}. Aggregate stats are keyed by the same score_id
values.
curl -X POST http://localhost:8080/api/schedules \
-H "Content-Type: application/json" \
-d '{
"suite_id": 1,
"cron": "0 3 * * *",
"enabled": true
}'Enabled schedules are loaded into APScheduler during application startup and manual schedule updates update the in-memory APScheduler job list.
For local task-image development, start the registry override:
docker compose \
-f docker-compose.yaml \
-f docker-compose.local-task-registry.yaml \
up -d --buildThe override starts a registry at 127.0.0.1:5000 and sets:
TASK_IMAGE_LOCAL_REGISTRY=127.0.0.1:5000
TASK_IMAGE_LOCAL_TAG=dev
When both are set, Park Bench rewrites effective refs in memory by preserving the final repository component and replacing the registry/tag. For example:
ghcr.io/danballance/park-bench-task-exercise-oas-generation:v1
becomes:
127.0.0.1:5000/park-bench-task-exercise-oas-generation:dev
The database row is not modified.
curl -LsSf https://astral.sh/uv/install.sh | sh
uv sync
uv run pytest --cov park_bench --cov-report term-missing tests
uv run ruff check --fix park_bench
uv run ruff format park_bench
uv run python .github/scripts/check_complexity.pyFor UI work:
cd ui
npm install
npm run build