How the Frontmatter System Functions for Memory and Fact Storage in Reasonix
Reasonix stores "facts" as plain Markdown files with YAML frontmatter metadata blocks, enabling human-readable, versioned, and backward-compatible persistent storage for auto-memory entries.
The DeepSeek-Reasonix repository implements a specialized frontmatter system that bridges structured data and free-form content. This design allows the AI assistant to save, retrieve, and migrate memories as simple files while preserving rich metadata like revision history, timestamps, and access scopes.
Core Frontmatter Parser Architecture
The frontmatter handling is centralized in internal/frontmatter/frontmatter.go. This package provides a robust, fault-tolerant parser that treats Markdown files as first-class data objects.
Split: Fence Detection and Body Extraction
The Split function detects and extracts the optional leading ----fenced YAML block. It returns a map[string]string of lower-cased keys together with the remaining body content.
// Implementation: internal/frontmatter/frontmatter.go#L27-L34
func Split(src string) (map[string]string, string)
The parser deliberately tolerates malformed input:
- Missing opening fence → entire input treated as body
- Unclosed fence → entire input treated as body (no partial parses)
- Empty frontmatter → empty map, full body preserved
Decode: Typed Schema Validation
For callers requiring strict validation, Decode performs the same fence detection but unmarshals YAML into a typed struct via yaml.Node traversal:
// Implementation: internal/frontmatter/frontmatter.go#L37-L50
func Decode(src string, out interface{}) (string, error)
Low-Level Fence Handling
The helper splitRaw (lines 53-64) implements the core fence-searching logic. It locates opening --- and closing --- delimiters, with safety checks for unterminated blocks to prevent data corruption on malformed files.
YAML Flattening for Legacy Compatibility
Once raw YAML is isolated, parseYAMLFrontmatter (lines 67-86) walks the yaml.Node tree with a specific flattening strategy:
- Nested maps: flattened to dot-notation keys (or joined paths)
- Sequences: joined into comma-separated strings
This preserves legacy "flat" frontmatter expectations while supporting richer YAML structures. For example:
allowed-tools: ["read_file", "grep"]
becomes allowed-tools: read_file, grep in the parsed map.
Memory Subsystem Integration
The memory store (internal/memory) wraps the frontmatter parser in a thin abstraction for fact persistence.
Loading Facts from Storage
In internal/memory/store.go lines 806-810, the store implements:
func splitFrontmatter(s string) (map[string]string, string) {
return frontmatter.Split(s)
}
When loading, the store maps normalized keys to Memory struct fields:
| Frontmatter Key | Memory Field |
|---|---|
id |
ID |
revision |
Revision |
created_at |
CreatedAt |
updated_at |
UpdatedAt |
name |
Name |
title |
Title |
description |
Description |
type |
Type |
scope |
Scope |
Because keys are lower-cased and structures flattened, the loading code uses simple map[string]string lookups without complex YAML handling.
Saving Facts to Storage
The render function in internal/memory/render.go (lines 38-77) handles serialization. It builds a memoryFrontmatter struct from the Memory object, then uses yaml.NewEncoder to generate properly escaped frontmatter:
type memoryFrontmatter struct {
ID string `yaml:"id"`
Revision int `yaml:"revision"`
CreatedAt time.Time `yaml:"created_at"`
UpdatedAt time.Time `yaml:"updated_at"`
Name string `yaml:"name"`
Title string `yaml:"title"`
Description string `yaml:"description"`
Type string `yaml:"type"`
Scope string `yaml:"scope"`
}
The rendered frontmatter block is concatenated with the body and written to disk as a complete Markdown file.
Version Compatibility Routing
The previousReleaseRoutingType function (lines 79-94) maintains backward compatibility with legacy directory layouts by inspecting type and scope fields. This ensures older Reasonix binaries correctly locate fact files after schema evolution.
Practical Code Examples
Creating a Memory File Manually
---
id: fact-123
revision: 1
created_at: 2024-01-01T12:00:00Z
updated_at: 2024-01-01T12:00:00Z
name: example-fact
title: Example Fact
description: This fact explains the frontmatter system.
type: user
scope: project
---
The body of the fact can contain any markdown you like.
Parsing an Existing Memory File
import (
"os"
"path/to/reasonix/internal/memory"
)
func loadMemory(path string) (*memory.Memory, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
fm, body := memory.SplitFrontmatter(string(data))
mem := memory.Memory{
ID: fm["id"],
Revision: parseInt(fm["revision"]),
CreatedAt: parseTime(fm["created_at"]),
UpdatedAt: parseTime(fm["updated_at"]),
Name: fm["name"],
Title: fm["title"],
Description: fm["description"],
Type: memory.Type(fm["type"]),
Scope: memory.FactScope(fm["scope"]),
Body: body,
}
return &mem, nil
}
Rendering and Saving a Memory
mem := memory.Memory{
ID: "fact-123",
Revision: 2,
Name: "example-fact",
Title: "Example Fact (updated)",
Description: "Updated description of the frontmatter system.",
Type: memory.TypeUser,
Scope: memory.FactScopeProject,
Body: "New body content with **markdown**.",
}
content := memory.Render(mem, mem.Name)
err := os.WriteFile(
store.Path(mem.Name),
[]byte(content),
0o644,
)
Key Design Decisions
- Human-readable format: Facts are plain Markdown files editable in any editor
- Graceful degradation: Malformed frontmatter never corrupts fact content
- Schema evolution: Typed serialization via
yaml.v3handles special characters safely - Legacy support: Flattened key structure accommodates both old and new data shapes
Summary
- Reasonix stores memories as Markdown files with YAML frontmatter, separating metadata from content
- The
frontmatter.Splitandfrontmatter.Decodefunctions ininternal/frontmatter/frontmatter.goprovide fault-tolerant parsing with backward-compatible flattening memory.SplitFrontmatterwraps the parser for loading, mapping normalized keys to struct fieldsmemory.Renderusesyaml.NewEncoderfor safe, typed serialization when saving- The system tolerates missing fences, unclosed blocks, and nested YAML structures without data loss
Frequently Asked Questions
What happens if a memory file has no frontmatter delimiter?
The parser treats the entire file as the body content. As implemented in splitRaw (lines 53-64), missing opening --- fences cause the full input to pass through unmodified, with an empty metadata map returned.
How does Reasonix handle special characters in metadata values?
The render function uses yaml.NewEncoder from yaml.v3, which properly escapes colons, quotes, newlines, and other YAML-significant characters. This prevents injection attacks and syntax errors in generated files.
Can I manually edit fact files without breaking Reasonix?
Yes. The frontmatter system is designed for human editability. The Split function tolerates minor formatting variations, and the flattened key structure ensures that standard YAML edits remain compatible with the parser's expectations.
What fields are required in a valid memory frontmatter?
The parser itself enforces no required fields—it returns whatever keys are present. However, the memory subsystem expects id, revision, created_at, updated_at, name, type, and scope for proper operation. Missing fields receive zero values during loading.
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 →