Skip to content

Machines

Compute › Machines is where engines run, how much runs at once, and what is running now: one card per host. Engines is what is installed; this page is what it runs on.

The Machines page

This server comes first and is always open: its cores, the two numbers that say how much it runs at once, the budget those add up to, and the engines whose binary is here with what each is doing. Then this browser, which becomes a machine the moment you install the engine that ships with the app, and each remote runner with its slots and the engines it advertised. A slot is one engine job or one analysis board.

Remote runner and How remote runners work (the ⓘ) are in the title bar. A browser or runner card opens with a press on its row, and the open one is highlighted. On this browser's card, Install browser Stockfish installs the engine, Start and Stop connect and disconnect it, and Uninstall… asks before it revokes the browser and forgets it.

How much at once

One cap, on this server's card, and everything that runs an engine here counts against it.

Setting
Queue processes Engine processes this machine may run at once — analysis passes, correspondence tasks, the analysis boards, and correspondence searches. 1 to 64; empty is the machine's cores minus two

A correspondence search holds one of these from start to pause, for as long as it runs, and while it does the queue has one fewer to work with: with four processes and four searches going, an imported game's analysis pass waits until you pause or stop one. That is by design — a search is visible on the correspondence page, in its capacity strip and on this card, so the wait is never a mystery — and it is why the card counts searches among what is in use. There is no separate cap for them any more.

Queue processes takes effect when you save. Raising it starts more workers at once; lowering it lets the runs already in flight finish, and simply admits fewer new ones — nothing is interrupted or requeued, so with two runs going and the cap set to one, the second finishes before the queue is down to one. If BLUNDERBASE_ANALYSIS_CONCURRENCY is set in the environment it pins the cap from outside; the field is then read-only and says so. BLUNDERBASE_ANALYSIS_WORKERS turns the in-process workers off entirely, for an installation that drains the queue from blunderbase analyze on its own schedule — the correspondence searches go with them. See Configuration.

Under the box, the budget line does the arithmetic. A process costs its engine's Threads, so what has to fit the cores is queue processes × threads; the card takes the threads from the rows on Engines — the heaviest engine switched on here, since any slot may be holding it — and says in words when the cap at full load would exceed the machine. Six processes of a four-thread Stockfish are twenty-four threads; on eight cores that thrashes, and the line says so. Lower the cap, or give a row fewer threads on Engines. Threads are per engine; how many processes is per machine. While correspondence mode is off only the engine holding Analysis is priced, because nothing else runs here unless you pick another in a game's Analyse….

A remote runner's slots are set when it is registered and can be changed on its card (Rename or resize); the machine's threads are in its own runner.yaml, so the budget line is this server's only.

Remote runners

A runner is a second Blunderbase process on a machine with cores to spare. It has no database and serves no page. It dials out to your server, says which engines it has, and is handed whole analysis jobs and analysis boards. Nothing about a finished run says where it happened. It also answers the moves of a practice game on its engines. A runner older than practice cannot, and the practice dialog says to update it.

Use one when the machine running Blunderbase is not the machine you want doing the searching.

1. Register the runner on the server

On the server:

$ blunderbase runners create gpu-box --slots 8
runner 'gpu-box' registered with 8 slot(s)
This token is shown once. Save the yaml below as runner.yaml on that machine:

# blunderbase runner — blunderbase-runner --config runner.yaml
# The token below is shown once. Keep this file readable only by the runner.
server: "https://blunderbase.example.com"
token: "bb_rnr_kY3…"
name: "gpu-box"
slots: 8
engines:
  # One entry per engine on THIS machine. Edit the paths before starting.
  - name: sf-remote
    path: /usr/games/stockfish
    options:
      Threads: 8

Remote runner in the title bar of the Machines page does the same thing and answers with the same yaml. Either way the token is handed over once and never again: only its SHA-256 is stored. A lost token is a revoke and a new runner, which costs nothing.

--server overrides the URL written into the yaml. Without it the command uses BLUNDERBASE_PUBLIC_URL, and failing that the address the server process binds. Set BLUNDERBASE_PUBLIC_URL on an installation behind a proxy — the server cannot otherwise know what it is called from outside. See Deploy.

2. Write runner.yaml on the other machine

Every key, with its default.

Top level

Key Default What it is
server required The http or https URL of your Blunderbase. The ws/wss URL is derived from it
token required From runners create, shown once
name required Informational — the token is the identity
slots 1 Engine jobs plus analysis boards at once
verify_tls true Whether the server's certificate is verified
poll_seconds 5.0 How often the HTTP fallback asks for work
log_level info debug, info, warning or error
reconnect see below How hard the runner tries to get back
engines [] One entry per binary on this machine

reconnect

Key Default What it is
initial_seconds 1.0 Backoff floor, with jitter
max_seconds 60.0 Backoff ceiling
websocket_failures 3 Failures to connect before the runner falls back to polling
retry_websocket_seconds 60.0 How often a polling runner retries the socket

An entry under engines

Key Default What it is
name required Unique on this machine; becomes the engine's name on the server
path required The path on this machine; a file or a full command line
kind uci uci or maia
options {} UCI options, validated at start-up against what the binary declares
streams true for uci Whether this engine may drive an analysis board. A Maia never streams, however this is written
instances one process per slot How many copies of this binary may run at once
tier — Accepted and ignored, whatever it says. A file written before roles existed, or before there was one analysis pass, still starts

A whole file:

# required
server: https://blunderbase.example.com
token: bb_rnr_…
name: gpu-box

# optional
slots: 4
verify_tls: true
poll_seconds: 5.0
log_level: info

reconnect:
  initial_seconds: 1.0
  max_seconds: 60.0
  websocket_failures: 3
  retry_websocket_seconds: 60.0

engines:
  - name: sf-remote
    path: /usr/games/stockfish
    kind: uci
    options:
      Threads: 8
      Hash: 4096
    streams: true
    instances: 2

  - name: maia3
    path: /engines/maia3/bin/maia3-5m
    kind: maia
    instances: 1   # one GPU process shared by all slots

instances is the one thing an engine can say about how it is run. Without it, every slot that wants this engine gets its own process, which is right for a CPU binary and wrong for anything holding a single accelerator: a Maia on one GPU wants instances: 1, so the slots queue on one process instead of starting a second and running the card out of memory. It can only lower the number of processes, never raise it above slots.

An unrecognised key is refused by name rather than ignored. A typo in a slot count is a mistake, not a preference, and the refusal names the field and the file it came from.

A runner advertises what it has and claims nothing about what it is for. Which engine serves Analysis and Human moves is assigned on the Engines page.

Four values can come from the environment instead of the file, and beat it, so a token need not live in something that gets copied around:

Variable Replaces
BLUNDERBASE_RUNNER_CONFIG The path to runner.yaml when --config is not given
BLUNDERBASE_RUNNER_SERVER server
BLUNDERBASE_RUNNER_TOKEN token
BLUNDERBASE_RUNNER_NAME name
BLUNDERBASE_RUNNER_SLOTS slots

With no file at all, those variables are the whole configuration, which is what a container that mounts nothing does.

3. Start it

$ blunderbase-runner --config runner.yaml --check
sf-remote: accepted as engine 7
$ blunderbase-runner --config runner.yaml

--check probes every binary, opens one connection, prints what the server accepted, and exits. Run it before starting the real thing, not on a timer: it opens a second connection with the same token, and a second connection takes the runner over.

Exit codes:

Code Meaning
0 The runner was asked to stop and did
1 The configuration is wrong, or not one engine could be started
2 The server refused this runner's protocol version — upgrade the runner to the server's release

SIGINT and SIGTERM ask for a clean stop rather than killing a search mid-frame.

As a container

A runner is the same image as the server, with a different command, no ports and no volumes but the yaml. There is a sample compose file, docker-compose.runner.yml, in the repository. Put it and runner.yaml in the same directory and start it:

$ BLUNDERBASE_RUNNER_TOKEN=bb_rnr_… docker compose -f docker-compose.runner.yml up -d

The image ships Stockfish at /usr/games/stockfish. A Maia build and its weights are yours to mount — uncomment the ./engines volume and register the path as kind: maia.

Disable the container's healthcheck if you copy the service elsewhere: it curls an API a runner does not serve, so docker compose ps would read "unhealthy" for a runner that is working perfectly. Watch the log lines instead.

Put TLS in front of the server. The token is a bearer credential on every frame. Deploy has a Caddyfile and an nginx site that do it; server: is then the https URL, and the socket is derived from it.

Watching it work

  • blunderbase runners list — one line per runner: connected or not, its slots, its advertised engines, and how much of the backlog only it can do.
  • The Machines page — this host and every runner, each with the engines it advertises. Engines names the machine on every row too.
  • The analysis queue, split by destination. A queue that is not moving because the machine with that engine is offline looks exactly like a long queue until you look here.
  • An MCP client has a read-only runners_status tool, which is the same picture. Minting and revoking stay out of the chat: they are token handling.

When it does not connect

What you see What it is
exit 1, "server is required" The file or the environment is incomplete; the message names the field and the file
exit 1, a probe failure The path of an engine is wrong on that machine
exit 2 The server speaks a different protocol version — upgrade the runner
close code 4401, then exit The token is not a registered runner's; mint a new one
close code 4403 The runner was revoked while it was connected
close code 4409 A second connection with the same token took it over — usually a --check run, or two copies of the container
close code 4426 The two halves do not speak the same runner protocol; the process exits 2
close code 4429 This token has been refused often enough that the server has shut its door on it. Dialling again only holds the door shut — fix the token and wait. Ten failures start a backoff that doubles from one second to a minute
the log says "polling" The socket failed three times. The runner is still working, over HTTP, and retries the socket every minute

A runner from before a run could stop at a depth or after a number of seconds still connects and still works: it is handed every run that stops at a node budget, which is the pass every imported game gets and anything asked for in nodes. A run you asked for with a depth or seconds on one of its engines waits in the queue until that runner is updated — the queue shows it waiting on that machine, and updating the runner is the fix.

Revoking

$ blunderbase runners revoke gpu-box

Revoke… on the runner's card on the Machines page does the same, once you confirm with Revoke. Either way the token stops working and the engines it advertised are deleted.

Revoking from the app also closes the open link and hands back whatever the runner was running, with the attempt refunded — a machine you took away did not fail. The command-line version is not the server process, so it cannot close a link it does not hold: it deletes the engines, the runner is given no further work, and the link ends at its next reconnect.

Runs that were in flight are queued again either way. The database is the queue; runners are expendable.

One run, one machine

A run's evaluation and its human-move pass execute in the same process, so both engines have to be on the same host — for a run that asks for both.

A search engine on a machine with no Maia, in an installation whose only Maia is elsewhere, is refused when you queue the analysis, naming both machines. A run queued with no Maia pass at all has one pass and so one host, and is never refused for this. An installation with no Maia anywhere is unaffected: the pass simply does not happen.