As a lot of you already know, backward compatibility
has never really been the strong suit of Linux or the
BSDs. Part of this is cultural: there's a mismatch in
expectations around dependency and package management,
and userspace developers don't always appreciate that
“old” code is often still doing real work.
People break it without realizing how much depends on
it, and compatibility suffers as a result.
So here's the problem, stated plainly:
Dependency management and compatibility haven't been
solved elegantly yet. There have been interesting
attempts: Canonical's Snaps, Red Hat's Flatpaks, and
Simon Peter's AppImage all come to mind, but it feels
like app distribution and compatibility are actually two
separate problems that keep getting bundled together.
The attempts above end up overreaching outside their
actual problem domain. App distribution shouldn't be on
the hook for handling userspace components or
compatibility with userspace, and it shouldn't be on the
hook for security either. Those are genuinely separate
concerns and should be treated as such.
To be clear, I'm not trying to single out Flatpak as the
villain here. What it's doing is a natural response to
trying to solve a fragmented ecosystem all at once. It's
a holistic fix for a problem that's anything but
holistic.
The plan, then, is to rethink this from the ground up.
Instead of meeting the fragmentation halfway, which is
essentially what Flatpak does, the focus should be on
building a stable, backwards-compatible userspace first,
as the actual foundation everything else builds on.
Package management or app distribution and security
would be split off as two separate concerns, each
handled by its own subsystem, rather than crammed into
the same tool. Naturally, this raises immediate concerns,
particularly regarding the risk of centralization. These
concerns are addressed in the Implementation Detail
section of this page.
Basically: bring the Windows NT compatibility promise to
Linux.
Implementation Detail
The compatibility layer is deliberately abstracted from
the userspace beneath it. The userspace is intended to
remain stable in its own right, even when software interfaces
with it directly; the abstract API exists to reduce applications’
dependence on particular userspace components and implementation
details. It is concerned with the operating-system facilities
that applications should be able to rely upon initialization,
service management, application lifecycle, permissions, desktop
integration, and similar functionality not with replacing every
library commonly found on a Linux system. It does not attempt
to impose project-specific versions of OpenGL, Vulkan, traditional
C-style sockets, or every other established interface.
Those technologies may remain available where they are appropriate,
while applications that want a more portable and durable target
can use the higher-level API. The purpose of this layer is
therefore not to make the userspace irrelevant, but to make
dependence upon any one part of it less necessary.
Applications will primarily be distributed as application packages,
but these packages are not required to be completely self-contained
in the manner of an AppImage. Because the operating system provides
a stable userspace and a stable application-facing API, an
application does not need to bundle everything merely to protect
itself from arbitrary changes in the host environment. The
package may contain libraries and dependencies that are not part
of the operating-system contract, including application-specific
runtimes or, where necessary, its own C library (The project’s
own C library is intended to be small, simple, and backward-compatible,
but applications are not required to use it if another
implementation better suits their needs). Publishers may
bundle a more complete userspace when there is a specific
reason to do so, just as with an AppImage, but this is an
option rather than the normal distribution model. The aim
is to avoid both extremes: applications should not depend
blindly on an unstable host system, but neither should
every application need to duplicate the entire operating system.
These packages may contain more than a single executable.
Applications can be nested within one another, allowing a
package to include supporting applications, tools, or
components without requiring each part to be distributed
independently. A Lisp-based definition and configuration
layer will describe the package’s contents, relationships,
metadata, dependencies, configuration, and required behavior.
The same language will also serve as the principal
configuration and systems language for the wider operating
system, allowing the entire system including installed
applications, services, permissions, system settings,
and other state to be described as a reproducible configuration.
Rather than applying a sequence of manual changes,
the system can evaluate that configuration to produce a
complete declared state, allowing environments to be
recreated, compared, versioned, and rolled back.
In this way, application packages and system configuration
can share a common, inspectable means of describing
composition and behavior, while still remaining separate
from the stable operating-system API itself.
The language is not intended to make every implementation
detail part of the compatibility promise; rather,
it provides a flexible mechanism through which applications,
packages, and system components can be composed and entire
system environments can be reproduced.
These packages will be distributed through a dual system.
They may be installed directly from downloadable or offline
sources, or they may be installed from repositories selected
by the user. A synchronization system is also intended to
allow packages installed from non-repository sources to be
synchronized with a repository, and packages obtained from a
repository to be detached, cloned, and used independently.
In either case, the package may later be synchronized with
its original or selected repository when necessary.
The precise verification method used to establish legitimacy
is outside the scope of this article; the important point is
that repository-based and standalone distribution are not mutually
exclusive states.
This gives the system flexibility without sacrificing compatibility.
Applications can be obtained through repositories when convenient,
deployed independently when required, and moved between those forms
without being permanently tied to a single distribution channel.
Combined with the stable userspace and application-facing API, this
provides a distribution model that is both highly compatible and
adaptable to online, offline, centralized, and independent use cases.
The project is not intended to become a centralized source for Linux
or a mandatory standard imposed upon existing distributions. It is an
independent system and reference implementation whose interfaces and
design can be adopted voluntarily. If the approach proves useful,
other projects should be able to implement compatible systems or
create direct derivatives without relying upon one central organization
or vendor-controlled source. A successful implementation would
earn its place by providing a useful stable userspace, a durable
abstract API, reliable application distribution, and a coherent
security model. Other implementations could then preserve the
same application-facing contracts while differing internally.
Compatibility would emerge from usefulness and adoption,
rather than being forced upon the wider Linux ecosystem.
Core Design and Organization
The sections above describe the problem and the idea. This part
covers how the platform would be built, and who would build and
maintain it. It follows two rules for the userspace:
do not break old code, and keep it small and simple.
The first rule is the product. The second makes the first one
affordable, because a small interface is cheap to test and cheap to
keep stable for years. Every choice below is measured against both.
The common API and the core
The core has two parts: a common API that gives system components
one standard way to talk to each other, and a small set of userspace
services behind it. You can think of the API as a bus connecting the
pieces, and as a set of guidelines for how they fit together.
The reason is to keep components from getting tangled up. When one
component grows more complex or adds features, the rest of the system
should not have to take on that complexity. Init is the clearest
example. A system could use a feature-heavy init such as systemd, as
long as it connects through the API. The desktop and other components
talk to the API instead of talking to systemd directly. If the OS
later wants a different init, nothing above it has to change.
What the core knows
The core only knows a short list of long-lived kinds of things, and
never a specific vendor’s device:
- Storage: files and directories as objects a program has been handed. There is no global filesystem to browse.
- Network: sockets stay as they are, since they are already stable. On top of that the core adds a small connectivity-status interface (online or offline, metered or not, which link is active) and thin Wi-Fi and Bluetooth management interfaces for the shell and settings: scan, join, pair, connect and forget. Ordinary apps do not use the management interfaces. They see Bluetooth devices through the audio and input interfaces they already use.
- Display and windows: create a window, draw into a buffer, receive input. This has to stay fixed so apps look right under every shell.
- Input: keyboards, pointers, touch and controllers as event sources.
- Audio: a stream in and a stream out.
- Time, notifications, clipboard and dialogs: the basics every app needs.
- Permissions: ask the user for access to something, and get a reference back if they agree.
The test for what belongs is lifespan. The core can know about
long-lived kinds of things, such as files, windows, Wi-Fi and
Bluetooth, which are decades old and are not going anywhere. It never
knows about fast-moving or vendor-specific ones. GPU vendors, Wine,
Waydroid, and Windows versus Android all stay outside. Only these
stable abstractions have to hold still, so everything beneath them can
change as often as it likes.
A native program does everything through a few calls: get a reference
to something, send it a message, read or write one of its slots,
release it. Everything else is an object reached through those calls.
These ideas (objects, references, messages) are decades old, and that
is deliberate. Nothing goes into the core unless something like it has
already survived for a long time somewhere else.
Why C, and how the interface stays stable
C is a good fit for a stable binary interface. It has a long history,
it is used everywhere, and it lacks the metaprogramming features that
open a gap between what the source promises and what the compiled code
does. It is not perfect. Inline functions and macros in public headers
end up baked into other people’s binaries. The fix is to export symbols
through map files, so the linker and loader enforce exactly what is
public.
- The abstractions describe relationships, like “something you can read from” or “something you can send a message to,” and say nothing about what the thing is. New hardware and new ideas can attach later without changing the core.
- Interfaces are versioned. Nothing is ever removed. A mistake gets a v2 next to the v1.
- The written documentation is the contract, and the compatibility tests come straight from it. A guarantee that is painful to document is probably too complicated to keep.
- Graphics and audio pass shared buffers by reference, so nothing gets copied and there is no message for every frame.
The services behind the API
- Init and service manager. Starts, stops and supervises services behind a stable interface. Nothing else goes around it. What runs underneath is up to whoever ships the system.
- App lifecycle. Launch, suspend, resume and quit. Suspend and resume is hard to get right, and users notice immediately when it is wrong, so it belongs in the core.
- Permission broker and sandbox. Hands out references and confines foreign code through a wrapper for each subsystem. More on this under Security below.
- Subsystem registry. A table that maps each kind of binary to the subsystem that runs it.
- Package format and install rules. What installing, removing and updating mean, defined once and kept stable.
- The C library. A stripped-down fork of musl that talks to the core instead of making raw system calls. A libc is a promise that lasts a decade, and the risk is getting it wrong, not failing to be original. musl is small, permissively licensed and already well tested, and the target is a fraction of glibc’s size.
- Facades over existing parts. For display, audio and networking, wrap what already exists (Wayland, PipeWire, iwd or wpa_supplicant for Wi-Fi, BlueZ for Bluetooth) behind the core’s API first, and replace a layer only when the wrapper cannot hide the problem. New display or audio stacks take years and sink projects.
Left out of the core: GPU drivers, newer or vendor-specific radios
such as cellular, Thread and UWB, Wine and Waydroid, the shell,
theming, default apps, branding and onboarding. Drivers ship in the
kernel bundle, compatibility layers and newer radios are subsystems,
and the look and feel belongs to whoever ships the system.
A company can restyle everything above the API without breaking a
single app, because the window, input and dialog API underneath does
not move.
Getting other projects to use it
Building a common API is the easy part. Getting other projects to use
it is hard. The seatd project is a useful lesson. It gives desktops a
clean interface for something they currently get from systemd’s
logind, and GNOME and KDE still have not made an effort to adopt it.
So this platform does not wait for upstream to cooperate. Its own shell
and native stack are built against the API, and foreign software gets
wrapped. The cost is that the platform has to own more of the system
itself.
Subsystems
Anything that brings its own world with it is a subsystem. Wine and
Proton bring Windows. Waydroid brings Android. They sit next to the
core as recognized parts of the OS and run the way they run on Linux
today, with no porting. The core keeps a registry that maps each kind
of binary to its subsystem, so launching a Windows program starts Wine
automatically. This is close to how Linux’s binfmt_misc hands programs
to Wine today, and how Windows NT handled its own subsystems.
They are not rebuilt inside the core because they are large,
fast-moving projects with their own developers, and a translation layer
like that is never finished. Microsoft tried it with the first version
of WSL and later replaced it with a design built on a real Linux
kernel. Pieces of a subsystem can move into the core later when it is
cheap, for example when a piece is small, performance-critical, or
removes a dependency. Nothing promises the move will ever be complete.
Ordinary programs do not need any of this. They are native, and only
foreign worlds get to be subsystems.
The kernel bundle
The kernel, graphics drivers (Mesa) and firmware ship together as one
tested bundle for each hardware family. That gives one test matrix and
a clear line of support: the bundle and the core belong to the
platform, and everything above belongs to the customer. The core only
asks the kernel for raw mechanisms like memory, scheduling and device
access, so the kernel could be replaced in principle. In practice it is
Linux, because Wine and Mesa expect Linux interfaces.
Why the package model works here
The closest existing model for the package format is the macOS app
bundle: a folder that gets copied into place, leaves the system’s
libraries out, and can hold several executables. AppImage tried the
same idea on Linux and never took off, largely because Linux has no
common set of libraries an app can assume. This platform supplies that
set. A game might bundle SDL2 and nothing else, which keeps packages
small and avoids a heavy runtime. Developers manage dependencies inside
their own app, and OS maintainers keep the userspace sane.
Security
The core idea is that to touch something, a program has to hold a
reference to it. A program that starts with nothing can reach nothing.
Native programs work this way directly.
Foreign binaries are handled differently. The subsystem registry
already knows which subsystem each binary belongs to, so every
subsystem gets a special-built wrapper: code that understands that
subsystem and knows how to protect against its binaries. A Windows
program and an Android app are different kinds of risk, and each
wrapper is written with that in mind. A small sandbox, targeted at
about 5,000 lines of C, does the enforcing with Linux’s own tools
(namespaces, Landlock and seccomp), and access is handed over through
the core.
Security is hard to keep modular because it sits at the center of how
a system works, and Linux’s security primitives are weak. OpenBSD is a
good reminder of how much a small, integrated system can do. Giving
each subsystem its own wrapper is how this design stays modular
without treating every foreign binary the same way.
The organization behind it
The technology will not carry this alone. It needs people who are paid
to maintain it for years, and those people need an employer that makes
money from the result. Volunteers have had about thirty years. In the
Linux world a problem usually gets fixed only when a corporation steps
in and has a reason to fix it. The parts that work best are the ones
where that is true: the kernel has paid engineers, Mesa is funded
largely by Valve, Intel and AMD, and Android has Google.
The proposal is to do what Google and Microsoft do and pay
professionals to maintain the unglamorous parts of the OS, but split
the work across many small companies instead of one big one. It would
be a decentralized network, not a monolith.
- About a dozen independent companies. They all share one tiny core, so a company can be as small as five people.
- Each one sells a stable OS platform that its customers brand as their own. The customers are hardware and device makers who need an OS but do not want to run an OS business.
- The core is the only thing the companies share. They agree to stay compatible with it.
- Everything else belongs to each company: name, trademark, products, hardware focus, support and pricing.
- The customer keeps their own product, brand and interface. The company maintains the platform and promises compatibility for ten years.
The whole structure depends on the core staying small. If the core
needs more than about five people to maintain, the model stops working.
That is why so many of the choices above favor taking a part from
upstream, wrapping what exists, or pushing something out to a
subsystem.
Companies already pay for Linux maintenance. Red Hat and Canonical sell
it for servers, and for specialized desktops and devices such as
studios and embedded systems. The method is the same everywhere:
freeze a narrow stack and keep patching it. That works when the
workload stays fixed. It does not work for a general desktop user whose
hardware and apps keep changing, and nobody has a model for that case.
This plan sells a stable interface that holds while the parts
underneath keep changing.
The funding problem is real. The companies have to be profitable, and
each one has to work out how to get there. Customers pay once they
believe something works, and it only works once somebody has built it,
so the early months have to be paid for before revenue arrives. Likely
buyers are companies like Valve and System76 that want an OS but do not
want to spend the time and money to build and maintain one. How a
company covers the early period is its own decision. It could be an
anchor customer, contracts, hardware sales or something else.
Limits of this design
- Linux is still the kernel in practice, because Wine and Mesa expect its interfaces.
- Each subsystem needs its own security wrapper, so adding a subsystem also means writing and maintaining one.
- The size targets (about 5,000 lines for the sandbox, a C library at a fraction of glibc) are estimates, not measurements.
- Which Lisp dialect to use is still undecided.