executable.md treats markdown documents as executable workflows. A document can expand markdown components, execute annotated code blocks, and evaluate in-process Effection operations while staying a valid, readable markdown file in any viewer.
The command-line tool is called xmd (eXecutable MarkDown).
This project is an implementation of the draft spec in specs/executable-mdx-spec.md.
- Expands JSX-style component invocations like
<Greeting name="world" />from markdown files. - Executes fenced code blocks marked with
execoreval. - Optionally journals component imports and command results to a diagnostic JSONL trace.
- Shares bindings across
evalblocks inside a component. - Supports long-lived background processes with
daemonand provider-style components for LLM-backed workflows.
README.md
---
title: My Project
---
# {meta.title}
<Greeting name="world" />
```bash exec
ls ./src
```components/Greeting.md
---
emoji: Hello
props:
type: object
properties:
name:
type: string
required: [name]
additionalProperties: false
---
{meta.emoji}, {props.name}!Rendered output:
# My Project
Hello, world!
main.ts
utils.tsInstall the xmd binary (macOS/Linux):
curl -fsSL https://executable.md/install.sh | shPrebuilt binaries for each platform are published on the releases page. The binary is self-contained — no Node or Deno required to run it.
xmd is also published to npm as @executablemd/cli:
npm install -g @executablemd/cliNo registry configuration is needed — every @executablemd package resolves from the default npm registry.
- Size: binaries are self-contained and fairly large (roughly 90–125 MB depending on platform) — the embedded Deno runtime dominates. Trimming this further is tracked in #66.
- Alpine / musl: no musl build is published. On Alpine, run via
denoor use the glibc binary undergcompat. - macOS: binaries are currently unsigned. The install script clears the Gatekeeper quarantine automatically; if you download a binary manually, run
xattr -d com.apple.quarantine ./xmdbefore first use. Signing/notarization is tracked in #68. - Windows: the binary runs, but
execblocks that invoke shell commands need a shell (e.g. Git Bash or WSL) onPATH. Provider andevaldocuments work without one.
xmd run packages/core/examples/hello-world.mdRun a document without writing one, for a quick experiment:
xmd -e '# Hello'
xmd -e '<File path="README.md" />'Write a diagnostic trace for one run:
xmd run packages/core/examples/hello-world.md --journal .xmd/events.jsonlUseful flags:
--eval,-e- execute the given markdown as the root document instead of a path. Exactly one of the two is required; printed errors report the source as<eval>and relative paths resolve from the current directory.--journal,-j- write current-run journal entries to a new JSONL file for debugging. The path must not exist and is never replayed.--verbose,-V- print durable journal entries to stderr while running.--component-dir- add component search directories. Defaults tocomponentsand..
Run ACP-compatible coding agents directly from a document with <Agent>,
<Session>, and <Prompt>. The coding-agent guide
explains provider selection, permissions, timeouts, sessions, and deterministic
tests with the bundled test agent.
For the deterministic test-agent walkthrough and its scenario format, see
packages/test-agent/README.md.
executable.md treats the root document like a component:
- Frontmatter becomes
meta. - JSX tags with capitalized names become component invocations.
<Content />acts as a slot for child content.- Text segments support
{meta.key}and{props.key}interpolation. <If condition={...}>, with an optional<Else>block, is a structural directive rather than a component.- Markdown is healed at execution boundaries with
remendso formatting does not bleed across components or executable blocks.
<If> expands one branch and only one. condition selects by ordinary JavaScript truthiness — false, 0, -0, 0n, NaN, "", null, and undefined take the false branch, and everything else takes the true one, including "false", [], and {} — and the branch that is not selected never expands, so nothing in it imports a component, runs a block, or creates a binding.
<If condition={hasFailures}>
## Test failures
<FailureReport />
<Else>
All checks passed.
</Else>
</If><Else> is optional and, when present, is the final substantive child of its <If>. See the control-flow guide for nesting and binding examples.
The first word in a fence info string is the language. The remaining words form a modifier chain. Standard renderers only read the first word, so the modifiers stay invisible everywhere else.
```bash silent timeout=30s exec
git diff --stat
```Built-in modifiers:
exec- run the block as a subprocess and render stdout.eval- run JavaScript/TypeScript in-process as an Effection operation.silent- execute but suppress rendered output.persist- keep resources created by an eval block alive for the component lifetime.timeout=30s- cancel a long-running block.daemon- start an arbitrary fixed-configuration subprocess tied to the component scope.service=name- start an attached service and publish its live loopback endpoint.ephemeral- reconstruct live eval state without writing a journal event.
LLM sampling is not a fence modifier — it happens through the <Sample> component installed by provider middleware (see Provider components).
Plain eval blocks run in a shared durable binding environment for the current component.
```bash service=server exec
node handshake-compatible-server.js
```
```ts persist ephemeral eval
import { callService } from "./client.ts";
const endpoint = server;
yield* Sample.around({
*sample([request]) {
return yield* callService(endpoint, request);
},
});
```Highlights:
- Top-level bindings are exported automatically for later blocks.
- Bare
{name}interpolation inside executable block content reads from eval bindings. output("...")lets an eval block render text into the document.renderChildren()andrender(markdown)let eval blocks render nested content intentionally.ephemeral evalreruns during live execution and partial replay, exports only invocation-local live bindings, and cannot render output.- Attached-service endpoints are available only to
ephemeral eval; they never enter interpolation, durable effect descriptions, or the journal.
The repo includes reusable markdown components (in packages/core/components/) that demonstrate the provider pattern:
AnthropicProvider.mdOllamaProvider.mdSample.mdInstruction.md
These components combine eval and Sample middleware so a document can talk to a cloud or already-running local model server without custom runtime wiring. A local process provider attaches a handshake-compatible command through service=<binding> and authenticates it with the XMD service handshake protocol.
packages/core/examples/hello-world.md shows the pattern combining a cloud model (Claude) and a local model (Ollama). Provider docs currently need the built-in components on the search path:
xmd run packages/core/examples/hello-world.md --component-dir packages/core/components--journal writes internal workflow journal entries for troubleshooting. A trace can include component source, command output, evaluated values, and errors, so treat it as potentially sensitive data.
Each invocation requires a new path. If the path already exists, xmd exits without executing the document or modifying the file. An interrupted process may leave a partial trace; the CLI preserves it for inspection and does not use it as recovery input.
packages/core/src/execute.ts- document entrypoint and durable import pipeline.packages/core/src/scanner.ts- boundary scanner for components and executable fences.packages/core/src/- component expansion, eval/exec handling, modifiers, and sampling helpers.packages/core/components/- reusable provider and demo components.packages/cli/src/cli.ts- thexmdcommand, runtime-neutral.packages/cli/src/{deno,node,bun,compiled}.ts- entrypoints; each installs the host adapters that command and compiler resolution need.packages/core/examples/hello-world.md- end-to-end example.specs/executable-mdx-spec.md- design and behavior spec.
This is a Deno-first project. Prepare a checkout once, then run the tool from
source and the checks with deno:
deno task setup # install both dependency layouts, build the browser bundle
deno task xmd run packages/core/examples/hello-world.md # run a document from source
deno task build # compile the standalone xmd binary
deno task lint # oxlint + oxfmt
deno task check # typecheck
deno task test # run the test suite
deno task verify # the whole applicable battery, concurrentlydeno task setup is the only thing that installs. Builds and checks read what
it prepared and leave what this repository owns — tracked files, node_modules,
deno.lock — exactly as they found it, so they compose instead of undoing one
another. A build is cache-pure on top of that; verification may add to the
runtime's own module cache, which is why it resolves graphs no build walks.
deno task verify starts the entire applicable battery at once and fails if any
of it leaves a tracked file changed, and deno task verify:clean runs that
claim end to end against a clean clone (AGENTS.md).
This is an early, first public release and a draft spec, optimized for experimentation around executable markdown workflows, Effection-based evaluation, and provider-driven AI documents. Feedback, issues, and contributions are very welcome — please open an issue.