Become the first admin
Where: the server's environment, then/loginAccounts live in Firebase Authentication; roles live in the simulator's own user directory. The very first admin is promoted by email at boot:
- Set that variable on the deployment (Railway → your service → Variables) and redeploy.
- Create a Firebase user with the same email in the Firebase console (Authentication → Users → Add user), or sign in once through the app's sign-in page after the account exists.
- Sign in at
/login. The first sign-in with that email, while no admin exists yet, makes you admin. The menu bar then shows Dashboard, which goes to/admin.
The dashboard opens on Overview: people by role, cohorts, sessions with how many are active, submitted, in progress or not started, coverage meters for personal Groq keys and repo-host tokens, a 14-day activity chart, sessions by scenario and level, and the most recent sessions.
Set the environment variables that make interviews work
Where: Railway → Variables. The full list is indocs/HOSTING.md and DEPLOYMENT-PLAN.mdGrouped by what breaks if they are missing.
| Variable | What it is for | Interview season |
|---|---|---|
| LLM_PROVIDER GROQ_API_KEY | Which language model runs the personas and the grader, and the classroom key for it. Groq is the hosted default: fast and cheap. | Required. Create the key at console.groq.com/keys (a paid tier avoids the free rate limit for a whole class). |
| BYOK_SECRET | Fernet secret that encrypts challengers' own Groq keys and GitHub / GitLab tokens at rest. Any long random string. | Required before anyone saves a key. Changing it later invalidates every stored key. |
| AUTH_MODE FIREBASE_PROJECT_ID, FIREBASE_WEB_API_KEY, FIREBASE_AUTH_DOMAIN, FIREBASE_CREDENTIALS_JSON | firebase turns on sign-in. The web key verifies tokens; the service-account JSON lets the server create users and send set-password emails. | Required. Paste the service-account JSON as the variable's value or mount it as a file. |
| PERSISTENCE | firestore when hosted. Sessions, transcripts, submissions, browser-edited files and user records all live there. | Required. SQLite is for local runs only; never add a third database. |
| PUBLIC_BASE_URL | The public origin, so set-password emails link back to the right /login. | Set it to the class URL. |
| WORK_MODE | auto infers hosted mode from the Firebase and Firestore settings. Hosted mode hides the local dev-box path for build scenarios. | Leave on auto. Interview scenarios behave the same either way. |
| GITHUB_TOKEN | Classroom-wide token for reading learners' public repos in build scenarios. No scopes needed. | Optional for interviews. Set it anyway; it costs nothing. |
| LLM_FALLBACK_PROVIDERS | Comma-separated providers tried in order when the primary hits a rate limit or outage, e.g. anthropic (needs that provider's key too). The Deployment panel shows failovers as they happen. | Recommended for a class: one bad minute at Groq should not stall twenty interviews. |
| GITHUB_OAUTH_CLIENT_ID | A GitHub OAuth app (Settings → Developer settings → OAuth Apps, with Device flow enabled) so challengers can connect their account and edit their fork from the Files app. Step 8b below walks through creating it. | Only for build scenarios. Without it, Files stays read-only for repos. |
| GITLAB_URL | A self-hosted GitLab (e.g. https://labs.gauntletai.com). Once set, repo links on that host are accepted next to GitHub ones, read through its API, and the Files app can browse them. | Only if your starters live on a GitLab. Public projects need no token at all. |
| GITLAB_TOKEN | Classroom-wide token for that instance (a personal or group access token with read_api). Raises the anonymous rate limit and lets the grader read internal projects. | Optional. Never needs api: the classroom token must not be able to write. |
| GITLAB_OAUTH_CLIENT_ID | The Application ID of an OAuth application on that GitLab (Admin Area → Applications, Confidential unticked, scope api) so challengers can Connect GitLab and edit their project from Files. Device flow needs GitLab 17.2 or newer. Step 8b below walks through creating it. | Only for build scenarios on GitLab. Without it, Files stays read-only for those projects. |
| GRADER_CALIBRATED | Removes the "directional" caveat from grades once the grader has been checked against human scores. | Leave unset until someone has run the calibration harness with real instructor scores. |
*-firebase-adminsdk-*.json, sim.db, and .sandboxes/ are git-ignored on purpose. Paste secrets into Railway, never into the repository.Create instructors and challengers
Where: UsersOne form creates an account: name, email, an optional password, a role (challenger, instructor, admin), and optionally a cohort. Leave the password blank and the person receives a set-password email with a link to choose their own; that is the normal path.
- Instructors get the instructor dashboard and see only the sessions they create.
- Challengers get a dashboard listing the sessions assigned to them and a Settings page for their name and personal keys.
- Edit changes name, email, password, role, or cohorts. Delete removes the account from both the directory and Firebase.
The Keys column shows, for each challenger, whether they have saved a personal Groq key, a GitHub token and (when a GitLab instance is configured) a GitLab token or connection (green with a tick). The Show filter above the table narrows the list to challengers missing one, which is the list to chase before a class.
Bulk import
Bulk import… takes a pasted list, one person per line: an email, Name <email>, or rows copied straight from a spreadsheet. Existing accounts are reused. Keep Email each new account a set-password link ticked so people can sign in without you handing out passwords.
Group challengers into cohorts
Where: CohortsA cohort is the unit instructors schedule against: one click creates an interview session for every member. Two ways to build one:
- New cohort from a pasted list… Name the cohort, paste the class list, and the accounts are created (or reused) and added in one go. This is the fastest route from a roster to a ready class.
- Create an empty cohort, then Open it and add members from the directory, or use the bulk box on the cohort page.
Inside a cohort you see the member list with Remove per person. Deleting a cohort does not delete its people.
Curate the scenario list
Where: ScenariosEvery scenario folder in the repo appears here with two controls:
- enabled: untick to hide a scenario from instructors' New session lists. For interview season, keep the twenty
iv_*scenarios on and hide the rest so the list stays short. - starter URL: the public repo hosted learners fork for a build scenario, on GitHub or on the configured GitLab instance. Interview scenarios leave this blank; they are edited and submitted in the browser. The instructor publishing steps are in
docs/PUBLISH-SCENARIOS.md.
Scenarios are data, and you can author them here. Edit YAML on any row opens the scenario in an editor: change a persona's voice, the reveal ladder (what unlocks when), the triggers, or the rubric weights; Validate checks it, Save makes it the version new sessions use, and Revert to file discards your edits. New scenario from a template copies any scenario under a new key and title (borrowing its starter folder) so a whole new interview is a few edits away. Files in the repo are never modified; your versions live in the database and win over the file with the same key.
See every session, and clean up
Where: SessionsThe admin list shows all sessions across all instructors: owner, assignee, scenario, level, status, message count, last activity. Replay via instructor opens the same replay view instructors use, including the drawn design diagram and grading. Delete removes the session and everything attached to it: transcript, unlock state, mail, tickets, submissions, browser-edited files, and its workspace folder.
Archive… keeps the ledger tidy at the end of a term: choose an age and the states to include (graded and reviewed by default), Preview the list, then Archive. Each session's markdown audit is stored and downloadable from the Archived sessions list below the table; the live session, transcript, files, grade and review are deleted.
Audit log
Where: AuditEvery administrative write is recorded: who, when, the action, the target, the HTTP result, and a summary when the action supplies one (session deletes, archives). It is append-only and cannot be edited from the app. Filter by person, action or target.
Settings: a health line and one default
Where: SettingsFour cards, plus a read-only Deployment panel.
- Classroom defaults. The engineer level a session gets when the instructor sets none (senior is the usual choice), and a one-line announcement shown as a banner on every challenger's dashboard. Blank hides it.
- Sign-in. Whether the sign-in page offers Create challenger account. Turn it off once your cohorts are imported: unknown emails are then told to ask their instructor, and only accounts you created can sign in.
- Grading. Whether grades carry the "directional, not calibrated" caveat, and the calibration check that earns the right to remove it: every session an instructor has reviewed is a fixture; Run calibration re-grades them with the live model and compares against the instructors' scores (total error, within-tolerance rate, rank correlation, per-criterion error). Mark grader calibrated only unlocks after a passing run. Aim for at least ten reviewed sessions across the quality range first.
- Deployment. Read-only: model and fallback providers, the running build, whether the classroom Groq key, GitHub token, GitHub connect app, GitLab instance, GitLab token, GitLab connect app and key-encryption secret are set, work mode, sandbox, public URL and bootstrap admin. If it shows password or sqlite on the hosted URL, the environment variables from step 2 are not being read.
Recount dashboards now clears the overview and session-list caches; they otherwise refresh themselves in the background about once a minute.
Keys, rate limits, and letting people bring their own
Where: Railway variables, plus each person's SettingsThe server has one classroom key per external service. Each challenger can add their own in Settings; the simulator then uses that person's key for their sessions and falls back to the classroom key for everyone else. Keys are validated on save, encrypted with BYOK_SECRET, and never displayed again.
| Service | Classroom key | Personal key | Get one |
|---|---|---|---|
| Groq (language model) | GROQ_API_KEY | Settings → Groq API key | console.groq.com/keys |
| GitHub (build scenarios) | GITHUB_TOKEN | Settings → GitHub token | github.com/settings/tokens, see GitHub's guide. No scopes needed. |
| GitLab (build scenarios on your instance) | GITLAB_TOKEN | Settings → GitLab token (card appears once GITLAB_URL is set) | On the instance: avatar → Preferences → Access tokens, scope read_api; see GitLab's guide. |
For an interview-only season the Groq key is the one that matters: every persona reply, reveal judgment, diagram, and grade is a model call, and a class submitting together is the peak. Ask challengers to add their own key before the day; the challenger guide walks them through it. The Overview's coverage meters and the Users tab's Show filter tell you who still has not.
Repo hosts: GitHub, your GitLab, and the Connect apps
Where: GitHub or the GitLab Admin Area, then Railway → Variables. Full detail indocs/REPO-HOSTS.mdBuild scenarios on the hosted server use a repo the challenger forks and pushes to. GitHub always works. A self-hosted GitLab (for example labs.gauntletai.com) works the same way once GITLAB_URL is set: links on that host are read through its API and the Workspace app shows its clone commands. Public repos need no token at all; the classroom tokens only raise rate limits and must stay read-only.
To let challengers edit their repo from the Files app, the server needs an OAuth application on each host so Connect GitHub / Connect GitLab can sign them in with a short code. No client secret is ever stored.
GitHub OAuth app
- GitHub → your avatar → Settings → Developer settings → OAuth Apps → New OAuth App (an organisation can own it instead).
- Name, homepage (your server URL) and any callback URL: the device flow never redirects, but the field is required.
- Tick Enable Device Flow. Register, copy the Client ID, set it as
GITHUB_OAUTH_CLIENT_ID.
GitLab OAuth application (GitLab 17.2 or newer)
- On the instance, as an administrator: Admin Area (wrench) → Applications → New application.
- Name it; put the server URL as the redirect URI (required, unused); untick Confidential; tick the api scope (the only scope that can commit through the API;
read_apiwould leave Files read-only). - Save, copy the Application ID into
GITLAB_OAUTH_CLIENT_ID, setGITLAB_URLto the instance root, redeploy.
Check on Settings → Deployment: the GitLab instance row shows the URL and both connect rows show set. GitLab sign-ins expire after two hours; the server keeps the refresh token and renews them quietly. If Connect GitLab answers unsupported grant type, the instance is older than 17.2; browsing and grading still work, only the browser editing needs the upgrade.
Make the set-password emails read like yours
Where: a terminal with the service account, or the Firebase consoleNew accounts get Firebase's password-reset email. A small tool reads and updates its sender name and subject:
The daily sending limit from step 3 applies to these emails too; the plan, not the template, decides how many go out.
Firebase only honours a custom message body once a sending domain is verified under Authentication → Templates in the Firebase console; until then the stock body is used and the tool says so. The link inside always returns people to your /login because of PUBLIC_BASE_URL.
Before the season: a checklist
Run through once, a week before the first cohort- Settings header shows firebase and firestore; the project id is yours.
- Create a test challenger with your own alternate email; confirm the set-password email arrives and the link lands on
/login. - Count the accounts you will create. More than about a hundred in one day needs the Blaze plan on the Firebase project before you import.
- Create one instructor account and sign in as them; create an interview session; open it as the test challenger; ask one question and see a reply.
- Save a personal Groq key on the test account and confirm it is accepted; remove it.
- Hide the scenarios you are not running.
- Running build scenarios? Set the starter URLs, then open one as the test challenger, link a fork, and press Connect GitHub or Connect GitLab once to prove the OAuth apps work.
- Build the cohorts from the rosters with New cohort from a pasted list.
- Send the instructor guide to instructors and the challenger guide to the class.