mindxtrain Coach (UI)
A single-page web UI that walks judges and new contributors through the mindxtrain pipeline without needing a GPU. Bundled inside the mindxtrain.operator FastAPI app at /coach/.
Why it exists
Hackathon judges have ~3 minutes per submission. The Coach lets them poke at the differentiator (the 60-second AOT autotune) and the cost story (4Γ cheaper than H100) interactively, in a browser, without setting up ROCm.
Boot
uv run uvicorn mindxtrain.operator.app:app --host 0.0.0.0 --port 8080
Open http://localhost:8080 β the root path redirects to /coach/.
The Coach works without a backend GPU:
- The autotune endpoint runs
run_autotune(dry_run=True)and emits the reference plan. - The compile endpoint produces a real Axolotl YAML against the dry-run plan.
- The cost calculator is pure arithmetic.
The chat panel stays disabled until MINDXTRAIN_BACKEND=vllm is set and a vLLM-ROCm server is reachable.
Layout
mindxtrain/operator/coach/
βββ __init__.py # exports the FastAPI router
βββ api.py # routes (recipes / bench / compile / cost / health /
β # runs / metrics / receipt / sea-decision / mei / diagnostics)
βββ run_metrics.py # 1 Hz system-metrics sampler (psutil + /proc)
βββ chronos_client.py # mindX promised-time client
βββ static/
βββ index.html # multi-card SPA shell (preflight β β¦ β train β receipt β chat)
βββ style.css # minimal dark-friendly CSS, AMD orange accent
βββ coach.js # vanilla JS state machine, no framework
The Coach mounts under /coach/; static assets are at /coach/static/*. The UI
has grown well past the original five-step demo: it now covers preflight, hardware
detection, dream-corpus stats, recipe pick, autotune, compile, live training with
diagnostic feedback, the verifiable receipt, MEI scoring, cost, deploy, and chat.
Routes
| Method | Path | Body / Query | Returns |
|---|---|---|---|
| GET | / |
β | 307 redirect to /coach/ |
| GET | /coach/ |
β | index.html |
| GET | /coach/static/{path} |
β | static files |
| GET | /coach/api/recipes |
β | list[RecipeSummary] (12 items) |
| GET | /coach/api/recipes/{name} |
β | { name, yaml, summary } |
| POST | /coach/api/bench |
(none) | AutotunePlan (dry-run reference) |
| POST | /coach/api/compile |
{recipe, plan?} |
{recipe, config_summary, plan, axolotl_yaml, overrides} |
| POST | /coach/api/cost |
{gpus, hours, safety_margin} |
{mi300x, h100, h200, speedup_vs_h100_x} |
| GET | /coach/api/health |
β | {coach_version, chat_backend_ready, recipes_available} |
| POST | /coach/api/runs/launch |
{recipe, plan?, out_dir?} |
Run snapshot (spawns training) |
| GET | /coach/api/runs/{id}/events |
β | SSE stream (status/step/eval/log/metrics/energy) |
| GET | /coach/api/runs/{id}/metrics |
?since= |
system-metrics backfill for the sparklines |
| GET | /coach/api/receipt/{run_id} |
β | ReceiptView β re-verified BLAKE3 hashes + verified |
| GET | /coach/api/sea-decision |
β | mindX SEA autonomous-training gate state |
| GET | /coach/api/mei/score/{run_id} |
β | MEIScoreView (mindX Efficiency Index) |
| GET | /coach/api/diagnostics/live |
β | host load / RAM% / disk% / operator RSS |
The full schema is rendered at /docs (Swagger).
Live training diagnostics
The Train (live) card is the accurate, real-time depiction of a run. Events
arrive over Server-Sent Events (/coach/api/runs/{id}/events) β step, eval,
log, and 1 Hz metrics β and drive these surfaces:
- Session headline β status badge, wall-clock + CPU-time elapsed, throttle%, last loss, freshest eval. The at-a-glance "is it healthy" line.
- Phase + progress β friendly phase narration ("Loading base modelβ¦",
"Trainingβ¦", "Saving checkpointβ¦") plus a progress bar with
step N / total Β· ETA, driven byStepEvent.total_steps. - Loss curve (Chart.js) β dual-axis loss (orange) +
mean_token_accuracy(green, NaN-gapped where a backend omits it). The primary "is it learning" signal.
Because a real MI300X run logs thousands of steps, the heavy detail is kept accurate but compressed behind accordions, with the truncation always shown β never silent:
- Loss curve keeps a rolling window of the last
MAX_CHART_POINTS(1500) points; once it rolls, ashowing last 1500 of N stepsnote appears under the chart. - Per-step metrics (step, loss, acc, entropy, lr, grad_norm) live in a collapsed
<details>accordion; the DOM table caps at 50 rows but the summary reports the true total βper-step metrics (N steps Β· last 50 shown). - train.log (live tail) is a
<details>accordion that auto-folds older lines and shows a running(N lines)count, capping the DOM atMAX_LOG_LINES(2000) and labellingΒ· oldest droppedonce it does. - System metrics β five d3 sparklines (host cpu%/ram%/load, trainer rss MB,
trainer cpu-s/s) sampled at 1 Hz, in their own
<details>(open by default).
This keeps the page legible on a laptop while the underlying data stays faithful.
Verifiable receipt card
When a run finishes, the operator emits manifest.json (BLAKE3 of the config
snapshot, checkpoint, and the frozen AutotunePlan) into the run directory. The
Verifiable receipt card fetches /coach/api/receipt/{run_id}, which re-hashes
the on-disk artifacts and returns a verified flag plus the per-field checks. A
verified β badge and the truncated hashes render in the card; the same check runs
from a shell via mindxtrain receipt out/runs/<run>/manifest.json --config <recipe>.yaml.
Binding the AutotunePlan hash to the checkpoint is the AOT-as-verification primitive β
it proves which compiled backend/heuristic/RCCL config produced the weights.
Create script + imprint (actor / persona / script)
mindXtrain (and Coach) train models. The model is an actor; an actor has a
persona (identity / voice) and a script (the training examples β the
"impression"). The Create script card authors a small script in the browser and
saves it as source: local JSONL the recipes ingest.
POST /coach/api/datasetsβ{name, persona_name, system_prompt, voice_examples, exchanges:[{user,assistant}], seed_voice}β writesout/datasets/<name>/script.jsonl(override the root withMINDXTRAIN_DATASETS_DIR).GET /coach/api/datasetslists them;GET /coach/api/datasets/{name}previews.GET /coach/api/personaβ pre-fills the form fromMINDXTRAIN_PERSONA_PATH(clean-room: recognised fields only, never copies mindX bytes).- Point the
mindx_persona_imprint_localrecipe'sdata.pathat the saved script and train the tiny actor (trl_local, CPU or local GPU).
Imprint = recall, before vs after. Pose the script's own user-turns back to the actor and compare the base model (before) with the trained adapter (after) against the script's assistant voice:
mindxtrain imprint mindxtrain/train/recipes/mindx_persona_imprint_local.yaml
prints an ImprintReport (before_voice, after_voice, imprint_delta, shift,
imprinted); exit 4 if no imprint took. POST /coach/api/imprint/score scores supplied
utterances without blocking the event loop on inference. mindxtrain imprint --trigger-dream hands the imprinted actor to mindX's machine.dream 8-hour cycle (via
MINDXTRAIN_API_BASE_URL /v1/dream/ingest, else a data/incoming/ inbox drop under
MINDXTRAIN_MINDX_HOME) β clean-room, an artifact pointer, never mindX code.
Create script β personas + skills
The Create script card authors a source: local JSONL from a persona and toggleable
skills:
- Built-in personas (
GET /coach/api/personas) βcodephreak,assistant,mentor(mindxtrain.data.personas.BUILTIN_PERSONAS). Pick one, or use the custom fields. - Skills β toggle Software Engineer / Platform Architect / Bash / Solidity to mix
each skill's in-domain exchanges into the script (
mindxtrain.data.personas.SKILLS,compose(persona, skills)). A skill is a system-prompt addendum + representative turns. POST /coach/api/datasetscomposes persona + skills + your exchanges and returns the row count plus training params auto-derived from the dataset size (derive_training_paramsβ small scripts overfit to imprint: more epochs, grad_accum 1).
Build an Ollama Modelfile (separate window)
The Build Modelfile⦠button (in the train card's push-to-ollama row) opens a standalone
builder at /coach/modelfile (a separate browser window), pre-filled for the current run:
- Every instruction is a toggle:
FROM(required),SYSTEM,TEMPLATE,ADAPTER,LICENSE,REQUIRES, plusMESSAGEexamples andstopsequences. - Every
PARAMETER(num_ctx,temperature,top_k,top_p,min_p,repeat_penalty,mirostat,seed, β¦ β the full catalogue fromGET /coach/api/modelfile/params) is a toggle + input, rendered dynamically with defaults and ranges. POST /coach/api/modelfile/buildrenders theModelfiletext;POST /coach/api/modelfile/createrunsollama create <tag>. Core logic:mindxtrain.deploy.modelfile(ModelfileSpec,render_modelfile,create_model).
The core storyboard
The original CPU-only demo path, top-to-bottom (the cards above and below it β preflight, hardware, dream-corpus, live training, receipt, MEI, deploy β flank it):
- Pick a recipe β clickable grid of all built-in recipes; the selected one's YAML expands inline.
- Run the autotune probe β single button; shows the
AutotunePlanJSON plus a six-chip summary (attention=ck,gemm=hipblaslt_default,rccl=1gpu_noop, β¦). - Compile to Axolotl YAML β translates
(recipe, plan)into the trainer-side YAML, surfaces the plan-driven overrides as chips above the YAML. - Train (live) β spawns the run and streams the diagnostic feedback described in Live training diagnostics; on a CPU box the
trl_cpulane trains a small model in-process so the whole loop is demoable without a GPU. Thetrl_locallane is the device-aware variant β it uses a local consumer GPU (CUDA or ROCm Radeon) when present and falls back to CPU otherwise, so the same recipe runs on a laptop or a gaming GPU.recommend_lanesends an Instinct/MI300X card toaxolotl_amdand any other local GPU totrl_local. - Verifiable receipt β the
verified βbadge + bound hashes appear the moment the run completes. - Cost vs H100 β sliders for GPUs and hours; emits a three-row comparison table (MI300X / H100 / H200) with a headline like "MI300X is 5.4Γ cheaper than the H100 baseline".
- Try the model β chat panel that proxies to
/v1/chat/completions. Stays disabled and explains why until the backend reports ready; a Check now button re-probes on demand.
Demo storyboard
0:00β0:30 open localhost:8080, point at the three-stage diagram in the header
0:30β1:00 click qwen3_8b_sft_lora; show the YAML preview
1:00β2:00 click "Run autotune (dry-run)"; show the plan JSON streaming in
and the six-chip summary populating
2:00β3:00 click "Compile"; show the Axolotl YAML diff (the autotune
plan's attention_backend appears as flash_attn_backend=ck)
3:00β4:00 drag the cost slider to 1 GPU Γ 1.5 hours; show the
"5Γ cheaper than H100" headline
4:00β5:00 the chat panel; show that it's gracefully disabled because
the backend isn't booted, then close
Every Coach interaction is screen-recordable on a CPU-only laptop. The MI300X work happens behind the scenes for the actual training run; the Coach surfaces the outcome judges care about.
Dependencies
- FastAPI β already a dep of mindxtrain.operator.
mindxtrainβ workspace dep added topyproject.tomlso the Coach can callmindxtrain.config.loader.list_recipes(),mindxtrain.autotune.benchmark.run_autotune(), andmindxtrain.train.compile_axolotl_yaml().pyyamlβ added for the recipeβsummary path.
No JavaScript framework, no build step, no node_modules.
Tests
tests/test_coach_api.py covers every endpoint via FastAPI's TestClient:
- root redirects to
/coach/ - index serves HTML with the right
<title> - static files serve (CSS + JS)
- recipes list returns 12 items
- recipe detail returns YAML + summary
- 404 on unknown recipe
- bench returns a valid
AutotunePlan - compile returns Axolotl YAML + overrides; 404 on unknown recipe
- cost returns three breakdowns; 422 on invalid input
- health endpoint reports
recipes_available=12 /healthmentionscoach_url=/coach/- the train card exposes the diagnostic accordions (
metrics-table-wrap,metrics-table-count,train-log-count,chart-window-note) and coach.js wires the rolling-window cap + counters (MAX_CHART_POINTS,_updateMetricsTableCount,_updateLogCount) - the receipt card + loader are present (
step-receipt,loadReceiptForRun)
The live-training + receipt round-trip is covered in tests/test_coach_receipt_api.py
(canned spawn β /coach/api/receipt/{id} returns verified=True).
Run with uv run pytest tests/test_coach_api.py -v.
Customizing for the demo
Tweak the cost-comparison constants in mindxtrain/operator/coach/api.py:
H100_USDC_PER_HOUR = 4.00
H200_USDC_PER_HOUR = 6.00
The MI300X rate is sourced from mindxtrain.budget.pricing.MI300X_USDC_PER_HOUR ($1.99/hr, AMD Developer Cloud list price).
Streaming chat + ollama controls (Try the model)
The Try the model card chats with a local model and streams the response
token-by-token β the AI SDK
text-stream pattern, implemented in vanilla JS (no build step): coach.js consumes a
text/event-stream whose data: lines are JSON token deltas, ending with data: [DONE].
POST /coach/api/chat/streamβ{model, messages, max_tokens?}β SSE token stream. Relaysbackend.stream_chat()(the OpenAI-compatible streaming the ollama/vLLM backends already speak). Backend errors are surfaced in-stream (event: error), never as a mid-stream 500.- Model picker β populated from
GET /coach/api/models(local models sorted ahead of:cloud), so the chat no longer defaults to a cloud model that silently returns nothing. - ollama controls β
GET /coach/api/ollama/status+POST /coach/api/ollama/{start,stop}start/stop the localollama serveand report its state;β» modelsre-lists.
For a remote vLLM-ROCm endpoint instead, set MINDXTRAIN_BACKEND=vllm +
MINDXTRAIN_VLLM_BASE_URL; the same streaming chat works against it
(see HANDOFF.md Β§Β§ 5β6).