Hardware overview
One process-wide HardwareManager registers eight providers — seven
connectable plus a non-functional Google scaffold; the kernel connects
simulator itself at startup, so a local target is always available.
All hardware state — connections, credentials, jobs — is shared across WebSocket connections. The wire protocol is on the hardware messages page.
What each provider accepts
Section titled “What each provider accepts”For hardware_submit, the kernel prepares a provider-appropriate payload
from your source code: the simulator takes raw source; real providers get
a circuit object extracted from the exec’d namespace, searched in a
provider-aware framework order; Q# is special-cased (QIR for Azure,
rejected elsewhere).
| Provider | Search order | What submit actually does |
|---|---|---|
simulator | n/a — raw source | Re-runs the source through the executor pipeline (any framework, Q# included). Completes synchronously. |
ibm | qiskit → cirq → cudaq | transpile(circuit, backend) + SamplerV2.run — effectively requires a Qiskit QuantumCircuit. |
ionq | qiskit → cirq → cudaq | qiskit-ionq backend.run(circuit, shots) — Qiskit circuits. |
braket | qiskit → cirq → cudaq | Passes the extracted object straight to AwsDevice.run — no conversion is performed; whether a Qiskit/Cirq object is accepted is up to the Braket SDK. |
azure | qiskit → cirq → cudaq | target.submit(circuit, shots) — translation relies on azure-quantum’s SDK plugins. Q# source is compiled to QIR first. Deep dive. |
quantinuum | qiskit → cirq → cudaq | Auto-converts Qiskit/Cirq circuits to pytket via pytket-extensions (clear install hint if missing), compiles, then process_circuit. |
nvidia | cudaq → qiskit → cirq | cudaq.sample on the selected GPU/CPU target — needs a CUDA-Q kernel (or (kernel, *args) tuple). Completes synchronously. |
google | — | Scaffolded only: connect always returns false; submission raises. Honest status: not usable yet. |
Q# programs can run on the simulator and Azure Quantum only; other providers reject Q# with a message suggesting Azure or a Qiskit rewrite.
Credential fields
Section titled “Credential fields”Keys the kernel’s connect() actually reads from the
hardware_connect.credentials object — scripted clients must use these
exact names:
| Provider | Required keys | Optional keys (default) | Get one at |
|---|---|---|---|
simulator | — | — | always available |
ibm | token | instance (ibm-q/open/main) | quantum.ibm.com |
ionq | token | — | IonQ console (or use Braket) |
nvidia | — | — | needs cudaq importable, nothing else |
braket | access_key_id, secret_access_key | region (us-east-1), session_token | AWS IAM with AmazonBraketFullAccess |
azure | subscription_id, resource_group, workspace_name | location (eastus) | portal.azure.com — no token field; auth uses the Azure default credential chain |
quantinuum | token (Nexus API token) | — | nexus.quantinuum.com — requires pytket-quantinuum<0.26 (see note below); the token is validated at connect with an authenticated device-list call (was accepted unvalidated before unreleased main) |
On a successful connect, non-empty credentials persist to the OS keyring, and the next kernel start auto-reconnects silently (failures are logged, not surfaced — the provider just shows as disconnected).
Job lifecycle
Section titled “Job lifecycle” hardware_submit │ ├─ routing/compilation raised ──────────▶ error envelope (no job exists) │ ▼ JobHandle ├─ provider error at submit ────────────▶ failed (terminal) ├─ synchronous provider (simulator, │ nvidia) ─────────────────────────────▶ complete (terminal) ▼ queued ──▶ running ──▶ complete (terminal) │ │ │ ├─ provider failure ─────────▶ failed (terminal) └──────────┴─ hardware_cancel ──────────▶ failed (no "cancelled" wire status)
any non-terminal status + kernel restart ──▶ stale (re-submit to run again) unrecognized provider state ───────────────▶ unknownTwo sharp conventions, both documented with replay-tested fixtures on the hardware messages page:
- Failed handle vs error envelope. A failed submission arrives either
as an
errorenvelope (no job exists) or as a normalhardware_job_submittedwhose handle hasstatus: "failed"— handle both. hardware_statusnever re-queries job status — it only refreshesqueue_position. Pollhardware_resultsto discover completion.
Persistence and the resubmit caveat
Section titled “Persistence and the resubmit caveat”Job metadata persists to ~/.nuclei/jobs.json (override the directory
with NUCLEI_DATA_DIR): atomic temp-file-and-rename writes, capped at 200
entries, terminal jobs pruned after 7 days — the prune runs on every write
and at kernel start when the store is loaded, so expired records
disappear even if you never submit another job. The write happens
immediately at submit, so a crash between submit and first poll can’t
orphan the record. Individual records can also be removed on demand with
hardware_dismiss.
What does not persist is the provider SDK handle. After a kernel restart:
- Terminal jobs (
complete/failed) keep their stored status — history survives. - Non-terminal jobs come back as
stale: polling can’t resume and results are unrecoverable through Nuclei — re-submit to run again. The job usually did keep running provider-side; you can find it in the provider’s own console, but note the kernel’s job id is a locally generated UUID, not the provider’s id, so cross-referencing is manual.