> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.conductorquantum.com/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
<package>.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 <node-token>
```

You can also set the token with an environment variable:

```bash
export CODA_NODE_TOKEN=<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.