The services, and how they reach each other
Everything above the kernel is a service: an isolated address space, a contract, a capability table, and one receive endpoint. Nothing here shares memory with anything else. Every arrow on every diagram below is an IPC endpoint - a message copied by the kernel from one address space into another, under a capability that had to be granted (§8, §7).
This page is the map. It is written from the supervisor's own IMAGES table, which is the single
place a service's name, image, core, peers, privileges and device class are declared. The rows for
the worked examples under examples/, which the test suites start, are left out.
How a connection is made at all
A service does not "find" another service. It is wired to it, or it asks for it by name and the kernel mints a capability - and nothing else works.
SPAWN TIME (the supervisor wires it)
┌────────────┐ ┌────────────┐
│ supervisor │──Spawn(image, peers:["fs"])─▶│ kernel │
└────────────┘ └─────┬──────┘
│ mints a SEND cap to
│ fs's endpoint, into
▼ the new task's table
┌────────────┐
│ shell │ holds cap → fs
└────────────┘
LATER (fs restarts; the shell's cap is now stale)
┌────────────┐ send ──▶ EndpointDead ┌────────────┐
│ shell │──AcquireSendCap("fs")───────▶│ kernel │ name directory
└────────────┘ ◀── fresh cap, new gen ────└────────────┘ fs → EndpointId
Two properties follow, and they are the reason the rest of this page is shaped the way it is:
- Identity is stable, location is not (invariant 11). A restarted
fsis a different task on a possibly different core with a new endpoint generation. Clients seeEndpointDead, reacquire by name, and carry on. Nobody tracks where anything runs. - Authority is explicit (invariant 1, 3). A service can only reach a peer it was granted, or one
it was spawned declaring (the peer list in its spawn request, not its contract) and reacquired by
name.
ACQUIRE_ANYlifts that for the operator instruments - the shell, the supervisor, the probes. There is no ambient directory to browse.
The whole system
Arrows point from the caller to the service it sends to.
┌──────────────┐
│ kernel │ spawns exactly one thing
└──────┬───────┘
│
┌──────▼───────┐
┌─────────│ supervisor │─────────┐ spawns everything else,
│ └──────┬───────┘ │ restarts what dies
│ │ │
┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ control │ │ shell │ │ events │
│ (COM2 ops) │ │ (the user) │ │ │
└──────────────┘ └──┬───┬───┬──┘ └─────────────┘
│ │ │
┌─────────────────┘ │ └──────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ fs │ │ console │ │ time │
└─────┬─────┘ │ (display) │ └──┬────────┘
│ └───────────┘ │
┌─────▼────────┐ ┌─────▼──────┐
│ block-driver │ │ net-stack │
└─────┬────────┘ └─────┬──────┘
│ (USB disk) │
┌─────▼────────┐ ┌─────▼──────┐
│ dwc2 / xhci │ │ nic-driver │
│ USB host │ └─────┬──────┘
└──────────────┘ │ (Pi 2: through USB)
┌─────▼──────┐
│ dwc2 │
└────────────┘
On x86 block-driver talks to an AHCI controller through its own MMIO capability and has no USB peer
at all; on the Pi 2, the Pi 4 and the VisionFive 2 the disk is a USB device, so it goes through the
USB host service. Same service, same block protocol, different peer - which is the point of naming
peers rather than devices.
The services
supervisor - restart authority and the image catalogue
kernel ──spawn (the ONE direct spawn)──▶ supervisor
│
┌─────────────────────────────────────┼──────────────────────────┐
│ IMAGES[]: name, ELF, core, peers, │ death notification │
│ privileges, device class │ ◀───────────────────────┤
▼ ▼ │
Spawn(image, class, bdf) ──▶ kernel respawn ──▶ kernel ───────────┘
Trusted, and restartable: when it dies the kernel respawns it, unconditionally and forever, and it reconciles by adopting the services still running rather than duplicating them. The only unkillable thing in the system is the kernel (§6.2, §6.3).
Peers: none. Everything reaches it, not the other way round.
shell - the user's interface, and a capability broker
keyboard ──▶ kernel console ring ──▶ shell ──▶ console (what you see)
│
┌────────────────────┼──────────────┬──────────┐
▼ ▼ ▼ ▼
fs block-driver time supervisor
(files, pipes) (drives) (clock) (spawn/kill)
There is no stdin and no fork. A pipe A | B is the shell carrying each stage's output to the
next: in-process between built-ins, and for a service stage a SEND cap to the shell's own endpoint,
granted at spawn (Appendix D.3, docs/pipes.md). A killed shell respawns as a fresh prompt - the in-flight command is
lost, the session is not.
Peers: fs, block-driver, time, console, events, supervisor.
fs - the filesystem, and files as capabilities
client ──Open("/doc.txt", READ)──▶ fs ──resource_mint──▶ kernel
◀──────── a real kernel capability ──────────────┘
│
client ──send ON THE FILE CAP──▶ kernel ──badged (resource_id, right)──▶ fs
│
▼
block-driver (512-byte blocks, tagged req/reply)
A file is a capability (§7.10): unforgeable, non-escalating, revocable by generation bump. fs
commits metadata through a crash-consistent redo journal, so its death is a restart rather than a
reboot - which is what took it out of the trusted computing base.
Peers: block-driver, events.
block-driver - blocks, and nothing above them
fs ──[op, lba, data?]──▶ block-driver ──▶ AHCI (x86: own MMIO cap + DMA)
◀──[status, data?]─── └──▶ dwc2 / xhci (USB: over IPC)
It knows sectors, not files. Where the disk is a USB device it sits behind the USB host service, so a peer restart makes its capacity temporarily unknowable - and unknowable is answered with an error, never with "no disk". Publishing zero sectors during a peer restart is how a filesystem gets mounted against nothing.
Peers: events, plus the USB host where the disk is on USB - dwc2 on the Pi 2, xhci on the
Pi 4 and the VisionFive 2.
xhci / ehci / dwc2 - the USB host controllers
kernel ──MMIO cap + DMA arena + IRQ route──▶ xhci
│
┌──────────────────────────────┼────────────────┐
▼ ▼ ▼
keyboard ──CONSOLE_PUSH──▶ shell hub mass storage
│
block-driver ◀┘
Ring-0 code parsing descriptors supplied by whatever was plugged in is exactly what §4.4 forbids, so these are ordinary services holding only what their contract grants. They cannot reboot the machine
REBOOTlives with the shell alone (SEC-2). Where an IOMMU confines one, a compromise is bounded to its granted DMA arena and it leaves the trusted computing base (§6.4). Today that isxhcion x86 alone:ehciruns in IOMMU passthrough even where there is one, and the Pis and the VisionFive have no IOMMU at all, so theredwc2andxhcican point their DMA anywhere and stay in the trusted computing base.
A host also serves an installed USB driver package: when a device the package names by vendor and
product is plugged in, the host binds it and carries the package's transfers to it (gs::driver::bus),
so the package needs no hardware authority of its own (§14.1, the 2026-10-23 amendment).
nic-driver and net-stack - the network
net-stack ──frames──▶ nic-driver ──▶ e1000 / RTL8168 (x86, own MMIO cap)
│ └──▶ dwc2 (Pi 2, USB ethernet)
│ └──▶ GENET (Pi 4, on the SoC; the radio when the cable is out)
│ └──▶ dwmac (VisionFive 2, on the SoC)
▼
ARP · IPv4 · ICMP · UDP · DHCP · DNS · TCP
│
└──▶ a socket is a capability (the same mechanism as a file)
Where a board has a radio - wifi-driver (Pi 4, VisionFive 2) or wifi-usb (a USB dongle) -
nic-driver bridges frames to it when the cable is out (not on QEMU's e1000, which has no bridge),
and the cable always wins.
The kernel gains nothing from networking: it routes messages, and a socket is a delegated resource
capability owned by net-stack. There is no ambient network any more than there is an ambient
filesystem.
The clock is not the network's: net-stack never calls the clock service, which fetches its own NTP answer through net-stack's op 12, a UDP datagram answered when the reply arrives.
Peers: net-stack → nic-driver.
console - the terminal
any service ──text──▶ console ──▶ framebuffer (grid, cursor, scroll, ANSI/CSI)
▲
kernel bootcon ────────────────────┘ boot + panic only, then hands over
1,172 lines of terminal emulation that used to live in the kernel. What the kernel kept is a blit that cannot format, position or scroll - because a panic halts every core including this service, so it cannot ask a service to report it (§11.4).
Peers: events only - everything else writes to it.
wifi-driver - the radio
shell (wifi) ──▶ wifi-driver ──▶ SDIO host ──▶ the radio chip (its own firmware, uploaded at start)
nic-driver ──▶ wifi-driver (frames, once the cable is out - the cable always wins)
├──▶ fs (/system/shared/wifi/keys: the keys a join earned)
└──▶ power (a lease on the Arm clock while it loads the chip)
The Pi 4's onboard radio, and the VisionFive 2 Lite's AIC8800 (docs/wifi-aic8800.md: scan, WPA2 join,
DHCP and ping over it, the cable taking the link back). The chip-independent half - the handshake, the key file, the wire protocol, the
SDIO protocol and the Station a serve loop drives - is a library, sdk/wifi, shared with the radios to
come. A respawn adopts a firmware still running; a stopped one is power-cycled cold.
Peers: fs, power.
wifi-usb - a USB WiFi dongle
wifi-usb ──usbfn──▶ USB host (dwc2 or xhci) ──▶ the dongle it bound (a Realtek RTL8188CUS)
The driver for a WiFi dongle, separate from wifi-driver so a board with an onboard radio can run both.
It holds no hardware: the USB host that enumerated the dongle binds it as the radio and answers
godspeed_wifi::usbfn for that one device - who it is, its control transfers and its frames. Behind
dwc2 on the Pi 2 and xhci everywhere else, it scans, joins WPA2 and carries the machine's traffic
through nic-driver when the cable is out; the supervisor starts it when the host reports the dongle and
stops it when the dongle leaves (docs/wifi-usb.md).
Peers: its USB host (dwc2 or xhci), and fs for /system/shared/wifi/keys.
power - the machine's power policy
wifi-driver ──lease──▶ power ──CpuClock──▶ kernel ──▶ firmware mailbox (the Arm clock)
Holds CPU_CLOCK alone. Leases of up to 30 s keep the Arm clock at its maximum while any is open; a
lease nobody returns expires on its own, so a holder that dies cannot pin the machine fast. Without a lease
a governor sets the clock by load, as Raspberry Pi OS's ondemand does: every 100 ms, a core over 50% busy
holds it at its maximum for 5 s. It reads each core's counters with INTROSPECT, and nothing more. It also gives a
shutdown's or a reboot's warnings and its tidy-up three seconds before the deadline, reading the deadline the kernel holds; it cannot shut the
machine down itself - only the shell can.
Peers: fs and copier, for a shutdown's or a reboot's tidy-up only; the leases still only answer.
audio-driver - sound
HD Audio controller ──MSI──▶ audio-driver ──▶ CORB/RIRB ──▶ codec (power, amps, pin, converter)
│
└──▶ a ring of sound in its DMA arena, refilled per period
Intel High Definition Audio, x86 only, heard on the Dell Wyse 5070 and the HP T630, speaker and headphones on both. The stream interrupts as each period of the ring is played and the driver refills it then, where the controller has MSI; where it has none (the T630) the driver polls. Its DMA is confined behind the IOMMU where there is one. On its death the kernel stops the controller's DMA and releases the confinement, keyed on the device it was given rather than its name.
Peers: fs, for its settings file /system/services/audio-driver/settings.
pwm-audio - sound on the Pis
shell (audio) ──▶ pwm-audio ──▶ DMA engine ──▶ PWM FIFO ──▶ the 3.5 mm jack
└──▶ fs (its settings file)
The same protocol as audio-driver, for a different device: the Pis have no codec, so each sample is a
PWM duty cycle, moved by the DMA engine as the PWM asks. The kernel routes the jack's pins and starts the
PWM clock when it grants the device, because both live in blocks shared with every other pin and clock.
Peers: fs, for its settings file /system/services/pwm-audio/settings.
time - the wall clock
shell ──▶ time ──▶ net-stack (op 12: one NTP datagram, answered when it arrives)
└──▶ fs (persist a clock floor across boots)
Peers: fs, net-stack.
events - the observability sink
Logging does NOT go through it, and that is the design. ctx.log() is a syscall: it writes the
kernel's 16 KiB ring buffer and the serial console DIRECTLY, for every service, for the life of the
machine. So a chaos storm that kills this service loses no log output, and ctx.log() never blocks
on it and never fails because of it.
ctx.log() ──syscall 5──▶ kernel ring + serial (always, never via events)
└──────▶ events (a best-effort COPY, offered after the fact)
gs::trace::metric() / IPC trace ──▶ events ──▶ three VOLATILE stores:
192-event IPC trace ring
64-slot metric table, keyed (owner, name)
8 KiB log window
That ordering is the whole point and must not be inverted: re-pointing logs AT this service would make observing a failure depend on a service that can fail.
Everything it holds is volatile and starts empty after a restart - a re-init, not a resume. The publishers are what make that work: they resolve lazily, ask the kernel for a cap when they hold none, retry a sample after reacquiring, and republish their liveness row on the first message after the sink returns.
Read it with: events ipc, events metrics, events log, events status.
Peers: thirteen services declare it as a send peer; it publishes to nobody.
recorder - the capture that outlives the screen
Spawned on demand by events persist start, never at boot, and deliberately not restarted.
Writing to disk blocks, and a blocked single-threaded loop stops draining its endpoint - so if
events did this itself it would drop the very events worth capturing, on a sick disk, exactly when
they matter. recorder has the identical problem and it does not matter, because nothing depends on
it: the dependency points the right way.
It is not restarted on purpose. A respawned recorder would not know its target path, so it would be
alive and writing nothing while status said "running" - worse than dead, because dead is visible.
Instead a capture opens with a header and closes with a footer, so a file with no footer says plainly
that it died.
Peers: events, fs.
ask, godspeed-pm, copier, chaos, observe - programs started on demand
None of these is started at boot and none is in MANAGED, so nothing restarts them: each is started
for one job and ends, or is ended, when the job is done.
askanswers one question about the machine (ask "is my system healthy?"). The shell starts it, hands it the question and reads back a record table of facts, inferences and unknowns. It only reads:INTROSPECTfor the task table, and requests to the supervisor,fs, the storage chain and the radio drivers on this board. A network question starts it under a second row,ask-net, whose peers arenet-stackand the radio drivers, because no one row may hold more than six.godspeed-pmis the machine's package manager, started by the shell for onegodspeedcommand. It sends to the shell for the answer,fsfor its settings and trust roots,net-stackfor the registry's connection,timefor the date a certificate is checked against, and the supervisor, which starts and stops an installed package for it. It holds no privilege.copierruns one detached job without owning the prompt -background copy, a tree delete, a check, a scrub - so a long job cannot take the shell down with it. It is not restarted, because a copier that came back would not know what it was copying. It sends tofs, and to the board's audio driver for the "done" sound a finished job makes.chaosischaos max-carnage's loop. It cannot live in the shell, because the shell is one of its targets. It holdsSPAWN,INTROSPECT,SERVICE_CONTROLandACQUIRE_ANY, and the console's input so thatqreaches it, and it never kills itself. Its spawn-storm ammunition, and the victim ofchaos mem-pressure, ismem-pressure: it allocates up to its memory limit, checks that a refused allocation stays refused, and waits to be killed, which is how its memory comes back.observeis the live task view: every task's core, state, memory, restarts, queue depth and CPU use. It holdsINTROSPECTand nothing else. It runs under three rows,observe,observe-nowandobserve-live, one binary that each row starts in a different mode.
hw-enumerator - hardware discovery in userspace
supervisor ──"which device is class 0x0C0330?"──▶ hw-enumerator
◀──────────── BDF ───────────────────── │
PCI_CFG: read ONE
config register,
select+fetch atomic.
No write. Ever.
It holds the narrowest hardware capability in the system. It cannot write configuration space at all - that would be write access to every BAR of every device on the bus.
Peers: events only - the supervisor asks it, never the other way round.
control - the operator channel
host ──COM2──▶ control ──▶ supervisor (KILL / RESTART, for tests and operators)
Peers: supervisor.
What a restart looks like from the outside
The single most important behaviour to understand, because every service depends on it:
t0 shell ──send──▶ fs works
t1 fs dies. kernel bumps its endpoint generation, drains the queue,
clears its name from the directory
t2 shell ──send──▶ EndpointDead the stale cap fails loudly
t3 supervisor respawns fs (possibly on a different core), which
re-registers its name
t4 shell ──AcquireSendCap("fs")──▶ fresh cap and carries on
Between t1 and t3 the name does not resolve. A client that treats that window as "the peer is gone forever" rather than "ask again" is the shape of most of the bugs this system has had.
Reacquiring the endpoint is necessary but not sufficient (§14.3). Anything derived from the dead instance - an open-file capability, a socket, a cached capacity - was issued by an instance that no longer exists. It must be re-derived, not remembered.