Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Running the platform locally

The local stack runs submission, admission, trials, matches and rankings together. To play an entry on your machine, use tinybrains, which needs no Compose, no database and no season; Testing before you submit covers it. Bring the stack up when you want the loop: a submission that is admitted, given a trial, promoted, paired and rated.

What you need

Install Docker Engine with Compose v2.21 or newer and a POSIX shell. You need two checkouts, each running one compose file:

web/      the platform: Postgres, Redis, MinIO, Soma, the Orion console and the site
kalam/    a runner: the one container that plays matches for a Soma

Every service is an image its own repository publishes. Soma is ghcr.io/tiny-brains/soma: orion-server with the Soma package inside, which applies the schema and loads its own package. Kalam is ghcr.io/tiny-brains/kalam, a runner that loads its own. The cartridge has no image: each of these images, and the site’s viewer, takes it from an Ants release. To try a Soma change, build that checkout and set SOMA_IMAGE; to try a Kalam or web change, run with --build.

Every command below is run from tinybrains/devops/, which holds the compose files, the setup scripts and each environment’s own settings. ./tb <env> <web|runner> ... passes the rest to docker compose with that environment’s file, project and settings already chosen.

The stack uses the pinned Orion 1.12.0 runtime, Postgres 16, Redis, MinIO and the browser application. There is no inference sidecar: each node runs models itself. You do not need Rust on the host.

Configure the platform

One command sets up every credential:

scripts/setup/init.sh

It creates dev/web/.env, mints POSTGRES_PASSWORD, SOMA_SESSION_SECRET, RUNNER_TOKEN_SECRET, the models read key and ORION_ADMIN_KEY, pins SOMA_IMAGE to the newest Soma release, generates the Ed25519 plugin trust root for this machine, and signs the plugins in the Soma image (and in a Kalam image, when one is here). It is idempotent, so it is also the repair command after a new image: an unsigned component quarantines the channels that call it, and the node stops at its boot apply. examples/web.env.example is the contract it fills, and it writes dev/web/.env.

Register a GitHub OAuth App with homepage http://localhost:5173 and callback http://localhost:5173/v1/auth/github/callback, and set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET in .env. Plain HTTP development uses a non-Secure cookie; an HTTPS deployment needs the matching cookie policy.

Bring it up

./tb dev web up -d --build
./tb dev web ps -a
./tb dev web logs soma-bootstrap soma

Two one-shots run before Soma. buckets makes the replay and models buckets, the models read key and models/*’s public read. soma-bootstrap creates the Orion state database, applies Soma’s migrations when the platform database is empty, and registers the game, the cartridge and the engine digest its image carries. It records a digest of the migrations it applied, and refuses a schema rewrite it cannot apply. soma then loads its own package and stops if a plugin fails to verify, so any soma node that is up is serving; its log says so with every [packages] artifact is applied and serving.

Open http://localhost:5173, or verify the proxy:

curl --fail --silent --show-error http://localhost:5173/v1/games

The local ports are Web at 5173, Soma at 8080, the Orion console at 8081 and MinIO at 9000/9001, all bound to loopback. Use one browser origin for application requests and sign-in.

Start a runner

The platform plays no match itself. A runner from Kalam’s checkout plays them, and reaches the platform only through Soma. Make yourself an administrator before you sign in:

scripts/setup/admin-user.sh <your-github-login>      # then ./tb dev web up -d soma

It writes github:<your numeric id> into SOMA_ADMIN_IDS in .env (never the login, which GitHub hands on after a rename), and signing in makes that account an administrator. Then mint the runner a key on the admin Runners page, which shows the key once.

Copy examples/runner.env.example to dev/kalam/.env and uncomment its local block: every address is host.docker.internal, and it already names RUNNER_SIG_DIR=../web/keys/signatures. Fill in the key, TB_TRUST_PUBLIC_KEY and the MODELS_READ_* pair from web’s .env, set KALAM_IMAGE (the released image, or tinybrains/kalam:dev with --build) and mint the runner an ORION_ADMIN_KEY of its own. Then:

./tb dev runner --profile admit up -d --build
./tb dev runner logs -f runner     # until "every [packages] artifact is applied and serving"
./tb dev runner logs -f admit      # the same line

--profile admit starts the admitting runner beside the runner. Soma runs no model, so the admitting runner admits every submission and baseline; while none is up, they wait in testing.

Build the runner and Soma from the same Ants release. A runner whose engine digest differs from the one Soma declared claims nothing, for ever, and looks healthy doing it.

Sign in and make a match happen

Sign in with GitHub at http://localhost:5173. Use localhost and never 127.0.0.1: the sign-in cookie is host-only. Create a model with the New model form on your profile, then submit through the site’s own /submit form, which hashes both files and uploads them itself, and shows the upload commands only if a transfer fails. A game needs an open local season with a board in play, an eligible account, a runner, and at least one runnable opponent for the trial and regular matches.

A fresh stack has no season. An administrator creates one on the admin pages, as in production; the stack seeds nothing.

It has no boards either. No release ships a season’s boards: an administrator uploads them on the season’s page and puts each in play. A season’s board is named <size>-<terrain>-<N>p-<H>h (The maps), so a basic board goes in under a name of that shape. tinybrains maps export ants <dir> writes the five basic boards the release ships, and tinybrains maps check tells you whether Soma would accept a board of your own. Until one is in play, the pair clock pairs nothing and a candidate waits in verified.

It has no baselines either. The platform ships no model: an administrator uploads a season’s baselines on the season’s page, each a name and its two files, and ants-starter/models holds three. The admit clock admits a baseline as it admits a submission, and the baseline lands switched off until the administrator switches it on there. A candidate stuck in verified has no baseline its trial can seat: check the season’s page for a baseline in play. The same files under a second name make a second opponent, and that is how a trial on a board of many seats finds enough of them.

Only an administrator can create a season, and an administrator promotes or demotes anyone else on the admin Users page. Follow the submitted model’s phase, its trial ID, then its finished match history and leaderboard entry. That proves a working stack better than a successful health probe.

Reloading and stopping

A package edit is a new image: rebuild it, and the node loads it on start.

docker build -t tinybrains/soma:dev ../soma && SOMA_IMAGE=tinybrains/soma:dev ./tb dev web up -d
scripts/setup/sign-plugins.sh       # after any new Soma or Kalam image

Loading a package applies no schema migrations. soma-bootstrap applies them only when the platform database is empty. The schema is pre-release, and a change rewrites 0001_init.sql or 0002_sessions.sql in place, so bootstrap refuses a rewritten schema at once; you never meet it later as a missing relation. ./scripts/dev/resync-dev-schema.sh rebuilds a local database on the new schema and keeps the accounts and sessions.

A new engine is a new Soma image and a new runner image, built from the same release, and bootstrap declares the new digest only with ENGINE_RELEASE=1, which it refuses while a season is live: close the season first.

Stop services with ./tb dev web stop. A runner drains before it stops, but a forced shutdown can still leave claims for the platform to recover. Delete volumes only when you mean to discard local history.

When nothing plays

SymptomCheck
Sign-in loops or returns unauthenticatedBrowser origin, OAuth callback, cookie policy, and session secret
Candidate stays testingWhether both objects are in the models bucket, the registered reference observations, the admission clock, and whether an admitting runner is up
Rejected ARTIFACT_MISSINGThe upload step. Nothing fetches from a release: the competitor PUTs to a presigned URL
Candidate stays verifiedWhether the season has a board in play that its baselines can seat, then the latest trial status and the pairing clock
Pending matches never runA runner is up and its key is live; its engine digest equals the one soma-bootstrap declared
The runner logs invalid_keyThe key comes from another database: mint one on this one
The runner releases every match without playing itThe runner’s kalam-roster clock, and whether its node has the seat’s model active
Models cannot loadThe bucket’s addresses (Soma signs an upload for the public one and dials the internal one, and a runner uses host.docker.internal), plus the read key
Matches play but cannot finishThe runner’s RUNNER_BLOB_ENDPOINT must equal Soma’s, character for character
Results exist but ratings do not moveThe count clock; check whether the match was an unrated trial
A node stops with a quarantined channelThe component’s Ed25519 signature. Re-run devops/scripts/setup/sign-plugins.sh after any new image

Read ./tb dev web logs for the relevant service, and keep model and match IDs in reports. A cloud deployment, TLS ingress, live R2 verification and autoscaling lie outside this local setup.