Client examples
Two complete clients that connect, parse a Bell circuit, execute it, and
print the measurement histogram. Both implement the one pattern every
kernel client needs: drain messages until the terminal type arrives,
relaying any interleaved output/stderr/error along the way (see
the streaming model). For
scripted hardware_submit loops built on the same pattern, see
pipelines.
Start a kernel first:
cd kernel && NUCLEI_KERNEL_TOKEN="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')" python server.py# Nuclei kernel ready on ws://localhost:9742The kernel requires a token (see Authentication). It
writes the token to ~/.nuclei/runtime/kernel-<port>.json (and to kernel.json,
which points at the newest kernel), and the example clients read the file for
the port they connect to (or $NUCLEI_KERNEL_TOKEN).
Requires pip install websockets. This exact file is executed by the kernel
test suite against a live server on every CI run
(kernel/tests/test_docs_fixtures.py), so it is guaranteed to speak the
current protocol.
#!/usr/bin/env python3"""Minimal Nuclei kernel client: parse a Bell circuit, run it, print counts.
Usage: python example_client.py [ws://localhost:9742]Requires: pip install websocketsAuth: reads the token from $NUCLEI_KERNEL_TOKEN or ~/.nuclei/runtime/kernel-<port>.json
This file is replay-tested: kernel/tests/test_docs_fixtures.py runs it as asubprocess against a live kernel server and asserts the histogram output."""import asyncioimport hashlibimport hmacimport jsonimport secretsimport osimport sysfrom urllib.parse import urlparse
import websockets
BELL = """from qiskit import QuantumCircuit
qc = QuantumCircuit(2, 2)qc.h(0)qc.cx(0, 1)qc.measure([0, 1], [0, 1])"""
def _port_of(url: str | None) -> int | None: """Port of a ws:// URL, or None when it has none.""" if not url: return None return urlparse(url).port
def _runtime_info(port: int | None = None) -> dict: """Live runtime-file contents ({} if absent, unreadable or stale).
With a port, prefers that kernel's own kernel-<port>.json (several kernels can run at once); kernel.json is the newest-kernel pointer. """ runtime_dir = os.environ.get("NUCLEI_RUNTIME_DIR") or os.path.join( os.path.expanduser("~"), ".nuclei", "runtime" ) names = ([f"kernel-{port}.json"] if port else []) + ["kernel.json"] for name in names: try: with open(os.path.join(runtime_dir, name)) as handle: info = json.load(handle) if port and info.get("port") != port: continue # never use another kernel's token os.kill(int(info["pid"]), 0) # raises if that kernel is gone return info except (OSError, ValueError, KeyError, TypeError): continue return {}
def default_url() -> str: """The newest kernel the runtime pointer describes (its token matches that port).""" return _runtime_info().get("url") or "ws://localhost:9742"
def load_token(url: str | None = None) -> str: """Token for this kernel launch: $NUCLEI_KERNEL_TOKEN, else the user-only runtime file the kernel writes at startup (like a Jupyter runtime file).""" token = os.environ.get("NUCLEI_KERNEL_TOKEN") if token: return token info = _runtime_info(_port_of(url)) if "token" not in info: raise RuntimeError( "no token: set NUCLEI_KERNEL_TOKEN or start a kernel (runtime file missing or stale)" ) return info["token"]
async def authenticate(ws, url: str | None = None) -> None: """First frame on every connection: present the token and a random nonce, then check the kernel's proof (HMAC-SHA256(token, nonce)) in the ack.""" token = load_token(url) nonce = secrets.token_hex(32) await ws.send(json.dumps({"type": "authenticate", "token": token, "nonce": nonce})) reply = json.loads(await ws.recv()) if reply.get("type") != "authenticated": raise RuntimeError("kernel rejected the authentication token") expected = hmac.new(token.encode(), nonce.encode(), hashlib.sha256).hexdigest() if not hmac.compare_digest(str(reply.get("proof", "")), expected): raise RuntimeError("peer did not prove it is the Nuclei kernel; refusing to continue")
async def drain_until(ws, terminal_type: str) -> dict: """Read messages until `terminal_type` arrives, relaying streamed text.
Every request produces an ordered SEQUENCE of messages — output/stderr may interleave before the terminal message, so a robust client loops. """ while True: message = json.loads(await ws.recv()) if message["type"] == "output": print(message["text"], end="") elif message["type"] == "stderr": print(message["text"], end="", file=sys.stderr) elif message["type"] == "error": print(f"kernel error: {message['message']}", file=sys.stderr) if message["type"] == terminal_type: return message
async def main(url: str) -> int: async with websockets.connect(url) as ws: await authenticate(ws, url) await ws.send(json.dumps({"type": "parse", "code": BELL, "language": "python"})) snapshot = await drain_until(ws, "snapshot") if snapshot["data"] is None: return 1 print(f"circuit: {len(snapshot['data']['gates'])} gates, " f"depth {snapshot['data']['depth']}")
await ws.send(json.dumps( {"type": "execute", "code": BELL, "shots": 512, "language": "python"} )) result = await drain_until(ws, "result") if result["data"] is None: # an error message already explained why return 1 for bitstring, count in sorted(result["data"]["measurements"].items()): print(f"{bitstring}: {count}") return 0
if __name__ == "__main__": url = sys.argv[1] if len(sys.argv) > 1 else default_url() sys.exit(asyncio.run(main(url)))python example_client.py # default ws://localhost:9742python example_client.py ws://localhost:9743Requires the ws package (npm install ws); run directly with
tsx — no build step. This client mirrors the Python one,
which is the replay-tested variant.
// Minimal Nuclei kernel client: parse a Bell circuit, run it, print counts.//// Run with: npx tsx example_client.ts [ws://localhost:9742]// Requires the `ws` package: npm install ws// (This file is documentation — it mirrors example_client.py, which is the// variant exercised by the kernel test suite.)import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';import { readFileSync } from 'node:fs';import { homedir } from 'node:os';import { join } from 'node:path';import WebSocket from 'ws';
const BELL = `from qiskit import QuantumCircuit
qc = QuantumCircuit(2, 2)qc.h(0)qc.cx(0, 1)qc.measure([0, 1], [0, 1])`;
// The kernel requires a per-launch token: $NUCLEI_KERNEL_TOKEN, or the// user-only runtime file the kernel writes at startup.// With several kernels running, kernel-<port>.json holds the token for that// port; kernel.json only points at the newest kernel.function loadToken(kernelUrl: string): string { if (process.env.NUCLEI_KERNEL_TOKEN) return process.env.NUCLEI_KERNEL_TOKEN; const dir = process.env.NUCLEI_RUNTIME_DIR ?? join(homedir(), '.nuclei', 'runtime'); const port = Number(new URL(kernelUrl).port); for (const name of [`kernel-${port}.json`, 'kernel.json']) { try { const info = JSON.parse(readFileSync(join(dir, name), 'utf8')) as { token: string; port: number }; if (info.port === port) return info.token; } catch { // Missing or unreadable: try the next candidate. } } throw new Error(`no token for port ${port}: set NUCLEI_KERNEL_TOKEN or start a kernel`);}
const url = process.argv[2] ?? 'ws://localhost:9742';const ws = new WebSocket(url);
type KernelMessage = { type: string; [key: string]: unknown };const queue: KernelMessage[] = [];let wake: (() => void) | null = null;
ws.on('message', (raw) => { queue.push(JSON.parse(raw.toString()) as KernelMessage); wake?.();});
// Every request produces an ordered SEQUENCE of messages — output/stderr// may interleave before the terminal message, so a robust client loops.async function drainUntil(terminalType: string): Promise<KernelMessage> { for (;;) { while (queue.length === 0) { await new Promise<void>((resolve) => { wake = resolve; }); } const message = queue.shift()!; if (message.type === 'output') process.stdout.write(message.text as string); if (message.type === 'stderr') process.stderr.write(message.text as string); if (message.type === 'error') console.error(`kernel error: ${message.message}`); if (message.type === terminalType) return message; }}
ws.on('open', async () => { // Send a random nonce and verify the kernel's proof, HMAC-SHA256(token, nonce) // in hex, so a program squatting the port cannot pass as the kernel. const token = loadToken(url); const nonce = randomBytes(32).toString('hex'); ws.send(JSON.stringify({ type: 'authenticate', token, nonce })); const ack = await drainUntil('authenticated'); const expected = createHmac('sha256', token).update(nonce).digest(); const proof = Buffer.from(typeof ack.proof === 'string' ? ack.proof : '', 'hex'); if (proof.length !== expected.length || !timingSafeEqual(proof, expected)) { console.error('peer did not prove it is the Nuclei kernel; refusing to continue'); process.exit(1); } ws.send(JSON.stringify({ type: 'parse', code: BELL, language: 'python' })); const snapshot = await drainUntil('snapshot'); const data = snapshot.data as { gates: unknown[]; depth: number } | null; if (!data) process.exit(1); console.log(`circuit: ${data.gates.length} gates, depth ${data.depth}`);
ws.send(JSON.stringify({ type: 'execute', code: BELL, shots: 512, language: 'python' })); const result = await drainUntil('result'); const sim = result.data as { measurements: Record<string, number> } | null; if (!sim) process.exit(1); for (const [bitstring, count] of Object.entries(sim.measurements).sort()) { console.log(`${bitstring}: ${count}`); } ws.close();});npx tsx example_client.ts # default ws://localhost:9742Expected output for either client (counts vary run to run):
circuit: 4 gates, depth 300: 25111: 261One-off requests from the shell
Section titled “One-off requests from the shell”For poking at the protocol without writing code,
websocat works well with the fixture files
from docs-site/fixtures/ — every documented request is sitting there as
ready-to-send JSON:
# The first frame must be the authenticate message, read here from the# kernel's user-only runtime file. jq -c compacts the pretty-printed fixture# to one line (websocat sends one frame per input line); --no-close keeps the# connection open after stdin EOF so the response frames have time to arrive.{ jq -c '{type: "authenticate", token: .token}' ~/.nuclei/runtime/kernel-9742.json jq -c . docs-site/fixtures/run_python_hello.request.json} | websocat --no-close ws://localhost:9742The first response frame is {"type": "authenticated"}. (This one-liner omits
the optional nonce, so there is no proof to check; the Python and
TypeScript clients above send a nonce and verify the proof, see
Server proof.) Each response frame
prints on its own line; exit with Ctrl-C. Without the authenticate frame the
kernel closes the connection with code 4401.