Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Multi-Process & IPC

Kael supports an native desktop multi-process architecture: the UI runs in the main process while heavy or untrusted work runs in supervised child processes that communicate over typed IPC. Transport is platform-native — Unix domain sockets on macOS/Linux, named pipes on Windows — and the framework handles framing, request/response correlation, progress streaming, and crash reporting for you.

Process model

Every process has a class describing its role:

#![allow(unused)]
fn main() {
use kael::ProcessClass;

ProcessClass::Ui;        // the main UI process
ProcessClass::Worker;    // background compute
ProcessClass::Media;     // media decode/playback
ProcessClass::Extension; // sandboxed plugins (see Plugins & Extensions)
}

A child process is described by a ProcessInfo. For generated host code, prefer ProcessInfoBuilder so empty names, missing executables, invalid environment keys, NUL-containing args, and missing required paths fail before the supervisor starts:

#![allow(unused)]
fn main() {
use kael::{ProcessId, ProcessInfoBuilder};

let info = ProcessInfoBuilder::worker(ProcessId(0), "thumbnailer")
    .executable("/path/to/worker-binary")
    .require_existing_executable()
    .arg("--quiet")
    .env("RUST_LOG", "warn")
    .build_checked()?;
}

Builders exist for each role: ProcessInfoBuilder::worker, ProcessInfoBuilder::media, and ProcessInfoBuilder::extension. The raw ProcessInfo::worker, ProcessInfo::media, and ProcessInfo::extension constructors remain available when an app owns lower-level validation.

Spawning a worker (host side)

WorkerHost owns the socket directory and supervises spawned children. request sends a typed payload and blocks for the response; fire_and_forget sends without waiting; health_check pings the child.

#![allow(unused)]
fn main() {
use kael::{
    ProcessClass, ProcessId, ProcessInfoBuilder, ProcessSpawnOptionsBuilder,
    WorkerHost,
};

let mut host = WorkerHost::with_temp_dir();
let info = ProcessInfoBuilder::worker(ProcessId(0), "thumbnailer")
    .executable(worker_binary_path)
    .require_existing_executable()
    .build_checked()?;
let spawn_options = ProcessSpawnOptionsBuilder::new()
    .restart_on_failure(3, std::time::Duration::from_secs(1))
    .heartbeat_interval(std::time::Duration::from_secs(5))
    .missed_heartbeats_before_unhealthy(3)
    .build_checked()?;

let worker = host.spawn_worker_with_options(ProcessClass::Worker, info, spawn_options)?;

worker.health_check()?; // round-trip ping

let response: serde_json::Value = worker.request(serde_json::json!({
    "op": "echo",
    "message": "hello from host",
}))?;
assert_eq!(response["message"], "hello from host");
}

The worker child

The child binary connects back to the host with WorkerClient::connect_from_env (it reads the KAEL_WORKER_SOCKET / KAEL_WORKER_PIPE environment variable the host sets) and serves requests with run. The handler receives a WorkerRequest and a progress callback for streaming intermediate updates, and returns a WorkerResponse or WorkerError.

use anyhow::Result;
use kael::{WorkerClient, WorkerProgress, WorkerRequest, WorkerResponse};

fn main() -> Result<()> {
    let client = WorkerClient::connect_from_env()?;
    client.run(|request, progress| match request {
        WorkerRequest::Ping => Ok(WorkerResponse::Pong),
        WorkerRequest::Execute { payload } => {
            progress(WorkerProgress::Update(serde_json::json!({ "step": 1 })));
            // ... do work ...
            Ok(WorkerResponse::Result(payload))
        }
    })
}

The message types:

TypeVariants
WorkerRequestPing, Execute { payload: serde_json::Value }
WorkerResponsePong, Result(serde_json::Value)
WorkerProgressUpdate(serde_json::Value)
WorkerErrorExecution(String), Cancelled

Supervision & crash handling

Register an event callback to observe lifecycle events. A child that crashes is reported as a SupervisorEvent::Exited rather than taking down the host:

#![allow(unused)]
fn main() {
use kael::SupervisorEvent;

host.on_event(|event| match event {
    SupervisorEvent::Exited { id, .. } => eprintln!("worker {id:?} exited"),
    _ => {}
});
}

Each WorkerHandle exposes id() to correlate it with supervisor events.

ProcessSpawnOptionsBuilder validates restart and heartbeat policy: restart counts must be non-zero when using restart_on_failure, backoff must be greater than zero, heartbeat intervals must be greater than zero, and the missed heartbeat threshold must be non-zero.

Extension processes

Extension children use the same transport but a richer RPC envelope (handshake, contribution discovery, command dispatch). They are managed by ExtensionHostRuntime rather than WorkerHost — see Plugins & Extensions.