YPares

Read documentation (paths shown in RIG.md)

32
1
# Install this skill:
npx skills add YPares/rigup.nix

Or install specific skill: npx add-skill https://github.com/YPares/rigup.nix/tree/main/riglets/agent-rig-system

# Description

AI agents and Nix: parametrable skills/instructions and tools, packaged together in a reproducible and modular fashion

# SKILL.md

Agent Rig System

Overview

A rig is a project-scoped collection of riglets that provide knowledge and tools for AI agents.

Core Concepts

Riglet

A riglet is executable knowledge packaged with its dependencies, as a Nix module:

  • Metadata: When should this riglet be used, is it production-ready or experimental, etc.
  • Knowledge: SKILL.md + detailed references/*.md files documenting processes and recipes
  • Tools: Nix packages needed to execute those recipes
  • Configuration: Settings to adapt tools' behaviour to project context

Rig

A project-level structure that declares which riglets are active:

  • Uses buildRig to compose riglet modules
  • Builds combined tool environment declaratively
  • Exposes riglets' tools and documentation

rigup

A Nix library and CLI tool: http://github.com/YPares/rigup.nix

rigup Nix library

Main functions:

  • buildRig: evaluates riglet modules and ensures they comply with the riglet schema used by rigup. Returns the rig as an attrset: { toolRoot = <derivation>; meta = { <riglet> = {...}; }; docAttrs = { <riglet> = <derivation>; }; docRoot = <derivation>; home = <derivation>; shell = <derivation>; }
  • resolveProject: inspects the riglets/ folder of a project and its rigup.toml to find out which riglets and rigs it defines. It calls buildRig for each rig in the rigup.toml
  • genManifest: generates a markdown+XML manifest file describing the contents of a rig, primarily for AI agent's consumption
  • mkRiglib: creates a set of utility functions to be used to define riglet Nix modules

Defined in {{repoRoot}}/lib/default.nix.

rigup CLI tool

A Rust app. It provides convenient access to rig outputs, via commands like rigup build and rigup shell. This tool is meant for the user primarily. Agents should not have to call it directly.

Defined in {{repoRoot}}/packages/rigup

Riglet Structure

Riglets are Nix modules with access to riglib helpers

Example Riglet

# First argument: the defining flake's `self`
# Gives access to `self.inputs.*` and `self.riglets.*`
# Use `_:` if you don't need it
self:

# Second argument: module args from evalModules
{ config, pkgs, lib, riglib, ... }: {
  # Riglet-specific options (optional)
  options.myRiglet = {
    myOption = lib.mkOption {
      type = lib.types.str;
      description = "Example option";
    };
  };

  # Riglet definition
  config.riglets.my-riglet = {
    # Dependency relationship/Inheritance mechanism: if B imports A, then whenever B is included in a rig, A will automatically be included too
    imports = [ self.riglets.base-riglet self.inputs.foo.riglets.bar ... ];

    # Tools can be:
    # - Nix packages: pkgs.jujutsu, pkgs.git, etc.
    # - Script paths: ./scripts/my-script (auto-wrapped as executables)
    tools = [
      pkgs.tool1
      pkgs.tool2
      ./scripts/helper-script  # Becomes executable "helper-script"
    ];

    # Metadata for discovery and context
    meta = {
      description = "What this riglet provides";
      mainDocFile = "SKILL.md"; # Where to start reading the docs (SKILL.md by default)
      intent = "cookbook"; # What the agent should expect from this riglet
      whenToUse = [
        # When the AI Agent should read/use this riglet's knowledge, recipes and tools
        "Situation 1" # or 
        "Situation 2" # or
        ...
      ];
      keywords = [ "keyword1" "keyword2" ];
      status = "experimental"; # Maturity level
      version = "x.y.z"; # Semantic version of riglet's interface (configuration + provided methods, procedures, docs...)
      disclosure = lib.mkDefault "lazy" # How much to show about riglet in manifest
        # mkDefault makes it possible for end users to override this in their rigup.toml
    };

    # Documentation file(s) (Skills pattern: SKILL.md + references/*.md)
    docs = riglib.writeFileTree {
      "SKILL.md" = ...;  # A main documentation file
      references = {       # Optional. To add deeper knowledge about more specific topics, less common recipes, etc.
                           # SKILL.md MUST mention when each reference becomes relevant
        "advanced.md" = ...;
        "troubleshooting.md" = ...;
      };
    };
    # Files can be defined either as inlined strings or nix file derivations/paths.
    # Folders can be defined either as nested attrsets or nix folder derivations/paths,
    # so if you have a ready to use folder you can do:
    #docs = ./path/to/skill/folder;

    # Configuration files (optional) for tools following the
    # [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir/latest/)
    configFiles = riglib.writeFileTree {
      # Built from a Nix attrset
      myapp."config.toml" = riglib.localTOML {
        setting = "value";
      };
      # Read from existing file
      myapp."stuff.json" = ./path/to/stuff.json;
      # Inlined as plain text
      myapp."script.sh" = ''
        #!/bin/bash
        echo hello
      '';
    };

    # EXPERIMENTAL: Prompt commands (slash commands for harnesses like Claude Code)
    # promptCommands.my-cmd = {
    #   template = "Do something specific with $ARGUMENTS";
    #   description = "What this command does";
    #   useSubAgent = false;
    # };

    # EXPERIMENTAL: MCP (Model Context Protocol) servers
    # mcpServers.my-server = {
    #   command = pkgs.my-mcp-server;  # For stdio transport
    #   transport = "stdio";
    # };
  };
}

The full Nix module schema of a riglet is defined in {{repoRoot}}/lib/rigletSchema.nix.

Examples of actual riglets: {{repoRoot}}/riglets.

Metadata

When defining a riglet, the meta section specifies its purpose, maturity, and visibility. See references/metadata-guide.md for comprehensive details on:

  • meta.intent - Primary focus (base, sourcebook, toolbox, cookbook, playbook)
  • meta.status - Maturity level (stable, experimental, draft, deprecated, example)
  • meta.version - Semantic versioning of the riglet's interface
  • meta.broken - Temporary non-functional state flag
  • meta.disclosure - Visibility control (none, lazy, shallow-toc, deep-toc, eager)

Implementation Utilities

See references/riglib-utilities.md for details on helper functions available via riglib:

  • riglib.writeFileTree - Convert nested attrsets to directory trees
  • riglib.useScriptFolder - Convert folder of scripts into wrapped tool packages

riglib is defined in {{repoRoot}}/lib/mkRiglib.nix

Experimental Features

WARNING: These features are still experimental and their schema may change.

Prompt Commands

Riglets can define reusable prompt templates (slash commands) for agent harnesses like Claude Code:

promptCommands.analyze = {
  template = "Analyze $1 for potential issues";
  description = "Perform code analysis";
  useSubAgent = false;  # Whether to run in a sub-agent
};

Templates use standard Claude command syntax: $ARGUMENTS for all args, or $1, $2, etc. for specific positional arguments.

MCP Servers

Riglets can provide MCP (Model Context Protocol) servers to extend agent capabilities:

mcpServers.my-tools = {
  command = pkgs.my-mcp-server;  # Package that starts the server
  transport = "stdio";            # stdio, sse, or http
};

Cross-Riglet/Flake Interaction

Advanced patterns for composing riglets together and sharing configuration. See references/advanced-patterns.md for:

  • Sharing configuration via config
  • Dependencies and inheritance via imports
  • Using packages from external flakes

Defining Rigs in Projects

Add a rigup.toml file to your project root:

[rigs.default.riglets]
self = ["my-riglet"]
rigup = ["jj-basics"]

[rigs.default.config.agent.identity]
name = "Alice"
email = "[email protected]"

Then use rigup.lib.resolveProject in your flake.nix:

{
  inputs.rigup.url = "github:YPares/rigup.nix";

  outputs = { self, rigup, ... }@inputs:
    # Using the rigup flake directly as a function is equivalent to calling
    # `rigup.lib.resolveProject`, as `rigup` defines the __functor attr.
    #
    # rigup follows the same pattern as the 'blueprint' flake (https://github.com/numtide/blueprint):
    #   - exposes one main "entrypoint" function, callable through the flake "object" itself
    #   - inspects user flake's inputs and repository's contents
    #   - constructs (part of) user flake's outputs
    rigup {
      inherit inputs;
      # A unique name, used in error messages, to make it more explicit where mentioned riglets come from
      projectUri = "some-username/some-project-name";
    }
}

Advanced: Directly use buildRig for complex config

For config not representable in TOML:

{
  inputs.rigup.url = "github:YPares/rigup.nix";

  outputs = { self, rigup, nixpkgs, ... }@inputs:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in
    pkgs.lib.recursiveUpdate # merges both recursively, second arg taking precedence
      (rigup.lib.resolveProject {
        inherit inputs;
        projectUri = "...";
      })
      {
        rigs.${system}.custom = rigup.lib.buildRig {
          name = "my-custom-rig";
          inherit pkgs;
          modules = [
            rigup.riglets.jj-basics
            {
              # Complex Nix expressions
              agent.complexOption = lib.mkIf condition value;
            }
          ];
        };
      };
}

resolveProject outputs

  • riglets.<riglet> - Auto-discovered riglet modules
  • rigs.<system>.<rig> - Output of buildRig for each discovered rig:
  • toolRoot - Folder derivation. Tools combined via nixpkgs buildEnv function (bin/, lib/, share/, etc.) and wrapped (when needed) to fix their XDG_CONFIG_HOME
  • configRoot - Folder derivation. The combined config files for the whole rig, with config files for all rig's wrapped tools.
  • meta.<riglet> - Attrset. Per-riglet metadata, as defined by the riglet's module
  • docAttrs.<riglet> - Folder derivation. Per-riglet documentation folder derivations
  • docRoot - Folder derivation. Combined derivation with docs for all riglets (one subfolder for each)
  • home - Folder derivation. All-in-one directory for the rig: RIG.md manifest + .local/ + docs/ + .config/ folders
  • shell - Shell derivation (via pkgs.mkShell) exposing ready-to-use RIG_MANIFEST and PATH env vars
  • extend - Nix function. Adds riglets to a pre-existing rig: takes {newName, extraModules} and returns a new rig
  • manifest - A manifest for this rig, overridable with options to shorten included paths to avoid repeatedly including long explicit paths into the Nix store

resolveProject is defined in {{repoRoot}}/lib/resolveProject.nix.

Using a Rig

The user decides how they and their agent should use the rig: either via its shell, home or entrypoint output derivations.
In any case, the agent's focus should be is the RIG.md manifest file. This file lists all available riglets with:

  • Name
  • Description
  • When to use each riglet
  • Keywords for searching
  • Documentation paths

Agents should read this file first to understand available capabilities.

buildRig output derivations

buildRig outputs a Nix attrset ("object") that notably contains several "all-in-one" derivations which all allow an AI agent to access the rig's tools and documentation.
Which derivation to use depends on what is the most convenient given the user's setup.
This section lists how and when to use each.

buildRig is defined in {{repoRoot}}/lib/buildRig.nix

shell output

The AI agent runs in a subshell: a $RIG_MANIFEST env var is set that contains the path to the RIG.md manifest the agent should read.
Also, $PATH is already properly set up by the subshell so all tools are readily usable.

# Start a rig as a sub-shell (the user should do that)
rigup shell ".#<rig>" [-c <command>...] # Does `nix develop ".#rigs.<system>.<rig>.shell" [-c <command>...]`

# Read the rig manifest
cat $RIG_MANIFEST

Advantages of using shell:

  • No extra setup needed: a single command gets everything ready to use
  • No risk of using an incorrect tool or config file if the agent misses a step
  • Convenient to use when AI agent runs inside a terminal application (like claude-code)

home output

The AI agent reads from a complete locally-symlinked "home-like" folder.
The RIG.md manifest and an activate.sh script will be added at the root of this folder.
The activate.sh, once sourced, provides the needed PATH.

# Build complete home directory with tools + docs + config as a `.rigup/<rig>` folder at the top-level of the project (the user should do that)
rigup build ".#<rig>" # Does `nix build ".#rigs.<system>.<rig>.home"`

# Read the rig manifest to see what's available
cat .rigup/<rig>/RIG.md

# Source the activation script to use the tools
source .rigup/<rig>/activate.sh && jj --version && other-tool ...

# Read documentation (paths shown in RIG.md)
ls .rigup/<rig>/docs/
cat .rigup/<rig>/docs/<riglet>/SKILL.md

Advantages of using home:

  • Rig can be rebuilt without having to restart the agent's harness: home folder contents are just symlinks that can be updated, paths remain valid
  • Manifest file is right next to doc files: can refer to them via short and simple relative paths
  • More convenient to use in contexts where setting up env vars is impractical (e.g. AI agent running inside an IDE, like Cursor)

entrypoint output

The entrypoint output is special in that it does not exist unless some riglet sets it, by defining config.entrypoint.
It is mainly used to provide direct integration with common coding agent harnesses.
Similar to home and shell, entrypoint packages the whole rig as a Nix derivation, but this time as a wrapper shell script that starts the harness with the proper config files and CLI args.

rigup run <flake>#<rig> executes a rig's entrypoint.
Internally it just runs nix run <flake>#rigs.<system>.<rig>.entrypoint.

Claude Code integration is currently available via the claude-code riglet.
See references/harness-integration.md for more details.

Advantages of using entrypoint:

  • More direct integration with the harness when such integration exists

More efficient Markdown reading: extract-md-toc

This riglet (agent-rig-system) comes with extract-md-toc. This is the tool that renders the inline table of contents of the rig manifests (for riglets with disclosure = "{shallow,deep}-toc";).
It can also be used to extract a similar ToC out of ANY Markdown file: e.g. extract-md-toc foo.md --max-level 3 will show all headers from # to ### with their line numbers.
It can also read from stdin: extract-md-toc - < foo.md

Defined in {{repoRoot}}/packages/extract-md-toc

Adding Riglets to a Rig

In the project defining the riglets OR in another one importing it as an input flake, either add riglets and their config to the rigs defined in the top-level rigup.toml file, or directly edit the flake.nix if more advanced configuration is needed.
In both cases, the flake should call rigup.lib.resolveProject (or just rigup, which contains a __functor attr which defers to resolveProject) to discover rigs and riglets, and the rigs should be under the rigs.<system>.<rig-name> output.

Creating New Riglets

In some project:

  1. Create riglets/my-riglet.nix, or riglets/my-riglet/default.nix for riglets with multiple supporting files
  2. Add the needed tools, documentation, metadata
  3. Define options (schema) and config (values) in this module
  4. Ensure the project has a top-level flake.nix that uses rigup.lib.resolveProject as mentioned above, so all the riglets will be exposed by the flake

If your rig contains riglet-creator, consult it for more detailed information about writing proper riglets.

Design Principles

  • Knowledge-first: Docs are the payload, tools are dependencies
  • Declarative: Configuration via Nix module options
  • Composable: Riglets build on each other
  • Reproducible: Nix ensures consistent tool versions

# README.md

logo

Self-Contained & Modular AI Agent Config

rigup is a Nix-based system for packaging knowledge, instructions and skills for your AI agent with the tools and config needed to execute them.

A riglet is executable knowledge:

  • metadata to indicate to your agent what this riglet is for and when it is useful to consult it
  • a set of operations, instructions, processes, a.k.a a new Skill for your agent
  • the tools (nix packages) needed to execute these instructions
  • the configuration for these tools (if any)

By combining the relevant riglets together, you build your agent's rig: the tools it needs to work on your project, and the operational knowledge it needs to use those tools properly and efficiently.

By default, the documentation of a riglet is lazily loaded (like Skills are): at startup your agent is fed a manifest, only showing for each riglet a quick description and a set of use cases.
It will then go on reading it when it needs to, or is prompted to.
However, rigup gives you more control than Skills traditionally do in terms of when your agent should be exposed to a riglet's content.
For instance, a fundamental riglet can be set to have an eager disclosure (forcing the agent to see the whole SKILL.md at startup), while another one can have a shallow-toc disclosure (the agent will see a table of contents of the riglet's main SKILL.md).

rigup has a "knowledge-first" design: documentation is the payload, tools are dependencies. The "main entrance" to the rig will be the RIG.md manifest that rigup will generate for your AI agent to read, and through which it will discover all the available tools, knowledge and processes to follow.

In short, rigup is parametrable agent skills + lightweight home management for your agent, with a high-level of interoperability with Claude Skills and Claude plugin marketplaces, so reusing published Skills as basis for riglets is made as easy as possible.

Quick start

First, install the rigup CLI tool:

nix profile add github:YPares/rigup.nix#rigup

Cached binaries should be provided by garnix.io (for both x86_64 Linux and ARM64 OSX). The first time, nix should prompt you about accepting the flake config to activate garnix as a substituter.

NOTE: On older Nix versions, "add" is "install" instead.

If already installed, update with:

nix profile upgrade rigup

This CLI tool makes it easier to:

  • get information about riglets and rigs exposed by a flake and its inputs (rigup show)
  • show the contents of a rig, including which options can be used to customize it with their description, type, default and current values (rigup inspect)
  • directly start a coding agent harness (rigup run or just rigup) if the rig contains a riglet that provides an entrypoint. Entrypoints act as connectors, wrapping a harness executable to start it with the appropriate config
  • build a full rig as a folder of symlinks (rigup build)
  • use a rig as a subshell, with $PATH and $RIG_MANIFEST (path to the RIG.md) set up (rigup shell)

(Note all these commands are just wrappers, provided for convenience, around the rigup Nix library. So you may still do everything with the usual nix {build,develop,run} commands if you prefer)

You can then create a new project from the templates provided by this repository, or even directly build the example rig defined here.

Use a rig from this project (or any other repo using rigup)

This project defines example riglets, and an example rig combining them.

# Show the riglets and rigs exposed by a remote flake
rigup show github:YPares/rigup.nix
# Show all metadata about riglets and each rig's contents
rigup show --detailed github:YPares/agent-skills  # A Skills repo that also packages them via rigup

# Directly build a rig from a remote flake
rigup build github:YPares/rigup.nix#example-rig # this builds <flake>#rigs.<system>.example-rig.home and outputs a symlink

# Directly open a rig from a remote flake in a subshell
rigup shell github:YPares/rigup.nix#example-rig

# Directly run a rig's entrypoint (if it has any)
rigup run github:YPares/agent-skills#claude-rig
  # Then in Claude Code: Ctrl+o to show details, to see the RIG.md manifest that Claude was prompted with via a startup hook
rigup run github:YPares/agent-skills#copilot-rig
  # Almost the same rig, but using `copilot-cli` as an entrypoint

Create a new project

Create it from one of the templates in this repo:

rigup new foo [-t minimal]
  # Use 'minimal' template for a project which should not define any local riglet 

cd foo

This creates a ./foo folder, initializes it as a git repo, calls nix flake init to create a basic project structure with a flake.nix and example riglets and rigs, adds all the files to git tracking (important), and finally runs nix flake check on the result to make sure everything is in order.

# List available riglets from flake's self and inputs
rigup show --detailed --with-inputs

# Build your rig
rigup build ".#default"

# Explore the rig
cat .rigup/default/RIG.md

# Or open the rig as a sub-shell instead of creating a local symlink
rigup shell ".#default"

Edit riglets/*.nix and rigup.toml to customize the project.

The default template includes some example riglets and shows how rigup supports using a Claude Marketplace as a direct input, to import pre-existing Skills and use them as a basis for riglets.

Deeper dive

This section covers the general workflow of defining and editing riglets and rigs.

Creating a riglet

Riglets are a simple use case of Nix modules.
Concretely, this means a riglet (in its most general form) is a (config, dependencies) -> data Nix function, where:

  • config is the final config of the rig which the riglet is part of,
  • data is a dictionary-like structure ("attribute set" in Nix lingo) providing nested fields that will themselves contribute to the final aggregated config

Create a riglets/ folder at the root of your project.
Then add to it a <riglet-name>.nix file:

# riglets/my-riglet.nix

# First argument: the defining flake's `self` - gives access to:
#   - `self.inputs.*` for external package dependencies
#   - `self.riglets.*` for inter-riglet imports
# Just use `_` if you don't need it
_:

# Second argument: module args constructed by the _user_ of the riglet, when the full rig is built
# - config is the final aggregated config of the rig using my-riglet,
# - pkgs is your usual imported nixpkgs,
# - system is just pkgs.stdenv.hostPlatform.system, for quicker access
# - riglib is injected by rigup, and contains utility functions to build riglets
{ config, pkgs, system, riglib, ... }: {

  # (Optional) Which riglets to depend on (whether from self or other flakes)
  # If this riglet is included in a rig, ALL the riglets it imports will automatically be included as well
  imports = [ ... ];

  # Each riglet must declare itself under config.riglets.<riglet-name>
  # <riglet-name> MUST be the SAME as the file name, minus the .nix extension
  config.riglets.my-riglet = {

    # (Optional) The tools needed by this riglet
    tools = [
      pkgs.mytool
      ./path/to/some/script.sh
    ];

    # The metadata that will enable your agent to know what this riglet
    # provides and when it should be consulted
    meta = {
      intent = "cookbook";
        # 'base', 'sourcebook', 'toolbox', 'cookbook', or 'playbook':
        # what should the agent expect from this riglet: general knowledge
        # that may come useful vs. highly specific procedure(s) to follow
      description = "What this provides";
      whenToUse = [
        "Need to know about X"
        "Want to achieve Y"
        "Have problems with Z"
        ...
      ];
      keywords = [ "search" "terms" ];
      status = "draft";
      version = "0.1.0";
    };

    # The Skill part of the riglet. It is a file hierarchy that should contain a
    # SKILL.md entry right under the root
    docs = riglib.writeFileTree {
      # Use inline strings...
      "SKILL.md" = ''
        # My Riglet Documentation

        ...

        For more advanced cases, see references/advanced-cases.md
      '';
      references = {
        # ...or local file paths...
        "advanced-cases.md" = ./path/to/advanced-cases.md;
        # ...or derivations that build a file
        "foo.md" = pkgs.writeText "foo.md" (mkFooContents x y z t);
      };
    };

    # Alternatively, if you already have a folder that follows the Agent Skill convention,
    # (SKILL.md at the top and references/*.md files), you can directly reuse it:
    #docs = ./path/to/skill/folder;

    # (Optional) The configuration that the tools should use.
    # This will go under `.config` in the final "home directory" of the rig, but only
    # for consultation purposes: tools are by default *wrapped* to see a XDG_CONFIG_HOME that
    # always points to this joint config folder. This way we can separate tools between those
    # that need custom config and those that will just "inherit" the pre-existing user config
    configFiles = riglib.writeFileTree {
      # Will generate `.config/mytool/config.toml`
      mytool."config.toml" = ''
        setting = "value"
      '';
    };

  };

}

...or just ask your agent to do it! :) rigup.nix comes with the riglet-creator riglet that teaches your agent to write riglets and... and, ahem, yes that's becoming very meta, sorry about that.

Just as with regular Agent Skills, the point of separating the docs into several files
is to allow progressive disclosure.
When using AI Agents, this is important because context is on a budget, and agents should not have to read more documentation than they need to complete a task.

Alternative structure

Instead of riglets/<riglet-name>.nix, you can define your riglet as riglets/<riglet-name>/default.nix to add supporting files next to it:

riglets/
β”œβ”€β”€ simple-riglet.nix          # Single-file riglet
└── complex-riglet/            # Directory-based riglet
    β”œβ”€β”€ default.nix            # Main riglet definition
    β”œβ”€β”€ SKILL.md               # Referenced in default.nix via ./SKILL.md
    └── references/
        └── advanced.md        # Referenced in default.nix via ./references/advanced.md

This is useful to break up a riglet into various Nix or raw text files to make it more manageable.
rigup will discover and treat both layouts identically.

Inter-riglet dependencies

If a riglet depends on another (for instance because it builds on its concepts or requires its tools or configuration), use imports with self.riglets.*:

# riglets/advanced-riglet.nix
self:
{ riglib, ... }: {
  # Import the base riglet - if both are added to a rig, evalModules deduplicates
  imports = [ self.riglets.base-riglet ];

  config.riglets.advanced-riglet = {
    # ...
  };
}

Important: Always use self.riglets.* for imports, never path-based imports like ./base-riglet.nix. The self.riglets.* form ensures proper deduplication when the same riglet is included both directly and via imports.

For dependencies on riglets from external flakes:

self:
{ riglib, ... }: {
  imports = [ self.inputs.other-flake.riglets.some-riglet ];
  # ...
}

Using external packages

Riglets can access packages from external flakes via self.inputs:

self:
{ pkgs, system, riglib, ... }: {
  config.riglets.my-riglet = {
    tools =
      let someFlakePkgs = self.inputs.some-flake.packages.${system};
      in [
        someFlakePkgs.default
        pkgs.git
      ];
  };
}

This lets riglets bundle their own dependencies without requiring consumers to know about them.

Creating a rig

Simple option: rigup.toml

Define rigs in rigup.toml at the top of your project:

# Riglets to include in this rig, grouped by source
[rigs.default.riglets]
rigup = ["jj-basics", "typst-reporter"]  # From the rigup flake input
self = ["my-local-riglet"]               # From your riglets/ folder

# Configuration for the riglets used in this rig
[rigs.default.config.agent.identity]
name = "Alice"  # This is used by both jj-basics & typst-reporter example riglets
email = "[email protected]"

Your flake.nix should be:

{
  inputs.rigup.url = "github:YPares/rigup.nix";

  outputs = { self, rigup, ... }@inputs:
    rigup {
      inherit inputs;
      # A unique name to identify your project in error messages :
      projectUri = "my-username/my-project";
      # Add this if you want your rigs and/or riglets to be built as part of `nix flake check` :
      #checkRigs = true;
      #checkRiglets = true;
    };
}

Finally, build the rig with:

rigup build ".#default"

...or enter it through a sub-shell with:

rigup shell ".#default"

NOTE: Just like nix build and nix develop target the "default" package or devShell when called without extra args, so do rigup build or rigup shell by targetting the rig named "default"

The riglets listed in your rigup.toml must match your flake inputs and what exists in your project. As a general case:

[rigs.my-rig.riglets]
some-flake = ["foo", "bar"]

means that your flake.nix has a some-flake input that exposes the riglets.foo and riglets.bar outputs.
self is just a special case of that, as every flake has an implicit self input which is the flake itself.

NOTE: The main reason to use a TOML file instead of always defining everything as Nix code is not just because TOML is (much) more well-known than Nix syntax.
It is mainly because pure data (that can already cover a large set of use cases) is easier to manipulate via CLI tools than Nix code.

Advanced option: combine with Nix

...that being said, building rigs directly in Nix (if you need the full Nix power to write your rig's configuration) is totally supported:

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  inputs.rigup.url = "github:YPares/rigup.nix";
  inputs.foo.url = "github:bar/foo";

  outputs = { self, rigup, nixpkgs, foo, ... }@inputs:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in
    pkgs.lib.recursiveUpdate  # merge both recursively, second arg taking precedence
      (rigup {
        inherit inputs;
        projectUri = "...";
      })
      {
        # Override or extend with custom rigs
        rigs.${system}.custom = rigup.lib.buildRig {
          inherit pkgs;
          modules = [
            self.riglets.aaa # Will use `$PROJECT_ROOT/riglets/{aaa.nix,aaa/default.nix}`
            foo.riglets.bbb  # Use external riglets defined by our inputs
            foo.riglets.ccc
            # Extra config is given in the form of a simple in-line Nix module,
            # with or without `{ config, pkgs, riglib, ... }` arguments
            {
              config = {
                # More advanced config, that e.g. requires direct access to some Nix functions,
                # or even takes the form of Nix functions:
                aaa-config.i-need-a-derivation = pkgs.someDerivationBuilder "..." "........";
                bbbModuleOpts.conditions.is-x-y-pair-valid = x: y: x * y <= 42.420000000000001;
              }
            }
          ];
        };
      };
}

IMPORTANT: This makes the rigup.toml entirely optional, but it is still necessary to use rigup { inherit inputs; ... } to construct your flake's outputs.
This is so rigup can discover which riglets are defined in your $PROJECT_ROOT/riglets/ and correctly set up the riglets output of your flake.

As the above suggests, you can totally mix both options: define some simple rigs in the rigup.toml and some advanced ones in Nix code.
Although, prefer splitting you rigs' definitions in separate Nix files rather than declaring everything in your flake.nix, the example above is just for the sake of concision.

Riglet metadata

You may have noticed that riglets are a bit richer but also stricter in terms of metadata than Skills.
This is to enforce a bit more rigidly some good documentation-writing practices, and to make critical information more upfront, both for agents reading a manifest and for humans browsing riglets provided by some source with rigup show.
This contributes to the final documentation's quality without being too constraining.

This is why, contrary to Skills:

  • a riglet must always have an intent: why it was written, what type of information it was meant to provide, what should one expect to gain when reading it.
    This notably guides the writing process, and limits the risk of riglets becoming a mash-up of contextual info, general guidelines and specific processes.
    Don't forget riglets can depend on one another: if some background knowledge is needed to specify some processes that an agent should follow, then rather than trying to conflate everything into one big mess, make a first sourcebook or toolbox riglet which can then be used as a dependency of subsequent playbook riglets.
    This way, the background information is shared, and will always come along with the more critical knowledge.
    Smaller, more focused riglets means more reusability.
  • a riglet has a dedicated list of use cases (whenToUse), which is not conflated with its description. When a riglet should "activate", i.e. when an agent should consult and follow it, is probably the most critical piece of information about it, and thus warrants a dedicated field

This is notably useful when using agents to write riglets: they are not great at filling general fields like "description". They are better when their thinking is guided by a stricter schema.

But this enables riglets to perform things that Skills just cannot do:

  • explicitly depend on one another (as we previously saw), to make sure critical background knowledge is always accessible if needed, and not simply assumed to be part of the LLM's training dataset.
  • specify how they should be disclosed to an agent. Not all skills are equal, some contain critical information which you want to be very upfront about, others are "just in case" tips and tricks that should not overwhelm the agent. This is what the meta.disclosure field is about.
  • document ahead-of-time the quality of the knowledge they contain. Has it been battle tested quite a few times and proven useful? Could it still benefit from some proofreading or clarifications? This is what the meta.status field is for. Agents, just like humans, can make much better decisions if they know how much trust they can put in an information source. It is always safer to progress on shaky ground when you know the ground is shaky.
  • provide a general framework for agent configuration besides just Markdown docs: riglets are Nix modules, and the main goal of Nix modules is to set up configuration in a modular and reusable fashion.
    Riglets are no different, it's just that the biggest part of an AI agent's "config" happens to be human-readable documentation, which is why the focus so far has been on packaging docs.
    But riglets can have meta.intent = "base": this is for riglets that should remain transparent to the agent while still interacting with the rig, e.g. to provide tools or plain old JSON or TOML config files.
    rigup enforces that these base riglets are NOT disclosed to the agent.
    This notably enables the most important tool of the rig, the agent's harness, to be expressed, packaged and used just like any other part of the rig.

Features

  • One Config To Rule Them All: rigup will generate config for:
  • Claude Code
  • OpenCode
  • Cursor & cursor-agent
  • copilot-cli (although copilot-cli does not allow custom prompt commands, and MCP servers can only be set in user's config for now)
  • ...and possibly others to come (PRs are welcome!)
  • Declarative composition: Module system for riglet interaction
  • Data-driven config: rigup.toml for CLI-manageable rigs
  • Auto-discovery: Riglets from riglets/ automatically exposed, as well as rigs from rigup.toml
  • Compatibility with Skills and Claude Marketplaces:
  • Directly import Claude Marketplace repos as inputs and use provided skills as basis for new riglets
  • Riglet documentation follows as much as possible the Skills structure and conventions to facilitate reusability
  • Plug-and-play integration with common agent harnesses: via riglets defining an entrypoint derivation
  • Type-checked metadata: Nix validates riglet structure
  • Auto-generated manifests: RIG.md lists all capabilities
  • Reproducible: Nix ensures consistent tool versions
  • Self-documenting: Riglets like agent-rig-system, riglet-creator, and nix-module-system teach agents how the system works β€” so they can help you write Nix and extend your rig. Learn Nix as a side effect, with AI assistance.

Experimental Features

WARNING: These feature are on the main branch, but their API is subject to change, and they are not supported by all entrypoints yet:

  • mcpServers: riglets can add MCP servers to the rig, through the harness config generated by entrypoint riglets
  • promptCommands: riglets can add custom "slash commands" (prompt templates) to the rig, through similar mechanism, for harnesses that support it (notably copilot-cli for now doesn't)

TODO

  • Add checks to riglets: automated and/or through-agent testing that a riglet is working as intended
  • rigup CLI commands to conveniently view and set config values, like rigup config set <rig> foo.bar.qux <value>
  • openskills: Universal Skill loader, following Claude Skills system and manifest
  • llm-agents.nix: Numtide's flake packaging AI coding agents and development tools
  • agent-skills-nix: Nix lib to discover and bundle skills, with home-manager integration

License

MIT


Built with Nix β€’ Formatting: nixfmt-rfc-style β€’ Uses blueprint

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.