System
Architecture
A machine is a typed value. The flake turns it into a list of modules and a set of special arguments, hands both to its platform's builder, and installs Home Manager inside. Everything else, from packages to live configuration, hangs off that one path.
Two flakes, one repository
| Flake | Holds | Visibility |
|---|---|---|
collective (root) | The registry and machine directories, private modules for every platform, local packages (cltv, the fleet daemon, login helpers, gateway tooling), overlays, scripts, live configuration in standalone/ and encrypted secrets. | Private |
collective-public/ | Reusable modules (supervisord, the Nix-on-Droid service stack, keyboard layouts, handheld daemon), Android helper packages, a Python overlay and collective-lib. | Public: collective-public |
The public flake lives in the same repository and is consumed as a path input, so a change to both lands in one commit, and cltv public push publishes it to the public mirror as a Git subtree. collective-lib's parser and evaluator moved to their own public flake, nix-reflect. Each of the two takes the other as an input, and follows = "" breaks the cycle.
How a machine is assembled
The function that does this, modulesFor, dispatches on the system type rather than on machine names, apart from a short list of machine-specific modules. Platform trees are attribute sets (name = import ./name.nix), merged with the public flake's and then flattened, so the public and private trees can each define modules without colliding. Two details make the matrix evaluate everywhere:
- Masking, not failing. The gaming module imports an x86-only flake. On aarch64 and darwin the module becomes an option-only stub and that input is filtered out of
inputs, so options set elsewhere still resolve. - Per-platform package sets. NixOS and darwin use unstable nixpkgs; Nix-on-Droid uses a pinned revision until an upstream issue is fixed. Home Manager receives the system's
pkgs, so overlays apply once.
Special arguments
Every module, system or home, can take these as arguments:
| Argument | Is |
|---|---|
inputs, outputs | Flake inputs (filtered per architecture) and this flake's outputs |
constants | Name, username, UI sizes, time zone, locale, Nix features and constants.registry |
systemName, systemType, systemArchitecture, systemTags | The typed identity of this machine |
hasSystemTag | A predicate: hasSystemTag WSL |
configType | ConfigType.System or ConfigType.Home |
collectiveEnvironment | The environment this machine exports |
collective-lib, typed, untyped | The library, and its typed and fast untyped views |
pkgs-stable, x86Pkgs | The stable package set, and x86 packages from any architecture |
getHomeConfig, mkHomeManagerSpecialArgs | Reach the home configuration from system modules, and build Home Manager's arguments |
The module tree
| Directory | Files | For |
|---|---|---|
modules/agnostic | 6 | Every system type: the daemon, FlakeHub, fonts, shell, SSH |
modules/home-manager | 103 | Every home: agents and AI, terminals, editors, window managers, Git and CI runners, local inference, liveness |
modules/nixos | 38 | NixOS: devboxes, prebuild service, graphics, gaming, audio, firewall, runners, the model gateway |
modules/nix-darwin | 17 | macOS: Linux VM guests, runners, audio, window manager, gateway |
modules/nix-on-droid | 8 | Android: bootstrap, SOCKS tunnel, HTTPS listener, VNC |
collective-public/modules | 37 | Public: supervisord, Termux, keyboards, handheld daemon |
Modules can carry their own tests. The trees are merged with tests.modules.withMergedSuites, exported with tests as testedModules, and stripped of them as modules for consumers.
Packages and first-party tools
The flake has about seventy inputs. Roughly thirty are the owner's own projects, among them the agent orchestrator, its node daemon, the shared context store, the session host, desktop control, terminal tools and work integrations. Each reaches a machine by the same route.
Flake input
The project is an input that follows Collective's nixpkgs where it can. Inputs marked
cltv:always-updateinflake.nix(31 at this revision) are advanced together or one at a time:cltv nix update-inputs --input NAME --no-publish# update only NAME,leave flake.lock uncommitted for review Overlay
overlays/default.nixmaps the input's package into the configured set, adapting it where needed: one tool is rebuilt against Android's pinned nixpkgs because a newer glibc breaks terminal calls under proot.Bootstrap category
modules/home-manager/bootstrap.nixselects about two hundred packages in named categories, each with an architecture selector:_all,_x86,_desktop,_mobile,_nonMac,_mac, and flags such as_workOnlyand_bareMetal.Try it, then switch
cltv runruns any package from the configured set at the checkout's pins without installing it. The operator's next switch installs it.cltv run tools#aztts -- --helpcltv run tools -b aztts -- --help
Several nixpkgs pins coexist, each with a reason recorded beside it: nixos-unstable for the system; a forward pin for packages that should update without a system update; a separate pin for the GitHub Actions runner, which goes stale faster than everything else; release-25.05 for cherry-picks; a fixed revision for Nix-on-Droid; one for the model gateway's Python package; and one for downgrades.
Live configuration outside the store
Home Manager normally copies configuration into the Nix store, so every change needs a switch. Configuration that agents and tools edit while running lives in standalone/ instead (35 directories) and is linked with mkOutOfStoreSymlink, about fifty links from nine modules:
home.file.".pi/agent/settings.json".source = mkOutOfStoreSymlink "${standaloneDir}/settings.json";home.file.".pi/agent/skills".source = mkOutOfStoreSymlink "${standaloneDir}/skills";
Edits take effect at once and are committed like any other change. The coding agent's settings, skills, extensions and prompts, the model gateway's configuration, account maps for GitHub tokens, and per-tool configuration for the workspace manager, chat bridges and agent services all live there.
~/collective. Everything that must work without one stays in the store.The environment every machine exports
Each configuration exports its identity, so scripts never guess what they are running on:
| Variable | Value |
|---|---|
__COLLECTIVE_SYSTEM_NAME | The machine's registry name |
__COLLECTIVE_SYSTEM_TYPE | nixos, nix-on-droid or nix-darwin |
__COLLECTIVE_SYSTEM_ARCHITECTURE | x86_64-linux, aarch64-linux or aarch64-darwin |
__COLLECTIVE_HOME_ONLY | 1 for standalone Home Manager, else 0 |
__COLLECTIVE_FLAKE_LOCAL_PATH | $HOME/collective#name |
__COLLECTIVE_FLAKE_STORE_PATH | The same flake pinned in the store |
__COLLECTIVE_FLAKE_GITHUB_PATH | The same flake on GitHub |
__COLLECTIVE_NIX_REBUILD_CMD | The platform's rebuild tool, with sudo --preserve-env=SSH_AUTH_SOCK where needed |
__COLLECTIVE_NIX_SWITCH_CMD | The full switch command, with --option accept-flake-config true |
COLLECTIVE_DIR, COLLECTIVE_STANDALONE | The checkout and its standalone/ |
COLLECTIVE_SECRETS_YAML | The encrypted secrets file |
COLLECTIVE_SYSTEM_SECRETS, COLLECTIVE_HOME_SECRETS | /var/run/secrets and $HOME/.config/sops-nix/secrets |
__COLLECTIVE_DEBUG | Set at run time by cltv debug: verbose cltv logging |
A few network constants for well-known hosts are exported too. SSH_AUTH_SOCK survives sudo because private inputs are fetched over SSH during evaluation, and the accepted flake configuration means a switch can use the caches it is about to install.