Benedikt Ritter
← Reproducible Thoughts

Verified Agent Skills Managed by Nix

3 min read

I manage my home environment using home-manager. Since AI agent skills reside in the user home, I naturally started putting mine into my Nix configuration repository. When I added the asd-ste100 skill, it shipped with a linter for Simplified Technical English. That made me wonder what other checks people run on their skills, so I asked one of my agents. In this blog post I’m going to show my writeSkill helper function, which runs these checks as part of the build and behaves like other writer functions from pkgs.writers.

The writers pattern

nixpkgs ships the writers: writePython3 runs flake8 on the script it packages, writeLua runs luacheck, writeShellApplication runs shellcheck. I like where these checks live: they are part of building the artifact. A broken script fails the build, so a broken script is never produced. There is no lint step to remember and nothing to wire up separately. Since I tend to have agents edit my skill files, I want this safety net for them as well.

writeSkill

I applied the same pattern to my skills. The writeSkill function turns a skill directory into a store path and runs two checks along the way:

pkgs.writeSkill "calculations" { } ./skills/math/calculations

The first check is skills-ref validate, the reference validator from the Agent Skills repository, which I package as skills-ref. It checks the frontmatter against the spec: required fields, the name rules, and the directory match. The validator compares the directory basename to the frontmatter name, and store paths carry a hash prefix. So the build first copies the source to a clean-named work/<name> directory.

The second check is ste-lint, the Simplified Technical English linter from the asd-ste100-skill project, which I package as a standalone ste-lint command. It fails the build on hard violations of the STE rules, while advisory findings such as passive voice don’t. Skills whose body I don’t maintain pass { lint = false; }: the spec check still runs, the prose check doesn’t.

This is the whole function, from packages/write-skill/default.nix:

{
  lib,
  runCommand,
  skills-ref,
  ste-lint,
}:
name:
{
  lint ? true,
}:
src:
runCommand name
  {
    nativeBuildInputs = [ skills-ref ] ++ lib.optionals lint [ ste-lint ];
  }
  (
    # skills-ref checks the directory basename against the frontmatter name,
    # and store paths carry a hash prefix, so validate a clean-named copy.
    # This is also the layout the harnesses install.
    ''
      mkdir work
      cp -r "${src}" "work/${name}"
      chmod -R u+w "work/${name}"
      skills-ref validate "work/${name}"
    ''
    + lib.optionalString lint ''
      # Prose check: SKILL.md must pass the STE linter with zero hard
      # violations; advisory findings (passive voice, compound tenses) do not
      # fail the build.
      ste-lint "work/${name}/SKILL.md"
    ''
    + ''
      cp -r "work/${name}" "$out"
    ''
  )

The checks also pay off when the author is an agent. The context files tell my agents to add and edit skills in the config repository. An agent that writes an invalid skill sees the build failure, reads the validator’s complaint, and fixes it before anything ships.

Why not a flake check?

The obvious alternative is a checks attribute in the flake. I didn’t want the plumbing. The check needs writing, and it has to stay in sync with how the skills build and install.

With the writer, checking is a property of the artifact. Building my home-manager generation builds every enabled skill as a dependency, so an invalid skill fails the switch with the validator’s output in the log. The feedback lands where I make the change, and Nix caches it like everything else.

writeSkill is forty lines, most of them comments. It works because the checks run where the artifact is built. Only verified skills exist, and neither my agents nor I can deploy a broken one.

Benedikt Ritter

Benedikt Ritter

Gradle & NixOS Consultant

Developer productivity consultant with five years at Gradle Inc. building Develocity, and co-founder of TestLens. Apache Software Foundation member and founder of GradleX. I help engineering teams ship faster through better Gradle build tooling and reproducible NixOS infrastructure.