> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.conductorquantum.com/coda/node/integration-guide/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.conductorquantum.com/_mcp/server. # Integration Guide This guide shows how to connect your own QPU or simulator backend to Coda with `coda-node`. Your integration is a Python package that exposes an executor. `coda-node` loads that executor, sends it compiled jobs, and handles the cloud-facing runtime around it. ## Prerequisites * Python 3.11 or newer * A QPU registered in Coda * A one-time node token from Coda for first startup * Network access from the node host to your hardware backend * OpenVPN installed if your node token uses VPN mode * A Python package for your hardware integration > **Note** > > After first startup, `coda-node` persists JWT credentials and reconnects automatically. You only need a fresh node token after resetting the node or rotating credentials. ## Create an executor package Create a Python package for your backend. It should depend on `coda-node` and any vendor SDKs or internal control libraries needed by your device. For local development, install `coda-node` from the repository: ```bash git clone https://github.com/conductorquantum/coda-node.git cd coda-node uv sync --dev ``` For a deployable integration, add `coda-node` as a dependency of your backend package. > **Note** > > The package installs both `coda-node` and `coda` as equivalent CLI entry points. These docs use `uv run coda-node ...` consistently so commands work from a local checkout without requiring a global install. ## Implement `run` Your executor must provide an async `run(ir, shots)` method. It receives a validated `NativeGateIR` and returns an `ExecutionResult`. ```python from coda_node.server.executor import ExecutionResult from coda_node.server.ir import NativeGateIR class AcmeQpuExecutor: def __init__(self, host: str, port: int) -> None: self.host = host self.port = port async def run(self, ir: NativeGateIR, shots: int) -> ExecutionResult: program = compile_native_ir_for_acme(ir) raw_counts, elapsed_ms = await submit_to_acme_hardware( host=self.host, port=self.port, program=program, shots=shots, ) return ExecutionResult( counts=raw_counts, execution_time_ms=elapsed_ms, shots_completed=sum(raw_counts.values()), ) ``` The `counts` dictionary maps measured bitstrings to shot counts, for example `{"00": 512, "11": 512}`. If your backend executes fewer shots than requested, set `shots_completed` to the actual number. ## Expose an executor factory `coda-node` loads your backend through a factory. The recommended convention is: ```text .executor_factory:create_executor ``` Create an `executor_factory.py` module in your package: ```python import yaml from pydantic import BaseModel from coda_node.server.config import Settings from acme_qpu.executor import AcmeQpuExecutor class AcmeDeviceConfig(BaseModel): host: str port: int def create_executor(settings: Settings) -> AcmeQpuExecutor: with open(settings.device_config) as config_file: config = AcmeDeviceConfig.model_validate(yaml.safe_load(config_file)) return AcmeQpuExecutor(host=config.host, port=config.port) ``` Factories can either accept the `Settings` object or accept no arguments. They can also return a prebuilt object that already has a `run` method. ## Define the device config Point `CODA_DEVICE_CONFIG` at a YAML file that your factory knows how to read. If `./site/device.yaml` exists, `coda-node` uses it automatically. ```yaml executor_factory: acme_qpu.executor_factory:create_executor target: cz num_qubits: 5 host: 192.168.1.120 port: 9095 ``` `coda-node` only reads the optional top-level `executor_factory` key. The remaining schema belongs to your backend package. Use it for hardware addresses, calibration files, credentials, topology, or any other device-specific settings. > **Note** > > If you set `CODA_EXECUTOR_FACTORY`, it takes priority over the `executor_factory` value in the YAML file. ## Start the node On first startup, provide the node token from Coda: ```bash export CODA_DEVICE_CONFIG=./site/device.yaml uv run coda-node start --token ``` You can also set the token with an environment variable: ```bash export CODA_NODE_TOKEN= uv run coda-node start ``` After provisioning succeeds, `coda-node` writes its runtime state and private key to disk. Subsequent restarts can omit the token: ```bash uv run coda-node start ``` To run the node as a background daemon: ```bash uv run coda-node start --daemon uv run coda-node status uv run coda-node logs -n 100 ``` ## Verify readiness The runtime exposes two local health endpoints: ```bash curl http://localhost:8080/health curl http://localhost:8080/ready ``` `/health` confirms the process is alive. `/ready` checks runtime dependencies and returns component status for VPN, Redis, and the current job. It returns `503` when a required dependency is degraded. Use the CLI for a local diagnostic summary: ```bash uv run coda-node doctor ``` In Coda, the QPU should move online after the node connects and starts sending heartbeats. Missing heartbeats eventually marks the QPU offline. ## Optional executor hooks For hardware that supports batching, add `batch_run(jobs)`: ```python async def batch_run( self, jobs: list[tuple[NativeGateIR, int]], ) -> list[ExecutionResult]: ... ``` Return one `ExecutionResult` for each input job in the same order. `coda-node` detects this method automatically. For hardware that can cancel an in-flight job, add `cancel_current_job()`: ```python def cancel_current_job(self) -> None: ... ``` When Coda cancels a job that is already running, `coda-node` calls this hook before cancelling the in-process task. ## Reset or rotate the node To stop the daemon, stop the managed VPN, and remove persisted credentials: ```bash uv run coda-node reset ``` After a reset, start again with a fresh node token. Use this flow when moving the node to a new machine, rotating credentials, or clearing a broken local state. > Implement a coda-node executor for your own QPU.