How the 99 Agent Rules System Parses and Loads SKILL.md Files
The 99 agent rules system treats each 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 repository implements a flexible agent rules architecture where domain-specific instructions live in 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 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 file:
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 files, it constructs lightweight rule descriptors containing three critical fields:
{
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 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:
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 file as a plain text blob and wrapping it in XML-like tags for contextual clarity:
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; 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 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, the provider generates rich documentation:
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.lsfunction inlua/99/extensions/agents/helpers.luawalks user-defined directories to locateSKILL.mdfiles, supporting both single-file and glob-based discovery patterns. - Metadata: Rule descriptors contain
name,path, andabsolute_pathfields, enabling efficient indexing without loading file contents. - Aggregation: The
Agents.rulesfunction compiles descriptors into ordered lists and name-indexed maps for fast lookup. - Content Loading:
Agents.get_rule_contentperforms lazy file I/O, returning raw markdown wrapped in XML-like tags without parsing the document structure. - UI Integration: The completion provider leverages
helpers.headto 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 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.
Does the 99 agent parse the markdown content of SKILL.md files?
No, the system treats 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 files reside. For example:
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 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →