Engineering Flight Simulator Admin
Standing up the hosted simulator for interview season

Keys in, accounts made, cohorts ready, and nothing shared that should be private

The admin owns the deployment and the people in it. This guide goes from the environment variables that make the interviews work, through creating instructors and challenger cohorts, to the dashboard tabs you will use every week.

01

Become the first admin

Where: the server's environment, then /login

Accounts live in Firebase Authentication; roles live in the simulator's own user directory. The very first admin is promoted by email at boot:

AUTH_BOOTSTRAP_ADMIN_EMAIL=you@yourschool.edu
  1. Set that variable on the deployment (Railway → your service → Variables) and redeploy.
  2. 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.
  3. 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.

Only the first admin needs thisFurther admins are created from the Users tab like anyone else, with the role set to admin. Remove the bootstrap variable afterwards if you like; it only acts while no admin exists.
02

Set the environment variables that make interviews work

Where: Railway → Variables. The full list is in docs/HOSTING.md and DEPLOYMENT-PLAN.md

Grouped by what breaks if they are missing.

VariableWhat it is forInterview 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_SECRETFernet 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.
PERSISTENCEfirestore 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_URLThe public origin, so set-password emails link back to the right /login.Set it to the class URL.
WORK_MODEauto 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_TOKENClassroom-wide token for reading learners' public repos in build scenarios. No scopes needed.Optional for interviews. Set it anyway; it costs nothing.
LLM_FALLBACK_PROVIDERSComma-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_IDA 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_URLA 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_TOKENClassroom-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_IDThe 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_CALIBRATEDRemoves 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.
Keep secrets out of git*-firebase-adminsdk-*.json, sim.db, and .sandboxes/ are git-ignored on purpose. Paste secrets into Railway, never into the repository.
03

Create instructors and challengers

Where: Users

One 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.

Firebase's email quotaSet-password emails are sent by Firebase, which allows 150 per project per day on the free (Spark) plan and 10,000 with a billing instrument attached (Blaze). Test sends, resends and "Forgot password?" all count. Past the limit, rows fail with QUOTA_EXCEEDED : Email quota exceeded; the Firebase account is created but the person is not saved in the simulator's directory, so delete them under Authentication → Users in the Firebase console and import them again once the quota resets or after moving to Blaze. Current usage is under Google Cloud console → IAM & Admin → Quotas, filtered on "Identity Toolkit". For a class of more than a hundred, attach billing before import day.
Resending a linkOpen a person's Edit panel and press Send reset email. That is the answer to "my email never arrived".
Create instructors firstCohort sessions are created by an instructor, so at least one instructor account must exist before class day. Make yourself one if you are also teaching; an admin can use the instructor dashboard directly.
04

Group challengers into cohorts

Where: Cohorts

A cohort is the unit instructors schedule against: one click creates an interview session for every member. Two ways to build one:

  1. 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.
  2. 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.

05

Curate the scenario list

Where: Scenarios

Every 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.

06

See every session, and clean up

Where: Sessions

The 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.

Delete is permanentSingle-session Delete has no undo and keeps no audit. Prefer Archive for finished work.
06b

Audit log

Where: Audit

Every 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.

07

Settings: a health line and one default

Where: Settings

Four 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.

08

Keys, rate limits, and letting people bring their own

Where: Railway variables, plus each person's Settings

The 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.

ServiceClassroom keyPersonal keyGet one
Groq (language model)GROQ_API_KEYSettings → Groq API keyconsole.groq.com/keys
GitHub (build scenarios)GITHUB_TOKENSettings → GitHub tokengithub.com/settings/tokens, see GitHub's guide. No scopes needed.
GitLab (build scenarios on your instance)GITLAB_TOKENSettings → 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.

08b

Repo hosts: GitHub, your GitLab, and the Connect apps

Where: GitHub or the GitLab Admin Area, then Railway → Variables. Full detail in docs/REPO-HOSTS.md

Build 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

  1. GitHub → your avatar → Settings → Developer settings → OAuth Apps → New OAuth App (an organisation can own it instead).
  2. Name, homepage (your server URL) and any callback URL: the device flow never redirects, but the field is required.
  3. Tick Enable Device Flow. Register, copy the Client ID, set it as GITHUB_OAUTH_CLIENT_ID.

GitLab OAuth application (GitLab 17.2 or newer)

  1. On the instance, as an administrator: Admin Area (wrench) → Applications → New application.
  2. 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_api would leave Files read-only).
  3. Save, copy the Application ID into GITLAB_OAUTH_CLIENT_ID, set GITLAB_URL to 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.

09

Make the set-password emails read like yours

Where: a terminal with the service account, or the Firebase console

New accounts get Firebase's password-reset email. A small tool reads and updates its sender name and subject:

python tools/email_templates.py show python tools/email_templates.py apply --app-name "Your course name"

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.

10

Before the season: a checklist

Run through once, a week before the first cohort
  1. Settings header shows firebase and firestore; the project id is yours.
  2. Create a test challenger with your own alternate email; confirm the set-password email arrives and the link lands on /login.
  3. 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.
  4. 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.
  5. Save a personal Groq key on the test account and confirm it is accepted; remove it.
  6. Hide the scenarios you are not running.
  7. 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.
  8. Build the cohorts from the rosters with New cohort from a pasted list.
  9. Send the instructor guide to instructors and the challenger guide to the class.