Skip to content

Kernel API overview

The Nuclei kernel is a standalone WebSocket service. The desktop frontend is just its first client — anything that can open a WebSocket and speak JSON can parse circuits, run simulations, and submit hardware jobs. This section documents that protocol as a public API.

PropertyValue
URLws://localhost:9742
Port fallbackIf 9742 is taken, the kernel scans upward through 9742–9761 (range of 20) and binds the first free port
FramingJSON text frames — one request or response object per frame
Max inbound message1 MiB (1_048_576 bytes); larger frames close the connection
HeartbeatWebSocket ping every 30 s; connection dropped after a 20 s pong timeout
AuthPer-launch token, presented in the first frame (see Authentication)

Requests on one connection are processed strictly in order: the handler fully finishes (and sends every response for) request N before reading request N + 1. For concurrent work, open multiple connections — each connection gets its own isolated Executor (parse/execute state never bleeds between clients), while hardware state (provider connections, job registry) is shared process-wide.

The kernel executes code it receives, so every connection must authenticate before any request is handled. This is protocol v2 and it is breaking for external clients: a client that does not send the authenticate frame first is disconnected. See the protocol changelog.

  1. The kernel reads a secret token on startup. The desktop app generates a fresh one per launch and writes it to the kernel’s stdin (NUCLEI_KERNEL_TOKEN_STDIN=1), never into its environment; scripts that launch a kernel themselves can set NUCLEI_KERNEL_TOKEN instead. With no token it refuses to start.
  2. It writes the token and port to user-only (0600, directory 0700) runtime files in ~/.nuclei/runtime/ (override the directory with NUCLEI_RUNTIME_DIR), so local scripts can read them. Each kernel writes its own kernel-<port>.json (for example kernel-9742.json), and also kernel.json, a pointer to the most recently started kernel (last writer wins). With several kernels running, read kernel-<port>.json for the port you are connecting to; kernel.json is only right when you want “the newest kernel”. Both files hold {"url", "port", "pid", "token"}, are written atomically, and are removed on clean shutdown (SIGTERM/SIGINT). Check that pid is still alive before trusting a file left behind by a crash.
  3. The first frame on a connection must be {"type": "authenticate", "token": "<token>", "nonce": "<64 hex chars>"}. The kernel answers {"type": "authenticated", "proof": "<hex>"} and only then handles requests. The token is never sent in the URL. nonce is optional for third-party clients (see Server proof).
  4. Browser connections must also come from an allowed Origin, and the Host header must be a loopback name on the kernel’s port. Scripts that send no Origin are accepted only with the token.

Every socket the app opens authenticates this way: the editor’s, the Trainability Lab’s, the Discovery Engine’s, and the audit and conjecture sessions. Replies in the Quantum AI Lab messages examples start after the handshake.

Anything can listen on a free port, so the kernel also proves it knows the token. The client sends a fresh random nonce (32 random bytes as 64 hex characters, 16 to 256 characters accepted) in the authenticate frame. The ack then carries

proof = hex( HMAC-SHA256( key = token, message = nonce ) )

where token and nonce are the strings exactly as sent (UTF-8, the nonce is not hex-decoded). The client must compare proof in constant time and close the connection without sending anything else when it is missing or wrong.

  • Nuclei’s own client always sends a nonce and requires the proof. It holds back every other frame (your code, buffers, credentials) until the proof verifies, and treats a missing or wrong proof as a failed kernel.
  • A kernel started by a Nuclei version before 0.14.1 answers without a proof, so the app refuses it.
  • For third-party clients the nonce stays optional: a frame without nonce is still accepted and acknowledged without a proof. A frame with a malformed nonce (not a string, or outside 16 to 256 characters) closes with 4401. Sending a nonce and checking the proof is recommended; see client examples.
SituationResult
Foreign Origin or non-loopback HostHTTP 403 on the WebSocket upgrade; the connection never opens
First frame is not a valid authenticate frame, wrong token, malformed nonce, or no frame within 3 sClosed with code 4401
Frame larger than 8 KiB before authenticationClosed with code 1009 (the normal 1 MiB limit applies once authenticated)
More than 32 simultaneous unauthenticated connectionsThe extra connection is closed at once with code 4429; wait a moment and reconnect
Request sent before the authenticated ackNot handled; the connection is closed with 4401

A client library’s “connection refused with 403” therefore means the Origin or Host is wrong, and a 4401 close means the token is wrong or missing.

The token protects the kernel from web pages and from other users on the machine. It does not protect it from other processes running as the same operating-system user: the runtime files are readable by that user (that is how local scripts authenticate), so any process running as you can read the token and run code through the kernel, and could equally run that code directly. This is the stated threat boundary, documented rather than defended: do not treat the token as a sandbox against software you already run as yourself.

When a client is rejected (WebSocket close code 4401), the token it presented did not match the kernel it reached. The Nuclei app shows “Kernel rejected the session token — restart Nuclei” instead of retrying.

For throwaway local experiments only, NUCLEI_KERNEL_INSECURE_DEV=1 disables authentication and prints a warning. The kernel never passes its token to the processes it starts (code execution, Lab workers, the discovery sandbox).

Terminal window
cd kernel && NUCLEI_KERNEL_TOKEN="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')" python server.py

On success the kernel prints exactly one ready line to stdout:

Nuclei kernel ready on ws://localhost:9742

This line is the port-discovery contract: when the default port was taken, the chosen port appears in this line, on its own line, flushed — parse it rather than assuming 9742.

A request does not get a reply. It produces an ordered sequence of response messages on the same connection, ending with a request-specific final message (result for execute, python_result for run_python, snapshot for parse — though a failed parse appends one error after it — and a single reply for every hardware_* request). Streamed output / stderr messages may interleave before the terminal message, so a robust client loops until it sees the terminal type — see client examples.

The canonical example — execute on a Bell circuit produces two messages, snapshot then result:

execute_bell_qiskit.request.json
{
"type": "execute",
"code": "from qiskit import QuantumCircuit\n\nqc = QuantumCircuit(2, 2)\nqc.h(0)\nqc.cx(0, 1)\nqc.measure([0, 1], [0, 1])\n",
"shots": 1024,
"language": "python"
}
execute_bell_qiskit.responses.json
[
{
"type": "snapshot",
"data": {
"framework": "qiskit",
"qubit_count": 2,
"classical_bit_count": 2,
"depth": 3,
"gates": [
{ "type": "H", "targets": [0], "controls": [], "params": [], "layer": 0 },
{ "type": "CNOT", "targets": [1], "controls": [0], "params": [], "layer": 1 },
{ "type": "Measure", "targets": [0], "controls": [], "params": [], "layer": 2 },
{ "type": "Measure", "targets": [1], "controls": [], "params": [], "layer": 2 }
]
}
},
{
"type": "result",
"data": {
"state_vector": [
{ "re": "<approx:0.70710678:0.0000001>", "im": "<approx:0:0.0000001>" },
{ "re": "<approx:0:0.0000001>", "im": "<approx:0:0.0000001>" },
{ "re": "<approx:0:0.0000001>", "im": "<approx:0:0.0000001>" },
{ "re": "<approx:0.70710678:0.0000001>", "im": "<approx:0:0.0000001>" }
],
"probabilities": {
"00": "<approx:0.5:0.000001>",
"11": "<approx:0.5:0.000001>"
},
"measurements": "<any>",
"bloch_coords": [
{ "x": "<approx:0:0.000001>", "y": "<approx:0:0.000001>", "z": "<approx:0:0.000001>" },
{ "x": "<approx:0:0.000001>", "y": "<approx:0:0.000001>", "z": "<approx:0:0.000001>" }
],
"execution_time_ms": "<any>",
"shot_count": 1024,
"metrics": {}
}
}
]

Values like "<any>" and "<approx:0.5:0.000001>" are matcher markers, not protocol values: every example in this section is a fixture file that the kernel test suite replays against the real handler (kernel/tests/test_docs_fixtures.py), and markers absorb nondeterminism (sampled counts, timings). The shapes and orderings you read here are machine-checked on every CI run.

Request typeTerminal responseReference
parsesnapshot (an error follows it on failure)Execution messages
executeresultExecution messages
run_pythonpython_resultExecution messages
environmentenvironmentExecution messages
hardware_connecthardware_connectedHardware messages
hardware_set_credentialshardware_connectedHardware messages
hardware_clear_credentialshardware_connectedHardware messages
hardware_connected_providershardware_connected_providersHardware messages
hardware_list_jobshardware_jobsHardware messages
hardware_list_backendshardware_backendsHardware messages
hardware_submithardware_job_submittedHardware messages
hardware_statushardware_job_updateHardware messages
hardware_resultshardware_resultHardware messages
hardware_cancelhardware_job_cancelledHardware messages

Any other type (and any frame that is not valid JSON) produces a bare error message — see errors. Payload schemas are in schemas. The TypeScript mirror of the whole protocol — the types the frontend itself compiles against — lives at src/types/quantum.ts.