This is what it actually took to get a Lovable-hosted research site talking to Quantinuum Nexus: the failures in the order they happened, the guards that survived, one costed dry run, and the list of things I would do differently. Every number here came out of a run in this repository.
Selene is the free offline emulator that ships inside guppylang. It runs in your own sandbox, costs nothing, and is where almost all of this programme's work happens. Nexus is the separate online lane: hosted emulators carrying the vendor error model, and — with the right access — real H-series hardware.
On a plain account the real QPUs simply do not appear in the device list. That is not an error to debug. Hosted emulators such as H2-1LE are visible and usable; if H1-1, H2-1 or H2-1SC are missing, you do not have hardware access yet, and no amount of config will conjure them.
The backends page is the ground truth, not the datasheet. Eleven backends are visible on this account and none of them is a QPU — every result on this site is therefore an emulator claim. Two details in this table bite if you ignore them: the widths are the emulator ceilings the account exposes (H2-Emulator shows 26 qubits even though H2-1 is a 56-qubit machine), and each family needs its own config class.
| Backend | Qubits | Type | Config class |
|---|---|---|---|
| H2-Emulator | 26 | Emulator (full H2 error model) | QuantinuumConfig |
| H2-1LE | 26 | Simulator (noiseless H2) | QuantinuumConfig |
| H1-Emulator | 20 | Emulator (full H1 error model) | QuantinuumConfig |
| H1-1LE | 20 | Simulator (noiseless H1) | QuantinuumConfig |
| Helios-1E-lite | 26 | Emulator (full Helios-1 error model) | HeliosConfig |
| aer_simulator | 26 | Simulator (Qiskit Aer) | AerConfig |
| aer_simulator_statevector | 26 | Simulator (statevector) | AerConfig |
| aer_simulator_unitary | 26 | Simulator (unitary) | AerConfig |
| QulacsBackend | 20 | Simulator (Qulacs) | QulacsConfig |
| Selene | 26 | Simulator (Selene, hosted) | SeleneConfig |
| SelenePlus | 26 | Simulator (Selene Plus) | SelenePlusConfig |
A name submitted through the wrong config class is accepted at job creation and then refused at submission with You do not have access to this machine (code: 14) — an access error for what is really a typing mistake. That is how the Helios leg was lost the first time. The preflight in quantum/backend_nexus.py now picks the class from the backend family, and an unreachable name prints the reachable list grouped the same way.
Reachable is not the same as usable. A backend can be visible, correctly configured, and still refuse shot-level output, and a backend wide enough on paper can be too narrow for the run in hand. Every dry run now prints this matrix before anything is submitted, built by asking devices.supports_shots(config) with the config class that backend's family requires — so the devices a sweep would silently skip are named up front rather than discovered one submission at a time.
| Backend | Kind | Width | Shots | Verdict |
|---|---|---|---|---|
| H2-Emulatorselected | emulator | 26q | unknown | unknown — no live session |
| aer_simulator | emulator | 26q | unknown | unknown — no live session |
| aer_simulator_statevector | emulator | 26q | unknown | unknown — no live session |
| aer_simulator_unitary | emulator | 26q | unknown | unknown — no live session |
| H1-1 | hardware | 20q | unknown | unknown — no live session |
| H1-1E | emulator | 20q | unknown | unknown — no live session |
| H1-1LE | emulator | 20q | unknown | unknown — no live session |
| H1-Emulator | emulator | 20q | unknown | unknown — no live session |
| H2-1 | hardware | 56q | unknown | unknown — no live session |
| H2-1E | emulator | 26q | unknown | unknown — no live session |
| H2-1LE | emulator | 26q | unknown | unknown — no live session |
| H2-2 | hardware | 56q | unknown | unknown — no live session |
| H2-2E | emulator | 26q | unknown | unknown — no live session |
| Helios-1 | hardware | 98q | unknown | unknown — no live session |
| Helios-1E | emulator | 26q | unknown | unknown — no live session |
| Helios-1E-lite | emulator | 26q | unknown | unknown — no live session |
| Qulacs | emulator | 20q | unknown | unknown — no live session |
| QulacsBackend | emulator | 20q | unknown | unknown — no live session |
| Selene | emulator | 26q | unknown | unknown — no live session |
| SelenePlus | emulator | 26q | unknown | unknown — no live session |
The snapshot above was written by python -m quantum.device_matrix --dump on 2026-08-26T00:52:33+00:00 from the pinned source, sized for 16 qubits. Two rules keep it honest. An unknown verdict is missing information, never a refusal: if the session is down or the config class is absent from the installed client, the device stays available and the submission path re-checks it. And the matrix is advisory only — the real gate is still the per-submissionsupports_shots preflight, because a table generated minutes ago is not a promise about the job you are about to pay for.
Login is a device-code flow. The call blocks until you open the URL it prints and approve the device; there is no way to script past that step. On success a token lands in ~/.qnx/auth and the Python client picks it up ambiently — you do not pass credentials to your own code, and you should not.
Two sandbox realities to plan for. Installing qnexus alone is not enough; it needs guppylang>=1.0 in the same place. And the sandbox home is ephemeral, so the login recurs — treat re-authentication as normal, and keep every script able to run in a dry lane without a session.
echo ".pydeps/" >> .gitignore # do this FIRST pip install --target .pydeps "guppylang>=1.0" qnexus PYTHONPATH=.pydeps python -c "import qnexus; qnexus.login()"
Each of these cost real time. They are listed roughly in the order they bit.
Turns kept dying with a bare "internal error", and every file written in that turn vanished.
The vendored Python tree at .pydeps was tracked, so each turn tried to save tens of thousands of files and blew the save budget.
Add .pydeps/ to .gitignore before the first pip install, then reinstall. The rollbacks stopped immediately.
ModuleNotFoundError: selene_core the moment a Nexus script imports anything.
qnexus was installed on its own; it expects the Guppy/Selene stack to be present alongside it.
Install guppylang>=1.0 in the same target directory as qnexus. Selene ships inside guppylang.
entry not found in database when starting an execute job.
The program ref handed to the execute call had never been through a compile job in that project.
Always compile first and pass the compiled ref, not the raw uploaded one.
TypeError from jobs.get() with what looks like a perfectly good job id.
The call is keyword-only; a positional argument is silently the wrong parameter.
Call it with named arguments, and wrap the lookup so a resumed sweep fails loudly rather than re-submitting.
Every shot classified as the wrong outcome; analysis looked like total decoherence.
Distribution keys are tuples of ints indexed by qubit, not strings — and the ordering is the mirror of the Qiskit convention many examples assume.
Run a one-gate X probe on each backend and each register shape before writing any analysis, and read the tuple by index.
A 25-qubit, 8192-shot noisy emulator job sat in RUNNING for about three hours, then raised TimeoutError.
The noisy hosted emulator has a practical sizing cliff around 17 qubits and around 2048 shots.
Stay under the cliff, batch several small programs into one job rather than firing many, and cache each row as it lands.
A backend rejected the config in a way that reads like an access-permission error.
Wrong config class — HeliosConfig and QuantinuumConfig are not interchangeable for a given device family.
Match the config class to the device family first; treat access errors as suspect until that is ruled out.
Quota check came back healthy, then the job was refused.
The quota meters cover CPU time and storage. They say nothing at all about whether you can afford the HQCs.
Treat max_cost as the only real spend guard, and estimate cost explicitly before submitting.
Every hardware path is inert unless an explicit confirmation flag is set. Cost estimation, quota reporting and device reachability all run in the dry lane, so a mistake costs nothing.
start_execute_job carries an explicit HQC ceiling. If the estimate creeps past it the submission is refused rather than quietly billed.
gate, driver, n_qubits, shots and seed are declared once on the project and stamped on each job, so a sweep stays queryable weeks later instead of being a wall of anonymous ids.
Job ids go into the row cache as soon as they exist, and `python -m quantum.resume` turns any of them — or a whole run found by its gate label — back into shots. Nothing is ever bought twice.
A 20-shot X probe pins which index is which before any analysis is written. It is the cheapest experiment you will ever run and it has caught real inversions.
One row of a sweep is one file. A timeout costs that row and nothing else, and the same files are what the site renders.
A hardware sweep loses its process more often than you would like — a rollback, a dropped websocket, a sandbox timeout. Nexus has still finished the jobs and still billed for them. Resubmitting to get those shots back is the single most expensive avoidable mistake on this lane, so recovery has to be one command rather than an ad-hoc script.
# ids in hand, from the console or a shipped dump python -m quantum.resume --backend hardware --job-id exec-1 exec-2 # ids recorded in a dump the sweep wrote before it died python -m quantum.resume --backend hardware --from-dump src/data/demos/x.json # no ids at all — find the run by the labels every submission stamps python -m quantum.resume --backend hardware --gate G16 --dry-run
Four details make the difference between a command that works months later and one that doesn't. The decode width comes from the n_qubits property stamped on the job, not from memory. A job that is not COMPLETED is reported with a plain reason and skipped, so one DEPLETED id does not abandon the other nine. The billed HQC from the original run stays in the meter, because a resumed sweep that reports itself as free has lost the true cost of the work. And the shots are cached on disk by job id, so the second resume is a file read and a driver's row loop consumes it exactly like a row it ran itself.
Recovery only works if you can see what is recoverable, so the saved-Ref store has its own audit view: the Ref ledger lists every persisted job by submission run, with its last known status and whether it still carries the sweep coordinates a re-attach needs.
Knowing a job ran is not the same as knowing what it cost. The estimate booked at submission and the figure Nexus actually billed are reconciled per job on the cost ledger, which flags any bill more than ten percent above its estimate and refuses to count a job with no bill back yet as free.
H2-1LE pinned the bit order conclusively, and every downstream reader was written against that result rather than against an assumption.A Lovable app's server code runs in a Cloudflare Worker. child_process is a stub that throws, and there is no arbitrary filesystem to read from. A Nexus call can therefore never live inside a server function, however tempting the "run experiment" button looks.
The working shape is a three-step loop: run Python in the sandbox, commit the result JSON into the repository, render a static view of that JSON. Every experiment page on this site is built that way. Keep each turn down to one committable artefact and a rollback costs you minutes rather than a day.
Free download for Lovable, Hermes and OpenClaw. Carries the nexus-jobs and nexus-admin cards so your agent starts with all of the above already known.
The experiment used as the dry-run payload, with its full emulator sweep.
How to re-run any figure on this site from a clean checkout.