# How the 99 Agent Rules System Parses and Loads SKILL.md Files

> Learn how the 99 agent rules system parses and loads SKILL.md files. Discover directory walking, rule descriptor building, and on-demand content loading.

- Repository: [ThePrimeagen/99](https://github.com/theprimeagen/99)
- Tags: how-to-guide
- Published: 2026-02-16

---

**The 99 agent rules system treats each [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) file as a plain text blob, discovering files via directory walking in `helpers.ls`, building rule descriptors with metadata, and loading content on-demand through `Agents.get_rule_content` wrapped in XML-like tags.**

The [ThePrimeagen/99](https://github.com/ThePrimeagen/99) repository implements a flexible agent rules architecture where domain-specific instructions live in [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files scattered across user-defined directories. Unlike complex parsers that analyze markdown structure, the 99 agent rules system focuses on file discovery, descriptor indexing, and lazy content loading to expose these rules through Neovim's completion engine.

## How SKILL.md Discovery Works

Rule discovery begins in [`lua/99/extensions/agents/helpers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/helpers.lua) through the `helpers.ls` function. This utility receives a directory path and determines whether it contains a single rule or a collection of rules in subdirectories.

The function first checks for a direct [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) file:

```lua
local direct_skill = vim.fs.joinpath(current_dir, "SKILL.md")
if vim.fn.filereadable(direct_skill) == 1 then
  table.insert(files, direct_skill)               -- a single rule in this dir
else
  local glob = vim.fs.joinpath(current_dir, "*/SKILL.md")
  files = vim.fn.glob(glob, false, true)          -- all sub-folders containing a SKILL.md
end

```

This dual-mode discovery allows users to organize rules either as flat files or as categorized subdirectories, with the system automatically adapting to either structure.

## Building Rule Descriptors

Once `helpers.ls` identifies valid [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files, it constructs lightweight rule descriptors containing three critical fields:

```lua
{
  name = filename,            -- folder name that contains the SKILL.md
  path = relative_path,       -- relative to the repo root
  absolute_path = file,       -- full filesystem path
}

```

These descriptors serve as the immutable metadata that the agent rules system uses for indexing and lookup, separating file location from content to enable efficient lazy loading.

## Aggregating Rules into the Agent

The `Agents.rules` function in [`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua) orchestrates the aggregation of all custom rules. It iterates over the user-defined `_99.completion.custom_rules` paths, invokes `helpers.ls` for each directory, and compiles the results into two data structures:

```lua
for _, path in ipairs(_99.completion.custom_rules or {}) do
  local custom_rules = helpers.ls(path)
  for _, r in ipairs(custom_rules) do
    table.insert(custom, r)
  end
end
add_rule_by_name(by_name, custom)  -- index by rule name

```

The resulting table contains a `custom` list for ordered access and a `by_name` map for O(1) lookups, which the completion engine and resolution actions consume.

## Loading and Resolving Rule Content

When the agent needs to materialize a rule's instructions—typically during prompt generation or completion resolution—it calls `Agents.get_rule_content`. This function performs the actual I/O, reading the entire [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) file as a plain text blob and wrapping it in XML-like tags for contextual clarity:

```lua
local file_path = rule.absolute_path or rule.path
local file = io.open(file_path, "r")
local content = file:read("*a")
file:close()
return string.format("<%s>\n%s\n</%s>", rule.name, content, rule.name)

```

Notably, the system does not parse markdown headers, code blocks, or frontmatter within the [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md); it treats the content as an opaque string to be injected directly into prompts.

## Exposing Rules Through the Completion UI

The completion integration in [`lua/99/extensions/cmp.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/cmp.lua) registers the agent's completion provider, which surfaces available rules as LSP-compatible completion items. Using `helpers.head` to extract the first five lines of each [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md), the provider generates rich documentation:

```lua
local docs = helpers.head(rule.absolute_path or rule.path)   -- first 5 lines
table.insert(items, {
  label = rule.name,
  insertText = "#" .. rule.path,
  filterText = "#" .. rule.name,
  kind = 12,
  documentation = { kind = "markdown", value = docs },
  detail = "Rule: " .. rule.path,
})

```

Users trigger this by typing `#` followed by the rule name, receiving immediate feedback with file previews before inserting the full rule reference.

## Summary

- **Discovery**: The `helpers.ls` function in [`lua/99/extensions/agents/helpers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/helpers.lua) walks user-defined directories to locate [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files, supporting both single-file and glob-based discovery patterns.
- **Metadata**: Rule descriptors contain `name`, `path`, and `absolute_path` fields, enabling efficient indexing without loading file contents.
- **Aggregation**: The `Agents.rules` function compiles descriptors into ordered lists and name-indexed maps for fast lookup.
- **Content Loading**: `Agents.get_rule_content` performs lazy file I/O, returning raw markdown wrapped in XML-like tags without parsing the document structure.
- **UI Integration**: The completion provider leverages `helpers.head` to preview rule content, inserting references as `#<path>` tokens.

## Frequently Asked Questions

### How does the 99 agent rules system handle nested directories of SKILL.md files?

The system uses a glob pattern `*/SKILL.md` within each custom rules directory to discover rules nested in subfolders. If a directory contains its own [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) directly, that single file is treated as the rule; otherwise, the glob finds all subdirectories containing skill files. This logic is implemented in `helpers.ls` at lines 22-28 of [`lua/99/extensions/agents/helpers.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/helpers.lua).

### Does the 99 agent parse the markdown content of SKILL.md files?

No, the system treats [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files as plain text blobs. When `Agents.get_rule_content` loads a rule, it reads the entire file using `io.open` and `file:read("*a")` without analyzing headers, code blocks, or frontmatter. The raw content is wrapped in XML-like tags (`<RuleName>…</RuleName>`) and injected directly into prompts.

### What configuration is required to load custom SKILL.md rules?

Users must specify the `completion.custom_rules` field in their 99 setup configuration, providing a list of directory paths where [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) files reside. For example:

```lua
require("99").setup({
  completion = {
    custom_rules = { "scratch/custom_rules", "config/ai_rules" }
  }
})

```

The `Agents.rules` function then iterates over these paths to discover and index available rules.

### How are SKILL.md files presented in the Neovim completion UI?

The completion provider in [`lua/99/extensions/agents/init.lua`](https://github.com/ThePrimeagen/99/blob/main/lua/99/extensions/agents/init.lua) generates LSP-compatible items where each rule's label matches its directory name. The `helpers.head` function extracts the first five lines of the [`SKILL.md`](https://github.com/ThePrimeagen/99/blob/main/SKILL.md) to populate the documentation field, giving users a preview before insertion. When accepted, the completion inserts `#<relative-path>` (e.g., `#backend`) into the buffer, which the system later resolves to the full rule content.