Skip to content

Kernel protocol

Each tab’s Rust-managed Python subprocess communicates over stdin and stdout. Every message is one JSON object followed by a newline.

Property Value
Encoding UTF-8 JSON text
Framing One JSON object per line
Request direction Rust writes to child stdin
Response direction Python writes to child stdout
Concurrency One I/O loop per tab, one request processed at a time
Queue Tokio MPSC channel with capacity 64 per tab
Correlation Request and response share id

The process’s original stdin is reserved for protocol messages. During cell execution, user code receives an empty text stream, so interactive input is not supported and cannot consume a later protocol request.

execute request
{
"id": "cell-uuid",
"type": "execute",
"code": "x = 6\nx * 7"
}

Rust models code as optional and omits it when absent. The server defaults missing code to an empty string.

type code Server behavior Used by released Rust path?
execute Python source Parse and run source in the persistent namespace. Yes
restart Ignored Replace the globals dictionary with builtins only. No; Rust restarts by killing and spawning the process.
ping Ignored Return pong in stdout. Used for the five-second kernel startup handshake.
Any other string Ignored Return an error naming the unknown request type. No
result response
{
"id": "cell-uuid",
"type": "result",
"stdout": "42\n",
"stderr": "",
"error": null,
"output_type": "text"
}
Field Type Meaning
id String Echoes the request ID. For malformed JSON it is an empty string.
type String Always result in the current server.
stdout String Captured standard output plus printed final-expression representation.
stderr String Captured standard error.
error String or null Traceback, interrupt string, protocol error, or null.
output_type String Always text in the current server.

For an execute request:

  1. Parse the source with ast.parse(..., mode="exec").
  2. Remove a final bare expression from the module body, when present.
  3. Execute preceding statements in the process-global dictionary.
  4. Evaluate the final expression separately.
  5. Store a non-None result as _ and print its repr into captured stdout.
  6. Catch KeyboardInterrupt and other BaseException values.
  7. Restore the process’s original stdin, stdout, and stderr objects.
  8. Serialize one result line and flush it.

The globals dictionary begins as:

{"__builtins__": __builtins__, "__name__": "__main__"}
{
"id": "",
"type": "result",
"stdout": "",
"stderr": "",
"error": "Invalid JSON: <decoder details>",
"output_type": "text"
}
{
"id": "request-id",
"type": "result",
"stdout": "",
"stderr": "",
"error": "Unknown request type: example",
"output_type": "text"
}

error contains traceback.format_exc() output. Stdout and stderr emitted before the exception remain available in their fields.

The Python server reports:

KeyboardInterrupt: Execution interrupted

The Rust backend also schedules SIGKILL two seconds after sending SIGINT, so the process may terminate after this response.

The command can reject with messages including:

  • No kernel for tab N
  • Kernel is not running. Restart to continue.
  • Kernel has no request channel
  • Failed to queue execution request
  • Response channel closed
  • stdin write failed: ...
  • stdin flush failed: ...
  • Invalid response JSON: ...
  • Kernel process terminated unexpectedly
  • stdout read error: ...

These errors are converted into a frontend error output when they occur during cell execution.