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.
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.
- The kernel owns everything physical — the CPU, interrupts, memory protection, the clock, the drivers, the control math. It is native Zig, it holds the microsecond timing and the safety, and it changes slowly.
- The orchestration layer decides what the machine does. It runs as Lambda C bytecode — actors — on a VM the kernel hosts. It changes often, it can be swapped whole, it is delivered separately from the kernel, and it does not have to be trusted.
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.
- Where there is an MMU (x86_64, riscv64, aarch64): an address space per actor. The kernel's page tables are cloned per actor, every other actor's pages lose their user bit, and the scheduler swaps the root on each context switch.
- Where there is none (a bare RV32, a Cortex-M0+): a PMP or MPU region per actor, reprogrammed on each context switch.
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.
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.
- The microcontroller is written in C, the machine above it in something else, and you maintain the protocol between them. Half your bugs live on that boundary.
- A bug in the mission logic takes the control loop down with it. They run at the same privilege, in the same memory.
- On a cheap chip, a deadline you cannot miss and logic that can are competing for the same core.
- You were told the requirement means a Linux-class SoC. The power and the cost do not work.
- No RTOS supports the chip you want, and porting one means finding every place that OS assumes an MMU, an allocator, an atomic instruction.
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.