Skip to main content

NixOS (4): Maintaining Configuration with AI — The Maintainer Skill

·1860 words·9 mins
Jin Li
Author
Jin Li
Fate lies within the lightcone.
NixOS Series - This article is part of a series.
Part 4: This Article

Motivation
#

Post 1 made the core reason for our return to NixOS explicit: the division of labor changed — the human decides “what I want”; the AI translates it into correct Nix code and verifies it. Posts 2 and 3 covered NixOS’s mechanisms and how our repository is organized. This post is about maintenance itself: when the AI takes a configuration change, what exactly does it do, in what order, and where are the hard boundaries it must not cross.

The answer is a written-down workflow document: /home/lijin/nixos-config/.codex/skills/nixos-config-maintainer/SKILL.md. It is a “skill” — a prompt file for the AI agent — and the agent follows it whenever the task touches NixOS modules, host hardware, kernels, services, packages, flake inputs, or the README and generation notes. Its job is to turn the day-to-day change workflow from post 3 into an executable, checkable contract with explicit prohibitions: not “it would be good to do this roughly”, but “step one is this, validation uses exactly these two commands, and this state does not count as done”.

The document is short (a little over a hundred lines), but every clause corresponds to a real scenario. Below I walk through it section by section, with the design rationale; all quotations come from the file itself.

Prerequisites
#

What the Skill File Is For
#

The skill file opens with the condition under which it applies, and that sentence is itself a routing rule:

Maintain this repository’s multi-machine NixOS flake and its operational documentation. Use when changing NixOS modules, host hardware, kernels, services, packages, flake inputs, README instructions, generation notes, or when validating, committing, and syncing configuration changes.

Note that it puts “changing configuration” and “changing documentation” in the same skill. This is not laziness: in our repository the README and the generation notes (build notes) are as important as the configuration code — one wrong line in a document and the AI, next time it picks up maintenance, acts on the wrong context. So “maintaining documentation” must be held to the same validation and commit discipline as “maintaining configuration”.

Repository Model: Pinning Down the Scopes
#

The first section of the workflow is the “Repository model”, which restates the three scopes from post 3 as hard rules. I’ll only quote the clauses the skill adds beyond that:

  • A host may only select its own hardware module: “A host must not inherit another host’s hardware module.” The current Surface host is named surface-pro-6 and uses nixos-hardware.nixosModules.microsoft-surface-pro-intel. This rule guards against the classic AI accident of “helpfully” reusing the Surface module when adding a new machine.
  • Keep the nixos compatibility alias unless the user explicitly asks for its removal.
  • Each machine is rebuilt with its own flake target: .#surface-pro-6 on Surf, .#halo on Halo. The AI is not allowed to “fix one machine and casually rebuild another” in the same session.
  • flake.lock is tracked configuration: updating it is a deliberate act that requires a reviewed diff, never a side effect of a failed build.

The Standard Workflow: Eight Steps
#

This is the core of the skill file; the original numbers the steps in order. I group them three ways.

Before touching anything: inspect and classify (steps 1–3)
#

Step 1 is read-only inspection. Before editing any file, the agent must:

  • check git status (are there uncommitted changes to preserve);
  • read the relevant host/module files;
  • look at recent Git history;
  • look at the current NixOS generation profile.

The last point deserves expansion: /nix/var/nix/profiles/system-*-link records every generation that was actually activated on this machine. The AI looks at “what is actually running here” before changing configuration, so it can tell “the configuration in the repository” apart from “the configuration on the machine” — the two can differ by several commits, especially after a failed build.

Step 2 is classification. Every setting to be changed is assigned to one of three classes: shared (modules/common.nix), host-specific (hosts/<host>/configuration.nix), or generated hardware data (hosts/<host>/hardware-configuration.nix), with the instruction to “Place it in the narrowest appropriate scope.” This is the step where the AI most often errs — writing a Surface-only policy into common.nix, or duplicating a shared policy into every machine. Stating the rule in one sentence works better than three paragraphs of explanation.

Step 3 is the minimal change. “Make the smallest declarative change.” For a new machine the fixed actions are: create its host directory and hardware file, add a separate nixosConfigurations.<name> entry, and do not import the Surface-specific modules.

After the change: docs, validation, commit (steps 4–7)
#

Step 4 is keeping the README in sync. Whenever commands, host names, directory structure, activation steps, verification, rollback methods, or hardware behavior change, the README changes with them — and it “Include[s] instructions useful to a newcomer”, i.e. readable by someone who did not participate in the change (including the AI three months from now).

Step 5 is the generation notes. This step has the most specific rules, because it implements the build-notes conventions from post 3. The skill requires recording each real generation in a fixed format:

1
2
3
4
5
6
## Generation N

- **Time:** YYYY-MM-DD HH:MM
- **Git commit:** `short-id`
- **Main changes:**
  - ...

Two sources must be consulted, and both:

  • Git history: git log --reverse --date=iso-local --format='%h|%ad|%s';
  • generation links: /nix/var/nix/profiles/system-*-link, including their link timestamps and kernel target. If nix-env --list-generations needs a root lock and cannot run, use the profile links to infer — and state clearly that it is inference.

Then come three prohibitions, all about “do not fabricate”:

  • never invent a generation;
  • never claim an unactivated configuration is running;
  • a commit that only changes documentation or tooling goes under Git-only follow-up changes and does not consume a generation number.

Step 6 is validation, with exactly two required commands:

1
2
git diff --check
nix flake check --no-build --show-trace

git diff --check catches whitespace errors and conflict markers; nix flake check requires that every intended nixosConfigurations.<name> evaluates. The skill also requires: for kernel, bootloader, filesystem, or hardware changes, tell the user the first real build may be long and requires a local activation/reboot — and do not claim the running system has changed until it has been verified. This clause operationalizes the insight from post 1: NixOS’s “declarative + verifiable” nature, in the AI’s hands, becomes an executable rule rather than a slogan.

Step 7 is commit discipline:

  • review the complete diff, including renames and deletions;
  • stage only the files belonging to the task;
  • keep the commit message concise;
  • preserve the repository’s existing Git author identity — if it is missing, ask the user rather than inventing credentials;
  • if the build notes need the new commit ID, create the configuration commit first, then a follow-up commit for the release note (the order cannot be reversed, or the note would cite a hash that does not exist).

Synchronization: step 8
#

Step 8 is pushing: only when the user requested synchronization or the task explicitly includes it. Use the authenticated gh/Git environment; verify the branch is clean and tracks origin. This is mostly a hint for sandboxed agent environments — pushing sometimes needs credentials, and the skill explicitly says “never ask the user to paste a token and never print token contents”.

Activation Handoff: Where the AI’s Boundary Is
#

The skill has a dedicated “Activation handoff” section. Its core principle: the AI prepares the commands; the human executes the activation. For the current host, the AI hands over the matching flake target:

1
2
3
4
cd /home/lijin/nixos-config
sudo nixos-rebuild switch --flake .#surface-pro-6   # on Surf
sudo nixos-rebuild switch --flake .#halo            # on Halo
sudo reboot                                         # only when required

Post-reboot verification is also per-machine. The Surface IPTS touchpad driver is a template unit, so it is checked with:

1
2
uname -r
systemctl list-units --all 'iptsd@*' --no-pager

On Halo, container-backed services (such as the ROCm inference container q38rocm):

1
2
uname -r
systemctl status docker-q38rocm.service

One counter-intuitive detail: the skill explicitly warns not to require the kernel name to contain surface — the configured Surface kernel may report only a plain version number, like 6.19.8. This is the assertion verification scripts get most wrong.

Documentation of the rollback path is also part of the standard handoff, in case activation fails:

1
sudo nixos-rebuild switch --rollback

Failure and Safety Rules: Four Guardrails
#

The final section lists the “Failure and safety rules” — all of them things the AI is not allowed to do:

RuleWhat it prevents
No destructive Git commands such as reset or checkout unless the user explicitly asksthe AI “casually” discarding the user’s uncommitted work
Do not modify /etc/nixos while this repository is the selected flake sourceconfiguration drift between the two sources (post 3 explains the deliberate separation)
Never hand-edit the generated hardware files; regenerate and review only for the corresponding hostnixos-generate-config describes this machine’s hardware
If evaluation fails, fix and re-validate before committing; if a build is too expensive to finish, report honestly that evaluation passed but a real build remains pendingconfusing “it compiles” with “it runs”

The last one fits AI collaboration especially well: compiling the Surface kernel locally takes hours and tens of GiB of disk. The AI neither has the patience to wait nor should — the correct behavior is to distinguish, and honestly state, the two states of evaluation (seconds, mandatory) and build (hours, possibly pending).

Why a Skill File Instead of a Handbook
#

Looking at the whole design, the difference from “writing a maintenance manual” is:

  1. It is written for the AI, and is organized around the mistakes the AI actually makes. Scope classification, “never invent a generation”, “never claim an unactivated configuration is running” — each clause targets a real failure mode in maintenance tasks, not generic best practice.
  2. It makes validation a hard gate. git diff --check + nix flake check are mandatory before commit, which is post 1’s “machine-verifiable” insight with teeth.
  3. It draws the human–AI boundary. Evaluation, editing, committing, and recording are the AI’s work; sudo nixos-rebuild switch, rebooting, and final acceptance are the human’s; pushing happens only when asked.
  4. It cross-checks against the repository’s own documentation. Generation notes, README, Git history, and profile links corroborate each other — any distortion in one is exposed by the other three. That is what gives “never fabricate” real force.

In one sentence: the AI is good at writing Nix, NixOS is good at backstopping it, and the skill file is what wires the two together. Every mechanism from post 2 becomes an executable step in this post; from post 5 onward we move to the machine-by-machine migration stories.

Related Posts#

NixOS Series - This article is part of a series.
Part 4: This Article