How Reasonix Parses Frontmatter for Skills and Commands: A Deep Dive into the YAML Pipeline

Reasonix parses skill and command metadata from YAML frontmatter blocks using a three-stage pipeline: split the file on --- fences, unmarshal YAML into a normalized map[string]string with lower-cased keys, then let skill- or command-specific loaders extract the fields they need.

Reasonix, the extensible AI agent framework in the esengine/DeepSeek-Reasonix repository, relies heavily on YAML frontmatter to declare metadata for both skills (reusable AI capabilities) and commands (CLI/desktop UI actions). This article breaks down exactly how the parsing pipeline works, from the low-level frontmatter.Split function through the specialized loaders that build runtime structs.

The Frontmatter Parsing Pipeline

The entire system centers on internal/frontmatter/frontmatter.go, which provides a reusable, permissive parser. The pipeline follows three clear stages:

Stage 1: Splitting the File on Fences

The frontmatter.Split function scans for a leading --- delimiter. When found, everything between the opening and closing fence becomes raw YAML; everything after the closing fence is returned as the markdown body.

// internal/frontmatter/frontmatter.go#L17-L35
func Split(content string) (map[string]string, string) {
    // Looks for ^---\n, extracts until next --- or end
    // Returns empty map + full content if no fence found
}

If no fence exists, the function returns an empty map and treats the entire file as body. This backward-compatible behavior allows legacy markdown files to load without errors.

Stage 2: YAML Decoding and Normalization

The parseYAMLFrontmatter function unmarshals YAML using gopkg.in/yaml.v3, then walks the node tree to produce a flat map[string]string:

YAML Construct How It's Flattened
description: "value" Direct key-value: "description": "value"
metadata:\n type: user Recursively flattened: "type": "user"
allowed-tools: [read, write] Joined with , : "allowed-tools": "read, write"

All keys are normalized via strings.ToLower, so Description: and description: are equivalent. Duplicate keys use "last write wins" resolution.

// Same file, parseYAMLFrontmatter implementation
// Uses addYAMLValue for recursive flattening
// Uses yamlScalarString for scalar extraction

Stage 3: Loader-Specific Field Extraction

Skills and commands each define their own expected key constants and parsing logic in dedicated packages.

Skill Frontmatter Parsing

Skills are loaded in internal/skill/skill.go. The Skill.Parse method (lines 68-99) calls splitFrontmatter, then consults skillFrontmatter* constants to populate a Skill struct.

Known Skill Keys

Constant Frontmatter Key Parsed Value
skillFrontmatterDescription description Direct string
skillFrontmatterName name Falls back to file stem
skillFrontmatterRunAs runas Processed by parseRunAs()
skillFrontmatterModel model Direct string
skillFrontmatterAllowedTools allowed-tools Split by parseAllowedTools()
---
description: Summarise a web page
runas: subagent
model: sonnet
allowed-tools:
  - read_file
  - web_fetch
---

# Skill body

Use the web_fetch tool to retrieve the page and then summarise it.
// internal/skill/skill.go#L68-L99
content, _ := os.ReadFile("my-skill.md")
fm, body := frontmatter.Split(string(content))

skill := Skill{
    Name:         fm["name"],                    // or file stem
    Description:  fm["description"],
    RunAs:        parseRunAs(fm["runas"]),
    Model:        fm["model"],
    AllowedTools: parseAllowedTools(fm["allowed-tools"]),
    Body:         body,
}

The parseAllowedTools function handles the comma-joined string (from YAML sequence) or a plain scalar, normalizing both into a slice.

Command Frontmatter Parsing

Commands follow an identical split-and-extract pattern in internal/command/command.go (lines 200-240), using commandFrontmatter* constants.

Known Command Keys

Constant Frontmatter Key Purpose
commandFrontmatterDescription description UI help text
commandFrontmatterArguments arguments CLI parameter schema
commandFrontmatterExample example Usage demonstration
---
description: Print a greeting
arguments:
  - name: who
    type: string
    description: Who to greet
example: hello --who Alice
---
Hello, {{.who}}!
// internal/command/command.go#L200-L240
fm, body := frontmatter.Split(string(content))

cmd := Command{
    Name:        "hello",                       // from filename
    Description: fm["description"],
    Arguments:   parseArguments(fm["arguments"]), // parses YAML sequence
    Example:     fm["example"],
    Body:        body,
}

The parseArguments function deserializes the YAML sequence into typed Argument structs for CLI validation and UI rendering.

Strict Validation with frontmatter.Decode

For install-time integrity checks, Reasonix provides frontmatter.Decode (lines 37-44) with schema enforcement through struct tags:

type CmdMeta struct {
    Description string `yaml:"description"`
    Example     string `yaml:"example"`
}

var meta CmdMeta
body, err := frontmatter.Decode(content, &meta, frontmatter.DecodeOptions{
    KnownFields: true,  // Reject unknown fields
})
// err != nil indicates malformed or incompatible frontmatter

The KnownFields: true option makes the parser strict, useful for internal/installsource/skill.go when vetting third-party contributions. Runtime loaders prefer the permissive Split for maximum compatibility.

Key Design Decisions

  • Lower-case normalization: Eliminates case-sensitivity bugs across author conventions
  • Flat string map: Keeps the core parser agnostic; semantic interpretation happens in domain packages
  • Comma-join for sequences: Simplifies the common allowed-tools use case while preserving readability
  • Graceful degradation: Missing frontmatter never blocks file loading

Summary

  • Frontmatter parsing in Reasonix uses frontmatter.Split in internal/frontmatter/frontmatter.go to separate YAML metadata from markdown body
  • Three-stage pipeline: split on --- fences → flatten YAML to map[string]string with lower-cased keys → domain-specific extraction
  • Skills parse description, runas, model, allowed-tools, and other fields via internal/skill/skill.go
  • Commands parse description, arguments, example, and more via internal/command/command.go
  • Strict validation available through frontmatter.Decode with KnownFields: true for install-time checks

Frequently Asked Questions

How does Reasonix handle YAML frontmatter without strict schemas?

Reasonix uses the permissive Split function by default, which returns a flat map[string]string with all keys lower-cased. The skill and command loaders then check for specific keys they recognize, ignoring unknown fields. This allows files to include extra metadata for future use without breaking current parsing.

What happens if a skill or command file has no frontmatter?

The frontmatter.Split function returns an empty map and the full file content as body. Loaders detect missing fields and apply sensible defaults: the skill name falls back to the file stem, runas defaults to a safe value, and allowed-tools becomes an empty slice. Legacy plain-markdown files load without errors.

Can I use nested YAML structures in Reasonix frontmatter?

Yes, but with flattening. The addYAMLValue function recursively flattens nested mappings, so metadata:\n type: user becomes type: user at the top level. Sequences are converted to comma-separated strings. Deeply nested structures beyond this are flattened aggressively—design your frontmatter for the flat map model.

Why does Reasonix join array values with commas instead of preserving slices?

The map[string]string contract keeps the core parser simple and serializable. The comma-join convention ("read, write") covers the dominant use case (allowed-tools) while allowing loaders to split on demand. For complex structured data like command arguments, dedicated parse* functions reconstruct typed structs from the YAML source.

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 →