Configuration reference
Crucible reads its configuration from crucible.toml in the working directory. You can pass --config <path> to any command to use a different file. Running crucible init writes a complete example you can edit in place.
This page documents every field. Defaults are shown in parentheses.
A complete example
[server]
web_port = 8877
web_host = "127.0.0.1" # use "0.0.0.0" in Docker
# Required when web_host is not loopback. Generate with: openssl rand -hex 32
# admin_token = "paste-generated-token-here"
[testing]
concurrency = 4
max_games = 10000
hash_mb = 16
engine_threads = 1
poll_interval_seconds = 60
[testing.time_control]
base_ms = 10000
increment_ms = 100
[testing.sprt]
elo0 = 0.0
elo1 = 5.0
alpha = 0.05
beta = 0.05
min_games = 16
[training]
output_dir = ".crucible/training"
selfplay_games = 100
collect_from_tests = true
selfplay_depth = 10
regression_min_depth = 10
idle_selfplay = false
idle_batch_games = 1
[[gate.opponents]]
name = "Stockfish"
binary_path = "/opt/engines/stockfish"
[[gate.opponents]]
name = "Ethereal"
binary_path = "/opt/engines/ethereal"
[[gate.profiles]]
name = "release"
opponents = ["Stockfish", "Ethereal"]
games_per_opponent = 100
min_score_delta = 0.0
[[engines]]
name = "my-engine"
repo = "https://github.com/you/your-engine"
branches = ["main", "release/*"]
experimental_branches = ["exp/*"]
build_cmd = "make"
binary_path = "my-engine"
start_from = "v1.0.0"
Top-level
| Field | Default | Notes |
|---|---|---|
data_dir |
./.crucible |
Where the SQLite database, cloned repositories, and build artifacts live. |
[server]
| Field | Default | Notes |
|---|---|---|
web_host |
127.0.0.1 |
Interface the dashboard binds to. Use 0.0.0.0 when running in Docker. |
web_port |
8877 |
Port the dashboard listens on. |
admin_token |
unset | Required when web_host is not loopback. The dashboard sends it as a Bearer token on protected API requests. |
Crucible refuses to start with common placeholder tokens such as changeme, change-me, or paste-generated-token-here. The browser client stores the admin token in local storage. Clear the tab data, or use a fresh browser profile, if you want to reset it.
[testing]
| Field | Default | Notes |
|---|---|---|
concurrency |
1 |
Number of test jobs that run in parallel. |
max_games |
10000 |
Maximum games per SPRT match before the test gives up as inconclusive. |
hash_mb |
16 |
Hash table size passed to every engine instance (setoption name Hash value ...). |
engine_threads |
1 |
Threads per engine instance (setoption name Threads value ...). |
poll_interval_seconds |
60 |
How often the daemon fetches new commits and schedules fresh jobs. |
opening_book |
unset | Path to an EPD opening suite containing one position per line (see below). |
The opening book is a plain text EPD file. Each non-empty, non-comment line must contain an EPD position: the first four FEN fields, followed by optional semicolon-terminated EPD operations. Lines beginning with # are ignored. Crucible honors hmvc and fmvn operations when present, defaulting them to 0 and 1 otherwise. Other EPD operations such as bm and id are accepted as metadata but are not used by the match runner.
# openings/stc.epd
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -
rnbqkbnr/ppp2ppp/4p3/3p4/3PP3/8/PPP2PPP/RNBQKBNR w KQkq d6 hmvc 0; fmvn 3; id "QGD";
Use an explicit starting-position EPD instead of the old startpos literal. Full six-field FEN lines are not valid opening-book entries.
[testing.time_control]
| Field | Default | Notes |
|---|---|---|
base_ms |
10000 |
Base time in milliseconds (10 seconds by default). |
increment_ms |
100 |
Increment per move in milliseconds. |
nodes |
unset | If set, the match uses a fixed nodes-per-move limit instead of the clock. |
[testing.sprt]
| Field | Default | Notes |
|---|---|---|
elo0 |
0.0 |
Null hypothesis: “no improvement”. |
elo1 |
5.0 |
Alternative hypothesis: “at least this many Elo points gained”. |
alpha |
0.05 |
False positive rate. |
beta |
0.05 |
False negative rate. |
min_games |
16 |
Minimum games that must complete before SPRT is allowed to stop, so tiny samples do not win. |
Regression-hunt probes always use fixed bounds tuned for detecting a drop, regardless of this block.
[training]
| Field | Default | Notes |
|---|---|---|
output_dir |
.crucible/training |
Root directory for exported training data. |
selfplay_games |
100 |
Default games per crucible selfplay-data run. |
selfplay_depth |
10 |
Exact reported search depth kept in a self-play export. |
collect_from_tests |
true |
When true, regression matches also write training JSONL alongside the usual results. |
regression_min_depth |
10 |
Minimum reported depth kept when collecting from regression matches. |
idle_selfplay |
false |
When true, idle worker slots generate short self-play batches whenever the test queue is empty. |
idle_batch_games |
1 |
Games per idle self-play batch. |
See Training data for the full layout of exported files.
[[gate.opponents]]
Each [[gate.opponents]] entry names an external engine binary that gate profiles can reference.
| Field | Default | Notes |
|---|---|---|
name |
- | Short name referenced by gate.profiles.opponents. |
binary_path |
- | Absolute path to the opponent’s UCI binary. |
options |
{} |
Map of UCI option names to string values set on the opponent before each match. |
[[gate.profiles]]
Each [[gate.profiles]] entry defines a named gauntlet.
| Field | Default | Notes |
|---|---|---|
name |
- | Short name passed to crucible gate --profile. |
opponents |
- | List of opponent names defined in [[gate.opponents]]. |
games_per_opponent |
100 |
Games played against each opponent by both the candidate and the baseline. |
min_score_delta |
0.0 |
Minimum score-percentage delta, candidate minus baseline, required for a Pass verdict. |
opening_book |
unset | Optional per-profile opening book, overrides testing.opening_book during the gate. |
time_control |
unset | Optional per-profile time control, overrides testing.time_control during the gate. |
See Release gates for how the verdict is computed.
[[engines]]
Each [[engines]] entry describes a tracked engine. Entries are imported into the database when the daemon starts, and any engine added via crucible add is also stored there.
| Field | Default | Notes |
|---|---|---|
name |
- | Human-readable engine name, used throughout the dashboard and CLI. |
repo |
- | Remote Git URL that Crucible clones into data_dir/repos/<name>. |
branches |
["main"] |
Branches to track. Entries can be exact names or wildcards (for example release/*) matched against origin/*. |
experimental_branches |
[] |
Branches tested normally but kept out of the canonical Timeline and Jobs views. They show up in the Experiments tab. |
build_cmd |
- | Shell command that produces the engine binary. Runs inside the cloned repository. |
binary_path |
- | Path to the built binary, relative to the repository root. It cannot be absolute or contain ./.. components. |
start_from |
unset | Skip all commits before this ref. Useful for engines with a long pre-Crucible history. |
If branches and experimental_branches are both empty, Crucible refuses to start and prints an error pointing at the offending engine.
Engine names are also used as directory names under data_dir/repos, so they must be a single path component and cannot contain /, \, or be ./...
Engine runtimes
Crucible can test any UCI engine that can be built and executed on the host or inside the container. The published Docker image includes common Rust, Go, C/C++, Zig, .NET/C#, Java/Maven, JavaScript/npm, and Python/pip tools. Zig is pinned to 0.15.2. Haskell, unusual SDK versions, host-specific dependencies, and other version-sensitive ecosystems are often simpler with a local binary; Docker can still work with a custom image or mounted toolchain. See Docker and Engine runtimes for examples.
A typical entry for a Zig engine looks like this:
[[engines]]
name = "sykora"
repo = "https://github.com/sb2bg/sykora"
branches = ["main"]
build_cmd = "zig build -Doptimize=ReleaseFast"
binary_path = "zig-out/bin/sykora"
start_from = "v0.2.1"