Kallichore is an experimental, cross-platform, headless supervisor for Jupyter kernels. It exposes an API that can be used to manage kernel sessions and provides a WebSocket-compatible interface for sending and receiving Jupyter messages to each kernel.
graph LR
frontend -- http --> kallichore
kallichore -- websocket --> frontend
frontend -- websocket --> kallichore
kallichore --> ki1[kernel interface 1]
kallichore --> ki2[kernel interface 2]
ki1 -- zeromq --> k1[kernel 1]
ki2 -- zeromq --> k2[kernel 2]
    Kallichore's primary residence is in the Positron IDE, where it provides durable Jupyter kernel sessions, especially in Posit Workbench. It takes care of the low-level minutiae of Jupyter kernel management (ZeroMQ, process interop, lifecycle supervision, etc.), leaving Positron to handle the user interface and other aspects of the development environment.
It is a companion to Posit's Amalthea and Ark projects. Like Amalthea, it is a Rust-based, Jupyter-adjacent project named after one of Jupiter's moons. Kallichore is also one of the Muses.
Here's how Kallichore functions in the larger Positron ecosystem for e.g. an R session:
graph TD
p[Positron] -- Positron API --> r[R Language Pack]
r -- Positron API --> p
r --> kp[Kallichore Plugin]
kp --> r
kp -- Kallichore API --> k[Kallichore]
k -- Kallichore API --> kp
k -- Jupyter over ZeroMQ --> rk[R Kernel]
rk -- Jupyter over ZeroMQ --> k
kp -- Jupyter over WebSocket --> k
rk -- LSP over TCP --> r
    To run Kallichore, first get a copy of the server from the GitHub Releases page on this repository (or build your own; see below for instructions). This release contains a pre-built binary named kcserver for your platform.
For most use cases, it's recommended to run kcserver with the --connection-file argument to have Kallichore generate a connection file for your client. On a typical Unix-like system, you might use a temporary file for connection information and tell Kallichore to use domain sockets as the transport (TCP and named pipes are also supported; see Connection Methods for details):
./kcserver --transport socket --connection-file /tmp/kc-connection-file.jsonThe server will start up and listen for incoming connections on the specified transport and generate a bearer auth token (see below for Security Considerations). Your client can read connection details from the connection file and connect to the server using the appropriate transport. The connection file looks like this:
{
  "socket_path": "/tmp/path-to/socket/kc-5259.sock",
  "transport": "socket",
  "server_path": "/path/to/kcserver",
  "server_pid": 5259,
  "bearer_token": "4dea37b8a3b3e09f",
  "log_path": null
}See Kallichore's help page for a full list of command-line options:
./kcserver --helpOnce the server is alive, you're ready to run kernels. Typically, you'll use the following API methods:
- Create a new session by executing a PUTon/sessionswith the session information. The new session ID is returned (let's say it'ssession1234)
- Begin listening to kernel events and output from the session by getting a WebSocket URL or path from the /sessions/session1234/channelsmethod and connecting to it in your client.
- Ask the session to start by executing a POSTon/sessions/session1234/start. During startup, state changes and output will be sent to the WebSocket connection.
- Once the session is successfully started, use your WebSocket connection to send and receive messages to/from the kernel.
By default, the server will run indefinitely until it is manually stopped. You can stop it by sending a SIGINT signal to the process, or by invoking the /shutdown API. Either one will attempt to gracefully shut down all active kernels (sending each a Jupyter message requesting shutdown) and then exit the server itself.
You can also cause Kallichore to exit after a period of inactivity by setting the --idle-shutdown-hours parameter when starting the server. This option will keep the server running as long as any kernel is active; the shutdown timer starts running only when all kernels are idle and none are connected to a front end.
Important
Kallichore's API accepts arbitrary paths to executable files, and will run them as subprocesses. Jupyter kernels can also be made to execute arbitrary commands, which can be used to execute malicious code. This presents a significant security risk if security measures are not in place to prevent unauthorized execution.
The following steps are recommended to ensure the security of your Kallichore instance:
- Use a low-privilege user account: Run Kallichore under a non-privileged user account, not as root. Because Jupyter kernels are basically code execution environments, presume that giving a user access to Kallichore's API is equivalent to giving them shell access.
- Use IPC, not TCP: If running Kallichore locally, use domain sockets (Unix/macOS) or named pipes (Windows) as the transport. These transports are more secure than TCP, as they can be locked down with file system permissions and do not expose the server to the network.
- Secure the authentication token: Kallichore uses a simple bearer token for authentication. If generating the token yourself, pass it as a file (so that it is not exposed in the process list) and delete it once the server has started. If allowing the server to generate the token, delete the connection file once it has been read.
To compile the Kallichore project, just run this in the root directory of the repository:
cargo buildRun the server from the target directory. You may want to set RUST_LOG to trace to get detailed debugging output.
export RUST_LOG=trace
./target/debug/kcserver .
 +-- kallichore.json -- OpenAPI description of the Kallichore API
 |
 +-- crates
      |
      +-- kallichore_api -- Code-generated Rust client/server
      |    |
      |    +-- examples/client -- Example client
      |    |
      |    +-- examples/server -- Example server
      |
      +-- kcshared -- Shared code for the server and client
      |
      +-- kcserver -- Main Kallichore server, using the kallichore_api crate
      |
      +-- kcclient -- Command-line client (for testing), using the kallichore_api crate
To make changes to the API, edit the kallichore.json file and then run the scripts/regen-api.sh script to regenerate the Rust client and server crates.
./scripts/regen-api.shNote
The regenerator script requires the OpenAPI Generator.
Important
The code generator produces code with HTTPS support. We remove the TLS/HTTPS support from the generated code in order to avoid creating a binary that links to OpenSSL (which would introduce a lot of system compatibility issues).
Kallichore supports multiple transport mechanisms for client connections. The server can be configured to use TCP, Unix domain sockets, or Windows named pipes.
TCP is the default transport method that works across all platforms. In TCP mode, the supervisor listens on a local TCP port and accepts RPCs over HTTP. This is suitable for remote connections and works on all operating systems.
Connections to individual kernels are made with WebSockets, which are established over the HTTP connection.
# Use default port (random)
./kcserver
# Use specific port
./kcserver --port 8080
# Create a connection file with TCP
./kcserver --connection-file connection.json --transport tcpWhen using --connection-file, the server writes connection details to the specified file:
{
  "port": 54321,
  "base_path": "http://127.0.0.1:54321",
  "transport": "tcp",
  "server_path": "/path/to/kcserver",
  "server_pid": 12345,
  "bearer_token": "your-auth-token",
  "log_path": "/path/to/logfile.log"
}Unix domain sockets provide high-performance IPC for local connections on Unix-like systems. They offer better security than TCP, since they use filesystem permissions. However, they are not available on Windows and are not suitable for remote connections.
When using domain sockets, the server listens on a specific socket file instead of a TCP port. When connecting to individual kernels, each kernel gets a dedicated Unix socket for communication.
# Use connection file with socket (default on Unix when using --connection-file)
./kcserver --connection-file connection.json
# Explicitly specify socket transport
./kcserver --connection-file connection.json --transport socket
# Use specific socket path
./kcserver --unix-socket /tmp/kallichore.sock{
  "socket_path": "/tmp/kallichore-12345.sock",
  "transport": "socket",
  "server_path": "/path/to/kcserver",
  "server_pid": 12345,
  "bearer_token": "your-auth-token",
  "log_path": "/path/to/logfile.log"
}Named pipes are the Windows equivalent of Unix domain sockets, providing efficient local IPC on Windows systems. They are not available on Unix-like systems and are only used for local connections.
When using named pipes, the server listens on a named pipe path instead of a TCP port or Unix socket. Each kernel session also gets its own named pipe for communication.
# Use connection file with named pipe (default on Windows when using --connection-file)
kcserver.exe --connection-file connection.json
# Explicitly specify named pipe transport
kcserver.exe --connection-file connection.json --transport named-pipe{
  "named_pipe": "\\\\.\\pipe\\kallichore-12345",
  "transport": "named-pipe",
  "server_path": "C:\\path\\to\\kcserver.exe",
  "server_pid": 12345,
  "bearer_token": "your-auth-token",
  "log_path": "C:\\path\\to\\logfile.log"
}The server automatically selects the appropriate transport based on:
- Explicit --transportflag: Overrides all other settings
- Connection file mode: Defaults to socketon Unix,named-pipeon Windows,tcpelsewhere
- Direct mode: Uses tcpwhen no connection file is specified
- TCP: Binds to 127.0.0.1(localhost only) by default for security
- Unix Sockets: Use filesystem permissions for access control
- Named Pipes: Use Windows security descriptors for access control
- Authentication: All transports support bearer token authentication when enabled
Kallichore is versioned similar to ARK. No release builds are produced by default. When you want to release a new version of Kallichore (for integrating into Positron, etc.), bump the version of the kcserver crate. This will trigger a release build.
Jupyter Kernel Gateway: a headless Jupyter server that uses WebSockets to deliver Jupyter messages.
