What if the operating system were a log?
felixd is the first step toward a distributed operating system whose state is a Felix log: programs that survive the loss of their machine, a stale copy that cannot write, and any past moment rebuilt on demand.
A machine dies at three in the morning. A cluster manager notices, starts the program somewhere else, and the program begins again from nothing. Whatever it held in memory is gone: the running totals, the half-finished batch, the session it was in the middle of. If it was lucky it kept a checkpoint somewhere. If it wasn't, someone spends the morning working out what was counted twice.
Restarting is the best a cluster manager can do, because the cluster manager doesn't know what the program saw. It only knows what the program was supposed to be.
felixd starts from the other end. If every input a program receives is a record in a durable, ordered log, then the program's state at any moment is something you can rebuild. You don't need its memory. You need its inputs and a way to run it again.
One log, every input
In felixd a workload is a WebAssembly module whose only inputs and outputs are Felix streams. It has no clock, no random numbers, no files and no network. If it needs the time, the time arrives as a record. Given the same records, it reaches the same state every time.
That rule is what everything else rests on. Two copies of a workload fed the same records can't disagree, so it doesn't matter which machine runs it, or whether two machines briefly do.
Each machine runs felixd. The agents share the work through Felix: a workload belongs to whichever node holds its lease, a cache entry with a time-to-live that the owner keeps renewing with a conditional write. When a node stops renewing, the lease expires and another node claims it. The new owner loads the latest snapshot of the workload, subscribes to its input from the offset the snapshot covers, and carries on.
Every output record names the input that produced it. So the new owner can tell which outputs its predecessor already wrote. It reruns those inputs to rebuild its state and doesn't write their outputs a second time.
The copy that wakes up late
Failover is the easy half. The hard half is the machine that isn't dead, only stuck: a long garbage-collection pause, a VM migration, a laptop lid closed and opened. Its lease expired while it was frozen, someone else took over, and now it wakes up with a stale copy of the workload and a pen in its hand.
Leases alone can't stop it. Its clock may say the lease is fine. What stops it is that the output stream refuses the write. felixd appends output with Felix's publish_if, which only succeeds if the batch lands at exactly the offset the writer expected. Each workload's output stream has one shard and one owner at a time, so once the new owner has written anything, the old owner's expected offset is stale and its write is refused. The refusal is how it learns it lost the workload.
If the stale copy happens to write first, nothing breaks either. Determinism means it wrote exactly what the new owner would have, and the new owner sees that and skips ahead. Either way, each input's output appears once, in order.
Any moment, rebuilt
A log that keeps every input is also a time machine. To see a workload as it was at 14:02, find the input offset at that time with offset_for_time, load the last snapshot before it, and run the inputs up to it. That's felixd replay. It needs no lease and writes nothing, so you can run it on a laptop against production history.
The same mechanism answers other questions. Run a new version of a module over recorded inputs and compare its outputs with the current version's before switching. Replay a workload twice and diff the results to check that it really is deterministic. Step through a production bug on the inputs that caused it, rather than guessing from logs.
Where this could go
felixd covers the first kind of program, deterministic ones. The longer path adds the rest of what an operating system does, each piece decided through records on the log:
| An operating system needs | Built on a log |
|---|---|
| Processes | Workloads: a module and its input streams, state rebuilt from them |
| Pipes | Streams, durable and across machines |
| A scheduler | A workload that reads heartbeats and writes assignments, so its decisions can be replayed too |
| System calls | Requests on a stream, answered by a host service; replay reuses the recorded answer |
| Files and a namespace | Streams and caches under a tree of names, with per-program views |
| Boot | Reading the desired state and claiming leases |
Programs that can't be made deterministic, like existing binaries and databases, would get checkpoint-and-restart with their I/O recorded. They're the escape hatch, not the model.
Plan 9 is the closest ancestor, and both a model and a warning. It had one protocol for everything, per-process namespaces, and a file system you could browse as it was on any past day. It also asked people to leave Unix behind, and a dead file server hung every machine that had mounted it. The design lists what to take from it and what to avoid.
What it isn't
A log in the path of every effect costs milliseconds, not microseconds. This isn't a desktop operating system, and it isn't for tight loops or low-latency request and reply. It's for programs where losing or repeating work is the expensive part: stream processing, ledgers and other state machines, fleets of machines that come and go, game and simulation servers.
None of the pieces are new. Temporal replays deterministic workflow code from an event history. wasmCloud runs Wasm components across machines. CorfuDB builds replicated data structures on a shared log. felixd asks whether one replicated log, with conditional writes and leases, is enough on its own: no separate workflow database, no coordinator, no job manager.
Join in
The design and milestones are on GitHub, and the discussions are open: what to build first, the open design questions, and how much of Plan 9 to borrow.
Try Felix
Write the compose file from the Docker Compose guide, then start a broker and its control plane. With Podman, use podman compose.
docker compose up -d