Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Programming Languages

capOS currently supports native Rust programs that are written for the capOS userspace runtime. Other languages are design tracks, not implemented platform support. The main rule is simple: a language runtime may expose familiar APIs, but authority still comes from the process CapSet and typed capability calls.

Current Support

Language or runtimeStatusPath
Rust, capOS-nativeImplemented baseline#![no_std], alloc, capos-rt, static ELF, x86_64-unknown-capos. Phase D best-effort fair scheduling closed at commit 77caafc0 (2026-05-10 19:39 UTC): per-thread weighted vruntime, per-CPU WFQ run queues, bounded steal/migration, and SchedulingPolicyCap weight/latency-class authority.
Rust stdNot implementedFuture Rust standard-library or adapter work over capabilities
CPhase 0 in tree (libcapos C-substrate v0 + libcapos-posix v0)libcapos.a exposes capos-rt syscalls, ring CALL, CapSet lookup, a heap shim, typed Console.writeLine, Timer.now, EntropySource.fill, VirtualMemory wrappers, and native ProcessSpawner.createPipe / Pipe wrappers through extern "C"; make run-c-hello proves baseline C wrappers and make run-c-pipe proves a C binary can create a Pipe, write/read a marker, close the writer, and observe EOF without using the POSIX adapter. libcapos_posix.a adds the POSIX adapter v0 surface above libcapos: per-process static fd table (32 fds), TLS errno via __errno_location(), historical UDP socket/sendto/recvfrom/close wrappers over the retired qemu-only kernel UdpSocket cap, clock_gettime(CLOCK_MONOTONIC, ...), gettimeofday(&tv, NULL), time, nanosleep, and sleep over the kernel Timer cap, fail-closed signal stubs, pipe/read/write/dup/dup2 over the kernel Pipe cap, and fork/execve/waitpid/_exit/posix_inherit_stdio plus a direct posix_spawn successor via the recording-shim Move-grant path through ProcessSpawner.createPipe / ProcessSpawner.spawn. See the POSIX adapter row for shipped smokes; the old DNS smoke is retired until resolver networking is rebuilt on the userspace stack.
C++Future experimentDepends on C startup, ABI choices, allocator, exceptions/RTTI policy, and a useful freestanding subset
GoFuture designCustom GOOS=capos per Go Runtime proposal; a separate Phase W.8 path (docs/proposals/wasi-host-adapter-proposal.md Task 9) targets a TinyGo / upstream Go GOOS=wasip1 CUE evaluator binary that runs inside the WASI host adapter against a future ScriptPackage cap
PythonFuture designNative CPython or MicroPython through a POSIX-style adapter; WASI/Emscripten for sandboxed or compute-only use
LuaPhase 1 in tree (L.3 deterministic memory release)demos/lua-smoke/ runs a hand-written Lua-subset interpreter that exercises three capability-aware host bindings: console:write_line, timer:now, and L.3 memory:{alloc,write,read,size,release} over capos-rt::VirtualMemoryClient (kernel-mapped address never crosses to Lua; every byte access is bounds-checked host-side; release unmaps the exact rounded region and marks the userdata dead). PUC Lua dialect compatibility is deferred to the future C/libcapos port. See Lua Scripting proposal.
JavaScript / TypeScriptFuture designQuickJS-style native runner or WASI-hosted engine; not a browser JS shell
WASI / WebAssemblyPhase W.5 landed 2026-05-17 05:42 UTC (Phase W.4 closed 2026-05-07 20:09 UTC; Phase W.3 closed 2026-05-07 18:25 UTC; Phase W.2 closed 2026-05-07 10:53 UTC)Host imports backed by capabilities; useful for sandboxed code and portable tools. W.1 vendored upstream wasmi (v1.0.9) at vendor/wasmi-no_std/wasmi-1.0.9/ and shipped the capos-wasm/ standalone crate that exposes a Runtime value (wasmi Engine + Store<HostState>). W.2 sub-slice 1 added the wasm-host userspace binary in capos-wasm/src/bin/wasm-host.rs, the system-wasm-host.cue focused-proof manifest, and make run-wasm-host, which still asserts the empty-instantiation regression. W.2 sub-slice 2 grew the same binary with the Preview 1 import resolver in capos-wasm/src/wasi/preview1.rs: 46 wasi_snapshot_preview1 imports land on the wasmi linker; clock_time_get(CLOCKID_MONOTONIC) is backed by the manifest-granted Timer cap; proc_exit exits via capos_rt::syscall::exit; fd_write(1, …) / fd_write(2, …) route through the manifest-granted Console cap with a fixed 4 KiB iov-total scratch ceiling and a 1 KiB per-call chunk that matches the kernel Console cap’s MAX_SERIAL_CAP_WRITE_BYTES; everything else (including random_get, which Phase W.4 promotes against EntropySource) returns ERRNO_NOSYS. A 114-byte hand-encoded probe module imports random_get, calls it once, stores the returned errno in an exported global, and the host refuses to print the [wasm-host] preview1 imports linked: clock_time_get, fd_write, proc_exit, args/environ empty; nosys=52 proof line unless that errno equals ERRNO_NOSYS. W.2 sub-slice 3 added demos/wasi-hello-rust/ (a one-liner println! Rust crate built for the upstream wasm32-wasip1 target), system-wasi-hello-rust.cue (now grants console, timer, and the optional boot (BootPackage) cap), tools/qemu-wasi-hello-rust-smoke.sh, and make run-wasi-hello-rust. The wasm-host binary keeps running the sub-slice 1 + 2 regression first; when the manifest grants boot, it also reads the manifest blob through BootPackage, decodes binaries[] via raw capnp readers (new capos_wasm::payload module), instantiates the wasi-payload wasm, explicitly invokes the _start export (wasmi’s instantiate_and_start runs the WebAssembly start section, NOT WASI’s _start), and lets the payload’s println! reach the kernel Console cap through Preview 1 fd_write. capos-rt grew narrow re-exports (capos_capnp and default_reader_options) so capos-wasm keeps a single direct path-dep on capos-rt and the vendored wasmi tree. The slice also kept the W.2 sub-slice 1 userspace-image budget bump (USER_STACK_BASE 0x100_0000) for wasmi’s ~3 MiB BSS. W.2 sub-slice 4 closed Phase W.2 by adding demos/wasi-hello-c/ (a single printf("Hello, wasi from capOS C\n") C main() built directly with system clang-18 against the Ubuntu wasi-libc + libclang-rt-18-dev-wasm32 apt packages: clang --target=wasm32-wasi --sysroot=/usr -O2 -Wall -Wextra produces a ~46 KiB wasm32-wasi module), system-wasi-hello-c.cue, tools/qemu-wasi-hello-c-smoke.sh, and make wasi-hello-c-build / make run-wasi-hello-c. C runs on capOS without any libcapos/POSIX work in tree because the wasm-host payload-load path landed in sub-slice 3 carries the C .wasm payload through the same wasm-host binary unchanged. Phase W.3 backed args_get / args_sizes_get with the manifest-supplied initConfig.init.wasiArgs text grant: the wasm-host walks the field through raw capnp readers in capos_wasm::payload::read_wasi_args, validates against WASI_ARGS_MAX_COUNT = 32 / WASI_ARGS_MAX_ARG_BYTES = 4096 / WASI_ARGS_MAX_TOTAL_BYTES = 8192 (rejecting interior NUL bytes), packs the bytes into a per-instance HostState argv buffer, and reflects them through Preview 1 to the wasm guest. A 2026-05-13 bounded environment grant mirrors that path for initConfig.init.wasiEnv: the wasm-host walks capos_wasm::payload::read_wasi_env, validates against WASI_ENV_MAX_COUNT = 32 / WASI_ENV_MAX_ENTRY_BYTES = 4096 / WASI_ENV_MAX_TOTAL_BYTES = 8192 (rejecting interior NUL bytes), packs KEY=value entries into a per-instance environment buffer, and reflects them through Preview 1 environ_get / environ_sizes_get; absent grants remain empty. The W.2 sub-slice 2 “args/environ empty” proof line stays byte-identical because the regression module passes empty argv and no environment. The new demos/wasi-cli-args/ Rust smoke (println! of argv[1]), system-wasi-cli-args.cue, tools/qemu-wasi-cli-args-smoke.sh, and make wasi-cli-args-build / make run-wasi-cli-args close the per-instance argv plumbing; demos/wasi-env/, system-wasi-env.cue, tools/qemu-wasi-env-smoke.sh, and make wasi-env-build / make run-wasi-env prove one granted environment value reaches a Rust wasm32-wasip1 payload. Schema/schema/capos.capnp is unchanged because initConfig is already a CueValue and unknown sub-fields under initConfig.init are ignored by the existing manifest decoder. Phase W.4 wires Preview 1 random_get through the kernel EntropySource cap. The wasm-host (capos-wasm/src/bin/wasm-host.rs) looks up an optional per-instance EntropySource cap from the CapSet under the well-known name random and installs the typed EntropySourceClient on HostState AFTER the W.2 sub-slice 2 probe regression has run, keeping the closed-fail nosys=52 proof line byte-identical. Preview 1 random_get (capos-wasm/src/wasi/preview1.rs) drains arbitrary wasm-supplied byte ranges through EntropySourceClient::fill_wait, chunked at the kernel cap’s MAX_ENTROPY_FILL_BYTES = 64 ceiling and capped per Preview 1 invocation at RANDOM_GET_MAX_BYTES = 65_536. RDRAND-unavailable / truncated kernel responses surface as ERRNO_IO; oversized requests as ERRNO_INVAL; out-of-bounds wasm pointer writes as ERRNO_FAULT. Manifests without the grant keep returning ERRNO_NOSYS from the closed-fail refusal branch which never enters the kernel, so an instance without an EntropySource grant cannot leak entropy. Wall-clock support stays deferred until capOS has a typed WallClock/RealTimeClock cap; clock_time_get(CLOCKID_REALTIME) keeps the W.2 sentinel ERRNO_NOSYS. The new demos/wasi-random/ Rust smoke (raw Preview 1 random_get binding reading N=64 bytes), system-wasi-random.cue (granted), system-wasi-random-ungranted.cue (ungranted), tools/qemu-wasi-random-smoke.sh, tools/qemu-wasi-random-ungranted-smoke.sh, make wasi-random-build, make run-wasi-random, and make run-wasi-random-ungranted close Phase W.4. A 2026-05-13 compatibility-import smoke adds demos/wasi-stdio-fd/, system-wasi-stdio-fd.cue, tools/qemu-wasi-stdio-fd-smoke.sh, and make run-wasi-stdio-fd; it directly imports clock_res_get(MONOTONIC), sched_yield, fd_fdstat_get(1/2), and fd_seek(1/2) and requires every promoted import to return a non-ERRNO_NOSYS result without granting filesystem, socket, or stdin authority. A 2026-05-13 harness-hardening smoke adds demos/wasi-preview1-refusals/, system-wasi-preview1-refusals.cue, tools/qemu-wasi-preview1-refusals-smoke.sh, and make run-wasi-preview1-refusals; it directly imports path_open, fd_prestat_get, fd_read, sock_send, and sock_recv and asserts the documented fail-closed errno when no Namespace/File/Store/socket authority exists. Phase W.5 (2026-05-17 05:42 UTC) wires the Preview 1 preopened-directory filesystem against the kernel Directory / File cap interface: the wasm-host looks up an optional per-instance Directory cap from the CapSet under the well-known name root and installs it as a single Preview 1 preopen at fd 3 named /preopen-0. capos-wasm/src/wasi/fs.rs implements path_open, fd_read, fd_write, fd_seek, fd_close, fd_filestat_get, fd_prestat_get, and fd_prestat_dir_name over DirectoryClient / FileClient; the resolver mirrors POSIX P1.4 Slice 4’s libcapos-posix/src/path.rs – intermediate segments walk Directory.sub, the leaf mints either an existing or freshly created File via `Directory.open(flags=CREATE
POSIX-shaped softwarePartial implementationCompatibility adapter over explicit file, directory, socket, stdio, timer, process, and namespace caps. See POSIX Adapter proposal and plan. P1.1, P1.2, and P1.3 are closed; the former direct DNS smoke is retired with the qemu-only kernel UdpSocket owner, while make run-posix-pipe-smoke, make run-posix-spawn-smoke, and make run-posix-stdio-smoke cover pipe/fork-for-exec, direct posix_spawn, and Console-backed stdio surfaces. P1.4 file/directory fd work closed at commit f97d9833 (2026-05-23 06:23 UTC): make run-posix-file proves open(), write(), lseek(), read(), opendir(), readdir(), and closedir() through a live C process over the RAM-backed root Directory cap. Closed P1.4 successors now include printf/string (make run-posix-printf), identity stubs (make run-posix-identity), and signal/time stubs (make run-posix-signal-time). Remaining P1.4 work is dash vendoring/patching, the multi-translation-unit C build, and make run-posix-shell-smoke; long-form decomposition lives in POSIX Adapter Dash Port.

Native Rust Today

The implemented path is Rust without the standard library. Programs use core and may use alloc types such as Vec, String, Box, and BTreeMap because capos-rt installs a userspace allocator. They do not get std::fs, std::net, std::thread, println!, environment variables, process arguments, or a libc syscall table.

capos-rt owns the repeated runtime machinery:

  • the _start entry point and capos_rt_main handoff;
  • fixed heap initialization;
  • panic output through an emergency Console path when available;
  • raw exit and cap_enter syscall wrappers;
  • CapSet lookup and interface-id checks;
  • a single-owner ring client;
  • typed clients for implemented kernel and service capabilities;
  • result-cap adoption and queued local release.

Native programs should keep ordinary Rust business logic in normal modules and push OS interaction to typed capOS clients. That keeps pure logic host-testable while making authority visible at capability lookup and child-spawn sites.

Why std Is Different

Rust std is not just “more Rust.” It is an operating-system binding. It expects an implementation of filesystem, networking, threads, time, standard I/O, process, environment, synchronization, and platform error APIs. On Linux those calls are ambient: a process can ask the kernel to open a path or create a socket and the kernel consults global process credentials.

capOS does not have that ambient authority model. A future Rust std path must choose how each std feature gets authority:

std areacapOS authority source
std::io::{stdin, stdout, stderr}StdIO, Console, or TerminalSession caps
std::fsscoped Directory, File, Store, or Namespace caps
std::netsocket or listener caps minted by a network service
std::threadThreadSpawner, ThreadControl, ThreadHandle, and ParkSpace support
std::timeTimer and future wall-clock caps
process spawn and waitProcessSpawner, RestrictedLauncher, and ProcessHandle caps
std::env and current directorysynthetic runtime state backed by manifest or namespace caps

That mapping can be implemented as a capOS std backend, a Rust compatibility crate, or a POSIX-style adapter. The project has not selected one shared ABI for all language runtimes.

Compatibility Terms

Use these terms instead of the vague phrase “compatibility layer”:

  • Native runtime adapter: language-specific runtime glue that talks to capOS capabilities directly. capos-rt is the implemented Rust example; GOOS=capos would be the Go example.
  • Capability-native bindings: generated or handwritten bindings that expose Cap’n Proto interfaces as language-level APIs without POSIX names.
  • POSIX compatibility adapter: a libc or library surface that translates open, read, write, socket, poll, clock_gettime, and similar APIs into operations on granted capabilities.
  • WASI host adapter: a WebAssembly host implementation whose imports are backed by granted capOS capabilities.

The adapter may make code look familiar, but it cannot create authority. A process without a namespace cap still cannot open a file. A process without a network cap still cannot create a socket. A process without a launcher or spawner cap still cannot create children.

Language Tracks

Rust

Rust is the only implemented userspace language. The current target is targets/x86_64-unknown-capos.json, which exposes target_os = "capos" while keeping the booted userspace baseline no_std, static, and panic = "abort". init, demos, shell, and the capos-rt smoke binary build through this custom target.

Open work before broader Rust support:

  • generated clients after the schema surface stabilizes;
  • runtime ParkSpace clients and multi-threaded ring demultiplexing;
  • a decision on Rust std over native capabilities versus a POSIX adapter;
  • package/build conventions for out-of-tree capOS Rust programs.

C and C++

C support is in tree as a Phase 0 substrate. The libcapos/ crate compiles to libcapos.a, a thin Rust staticlib that exposes the capos-rt syscall, ring CALL, CapSet lookup, typed Console.writeLine, Timer.now, EntropySource.fill, VirtualMemory wrappers, native ProcessSpawner.createPipe / Pipe wrappers, and the global allocator under an extern "C" ABI. C binaries link statically against the archive and run on the same userspace ELF layout as Rust demos; make run-c-hello boots a C main() that calls the baseline wrappers, and make run-c-pipe boots a C main() that creates a Pipe, round-trips a marker, closes the writer, and observes EOF. The substrate is intentionally narrow – no errno, no fd table, no POSIX surface – so the separate libcapos-posix layer can own those decisions without churning the substrate. The same archive is what later runtimes such as CPython, MicroPython, Lua, and QuickJS will link against.

libcapos-posix/ builds libcapos_posix.a on top of libcapos.a and ships the v0 POSIX surface: a 32-fd static table, __errno_location() TLS, UDP socket/sendto/recvfrom/close over the kernel UdpSocket cap, clock_gettime(CLOCK_MONOTONIC, ...) and gettimeofday(&tv, NULL) over the kernel Timer cap, pipe/read/write/dup/dup2 over the kernel Pipe cap, file/directory fd operations (open, lseek, opendir, readdir, closedir) over the RAM-backed root Directory cap, and a recording-shim fork/execve/waitpid/_exit/posix_inherit_stdio path plus direct posix_spawn with posix_spawn_file_actions support, all routed through ProcessSpawner.createPipe / ProcessSpawner.spawn when spawning is needed. The shipped smokes are make run-posix-pipe-smoke, make run-posix-spawn-smoke, make run-posix-stdio-smoke, and make run-posix-file. The former make run-posix-dns-smoke target is retired with the qemu-only kernel UdpSocket owner. The remaining v0 phase is the dash port (Phase P1.4) over the kernel RAM-backed File/Directory/Store/Namespace caps from Storage Phase 3 slices 1-3. See docs/backlog/posix-adapter-dash-port.md for the long-form decomposition.

C++ should wait until the C substrate exists and the project decides its C++ ABI policy: exceptions, RTTI, TLS, allocation, unwind behavior, and standard library scope. A freestanding container/arena subset is plausible earlier than full hosted C++.

Go

Go is a dedicated future design because its runtime is close to a userspace operating system. A native GOOS=capos port needs virtual memory reservation and commitment, TLS setup, OS-thread creation, park/wake, monotonic time, debug output, process exit, and eventually network polling.

The current kernel/runtime substrate already proves useful pieces: VirtualMemory, Timer, ThreadControl, ThreadSpawner, ThreadHandle, and private ParkSpace wait/wake exist at the capOS level. The missing work is the Go runtime port and the runtime-side integration contract, not a new ambient syscall namespace.

Go through WASI may be sufficient for CPU-bound tools such as CUE evaluation; that path is tracked as Phase W.8 in WASI Host Adapter (TinyGo or upstream Go GOOS=wasip1 against a future ScriptPackage cap). Native GOOS=capos remains the path for Go network services and full runtime behavior.

Python

Python is not currently supported on booted capOS. The practical paths are:

  • Native CPython through a POSIX compatibility adapter. This depends on the C/libc substrate plus file, stdio, timer, networking, and process adapters. It is the likely path for trusted system scripts, configuration tooling, and Python programs that need capOS networking or storage.
  • MicroPython through the same native C substrate. This is a smaller early scripting option with less runtime surface than CPython.
  • WASI or Emscripten-hosted Python. This is useful for sandboxed or compute-oriented Python. It still runs a Python interpreter; WebAssembly is the sandbox/host ABI, not a way to avoid Python runtime work.

Current upstream CPython support is relevant but not sufficient by itself: PEP 11 lists wasm32-unknown-wasip1 as a Tier 2 CPython platform and wasm32-unknown-emscripten as Tier 3, while PEP 776 records Emscripten support for Python 3.14. Those targets help the WASM path. They do not provide native capOS file, socket, thread, or capability bindings.

Lua

Lua is a capability-scoped scripting runner. The target is not a POSIX Lua shell. A capos-lua process should receive an exact CapSet, load curated standard libraries, expose capabilities as unforgeable host userdata, deny raw CapIds, and flush owned handles at script exit.

Phase 0 lives in demos/lua-smoke/ as a hand-written Lua-subset interpreter written entirely on top of capos-rt. It exists to validate the long-term capability-aware host API design (typed userdata, obj:method(args) dispatch through a host registry, no raw SQE or method-id leak into Lua, errors surfaced as Lua runtime errors) without committing capOS to a particular Lua dialect. The interpreter accepts a strict subset (local, if/elseif/ else, numeric for, while, integer/float arithmetic, string concat, comparison, obj:method(args) calls); tables, closures, coroutines, metatables, and the Lua standard library are not implemented.

Upstream PUC Lua is a small C implementation, so the dialect-compatible path waits on the C/libcapos substrate. The Phase 0 interpreter is not a promise of PUC Lua compatibility and the smoke binary is explicitly labelled runtime = "capos-lua-subset" rather than lua-5.x. When the C/libcapos port lands, the embedded interpreter is replaced or kept as a research-grade sandbox; the host binding shape stays.

JavaScript and TypeScript

JavaScript support means running an engine as an ordinary capOS process. A small QuickJS-style runner is the plausible early native path once C support exists. V8 or SpiderMonkey are much larger C++ runtime ports and should be treated as later experiments. TypeScript would normally compile before execution; capOS should not make a TypeScript compiler part of the kernel or base runtime.

WASI and Browser WebAssembly

WASI support is a host-runtime track: the host imports become capability calls. The full design is in the WASI Host Adapter proposal, and the implementation decomposition is in WASI Host Adapter. The proposal selects wasmi for the v0 phases (no_std + alloc userspace runtime, fuel metering, externref support) and frames wasmtime / WAMR as the W.7+ migration targets. Each WASI import is backed by a typed capOS capability the host adapter already holds; ungranted authority is refused, not synthesised. WASI is a good fit for code that is already designed around explicit imports and sandboxed execution. It is not a replacement for native runtime ports when the language expects OS threads, signals, sockets, memory mapping, or a large POSIX surface.

The browser/WebAssembly proposal is separate. It explores running capOS concepts in a browser using worker-per-process isolation and SharedArrayBuffer-backed rings. It is a teaching and demo target, not current native userspace language support.

Proposal Map

Validation

Current language-runtime validation is Rust-only:

  • tools/check-userspace-runtime-surface.sh verifies that capos-rt owns _start, panic handling, allocator setup, raw syscalls, and entry macros.
  • make capos-rt-check, make init-capos-build, make demos-capos-build, make shell-capos-build, and make capos-rt-capos-build build the booted userspace artifacts against the capOS custom target.
  • make run-smoke, make run-spawn, make run-shell, and make run-terminal exercise the runtime surface through QEMU.

No page should claim support for Python, Go, Lua, C, C++, JavaScript, WASI, or Rust std until there is a booted artifact and a validation target for that runtime.