Skip to main content

runxmd — add state and execution to your .md files. .xmd is plain Markdown with a run-intent label.

Project description

runxmd — run your Markdown files

The simplest thing: write - @python, - @node, - @go (or any of 10 language plugins) directly in a .md file. Run it. Get a rendered output file with your prose intact and every code block replaced by its result. No new syntax. No @workflow required to get started.

PyPI version Downloads status deps python


The idea in one sentence

runxmd adds state and execution to .md files. You keep writing Markdown exactly as you do today — it still renders in GitHub, VS Code, and every viewer. The difference is that bare - @plugin steps execute directly, output renders back inline, and @memory persists state across runs. Static doc → stateful doc. Same file.

The bigger goal — save tokens, stop making models recompute. When a document already carries the results of its code (the render output), an LLM reading it doesn't have to mentally execute the snippets, guess at outputs, or burn tokens reasoning through logic it can't actually run. The answer is already in the file. runxmd turns "here's code, imagine what it prints" into "here's code and exactly what it printed" — so the model reads facts instead of computing them. Deterministic output, computed once by the real interpreter, reused by every reader (human or model) forever after.


The simplest possible file

No @workflow. No @memory. No annotations at all — just prose and code steps.

# Analysis

This document runs three Python snippets and renders the results inline.

- @python
  run: |
    print("=== Arithmetic ===")
    x = 10
    print("x =", x)

This section explains what the next snippet does.
Feel free to write as much prose as you like between steps.

- @python
  run: |
    print("=== String ===")
    msg = "hello world"
    print(msg.upper())

Final note before the last snippet.

- @python
  run: |
    print("=== List ===")
    nums = [1, 2, 3, 4, 5]
    print("sum:", sum(nums))

Closing prose — steps are done.

Run it:

runxmd run analysis.md

You get analysis_render.md — your original document with every code block replaced by its output, prose preserved exactly as written:

# Analysis

This document runs three Python snippets and renders the results inline.

=== Arithmetic ===
x = 10

This section explains what the next snippet does.
Feel free to write as much prose as you like between steps.

=== String ===
HELLO WORLD

Final note before the last snippet.

=== List ===
sum: 15

Closing prose — steps are done.

The source file is never modified. The render file is your shareable, LLM-readable output. Re-run any time to refresh it.


Worked examples in this repo

Two complete, runnable showcases live in examples/showcase/. Each is a real .md you can run, paired with its committed render output so you can see exactly what runxmd produces — the render file is the token-saving artifact: a model reads the precomputed results instead of trying to execute the code in its head.

1. Polyglot capability showcase

examples/showcase/readme_showcase.mdreadme_showcase_render.md

Runs Python, Node.js, Perl, and PowerShell — inline snippets and external scripts — at basic / medium / advanced levels, with tables, blockquotes, and headings woven between the steps to prove all Markdown survives untouched.

cd examples/showcase
runxmd run readme_showcase.md

2. File-existence guardrails

examples/showcase/file_checks_test.mdfile_checks_test_render.md

The common "does this file/dir exist before I proceed?" guardrail at three levels: a single inline check, a multi-file inline check, and an advanced external script that audits a whole directory and prints a PASS/FAIL summary.

Before (source — code the reader would have to mentally execute):

## Level 1 — Simple: does a specific file exist?

- @python
  run: |
    import pathlib
    target = pathlib.Path("scripts/basic.py")
    if target.exists():
        print(f"  FOUND    : {target}")
        print("  GUARDRAIL PASS: required file is present")
    else:
        print("  GUARDRAIL FAIL: required file is missing")

After (file_checks_test_render.md — the answer is already in the file):

## Level 1 — Simple: does a specific file exist?

=== Simple File Check ===
  FOUND    : scripts\basic.py
  Size     : 648 bytes
  Is file  : True

  GUARDRAIL PASS: required file is present

The prose, heading, and tables are identical between the two — only the - @python block is replaced with its real, computed output.


Output modes

Every run produces an output file automatically. You control the format with an @on_done hook — or leave it out to get the default.

Hook in @on_done Output file Content
(none — default) {stem}_render.md Prose kept, steps replaced with results, @sections stripped
render / render(name.md) same, explicit Same as default, named
results / results(name.md) {stem}_results.md Step outputs only — no prose, no code, no @sections
write / write(name.md) {stem}_output.md Full replica with result: fields injected into each step

Default (render) is best for sharing and LLM consumption — it reads like a notebook. results is best when you want only the raw output. write is best when you want to re-run the file and see diffs over time.

Example — explicitly choose render:

@on_done
render(reports/analysis_rendered.md)

Example — output only:

@on_done
results

Install

pip install runxmd

Zero third-party dependencies — pure Python standard library (≥ 3.9).

Or from source:

git clone https://github.com/shubham10divakar/xmd.git
cd xmd
pip install -e .

Running the tests

pip install -e ".[test]"
pytest

Platform compatibility

runxmd runs on Windows, Linux, and macOS — the core runtime is pure Python stdlib with no platform-specific code.

Platform Status Notes
Windows Full support
Linux Full support
macOS Full support

PowerShell note: On Windows, @powershell uses powershell -ExecutionPolicy Bypass. On Linux and macOS, it uses pwsh (PowerShell Core) — install from https://aka.ms/install-powershell if needed.


Security & trust model

runxmd executes code embedded in a Markdown file. Treat a runnable .md exactly as you would treat a shell script: only run files you trust.

What this means in practice:

  • runxmd run file.md executes the code inside it. @python, @node, @shell, the *_script plugins, etc. all run real commands on your machine with your permissions. There is no sandbox.
  • Only run .md files you wrote or have reviewed. A document from an untrusted source can contain - @shell / - @python steps that do anything you could do from a terminal. This is the same trust decision as bash somescript.sh.
  • The source file is never modified by default. Output goes to a new _render.md / _results.md / _output.md file. Memory write-back into the source only happens with the explicit --write-back flag.
  • @llm and agent --autonomous run model-generated commands. With --autonomous, the LLM both writes and runs the steps — that is arbitrary code execution directed by a model. Use it only on goals and in environments where that is acceptable.
  • No network or filesystem access beyond what your steps request. runxmd itself makes no outbound calls (except @http/@llm when you use them) and installs nothing — detect-don't-install means a missing interpreter fails loudly rather than fetching anything.
  • Inspect before running. Use runxmd parse file.md to see the structured steps, or runxmd validate file.md to list sections, before executing an unfamiliar document.

In short: the power of runxmd is that the document is the program. The responsibility that comes with it is the same as any executable — run only what you trust.


Check what's available on your machine

Before writing a multi-language doc, run:

runxmd check

It scans every interpreter, shows what's installed, and gives install hints for anything missing:

runxmd check — interpreter availability

  Language     Plugin            Executable   Status      Version
  ---------------------------------------------------------------
  @python      @python_script    python       ✓  found    Python 3.14.5
  @node        @node_script      node         ✓  found    v24.15.0
  @typescript  @typescript_script ts-node     ✗  missing  install: npm install -g ts-node
  @ruby        @ruby_script      ruby         ✗  missing  install: https://...
  @bash        @bash_script      bash         ✓  found    GNU bash 5.3.9
  @go          @go_script        go           ✗  missing  install: https://go.dev/dl/
  @r           @r_script         Rscript      ✗  missing  install: https://cran.r-project.org/
  @php         @php_script       php          ✗  missing  install: https://...
  @perl        @perl_script      perl         ✓  found    v5.42.2
  @powershell  @powershell_script powershell  ✓  found    5.1.26100

  5 of 10 languages available.

Only install the languages you actually use — runxmd never installs anything itself.


.md and .xmd — same Markdown, one is a signal

runxmd runs both .md and .xmd files identically. The only difference is the name:

  • .md — a regular Markdown file that may or may not have runnable steps.
  • .xmd — the same format, byte-for-byte. The .xmd extension is a label that signals "this file is meant to be executed."
runxmd run notes.md       # runs — finds steps inside
runxmd run pipeline.xmd   # identical engine, identical grammar

Adding @workflow when you need it

For simple scripts the bare - @plugin syntax is enough. Add @workflow when you need named workflows — so you can run one selectively, or link it from a task.

@workflow deploy
- @shell
  run: docker pull myapp:latest
- @shell
  run: docker restart myapp
runxmd run deploy.md --workflow deploy   # run only this workflow

Stateful docs with @memory

@memory makes a Markdown file a system instead of a static doc.

@memory
name: "world"
runtime.status: "pending"

@workflow hello
- @python
  run: |
    print("hello {{ memory.name }}")

@on_done
set: memory.runtime.status = "done"
render
  1. Read-in — memory keys are loaded at run start.
  2. Substitution{{ memory.key }} anywhere in a step resolves live.
  3. Write-back — use --write-back to persist runtime.* keys back into the source file after the run.

Field ownership

  • runtime.* keys — only the runtime writes these (via @on_done set:).
  • Everything else — yours. The runtime reads them but never overwrites them.

The full section set

Section Purpose Grammar
@goal Describes intent for humans/agents free prose
@memory Key-value state, optionally persisted key: value lines
@tasks Checklist the agent can tick - [ ] / - [x]
@workflow <name> Named group of steps - @plugin + params
@on_done Hooks that run after workflows finish set: / render / results / write

Steps written outside any @workflow (top-level in the file) run as an implicit unnamed workflow — no annotation needed.


Plugins

A step is - @plugin plus indented params:

Inline code plugins

Write code directly in the document using run: |:

Plugin Language Key params
@print text
@shell Shell (/bin/sh on Linux, cmd on Windows) run
@python Python run
@node JavaScript run
@typescript TypeScript (needs ts-node) run
@ruby Ruby run
@bash Bash run
@go Go run
@r R (needs Rscript) run
@php PHP run
@perl Perl run
@powershell PowerShell (powershell on Windows, pwsh on Linux/macOS) run

External script plugins

Point at an existing file on disk using path::

Plugin Runs Key params
@python_script .py file path, args (optional)
@node_script .js file path, args
@typescript_script .ts file path, args
@ruby_script .rb file path, args
@bash_script .sh file path, args
@go_script .go file path, args
@r_script .R file path, args
@php_script .php file path, args
@perl_script .pl file path, args
@powershell_script .ps1 file path, args
- @python_script
  path: scripts/analyze.py
  args: --input data.csv

path: is resolved relative to the MD file's directory — so scripts/foo.py always means "next to the file being run", regardless of where you invoke runxmd from. Absolute paths also work. args: is split on spaces and passed to the interpreter.

Other plugins

Plugin Does Key params
@http Make an HTTP request url, method, body
@write Write a file (creates dirs) path, content
@read Read a file into output path
@llm Call an LLM (needs ANTHROPIC_API_KEY) prompt, model, max_tokens

Detect, don't install. Language plugins use whatever is already on your machine. If an interpreter is missing, the step fails with a clear message and the run continues — runxmd never installs anything for you.

Prerequisites: runxmd itself has zero dependencies (pure Python ≥ 3.9). Each language plugin requires its interpreter on PATH. Run runxmd check to see exactly what is available on your machine before writing a multi-language doc.

Multi-language example

Mix languages freely in the same file:

# Polyglot Report

- @python
  run: |
    data = [1, 2, 3, 4, 5]
    print("Python sum:", sum(data))

- @node
  run: |
    const nums = [1, 2, 3, 4, 5];
    console.log("JS sum:", nums.reduce((a, b) => a + b, 0));

- @perl
  run: |
    my @nums = (1..5);
    printf "Perl sum: %d\n", eval join("+", @nums);

Each step runs in isolation. If an interpreter is missing, that step is marked ✗ and execution continues with the next step.


Watch mode

runxmd watch report.md                  # re-run and re-render on every save
runxmd watch report.md --interval 0.5   # poll faster
runxmd watch report.md --max-runs 3     # stop after 3 runs

Agent mode

runxmd agent turns the doc from a program you run into a goal that runs itself:

Read @goal → plan @tasks → execute each task → update @memory → write back
runxmd agent project.md              # plan (if no tasks), then run linked workflows
runxmd agent project.md --replan     # regenerate tasks from the goal
runxmd agent project.md --autonomous # LLM generates AND runs steps for unlinked tasks
runxmd agent project.md --dry-run    # show the plan without executing or writing

Planning requires ANTHROPIC_API_KEY. See examples/AGENT.xmd.


Commands

runxmd run <file> [--workflow NAME] [--write-back]
runxmd watch <file> [--interval S] [--max-runs N] [--write-back]
runxmd agent <file> [--replan] [--autonomous] [--model M] [--max-tokens N] [--dry-run]
runxmd check
runxmd parse <file>
runxmd validate <file>
runxmd --version
Flag Applies to Effect
--write-back run, watch Persist runtime.* memory back into the source file
--workflow NAME run, watch Run only the named workflow
--write-back omitted default Source file is never modified

Design principles

  • Compute once, read forever. The render output carries real results so models and humans read facts instead of recomputing them — fewer tokens, no guessing.
  • Run only what you trust. The document is the program; treat a runnable .md like a shell script.
  • No annotation required to get started. A file with bare - @plugin steps runs.
  • Source files are read-only by default. Output always goes to a new file.
  • Render is the default output. Prose is preserved; only code blocks are replaced.
  • Cross-platform. Runs on Windows, Linux, and macOS without changes.
  • Your Markdown, not a new format. Everything outside @sections is untouched prose.
  • Inline-first. Code lives in the document; the runtime is a thin dispatcher.
  • Detect, don't install. Use what's on the machine; fail clearly when it's not.
  • Stdlib only. Zero dependencies, including @http.

Status & roadmap

Current: v1.0.2 — see SPEC-v0.0.3.md for the full contract.

  • ✅ Parser, executor, CLI (run / watch / agent / parse / validate / check)
  • ✅ Plugins: shell, http, filesystem, llm
  • ✅ Inline language plugins: Python, Node.js, TypeScript, Ruby, Bash, Go, R, PHP, Perl, PowerShell
  • ✅ External script plugins: @python_script, @node_script, and one for every supported language
  • ✅ Bare steps without @workflow — implicit unnamed workflow
  • ✅ Render output (default) — prose preserved, code replaced with results
  • ✅ Results output — step outputs only, for LLM consumption
  • ✅ Write output — replica with result: fields for re-running
  • ✅ Memory: read / substitute / write-back, with field-ownership safety
  • ✅ Reactive runxmd watch
  • ✅ Agent engine (@goal → auto-generate @tasks → execute → update memory)
  • runxmd check — interpreter availability report with versions and install hints
  • ✅ Cross-platform: Windows, Linux, macOS (PowerShell uses pwsh on Linux/macOS)
  • ⏳ Declarative events (@on_file_change, @daily, @on_commit)
  • ⏳ Portable @task abstraction
  • ⏳ Multi-agent / distributed

Contributions and ideas welcome.


License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

runxmd-1.0.2.tar.gz (54.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

runxmd-1.0.2-py3-none-any.whl (30.4 kB view details)

Uploaded Python 3

File details

Details for the file runxmd-1.0.2.tar.gz.

File metadata

  • Download URL: runxmd-1.0.2.tar.gz
  • Upload date:
  • Size: 54.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for runxmd-1.0.2.tar.gz
Algorithm Hash digest
SHA256 31734b07c8a07c4e4ccb4c4752304720b121228a0be72a292c3bcacead6d1df2
MD5 ed596992a3b7c0112bead38ec260e0a6
BLAKE2b-256 887798789445f93f9329cd07431409fd0a9ce1a83f3260e4d18223d612b8d4fe

See more details on using hashes here.

File details

Details for the file runxmd-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: runxmd-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 30.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for runxmd-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 873ad9853c3476db466902fb62161a3061a5cdad37391156ade4ca11ae088b81
MD5 ba45c7f2d204ecd53df460d8ef772703
BLAKE2b-256 02b7fe3f02510a26c343f7dc76813f5a39649f0d78c2d48d99b984a7e9f400c3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page