Skip to content

QEC messages

Stim stabilizer circuits are a first-class kernel framework, reachable two ways:

  • Python mode — code that import stim and builds stim.Circuit objects runs through the ordinary parse/execute pipeline (framework detection, last-circuit-in-namespace, params/seed/record_metric all work). This is the power path for parameterized experiments.
  • Raw .stim text — send language: "stim" and the code field is parsed with stim.Circuit(text) directly, never through Python exec (the same source-mode routing Q# uses). This is the interchange path: researchers share bare .stim files constantly.

Simulation uses Stim’s compiled sampler. An explicit integer seed is always honored (seed_honored: true) — with Stim’s documented scope: same seed + same stim version + same machine reproduces identical samples, but results can differ across CPUs (SIMD width) and stim versions. Manifests remain honest either way. Because stabilizer circuits routinely exceed statevector sizes, the result is honest about being a sampler: state_vector, probabilities, and bloch_coords are empty, and measurements keys are measurement-record bitstrings (first recorded measurement rightmost, matching the Python frameworks’ bit order).

Generates one of Stim’s built-in QEC circuits and returns its text. The desktop app writes the result into a real project file — the circuit you run is always a file you can read and edit, never hidden state.

qec_generate.request.json
{
"type": "qec_generate",
"code": "repetition_code:memory",
"distance": 3,
"rounds": 2,
"noise": {
"before_measure_flip_probability": 0.01
}
}
qec_generate.responses.json
[
{
"type": "qec_generated",
"code": "repetition_code:memory",
"distance": 3,
"rounds": 2,
"circuit_text": "R 0 1 2 3 4\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.01) 1 3\nMR 1 3\nDETECTOR(1, 0) rec[-2]\nDETECTOR(3, 0) rec[-1]\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.01) 1 3\nMR 1 3\nSHIFT_COORDS(0, 1)\nDETECTOR(1, 0) rec[-2] rec[-4]\nDETECTOR(3, 0) rec[-1] rec[-3]\nX_ERROR(0.01) 0 2 4\nM 0 2 4\nDETECTOR(1, 1) rec[-2] rec[-3] rec[-5]\nDETECTOR(3, 1) rec[-1] rec[-2] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]"
}
]

Request fields:

FieldTypeNotes
codestringOne of repetition_code:memory, surface_code:rotated_memory_x, surface_code:rotated_memory_z, surface_code:unrotated_memory_x, surface_code:unrotated_memory_z, color_code:memory_xyz
distanceint ≥ 2Code distance (stim itself rejects invalid combinations, e.g. even distances for surface codes)
roundsint ≥ 1Syndrome-extraction rounds
noiseobject, optionalAny of stim’s generator noise arguments: after_clifford_depolarization, before_round_data_depolarization, before_measure_flip_probability, after_reset_flip_probability — each a probability in [0, 1]

Validation failures answer with an error of code qec_generate_invalid whose message is safe to show verbatim. A kernel without Stim installed answers missing_dependency (install it from the setup wizard’s Research group).

When parse or execute produces a Stim circuit, one extra response follows the snapshot (and precedes the result): the QEC sidecar with detector/observable counts, qubit and detector coordinates, and the Detector Error Model reduced to a graph.

{
"type": "qec_snapshot",
"data": {
"num_qubits": 5, "num_detectors": 4, "num_observables": 1,
"num_ticks": 6,
"coords": { "qubits": [[1.0, 0.0], null, ""], "detectors": [[1.0, 0.0, 0.0], ""] },
"dem": {
"nodes": 4,
"edge_count": 5, "boundary_edge_count": 2, "hyperedges_count": 0,
"truncated": false,
"edges": [{ "d1": 0, "d2": 1, "obs": [], "p": 0.01 }],
"boundary_edges": [{ "d": 2, "obs": [0], "p": 0.0066 }]
},
"sample_decode": null
}
}

Reading it honestly:

  • The DEM is extracted with decompose_errors=True, so edges are the matchable components decoders like PyMatching consume. Parallel error mechanisms on the same detector pair are merged with the XOR rule p = p₁(1−p₂) + p₂(1−p₁).
  • Circuits whose errors don’t decompose (non-matchable codes) fall back to the undecomposed model with hyperedges_count > 0 — render the pairwise projection with a badge, don’t pretend it’s a matching graph.
  • Circuits with no valid DEM at all (e.g. a non-deterministic observable) keep their stats but carry dem: null plus a dem_error string.
  • Payload cap: above 5,000 total edges the lists are emptied, truncated: true, and the counts remain — never a silently missing graph. Re-request with a higher cap to render anyway (below).
  • dem_text (present only when truncated: true): the flattened DEM as text (str(dem.flattened())). The frontend parses it client-side (a WASM parser) to render the full graph with no kernel-side edge cap — so arbitrarily large detector graphs render without the kernel serializing a huge edge list. Absent when the graph fits, so the common case pays nothing.
  • coords arrays are index-aligned (entry i = qubit/detector i), null where stim has no coordinates. sample_decode is reserved for the campaign protocol increment.

The same payload is available on demand — with code (stateless, any .stim text or stim-building Python), or without code to reuse the last Stim circuit this connection parsed or executed, optionally at a different DEM cap. That second form is the graph view’s “render anyway” button.

The full loop — generate, execute (watch the sidecar arrive between snapshot and result), then re-request the DEM at a deliberately tiny cap to see truncation:

qec_studio.session.json
[
{
"request": {
"type": "qec_generate",
"code": "repetition_code:memory",
"distance": 3,
"rounds": 2,
"noise": {
"before_measure_flip_probability": 0.01
}
},
"responses": [
{
"type": "qec_generated",
"code": "repetition_code:memory",
"distance": 3,
"rounds": 2,
"circuit_text": "R 0 1 2 3 4\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.01) 1 3\nMR 1 3\nDETECTOR(1, 0) rec[-2]\nDETECTOR(3, 0) rec[-1]\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.01) 1 3\nMR 1 3\nSHIFT_COORDS(0, 1)\nDETECTOR(1, 0) rec[-2] rec[-4]\nDETECTOR(3, 0) rec[-1] rec[-3]\nX_ERROR(0.01) 0 2 4\nM 0 2 4\nDETECTOR(1, 1) rec[-2] rec[-3] rec[-5]\nDETECTOR(3, 1) rec[-1] rec[-2] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]"
}
]
},
{
"request": {
"type": "execute",
"code": "R 0 1 2 3 4\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.01) 1 3\nMR 1 3\nDETECTOR(1, 0) rec[-2]\nDETECTOR(3, 0) rec[-1]\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.01) 1 3\nMR 1 3\nSHIFT_COORDS(0, 1)\nDETECTOR(1, 0) rec[-2] rec[-4]\nDETECTOR(3, 0) rec[-1] rec[-3]\nX_ERROR(0.01) 0 2 4\nM 0 2 4\nDETECTOR(1, 1) rec[-2] rec[-3] rec[-5]\nDETECTOR(3, 1) rec[-1] rec[-2] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]",
"shots": 128,
"language": "stim",
"seed": 42
},
"responses": [
{
"type": "snapshot",
"data": {
"framework": "stim",
"qubit_count": 5,
"classical_bit_count": 7,
"depth": 7,
"gates": [
{
"type": "Reset",
"targets": [
0
],
"controls": [],
"params": [],
"layer": 0
},
{
"type": "Reset",
"targets": [
1
],
"controls": [],
"params": [],
"layer": 0
},
{
"type": "Reset",
"targets": [
2
],
"controls": [],
"params": [],
"layer": 0
},
{
"type": "Reset",
"targets": [
3
],
"controls": [],
"params": [],
"layer": 0
},
{
"type": "Reset",
"targets": [
4
],
"controls": [],
"params": [],
"layer": 0
},
{
"type": "CNOT",
"targets": [
1
],
"controls": [
0
],
"params": [],
"layer": 1
},
{
"type": "CNOT",
"targets": [
3
],
"controls": [
2
],
"params": [],
"layer": 1
},
{
"type": "CNOT",
"targets": [
1
],
"controls": [
2
],
"params": [],
"layer": 2
},
{
"type": "CNOT",
"targets": [
3
],
"controls": [
4
],
"params": [],
"layer": 2
},
{
"type": "NOISE:X_ERROR",
"targets": [
1
],
"controls": [],
"params": [
0.01
],
"layer": 3
},
{
"type": "NOISE:X_ERROR",
"targets": [
3
],
"controls": [],
"params": [
0.01
],
"layer": 3
},
{
"type": "MR",
"targets": [
1
],
"controls": [],
"params": [],
"layer": 3
},
{
"type": "MR",
"targets": [
3
],
"controls": [],
"params": [],
"layer": 3
},
{
"type": "DETECTOR",
"targets": [],
"controls": [],
"params": [
1.0,
0.0
],
"layer": 3
},
{
"type": "DETECTOR",
"targets": [],
"controls": [],
"params": [
3.0,
0.0
],
"layer": 3
},
{
"type": "CNOT",
"targets": [
1
],
"controls": [
0
],
"params": [],
"layer": 4
},
{
"type": "CNOT",
"targets": [
3
],
"controls": [
2
],
"params": [],
"layer": 4
},
{
"type": "CNOT",
"targets": [
1
],
"controls": [
2
],
"params": [],
"layer": 5
},
{
"type": "CNOT",
"targets": [
3
],
"controls": [
4
],
"params": [],
"layer": 5
},
{
"type": "NOISE:X_ERROR",
"targets": [
1
],
"controls": [],
"params": [
0.01
],
"layer": 6
},
{
"type": "NOISE:X_ERROR",
"targets": [
3
],
"controls": [],
"params": [
0.01
],
"layer": 6
},
{
"type": "MR",
"targets": [
1
],
"controls": [],
"params": [],
"layer": 6
},
{
"type": "MR",
"targets": [
3
],
"controls": [],
"params": [],
"layer": 6
},
{
"type": "DETECTOR",
"targets": [],
"controls": [],
"params": [
1.0,
1.0
],
"layer": 6
},
{
"type": "DETECTOR",
"targets": [],
"controls": [],
"params": [
3.0,
1.0
],
"layer": 6
},
{
"type": "NOISE:X_ERROR",
"targets": [
0
],
"controls": [],
"params": [
0.01
],
"layer": 6
},
{
"type": "NOISE:X_ERROR",
"targets": [
2
],
"controls": [],
"params": [
0.01
],
"layer": 6
},
{
"type": "NOISE:X_ERROR",
"targets": [
4
],
"controls": [],
"params": [
0.01
],
"layer": 6
},
{
"type": "Measure",
"targets": [
0
],
"controls": [],
"params": [],
"layer": 6
},
{
"type": "Measure",
"targets": [
2
],
"controls": [],
"params": [],
"layer": 6
},
{
"type": "Measure",
"targets": [
4
],
"controls": [],
"params": [],
"layer": 6
},
{
"type": "DETECTOR",
"targets": [],
"controls": [],
"params": [
1.0,
2.0
],
"layer": 6
},
{
"type": "DETECTOR",
"targets": [],
"controls": [],
"params": [
3.0,
2.0
],
"layer": 6
},
{
"type": "OBSERVABLE",
"targets": [],
"controls": [],
"params": [
0.0
],
"layer": 6
}
]
}
},
{
"type": "qec_snapshot",
"data": {
"num_qubits": 5,
"num_detectors": 6,
"num_observables": 1,
"num_ticks": 6,
"coords": {
"qubits": [
null,
null,
null,
null,
null
],
"detectors": [
[
1.0,
0.0,
0.0
],
[
3.0,
0.0,
0.0
],
[
1.0,
1.0,
0.0
],
[
3.0,
1.0,
0.0
],
[
1.0,
2.0,
0.0
],
[
3.0,
2.0,
0.0
]
]
},
"dem": {
"nodes": 6,
"edge_count": 5,
"boundary_edge_count": 2,
"hyperedges_count": 0,
"truncated": false,
"edges": [
{
"d1": 0,
"d2": 2,
"obs": [],
"p": 0.01
},
{
"d1": 1,
"d2": 3,
"obs": [],
"p": 0.01
},
{
"d1": 2,
"d2": 4,
"obs": [],
"p": 0.01
},
{
"d1": 3,
"d2": 5,
"obs": [],
"p": 0.01
},
{
"d1": 4,
"d2": 5,
"obs": [],
"p": 0.01
}
],
"boundary_edges": [
{
"d": 4,
"obs": [],
"p": 0.01
},
{
"d": 5,
"obs": [
0
],
"p": 0.01
}
]
},
"sample_decode": null
}
},
{
"type": "result",
"data": {
"state_vector": [],
"probabilities": {},
"measurements": "<any>",
"bloch_coords": [],
"execution_time_ms": "<any>",
"shot_count": 128,
"metrics": {},
"seed_honored": true
}
}
]
},
{
"request": {
"type": "qec_snapshot",
"max_edges": 5
},
"responses": [
{
"type": "qec_snapshot",
"data": {
"num_qubits": 5,
"num_detectors": 6,
"num_observables": 1,
"num_ticks": 6,
"coords": {
"qubits": [
null,
null,
null,
null,
null
],
"detectors": [
[
1.0,
0.0,
0.0
],
[
3.0,
0.0,
0.0
],
[
1.0,
1.0,
0.0
],
[
3.0,
1.0,
0.0
],
[
1.0,
2.0,
0.0
],
[
3.0,
2.0,
0.0
]
]
},
"dem": {
"nodes": 6,
"edge_count": 5,
"boundary_edge_count": 2,
"hyperedges_count": 0,
"truncated": true,
"edges": [],
"boundary_edges": []
},
"sample_decode": null,
"dem_text": "<any>"
}
}
]
}
]

Azure Quantum Resource Estimator over a circuit, wrapping the estimator that ships in qdk. language: "qsharp" compiles the Q# source and estimates its entry expression; language: "qasm3" estimates OpenQASM 3 (e.g. qiskit.qasm3.dumps of the active qiskit circuit). options carries a qubit model preset, a QEC scheme, and an error budget; the reply’s data leads with the headline numbers (physical qubits, runtime, code distance, T-factory count) plus the estimator’s pre-formatted strings and the full document for export. Available for any framework — not just stim.

qec_estimate.request.json
{
"type": "qec_estimate",
"language": "qsharp",
"code": "operation Main() : Result {\n use qs = Qubit[2];\n H(qs[0]);\n CNOT(qs[0], qs[1]);\n T(qs[0]);\n let r = M(qs[0]);\n ResetAll(qs);\n return r;\n}\n",
"options": {
"qubit_params": "qubit_gate_ns_e3",
"qec_scheme": "surface_code",
"error_budget": 0.001
}
}
qec_estimate.responses.json
[
{
"type": "qec_estimate_result",
"data": {
"physical_qubits": "<any>",
"runtime_ns": "<any>",
"rqops": "<any>",
"code_distance": "<any>",
"logical_error_rate": "<any>",
"num_tfactories": "<any>",
"physical_qubits_algorithm": "<any>",
"physical_qubits_tfactories": "<any>",
"qubit_params": "<any>",
"qec_scheme": "<any>",
"formatted": "<any>",
"full": "<any>"
}
}
]

A campaign is a sinter Monte Carlo collection — tasks (circuit × decoder), adaptive shot allocation, multiprocess workers — wrapped as a managed kernel job. This is the deliberate exception to the “frontend orchestrates, kernel stays dumb” rule: sinter is the orchestrator.

{
"type": "qec_campaign_start",
"campaign_id": "surface-vs-repetition",
"tasks": [
{ "circuit_text": "R 0 1 2 3 4\n", "decoder": "pymatching",
"json_metadata": { "code": "repetition", "d": 3, "p": 0.02 } }
],
"collect": { "max_shots": 50000, "max_errors": 500 },
"workers": "auto",
"progress_interval_s": 1.0,
"existing_stats_csv": " shots, errors, …"
}
  • At least one of collect.max_shots / collect.max_errors is required — an unbounded campaign never terminates.
  • workers: "auto" (default) means CPU count − 1. Task count is capped at 2,000 (an explicit error above — sinter’s adaptive allocation makes large grids sane, but not unbounded ones).
  • One campaign at a time per kernel: a second start answers an error with code campaign_already_running (409-style) naming the running id.
  • Resume: pass the previous run’s csv text as existing_stats_csv. It loads through sinter’s own existing_data machinery, counts toward the collection bounds, and completed tasks are never re-sampled — the result’s sampled_shots reports newly collected shots only (0 for an already-complete campaign). Kernel restarts kill workers by design; resume is what makes that acceptable.
  • No seed field. sinter.collect has no seeding API — Monte Carlo campaigns are not shot-for-shot reproducible, and the protocol refuses to imply otherwise. (Single runs and qec_decode_sample do seed.)

The ack is qec_campaign_started {campaign_id, tasks_total, workers}; progress and the final result then stream on the same connection:

  • qec_campaign_progress (throttled — at most one per progress_interval_s, default 1s): tasks holds accumulated totals for tasks whose numbers changed since the last update (merge client-side by strong_id), plus tasks_complete/tasks_total and sinter’s free-form status_message (its ETA text, version-dependent — display, don’t parse).
  • qec_campaign_result: partial (true after cancel or a worker failure), sampled_shots, stats rows, and csv — the stats in sinter’s native CSV format, bit-for-bit loadable by existing researcher scripts. Write it to disk as-is; never invent a private format.

A validation failure is a plain error (code qec_campaign_invalid) naming exactly what’s wrong — this one omitted the collection bounds:

qec_campaign_start_invalid.request.json
{
"type": "qec_campaign_start",
"campaign_id": "demo",
"tasks": [
{
"circuit_text": "R 0 1 2 3 4\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.02) 1 3\nMR 1 3\nDETECTOR(1, 0) rec[-2]\nDETECTOR(3, 0) rec[-1]\nREPEAT 2 {\n TICK\n CX 0 1 2 3\n TICK\n CX 2 1 4 3\n TICK\n X_ERROR(0.02) 1 3\n MR 1 3\n SHIFT_COORDS(0, 1)\n DETECTOR(1, 0) rec[-2] rec[-4]\n DETECTOR(3, 0) rec[-1] rec[-3]\n}\nX_ERROR(0.02) 0 2 4\nM 0 2 4\nDETECTOR(1, 1) rec[-2] rec[-3] rec[-5]\nDETECTOR(3, 1) rec[-1] rec[-2] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]",
"decoder": "pymatching",
"json_metadata": {
"d": 3
}
}
]
}
qec_campaign_start_invalid.responses.json
[
{
"type": "error",
"message": "at least one of collect.max_shots / collect.max_errors is required \u2014 an unbounded campaign never terminates",
"code": "qec_campaign_invalid",
"phase": "qec_campaign"
}
]

{type: "qec_campaign_cancel", campaign_id} → immediate ack qec_campaign_cancelled {campaign_id, accepted}, then a graceful sinter shutdown: everything already collected (plus any resumed prior data) comes back as a qec_campaign_result with "partial": true.

A full captured session — start, one progress update, cancel, partial result. Illustrative (timing-dependent message counts; the semantics are pinned by kernel/tests/test_qec_campaign.py instead):

illustrative/qec_campaign.session.json
[
{
"request": {
"type": "qec_campaign_start",
"campaign_id": "surface-vs-repetition",
"tasks": [
{
"circuit_text": "R 0 1 2 3 4\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.02) 1 3\nMR 1 3\nDETECTOR(1, 0) rec[-2]\nDETECTOR(3, 0) rec[-1]\nREPEAT 2 {\n TICK\n CX 0 1 2 3\n TICK\n CX 2 1 4 3\n TICK\n X_ERROR(0.02) 1 3\n MR 1 3\n SHIFT_COORDS(0, 1)\n DETECTOR(1, 0) rec[-2] rec[-4]\n DETECTOR(3, 0) rec[-1] rec[-3]\n}\nX_ERROR(0.02) 0 2 4\nM 0 2 4\nDETECTOR(1, 1) rec[-2] rec[-3] rec[-5]\nDETECTOR(3, 1) rec[-1] rec[-2] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]",
"decoder": "pymatching",
"json_metadata": {
"code": "repetition",
"d": 3,
"p": 0.02,
"i": 0
}
},
{
"circuit_text": "R 0 1 2 3 4\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.02) 1 3\nMR 1 3\nDETECTOR(1, 0) rec[-2]\nDETECTOR(3, 0) rec[-1]\nREPEAT 2 {\n TICK\n CX 0 1 2 3\n TICK\n CX 2 1 4 3\n TICK\n X_ERROR(0.02) 1 3\n MR 1 3\n SHIFT_COORDS(0, 1)\n DETECTOR(1, 0) rec[-2] rec[-4]\n DETECTOR(3, 0) rec[-1] rec[-3]\n}\nX_ERROR(0.02) 0 2 4\nM 0 2 4\nDETECTOR(1, 1) rec[-2] rec[-3] rec[-5]\nDETECTOR(3, 1) rec[-1] rec[-2] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]",
"decoder": "pymatching",
"json_metadata": {
"code": "repetition",
"d": 3,
"p": 0.02,
"i": 1
}
}
],
"collect": {
"max_shots": 50000,
"max_errors": 500
},
"workers": 2,
"progress_interval_s": 0.3
},
"responses": [
{
"type": "qec_campaign_started",
"campaign_id": "surface-vs-repetition",
"tasks_total": 30,
"workers": 2
},
{
"type": "qec_campaign_progress",
"campaign_id": "surface-vs-repetition",
"tasks": [
{
"strong_id": "533523ae9d5717332fe8905449e21bbbdee0562c4ffa0bb2ca99066612a9d80b",
"decoder": "pymatching",
"json_metadata": {
"code": "repetition",
"d": 3,
"p": 0.02,
"i": 1
},
"shots": 50000,
"errors": 37,
"discards": 0,
"seconds": 0.004,
"custom_counts": {}
}
],
"tasks_complete": 1,
"tasks_total": 30,
"status_message": "29 tasks left:\n workers decoder eta shots_left errors_left json_metadata \n 1 pymatching ? 50000 500 code=repetition,d=3,p=0.02,i=0 \n 1 pymatching ? 50000 500 code=repetition,d=3,p=0.02,i=2 \n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=3 \n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=4 \n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=5 \n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=6 \n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=7 \n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=8 \n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=9 \n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=10\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=11\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=12\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=13\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=14\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=15\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=16\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=17\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=18\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=19\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=20\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=21\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=22\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=23\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=24\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=25\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=26\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=27\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=28\n 0 pymatching ?\u00b7\u221e 50000 500 code=repetition,d=3,p=0.02,i=29"
}
]
},
{
"request": {
"type": "qec_campaign_cancel",
"campaign_id": "surface-vs-repetition"
},
"responses": [
{
"type": "qec_campaign_cancelled",
"campaign_id": "surface-vs-repetition",
"accepted": true
},
{
"type": "qec_campaign_result",
"campaign_id": "surface-vs-repetition",
"partial": true,
"sampled_shots": 250000,
"stats": [
{
"strong_id": "533523ae9d5717332fe8905449e21bbbdee0562c4ffa0bb2ca99066612a9d80b",
"decoder": "pymatching",
"json_metadata": {
"code": "repetition",
"d": 3,
"p": 0.02,
"i": 1
},
"shots": 50000,
"errors": 37,
"discards": 0,
"seconds": 0.004,
"custom_counts": {}
},
{
"strong_id": "8c8ed2950a964d713970cb7ff54fe27c771bbd8c3c31908a1a95cbccbe259ce3",
"decoder": "pymatching",
"json_metadata": {
"code": "repetition",
"d": 3,
"p": 0.02,
"i": 0
},
"shots": 50000,
"errors": 58,
"discards": 0,
"seconds": 0.004,
"custom_counts": {}
}
],
"csv": " shots, errors, discards, seconds,decoder,strong_id,json_metadata,custom_counts\n 50000, 37, 0, 0.004,pymatching,533523ae9d5717332fe8905449e21bbbdee0562c4ffa0bb2ca99066612a9d80b,\"{\"\"code\"\":\"\"repetition\"\",\"\"d\"\":3,\"\"i\"\":1,\"\"p\"\":0.02}\",\n 50000, 58, 0, 0.004,pymatching,8c8ed2950a964d713970cb7ff54fe27c771bbd8c3c31908a1a95cbccbe259ce3,\"{\"\"code\"\":\"\"repetition\"\",\"\"d\"\":3,\"\"i\"\":0,\"\"p\"\":0.02}\",\n..."
}
]
}
]

Campaigns whose source is a Python entry: file provide circuits via the nuclei_circuits(noise) contract: the file defines nuclei_circuits(noise: dict) -> dict[str, stim.Circuit], the runner sends the file’s code plus one grid point’s resolved noise dict, and the kernel returns {label: circuit_text} (qec_circuits). Arbitrary custom noise stays possible — the user applies the dict however they like. Contract violations (missing function, wrong return shape, non-Circuit values) are qec_materialize_invalid errors with the exact complaint.

qec_materialize.request.json
{
"type": "qec_materialize",
"code": "import stim\n\ndef nuclei_circuits(noise):\n p = noise[\"p\"]\n return {\n \"rep_d3\": stim.Circuit.generated(\n \"repetition_code:memory\", distance=3, rounds=2,\n before_measure_flip_probability=p,\n ),\n }\n",
"noise": {
"p": 0.01
}
}
qec_materialize.responses.json
[
{
"type": "qec_circuits",
"circuits": {
"rep_d3": "R 0 1 2 3 4\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.01) 1 3\nMR 1 3\nDETECTOR(1, 0) rec[-2]\nDETECTOR(3, 0) rec[-1]\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.01) 1 3\nMR 1 3\nSHIFT_COORDS(0, 1)\nDETECTOR(1, 0) rec[-2] rec[-4]\nDETECTOR(3, 0) rec[-1] rec[-3]\nX_ERROR(0.01) 0 2 4\nM 0 2 4\nDETECTOR(1, 1) rec[-2] rec[-3] rec[-5]\nDETECTOR(3, 1) rec[-1] rec[-2] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]"
}
}
]

One sampled shot, decoded — the detector-graph overlay’s “sample a shot” button. Cheap, synchronous, and deterministic for a given seed. v1 decodes with pymatching only (campaigns support every decoder sinter knows); matched_edges pairs detector indices, with d2: null for boundary matches.

qec_decode_sample.request.json
{
"type": "qec_decode_sample",
"circuit_text": "R 0 1 2 3 4\nTICK\nCX 0 1 2 3\nTICK\nCX 2 1 4 3\nTICK\nX_ERROR(0.02) 1 3\nMR 1 3\nDETECTOR(1, 0) rec[-2]\nDETECTOR(3, 0) rec[-1]\nREPEAT 2 {\n TICK\n CX 0 1 2 3\n TICK\n CX 2 1 4 3\n TICK\n X_ERROR(0.02) 1 3\n MR 1 3\n SHIFT_COORDS(0, 1)\n DETECTOR(1, 0) rec[-2] rec[-4]\n DETECTOR(3, 0) rec[-1] rec[-3]\n}\nX_ERROR(0.02) 0 2 4\nM 0 2 4\nDETECTOR(1, 1) rec[-2] rec[-3] rec[-5]\nDETECTOR(3, 1) rec[-1] rec[-2] rec[-4]\nOBSERVABLE_INCLUDE(0) rec[-1]",
"decoder": "pymatching",
"seed": 3
}
qec_decode_sample.responses.json
[
{
"type": "qec_decode_sample",
"num_detectors": 8,
"syndrome": "<any>",
"matched_edges": "<any>",
"predicted_observable_flips": "<any>",
"actual_observable_flips": "<any>"
}
]

A concrete response, captured on one machine (values are seed-and-machine deterministic — like all stim sampling, they vary across CPUs, which is why the replayed fixture above pins the shape rather than the values):

{
"type": "qec_decode_sample",
"num_detectors": 8,
"syndrome": [5, 7],
"matched_edges": [{ "d1": 5, "d2": 7 }],
"predicted_observable_flips": [0],
"actual_observable_flips": [0]
}