Lambda OS

A bug in the mission logic cannot corrupt the control loop.
The mission runs unprivileged, confined by the hardware. The control loop runs native, beside it. On a $6 microcontroller with no MMU.

Real-time system architecture — sense, analyze, decide, act
x86_64 riscv64 aarch64 RV32 ARMv6-M
What runs on which architecture

The problem

On a small embedded system, two kinds of code share one chip.

There is code that must not miss a deadline — the control loop, the safety envelope. And there is code that decides what to do — the mission, the flight plan, the part that changes from job to job.

They have opposite needs. The deadline code must be trusted and fixed. The mission code you want to change often, and a bug in it is normal. But on a cheap chip they run at the same privilege, in the same memory — so a mistake in the mission takes the control loop down with it.

The usual escapes are to write everything in careful C and have no separable mission at all, or to buy a bigger SoC with an MMU and run Linux. The first costs development speed; the second costs power and unit cost.

The one decision

Lambda OS splits the chip into two layers, and lets them touch only through one narrow, checked boundary.

An actor can call only the fixed vocabulary the kernel exposes — a set of operations, nothing else. Everything below follows from this one split.

Because they are split, the actors are confined

The kernel does not trust an actor, so it confines it in hardware. Each actor runs unprivileged, and the silicon's own memory protection boxes it in.

This is not a promise in software. An actor storing outside its region takes a hardware fault — a page fault on x86, a store-access fault on RISC-V, a HardFault on Cortex-M. A probe checks it on every boot, and removing the confinement lets the identical store land.

And because an actor calls only the fixed vocabulary, it is confined a second way: if there is no operation to command a thing, the actor cannot command it. An actor that may propose a target but was never given the operation that sets the final target cannot override the controller, however it misbehaves.

Because the timing is native, it holds the deadline

The deadline code sits in the kernel, as native Zig, so a runaway actor cannot stop it — a preempting timer switches away, and a per-frame instruction budget bounds any single frame.

The scheduler's clock is a nanosecond clock, and its timer is armed at the next deadline, not on a fixed tick. So a 1 kHz control loop is an ordinary periodic task. On a real RP2350 the real control law ran as a scheduled 1 kHz task and met its deadline in 1,741,824 of 1,741,824 jobs — zero misses — while the network and the mission shared the chip.

The same control law's worst case was measured on both boards over a 20,000-input adversarial sweep: 190 µs on the Cortex-M0+, 98 µs on the Hazard3 — a 5.2× and 10.2× margin under a 1 kHz budget.

Because only the kernel knows the hardware, the port is small

Everything above the hardware — the scheduler, the VM, the actor layer, the network stack — is the same source on all five targets. Only the kernel's bottom edge — its HAL — is swapped, chosen at compile time. An architecture that implements it plugs in; one that does not fails to compile, not at runtime.

That is why one OS, written for a 64-bit x86 machine, came all the way down to an ARMv6-M Cortex-M0+ — no MMU, no FPU, no atomic instructions at all — and the scheduler's only concession was the size of its tables.

0bytes an actor can write outside its own memory — on every target, including two chips with no MMU at all
190 µsworst-case control cascade, measured on a real Cortex-M0+ — 5.2× headroom under a 1 kHz budget
1 kHzthe real control law as a scheduled task on real RP2350 silicon — 0 deadline misses in 1,741,824 jobs
5targets from one Zig toolchain: x86_64 · riscv64 · aarch64 · RV32 · ARMv6-M

Where the two layers meet the ground: a delivery drone

The split is easiest to see in the flight controller that runs on a Raspberry Pi Pico 2 W (RP2350, no MMU), on real silicon, over ordinary Wi-Fi.

The kernel flies the aircraft. The cascade-PID-plus-quaternion control loop and the safety envelope are native Zig, in the kernel. The safety envelope holds the geofence, comes home on a low battery, isolates a failed IMU and flies on the backup, and deploys the parachute when control is genuinely lost.

The mission is one confined actor. It is the flight plan — climb from base, cruise over the sea to an island 90 m out, drop the cargo, return, land — uploaded as bytecode, unprivileged, boxed in by the PMP. It proposes the next waypoint; the native safety envelope decides the target the controller actually flies. A bug in the flight plan cannot touch the safety envelope or the control loop: they are across the kernel boundary.

And the board proves the boundary before it flies. Its pre-flight sequence attempts an unprivileged store into kernel memory; if the hardware does not refuse it, the board reports the failure and does not arm the mission. Only after the refusal is observed is the mission spawned, and the board holds until a human authorises launch.

The plant — the physics, the wind, the sensor noise, the injected faults — runs on a PC. The controller runs on the RP2350 on the desk. The two close the loop over the radio. The demo in full is on the Flight Control page.

A mission, in Lambda C

A mission actor reads a real hardware timer, drives a pin every 50 ms, and hands the count to another actor:

// actor.c — a Lambda C script, running hardware-isolated
int self(void);
int send(int dest, int tag, int a, int b);
int recv(void);            // returns the message tag
int msg_a(void);           // first payload word of that message
int micros(void);          // real hardware timer
int pin(int n, int v);     // drive an output pin

int last, state, count;

int main() {               // called once per scheduler frame
    if (self() == 0) {                       // producer
        int now = micros();
        if (now - last >= 50000) {           // every 50 ms
            last = now;
            state = state == 0 ? 1 : 0;
            count = count + 1;
            pin(0, state);                   // actuate
            send(1, 0, count, 0);            // coordinate: tag 0, payload = count
        }
    } else {                                 // consumer
        int tag = recv();
        if (tag >= 0) host_log(msg_a());
    }
    return 0;
}

The script never touches a register. micros(), pin() and send() are the fixed vocabulary — FFI handlers in the kernel that read the real timer, write the real GPIO, and enqueue into a kernel-owned mailbox. The bytecode is the intent; the kernel is the mechanism.

It also does not write an internal loop: the scheduler re-runs main() once per frame. Globals persist across frames, the operand stack resets each frame, so every actor stays finely preemptible and no runaway frame can hang the machine.

Actors on different chips talk to each other

An actor on one chip can send a message to an actor on another. It arrives in an ordinary mailbox, is read with the same recv(), and dispatched on the same tag — no gateway, no protocol translation, no second runtime at the boundary. A PMP-confined actor on a Pico 2 W has sent a message over real Wi-Fi and a real LAN to an actor on an x86 Lambda OS and read the reply from its own mailbox.

This is the point of the actor model here: not to pack many actors onto one cheap chip, but to let each small board hold a few actors and coordinate over the network — a distributed system whose edge happens to cost a few dollars.

That gives the two kinds of node different jobs. An orchestrator (x86_64, riscv64, aarch64) holds many actors and has the machinery for them — dynamic spawn, supervision that rebuilds a dead worker. An edge node (an MMU-less MCU) holds one or two and coordinates over the wire; putting the many-actor machinery on a chip that small is the thing distributed actors exist to avoid.

It is an RTOS in its own right

Lambda OS is a from-scratch RTOS, complete on its own. The kernel — scheduler, drivers, network stack, real-time primitives — stands alone, and control that must never miss a deadline runs as native Zig. A deployment can be entirely native, with no bytecode at all.

The network stack is real: Ethernet, ARP, IPv4, ICMP, UDP, DHCP, DNS, and a TCP with retransmission, congestion control, fast retransmit, SACK and three simultaneous connections — the same source over wired Ethernet and over Wi-Fi. The real-time primitives are the usual RTOS set: priority-inheritance mutex, CPU-time reservation, rate-monotonic analysis, a nanosecond clock.

Where a mission is used, it runs inside a single trust domain. All bytecode comes from the same operator over a trusted channel, so Lambda OS deliberately does not provide multi-tenant execution, a verifier for hostile bytecode, or general-purpose scripting — no closures, no GC, no dynamic typing. The isolation here is a robustness boundary — it contains a bug — not a defence against an adversary.

Where it fits

Lambda OS is proprietary software, all rights reserved — not open source. We engage through joint development, customization for a specific hardware target, and source disclosure under NDA as part of a paid engagement.

It was built for situations like these.

If the RTOS you have already answers these, it is the right tool. If it does not, what runs on which architecture is written out in full, and every claim on this site can be checked against it.

Contact: Lambda LLC · the sibling VM it hosts is Lambda C.