# 0032: Optional modules

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

Source: https://wolos.org/reference/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 other desktops ship their developer and studio suites as installs, not as the base. An MIT-licensed desktop configuration project (credited in `docs/THIRD-PARTY.md`) 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 that project 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](/reference/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 in one approval (the "Adobe kit" idea: the Kits concept is new). **Planned, not in v0.12:** a kit tile that installs a module; the shelf holds only `wol_core::store::KITS` (Media Codecs, Craft Suite), and modules install from the installer and Settings > Modules.
- **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) is to ship as part of ai-claude, pre-wired so memory survives sessions (planned, accepted: not in v0.12, see below). **Planned for 0.13, not in v0.12:** `wol jail run`, the workspace jail and the "Ask before every action" switch are not implemented (no `jail` route in `crates/controller/src/cli.rs`), and ai-claude does not ship the memory tool yet. In v0.12 an agent runs as the person, unjailed, and changes WolOS only through `wol mcp` and the gate (the bullet above); the managed settings deny the escalation tools.
- **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. **Planned for 0.13, not in v0.12:** there is no `codecs` record (`wol_core::modules::IDS`, `files/usr/share/wolos/modules/`). It is not a data-only entry: it needs a `media` category, its `wolos-module-codecs` meta package, and the Microsoft fonts are no Arch package, so under the rule above they become a user-side recipe first.
- **Agent-generated extensions** stay in the existing sandboxes: Lua widgets and [ADR 0031](/reference/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](/reference/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](/reference/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 the community package collection. 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.
