Files
gregandClaude Opus 5.5 f1271341ef
Validate game templates / validate (push) Successful in 36s
Validate game templates / boot-test (push) Successful in 1m11s
Validate game templates / publish (push) Skipped
feat(builder): repair existing templates, a weekly check that queues repairs, and eval
The builder could only create games; nothing noticed when a live template
stopped working, and there was no way to tell whether a builder change made
it better or worse.

- repair -id ID: boots an existing template as nodes run it; if it fails,
  the same investigation as for a draft runs and the model changes only what
  the diagnosis says. The worker handles "repair" jobs (gameId from the
  panel) and opens "fix(games): repair ..." on draft/<id>; a template that
  boots as it is reports "nothing to repair" and opens no PR. The boot and
  fix loop is now shared by builds and repairs.
- validate.yml runs weekly and boot-tests every game; failed games are sent
  to the panel (X-GBR-Event: games_boot_failed) to queue repairs.
- eval -ids all: builds games that already have a template from scratch,
  blind, and scores each draft against it (boots, same image and app, ports,
  ready line, settings found) into eval.md and eval.json.
- research skips our own sites, so neither research nor eval reads our
  templates back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 00:42:34 -04:00

5.9 KiB

AI builder (gbrgames worker)

The panel's AI builder (Admin → Games → AI Config Generator) is run by a worker we host ourselves. The panel only keeps a queue; every result is a pull request to this repo, which CI validates and boot-tests like any other.

Panel                                     Builder worker (gbrgames worker, Docker)
─────                                     ─────────────────────────────────────────
admin starts a job ──► queued    ◄─poll── research: web search + page reader + model
progress / log / variants        ◄─post── build: first boot → draft → boot with the node-agent
"Review draft PR"                         → branch draft/<id>-<job> + PR as the bot ──► CI (validate + boot test)
  1. Research (gbrgames research -name N): searches the web, reads the best pages, and has the model list the server's variants (edition, image or SteamCMD app, startup, ports). Every image and Steam app is checked for real (docker manifest inspect, anonymous steamcmd +app_info_print) before the admin sees it.

  2. The admin picks a variant in the panel.

  3. Build (gbrgames build -name N -variant @v.json): first boot of the image untouched (first-boot), a draft by the model (draft, rules in AUTHORING.md), then the draft is deployed with the node-agent's BootTest, the same check CI runs. A failed boot is investigated before the model changes anything (see below).

    Every server the builder starts, first boots included, is deployed by the node-agent's own code, so it runs exactly as on a node. A boot passes when the server reaches its ready line, stays up for holdSec (60 s) after it, and listens on every port the template publishes. Files the server writes anywhere but /server and /data (the image's own VOLUME folders included) are reported as lost: nodes recreate the container on every start.

    Steam games are downloaded once per build into a cache volume (gbr-steamcache-<app>, labelled gbr.cache) and copied into each boot, so later boots only check the files. STEAM_CACHE=off turns it off.

  4. The worker pushes games/<id>/ to a branch and opens a pull request as the bot account. A draft that validates but never booted still gets a PR, titled WIP:, with what's left listed. Nothing reaches players until a person merges it and enables the game.

The builder is game-agnostic: everything about a game comes from that game's research, first boot and boot tests. No game's name, id or code may appear in the builder's source, its rules for the model (AUTHORING.md), the schemas or this file, and the model is never shown another game's template (its format example is an invented server). TestBuilderIsGameAgnostic fails the build if one does.

The worker only connects out (to the panel, Gitea, the model and the search services).

Keeping games working

  • Repair (gbrgames repair -id ID): boots an existing template as nodes run it. If it boots, there is nothing to do. If not, the same investigation as for a draft runs, the model changes only what the diagnosis says, and the worker opens fix(games): repair … on draft/<id> (replacing any open draft of that game). The panel queues repairs from a game's admin page, or by itself (next point).

  • Weekly check: validate.yml boot-tests every game each Monday. Each game that no longer boots is sent to the panel (X-GBR-Event: games_boot_failed), which queues a repair job for it.

  • Eval (gbrgames eval -ids all): builds games that already have a template from scratch, blind (the builder never reads games/, and research skips our own sites), and scores each draft against the merged template: boots, same image and Steam app, same ports, ready line, settings found. Run it before and after changing the builder or the model. It takes as long as the builds; on the builder VM:

    docker exec gbr-builder sh -c 'cd /work && rm -rf eval && git clone -q https://www.gitea.gbreed.com/gbreed-public/gbr-games.git eval && cd eval && gbrgames eval -ids all -out eval.md && cat eval.md'
    

Requirements

A Docker host with the isolated gbr-boottest network (RUNNER.md, step 1), so servers booted by the builder can't reach private networks. Enough memory for the largest server it builds (the biggest need ~16 GB) and disk for SteamCMD downloads. The image gbr-builder:latest is published by .gitea/workflows/builder-image.yml whenever cmd/ changes on main.

Configuration

All through environment variables; nothing about where it runs lives in this repo.

Variable What
PANEL_URL The panel the worker takes jobs from
BUILDER_TOKEN Shared secret the panel checks on /api/v1/builder/** (same value in the panel's env)
GITEA_TOKEN The bot account's token (repository read/write; the account needs Write on this repo): pushes draft branches, opens PRs as that account. Checked when the worker starts
OLLAMA_URL Ollama for the model (or MODEL_PROVIDER=gemini with GEMINI_API_KEY)
SEARXNG_URL, READER_URL A SearXNG instance (JSON API) and a page-to-text reader (READER_URL/<page url>)
MODEL, POLL_INTERVAL, WORKER_NAME, WORK_DIR Optional: model name, poll interval (20s), name shown in the panel, work folder
BOT_NAME Optional: commit author when the token can't read its own account (no read:user scope); default gbr-bot

Running a build by hand

On any machine with Docker and the gbr-boottest network, with OLLAMA_URL, SEARXNG_URL and READER_URL set:

go run ./cmd/gbrgames research -name "<game>" -out research.json
# pick a variant from research.json into v.json, then:
go run ./cmd/gbrgames build -name "<game>" -variant @v.json -out build.json

The draft lands in games/<id>/, first-boot reports in firstboot-out/.