Message protocol between the browser process and the kernel process. Applies equally to pipe and WebSocket transports. See kernel-architecture for the overall design.
All messages are newline-delimited JSON (one JSON object per line, terminated by \n). No length prefix is needed — JSON objects cannot contain unescaped newlines, so splitting on \n is unambiguous.
For WebSocket: each WebSocket text frame carries exactly one JSON message (no newline needed, but including one is harmless).
For pipe: the kernel reads from stdin line-by-line; the browser reads from the kernel's stdout line-by-line.
Every message is a JSON object. The kind field identifies the message class:
// Request (browser → kernel)
{"kind": "request", "id": "<uuid>", "method": "<name>", "params": {...}}
// Response (kernel → browser, to a request)
{"kind": "response", "id": "<same uuid>", "result": {...}}
{"kind": "response", "id": "<same uuid>", "error": {"code": -32000, "message": "..."}}
// Event (kernel → browser, unsolicited)
{"kind": "event", "event": "<name>", "data": {...}}
Request IDs are UUIDs generated by the browser. The kernel echoes the same ID in the response. Events have no ID.
Handshake. Returns kernel capabilities.
// Request
{"kind": "request", "id": "1", "method": "kernel.info", "params": {}}
// Response
{"kind": "response", "id": "1", "result": {"version": 1, "language": "python", "python_version": "3.12"}}
Optional authentication (WebSocket mode only). Send immediately after the ready event.
{"kind": "request", "id": "2", "method": "kernel.auth", "params": {"token": "<secret>"}}
// Response
{"kind": "response", "id": "2", "result": {"ok": true}}
Execute a cell. Returns immediately with {"status": "started"} — output arrives as events. Only one cell may execute at a time; sends an error if busy.
{"kind": "request", "id": "3", "method": "execute.cell",
"params": {"cell_id": "stage4", "source": "print(1+1)",
"inputs": {"var_name": "value"}}}
// Response (immediate)
{"kind": "response", "id": "3", "result": {"status": "started"}}
Interrupt the currently running cell. Returns after raising KeyboardInterrupt into the executor thread.
{"kind": "request", "id": "4", "method": "execute.interrupt", "params": {}}
{"kind": "response", "id": "4", "result": {"ok": true}}
Clear the namespace. Blocks until any running cell finishes or is interrupted.
{"kind": "request", "id": "5", "method": "kernel.reset", "params": {}}
{"kind": "response", "id": "5", "result": {"ok": true}}
Reply to an input_request event. Must be sent while the cell is paused waiting for input.
{"kind": "request", "id": "6", "method": "execute.input_reply",
"params": {"value": "user typed this"}}
Sent once on connection. First message from the kernel.
{"kind": "event", "event": "kernel.ready", "data": {"version": 1}}
A chunk of stdout or stderr from the running cell. May be sent many times per cell execution.
{"kind": "event", "event": "stream",
"data": {"cell_id": "stage4", "name": "stdout", "text": "Retry 1...\n"}}
A show() or plot() item produced during cell execution. The browser appends it to the cell's output panel immediately.
{"kind": "event", "event": "display_data",
"data": {"cell_id": "stage4", "item": {"kind": "plot", "title": "...", "series": [...]}}}
The item field has the same structure as the current show_items list: {"kind": "plot", ...}, {"kind": "html", "content": "..."}, a JSONML list, or a string.
The cell called input(prompt). The kernel pauses until execute.input_reply is received.
{"kind": "event", "event": "input_request",
"data": {"cell_id": "stage4", "prompt": "Enter value: "}}
Cell execution finished (success or error). The browser marks the cell Done/Error and re-enables the Run button.
// Success
{"kind": "event", "event": "execute.done",
"data": {"cell_id": "stage4", "status": "ok"}}
// Error
{"kind": "event", "event": "execute.done",
"data": {"cell_id": "stage4", "status": "error",
"error": "Traceback (most recent call last):\n ..."}}
The kernel is always in one of three states:
execute.cell or kernel.reset.execute.interrupt and execute.input_reply. Rejects execute.cell with error -32001 KERNEL_BUSY.input(). Accepts only execute.input_reply.-32700 — parse error (bad JSON)-32600 — invalid request (missing fields)-32601 — method not found-32001 — KERNEL_BUSY (execute.cell sent while running)-32002 — AUTH_REQUIRED (WebSocket mode, no token sent)-32003 — AUTH_FAILED (wrong token)The existing control-api JSON-RPC socket on the browser process is unchanged in structure. The only behavioural change is that sheet.cell.run and sheet.run_all become non-blocking: they return {"status": "started"} immediately and fire-and-forget to the kernel. A new method sheet.cell.await blocks (with timeout) until execute.done is received for a given cell.
For WebSocket mode, a shared secret token is passed as a command-line argument to the kernel process and configured in the browser. The browser sends kernel.auth immediately after the ready event. Unauthenticated requests are rejected with -32002. Token rotation is not in scope for v1.
Browser Kernel
| |
|<-- {event: kernel.ready} ----|
| |
|--> {method: execute.cell} -->|
|<-- {result: started} --------|
| |
|<-- {event: stream, "Retry 1"}|
|<-- {event: stream, "Retry 2"}|
|<-- {event: display_data, df}--|
|<-- {event: execute.done, ok}-|
| |