MarkdownAI
June 3, 2026 ยท View on GitHub
MarkdownAI
the AI workflow engine.
What is MarkdownAI
MarkdownAI is a phase-aware AI workflow engine for Claude and AI agents. It phases runbooks, injects live data, and delivers scoped context one step at a time. Documents mix prose with executable directives - data sources, control flow, file ops, test runs, DB queries - so the engine renders exactly what the agent needs for the current step, nothing more.
The directive grammar in one snapshot
# 1. Self-closing (atomic, no body, no continuation)
@import ~/path/to/macros.md /
@touch path="src/foo.ts" /
@on-complete next-phase /
# 2. Block with attributes (no body)
@db
using="mdd"
find="features"
where='id == "X"'
label=feature
@db-end
# 3. Block with attributes + body
@phase 0_branch_check
required=true
>
@call branch-guard /
@on-complete 0_5_repo_version_check /
@phase-end
Familiar to anyone who's written HTML / JSX / Vue / Svelte / Astro - @ instead of < so directives don't conflict with embedded HTML in markdown.
What's new in v2
Unified directive grammar
Every directive uses the three forms above. The v1 split between "block" directives (closed with bare @end) and "inline" directives (single-line only, silently dropped continuations) is gone. The close tag carries the directive name, so nested blocks read clearly:
@phase X
@if {{ ready }}
@foreach f in {{ files }}
- {{ f }}
@foreach-end
@if-end
@phase-end
Bare @end, @endif, @endswitch, and @on complete -> X are no longer accepted. The migration tool rewrites existing v1 files mechanically.
Synchronous MongoDB queries
@db using="..." actually hits Atlas or self-hosted Mongo now. In v1 the directive was stubbed and emitted an "async execution required" warning. v2 runs read-only queries through a sync worker so the result is available in the same render pass.
Struct labels
@db ... as=row label=feature captures the row into ctx.data[label], so {{ feature.source_files }} dot-access works on real arrays and nested objects. Same shape works for @read and any directive that materializes data.
New sandbox builtins
parse_brief, read_section, extract_paths, now_iso, to_json, truncate, parse_iso_ms, uuid_v4, allowed. Available inside @if conditions and {{ }} interpolations alike.
Cross-call closure for skill flows
A skill_session_id keys per-(session x document) state inside the MCP server. @set values persist across resolve_phase calls in multi-phase flows, so a skill can collect values in phase 1 and read them back in phase 5 without round-tripping through the host.
Plugin loader and @markdownai-detect
Framework descriptors (mdd.plugin.md, others) declare project layout at render time. Documents pull layout facts from the descriptor instead of guessing - no more layout-inference hallucinations when the AI hasn't seen the project before.
Reusable partials with bound data
@template ./row.md data=<expression> / inlines another MarkdownAI document at the call site and binds it to a data context, like a partial in Angular or Vue. @data <name> ... @data-end composes a single object from any in-scope values (db results, set variables, env fallbacks) using <key> = <expression> assignments, dot-notation for nested keys, and ...<expression> spreads. Inside the partial, the bound value is accessible as {{ data.* }} (or {{ <name>.* }} via as=<name>). Reads inherit from the caller's scope; writes stay local, so the same partial can be called repeatedly inside @foreach without name collisions.
@touch directive
Idempotent empty-file creation for scaffolding. Safe to re-run.
Interpolation in file ops
@touch, @update-frontmatter, @render-template, @check, and @test all expand {{ }} correctly in paths, commands, and arguments. v1 only honoured interpolation in a subset of attributes.
Test runners on the default allowlist
npx vitest, npx playwright, pnpm test, tsc, and other common test commands work in @check and @test blocks without manual ~/.markdownai/security.json edits.
Package layout
| Package | What it does | Path |
|---|---|---|
@markdownai/parser | directive grammar + AST | packages/parser |
@markdownai/engine | executes directives, renders documents | packages/engine |
@markdownai/renderer | source-output formatters (table/list/row/json/...) | packages/renderer |
@markdownai/mcp | Model Context Protocol server (11 tools) | packages/mcp |
@markdownai/core | mai CLI | packages/core |
A VS Code extension (markdownai on the marketplace) provides syntax highlighting, snippets, and diagnostics for .md files that start with @markdownai.
Quickstart
Install the CLI globally:
npm install -g @markdownai/core
Create a small doc - hello.md:
@markdownai v2.0
# Project status
Database has @count using="local" find="users" / users right now.
@if {{ env.NODE_ENV == "production" }}
Running in production mode.
@if-end
Render it:
mai render hello.md
The same file renders through the MCP server when AI assistants use the mcp__markdownai__render tool - the document arrives with directives already resolved, no extra round-trips needed.
Migration from v1
v2 is a breaking syntax change. Pin to ^2.0.0 (v1 stays on ^1.x and keeps working). Run the migration tool once over each v1 file:
node ~/projects/markdownai/packages/parser/scripts/migrate-v1-to-v2.mjs <file> --in-place
The script is idempotent - re-running on a v2 file is a no-op. Full grammar spec is in MDs/markdownai-spec-v2.0.md.
Security model
The engine enforces shell, HTTP, DB, and filesystem allowlists at ~/.markdownai/security.json. Defaults are restrictive: shell starts with read-only commands plus common test runners; HTTP is off; DB is off. Users opt in to broader access by editing the config. See the engine README for the full policy.
Status
v2.0.0. Currently in use for MDD v2.
License
MIT.