# WAI Extension: Quantum Providers (`wai.quantum.provider/1`) Status: **Draft.** Client, reference provider and conformance checker: `wai-quantum-qpu` (`src/open_provider.rs`, `src/reference.rs`, `src/conformance.rs`); vectors: `wai-quantum-qpu/conformance/provider-v1.json`. The engine's side of a provider job: `wai-quantum/src/engine/provider.rs`. Keywords **MUST**, **MUST NOT**, **SHOULD** and **MAY** are RFC 2119/8174. ## 0. What this extension adds Bring your own quantum. WAI provides the engine, the free job service and the verifier at no cost, and never pays for or resells time on anyone's hardware. A person connects their own quantum hardware, provider accounts, on-premises devices or simulators, and every surface treats a provider's result as it treats a WAI result: planned, its cost shown before it is submitted, checked, and sealed in a receipt. This text is the protocol a device speaks so that it can be brought without a client written for it: JSON messages, one request and one answer at a time, over HTTP or to a local process (§2). Six operations (§5) describe the device, estimate a job, submit it, follow it, read its counts and cancel it. A program travels in the device's own native gates, with every angle an exact fraction of a turn (§4), so the program the device runs is the one the client compiled and checked. A provider is the program or service that answers the messages. A device is what it runs programs on: a processor, an emulator or a simulator. A client is what sends the messages; a host is the client's process, which holds the person's credential and signing key (the command line, the desktop app). ## 1. Principles 1. **The credential stays with the person.** A host keeps a provider's credential in the operating system's keychain (or, where there is none, a file only its owner can read, labelled as such) and sends it only to that provider (§2). A credential MUST NOT appear in a browser page, a log, a receipt, a command line, an answer the host prints, or the hosted job service. The job service never routes a job to a provider and never takes a credential: a request that names a provider is refused there. 2. **Cost before submit.** A client MUST show a job's estimate (§5.2) before it submits the job, and MUST NOT submit a job that is not free without the person's explicit confirmation (§7). An unknown cost is not free. 3. **Exact programs.** A client compiles a circuit exactly to the device's native gates and connectivity and checks the compiled program against the circuit before sending it. Angles cross the wire exactly (§4). 4. **Measured, not recomputed.** A provider's counts are sealed as measured on the device it names (§8). Nothing recomputes them. ## 2. Transports Every message is a JSON object, and so is every answer. A client sends one message and reads its answer before it sends the next on the same transport. ### 2.1 HTTP A message is the body of a `POST` to the provider's endpoint (one URL for every operation), with `Content-Type: application/json`. The answer is the body of a `200` response, also for an answer with `"ok": false` (§3). Any other status is a transport failure, not an answer. A credential, when there is one, goes in `Authorization: Bearer `. A client MUST NOT send a credential over `http://` to any host but this machine (`localhost`, a loopback address); over the network it uses `https://`. ### 2.2 A local process The provider is a program the person names, with its arguments, started by the client without a shell. Each message is one line of JSON on the program's standard input; each answer is one line of JSON on its standard output, in order. The program MUST write nothing else to its standard output. Its standard error is its own log, and a client MAY discard it. The program runs until its standard input ends. A credential, when there is one, is in the program's environment as `WAI_PROVIDER_CREDENTIAL`, never on its command line. A client SHOULD NOT pass the program another provider's credential. ## 3. Messages and errors Every message carries: | field | | |---|---| | `protocol` | `"wai.quantum.provider/1"` | | `op` | `describe`, `estimate`, `submit`, `status`, `result` or `cancel` | | `settings` | optional: the person's non-secret settings for this provider (a device name, a region), passed as they are | and the operation's own fields (§5). Every answer carries `"ok": true` and the operation's fields, or: ```json { "ok": false, "error": { "code": "not_found", "message": "no job j-17" } } ``` The `message` is for a person; it MUST NOT quote a credential. The codes: | code | when | |---|---| | `invalid` | the message is malformed: a field missing or of the wrong type, a program that does not read (§4), zero shots, a message without `protocol` | | `unsupported` | an operation the provider does not offer (`cancel`, `estimate`), or an `op` it does not know | | `unsupported_version` | a `protocol` other than this one | | `unauthorized` | no credential, or one the provider does not accept | | `not_found` | a job the provider does not hold | | `refused` | a well-formed program the device cannot run: a gate outside its basis, a qubit it does not have, a pair it does not connect, too many shots; or a result asked of a job that has not finished or was cancelled | | `quota` | the person's account cannot pay for or schedule the job | | `unavailable` | the device is offline or in maintenance | | `internal` | anything else | ## 4. The program `estimate` and `submit` carry a `program` in the device's own gates, on its physical qubits: ```json { "format": "wai.native/1", "basis": "rzsxxcz", "bits": 2, "gates": [ { "gate": "rz", "qubits": [0], "turns": [[1, 2]] }, { "gate": "sx", "qubits": [0] }, { "gate": "cz", "qubits": [0, 1] } ], "measure": [{ "qubit": 0, "bit": 0 }, { "qubit": 1, "bit": 1 }], "qasm3": "OPENQASM 3.0; …" } ``` - `basis` is one of three gate sets, and every gate is of it: | basis | gates | |---|---| | `rzsxxcz` | `rz(t)`, `sx`, `x`, `cz` | | `prxcz` | `prx(t, p)`, `cz` | | `gpizz` | `gpi(p)`, `gpi2(p)`, `zz(t)` | - An angle is a fraction of a full turn, `[m, k]` meaning `m/2^k` of 2π, with `0 ≤ m < 2^k` and `k ≤ 32`, so every angle crosses the wire exactly. The gates, as operators on the named qubits, up to a global phase: - `rz(t)` = exp(−iπt·Z); `sx` = √X; `x` = X; `cz` = diag(1, 1, 1, −1); - `prx(t, p)` = Rz(2πp)·Rx(2πt)·Rz(−2πp); - `gpi(p)` = Rz(2πp)·X·Rz(−2πp); `gpi2(p)` = Rz(2πp)·Rx(π/2)·Rz(−2πp); - `zz(t)` = exp(−iπt·Z⊗Z). - `measure` gives, for each classical bit `0 … bits − 1`, the physical qubit it reads at the end of the program. Every bit reads exactly one qubit, and no qubit is read twice. - `qasm3` is the same program as OpenQASM 3 on physical qubits (`$p`), the `prxcz` and `gpizz` gates defined in it from the standard gates, for a provider that reads OpenQASM. It is informative: where the two differ, the `gates` are the program. A provider MUST refuse (`invalid`) a program of another `format`, a gate outside its `basis`, an angle out of range, or a bit that reads no qubit; and (`refused`) a program in a basis its device does not run, or on qubits or pairs it does not have. ## 5. Operations ### 5.1 `describe` No fields. The answer describes the device: | field | | |---|---| | `protocol` | `"wai.quantum.provider/1"` | | `provider`, `device` | the provider's name for itself, and the device's | | `provenance` | `silicon` (a processor), `emulator`, or `simulator`; with `simulator` (or `emulator`) a sentence naming what it is | | `qubits` | the device's qubit count | | `native_gates` | the gates it runs; a client compiles to the first of the bases of §4 whose every gate is listed | | `connectivity` | `"all"`, or the pairs `[a, b]` a two-qubit gate may act on, in either order | | `durations` | optional: `{"unit", "gates", "measure"}`: the unit (`ns`, `us`, `ms` or `s`), each native gate's duration wherever it runs (`{"sx": 35, "cz": 68}`), and the measurement's | | `calibration` | `{"id"}`, the calibration snapshot the device is under; optionally `qubits` (`{"qubit", "readout_error", "error_1q", "t1_us", "t2_us"}`) and `edges` (`{"qubits": [a, b], "error"}`), each error from 0 to 1 | | `billing` | `{"free": true}`, or `{"unit", "price", "currency"}`: the billing unit (`shot`, `job`, `second`, …), the price of one as a decimal string (`"0.0003"`), and the currency or credit it is in; optionally `free_tier`, the free use offered and its limits, as the provider states them | | `max_shots` | optional: the most shots a job may ask | | `seeded` | optional: `true` when a job's `seed` makes its counts reproducible (a simulator) | A client MUST NOT take a device to be free unless `billing` says `"free": true`, or an estimate says so (§5.2). A client MAY read a `describe` answer whose `calibration` states errors as a device target (`wai.quantum.target/1`, `wai-quantum/src/engine/targets`) and place the program on the qubits and pairs most likely to succeed. The program it sends is checked against the circuit as every program is (§1), and the receipt names how it was placed (§8). ### 5.2 `estimate` Fields: `program` (§4), `shots`. The answer, before anything is submitted: | field | | |---|---| | `free` | whether this job is free | | `amount`, `currency` | when it is not: the cost, the amount as a decimal string | | `unit` | optional: the billing unit the cost counts | | `seconds` | optional: how long it is expected to take, queue included | | `note` | optional | A provider that cannot estimate answers `unsupported`; the client then treats the cost as unknown (§7). ### 5.3 `submit` Fields: `program`, `shots`, and optionally `seed` (a seeded simulator uses it; a device ignores it). The answer's `job` is the job's id. ### 5.4 `status` Field: `job`. The answer's `status` is `queued`, `running`, `done`, `failed` or `cancelled`, with an optional `reason`. ### 5.5 `result` Field: `job`. The answer, once the job is `done`: | field | | |---|---| | `counts` | an object from outcomes to counts (§6), summing to the shots | | `shots` | the shots | | `calibration` | optional: the calibration snapshot the job ran under, when it is not the one `describe` names | | `charged` | optional: `{"amount", "currency"}`, what the job cost | | `energy` | optional: `{"kwh"}`, the provider's own figure for the job's energy | A `result` asked twice of the same job MUST give the same counts. ### 5.6 `cancel` Field: `job`. The answer's `status` is `cancelled`, or `done` for a job that had already finished. A provider that cannot cancel answers `unsupported`. ### 5.7 A job outlasts its client A device's queue can outlast any process waiting on it, so a job is not tied to the connection or the process that submitted it. A provider SHOULD answer `status` and `result` for a job's id from any later connection, and SHOULD keep a finished job's result for as long as it can, and answers `not_found` for one it no longer holds. A provider that runs as a local process (§2.2) ends with its client unless it keeps its jobs itself. A client: - SHOULD record a job before it submits it, and the job's id as soon as it has one, with the program it sent, since a device's calibration can change before the counts are read; - MUST NOT cancel a job only because it stopped waiting for it, unless it keeps no record to come back to; - MUST NOT submit a job again when its `submit` did not complete, without the person's confirmation: the first may have been taken, and may be billed. ## 6. Counts and bit order An outcome is a string of `bits` characters, `0` or `1`, the highest bit first: its last character is bit 0. `"01"` is bit 0 read as 1 and bit 1 read as 0. This is OpenQASM's order, and the job service's. The client compiles each logical qubit `i` of the person's circuit to end on the physical qubit that bit `i` measures, and reads the counts back onto the program's own classical bits. The first job on a new provider SHOULD be the probe (an `X` on qubit 0, every qubit measured), whose counts must all land on `…01`; the conformance vectors begin with it (§9). ## 7. Cost and confirmation A job is free when the provider's estimate says `"free": true`, or, for a provider that gives no estimate, when its `billing` says so. Every other job, an unknown cost included, is not free. A client: - MUST show the estimate (or that the cost is unknown) before submitting; - MUST NOT submit a job that is not free without the person's explicit confirmation for that run: a command-line flag (`wai-quantum run --provider NAME --yes`), an agent tool's argument the agent sets only after the person agreed (`run_on_provider` with `confirm_cost: true`), or a dialog (the desktop app); - MUST NOT hold a standing confirmation across runs. A tool that can submit a job that is not free MUST say in its description that it can spend the person's money. ## 8. The receipt The host seals each provider job as the job service seals its own: an Ed25519 signature, by the host's key, over the request as submitted and the result as returned, each in its RFC 8785 canonical text, verified by the same verifier. The result's `method` is `provider` and its accuracy class is `measured`. It carries: - `provider`: the person's name for it, its kind, its protocol, the device, its provenance, and the BLAKE3 hash of its `describe` answer; - `calibration`: the calibration snapshot the job ran under; - `compiled`: the basis, the BLAKE3 hash of the compiled program's OpenQASM 3 text, its gate and swap counts, its initial and final layout, the check it passed before it was sent, and how it was placed (`placement`: with a calibration read as a target, the target's hash, the layout's expected success and the fewest-swaps layout's); - `job`: the provider's job id and the status checks before it finished; - `counts`, keyed by the program's classical bits (§6); - `ideal`: the circuit's exact distribution set beside the counts (up to 22 qubits): the total variation distance, the Hellinger fidelity, and the distance the shots alone would leave on average; - `cost`: the estimate shown before submitting, what the provider says it charged, and whether the person confirmed a run that was not free; - `energy`: unmetered here, with the provider's own figure, labelled as its own, when it gives one. What the receipt proves: that this result, with these counts, is what the signer's host received for this request from the provider and device it names, under the calibration snapshot named, for the compiled program whose hash it carries; and that the host compiled that program exactly and checked it against the circuit before sending it. What it does not prove: that the device ran that program, or ran anything. The counts are the provider's word, and they are not reproducible by recomputation: the same job run again gives other counts. The `ideal` distance says how far they are from what the circuit gives; a distance well above the sampling distance is the device's error, or a program the device did not run as sent. ## 9. Conformance `wai-quantum-qpu/conformance/provider-v1.json` holds the vectors: - **messages**, sent as they are: an unknown operation (`unsupported`), another version (`unsupported_version`), a message without its protocol (`invalid`), a job that does not exist (`not_found`), malformed programs (`invalid`); - **programs**, each compiled to the device, estimated, run with seed 1, and checked by its counts: the probe, bits named out of order, a phase only interference shows, a phase kicked back by two quarter-turn controlled phases, a Bell pair, and a triangle of two-qubit gates that needs routing on a line. The thresholds leave room for a device's noise; - **reference**: the counts the reference provider gives for each program at seed 1, so its determinism is held on every platform. The checker (`wai-quantum providers test NAME`) also checks the `describe` answer in full, that zero shots are `invalid`, and that a program in another basis is refused. It runs the programs only when every estimate says free, or the person allows the spend (`--yes`). The reference provider (`wai-quantum providers reference --stdio`, or `--http ADDR`) is this protocol around WAI's exact simulator. It is the provider the tests run against over both transports, and the template to copy: a provider for any device is the reference with its simulation replaced by the device. ## 10. Versioning `wai.quantum.provider/1` is this text. A later version is a new `protocol` string; a provider that speaks only this one answers `unsupported_version` to another. Fields marked optional may be added to answers without a new version, and a client MUST ignore fields it does not know.