This guide explains how to use the Beskar AI console, the web frontend for Beskar AI's distributed GPU platform. It assumes you're a software developer but have never seen this project before.
Beskar AI runs GPU jobs on a fleet of worker machines. You describe a job — either a git repository containing a small pipeline file, or a container image — and submit it through the console. The job lands in a Postgres-backed queue; a worker machine claims it, runs it in an ephemeral Docker container (with GPU access), uploads whatever the job wrote to its output directory as a downloadable artifact, and reports success or failure back. The console is where you submit jobs, watch them run, and download their results.
The console lives at https://mandal-automations.com/. Signed out, you'll see a landing page with a Continue with GitHub button on the left and a "secure sign-in" trace pane on the right.
- Click Continue with GitHub.
- Approve the OAuth request on GitHub (scope:
read:user repo— your public profile, plus repo access so jobs can be launched from your repositories). - You're redirected back and the console renders.
On first sign-in a Beskar account is created for you automatically (plan
free, balance $0.00). Your GitHub profile fields are refreshed on every
later sign-in. Identity is kept in a signed session cookie; the GitHub access
token itself is discarded after sign-in.
To sign out, click Sign out in the button row under your profile.
Once signed in, the screen has two panes:
Left — your account. Your GitHub avatar, login, name, and plan badge, plus three summary tiles:
| Tile | Meaning |
|---|---|
| Active GPUs | Running instances plus jobs currently executing on workers |
| GPU-minutes · 30d | Your metered usage over the last 30 days |
| Balance | Your account balance |
Below the tiles: Launch instance (opens the job form), Sign out, and — if you're an admin — Admin.
Right — the fleet table. Everything of yours that is running, waiting, or recently finished, one row per item. It refreshes automatically every 15 seconds (paused while the browser tab is hidden), so elapsed times tick and new state appears without a manual reload.
All numbers are live from the backend. If the tiles show — and the fleet
pane says data is unavailable, the backend genuinely has no database attached
or can't reach it — the console never shows placeholder data.
Click Launch instance. The "Submit a job" modal opens with two modes, toggled at the top.
Runs a pipeline defined in a git repository. Fill in:
- Repository URL — e.g.
https://github.com/your-org/your-repo. Onlygithub.comandgitlab.comare accepted. - Git ref — branch, tag, or commit SHA (default
main). - Workflow path — path to the pipeline YAML inside the repo
(default
orchestra.yml).
The worker clones your repo at that ref, pip-installs requirements.txt if
present (the bootstrap), then executes the workflow file step by step.
A workflow file is a YAML document with an optional env block and a list of
steps. Each step's run script executes with bash -euo pipefail, so any
failing command fails the step, and the pipeline stops at the first failing
step.
name: my-training-job
env:
OUTPUT_DIR: /scratch/workspace/artifacts
MAX_STEPS: "50"
steps:
- name: Install dependencies
run: pip install --no-cache-dir -r requirements.txt
- name: Train
run: python main.py
- name: Verify artifacts
run: test -f "$OUTPUT_DIR/model.pt"Two conventions worth knowing:
- Write outputs to
$OUTPUT_DIR(/scratch/workspace/artifactson the worker). Everything in that directory is collected as the job's artifact — anything written elsewhere is lost when the container exits. - Keep the pipeline self-sufficient: re-running
pip installin a step is a fast no-op on the worker (bootstrap already did it) but lets the same file run anywhere.
You don't have to write the workflow file by hand. This repository ships
prompt.md, a ready-made prompt that turns an AI coding
assistant into a pipeline author. To use it:
- Open your project in Claude Code (or paste your repo's context into claude.ai — any assistant that can read your files works).
- Copy the entire contents of
prompt.mdinto the chat. - Review what comes back, then submit the job from the console.
The prompt does more than describe the YAML syntax — it encodes the full Beskar-Core worker contract, which is where hand-written pipelines usually go wrong:
- Inspect before writing. The assistant is told to read your repo first — toolchain, dependency manifest, entrypoint, expected outputs — and to ask rather than guess if there's no obvious entrypoint.
- Worker environment. Steps run with
bash -euo pipefailfrom the repo root, on a CUDA 12.8 runtime image (GPU present, but no compilers — anything needinggcc,nvcc, Rust, or Node must apt-get/rustup it in the first step). Steps don't share shell state, soexports must be repeated per step. - Artifact rules. Outputs must land under
$OUTPUT_DIRor they're lost with the container; the generated pipeline ends with a verify step so a run that silently produced nothing fails loudly instead. - Tunables in
env:. Sample counts, step limits, and model names go in theenv:block with small defaults, so you can resize runs without code changes.
The deliverables are the orchestra.yml itself, any minimal code change
needed to route outputs into $OUTPUT_DIR, and a README note describing the
pipeline's knobs. The prompt also has the assistant validate the result —
dry-running it with Beskar-Core's run_pipeline.py --dry-run when available,
or at least parsing the YAML — before handing it back.
Instead of a repo, provide any OCI image your org can pull (e.g.
ghcr.io/acme/trainer:latest). Note that the worker execution path is built
around workflow jobs; treat image mode as secondary and prefer workflow mode
unless you know your deployment's workers handle images.
Both modes share the resource fields:
- GPU type — pick a specific card or "Any available".
- Min VRAM (GB) — floor on GPU memory, "No minimum" by default.
- CPU cores / Memory (GB) — CPU-side resources for the container.
Click Submit job. On success the modal closes and the fleet table immediately shows your job. Validation errors (bad repo host, missing fields) stay in the modal so you can correct them. If you instead see "job was not persisted — the backend has no database attached", the server accepted the request but had nowhere to store it — contact whoever operates your deployment.
Your job appears in the fleet table as job-#<id> and moves through these
states:
- awaiting scheduler — queued; no worker has picked it up yet.
- running on <worker> — a worker claimed it. The row shows which machine has it and the elapsed time, ticking live.
- finished — the row shows when it finished, how long it ran, what it
cost (a total, not an hourly rate — very small metered totals show as
<$0.01), and a status dot: green forsucceeded, red forfailed.
Each row also shows the job's spec (repo/ref/workflow or image), the GPU it requested, and — for long-lived instances — a utilization bar with the latest telemetry event.
A job that fails is automatically re-queued with exponential backoff (30s,
60s, 120s, …) up to its maximum attempt count; only after the last attempt
does it show as failed. Similarly, if a worker dies mid-job, a reaper
returns the job to the queue after ~10 minutes, so a stuck job recovers on
its own — just slower than you'd like.
Finished jobs — including failed ones — carry a
↓ download artifacts (.tar) link in their fleet row. That's a tar of
everything the job wrote to $OUTPUT_DIR, plus the run's log.txt, so a
failed job's logs are your first stop when debugging. The download link is a
time-limited URL valid for 7 days after the run; grab anything you need
within that window.
If your GitHub login is on the deployment's admin list, you get three extra controls (they don't exist in the DOM for anyone else, and every admin endpoint re-checks authorization server-side):
- Admin — swaps the fleet pane for a list of every account: plan, balance, live instances, and total job count. Click again ("Fleet") to switch back.
- Queue (inside the admin view) — shows the raw job queue across all users: each entry's owner, priority, attempt count, and whether it's unclaimed, claimed (and by which worker), or waiting out a retry backoff.
- Register worker — the web version of the
db/add_worker.pyCLI. Enter a machine name and click Generate token; the worker's bearer token is displayed exactly once (only its hash is stored), so copy it immediately. Tick Rotate to reissue a token for an existing machine — the old token stops working. Hand the token to the machine's spawner configuration; the worker then authenticates all its queue calls with it.
| Symptom | What it means |
|---|---|
Tiles show —, fleet pane says "unavailable" |
The backend has no database attached or can't reach it. Nothing is wrong with your account. |
| "Sign-in failed" banner on the landing page | The OAuth exchange failed (denied authorization, stale state, or server misconfiguration). Just retry. |
| Job submit rejected with a host error | Only github.com and gitlab.com repository URLs are accepted. |
| Job stuck on "awaiting scheduler" | No worker is free (or none matches your GPU/VRAM constraints). It runs as soon as one claims it. |
| Job failed | Download the artifact tar and read log.txt — the pipeline stops at the first failing step, and that step's output is at the end of the log. |
| Job disappeared and re-queued | Its worker died or stalled; the reaper returned it to the queue and another worker will retry it. |
The frontend is plain HTML/CSS/JS (public/) served by a FastAPI app — no
build step. In the GH-Auth repository:
cp .env.example .env # fill in GITHUB_CLIENT_ID / SECRET / SESSION_SECRET
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --reload --port 3000You'll need a GitHub OAuth App with callback URL
http://localhost:3000/auth/callback. Without a DATABASE_URL, sign-in works
but the console shows its "unavailable" state everywhere; apply
db/schema.sql to a Postgres instance and optionally run db/seed_demo.py
to see example fleet data. Check GET /api/health to confirm database
connectivity.