Skip to main content

WAI Extension: Quantum Answer Views

Mirrored from the canonical text at commit 260cbd09 ().

Status: Draft. Defines wai.quantum.answer/1, the answer view layer: a few view statements written beside an OpenQASM 3 program, each view’s numbers bound to an engine call written with @, so that an answer a model gives about a quantum program shows numbers the engine computed and never numbers the model wrote. The text is read line by line as it streams, every problem has a stable code with a defined outcome, and the calls the answer makes can be sealed and checked again later by running the answer. §9 records what the layer was measured to cost a model in tokens against plain OpenQASM and against a job’s JSON (29 to 32 percent more than the JSON for the same request), and how reliably a model wrote it (far less than a program alone). The syntax is not frozen. Reference implementation: wai_quantum::engine::answer; corpus: quantum-conformance/answers/. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

1. Scope and model

An answer is the text a model writes when it is asked to do something with a quantum program: the program, and what to show of it. In this layer the program is OpenQASM 3, unchanged, in a fenced block; what to show is a handful of view statements (Histogram, Verdict, Receipt, …), and every number a view shows comes from an engine call (@Sample, @Check, @Seal, …) that the host carries out on the engine:

root = Answer([bell, counts, check, seal])
```qasm bell
OPENQASM 3.0;
qubit[2] q;
bit[2] c;
h q[0];
cx q[0], q[1];
c = measure q;
```
counts = Histogram(@Sample(bell, 1000))
check = Verdict(@Check(bell, {"00": 0.5, "11": 0.5}))
seal = Receipt(@Seal([bell, counts, check]))

The trust boundary: a model writes only this declarative text. It writes no markup and no script, and the numbers it could get wrong (counts, probabilities, expectation values, a verdict) are not in its text at all. The host draws each view from the engine’s answer to its call; a view whose argument is a number the model wrote is refused (§6, J104). A renderer MUST NOT execute anything in an answer other than the engine calls of §5, and MUST NOT draw a number in a view that the engine did not give.

In scope: the text (§2), statements (§3), the views (§4) and engine calls (§5), the diagnostics (§6), reading as the text streams (§7), carrying the calls out and sealing them (§8), the token comparison (§9) and conformance (§10). Out of scope: how a host lays a view out, and the program language, which is OpenQASM 3 as the engine’s reader reads it.

2. The text

An answer is UTF-8 text, read a line at a time. A line ends at LF; a CR before the LF is not part of the line. The last line need not end with LF. Each line is one of:

Any other line is not read (J100). A name is a letter or _, then letters, digits or _.

The answer’s source is its text before its first marker, each line ending in LF (a CR before it dropped, and an LF added to a last line that has none). Its digest is the BLAKE3 of the source, as lowercase hex.

3. Statements

statement  = name ws "=" ws expr [ws "//" comment]
expr       = view | call | name | literal | list | object
view       = Name "(" [expr *("," expr)] ")"
call       = "@" Name "(" [expr *("," expr)] ")"
list       = "[" [expr *("," expr)] "]"
object     = "{" [key ":" expr *("," key ":" expr)] "}"
key        = json-string | name
literal    = json-number | json-string | "true" | "false" | "null"

A statement’s value is a view, an engine call, or a literal (a value: a number, a string, a list or an object, which an engine call’s parameter may name). Arguments are positional, in the order §4 and §5 give; null leaves out an optional one. There are no named arguments: seed=7 in a call is a line that does not parse (J100), and a reader SHOULD say that arguments go by position. A value that several calls take, such as the noise a sample and a verdict both run under, is declared once and passed by its name. A name used before the statement that declares it is a forward reference, and is allowed. A name declared again replaces the earlier declaration (J110): this is how an answer is edited, by writing the changed statement again.

The statement named root is the answer’s layout, root = Answer([...]), and SHOULD come first, so that a renderer can lay the answer out before its parts arrive. With no root, the views are drawn in the order they are written (J107).

4. Views

A view’s first argument is what it shows: an engine call of the kind the table names, written in place (Histogram(@Sample(bell, 1000))) or named (s = @Sample(bell, 1000) then Histogram(s)).

ViewArgumentsShows
Answerchildren: a list of views (a program in the list is drawn as its circuit)the root: the views, in order
Texttext: a stringa line of prose; a number in it is the model’s, and is warned of (J111)
Circuitprogramthe program’s circuit, from the engine’s layout, with the circuit in words
Histogram@Samplethe counts of a sample
Expectation@ExpectPauli expectation values, each with its accuracy
Amplitudes@Amplitudesamplitudes of basis states, with each one’s probability and phase
Bloch@Stateone qubit’s Bloch vector and purity
Verdict@Checkhow far the engine checked the program against its intent
Equivalence@Equivalentwhether two programs are the same circuit, and how the engine knows
Critique@Critiquethe program’s measures and smells
Estimate@Estimatea fault-tolerant resource estimate and its frontier
Compile@Compilethe program compiled through checked passes, and the bound on its distance
Plan@Planthe method a run would take, and why
Receipt@Seala seal over the statements it names, checked where it is shown

5. Engine calls

Each call is the engine’s own operation on the program it names. A parameter (shots, a seed, a qubit, a tolerance, ε, a budget) MAY be a literal or the name of a value statement; a call’s program is always a program’s name.

CallArgumentsThe engine’s operation
@Sampleprogram, shots, seed? (1), noise?a job of that many shots at that seed (under that noise, an object of channel probabilities as a job’s noise), planned and run: its method, why, the request and the result
@Expectprogram, observables (a list of Pauli strings, as "Z0 Z1")a job asking those expectation values
@Amplitudesprogram, states (bit strings, qubit n−1 first)a job asking those amplitudes
@Stateprogram, qubitthe debugger stepped through every gate, before the final measurements: the qubit’s Bloch vector, its length and its purity. A program that measures in the middle is refused
@Checkprogram, intent, tolerance?, noise?the verdict a job with that intent (and that noise) gets (wai_quantum::engine::verdict): a distribution of its measured bits as an object ({"00": 0.5, "11": 0.5}), a program it must equal (its name, or {"equivalent": name}), or one whose state it must prepare ({"prepares": name})
@Equivalentprogram, referencethe equivalence ladder: the same circuit up to a global phase, and how that was decided
@Critiqueprogramthe critique: measures and smells
@Estimateprogram, qubit? (a physical qubit’s name), budget?the resource estimate
@Compileprogram, epsilon?the compile, with its certificate
@Planprogramthe plan of a run of 1024 shots
@Sealstatements: a list of programs, calls, or views of a calla receipt over what they give (§8.2)

A call the engine refuses is a refusal with its codes (the job layer’s J…, a program’s Q…), for that view alone; the rest of the answer is drawn.

6. Diagnostics

Every problem has a code that never changes meaning once released. A program’s problems are the reader’s own codes (Q…), at the answer’s line. A statement’s are the job layer’s, J100 to J112, each with what becomes of the statement:

CodeOutcomeMeaning
J100droppedA line that is not a statement, a program, a comment or a marker.
J101droppedA view the layer does not have.
J102droppedAn @ call the engine does not have.
J103droppedA required argument missing.
J104droppedAn argument of the wrong kind: a number where a view takes the engine’s call (a number in a view comes from the engine), a list entry the list does not take (left out of the list), a root that is not Answer.
J105warnedMore arguments than the view or call takes: the extra ones are left out.
J106droppedA name no statement declares, or one whose statement was left out (a program the reader refused, say): the statement that names it is left out, and a list that names it is drawn without it.
J107defaultedNo root: the views in the order they are written.
J108droppedThe text ends inside a program or a statement.
J109droppedStatements that name each other in a cycle.
J110warnedA name declared again: the later declaration stands.
J111warnedA number in a view’s text, which no engine call gave (one with a decimal point, a per cent, or two digits or more).
J112warnedA statement the root does not reach: it is not drawn.

wai_quantum::engine::refusal holds this table with the job layer’s others (wai/wai-quantum/docs/job-refusals.md). An error is a diagnostic whose outcome is dropped or refused; the rest are notes. A host that sends an answer’s problems back to the model so that it can repair them SHOULD send the errors alone, each as line N: message [CODE], and never the notes.

7. Reading as the text streams

A reader takes the text in pieces as they arrive, in any split, and reads each line as it completes. After each piece it can give a snapshot: each statement read so far, ready (read, checked, and everything it names is ready), pending (it names a statement that has not arrived) or dropped (with the diagnostic that says why). The root is ready as soon as its line arrives, and the snapshot’s order holds every view it lists, those that have not arrived among them, so a renderer lays the answer out first. A view that is ready can be drawn, and its call carried out, at once.

When the text has ended, the reader gives the answer: a last line without its LF is read; a program or a statement the text ends inside is dropped (J108); a name still undeclared is J106; the root’s reach is checked (J112). A reader MUST give, for the same text, the same answer however it was split: the same statements, statuses, order, diagnostics and digest as reading the whole text at once.

8. Carrying the calls out, and sealing them

8.1 Values

A view’s value is its call’s; a Circuit’s is the engine’s layout of the program and its description in words; a program’s is {"qasm", "digest"}, its text and the BLAKE3 of it. A value MUST hold nothing the engine did not compute: a run’s time is the host’s, and a value MUST NOT carry it, so that running the answer again gives the same bytes.

8.2 The seal

@Seal([a, b, …]) signs, with the host’s key, as the job layer’s receipt does (Ed25519 over the BLAKE3 of the RFC 8785 canonical text), the request

{ "format": "wai.quantum.answer/1", "answer": "<the answer's digest>", "sealed": ["a", "b"] }

and the result {"statements": {"a": <value>, "b": <value>}}, the names those of the statements listed that are ready. Its value is in a job’s form, {"request", "result"} with the receipt in the result, so any surface that checks a job’s receipt checks it. A host with no key refuses a seal.

8.3 A stored answer

A stored answer is its source followed by a marker line carrying its seal’s receipt:

]]>wai:receipt {"statement":"seal","receipt":{"alg":"ed25519","canon":"rfc8785","key":"…","digest":"…","signature":"…"}}

To check it, read the answer, carry the sealed statements out again, build the request and the result of §8.2, and check the receipt over them. The engine is deterministic, so the same answer gives the same bytes on every build; a changed statement changes the digest or the values, and the receipt does not verify. A receipt shows that the key sealed these values for this answer; it does not show who holds the key.

9. What the layer costs in tokens

The corpus is quantum-conformance/eval/answers.json: the fourteen tasks of the model harness’s task set (eval/tasks.json), each answer written four ways by wai-quantum eval corpus, and held by a test to the task set, every answer in the layer reading with no diagnostic and running to its intents:

FormWhat it carries
plainthe program alone, in a fenced block
viewsthe layer whole: the program, its circuit, a sample when it measures, a verdict for each intent, a seal
views_requestthe layer with the sample and the verdicts alone
jobthe request an agent sends a run tool, as JSON with no white space

views_request and job carry the same program and the same request, so they are the comparison of the layer against JSON; plain is what a model writes when it is asked for the program alone. Noise that a sample and a verdict both run under is declared once in the layer and passed by name (§3), as the JSON states it once. eval/token_cost.py counted every answer with the vocabularies of three open-weight models, read from their GGUF files by a tokenizer tool (eval/token-cost.json). Totals over the fourteen answers:

Vocabularyplainviewsviews_requestjobviews_request / job
A, 49 159 tokens8412 3651 9671 4921.318
B, 248 320 tokens8342 3121 9281 4911.293
C, 262 144 tokens8352 2821 9401 4831.308

The means per answer are 60 tokens for the program alone, 163 to 169 for the layer whole, 138 to 141 for the layer with the request alone and 106 for the JSON. The three vocabularies, one a fifth the size of the other two, agree to within 3 percent on every ratio.

What the comparison shows:

The layer is not cheaper than JSON in tokens, and this draft does not claim it is. What it buys for the tokens is §1’s: the numbers an answer shows are the engine’s, never the model’s, and the answer is drawn as it streams.

9.1 How well a model writes it

wai-quantum eval run asked one open-weight model each of the fourteen tasks at temperature 0: a mixture of experts of 35 billion parameters with 3 billion active, quantised to 5 bits and served on the build host with its reasoning turned off. It was asked once for the program alone and once for an answer in the layer, each with no repair round and with one, which sends the engine’s errors back for a corrected answer. Valid is an answer the engine reads with no error (in the layer, no error in the layer either); solved is one whose program holds the task’s intent by proof or evidence. Joules are the model server’s CPU energy as the kernel counts it for its process; the GPU the model runs on is not counted, so they compare these runs with each other and do not measure a task’s whole cost.

Asked forRepair roundsValidSolvedJoules per solved task
the program012114.4
the program113124.0
the layer04317.6
the layer17610.6

This model writes the layer far less reliably than it writes a program. Its errors are its own, not the reader’s: a program in a fence with no name, a view given an engine call where it takes a program (Circuit(@Compile(p))), a name the root or a seal lists that no statement declares, a text or a circuit listed in a seal, arguments written as seed=7, and the prompt’s example copied with its program’s name. An answer that is not valid still draws, since a list leaves out what falls (§7) and only the statements in error are missing, unless its program is lost, as in the four first answers whose fence had no name or that ended inside the program. One repair round, with the codes and the lines, corrects three of the ten. The prompt was revised after the first run on these tasks to give each argument’s kind and to state the rules those errors broke; the first run scored the same, 4 valid and 3 solved with no repair and 7 and 6 with one, and the errors moved rather than went. The prompt and the reader are answer::prompt() and §6; whether a larger model or a model tuned on the layer does better is not measured here, and a report ranks nothing.

10. Conformance

quantum-conformance/answers/ holds answers, each with the codes of its errors and warnings in line order, its root, its order, what its views show when run, and, for stored answers, whether the receipt verifies when the answer is run again. Every code of §6 has a case. A conforming reader MUST give each case’s codes, root and order, and MUST give the same answer however the text is split (§7). A conforming host MUST give each view what its manifest says when run, and MUST verify the stored cases as they say. The reference implementation passes the set natively, under wasm32-wasip2 and in the browser (wai-web’s engine_answer_parse and engine_answer_verify); its stored case was written natively and verifies in each.

11. Implementation