A coding agent is only as good as the procedures you hand it. Out of the box, a model improvises every task from scratch; give it a skill — a written procedure for "do TDD," "diagnose a hard bug," "turn this into issues" — and it stops guessing and starts following a method you trust. I've built up a collection of these, and once you have more than a handful the real problem isn't writing them, it's distribution: getting the same skills onto every machine I work from, version-pinned, without copy-pasting Markdown around.
I solved that with Nix. Here's the setup.
What a skill is
My skills live in a repo (a fork of Matt Pocock's skills, credit where it's due), organized into buckets — engineering, qa, productivity, personal, misc. Each skill is a directory with a SKILL.md: YAML frontmatter naming it and describing when to use it, then the procedure itself.
---
name: diagnose
description: Disciplined diagnosis loop for hard bugs and performance
regressions. Reproduce → minimise → hypothesise → instrument → fix →
regression-test. Use when user says "diagnose this" / "debug this"...
---
# Diagnose
...
The description is load-bearing — it's what the agent matches against to decide whether the skill is relevant. A few I reach for constantly: tdd (red-green-refactor), diagnose (the loop above), to-prd / to-issues / triage (turning a vague ask into tracked work), and write-a-skill (the skill that writes more skills). Small, composable, model-agnostic. No framework owning my process — just procedures I can read, edit, and trust.
Three ways to install them
The repo supports three distribution paths, in increasing order of how much I actually rely on them.
1. npx, for anyone. The zero-commitment path:
npx skills@latest add parallaxisjones/skills
Pick the skills and agents you want, and you're set. Great for trying them on a machine that isn't mine.
2. A symlink script, for a local clone. If I've cloned the repo, link-skills.sh symlinks every SKILL.md into ~/.claude/skills/ so the CLI picks them up directly. It's idempotent — re-run after pulling — and it specifically guards against the footgun where ~/.claude/skills is itself a symlink back into the repo, which would write per-skill symlinks into my own working copy:
if [ -L "$DEST" ]; then
resolved="$(readlink -f "$DEST")"
case "$resolved" in
"$REPO"/*) echo "refusing to pollute the repo"; exit 1 ;;
esac
fi
That defensive check is the kind of thing you write after the first time a script eats its own tail.
3. Nix, for my actual fleet. This is the one that matters. My skills repo is a flake input in my system config, and a home-manager module materializes them declaratively on every machine.
The Nix wiring
Two inputs do the work — my skills repo, and agent-skills-nix, a home-manager module that knows how to turn a skills repo into materialized files:
# flake.nix
inputs = {
agent-skills-nix = {
url = "github:Kyure-A/agent-skills-nix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.home-manager.follows = "home-manager";
};
my-skills.url = "github:parallaxisjones/skills";
};
Then in home-manager I point the module at my repo, filter to the buckets I want live, and enable everything:
agent-skills = {
enable = true;
sources.mine = {
input = "my-skills";
subdir = "skills";
filter.nameRegex = "^(engineering|misc|personal|productivity)/.*";
};
skills.enableAll = true;
targets.claude.enable = true;
};
That's the whole thing. On nixos-rebuild switch (or darwin-rebuild on the Mac), every SKILL.md matching the regex gets written into Claude's skills directory. The deprecated/ bucket is excluded by the filter, so retiring a skill is a one-line regex change, not a manual delete on five machines.
Why bother — what Nix actually buys here
You could argue the symlink script is simpler, and for one machine it is. The fleet is where Nix earns it:
- Pinning.
flake.lockrecords the exact skills commit each machine is on. "Which version of mytriageskill is the laptop running?" has an answer, not a shrug. - Parity from scratch. A fresh machine reaches full skill parity as a side effect of building its system config. There's no separate "and don't forget to install your skills" step — it's the same
switchthat installs my shell and my packages. - Atomic rollback. If a skill edit makes the agent behave worse, rolling back the generation rolls back the skills with it.
There's a framing I lean on for deciding what to manage this way — think of three zones. Zone 1 is stable, declarative config (the home-manager module enabling skills). Zone 3 is runtime state that should never touch Nix (per-conversation agent memory). Skills sit in Zone 2: authored content, edited as plain files in their repo, but materialized onto each machine by Nix. Nix owns where they land and which version; I own what they say.
The honest caveat
Nix doesn't validate skill content — a SKILL.md with a bad description will deploy just as reliably as a good one. This pipeline guarantees distribution and pinning, not quality. The quality comes from treating skills like code: review them, iterate on the descriptions when the agent picks the wrong one, and delete the ones that stop earning their place (that's what deprecated/ is for).
But that's the right division of labor. Writing a good procedure is human work. Making sure that procedure is identically present on every machine I touch is exactly the kind of toil Nix exists to kill. Treat your agent's skills as part of your declarative system, not as dotfiles you sync by hand.
— Parker Jones, parkerjones.dev