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

> Learn how Reasonix parses YAML frontmatter for skills and commands. Discover its three-stage pipeline: split, unmarshal, and load for efficient metadata extraction.

- Repository: [YHH/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix)
- Tags: deep-dive
- Published: 2026-08-05

---

**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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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.

```go
// 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.

```go
// 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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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()` |

```yaml
---
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.

```

```go
// 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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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 |

```yaml
---
description: Print a greeting
arguments:
  - name: who
    type: string
    description: Who to greet
example: hello --who Alice
---
Hello, {{.who}}!

```

```go
// 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:

```go
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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/skill/skill.go)
- **Commands** parse `description`, `arguments`, `example`, and more via [`internal/command/command.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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.