Execution messages
Three request types drive the editor loop: parse (cheap, on every code
change — no simulation), execute (full simulation, on Cmd+Enter), and
run_python (plain Python, no circuit semantics). environment (protocol
v1.1) is a fourth, standalone message reporting the kernel’s own versions.
All response payload schemas are defined in schemas; error codes in
errors.
Framework routing and the language field
Section titled “Framework routing and the language field”Each request carries the source in code and an optional language hint:
language | Behavior |
|---|---|
"qsharp" | Route straight to the Q# adapter, skipping all detection. |
"python" | Regex detection among Python frameworks only — Q#-looking text inside Python strings or comments cannot hijack routing. |
| omitted | Pure regex detection, in order: Q#, Qiskit, Cirq, CUDA-Q (first match wins). |
Detection regexes (first match in this order):
| Framework | Pattern |
|---|---|
qsharp | ^\s*(namespace\s+[\w.]+|operation\s+\w+\s*\(|import\s+Std|open\s+Microsoft\.Quantum) (multiline) |
qiskit | from\s+qiskit\s+import|import\s+qiskit |
cirq | import\s+cirq|from\s+cirq\s+import |
cuda-q | import\s+cudaq|from\s+cudaq\s+import|@cudaq\.kernel |
Q# is a source-mode framework: its source is compiled by the QDK
interpreter and never reaches Python exec(). Python frameworks are
executed with exec() and the resulting namespace is scanned for a circuit
object (the last one defined wins).
Extracts a CircuitSnapshot without simulating. Sent by the frontend on a
300 ms debounce as the user types.
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
type | "parse" | yes | — | |
code | string | no | "" | Source text (Python or Q#). |
language | "python" | "qsharp" | no | omitted | See routing above. |
Response sequence, in order:
| # | Message | When |
|---|---|---|
| 1 | output | Only if the code printed to stdout. |
| 2 | stderr | Only if the code wrote to stderr. |
| 3 | snapshot | Always. data is the CircuitSnapshot, or null when there is no circuit or the code failed. |
| 4 | error | Only on failure, with phase: "parse". |
A source file with no circuit in it is not a parse error: the kernel
sends snapshot with data: null and no error, so a live-typing user
sees a quietly empty diagram instead of a red banner. (execute treats the
same situation as a no_circuit error — see below.)
{ "type": "parse", "code": "from qiskit import QuantumCircuit\n\nqc = QuantumCircuit(2, 2)\nqc.h(0)\nqc.cx(0, 1)\nqc.measure([0, 1], [0, 1])\n", "language": "python"}[ { "type": "snapshot", "data": { "framework": "qiskit", "qubit_count": 2, "classical_bit_count": 2, "depth": 3, "gates": [ { "type": "H", "targets": [0], "controls": [], "params": [], "layer": 0 }, { "type": "CNOT", "targets": [1], "controls": [0], "params": [], "layer": 1 }, { "type": "Measure", "targets": [0], "controls": [], "params": [], "layer": 2 }, { "type": "Measure", "targets": [1], "controls": [], "params": [], "layer": 2 } ] } }]On failure, snapshot: null still arrives first (so stale diagrams clear),
then the error:
{ "type": "parse", "code": "from qiskit import QuantumCircuit\nqc = QuantumCircuit(2\n", "language": "python"}[ { "type": "snapshot", "data": null }, { "type": "error", "message": "SyntaxError: '(' was never closed", "code": "compile_error", "phase": "parse", "traceback": "<any>", "framework": "qiskit" }]Q# parses through the same message — only the payload’s framework and the
compile pipeline differ:
{ "type": "parse", "code": "operation Main() : Result[] {\n use qs = Qubit[2];\n H(qs[0]);\n CNOT(qs[0], qs[1]);\n return [M(qs[0]), M(qs[1])];\n}\n", "language": "qsharp"}[ { "type": "snapshot", "data": { "framework": "qsharp", "qubit_count": 2, "classical_bit_count": 2, "depth": 3, "gates": [ { "type": "H", "targets": [0], "controls": [], "params": [], "layer": 0 }, { "type": "CNOT", "targets": [1], "controls": [0], "params": [], "layer": 1 }, { "type": "Measure", "targets": [0], "controls": [], "params": [], "layer": 2 }, { "type": "Measure", "targets": [1], "controls": [], "params": [], "layer": 2 } ] } }]execute
Section titled “execute”Runs the full pipeline: build the circuit, extract a snapshot, simulate.
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
type | "execute" | yes | — | |
code | string | no | "" | Source text. |
shots | integer | no | 1024 | Sampled-measurement count. |
language | "python" | "qsharp" | no | omitted | See routing above. |
params | {string: number} | no | omitted | Protocol v1.1. Python: injected into the exec namespace as params (always a dict — {} when this field is omitted, so params.get("theta", default) is portable). Q#: binds by name to the entry operation’s declared arguments (Double/Int only) — see below. |
seed | integer | no | omitted | Protocol v1.1. Requests reproducible sampling. Whether it was actually honored comes back as result.data.seed_honored. Also seeds Python’s random and numpy.random before exec for Python code. |
Response sequence, in order:
| # | Message | When |
|---|---|---|
| 1 | output | Only if stdout was produced. |
| 2 | stderr | Only if stderr was produced. |
| 3 | snapshot | If a snapshot was extracted, or the error code is one of unsupported_framework, missing_dependency, no_circuit, execution_error, adapter_error (sent with data: null so the frontend clears the diagram). Not sent for compile_error. |
| 4a | error then result with data: null | On failure. The error is deliberately sent before the null result so a client that keys its UI off result never flashes “success with no data” before learning why. |
| 4b | result | On success, with the SimulationResult in data. |
result is the terminal message of every execute, success or failure —
drain until you see it.
A subtlety worth knowing: when simulation itself fails (simulation_error),
the snapshot was already extracted, so the sequence is snapshot (with
data) → error → result: null — the diagram stays useful even though the
run failed.
{ "type": "execute", "code": "from qiskit import QuantumCircuit\n\nqc = QuantumCircuit(2, 2)\nqc.h(0)\nqc.cx(0, 1)\nqc.measure([0, 1], [0, 1])\n", "shots": 1024, "language": "python"}[ { "type": "snapshot", "data": { "framework": "qiskit", "qubit_count": 2, "classical_bit_count": 2, "depth": 3, "gates": [ { "type": "H", "targets": [0], "controls": [], "params": [], "layer": 0 }, { "type": "CNOT", "targets": [1], "controls": [0], "params": [], "layer": 1 }, { "type": "Measure", "targets": [0], "controls": [], "params": [], "layer": 2 }, { "type": "Measure", "targets": [1], "controls": [], "params": [], "layer": 2 } ] } }, { "type": "result", "data": { "state_vector": [ { "re": "<approx:0.70710678:0.0000001>", "im": "<approx:0:0.0000001>" }, { "re": "<approx:0:0.0000001>", "im": "<approx:0:0.0000001>" }, { "re": "<approx:0:0.0000001>", "im": "<approx:0:0.0000001>" }, { "re": "<approx:0.70710678:0.0000001>", "im": "<approx:0:0.0000001>" } ], "probabilities": { "00": "<approx:0.5:0.000001>", "11": "<approx:0.5:0.000001>" }, "measurements": "<any>", "bloch_coords": [ { "x": "<approx:0:0.000001>", "y": "<approx:0:0.000001>", "z": "<approx:0:0.000001>" }, { "x": "<approx:0:0.000001>", "y": "<approx:0:0.000001>", "z": "<approx:0:0.000001>" } ], "execution_time_ms": "<any>", "shot_count": 1024, "metrics": {} } }]The no_circuit failure path (framework imported, no circuit defined):
{ "type": "execute", "code": "from qiskit import QuantumCircuit\n", "shots": 1024, "language": "python"}[ { "type": "snapshot", "data": null }, { "type": "error", "message": "No quantum circuit found in code.", "code": "no_circuit", "phase": "execute", "framework": "qiskit" }, { "type": "result", "data": null }]Parameters, seeding, and metrics (protocol v1.1)
Section titled “Parameters, seeding, and metrics (protocol v1.1)”Three additive fields on execute — introduced for Research mode
experiments but useful any time you
want reproducible or parameterized runs from any client:
params— for Python code, always injected into the exec namespace as a dict namedparams(empty when the field is omitted), soparams.get("theta", default)works identically whether or not a caller supplied values. For Q# code,paramsbinds by name to the entry operation’s declared arguments — see the Q# subsection below.seed— forwarded to the framework’s simulator (AerSimulator(seed_simulator=...)for Qiskit,cirq.Simulator(seed=...)for Cirq,qsharp.run(..., seed=...)for Q#,cudaq.set_random_seed(...)for CUDA-Q when the installed build exposes it) and used to seed Python’srandomandnumpy.randombeforeexec, so a seeded run’sprint(random.random())is reproducible too. Whether the backend actually honored the seed comes back asresult.data.seed_honored— see schemas.record_metric(name, value)— always available in the Python exec namespace (no request field needed). Calls accumulate intoresult.data.metrics; recording the same name twice overwrites (last write wins). Empty object when nothing was recorded.
Same seed, same measurements — replayed twice in one session:
[ { "request": { "type": "execute", "code": "from qiskit import QuantumCircuit\n\nqc = QuantumCircuit(2, 2)\nqc.h(0)\nqc.cx(0, 1)\nqc.measure([0, 1], [0, 1])\nrecord_metric(\"theta\", params.get(\"theta\", 0.0))\n", "shots": 256, "language": "python", "params": { "theta": 0.7854 }, "seed": 1234 }, "responses": [ { "type": "snapshot", "data": { "framework": "qiskit", "qubit_count": 2, "classical_bit_count": 2, "depth": 3, "gates": [ { "type": "H", "targets": [0], "controls": [], "params": [], "layer": 0 }, { "type": "CNOT", "targets": [1], "controls": [0], "params": [], "layer": 1 }, { "type": "Measure", "targets": [0], "controls": [], "params": [], "layer": 2 }, { "type": "Measure", "targets": [1], "controls": [], "params": [], "layer": 2 } ] } }, { "type": "result", "data": { "state_vector": "<any>", "probabilities": "<any>", "measurements": { "00": 136, "11": 120 }, "bloch_coords": "<any>", "execution_time_ms": "<any>", "shot_count": 256, "metrics": { "theta": 0.7854 }, "seed_honored": true } } ] }, { "request": { "type": "execute", "code": "from qiskit import QuantumCircuit\n\nqc = QuantumCircuit(2, 2)\nqc.h(0)\nqc.cx(0, 1)\nqc.measure([0, 1], [0, 1])\nrecord_metric(\"theta\", params.get(\"theta\", 0.0))\n", "shots": 256, "language": "python", "params": { "theta": 0.7854 }, "seed": 1234 }, "responses": [ { "type": "snapshot", "data": { "framework": "qiskit", "qubit_count": 2, "classical_bit_count": 2, "depth": 3, "gates": [ { "type": "H", "targets": [0], "controls": [], "params": [], "layer": 0 }, { "type": "CNOT", "targets": [1], "controls": [0], "params": [], "layer": 1 }, { "type": "Measure", "targets": [0], "controls": [], "params": [], "layer": 2 }, { "type": "Measure", "targets": [1], "controls": [], "params": [], "layer": 2 } ] } }, { "type": "result", "data": { "state_vector": "<any>", "probabilities": "<any>", "measurements": { "00": 136, "11": 120 }, "bloch_coords": "<any>", "execution_time_ms": "<any>", "shot_count": 256, "metrics": { "theta": 0.7854 }, "seed_honored": true } } ] }]Metrics recorded without params or a seed (both are independent — you can use one without the other):
{ "type": "execute", "code": "from qiskit import QuantumCircuit\n\nqc = QuantumCircuit(1, 1)\nqc.h(0)\nqc.measure(0, 0)\nrecord_metric(\"energy\", -1.1372)\nrecord_metric(\"iterations\", 40)\n", "shots": 512, "language": "python"}[ { "type": "snapshot", "data": { "framework": "qiskit", "qubit_count": 1, "classical_bit_count": 1, "depth": 2, "gates": [ { "type": "H", "targets": [0], "controls": [], "params": [], "layer": 0 }, { "type": "Measure", "targets": [0], "controls": [], "params": [], "layer": 1 } ] } }, { "type": "result", "data": { "state_vector": "<any>", "probabilities": "<any>", "measurements": "<any>", "bloch_coords": "<any>", "execution_time_ms": "<any>", "shot_count": 512, "metrics": { "energy": -1.1372, "iterations": 40 } } }]Q# parameter binding
Section titled “Q# parameter binding”The entry operation’s signature is parsed with a regex over its
declaration (not a real parser — approximate by design, Double/Int
only in v1) and its arguments are bound by name to params. Double
values are always rendered with a decimal point (2 → 2.0) so Q#
doesn’t parse them as Int.
{ "type": "execute", "code": "operation Rotate(theta : Double) : Result {\n use q = Qubit();\n Rx(theta, q);\n let r = M(q);\n Reset(q);\n return r;\n}\n", "shots": 100, "language": "qsharp", "params": { "theta": 3.14159 }, "seed": 42}[ { "type": "snapshot", "data": { "framework": "qsharp", "qubit_count": 1, "classical_bit_count": 1, "depth": 3, "gates": [ { "type": "RX", "targets": [0], "controls": [], "params": ["<approx:3.14159:0.0001>"], "layer": 0 }, { "type": "Measure", "targets": [0], "controls": [], "params": [], "layer": 1 }, { "type": "Reset", "targets": [0], "controls": [], "params": [], "layer": 2 } ] } }, { "type": "result", "data": { "state_vector": [], "probabilities": { "1": "<approx:1.0:0.000001>" }, "measurements": { "1": 100 }, "bloch_coords": [ { "x": "<approx:0:0.000001>", "y": "<approx:0:0.000001>", "z": "<approx:-1:0.000001>" } ], "execution_time_ms": "<any>", "shot_count": 100, "metrics": {}, "seed_honored": true } }]A missing/extra/mis-typed parameter is a compile_error returned before
any Q# code runs — note there’s no snapshot message at all here, because
compile_error isn’t one of the codes that gets a data: null snapshot
(see the response-sequence table above):
{ "type": "execute", "code": "operation Rotate(theta : Double) : Result {\n use q = Qubit();\n Rx(theta, q);\n let r = M(q);\n Reset(q);\n return r;\n}\n", "shots": 100, "language": "qsharp", "params": {}, "seed": 42}[ { "type": "error", "message": "Missing value for parameter 'theta' (Double) of Rotate().", "code": "compile_error", "phase": "execute", "framework": "qsharp" }, { "type": "result", "data": null }]run_python
Section titled “run_python”Executes arbitrary Python with stdout/stderr capture — no framework detection, no snapshot, no simulation. Used for terminal-style scratch code.
| Field | Type | Required | Default |
|---|---|---|---|
type | "run_python" | yes | — |
code | string | no | "" |
Response sequence, in order:
| # | Message | When |
|---|---|---|
| 1 | output | Only if stdout was produced. |
| 2 | stderr | Only if stderr was produced. |
| 3 | error | Only on failure, with phase: "python" — sent before python_result so failure UI never flashes success. |
| 4 | python_result | Always. { "success": true | false }. |
{ "type": "run_python", "code": "print('hello from the kernel')\n"}[ { "type": "output", "text": "hello from the kernel\n" }, { "type": "python_result", "success": true }]environment
Section titled “environment”Protocol v1.1 — a new message type. Reports the kernel’s own
interpreter, platform, and installed framework versions — useful for
stamping reproducibility manifests (Research mode experiments) and for bug
reports. Takes no request fields beyond type.
| Field | Type | Required | Default |
|---|---|---|---|
type | "environment" | yes | — |
Response (single message, environment):
| Field | Type | Notes |
|---|---|---|
python | string | platform.python_version() on the kernel host. |
platform | string | platform.platform() on the kernel host. |
packages | {string: string} | Installed version per framework, keyed qiskit, qiskit_aer, cirq, cudaq, qsharp. A key is omitted (never a placeholder) when that framework isn’t installed — resolved via importlib.metadata, trying each framework’s actual PyPI distribution name(s) since those can diverge from the import name (e.g. cirq is commonly provided by the cirq-core distribution alone; the Q# runtime is the qdk package, with qsharp installed as a compatibility shim in some setups). Never raises — a metadata lookup failure just omits that key. |
{ "type": "environment"}[ { "type": "environment", "python": "<any>", "platform": "<any>", "packages": { "qiskit": "<any>", "qiskit_aer": "<any>", "cirq": "<any>", "qsharp": "<any>", "stim": "<any>", "sinter": "<any>", "pymatching": "<any>" } }]transpile
Section titled “transpile”Protocol v1.2 era (app 0.11.0) — additive. Runs the code, finds its
circuit, and transpiles it for a target with a Qiskit preset PassManager,
returning everything the Transpiler Explorer
needs to show what the compiler did. Qiskit-only — it is the only
supported framework with an introspectable compiler; any other framework
answers with a transpile_unsupported_framework error (never a Python exec
of foreign code). It does not simulate.
| Field | Type | Required | Default |
|---|---|---|---|
type | "transpile" | yes | — |
code | string | no | "" |
language | "python" | "qsharp" | "stim" | no | detected |
basis_gates | string[] | no | none (no basis constraint) |
coupling_map | [[int, int], …] | no | none (all-to-all) |
optimization_level | 0 | 1 | 2 | 3 | no | 1 |
Each optional target field is validated defensively at the socket boundary: a
malformed basis_gates / coupling_map / optimization_level degrades to the
unconstrained default rather than dropping the connection.
Response sequence, in order:
| # | Message | When |
|---|---|---|
| 1 | output | Only if the code produced stdout. |
| 2 | stderr | Only if the code produced stderr. |
| 3 | error | Only on failure, phase: "transpile" — sent before transpile_result: null. |
| 4 | transpile_result | Always. data is the payload below, or null on error. |
transpile_result.data:
| Field | Type | Notes |
|---|---|---|
before | CircuitSnapshot | The logical circuit, before transpilation. |
after | CircuitSnapshot | The transpiled circuit. |
metrics | object | depth, two_qubit, gate_count, each { "before": int, "after": int }. two_qubit/gate_count exclude measure/barrier. |
passes | array | Every pass that changed the gate makeup, in pipeline order: { "name": string, "depth": int, "added_gates": {string: int} }. added_gates is the signed count delta vs. the previous pass — positive is added (routing SWAPs, basis rewrites), negative removed/rewritten. Passes that changed nothing are omitted. |
target | object | { "basis_gates": string[] | null, "coupling_size": int } — the constraints that were applied. |
{ "type": "transpile", "code": "from qiskit import QuantumCircuit\nqc = QuantumCircuit(4, 4)\nqc.h(0)\nfor i in range(3): qc.cx(0, i + 1)\nqc.measure(range(4), range(4))", "basis_gates": ["rz", "sx", "x", "cx"], "coupling_map": [[0,1],[1,2],[2,3]], "optimization_level": 1 }{ "type": "transpile_result", "data": { "metrics": { "depth": {"before": 5, "after": 18}, "two_qubit": {"before": 3, "after": 6}, "gate_count": {"before": 4, "after": 39} }, "passes": [ { "name": "SabreLayout", "depth": 7, "added_gates": {"swap": 1} }, { "name": "BasisTranslator", "depth": 12, "added_gates": {"sx": 1, "cx": 3, "rz": 2, "h": -1, "swap": -1} } ], "target": { "basis_gates": ["rz","sx","x","cx"], "coupling_size": 3 } } }debug_trace
Section titled “debug_trace”App 0.12.0 — additive. Runs the code, finds its circuit, and returns the
per-gate state trajectory for the Quantum Debugger:
the quantum state after every gate, computed in one call so a client can scrub
through steps with no round-trip each. Qiskit and Cirq only (the frameworks
with a statevector path); others answer with a debug_unsupported_framework
error. It does not sample measurements.
| Field | Type | Required | Default |
|---|---|---|---|
type | "debug_trace" | yes | — |
code | string | no | "" |
language | "python" | "qsharp" | "stim" | no | detected |
Response sequence: output/stderr (if any), then a debug_trace_result whose
data is the payload below (or null on error, preceded by an error with
phase: "debug").
debug_trace_result.data:
| Field | Type | Notes |
|---|---|---|
framework | string | "qiskit" or "cirq". |
qubit_count | int | Number of qubits. |
steps | array | G+1 steps. steps[0] is the initial |0…0⟩ (gate_index: -1); steps[k+1] is the state after gate k, aligned with CircuitSnapshot.gates[k]. Each step: { "gate_index": int, "label": string, "probabilities": {string: float}, "bloch_coords": [{x,y,z}, …] }. Non-unitary ops (measure/barrier/reset) appear as steps with the state unchanged. |
Steps carry probabilities + Bloch coords only — not the full 2ⁿ-amplitude
vector at every step, which would blow up the payload. The trace is bounded to
12 qubits and 200 gates; past either, a circuit_too_large error names
the limit (no silent truncation). The per-qubit Bloch coordinates use the same
per-framework convention as SimulationResult.bloch_coords.
{ "type": "debug_trace_result", "data": { "framework": "qiskit", "qubit_count": 2, "steps": [ { "gate_index": -1, "label": "initial", "probabilities": {"00": 1.0}, "bloch_coords": [{"x":0,"y":0,"z":1},{"x":0,"y":0,"z":1}] }, { "gate_index": 0, "label": "h q0", "probabilities": {"00": 0.5, "01": 0.5}, "bloch_coords": [{"x":1,"y":0,"z":0},{"x":0,"y":0,"z":1}] }, { "gate_index": 1, "label": "cx q0,q1", "probabilities": {"00": 0.5, "11": 0.5}, "bloch_coords": [{"x":0,"y":0,"z":0},{"x":0,"y":0,"z":0}] } ] } }lint and format
Section titled “lint and format”App 0.13.0 — additive editor support via ruff. Both are Python-only (Q# has its own compiler diagnostics via the QDK language service) and both degrade gracefully when ruff isn’t installed.
lint → lint_result — diagnostics for the buffer:
| Field | Type | Notes |
|---|---|---|
type (req) | "lint" | — |
code (req) | string | — |
diagnostics (resp) | array | { line, column, end_line, end_column, severity, code, message }, 1-based. severity is "error" only for syntax errors (E9xx), else "warning". Empty when the code is clean, the buffer is Q#, or ruff is unavailable — never an error that blocks typing. |
format → format_result — the formatted buffer:
| Field | Type | Notes |
|---|---|---|
type (req) | "format" | — |
code (req) | string | — |
formatted (resp) | string | null | The ruff format output. null on failure (e.g. a syntax error ruff can’t parse), preceded by an error with phase: "format". |
{ "type": "lint_result", "diagnostics": [ { "line": 1, "column": 8, "end_line": 1, "end_column": 10, "severity": "warning", "code": "F401", "message": "`os` imported but unused" } ] }Timeout semantics, honestly
Section titled “Timeout semantics, honestly”Python frameworks (Qiskit, Cirq, CUDA-Q): the executor defines a
30-second timeout (EXECUTION_TIMEOUT_SECONDS) enforced with SIGALRM —
which only works on the main thread of a POSIX process. The production
server runs parse/execute/run_python inside asyncio.to_thread
worker threads to keep the event loop responsive, so in practice the
timeout never fires and long-running Python programs run to completion.
Q# is different: it now has a real watchdog.
All qdk interpreter work runs on one dedicated thread (the interpreter
context is thread-pinned), and the caller waits on that thread’s future
with a budget: 30 s for parse (compiler-only work) and 300 s for
execute (user programs at user shot counts). On expiry the caller gets a
timeout error — but qdk offers no cancellation, so only the caller is
freed: the runaway program keeps the interpreter thread busy. The kernel
tracks that wedged state, and subsequent Q# requests fail fast (no second
full wait) with a timeout error saying a previous Q# run is still
occupying the interpreter. Restarting the kernel recovers; if the runaway
program eventually finishes on its own, the wedge clears and Q# requests
flow again without a restart. Hardware QIR compilation gets the same 30 s
budget (surfaced as a Hardware submit failed: ... message).
What protects clients beyond that:
- The WebSocket heartbeat (ping 30 s / timeout 20 s) detects a hung kernel process; the IDE restarts the kernel when the connection dies. Note that a wedged Q# interpreter does not hang the kernel process — the heartbeat stays healthy, which is exactly why the watchdog reports the state explicitly.
- Requests on one connection are serialized, so a long
executedelays your next message, not other connections.
The timeout error code is therefore reachable two ways: always for Q#
(the watchdog), and for Python frameworks only when the Executor is
embedded directly on a main thread (e.g. scripts and tests). See
errors.