| name | nix-os |
| description | Comprehensive Nix package and configuration management following Determinate Systems best practices. |
| metadata | {"version":"1.0.0","author":"zk::anticompiler","category":"development"} |
Nix Package & Configuration Manager
Comprehensive Nix management following Determinate Systems best practices and this repository's patterns.
Core Architecture Components
1. agent-skills-nix (Framework)
The primary orchestrator for Declarative Skill Management.
- The Concept: Skills are self-contained directories with a
SKILL.md (instructions/YAML frontmatter).
- The Mechanism: Uses a Home Manager module to "sync" selected skills into agent configuration directories (e.g.,
~/.claude/skills).
- Key Feature: Supports Markdown Transformations. You can inject local paths to binaries (like
jq or curl) directly into the SKILL.md so the agent doesn't have to guess where they are or download them, saving context tokens.
2. llm-agents.nix (Distribution)
A daily-updated collection of Nix packages for AI agents.
- Provides ready-to-use derivations for
claude-code, codex, amp, and more.
- This is your source for the actual agent binaries.
3. khanelinix (System-Level context engine)
Beyond just "skills," this repo is a masterclass in Nix Meta-Programming for AI context.
- The Transformation Engine: Uses custom Nix logic (
lib.importSubdirs + mapAttrs) to translate a single source of truth for agents and commands into:
- Claude Code: Multi-part Markdown with YAML frontmatter.
- Gemini CLI: JSON-friendly descriptors and prompt blocks.
- OpenCode: Specialized frontmatter for tool permissions (bash, edit, write).
- MCP Integration: Directly imports
mcp-servers-nix, treats MCP tools as system-level resources available across all host archetypes.
- Flake-Parts Partitioning: The entire system is modularly partitioned. You can swap neovim flavors (
khanelivim) or shell environments without breaking the core system logic.
Instructions
0. Required Workflow
Every config change follows this sequence — no shortcuts:
- Discover — use
mcp-nixos to find correct package attr or option path. Never guess.
- Inspect — read the target file before editing (
inspect_state or Read tool).
- Patch — edit the module. Minimal diff.
- Validate — targeted eval before deploying (see §3).
- Apply —
zcli deploy <host> for system switches.
- Report — surface
rollback_generation so user can recover: sudo nixos-rebuild switch --rollback.
On validation failure: extract the first error: line, surface it, stop. Don't retry blind.
System config vs Home Manager:
- System modules (
/mnt/repository/nix-os/hosts/, modules/) → zcli deploy <host>
- Home Manager config →
home-manager build then home-manager switch (no sudo)
1. Understand Repository Context
Check current Nix setup:
- Flake location:
/mnt/repository/nix-os/
- Package management: Declarative NixOS configuration via flakes.
- Update mechanism:
zcli deploy <host>
Read current flake.nix to understand:
- Input sources (currently:
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable")
- Package definitions (buildEnv with ~60+ packages)
- Outputs:
packages, devShells, formatter
- Supported systems: x86_64-linux
2. Package Management Operations
Install New Package
Process:
- Query
mcp-nixos for correct package attribute name
- Add package to appropriate module in
/mnt/repository/nix-os/
- Validate (§3)
- Run
zcli deploy <host> to apply
- Verify package availability
Update All Packages
Process:
nix flake update -C /mnt/repository/nix-os
zcli deploy <host>
Remove Package
Process:
- Remove from flake configuration
- Run
zcli deploy <host> or nh os switch
- Old package remains in store but not in PATH
Note: Garbage collection removes unreferenced packages:
nh clean all
3. Flake Configuration
Modify flake.nix
Common operations:
Add new input:
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# Add new input
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs"; # Prevent duplicate nixpkgs
};
Add host-specific packages:
paths = [
# Universal packages
git gh lazygit
] ++ lib.optionals (config.networking.hostName == "nxiz") [
# nxiz-only
nvidia-vaapi-driver
];
Modify devShell:
devShells = forAllSystems ({ pkgs }: {
default = pkgs.mkShell {
packages = with pkgs; [
fish
nixpkgs-fmt
shellcheck
# Add development tools here
];
inputsFrom = [ self.packages.${pkgs.system}.default ];
shellHook = ''
echo "🐠 Dotfiles development environment"
echo "Ready for development."
'';
};
});
Validate Before Applying
Choose the right tool for the scope of change:
zcli deploy <host> --dry
nix eval /mnt/repository/nix-os#nixosConfigurations.<host>.config.<attr.path>
nix eval /mnt/repository/nix-os#nixosConfigurations.<host>.config.environment.systemPackages --json
nix build /mnt/repository/nix-os#nixosConfigurations.<host>.config.system.build.toplevel --dry-run
nix flake check /mnt/repository/nix-os --no-build
nix flake metadata /mnt/repository/nix-os
On error: extract the first error: line and surface it. Stop. Don't retry blind.
Common issues:
- Package renamed in nixpkgs (e.g.,
du-dust → dust)
- Missing comma in package list
- Invalid attribute path
- Syntax errors in Nix expressions
Update Lock File
When to update:
- Regular maintenance (weekly/monthly)
- Security updates needed
- Specific package version required
How:
nix flake update
nix flake lock --update-input nixpkgs
git diff flake.lock
4. Troubleshooting
Slow Nix Operations
Diagnosis:
nix store info
nix store gc --dry-run
Solutions:
- Run
nix-collect-garbage -d to remove old generations
- Run
nix store optimise to deduplicate files
- Check network connectivity (binary cache downloads)
Package Not Found
Error: error: attribute 'package-name' missing
Solutions:
- Check nixpkgs version: some packages only in unstable
- Search for package:
nix search nixpkgs package-name
- Check if package was renamed
- Try alternative package names
Evaluation Errors
Error: error: ... while evaluating ...
Common causes:
- Syntax error in
flake.nix
- Recursive attribute access
- Type mismatch (string vs list)
Debug:
nix eval .#packages.aarch64-darwin.default.name
nix repl
:lf .
packages.aarch64-darwin.default.name
Lock File Conflicts
Error: error: flake.lock is dirty
Solutions:
rm flake.lock
nix flake update
nix flake check --impure
Generation Issues
Rollback to previous generation:
Use the boot menu on restart, or temporarily switch:
/nix/var/nix/profiles/system-*-link/bin/switch-to-configuration test
6. Best Practices (Determinate Nix Patterns)
Use nixos-unstable Instead of master
Reasoning:
nixos-unstable: Tested, passes Hydra CI
master: Untested, may have broken packages
Current setup:
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
Never Use --impure
Problem: Breaks reproducibility by allowing environment variable access
Correct:
nix flake check
Incorrect:
nix flake check --impure
Exception: Only use --impure if flake explicitly uses getEnv or similar
Pin Dependencies in Lock File
Why:
- Ensures reproducible builds across machines
- Prevents "works on my machine" issues
- Required for CI/CD reliability
How:
git add flake.lock
git commit -m "chore: update flake lock"
Use buildEnv for Package Groups
Pattern in this repo:
packages.default = pkgs.buildEnv {
name = "system-packages";
paths = [ git gh lazygit ... ];
};
Benefits:
- Single derivation for all packages
- Atomic updates (all packages succeed or fail together)
Enable Flakes in Config
User-level config (~/.config/nix/nix.conf):
experimental-features = nix-command flakes
7. Development Workflows
Create New Project Flake
Use repository root as template:
cp flake.nix /path/to/new-project/
cd /path/to/new-project
$EDITOR flake.nix
Or use templates directory:
nix flake init -t .#template-name
Test Flake Locally
Without installing:
nix develop
nix build
nix run .#package-name
Format Nix Code
Using formatter output:
nix fmt
Manual formatting:
nixpkgs-fmt flake.nix
8. Transient Agent Tooling (Daemon Profile)
This skill enables agents to maintain a local Nix profile in a user-provided directory, allowing dynamic installation of tools without requiring system-wide package management or sudo access.
Quick Start: Setting Up a Local Profile
The DAEMON_PROFILE env var is automatically set to ~/.local/state/daemon-profile and its bin directory is added to the $PATH via Home Manager. The agent can use:
nix profile add --profile "$DAEMON_PROFILE" "nixpkgs#git"
The --profile flag stores the profile metadata in that location.
(Note: on older Nix versions, the add sub-command was called install).
Essential Commands for Agents
Search for Packages:
nix search nixpkgs git
nix search "github:user/repo[/branch]" <package-name>
nix search nixpkgs python3 --json | jq '.[].pname'
Manage Daemon Profile:
nix profile add --profile "$DAEMON_PROFILE" "<flake>#<package>"
nix profile list --profile "$DAEMON_PROFILE"
nix profile remove --profile "$DAEMON_PROFILE" 0
nix profile upgrade --profile "$DAEMON_PROFILE" <package_name>
nix profile upgrade --profile "$DAEMON_PROFILE" --all
General Agent Workflow
echo $DAEMON_PROFILE
nix search nixpkgs git
nix profile add --profile "$DAEMON_PROFILE" "nixpkgs#git"
git --version
Important Details:
- Path: The directory containing the profile (
$DAEMON_PROFILE/bin) is automatically in $PATH.
- Immutability:
nixpkgs#git resolves to the current nixpkgs version. Use github:user/repo/ref#package to pin.
- Locking: Only one agent should modify a profile at a time; locking is not automatic.
9. Output Format
When modifying flake.nix:
Use Edit tool for existing files:
- Modify specific sections
- Preserve comments and formatting
- Minimize diff size
Use Write tool for new files:
- Complete flake.nix from scratch
- Include comments explaining choices
- Follow repository formatting style
After changes, always:
- Validate: targeted
nix eval or zcli deploy <host> --dry
- Apply:
zcli deploy <host>
- Report rollback generation to user
Include testing commands:
nix flake check
nix flake show
Repository Patterns
This NixOS mesh repository follows these conventions:
File Structure:
flake.nix - Package definitions and outputs
flake.lock - Pinned dependency versions
hosts/ - Host-specific configuration (nxiz, zrrh, adeck)
modules/ - Shared configuration modules
Package Organization:
Packages grouped by purpose with comments:
paths = [
# Version control
git gh lazygit
# Build tools
gnumake cmake pkg-config
# Programming languages
rustup go python3 deno
# ... etc
];
Common Commands:
zcli deploy <host> --dry - Dry-run eval without applying
zcli deploy <host> - Build and apply to a host
nh clean all - Garbage collect
Mesh Flake Idioms
Patterns this flake uses that agents must recognize and preserve.
Custom my.* Option Namespace
Host identity and opt-in features flow through my.* options defined in modules/system.nix and modules/performance.nix:
# modules/system.nix
options.my.host = lib.mkOption {
type = lib.types.enum [ "nxiz" "adeck" "zrrh" ];
description = "Host identifier — selects per-host blocks in shared modules";
};
# modules/performance.nix
options.my.performance.enable = lib.mkEnableOption "system-wide performance optimizations";
Each host's configuration.nix sets my.host = "<name>"; — every shared module keys off this.
perHost Lookup Tables
Instead of cascading lib.mkIf checks, shared modules declare a perHost attrset and select with h = perHost.${config.my.host}. Found in modules/{boot,networking,storage,nh}.nix:
let
perHost = {
nxiz = { flake = "/mnt/repository/nix-os"; resolvedDns = true; };
adeck = { flake = "/etc/nixos"; resolvedDns = false; };
zrrh = { flake = "/etc/nixos"; resolvedDns = true; };
};
h = perHost.${config.my.host};
in {
programs.nh.flake = h.flake;
services.resolved.enable = h.resolvedDns;
}
When adding host-divergent config, extend perHost rather than sprinkling conditionals.
Home Manager Integrated into NixOS
HM is bound into each nixosConfiguration via home-manager.nixosModules.home-manager in flake.nix. Key settings:
home-manager.useGlobalPkgs = true; # share nixpkgs with system
home-manager.useUserPackages = true; # install to /etc/profiles/per-user/zk
home-manager.backupFileExtension = "hm-bak";
home-manager.overwriteBackup = true; # resolves dotfile collisions on deploy
home-manager.extraSpecialArgs = { inherit inputs; }; # threads flake inputs into HM
HM modules live under modules/home/** and per-host HM entry is hosts/<host>/home.nix.
Inline Derivations in home.packages
Ad-hoc derivations live next to their install site — avoid creating separate files for one-off packages. Found in hosts/{nxiz,adeck,zrrh}/home.nix:
# Prebuilt binary with ELF patch
pkgs.runCommand "nullclaw-2026.3.1" {
src = pkgs.fetchurl { url = "..."; hash = "..."; };
nativeBuildInputs = [ pkgs.autoPatchelfHook ];
} ''install -Dm755 $src $out/bin/nullclaw''
# Go module from GitHub
pkgs.buildGoModule {
pname = "picoclaw";
src = pkgs.fetchFromGitHub { owner = "..."; repo = "..."; rev = "..."; hash = "..."; };
vendorHash = "...";
}
# Shell wrapper with runtime deps
pkgs.writeShellApplication {
name = "fzf-emoji";
runtimeInputs = [ fzf jq wl-clipboard curl coreutils ];
text = ''...'';
}
Rule of thumb: if it's one derivation used in one place, inline it. If it's used across hosts, make it a module with options.programs.<name> (see modules/home/msgvault.nix for the reference pattern: mkEnableOption + mkOption + lib.mkIf cfg.enable).
Overlays for Upstream Fixes
modules/overlays.nix holds all overrideAttrs surgeries. Pattern is always _final: prev: { pkg = prev.pkg.overrideAttrs (old: {...}); }. Real examples:
tumbler rebuilt without libgepub to drop webkitgtk dep
lmstudio rebuilt with patchelf to fix Bun ELF layout corruption
yaziPlugins namespace created from flake = false input source tree
Input-provided overlays are applied per-host in flake.nix via nixpkgs.overlays = [ inputs.nix-cachyos-kernel.overlays.pinned ];.
agenix Secret Flow
Secrets encrypted with recipient SSH keys, decrypted at boot into /run/secrets/:
# modules/user.nix
age.identityPaths = [
"/etc/ssh/ssh_host_ed25519_key"
"/home/zk/.ssh/id_ed25519"
];
age.secrets.github-token = {
file = ../secrets/github-token.age;
owner = "zk"; group = "users"; mode = "0400";
path = "/run/secrets/github-token";
};
Recipients declared in secrets/secrets.nix (per-file publicKeys list). To add a new secret: agenix -e secrets/new.age (after adding to secrets.nix), then reference via config.age.secrets.new.path in the module.
Home Activation + DAG Ordering
HM activation scripts use config.lib.dag.entryAfter [ "writeBoundary" ] to run after file symlinks are in place:
home.activation.ensureLocalBin = config.lib.dag.entryAfter [ "writeBoundary" ] ''
mkdir -p "$HOME/.local/bin"
'';
System activation uses system.activationScripts.<name> = { text = "..."; deps = [ "etc" ]; } — the deps list controls ordering (e.g. SSH host key staging runs after /etc is populated).
mkOutOfStoreSymlink for Live Files
For dotfiles that point at mutable paths outside /nix/store (edited live, not baked into the system closure):
home.file.".pi".source = config.lib.file.mkOutOfStoreSymlink
"/mnt/repository/daemonturgy/pi/.pi";
Use for editable config dirs, large data caches, or anything that shouldn't trigger a rebuild on change.
Flake Outputs (Current)
nixosConfigurations.{nxiz,adeck,zrrh} — host configs
formatter.x86_64-linux — nixfmt-rfc-style (run nix fmt)
devShells.x86_64-linux.default — includes nixd, nil, nixfmt, statix, deadnix (nix develop)
checks.x86_64-linux.{statix,deadnix} — lint gates (nix flake check)
Run nix fmt before committing .nix changes. Run nix flake check --no-build to catch lint regressions cheaply.
Reference Documentation
Quick Reference
Essential Commands:
zcli deploy <host>
zcli deploy <host> --dry
zcli deploy all
nix eval /mnt/repository/nix-os#nixosConfigurations.<host>.config.<attr>
nix build /mnt/repository/nix-os#nixosConfigurations.<host>.config.system.build.toplevel --dry-run
nix flake check /mnt/repository/nix-os --no-build
nix search nixpkgs <package>
nix flake update /mnt/repository/nix-os
nix flake show /mnt/repository/nix-os
nix flake metadata /mnt/repository/nix-os
nh clean all
nix store gc --dry-run
nix develop
nix fmt
nix shell nixpkgs#<tool>