Skip to content

feat: MDS Runtime — on-read compilation via virtual filesystem #82

Description

@dean0x

Summary

MDS currently compiles .mds templates into static .md files at build time. This feature makes MDS a runtime: when any process reads a .md file containing MDS directives, it transparently receives the compiled output. No source/target split, no build step — the file on disk contains MDS, but every reader sees rendered markdown.

Example

File on disk (CLAUDE.md):

# Project Context

@if git.branch == "main":
You are on the stable branch. Be conservative with changes.
@else:
You are on feature branch: {{git.branch}}
@end

@if system.os == "macos":
Run tests with: `cargo test --workspace`
@else:
Run tests with: `cargo test --workspace --target x86_64-unknown-linux-gnu`
@end

What Claude Code (or any process) reads:

# Project Context

You are on feature branch: feat/cool-thing

Run tests with: `cargo test --workspace`

Motivation

AI agents, LLM workflows, and developer tools all consume markdown files — CLAUDE.md, memory systems, prompt libraries, documentation. These files would benefit from being dynamic:

  • Conditional sections based on git branch, OS, environment
  • Context-aware instructions that adapt to who/what is reading
  • Template composition without a separate build step

The compilation engine already exists in mds-core. The missing piece is a runtime trigger that intercepts file reads and compiles on the fly.

Architecture

Two platform-native implementations sharing mds-core as the compilation engine:

Linux:                            macOS:
┌──────────────────┐              ┌──────────────────────┐
│    mds-fuse      │              │  mds-fs (Swift)      │
│  (pure Rust)     │              │  FSKit extension     │
│  /dev/fuse proto │              │  (macOS 15+)         │
└────────┬─────────┘              └──────────┬───────────┘
         │                                   │ C FFI
         ▼                                   ▼
┌──────────────────┐              ┌──────────────────────┐
│    mds-core      │              │     mds-cffi         │
│    (Rust)        │              │  (Rust → C API)      │
└──────────────────┘              └──────────┬───────────┘
                                             ▼
                                  ┌──────────────────────┐
                                  │      mds-core        │
                                  └──────────────────────┘

Linux — FUSE via /dev/fuse (pure Rust)

  • Use the fuse3 crate, which implements the FUSE protocol directly in Rust — no libfuse C dependency
  • The kernel FUSE module is built into the Linux kernel, so there are zero external dependencies
  • Passthrough filesystem: all operations delegate to the real filesystem except read() on .md files
  • On read(): check if file contains MDS directives → compile with context → return compiled bytes
  • Handle size discrepancy (compiled output differs from source) via correct getattr reporting

macOS — FSKit (native, no kext)

  • Use Apple's FSKit framework (macOS 15+), the official userspace filesystem API
  • No kernel extension, no macFUSE, no third-party dependencies
  • Thin Swift layer (~500-800 lines) that implements the FSKit filesystem interface
  • Calls into mds-core via C FFI through the mds-cffi crate
  • Ships as a macOS system extension

C FFI bridge (mds-cffi)

  • Thin C API over mds-core: mds_compile(source, context) -> char*
  • Enables the Swift FSKit extension to call into Rust
  • ~200 lines — just marshaling strings and context across the FFI boundary
  • Also opens the door for future integrations (Python ctypes, etc.)

Context System

Templates at read time need runtime context. A .mdscontext.toml at the mount root declares what's available:

[providers]
git = true          # branch, commit, remote, dirty
env = true          # all env vars (or allowlist with env.allow = ["HOME", "USER"])
system = true       # os, arch, hostname, user

[static]
project = "mds"
tier = "production"

Built-in providers

Provider Variables Refresh
git git.branch, git.commit, git.remote, git.dirty On every read (cached, invalidated by mtime of .git/HEAD)
env env.HOME, env.PATH, etc. On every read
system system.os, system.arch, system.hostname, system.user On mount (static)

Access in templates

@if git.branch == "main":
...
@end

Current user: {{system.user}}
Platform: {{system.os}}/{{system.arch}}

Directory overrides

Place another .mdscontext.toml deeper in the tree to override or extend context for a subtree. Child contexts inherit from parent and can override specific values.

CLI

mds mount <source> <mountpoint>       # mount and compile on read
mds mount status                       # show active mounts
mds mount install                      # install as system service (auto-start on boot)
mds mount uninstall                    # remove system service

Service Integration

Users should not have to manually run mds mount after every reboot.

macOS — launchd

  • mds mount install generates and loads a launchd plist
  • Auto-start on login, restart on crash
  • mds mount uninstall removes the plist and unmounts

Linux — systemd

  • mds mount install generates and enables a systemd user unit
  • Auto-start on login, restart on crash
  • mds mount uninstall disables and removes the unit

Caching

  • Cache compiled output keyed by hash(source_bytes) + hash(context_state)
  • Invalidate on source file mtime change or context state change (e.g., git branch switch detected via .git/HEAD mtime)
  • LRU eviction with bounded memory
  • Passthrough for files with no MDS directives (detected by quick scan for @if, @for, @define, {{)

New Components

Component Language Location Estimated size
mds-cffi Rust crates/mds-cffi/ ~200 lines
mds-fuse Rust crates/mds-fuse/ ~800 lines
mds-fs Swift darwin/mds-fs/ ~500-800 lines
Context system Rust crates/mds-core/ (or new crate) ~400 lines
Service integration Rust CLI extension ~300 lines
Cache layer Rust Shared between fuse/cffi ~150 lines

Estimated Effort

Phase Time
Context system design + providers 1 week
mds-cffi (C FFI bridge) 1 day
mds-fuse (Linux, pure Rust) 3-4 days
darwin/mds-fs (Swift FSKit) 3-4 days
Context-aware caching 2 days
CLI + service integration (launchd/systemd) 3-4 days
Testing (mount/unmount cycles, concurrent reads, context changes) 3-4 days
Total ~4 weeks

Risks

Risk Impact Mitigation
FSKit API instability (macOS 15 is year one) API changes break macOS builds Pin to specific macOS SDK, abstract behind trait
Compiled output size ≠ source size Readers may truncate or over-read Report compiled size in getattr, not source size
Concurrent reads during context change Inconsistent output Atomic context snapshots per read
Deep/recursive MDS templates on hot path Latency on file reads mds-core already has MAX_* limits; add per-read timeout
FSKit requires macOS 15+ Excludes older macOS versions Document minimum version; consider macFUSE fallback as opt-in

Non-goals (for this issue)

  • Network/remote context providers (future work)
  • Write interception (editing compiled output and saving back as MDS)
  • Windows support (no clear userspace FS path yet)
  • Custom plugin/extension providers (start with built-ins, extend later)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions