Skip to content

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.

Each request carries the source in code and an optional language hint:

languageBehavior
"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.
omittedPure regex detection, in order: Q#, Qiskit, Cirq, CUDA-Q (first match wins).

Detection regexes (first match in this order):

FrameworkPattern
qsharp^\s*(namespace\s+[\w.]+|operation\s+\w+\s*\(|import\s+Std|open\s+Microsoft\.Quantum) (multiline)
qiskitfrom\s+qiskit\s+import|import\s+qiskit
cirqimport\s+cirq|from\s+cirq\s+import
cuda-qimport\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.

FieldTypeRequiredDefaultNotes
type"parse"yes
codestringno""Source text (Python or Q#).
language"python" | "qsharp"noomittedSee routing above.

Response sequence, in order:

#MessageWhen
1outputOnly if the code printed to stdout.
2stderrOnly if the code wrote to stderr.
3snapshotAlways. data is the CircuitSnapshot, or null when there is no circuit or the code failed.
4errorOnly 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.)

parse_bell_qiskit.request.json
{
"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"
}
parse_bell_qiskit.responses.json
[
{
"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:

parse_syntax_error_qiskit.request.json
{
"type": "parse",
"code": "from qiskit import QuantumCircuit\nqc = QuantumCircuit(2\n",
"language": "python"
}
parse_syntax_error_qiskit.responses.json
[
{
"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:

parse_bell_qsharp.request.json
{
"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"
}
parse_bell_qsharp.responses.json
[
{
"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 }
]
}
}
]

Runs the full pipeline: build the circuit, extract a snapshot, simulate.

FieldTypeRequiredDefaultNotes
type"execute"yes
codestringno""Source text.
shotsintegerno1024Sampled-measurement count.
language"python" | "qsharp"noomittedSee routing above.
params{string: number}noomittedProtocol 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.
seedintegernoomittedProtocol 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:

#MessageWhen
1outputOnly if stdout was produced.
2stderrOnly if stderr was produced.
3snapshotIf 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.
4aerror then result with data: nullOn 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.
4bresultOn 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) → errorresult: null — the diagram stays useful even though the run failed.

execute_bell_qiskit.request.json
{
"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"
}
execute_bell_qiskit.responses.json
[
{
"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):

execute_no_circuit.request.json
{
"type": "execute",
"code": "from qiskit import QuantumCircuit\n",
"shots": 1024,
"language": "python"
}
execute_no_circuit.responses.json
[
{
"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 named params (empty when the field is omitted), so params.get("theta", default) works identically whether or not a caller supplied values. For Q# code, params binds 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’s random and numpy.random before exec, so a seeded run’s print(random.random()) is reproducible too. Whether the backend actually honored the seed comes back as result.data.seed_honored — see schemas.
  • record_metric(name, value) — always available in the Python exec namespace (no request field needed). Calls accumulate into result.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:

execute_params_seed.session.json
[
{
"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):

execute_metrics.request.json
{
"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"
}
execute_metrics.responses.json
[
{
"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 }
}
}
]

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 (22.0) so Q# doesn’t parse them as Int.

execute_qsharp_params.request.json
{
"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
}
execute_qsharp_params.responses.json
[
{
"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):

execute_qsharp_params_missing.request.json
{
"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
}
execute_qsharp_params_missing.responses.json
[
{
"type": "error",
"message": "Missing value for parameter 'theta' (Double) of Rotate().",
"code": "compile_error",
"phase": "execute",
"framework": "qsharp"
},
{
"type": "result",
"data": null
}
]

Executes arbitrary Python with stdout/stderr capture — no framework detection, no snapshot, no simulation. Used for terminal-style scratch code.

FieldTypeRequiredDefault
type"run_python"yes
codestringno""

Response sequence, in order:

#MessageWhen
1outputOnly if stdout was produced.
2stderrOnly if stderr was produced.
3errorOnly on failure, with phase: "python" — sent before python_result so failure UI never flashes success.
4python_resultAlways. { "success": true | false }.
run_python_hello.request.json
{
"type": "run_python",
"code": "print('hello from the kernel')\n"
}
run_python_hello.responses.json
[
{
"type": "output",
"text": "hello from the kernel\n"
},
{
"type": "python_result",
"success": true
}
]

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.

FieldTypeRequiredDefault
type"environment"yes

Response (single message, environment):

FieldTypeNotes
pythonstringplatform.python_version() on the kernel host.
platformstringplatform.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.
environment.request.json
{
"type": "environment"
}
environment.responses.json
[
{
"type": "environment",
"python": "<any>",
"platform": "<any>",
"packages": {
"qiskit": "<any>",
"qiskit_aer": "<any>",
"cirq": "<any>",
"qsharp": "<any>",
"stim": "<any>",
"sinter": "<any>",
"pymatching": "<any>"
}
}
]

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.

FieldTypeRequiredDefault
type"transpile"yes
codestringno""
language"python" | "qsharp" | "stim"nodetected
basis_gatesstring[]nonone (no basis constraint)
coupling_map[[int, int], …]nonone (all-to-all)
optimization_level0 | 1 | 2 | 3no1

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:

#MessageWhen
1outputOnly if the code produced stdout.
2stderrOnly if the code produced stderr.
3errorOnly on failure, phase: "transpile" — sent before transpile_result: null.
4transpile_resultAlways. data is the payload below, or null on error.

transpile_result.data:

FieldTypeNotes
beforeCircuitSnapshotThe logical circuit, before transpilation.
afterCircuitSnapshotThe transpiled circuit.
metricsobjectdepth, two_qubit, gate_count, each { "before": int, "after": int }. two_qubit/gate_count exclude measure/barrier.
passesarrayEvery 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.
targetobject{ "basis_gates": string[] | null, "coupling_size": int } — the constraints that were applied.
transpile a Bell pair onto a linear device
{ "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 }
transpile_result (abridged)
{ "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 } } }

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.

FieldTypeRequiredDefault
type"debug_trace"yes
codestringno""
language"python" | "qsharp" | "stim"nodetected

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:

FieldTypeNotes
frameworkstring"qiskit" or "cirq".
qubit_countintNumber of qubits.
stepsarrayG+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.

debug_trace_result for a Bell pair (abridged)
{ "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}] } ] } }

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.

lintlint_result — diagnostics for the buffer:

FieldTypeNotes
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.

formatformat_result — the formatted buffer:

FieldTypeNotes
type (req)"format"
code (req)string
formatted (resp)string | nullThe ruff format output. null on failure (e.g. a syntax error ruff can’t parse), preceded by an error with phase: "format".
lint_result for an unused import
{ "type": "lint_result", "diagnostics": [
{ "line": 1, "column": 8, "end_line": 1, "end_column": 10,
"severity": "warning", "code": "F401", "message": "`os` imported but unused" } ] }

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 execute delays 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.