# 0032: Optional modules

> AI, developer, creative and console stacks as removable modules and Kits.

Source: https://wolos.pages.dev/manual/adr-0032

## Context

WolOS ships one desktop for everyone and installs drivers online at first boot. People who code or create want Claude Code and other agents, local models, toolchains, editors, Blender-class apps and a serious console, the way macOS ships Xcode or Final Cut as installs, not as the base. Omarchy sets the UX bar (one menu, lazy mise installs, an agents panel, everything themed) but runs agents auto-approving and plugins unsandboxed. WolOS already has what Omarchy lacks: every agent action can pass `wol mcp` -> helper policy -> on-screen approval -> audit (`crates/controller/src/mcp.rs`, `approve.rs`, `crates/core/src/automation/`). The base must stay small and offline-installable; modules must never be mandatory and must stay pinned to the release.

## Decision

- **A module is one meta package** `wolos-module-<id>` in wolos-pkgbuilds (`depends=` on snapshot-pinned Arch and `[wolos]` packages, plus `/usr/share/wolos/modules/<id>.json` and config drop-ins), listed in `packages`. Anything outside Arch or `[wolos]` (Claude Code, PowerShell, Craft launcher, DaVinci) is never a pacman dependency: the module's `enable` step installs it as the user through wopkg (recipe or AppImage, [ADR 0021](/manual/adr-0021)) or mise, so the sealed system stays sealed and no root is needed.
- **Registry** `/usr/share/wolos/modules/<id>.json`: `{id, name, summary, icon, category, size_mb, packages, flatpaks, recipes, mise, post, offline, needs}` read by `wol_core::modules`. Verbs: `wol modules list|status` (Read), `wol modules install|remove` (Admin, new capability `modules`), `wol modules enable` (Change, `apps`). pacman is the truth for packages; `$XDG_STATE_HOME/wolos/modules.json` holds per-user enablement.
- **Three doors, one registry**: (a) installer `Step::Modules` between Account and Summary (checkboxes, none pre-ticked, a "Downloads N MB" badge, `Plan.modules`); `install-run` installs ticked modules in its existing online step and queues the rest in `/var/lib/wolos/modules-pending`, which `firstboot` drains like the NVIDIA driver; (b) Settings > Modules (toggle rows, same verbs); (c) App Store > Kits shelf, where a kit tile installs a set of apps or a module in one approval (the "Adobe kit" idea: the Kits concept is new).
- **Agents act through WolOS policy.** `wolos-module-ai-claude` ships `/etc/claude-code/managed-settings.json` allowing the `wol mcp` server (not exclusive, so people keep their own servers), and its `enable` step runs `claude mcp add --scope user wolos -- wol mcp` per user. Agents change WolOS only through `approve::decide`; every action appears in Settings > Assistants & Automation. The gate itself does not change.
- **Productive by default, jailed by default** (lead, 2026-11-01: "AI needs to be productive; the system needs to disappear; not too limiting, also for lazy people"). The WolOS agent runs in a *workspace jail* that needs no configuration: a per-project directory tree (default `~/Work/<project>`), its own `$XDG_*` dirs, network allowed, no access to the rest of `$HOME`, implemented with Landlock + seccomp + a mount namespace by `wol jail run` (the technical model is github.com/akitaonrails/ai-jail, whose UX we do not copy). Inside the jail the agent does not ask: file edits, builds, tests, reports and MCP calls to other servers are approved by the jail itself. One approval sheet appears only when the agent reaches outside: another directory (the sheet offers "add this folder to the project"), a system change through `wol` (the existing gate), or a credential. Settings shows one switch, "Ask before every action", off by default. `kleedolinux/claude-infinite` (the lead's Claude memory tool) ships as part of ai-claude and is pre-wired so memory survives sessions.
- **Proprietary formats one click away, never default**: a `codecs` entry in the module registry (ffmpeg full, gst-plugins-ugly/bad, libdvdcss, Microsoft fonts) offered by the Store and by the "can't play this file" sheet of the media apps; not on the ISO, not pre-ticked in the installer.
- **Agent-generated extensions** stay in the existing sandboxes: Lua widgets and [ADR 0031](/manual/adr-0031) plugins. The ai-claude module ships a skill (`/usr/share/wolos/agents/skills/wolos/SKILL.md`, linked into `~/.claude/skills` and `~/.agents/skills`) documenting the `wol` verb tree, the plugin manifest and `wol plugin check`, so an agent can author and self-check a plugin without root. Installing a plugin stays a Change capability with an approval sheet.
- **Pinning.** pacman dependencies are pinned by the snapshot. mise ships `/etc/mise/config.toml` (global-only: `lockfile=true`, `disable_backends=["asdf"]`, `paranoid=["plugins","provenance"]`, no trusted project paths, python shims excluded) and a per-release `mise.lock`; its registry is pinned by the `mise` package version (`registry_floating=false`). Two vendor self-updaters are the deliberate exceptions, both user-side and vendor-signed, both labelled "updates on its own" in Software Update: Claude Code (stable channel, release manifest signature checked by the recipe) and the Craft launcher.
- **Security coexistence** ([ADR 0024](/manual/adr-0024), xguard): mise, Claude and wopkg write under `/home`, which xguard scans on first exec. The dev module adds a mise postinstall hook that runs `wol-xguard scan` on the install directory and tags files like a browser download. Interpreters stay the known gap and the copy says so. Models are data and are not scanned.
- **GPU for local AI** reuses the hardware catalog: `wol hw compute --json` returns `{backend: cuda|rocm|vulkan|cpu, vram_mb, gpu}`; `wolos-module-ai-local` depends on `ollama` (CPU) and installs the matching backend package (`ollama-cuda` for NVIDIA Turing+, `ollama-rocm` for AMD gfx9+, `ollama-vulkan` otherwise). Settings shows the backend badge and a model picker sized by VRAM.
- **Sessions** mean re-openable work: `{workspace, cwd, kind: terminal|agent, command, title}` saved by wol-shell at logout to `$XDG_STATE_HOME/wolos/sessions.json`; at login the shell offers "Reopen windows from last time?" and relaunches each terminal (`foot -D <cwd>`, zellij attach) and agent (`claude --continue`) in its workspace. Multiplexing inside a boot is zellij, themed by `wol theme`. No process survives a reboot; layout, place and the conversation pointer do.
- **Console**: zsh stays the default login shell. One prompt engine, starship, themed from the tokens (`toolkits/starship/palette.toml` written by `wol theme`), with the pure-zsh prompt kept as fallback. fzf history (Ctrl+R), zoxide, eza, bat, fd, ripgrep wired behind `command -v`. fish, nushell, bash and PowerShell (wopkg recipe from Microsoft's tarball; no Arch package) get the same keys, colours and history size from the same tokens. Settings > Modules > Console chooses the default shell and terminal (foot default; ghostty, kitty optional).
- **Developer**: mise for every runtime (never pacman), git, gh, docker or podman as a choice (docker group opt-in), Code-OSS or Zed themed by `wol theme`, lazygit and btop as floating TUI apps, `~/Work`.
- **Creative**: the Arch creative set (Blender, GIMP, Krita, Inkscape, OBS, darktable, Kdenlive, Ardour, Audacity), fonts, colord with per-display ICC, a switchable low-latency PipeWire profile (`wol audio profile low-latency`, Control Center toggle), DaVinci Resolve as a user-side wopkg recipe on NVIDIA/AMD only.
- **gpu-unlock is kernel work, not a module** (lead's call): `Zagitta/gpu-unlock` (GPL-2.0, out-of-tree, lifts NVIDIA GeForce tensor-core FP16/BF16/TF32 half-rate caps before the stock driver binds; Ampere stable, Ada/Turing experimental) is carried in-tree on `wonderlandos/linux` `wolos-7.3` as `drivers/gpu/wolos_gpu_unlock/`, `CONFIG_WOLOS_GPU_UNLOCK=m`, off by default. The NVIDIA firmware it depends on is never redistributed: `wol hw boot --online` fetches it into `/usr/lib/firmware/wolos/gpu-unlock/` and the module loads it with `request_firmware()`. Built in-tree it is signed like every module ([ADR 0025](/manual/adr-0025)). Settings > Hardware gets an experimental toggle writing `/etc/modprobe.d/wolos-gpu-unlock.conf`. It is a driver, recorded in SERIES.md as such, exempt from the "under 100 lines" patch rule.
- **Craft Suite (storytold) is a Store kit, not a module**: the `craft-launcher` AppImage (Apache-2.0/MIT, alpha, self-updating) installed user-side through wopkg with a pinned key, xguard-scanned; the launcher installs the Craft apps under the user. Not on the ISO.

## Consequences

the base ISO does not grow. Every module is removable with `pacman -R wolos-module-<id>` plus `wol modules remove` for user-side parts. Each module gets a contract test in wolos-pkgbuilds/checks (dependencies exist at the snapshot) and a VM check in wolos-iso/tests. Rejected: everything on the ISO (3 to 6 GB more, not optional); Flatpak for everything (no CLI tools, no GPU compute, no mise); exclusive `managed-mcp.json` (blocks people's own servers); asdf plugins in mise (arbitrary shell); gpu-unlock as unsigned DKMS from AUR. Open for the lead: starship for all shells vs hand-written prompts; whether the two self-updaters are acceptable; the maintenance cost of carrying gpu-unlock in-tree against a small upstream.
