What's new new mark as read show all close
What is happening right now: everyone sitting an assessment today, and what each of them needs from you.
Book someone in
Booking costs nothing. Their environment builds itself before they arrive, and shuts down afterwards.
Every candidate environment this panel knows about, and what state each one is in. Open one to work on it, or register a new one.
-
A teardown that is past due is held, not failed: it will not run until that candidate's grading bundle has been collected.
Ending soon refresh
-
Collect the grading bundle before a scheduled teardown: the objective checks read the live lab and cannot be re-run afterwards.
Labs - refresh
Every registered lab: who is in it, what it is doing and how long is left. Open a lab to build, deploy, hand over or tear it down - every destructive control lives inside the lab, never here. Unticking live state skips the per-lab cloud call and shows registry data only.
| lab | state | candidate | left | up / cost | scheduled | |
|---|---|---|---|---|---|---|
| - | ||||||
-
A lab shown as unknown could not be read - it is not proof the lab is down. Do not rebuild over one.
Sizes are the platform's own instance types - t3.large on AWS, a flavour name on OVH, a server type on Hetzner. Leave blank for the platform default. On Proxmox, size is memory and is set under Settings.
Spot is far cheaper and is reclaimed with about two minutes' notice. The panel will therefore refuse to arm this lab, deploy a test to it, or hand it to a candidate - it is for rehearsals, soak tests and your own work. Rebuild the lab on-demand when it needs to take a sitting.
Registering a lab provisions nothing and bills nothing. Open the lab afterwards and build it. Up to 10 labs may exist at once.
Armed labs refresh
An armed lab has only its gateway running, so a sitting booked for Thursday is not billing four hosts from Monday. Send the candidate their link: signing in to the portal on that gateway is what builds the head node and the two compute nodes, and deploys the test.
| lab | test | phase | armed | expected start | candidate link |
|---|---|---|---|---|---|
| - | |||||
pending is the wanted state: armed, waiting for the candidate to arrive. building means they have arrived and the cluster is coming up. Only failed needs you.
Arm a lab
Builds the gateway for the chosen lab and holds it there until the candidate signs in.
The hiring name is for your records: it labels this lab, its recordings and its logs so you can tell whose results you are looking at. It is never sent to an AI model and is never written into a grading bundle, which carries the 8-digit candidate number only.
Arming starts a gateway build. That spends money from the moment you click, whether or not the candidate ever signs in. Disarming later does not refund it and does not stop it.
Who is booked in, on which day, for which assessment. Booking costs nothing until the day it runs.
Schedule 0 booked refresh
Book candidates against a day. At 09:00 on that day the lab's gateway is armed and the candidate's link starts working; if nobody signs in within 48 hours the hosts are torn down as a no-show and the booking is kept so you can re-book them. When the candidate does sign in, the rest of the lab builds from the baked image.
A booking spends nothing until its day. Only the 09:00 arming starts an instance, and that is the gateway alone - the head and nodes wait for the candidate.
One candidate per row, with a first row of column names: name,email,date, and optionally time and test. Dates as 2026-11-20 or 20/11/2026. With no time, the session is at 09:00. A blank test uses the one chosen below.
Set up a whole hiring round at once: say how many environments of each kind you need and register them in one go. Registering builds nothing and bills nothing.
Plan a round 0 labs refresh capacity
Describe a hiring round as groups - "three triage labs, four cloud-engineer labs, three storage labs" - and register them in one go. Each lab is managed individually afterwards, because candidates start, finish and reschedule separately.
Planning allocates from lab 1 upwards, so a panel with lab 0 registered has nine free. Lab 0 is an ordinary lab and can be removed like any other; it is simply never handed out by a plan.
Groups
No groups yet - add one to start planning.
Every group needs a test, a count and a platform. The platform is never inherited from the Settings tab - the person spending the money says where it goes.
Preview not previewed
See exactly which lab indices each group would take, and what the round would cost, before anything is registered. Preview spends nothing: no builds are started, no cloud APIs are called and no slots are reserved.
| group | test | platform | count | lab indices |
|---|
Cost estimate
| Labs to register | - |
|---|---|
| Hosts to build | - (four per lab: gateway, head, node01, node02) |
| Build time | - (15-25 min per lab; builds are not serialised by the panel) |
| Estimated spend | - per hour, all labs running |
| Per platform | - |
This is an estimate from the per-platform rates below, not a quote. Registering costs nothing; building is what spends. Apply registers the labs only - you still start each build yourself.
Apply nothing applied
Registers the previewed labs in the registry. Nothing is built and nothing is billed by this step. A plan is all-or-nothing: if any entry fails, every entry from this plan is rolled back.
Preview the plan first - Apply unlocks once a preview succeeds.
| lab | name | platform | test | outstanding |
|---|
Each lab still needs a build and then its test deployed. Open each one from the Board.
What the environments currently running are costing, and a check for anything still billing that this panel has lost track of.
Running spend refresh prices refresh
What the recorded resources would cost while they run. Read from the panel's own inventory and price table - no cloud call, nothing is contacted.
loading…
Show the price list these figures are built on
One row per machine size. A row marked unverified is a placeholder somebody typed, not a price fetched from the platform, and any total including it is a rough guide rather than a quote.
loading…
Records vs reality not checked run a reconcile
Compares every recorded resource against what each platform actually reports, and looks for resources matching a lab's naming convention that were never recorded. Those are orphans - usually the remains of a half-failed teardown, and still billing.
Slow by nature and not run automatically: one cloud lookup per recorded resource and per expected role, so a full estate is several dozen API calls, each able to sit on its own timeout.
| lab | resource | id | state | what it means |
|---|
A lab that could not be fully read is reported unreadable, and any missing verdict reached before the failure is withdrawn. Half a reconcile is not a reconcile.
Inventory - refresh
Every cloud resource the panel recorded creating, keyed by platform, kind and id. This record is what makes an abandoned lab's resources findable after its registry entry has gone.
| lab | platform | kind | id | role | size | recorded | |
|---|---|---|---|---|---|---|---|
| - | |||||||
Forget deletes the panel's record of a resource and nothing else. It destroys no instance, releases no IP and detaches no volume. If the resource still exists it carries on billing, and the panel can no longer tell you its id.
The candidate agreement candidates sign before they start, and everything you need to answer a request from a candidate about their data.
Candidate agreement refresh
-
Candidates sign this, by typing their full name, before they can start. The signed copy is kept on their record and is included in the download below. A new version takes effect only once it carries an approval (approved_by and approved_on) in control/php/agreement/, added through a reviewed change.
Download everything we hold about a candidate
For a subject access request. Gives one zip with a plain-English cover note and every record held about that person: bookings, name, attempts, transcripts, AI scoring and reports, follow-ups, archives, recordings and the log lines that mention them. Matched on the candidate number and the email address only, never on a name. Give both for a complete copy: bookings are held against the email, assessment records against the number. Check the download for anyone else's details before sending it.
Deleted after 6 months refresh
Everything about a candidate (their booking, name, work, recordings, scores, reports, the decision and the signed agreement) is deleted automatically 6 months after they sat, every night, on this machine and in the offsite copy. If the outcome is disputed, put the record on hold: a hold needs a reason and an end date. A candidate who sat with no date recorded is never deleted automatically; delete them from the candidate's page when they are no longer needed.
-
| record | sat | deleted on | status | |
|---|---|---|---|---|
| - | ||||
Recently deleted (no personal details are kept about what was deleted):
- -
Everyone who has ever sat, or is about to sit, an assessment here. Look a candidate up to see their attempts and collect their work.
Candidate roster refresh
Every candidate number seen across assigned names, test attempts and the current session. Click a number to open its detail. Dirty means the candidate has session recordings or test attempts (used); clean means unused.
| number | name | linked | attempts | status | |
|---|---|---|---|---|---|
| - | |||||
Candidate detail
Select a candidate from the roster above.
Name a session refresh
Each session gets a random 8-digit number, stamped into its recording and ASCII play-file names. Assign a name to a number to recognise the session when reviewing downloads.
-
| number | name | |
|---|---|---|
| - | ||
Screen recordings of every sitting, across every environment. Press load to fetch them; it takes a minute because each environment is contacted in turn.
Recordings, all labs - load
Every recording and play log across every registered lab, newest first. This one is slow and is not loaded automatically: it reaches each lab's gateway in turn, so it costs a cloud call plus an SSH round-trip per lab.
| lab | candidate | recording | size | date |
|---|---|---|---|---|
| not loaded - press load above | ||||
A lab that could not be reached is listed under the table, not skipped: a missing recording reads as "there is no evidence" when it means "we could not reach the box".
What one candidate actually did, and how it scored. This screen only reads: opening it cannot change or overwrite anything.
Evidence for a candidate refresh
Everything this control VM is holding for one candidate, in one place: the grading bundle, every scoring run, the session recordings, and the parked archive of their working files. Pick a candidate number to see what was captured and what is missing.
This screen only reads. It never re-collects, so opening it cannot overwrite a bundle or change what is on disk. Collecting is still done from Assessments › Candidates.
Evidence -
| evidence | state | detail |
|---|---|---|
| - | ||
Grading bundle
Scoring runs
Session recordings
Questions and panel decisions
Ask the model that scored this candidate a follow-up question, and record where the panel disagrees with it. Everything asked and everything decided is kept on the candidate's record, in the order it happened.
Only the candidate's 8-digit number ever reaches the model. Do not type their name into a question: it will be refused rather than sent, and there is no reason to risk it.
Parked archive
Restoring copies the parked files back onto a lab that has already been rebuilt. It does not recreate deleted instances, and it does not re-open the candidate's access window: grant any extra time separately, so the extra time stays a deliberate decision.
Everything held on disk, and removing it
Byte counts read from this control VM's own evidence directory. This is the list the delete buttons act on, so what you see here is exactly what would go.
Deleting evidence cannot be undone and there is no copy elsewhere. Each button asks you to type the name of the thing being removed, and the server checks the same words again before it deletes anything.
The technical detail of the one environment named in the bar above: which machines exist, whether they answer, how long it has been up and what it has cost.
Status …
-
Aborts the build, kills any hung build steps, and removes half-built hosts so nothing is left in a broken state. Always use this instead of Teardown while a build is running.
Tip: click a host row to open its VNC console (out-of-band; works even when SSH is down).
On-box timers (gateway report)
Get one candidate's environment ready for them to sit in. Work through the steps in order; each unlocks when the one before it is done.
Session setup
-
1Build the lab
Create the four hosts from bare OS (~15-20 min). Checks the server limit first.
-
2Deploy a test none
-
3Credentials
The lab passwords for this candidate. Read them out or paste them into the handout email; they are not stored in your browser.
Reveals the lab passwords in plain text so you can read the candidate's details out to them. Assessor-only values are marked in red - do not share those.
Candidate email …The text below is composed by the control VM from this lab. It carries the candidate's password. Send by email shows you the final message, exactly as it will go, before anything is sent. Bcc is blind and comma-separated - use it to keep your own record of what was issued.
You can edit this before sending - add a note, change the wording. The link, sign-in details and candidate reference must stay in, and the panel checks they are still there.
-
4Access window
When candidates may log in, and how long they get.
-
5Hand off
Give the candidate the portal URL and their credentials (over separate channels).
-
Start, pause and shut down the environment named in the bar above. Shutting it down destroys the candidate's machines, so collect their results first.
Power
Power the lab hosts on or off (compute stops when suspended; still billed).
Portal … refresh state
Start or stop the Guacamole portal (guacd), and change its public IP. Stopping it closes the portal for new candidate sessions; recordings and session files stay on disk and remain downloadable under Logs & audit.
Snapshot & reset
After a build succeeds the base build is snapshotted (day-scoped). Reset redeploys the four hosts from that snapshot on new IPs with fresh candidate credentials, in a few minutes, without a from-scratch rebuild.
Teardown
Delete the lab. Reset (above) is the fast way back; teardown requires a full rebuild. You will be asked to type the lab's name to confirm.
Teardown is explicit only. There is no auto-teardown timer: a lab destroyed on a timer cannot be given back when an assessor grants extra time. To end a candidate's session on a clock, use the access expiry below, which leaves the lab standing.
Session expiry
Close the candidate's portal at a chosen time. The lab keeps running and nothing is deleted, so an assessor can reopen the portal to grant extra time.
Holds the portal shut at the chosen time today. Fires unattended even with no browser open. The four lab hosts, the control VM and the gateway's public IP are all untouched.
Extend a running session
Add time to the end of the candidate's current access window. The time they have left is kept and the extension is added on top of it - this does not restart or replace the window.
Between 1 and 480 minutes. If the window has already closed, the extension runs from now. The candidate's in-session countdown on every lab host is moved to match.
What was recorded on the one environment named in the bar above, ready to watch or download.
Session logs refresh
Recordings + ASCII play logs from this lab's gateway - view or download here. For every lab at once, use Assessments › Recordings.
Every candidate's recordings, casts and transcripts as one archive (candidate-sessions-<date>.tgz).
- -
Build & audit log
The full build/provisioning log (for debugging a failed build) and the action audit trail (who did what, when).
Where this panel builds candidate environments. Enter each cloud account once; saving also proves the credential works.
Platform credentials - refresh
The cloud accounts this panel builds into. Enter them here rather than hand-editing config.php on the control VM. Saving also proves the credential works, so a mistyped token fails now instead of fifteen minutes into a build.
A blank secret means "leave unchanged" - it never erases the stored one.
Secrets are never sent back to your browser, so these boxes are always empty. Type a value only when you are replacing it. Non-secret fields (region, project, node) do accept a blank, which clears them back to the default.
Testing a Hetzner credential creates and immediately deletes one real server to check the account limit. That is why it happens on save, not on load.
Default platform for new labs refresh
The platform a lab is built on when it does not carry one of its own. Labs registered or planned through this panel record their own platform and are not affected by this setting. Lab 0 is an ordinary lab like any other: it has no special status here, and the control panel itself is separate infrastructure that this setting never touches. Changing it only affects labs that are torn down or not yet built.
The build script (build_cmd in config.php) is platform-specific and is not changed by this setting. If the two disagree, the build will target the wrong platform.
Lab guest memory refresh
How much memory each lab host is built with. Proxmox only, and it applies on the next build: a lab that is already running is not affected. On the other platforms, memory is a property of the server type.
Who may sign in to this panel and how. API keys for automation, and single sign-on for people.
API keys refresh
Bearer keys for programmatic access (debugging and deployment automation): send Authorization: Bearer <key> to api.php instead of signing in. Keys are shown once and stored hashed; revoke or regenerate any time.
| name | owner | created | last used | status | |
|---|---|---|---|---|---|
| - | |||||
Panel sign-in (Entra SAML) refresh
Operators and assessors sign in with Entra. Group membership sets the role: the admin group gets full control, the assessor group is read-only. SSH-key sign-in remains as an admin break-glass.
-
1Register this panel in Entra
Create a SAML Enterprise App in Entra, then download this panel's metadata and upload it there (it fills in the Identifier + Reply URL). Or enter these by hand:
Identifier (Entity ID) -Reply URL (ACS) - -
2Upload Entra's IdP metadata not configured
In Entra, download the Federation Metadata XML (it contains the sign-on URL and signing certificate), then upload it here.
-
3Groups and activation
Panel sign-in (Entra OpenID Connect) refresh
The OIDC alternative to the SAML sign-in above, using its own Entra app. Enabled adds an OIDC sign-in beside SAML; Enforced then hides SAML. They are separate switches so nothing is hidden until OIDC has been proven. SSH-key and API-key sign-in are never affected. Groups are matched by Entra object ID, not name.
| Redirect URI(s) to register in Entra | - |
|---|---|
| Issuer | - |
| Client secret | not set |
Portal SSO (Guacamole / Entra) refresh
Let assessors sign in to the candidate portal with Entra (a separate Entra app from the control panel). Candidates keep their local portal credentials. Group membership grants access. Applies on the next build or reset.
| Identifier (Entity ID) | - |
|---|---|
| Reply URL (ACS) | - |
Download the portal SP metadata XML and upload it to the portal Entra app (it prefills the Identifier + Reply URL), or set those two fields by hand. Then give the panel Entra's IdP metadata: either upload the Federation Metadata XML (recommended - no reachable URL needed) or paste the metadata URL below.
The web addresses people type: one for this panel, one per candidate portal. Kept pointed at the current servers automatically.
DNS / Cloudflare refresh
Stable hostnames for the control panel and the candidate portal, kept pointed at the current server IPs via Cloudflare. The panel repoints these on every build, reset and IP change; use Update DNS now to repoint them immediately.
The portal hostname is per lab. Enter the base name here once: lab 0 uses it as typed, and lab N gets -N on the first label (candidate-testing-3.ocf.co.uk for lab 3). Update DNS now writes only the record belonging to the lab currently in scope, and refuses outright if the two disagree, so it cannot repoint another lab's live portal. The control-panel hostname is one record for the whole panel.
Per-lab portals need more than DNS. Each lab's portal is published through its own named Cloudflare tunnel, so the token also needs Account / Cloudflare Tunnel / Edit and the account ID above must be set. Use Portal on a lab's row in the lab board to create or find that lab's tunnel, write its run token and point candidate-testing-N at it. Do this before building the lab: the build reads the token file as it starts. Without it the lab builds, the handout email quotes a hostname, and that hostname does not resolve.
The AI model that reads a candidate’s collected evidence and writes a report. A candidate’s name is never sent to it.
Automated scoring - refresh
Credentials for the model that scores a candidate's collected evidence. With a key set here, Send for scoring appears on a candidate in Assessments › Candidates and produces two reports: an internal suitability report scored against the rubric, and a separate public candidate feedback note that is safe to hand over.
The candidate's name is never sent to a model.
The payload carries the 8-digit candidate number only. The name is joined back locally when a report is rendered for a human. One honest caveat: the session transcripts are raw terminal text and are not redacted, so if a candidate typed their own name during the test (in a git config, a commit message, a file they wrote) that text is in the evidence and does go to the model. The panel never puts the name there itself.
This puts an AI credential on the control VM.
The original grading tool (provision/grade/grade.py) was designed to run on the admin laptop so this key never left it. Scoring from the panel is more convenient and keeps every run on one audited box, but the control VM also fronts the candidate portal and is therefore semi-public. Anyone who can reach the panel as an admin can spend this key. If that tradeoff is not acceptable, leave these fields blank and keep using grade.py from the laptop: everything else in the panel works unchanged.
A blank key means "leave unchanged" - it never erases the stored one.
Keys are never sent back to your browser, so these boxes are always empty. Type a value only when you are replacing it. The model boxes do accept a blank, which resets them to the built-in default.
Both providers are asked for the same structured schema, so the two are directly comparable and neither can return free text. The model boxes exist because a pinned model id goes stale: left blank, each falls back to the current built-in default.
Scoring is re-runnable and every run is kept, so a disputed score can be compared against an earlier one. Runs are stored per lab and per candidate under the evidence directory.
Make tests from the question bank: choose the topics and difficulty, see what that gives, then publish a test or create a hiring round, whose paper is fixed so every candidate in it sits the same questions.
Question bank - refresh
Start from a ready-made test or one saved earlier, then adjust it. Nothing here changes a test anyone is already booked on.
Length
Topics
Move a slider to give a topic more of the paper; 0 leaves it out. Beside each is how many questions the bank holds on it.
Difficulty mix
Always adds up to 100%: moving one slider shares the difference across the others.
Prefer questions written for
What this would give not yet
Adjust the settings above to see the paper they would make.
| Topic | Asked for | Would get | Questions | Points |
|---|
Save or use these settings
A saved preset appears in the list above and can be published or used for a round later.
Builds one paper from the selected preset and fixes it for the round. Book candidates onto it like any other test.
Hiring rounds and published tests
Each has its own fixed paper: the candidate's brief and the marking guide. Archiving hides one from the test list; its paper is kept, because past sittings, re-marking and subject access requests rely on it.
| Name | Kind | Built from | Created | Paper |
|---|
No hiring rounds or published tests yet.
Take an encrypted copy of everything a rebuild would not recreate, and put one back. Keep the passphrase somewhere other than the backup.
Backup and restore
Downloads everything a rebuild does not recreate: candidate records and history, the lab registry, the uptime / cost ledger, the DNS / SSO settings, your API keys, the assessor and lab SSH keys, the grading evidence and the session recordings. Per-build infrastructure IDs and per-cluster lab secrets are also captured, but a restore puts those in a quarantine directory instead of writing them back, so a restore after a fresh build can never point the panel at deleted infrastructure.
The backup is encrypted with a passphrase you choose. It contains API keys, the lab SSH private key and candidate names, so it will not be written without one. Keep the passphrase somewhere other than the backup: nothing else can decrypt it, and a lost passphrase is a lost backup.
Restore merges the backed-up records, registry, ledger, settings, keys and recordings over the current system. Per-build infra IDs and lab secrets are left in a quarantine directory, named in the output, for you to copy back by hand if you need them. The panel may need a refresh (Ctrl+R) afterwards.