Understanding the Custom Rules and SKILL.md System in ThePrimeagen's 99 Neovim AI Agent

The SKILL.md system in 99 is a file-based custom rules engine that lets you define reusable AI prompts as markdown files, which are dynamically loaded and triggered via # completions inside Neovim.

The 99 repository by ThePrimeagen implements a lightweight AI assistant that lives entirely within Neovim. At the heart of its extensibility lies the custom rules system, which uses specially named SKILL.md files to inject domain-specific knowledge into AI prompts. Understanding how this SKILL.md system works is essential for customizing the agent's behavior to match your specific codebase or workflow.

What is the SKILL.md System?

The SKILL.md system is a convention-based approach to prompt engineering built into 99. Instead of typing repetitive instructions into the AI chat, you write reusable rules as markdown files named SKILL.md and store them in designated directories. When you type # followed by the rule name in a prompt, 99 injects the contents of that markdown file into the request context sent to the AI provider.

This system is implemented in lua/99/extensions/agents/init.lua, where the agents extension scans configured directories, parses SKILL.md files, and registers them as completion items.

How Custom Rules Work in 99

The Agents Extension Architecture

The agents extension serves as the bridge between your filesystem and the AI prompt builder. When 99 initializes via _99.setup(opts), the extension reads the completion.custom_rules configuration and recursively scans each directory for SKILL.md files.

The core logic uses helpers.ls to enumerate files and builds a lookup table (by_name) that maps rule names to their file contents. This table is then exposed through a completion_provider function compatible with nvim-cmp or any LSP-compatible completion engine.

Loading Rules from Disk

When you trigger a completion with #, the provider queries the by_name table and returns matching rules as completion items. Upon selecting a rule, 99 reads the file contents and appends them to the RequestContext before sending the request to the AI provider.

The prompt assembly happens in lua/99/ops/make-prompt.lua, which stitches together:

  1. Auto-injected markdown files (md_files from config)
  2. The selected custom rule content
  3. The user's free-form text input

Configuring Custom Rules in Your Setup

To enable custom rules, configure the completion.custom_rules path in your 99 setup. Here is a complete Lazy.nvim configuration example:

-- init.lua
return {
  "ThePrimeagen/99",
  config = function()
    local a99 = require("99")
    a99.setup({
      completion = {
        source = "cmp",        -- Use nvim-cmp for completions
        custom_rules = {       -- Directories containing SKILL.md files
          vim.fn.stdpath("config") .. "/99-rules/",
          "scratch/custom_rules/",
        },
        files = {              -- Optional: enable @-file completion
          enabled = true,
        },
      },
      md_files = { "AGENT.md" },   -- Auto-inject into every prompt
    })
  end,
}

Each directory listed in custom_rules is scanned recursively. 99 identifies valid rules by the filename SKILL.md and extracts the rule name from the parent directory or a header within the file.

Using SKILL.md Rules in Prompts

Once configured, invoke custom rules by typing # followed by the rule name in any 99 operation. For example, if you have a rule at 99-rules/rust/SKILL.md, you can reference it as #rust:

:'<,'>lua require('99').visual({ prompt = "#rust Refactor this to use iterators" })

The #rust trigger causes 99 to:

  1. Locate the rust rule in the by_name lookup table
  2. Read the contents of 99-rules/rust/SKILL.md
  3. Prepend the rule content to your prompt before sending to the AI provider

This works across all operations: visual, search, and tutorial.

File Structure and Location

Understanding where 99 stores its logic helps when debugging or extending the SKILL.md system:

File Purpose
lua/99/init.lua Core state (_99_State), public API, and request tracking
lua/99/extensions/agents/init.lua Custom rule loading and # completion provider
lua/99/extensions/files/init.lua @ file completion provider
lua/99/ops/make-prompt.lua Prompt assembly logic that injects SKILL.md content
lua/99/request-context.lua Context object holding buffer, cursor, and temp file info

The agents extension specifically relies on helpers.ls (likely in lua/99/util/ or similar) to scan directories for SKILL.md files during initialization.

Summary

  • SKILL.md files are markdown-based custom rules stored in directories configured via completion.custom_rules.
  • The agents extension (lua/99/extensions/agents/init.lua) scans these directories at startup and provides # triggered completions.
  • When you type #rulename in a prompt, 99 injects the contents of that rule's SKILL.md file into the RequestContext before sending to the AI provider.
  • Rules work across all 99 operations including visual, search, and tutorial.
  • The system integrates with nvim-cmp via the completion_provider interface, offering fuzzy matching against loaded rule names.

Frequently Asked Questions

What format should a SKILL.md file use?

A SKILL.md file should be valid markdown containing instructions, code examples, or context you want the AI to consider. The first line can contain a heading (e.g., # Python Optimization) which the agents extension may use for display purposes, but the entire file content is injected verbatim into the prompt. There is no required frontmatter or YAML metadata.

How does 99 resolve conflicting rule names?

If multiple directories in completion.custom_rules contain SKILL.md files with identical parent directory names (e.g., rules/vim/SKILL.md and backup/vim/SKILL.md), the agents extension loads both but the by_name lookup table will contain the last one scanned. To avoid collisions, use unique directory names or organize rules under a single root directory with descriptive subfolders like vim-advanced or vim-basics.

Can I use SKILL.md rules with any AI provider?

Yes. The SKILL.md system is provider-agnostic. The agents extension loads rules and injects them into the RequestContext before the prompt is sent to whichever provider is configured (OpenCode, Claude Code, Cursor, or Kiro). The provider implementation in lua/99/providers.lua receives the fully assembled prompt containing the rule content, regardless of the backend CLI being used.

Where should I store my custom rules?

Store custom rules in a directory outside of the 99 plugin directory to prevent loss during updates. Common locations include:

  • ~/.config/nvim/99-rules/ for Neovim-specific rules
  • Project-specific rules in a .99/ or prompts/ directory within your repository
  • Shared team rules in a separate git repository cloned to ~/code/99-rules/

Add these paths to the completion.custom_rules array in your _99.setup() configuration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →