Skip to main content
Ganimede executes code through a Jupyter kernel, giving you the full power of IPython with real-time output streaming. Every cell runs in a shared kernel session, so variables and imports persist across executions.

Jupyter kernel integration

Ganimede uses jupyter_client to manage a kernel process:
from jupyter_client.manager import AsyncKernelManager

class Kernel:
    def __init__(self, comms: Comms, ydoc: Y.YDoc) -> None:
        self.kernel_manager = AsyncKernelManager()
        self.kernel_client = None
        self.run_queue = asyncio.Queue()
The kernel starts automatically when you execute your first cell.

Execution flow

When you run a cell, Ganimede:
1

Queues the cell

The cell enters the run queue and its state changes to “queued”
2

Starts kernel if needed

If no kernel is running, Ganimede starts one automatically
3

Sends code to kernel

The cell’s code is submitted to the Jupyter kernel client
4

Streams outputs

Outputs appear in real-time as the kernel generates them
5

Updates execution count

The cell receives an execution count when complete

Cell states

Cells transition through these states during execution:
idle
state
Cell is not running and has no queued execution
queued
state
Cell is waiting in the execution queue
running
state
Cell code is currently executing in the kernel
The kernel itself has a busy state that coordinates with cell execution:
@property
def busy(self):
    return self._busy

@busy.setter
def busy(self, value):
    self._busy = value
    with self.ydoc.begin_transaction() as t:
        self.ykernel.set(t, "busy", value)

Output types

Ganimede handles all standard Jupyter output types:

Text output

Plain text output from print() statements:
output = {
    "output_type": "stream",
    "name": "stdout",
    "text": ["Hello, world!\n"]
}

Rich display data

Images, plots, HTML, and other rich media:
output = {
    "output_type": "display_data",
    "data": {
        "text/plain": ["<Figure size 640x480>"],
        "image/png": ["iVBORw0KGgoAAAANS..."]
    },
    "metadata": {}
}

Errors and tracebacks

Exceptions with full stack traces:
output = {
    "output_type": "error",
    "ename": "NameError",
    "evalue": "name 'x' is not defined",
    "traceback": [
        "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
        "\u001b[0;31mNameError\u001b[0m: name 'x' is not defined"
    ]
}
When an error occurs, Ganimede automatically clears the execution queue to prevent cascade failures.

Execution queue

Cells execute sequentially through a queue system:
async def run_queue_loop(self):
    while True:
        cell_id = await self.run_queue.get()
        self._change_cell_state(cell_id, "running")
        self._clear_outputs(cell_id)
        await self.run(cell_id)
        with self.ydoc.begin_transaction() as t:
            self.ydoc.get_array("run_queue").delete(t, 0)
This ensures:
  • Cells run in the order they’re queued
  • Only one cell executes at a time
  • The kernel state stays consistent
You can queue multiple cells by running them in sequence. They’ll execute one after another automatically.

Interrupting execution

If a cell is taking too long or you need to stop execution:
async def interrupt(self):
    await self.kernel_manager.interrupt_kernel()
Interrupting clears the execution queue and sends a keyboard interrupt to the kernel.

Restarting the kernel

Reset the kernel state without losing your canvas layout:
async def restart_kernel(self):
    if self.kernel_client is None:
        return
    
    try:
        await self.kernel_manager.restart_kernel()
    except Exception as e:
        log.error(e)
        self.kernel_client = None
Restarting the kernel clears all variables, imports, and defined functions. You’ll need to re-run cells to rebuild your environment.

Real-time output streaming

Outputs stream to your cell as the kernel generates them:
async def proc_io_msgs() -> None:
    self.busy = True
    while self.busy:
        try:
            msg = await self.kernel_client.get_iopub_msg(timeout=1)
            
            if msg["msg_type"] == "status":
                if msg["content"]["execution_state"] == "idle":
                    self.busy = False
            else:
                msg = self._msg_to_output(msg)
            
            msg_queue.put_nowait(msg)
        except queue.Empty:
            pass
This gives you immediate feedback on long-running operations like training models or processing large datasets.

Next steps

Cell management

Learn about cell types and properties

Configuration

Configure output display settings

Build docs developers (and LLMs) love