Decision — one repository, install profiles
Status: accepted 2026-10-12; amended 2026-10-12 after review. AGENTS.md
§2.3 (identity and metadata), §2.5 (Mix components) and §10 (the
checklist) are amended in the same change that adds the profiles field
and its gate.
The question
MixOS has two audiences: a desktop (the compositor, the apps and their settings services) and servers (mail, web, blobs, DNS and the mesh daemons on headless nodes). The server programs have not moved into this repository yet. Should they get a repository of their own?
The decision
-
Everything stays in this repository and its one Cargo workspace. Desktop, server, command-line and library code alike. There is no second MixOS source repository for server programs.
-
The split is an install split, not a source split. Every shipping component declares the install profiles that include it:
Profile Meaning baseOn every MixOS system: the shell and the Bus node serverWhat a headless node may run desktopThe desktop session: compositor, apps, their services and the session's own wiring -
Composition.
serveranddesktopeach includebase: resolving either one yields its own components plusbase's. A system may install both. A resolved set is the union of its profiles, each component once. -
Who declares. A component's principal package declares
profiles: the package whose name is its component (the directory underapps/,services/orcli/), or a Mix component'scomponent.mx. The list is non-empty, has no duplicates and names only the three profiles. The field is absent on libraries, on a component's private crates (whateverkindthey inherit from their owner), and ontoolandtestpackages. Libraries are never installed on their own; they are linked into what needs them. The session's targets and wiring (session/) belong todesktop; when thesessionkind lands, it declaresprofileslike any other shipping component. -
Where it is declared. A Rust component adds
profilesto its[package.metadata.mixos]:[package.metadata.mixos] component = "webd" kind = "service" layer = "core" contract = "public" profiles = ["server"]A Mix component adds the same
profileslist to itscomponent.mx. -
Naming. Install profiles are unrelated to Cargo's build profiles (
release,release-fast) and to any service's own settings profiles. Prose says "install profile"; tools take--install-profile, beside--cargo-profileand--target, never a bare--profile. -
The profile gate is new, workspace-wide work. Today's closure and layering gates check compd's own crates only; they do not protect other components. The profile gate:
- refuses the declaration errors above;
- refuses a
baseorservercomponent that itself declares thedesktoplayer; - resolves the normal and build dependency closure of each
baseandserverprincipal, for every feature set its profiles build, and refuses anydesktop-layer workspace package in it, and any upstream GUI crate (smithay, egui, eframe, wgpu, winit, Bevy and the like) even though those carry no MixOS metadata; - for a Mix component, which has no Cargo closure, refuses a
desktoplayer in abaseorservercomponent; what it calls on the Bus is checked at review, as AGENTS.md §2.5 already says.
The component index (
/docs/components/) shows each component's profiles. -
Resolving, installing and activating are separate.
- Resolve: one tool answers "what is in
server?" from the metadata, deterministically. Its output covers the packages to build, the Mix components, and each component's runtime artefacts: units by class (system or session),sysusersandtmpfilesfragments, and assets. Build scripts, images and installers never keep their own lists. - Install: places those files. Rust components go to an installer
that gives binaries and units the record, rollback and drift check
tools/component_install.mixgives Mix components (separate work that follows this decision); Mix components go to that existing installer. - Activate: which services a particular node runs, with what
configuration, is that node's decision, not the profile's. Installing
servermakes a service available; it does not start every server on every node.
- Resolve: one tool answers "what is in
The first assignment
| Component | Profiles |
|---|---|
mix-shell (the mix binary), noded | base |
compd, settingsd, portald, editd, releasesd, busviewer, ced, dopus, prefs | desktop |
server starts empty: the server programs join it as they move in (mail,
web, blobs and the rest). releasesd is a per-user session service and
editd serves the session's editors, so both are desktop. A headless
mode for either would be its own decision, with suitable supervision,
before it joins server.
Why
- The two sides share their foundation. The Bus, noded, Mix, the config and props libraries and logging sit under both, and they change almost daily. A second repository could pin them as Git dependencies rather than wait for releases, but every foundation change would still mean a coordinated upgrade on the other side, and consolidating duplicated code across the boundary would mean moving it between repositories.
- Changes cut across both. The verb registry's move to classes changed the registry, its gate and every application in one commit. Across two repositories that is coordinated pull requests that can drift apart.
- The separation that matters can be enforced here. Headless code
never needing a GUI stack is a property of a dependency closure, and the
profile gate checks exactly that for every
baseandservercomponent. A repository boundary would enforce it less precisely: it says nothing about which features or which programs a node gets. - This was decided once already. The source layout decision rejected one repository per component so that one dependency graph can be gated, shared code consolidated, and changes made atomically. A desktop/server split is the same proposal in two pieces, and nothing it relied on has changed.
- What people actually need separated is the build and the install. A mail server should never compile or install the compositor. Profiles say that precisely, from data, in one place.
Alternatives
- A second repository for server programs. The strongest case for it: an independent lockfile, toolchain and release pipeline, so desktop dependency churn never touches a server build and a server fix never waits on the desktop. That isolation is real; it loses to the shared, fast-moving foundation and the consolidation still to do. A split would be the right call if the server side ever needed a different licence, private code, its own team or its own release cadence. None of these applies. Private deployment detail already lives outside this repository.
- Cargo features or a second workspace in this repository. Rejected. A second workspace means two lockfiles and two dependency graphs, which is what AGENTS.md §0 rules out. Features select code inside a crate, not which programs a system gets.
- A top-level
server/directory. Rejected: the top level is classified by kind only (AGENTS.md §1). A web server is a service like any other; which systems it belongs on is data.
Consequences
- One
Cargo.lock: a dependency bump for the desktop also moves the servers' resolved versions, and a server fix ships from the same tree as that day's desktop work. Each component's tests and the gates catch a break; the alternative was version drift between repositories. - Building a server set compiles only that set's closure (
cargo build -p), so headless builds stay small. A server build host still checks out the whole tree, desktop vendor forks included. - AGENTS.md §2.3 and §2.5 gain the
profilesfield, and §10's checklist asks for it.