Documentation

Get started with QObserva in minutes. Complete guides, examples, and API reference.

Quick Start

Get QObserva running in 3 steps. Current release: qobserva 0.1.9 (what's new).

1. Install QObserva

Install QObserva together with the adapter extras for the SDK you use:

pip install qobserva "qobserva-agent[qiskit]"

Replace qiskit with braket, cirq, pennylane, pyquil or dwave, or use all-sdks for all six. The dashboard is prebuilt, so no Node.js is needed. Python 3.12 supports every SDK (see Python compatibility).

Or install from source:

git clone https://github.com/BuildersArk/qobserva.git
cd qobserva
pip install -e packages/qobserva_agent -e packages/qobserva_collector -e packages/qobserva_local -e packages/qobserva
cd packages/qobserva_ui_react && npm install   # dashboard dev server (needs Node.js)

2. Start QObserva

qobserva up

This starts the collector (http://127.0.0.1:8080) and the dashboard. Open http://localhost:3000 in your browser. Stop everything with qobserva down from another terminal, or Ctrl+C. If QObserva is already running, qobserva up tells you where instead of starting a second copy.

3. Instrument Your Code

from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler
from qobserva import observe_run

@observe_run(
    project="my_project",
    tags={"sdk": "qiskit", "algorithm": "bell_state"},
    backend="statevector_sampler",  # V2 primitive results don't name their backend
    provider="local_sim",
)
def my_quantum_algorithm():
    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    qc.measure_all()
    return StatevectorSampler().run([qc], shots=1024).result()

my_quantum_algorithm()

Return the SDK's result object from the decorated function. QObserva records the run in the background, so your program never waits for it. If the collector isn't running, you get one warning and your code carries on.

⚠️ Important: SDK Tag Required

Always include tags={"sdk": "..."} in your decorator for reliable adapter selection. Supported values: "qiskit", "braket", "cirq", "pennylane", "pyquil", "dwave".

Python Version Compatibility

Recommended: Python 3.12 (supports all 6 SDKs). Each SDK's example was run on Python 3.12, 3.13 and 3.14 in September 2026 (QObserva 0.1.6; 0.1.7 and 0.1.8 were re-checked on Python 3.12 with every SDK, 0.1.8 from PyPI):

SDK Version tested 3.12 3.13 3.14
Qiskit2.5.2✅✅✅
Braket1.127✅✅✅
Cirq1.7.0✅✅✅
PennyLane0.45.1✅✅✅
pyQuil4.20 / 4.21✅❌❌
D-Wave (dimod)0.12.22✅✅✅

pyQuil itself supports only Python 3.11–3.12, so qobserva-agent[pyquil] skips it on 3.13 and later. QObserva's own packages support Python 3.10+.

SDK Examples

Each example below is complete: save it as a file, start qobserva up, and run it. The same examples are in the examples folder on GitHub.

Qiskit

from qobserva import observe_run
from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler

@observe_run(
    project="qiskit_test",
    tags={"sdk": "qiskit", "algorithm": "bell_state"},
    benchmark_params={"target_bitstrings": ["00", "11"]},
    backend="statevector_sampler",  # V2 primitive results don't name their backend
    provider="local_sim",
)
def run():
    qc = QuantumCircuit(2, 2)
    qc.h(0)
    qc.cx(0, 1)
    qc.measure([0, 1], [0, 1])
    sampler = StatevectorSampler()
    return sampler.run([qc], shots=1024).result()

run()

Install: pip install qobserva "qobserva-agent[qiskit]". Backends: Aer runs are labeled local_sim / aer_simulator automatically. For IBM hardware, return the job and set await_result=True, or pass backend=service.backend("ibm_...").

Amazon Braket

from qobserva import observe_run
from braket.circuits import Circuit
from braket.devices import LocalSimulator

@observe_run(
    project="braket_test",
    tags={"sdk": "braket", "algorithm": "bell_state"},
    benchmark_params={"target_bitstrings": ["00", "11"]},
)
def run():
    bell = Circuit().h(0).cnot(0, 1)
    device = LocalSimulator()
    return device.run(bell, shots=1024).result()

run()

Install: pip install qobserva "qobserva-agent[braket]". Works on Python 3.12–3.14. No AWS credentials needed for LocalSimulator.

Cirq

from qobserva import observe_run
import cirq

@observe_run(
    project="cirq_test",
    tags={"sdk": "cirq", "algorithm": "bell_state"},
    measurement_key="result",  # Required! Must match cirq.measure(..., key=...)
    benchmark_params={"target_bitstrings": ["00", "11"]},
)
def run():
    q0, q1 = cirq.LineQubit(0), cirq.LineQubit(1)
    circuit = cirq.Circuit()
    circuit.append(cirq.H(q0))
    circuit.append(cirq.CNOT(q0, q1))
    circuit.append(cirq.measure(q0, q1, key="result"))
    return cirq.Simulator().run(circuit, repetitions=1024)

run()

Install: pip install qobserva "qobserva-agent[cirq]". Note: the measurement_key parameter is required.

PennyLane

from qobserva import observe_run
import pennylane as qml

@observe_run(
    project="pennylane_test",
    tags={"sdk": "pennylane", "algorithm": "bell_state"},
    benchmark_params={"target_bitstrings": ["00", "11"]},
)
def run():
    dev = qml.device("default.qubit", wires=2)
    @qml.set_shots(1024)
    @qml.qnode(dev)
    def circuit():
        qml.Hadamard(wires=0)
        qml.CNOT(wires=[0, 1])
        return qml.counts()
    return circuit()

run()

Install: pip install qobserva "qobserva-agent[pennylane]". Works with qml.counts() and qml.expval(). qml.set_shots needs PennyLane 0.42+; on older versions pass shots=1024 to qml.device instead.

pyQuil

from qobserva import observe_run
from pyquil import Program, get_qc
from pyquil.gates import H, CNOT, MEASURE

@observe_run(
    project="pyquil_test",
    tags={"sdk": "pyquil", "algorithm": "bell_state"},
    benchmark_params={"target_bitstrings": ["00", "11"]},
    backend="2q-qvm",
    provider="local_sim",
)
def run():
    program = Program()
    ro = program.declare("ro", "BIT", 2)
    program += H(0)
    program += CNOT(0, 1)
    program += MEASURE(0, ro[0])
    program += MEASURE(1, ro[1])
    program.wrap_in_numshots_loop(1024)
    qc = get_qc("2q-qvm")
    return qc.run(qc.compile(program))

run()

Install: pip install qobserva "qobserva-agent[pyquil]" on Python 3.11–3.12. pyQuil needs Rigetti's QVM and quilc servers, e.g. docker run -d -p 5000:5000 rigetti/qvm -S and docker run -d -p 5555:5555 rigetti/quilc -R.

D-Wave

from qobserva import observe_run
import dimod

@observe_run(
    project="dwave_test",
    tags={"sdk": "dwave", "algorithm": "qubo"},
    backend="ExactSolver",  # a SampleSet doesn't say which sampler produced it
    provider="local_sim",
)
def run():
    Q = {(0, 0): -1, (0, 1): 1, (1, 2): 1}
    bqm = dimod.BinaryQuadraticModel.from_qubo(Q)
    return dimod.ExactSolver().sample(bqm)

run()

Install: pip install qobserva "qobserva-agent[dwave]". QObserva records the lowest energy. Add "ground_state_energy" to benchmark_params to get an approximation ratio.

API Reference

@observe_run Decorator

@observe_run(
    project: str,                     # Project name (e.g., "my_project")
    tags: dict = None,                # Tags dict (must include "sdk")
    capture_program: str = "full",    # "full", "hash", "none" or "attachment"
    await_result: bool = False,       # Wait for a returned cloud job and record its results
    circuit = None,                   # The circuit that ran, for circuit metrics and text
    measurement_key: str = None,      # Required for Cirq
    benchmark_id: str = None,         # Optional benchmark identifier
    benchmark_params: dict = None,    # Optional benchmark parameters
    endpoint: str = None,             # Collector URL (default: local collector)
    api_key: str = None,              # Token if the collector requires one
    backend = None,                   # Backend object or name, if the result doesn't say
    provider: str = None,             # Provider label, e.g. "ibm", "aws_braket", "local_sim"
)

Parameters

Parameter Type Required Description
project str Yes Project name for grouping runs (e.g., "qiskit_test")
tags dict Yes Tags dict. Must include "sdk" key. Recommended: "algorithm", "test"
await_result bool No If your function returns a cloud job (IBM Runtime job, Braket task, Google Quantum Engine job), QObserva waits for it, records its results, backend and job details, and the decorated function returns the result. Without it, a cloud job is recorded as pending with its job id and returned unchanged. Jobs from local simulators are always read.
circuit circuit No The Qiskit, Cirq, Braket or pyQuil circuit that ran. QObserva records its qubits, depth, two-qubit gates and gate counts, plus its OpenQASM (Quil for pyQuil) and a text diagram. Not needed when the job or result already carries the circuit (IBM Runtime jobs, Braket results, Cirq Engine jobs, PennyLane QNodes).
measurement_key str Cirq only Measurement key matching cirq.measure() key
benchmark_id str No Benchmark identifier for comparison
benchmark_params dict No Algorithm-specific values. "target_bitstrings" enables the success-probability metric; "ground_state_energy" enables the approximation ratio.
backend object or str No The backend the run executed on, when the SDK result doesn't include it (e.g. AerSimulator(), service.backend("ibm_brisbane"), "statevector_sampler")
provider str No Provider label to record, e.g. "ibm", "aws_braket", "local_sim"
endpoint, api_key str No Send runs to a different collector, or authenticate with one that requires a token
capture_program str No What to record about the circuit: "full" (default since 0.1.8: metrics, a hash, OpenQASM/Quil and a diagram), "hash" (metrics and a hash only, the 0.1.7 behavior), "none" (nothing) or "attachment". Run data goes only to the collector you configure (local by default).

Provider and backend labels

QObserva records the provider and backend that the SDK's result actually reports. When the result doesn't say, you'll see unknown rather than a guess; pass backend= and provider= to label those runs. Examples: Aer and IBM's fake backends are local_sim, IBM hardware is ibm with the device name, Braket's LocalSimulator is local_sim, AWS-managed simulators are aws_braket (e.g. sv1), Braket QPUs are labeled by maker (e.g. ionq / Forte-1), Cirq's Quantum Virtual Machine is local_sim with the processor name (e.g. willow_pink (virtual)), PennyLane plugins are labeled by the device they ran (e.g. ibm / ibm_fez for qiskit.remote), and the Rigetti QVM is local_sim.

Results computed exactly, without sampling (a statevector Estimator, PennyLane in analytic mode, Braket with shots=0), show Exact instead of a shot count.

Configuration

Everything works with the defaults. These environment variables change them:

Variable Default Description
QOBSERVA_ENDPOINTlocal collectorWhere your code sends runs (http://127.0.0.1:8080/v1/ingest/run-event)
QOBSERVA_HOST_MODEhashMachine identity on each run: hash, raw (hostname) or none
QOBSERVA_ASYNC1Set to 0 to send each run before your function returns
QOBSERVA_DATA_DIRuser data folderWhere the collector stores its database and run data
QOBSERVA_COLLECTOR_PORT, QOBSERVA_UI_PORT8080, 3000Ports used by qobserva up
QOBSERVA_LOCAL_TOKENunsetWhen set, the collector requires this token (the dashboard passes it automatically)

In a notebook or test that reads runs back immediately, call qobserva.flush() to wait until pending runs are delivered. The full list is in the API reference.

Privacy and Security

Common Use Cases

Algorithm Tagging

Tag your runs with algorithm names to enable algorithm-specific dashboards:

tags={
    "sdk": "qiskit",
    "algorithm": "vqe",      # Enables algorithm comparison
    "test": "variational"
}

Common algorithm tags: "bell_state", "grover", "vqe", "qaoa", "qubo", "qft"

Benchmark Parameters

Include algorithm-specific metrics for better analysis:

@observe_run(
    project="vqe_experiment",
    tags={"sdk": "qiskit", "algorithm": "vqe"},
    benchmark_id="h2_ground_state",
    benchmark_params={
        "energy": -1.137,
        "convergence_iterations": 10
    }
)

Viewing Results

After running your instrumented code:

  1. Open http://localhost:3000 in your browser
  2. Filter by project name (e.g., qiskit_test)
  3. View run details, compare runs, and analyze trends

Troubleshooting

More Documentation

For comprehensive guides, examples, and troubleshooting:

View Full Documentation on GitHub →