# mdflow turns markdown files into agent commands and keeps the roster in your repository

> mdflow is a multi-backend CLI that runs .md files against Claude, Codex, Gemini, Copilot, pi and other engines, with the engine chosen by filename, frontmatter, config or flag. It adds a flow roster in ./flows, behavioural evals kept in separate files, and a proposal-first evolve loop that never edits your source without an explicit apply.

**johnlindquist/mdflow** — Multi-backend CLI for executable markdown prompts. Run .md files against Claude, Codex, Gemini, or Copilot.

- Repository: https://github.com/johnlindquist/mdflow
- Website: https://mdflowdev.vercel.app
- Stars: 603 · Forks: 44
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/johnlindquist-mdflow

## The command name is the filename

mdflow's central idea is that markdown files become first-class CLI commands. Write a prompt in markdown, run it like a script, and the command is inferred from the filename. The README opens with three lines that show the whole model:

```bash
review.md                        # Runs on your resolved engine (default: pi)
review.claude.md                 # Pin an engine in the filename
git diff | explain.md            # Pipe through any command
```

A bare review.md runs on whichever engine your environment resolves to. The .claude.md suffix pins the engine in the filename itself, which is the pattern most teams will end up using, because the suffix makes the engine visible in a directory listing and in a pull request diff. The third example shows the stdin side of the model: a diff piped into a markdown file that acts as a pipe-through command.

The package is a Bun and TypeScript project with two binaries. The bin map in package.json points mdflow at ./bin/mdflow.mjs and md at ./src/index.ts, so the short md invocation is the real entry point and the two names are the same program.

```json
  "bin": {
    "mdflow": "./bin/mdflow.mjs",
    "md": "./src/index.ts"
  },
```

That dual naming is deliberate. The README's coding agent section tells agents to run command -v md || npm install -g mdflow, which keeps the shell command short enough to type inside an agent turn.

## init builds a roster without calling any engine

The setup command is one line:

```bash
npx mdflow init
```

The README is careful about what that command does. One command safely creates a starter ./flows roster and .mdflow.yaml with zero engine invocations, and repeat runs leave an existing roster untouched. That is the property that matters: initialising a repository is a deterministic local write, not a request to a model, so it can run in a script or a first commit hook without spending anything.

After init, bare md opens a single searchable Flow Workbench. It contains project flows, every globally installed flow, and runnable markdown flows found directly on PATH, with clear PROJECT, GLOBAL, INSTALLED and PATH provenance. If the project has no owned roster, the global and PATH flows remain immediately runnable, and a Set up project flows row opens guided setup, a deterministic starter roster, or the printable setup guide without leaving the Workbench.

The other route into the same place is md init --guided, which runs the repo-tailored setup conversation directly, or md init --print-guide, which copies the same guide into your own agent session so another tool can drive the setup.

For teams that want agents to find the flows automatically, the opt in is md roster sync --agents. It maintains one marker-managed mdflow block in AGENTS.md and CLAUDE.md so coding agents discover the roster and hand matching tasks off to flows, and it never touches your text outside the markers. Every guidance write requires that explicit consent, and plain md roster sync is README-only and merely reports drift. That consent boundary is a good design: the tool will not silently rewrite your agent instructions.

## Engine resolution is an ordered list, and the order is printed

The README calls engines environment rather than filename ceremony, and then gives the resolution order explicitly: the --engine flag, then the MDFLOW_ENGINE environment variable, then the filename, then frontmatter engine:, then config engine:, and finally the built-in default, which is pi.

That is six levels, and the point of listing them is that you can predict what will run. The README also notes that implicit choices print a dim line such as review.md to pi (engine: default), so the resolution is inspectable rather than magic.

There is one important exception in the same paragraph. Files with no frontmatter and no explicit engine are documents, and md README.md prints instead of executing. Given a tool whose whole premise is that any markdown file is a command, that exception is a necessary guard against a stray README being sent to a model.

The frontmatter key was also renamed. It is now engine:, and the older tool: and _tool: keys still work, with a warning. A migration that breaks silently is worse than one that complains, so the warning is the right call.

Two more engine facts matter in practice. pi is the default engine, runs without ambient extensions, skills or context files, and bridges your Codex CLI login automatically. And new engines have been added, cursor-agent and agy, which the README describes as Google Antigravity, the gemini CLI successor, with the old gemini adapter remaining for Code Assist Standard and Enterprise.

## Isolation strips context, and the README says it is not a sandbox

This is the part of the project to read carefully, because the word isolation can carry more weight than it should.

The README states that engine context isolation is the default where supported, and that flows run with the engine's ambient context stripped using the engine's own verified flags. The flags are named: claude --safe-mode --no-session-persistence, codex --ignore-user-config --ephemeral --skip-git-repo-check with a project_doc_max_bytes=0 config, gemini --extensions none, copilot --no-custom-instructions --disable-builtin-mcps, opencode --pure, and pi's own context isolation flags.

Then it draws the line plainly. This does not sandbox the host filesystem, network, environment, or inline shell commands; those remain explicit capabilities of the flow. Engines with no controls, listed as droid, cursor-agent and agy, run ambient and warn only on an explicit _isolated: true, never pretending otherwise.

That last clause is the detail that makes the feature usable. A tool that silently ran an engine with full ambient context while claiming isolation would be worse than having no option, and mdflow instead warns.

Skills, MCP servers and context a flow genuinely needs are declared explicitly in frontmatter, and _isolated: false opts back into ambient behaviour. The system prompt is also a first class key: _system-prompt: replaces the engine's system prompt and _append-system-prompt: appends to it, translated per engine through flags, config keys or an environment variable depending on the engine. Engines with no mechanism for it fail the run rather than dropping your prompt silently.

## Evals live in their own files and print cost before running

The eval mechanism is the part of mdflow that turns a prompt into something you can hold to a standard. The README is blunt about the gap it fills: if a guardrail is not covered by an eval, it is a wish.

Mechanically, md eval flows/review.md runs flows/review.eval.ts. The behavioural cases run in isolated temporary workspaces, the cost is printed before running, and the results land in a trust ledger. The eval file is a separate TypeScript file next to the flow, and the examples directory has matching pairs such as commit.claude.md with commit.claude.eval.ts and logo-grid.claude.md with logo-grid.claude.eval.ts.

The split between the flow and its eval matters. A markdown flow is the thing you want readable in a pull request, while the eval is executable code that asserts behaviour. Keeping them apart means tightening a prompt does not mean rewriting assertions, and running an eval does not require the flow to contain anything but prose.

The agent-facing section of the README annotates which commands are free and which cost money. md doctor --json is described as free, static and read-only project diagnosis, md explain flows/<name>.md --json as a read of the flow, and md eval flows/<name>.md --plan as free, with a real eval needing separate approval. The same section notes that eval and hook sidecars are executable local code, and that a registry install adds one flow and not trusted sidecars.

For a coding agent operating in the repository, the block the README gives is short enough to use verbatim:

```bash
command -v md || npm install -g mdflow
md doctor --json                 # FREE, static, read-only project diagnosis
npx mdflow init --yes            # LOCAL WRITE, deterministic starter roster
md doctor --json
md explain flows/<name>.md --json
md eval flows/<name>.md --plan   # FREE; a real eval needs separate approval
```

The labelling is unusual for a README and useful: it tells the reader which of these commands touch the model and which do not.

## Evolution produces a proposal, and the file stays byte-identical until apply

The evolve loop is the most interesting design decision in the project, because it refuses to make the usual move of editing your prompt automatically.

The workflow starts with md feedback, which records durable evidence against a flow, for example a note that a review missed a race condition. Then md evolve plan previews the proof, the capabilities, the writes and the bounded invocation cost, and it does so for free. md evolve propose then drafts and evaluates private off-path snapshots.

The critical line is that the source stays byte-identical until a separate md evolve apply <run-id> is run. And the standard of proof is narrow: mdflow says verified improvement only when a feedback-linked case fails on the current version and passes on the proposal, and otherwise a green candidate is described as merely regression-safe. The legacy evolve: auto setting now means queued proposal-only work and never unattended application.

So there are three labels rather than two. A change can be an improvement with evidence, it can be regression-safe, or it can be an unattempted proposal. Most prompt tooling collapses this into a pass or fail, and the middle category is where a regression quietly ships.

The documentation set backs this up. The README points to docs/evolve.md for the normative change-with-proof protocol, docs/V3-FLOWS.md as the historical v3 design record, docs/public-api.md for the stable CLI contract, and GUIDE-NEW-FEATURES.md for the newer workflows such as _steps, structured outputs via _output, context providers with @git:diff, the flow registry, --json mode and run telemetry.

There is also a DESIGN.md and a VISION.md at the repository root alongside CHANGELOG.md, SECURITY.md, CONTRIBUTING.md, CLAUDE.md, instructions/ and skills/ directories, a site/ directory, and a smoke-tests/ directory. Security-sensitive behaviour is documented separately in SECURITY.md, which is the right place to look before enabling hooks.

## A Bun project with generated facts and generated guidance

The repository is built on Bun rather than Node.js, and the toolchain is visible in the scripts. The test command is bun test --bail=1 and typecheck is bunx tsc -p tsconfig.json --noEmit, with a tsconfig.json at the root.

The interesting part is the pair of generated artefacts. There is a facts script and a facts:check script, and a guidance script with a guidance:check counterpart. The verify script chains typecheck, tests, facts:check, guidance:check and a package check, and the postversion hook regenerates facts and guidance. A package smoke test runs as its own script.

That structure is a deliberate answer to a specific failure mode in agent tooling. When a CLI's documented behaviour is maintained by hand, the docs drift from the code, and an agent following stale documentation does the wrong thing. Generating both the facts and the agent guidance from the source, and then checking them in CI, means the claims the README makes are the claims the code makes.

The remaining scripts show the isolation work is tested rather than asserted. verify:codex-isolation runs a specific set of test files, the ones covering codex hooks, isolation, the command runner and hooks integration, and a separate probe script exercises the codex isolation path directly.

The release setup uses semantic release, with the changelog, commit analyser, git, github and npm plugins, and the project version is 4.9.0 with releases v4.7.0, v4.8.0 and v4.9.0 published in July 2026. The last push was on 2026-08-30 and the repository is not archived. The licence is MIT, recorded in the package.json, so there is no copyleft obligation on the tool itself, which is a licensing note rather than legal advice.

The alternative worth naming is writing the prompts yourself and calling each engine's CLI directly. The difference in approach is that mdflow standardises the invocation, the engine isolation flags, the eval harness and the change protocol in one place, so a reviewer can read a markdown file and a matching eval in a pull request. If your use is a single prompt run occasionally, that standardisation is overhead, and the raw engine CLIs are the shorter path.

## Conclusion

Adopt mdflow if you want repeatable agent jobs that live as reviewable markdown in your repository rather than as ad hoc commands, and if you already use one of the supported engines. Do not adopt it if you need a sandbox, because the README states plainly that engine context isolation is not a host filesystem, network, process, environment or credential sandbox. Verify first which engine your environment resolves to, since the default is pi, that the eval for each flow exists as a separate file before you rely on md eval as a guard, and that your flows declare whatever skills or context they need rather than assuming ambient context survives.

## FAQ

### How do I install and initialise mdflow?

Run npx mdflow init, which creates a starter ./flows roster and .mdflow.yaml with zero engine invocations and leaves an existing roster untouched on repeat runs. The CLI is also published as an npm package with two binaries, mdflow and md, and the agent-facing check is command -v md || npm install -g mdflow.

### How does mdflow decide which engine to run a flow on?

The order is the --engine flag, then the MDFLOW_ENGINE environment variable, then the filename, then frontmatter engine:, then config engine:, then the built-in default of pi. Implicit choices print a line such as review.md to pi (engine: default).

### Does mdflow sandbox the engines it runs?

No. The README states that engine context isolation is not a host filesystem, network, process, environment or credential sandbox, and that those remain explicit capabilities of the flow. Isolation strips ambient context using each engine's own flags, and engines with no controls run ambient and warn.

### How do evals work in mdflow?

md eval flows/review.md runs the neighbouring flows/review.eval.ts file. Behavioural cases run in isolated temporary workspaces, the cost is printed before the run, and results are recorded in a trust ledger. md eval with the --plan flag is free and a real eval needs separate approval.

### Can mdflow edit my prompt files automatically?

Not without an explicit step. md evolve plan previews work for free, md evolve propose drafts and evaluates it, and the source stays byte-identical until you run md evolve apply with a run id. The legacy evolve: auto setting now means queued proposal-only work, never unattended application.

## Sources

- [johnlindquist/mdflow on GitHub](https://github.com/johnlindquist/mdflow)
- [License: MIT](https://github.com/johnlindquist/mdflow/blob/main/LICENSE)
- [Project website](https://mdflowdev.vercel.app)
- [README](https://github.com/johnlindquist/mdflow/blob/main/README.md)
- [Releases](https://github.com/johnlindquist/mdflow/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/johnlindquist-mdflow
