https://github.com/jlevy/markform
Structured Markdown documents for agents and humans
https://github.com/jlevy/markform
Last synced: 3 months ago
JSON representation
Structured Markdown documents for agents and humans
- Host: GitHub
- URL: https://github.com/jlevy/markform
- Owner: jlevy
- License: agpl-3.0
- Created: 2025-12-22T21:00:43.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-01-25T18:55:22.000Z (4 months ago)
- Last Synced: 2026-01-25T21:57:27.430Z (4 months ago)
- Language: TypeScript
- Homepage:
- Size: 4.39 MB
- Stars: 30
- Watchers: 1
- Forks: 1
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# Markform
[](https://github.com/jlevy/markform/actions/runs/21981024810)
[](https://github.com/jlevy/markform/actions/runs/21981024810)
[](https://www.npmjs.com/package/markform)
[](https://x.com/ojoshe)
### What if your Markdown docs had an agent-friendly semantic API?
**Markform** is a text format for defining structured forms that humans can read,
machines can parse, and agents can fill via tool calls.
Define instructions, fields, and validation rules in a single `.form.md` file.
Agents fill forms incrementally via patches.
Fields are validated, so errors are caught early and can be corrected.
Humans can review or intervene at any point.
### Why forms?
For deep research or complex AI tasks, you need more than just prompts or
flow: you need *structure*, which is precise control over agent output at every stage of
a workflow. A well-designed form combines instructions, structured data, and validations
in one place.
### How it Works
- A Markform document exposes a programmatic interface: users fill fields via CLI or web
UI, agents fill via tool calls ([Vercel AI SDK](https://github.com/vercel/ai)
integration included).
- Changes are explicit patch operations
(`{ "op": "set_string", "fieldId": "name", "value": "Alice" }`) validated against a
schema specified in the form.
The agent sees validation errors and can self-correct.
- The format extends Markdown with a
[precise specification](https://github.com/jlevy/markform/blob/main/docs/markform-spec.md).
Export Markform syntax to JSON, YAML, JSON Schema, or plain Markdown reports.
- Optionally, the whole thing is wrapped in a harness where large forms can be filled
concurrently by any LLM in a structured agentic loop.
### Useful details
- Markform syntax is a good source format: it is **token-efficient text** you can read, diff, and
version control and it is **ideal for context engineering** because it combines
document context, data schema, and memory (data filled so far).
- Structure is defined with HTML comment tags (``) that
render invisibly on GitHub, so **forms look like regular Markdown**. (Jinja-style
tag syntax also works if you prefer.)
- Optionally, **a fill record** of the form-filling process is kept, so you can see
and debug exactly how forms are filled by agents, tool usage, LLM call time, etc.
- The CLI has a built-in web renderer, **`markform serve`**, for easy viewing and debugging
of forms (including a form web UI, the form schema, and a waterfall-style overview of the
fill record, including performance details, which is useful for large, concurrently filled forms).
## Simple Example: Research a Movie
### Form Definition
A `.form.md` file combines YAML frontmatter with HTML comment tags that define
structure. The text can be any Markdown.
Here is the
[movie-research-demo.form.md](https://github.com/jlevy/markform/blob/main/packages/markform/examples/movie-research/movie-research-demo.form.md):
```markdown
---
markform:
spec: MF/0.1
title: Movie Research Demo
description: Movie lookup with ratings from IMDB and Rotten Tomatoes.
run_mode: research
roles:
- user
- agent
role_instructions:
user: "Enter the movie title."
agent: |
Identify the movie with web searches and use imdb.com and rottentomatoes.com to fill in the ratings.
---
## What movie do you want to research?
Enter the movie title (add year or details for disambiguation).
## Movie Ratings
Here are the ratings for the movie:
- [ ] G
- [ ] PG
- [ ] PG-13
- [ ] R
- [ ] NC-17
- [ ] NR/Unrated
| Source | Score | Votes |
|--------|-------|-------|
Fill in scores and vote counts from each source:
- IMDB: Rating (1.0-10.0 scale), vote count
- RT Critics: Tomatometer (0-100%), review count
- RT Audience: Audience Score (0-100%), rating count
```
Fields have types defined by the attributes.
A field can have `role="user"` (filled interactively) or `role="agent"` (filled by an
agent). Values are filled in incrementally, just like any form.
### Filled Form
The key point is that with this structure **a Markdown file automatically gets a schema
and a tool API.**
Agents or users can fill in values using a TypeScript API or via agent tool calls.
And agents find this format **highly context efficient**. All information needed to fill
in the form is right there, not in long conversation history.
And it can be done incrementally, a few fields at a time.
Once filled in, values appear directly inside the tags, in Markdown format:
````markdown
```value
The Shawshank Redemption
```
- [ ] G
- [ ] PG
- [ ] PG-13
- [x] R
- [ ] NC-17
- [ ] NR/Unrated
| Source | Score | Votes |
| --- | --- | --- |
| IMDB | 9.3 | 3100000 |
| RT Critics | 89 | 146 |
| RT Audience | 98 | 250000 |
````
### Report Output
Run `npx markform examples` to copy examples, then `npx markform run` and select
`Movie Research Demo` to fill it.
A form can be exported as the filled form (Markform), as a report (plain Markdown), as
values (YAML or JSON), or as a JSON Schema (just the structure).
The report output (using `gpt-5-mini` to fill it in) looks like:
```markdown
Movie:
The Shawshank Redemption
## About the Movie
MPAA Rating:
R
Ratings:
| Source | Score | Votes |
| --- | --- | --- |
| IMDB | 9.3 | 3100000 |
| RT Critics | 89 | 146 |
| RT Audience | 98 | 250000 |
```
YAML Export (click to expand)
```yaml
values:
movie:
state: answered
value: The Shawshank Redemption
mpaa_rating:
state: answered
value: r
ratings_table:
state: answered
value:
- source: IMDB
score: 9.3
votes: 3100000
- source: RT Critics
score: 89
votes: 146
- source: RT Audience
score: 98
votes: 250000
```
JSON Schema (click to expand)
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "movie_research_demo",
"type": "object",
"properties": {
"movie": {
"type": "string",
"title": "Movie",
"minLength": 1,
"maxLength": 300
},
"mpaa_rating": {
"type": "string",
"enum": ["g", "pg", "pg_13", "r", "nc_17", "nr"],
"title": "MPAA Rating"
},
"ratings_table": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source": { "title": "Source", "type": "string" },
"score": { "title": "Score", "type": "number" },
"votes": { "title": "Votes", "type": "number" }
}
},
"title": "Ratings",
"minItems": 0,
"maxItems": 3
}
},
"required": ["movie", "ratings_table"]
}
```
## Why Do Agents Need Forms?
For centuries, humans have used paper forms and
[checklists](https://en.wikipedia.org/wiki/The_Checklist_Manifesto) to systematize
complex processes. A form with instructions, field definitions, and validations is a
concise way to share context: goals, background knowledge, process rules, and state
(memory). I don’t think AI changes this essential aspect of knowledge work.
Most agent frameworks focus on *prompts* and *flow* (the how) over the *structure* of
results (the what).
But for deep research or other multi-step workflows, you need precise
control over intermediate states and final output.
You don’t want that structure in a GUI (not token-friendly), in code (hard to update),
or dependent on model whims (changes unpredictably with model updates).
Forms solve this. Forms codify operational excellence.
They’re easy to read, easy to edit, and enforce standards.
Because LLMs handle Markdown well, agents can also help create and improve the forms
themselves—closing the meta-loop.
It’s time to bring bureaucracy to the agents!
See [the FAQ](#faq) for more on the design.
## Quick Start
```bash
# Copy example forms to ./forms/ and run one interactively.
# Set OPENAI_API_KEY or ANTHROPIC_API_KEY (or put in .env) for research examples
npx markform@latest examples
# Read the docs (tell your agents to run these; they are agent-friendly!)
npx markform # CLI help
npx markform readme # This file
npx markform docs # Quick reference for writing Markforms
npx markform spec # Read the full spec
```
The `markform examples` command copies some sample forms to `./forms` and prompts you to
fill in a form interactively and then optionally have an agent complete it.
Pick `movie-research-demo.form.md` for a quick example.
## Installation
Requires Node.js 20+.
```bash
# As a global CLI
npm install -g markform
# Or as a project dependency
npm install markform
```
### More Example Forms
The package includes example forms.
View them with `markform examples --list`, copy with `markform examples`, and run with
`markform run`:
- [`simple.form.md`](https://github.com/jlevy/markform/blob/main/packages/markform/examples/simple/simple.form.md)
\- Basic form demonstrating all field kinds.
- [`movie-research-demo.form.md`](https://github.com/jlevy/markform/blob/main/packages/markform/examples/movie-research/movie-research-demo.form.md)
\- The quick example above.
- [`movie-deep-research.form.md`](https://github.com/jlevy/markform/blob/main/packages/markform/examples/movie-research/movie-deep-research.form.md)
\- Comprehensive movie analysis with streaming, box office, technical specs.
### Why a New Format?
The closest alternatives I’ve seen are:
- Plain Markdown docs can be used as templates and filled in by agents.
These are more expressive, but it is hard to edit them programmatically or use LLMs to
update them reliably.
- JSON + JSON Schema which are good for struture but terrible for additional
unstructured context like instructions Markdown.
- Agent to-do lists are part of many chat or coding interfaces and are programmatically
edited by agents. But these are limited to simple checklists, not forms with other
fields.
- Numerous tools like Typeform, Google Forms, PDF forms, and Docusign offer
human-friendly UI. But these do not have a human-friendly text format for use by
agents as well as humans.
| Approach | Usable GUI editor | Human-readable source format | Agent-editable | APIs and validation rules |
| --- | :---: | :---: | :---: | :---: |
| Plain Markdown | ✅
IDEs/editors | ✅ | ⚠️
fragile | ❌ |
| JSON + JSON Schema | ✅
IDEs/editors | ⚠️
no free text | ✅ | ✅ |
| SaaS tools (Typeform, Docusign, PDF forms) | ✅ | ⚠️
rarely | ⚠️
sometimes | ⚠️
sometimes |
| HTML/web Forms | ✅
IDEs/editors | ⚠️
HTML+code | ⚠️
coding agent | ✅ |
| Excel/Google Sheets | ✅
app | ❌
.csv/.xlsx | ⚠️
with tools | ✅
with some coding |
| **Markform** | ✅
IDEs/editors | ✅ | ✅
with this package | ✅
with this package |
## Architecture
This repo has a specification and an implementation.
The implementation is a TypeScript API with Vercel AI SDK integration, and a CLI
interface.
```mermaid
flowchart LR
subgraph SPEC["MARKFORM SPEC"]
direction TB
subgraph L1["LAYER 1: SYNTAX
Markdoc tag syntax
and frontmatter (form,
group, string-field,
checkboxes, etc.)"]
end
subgraph L2["LAYER 2: FORM DATA MODEL
Schema definitions
for forms, fields, values"]
end
subgraph L3["LAYER 3: VALIDATION
AND PATCHES
Rules for filling forms
via patches, required
field semantics, validation"]
end
subgraph L4["LAYER 4: TOOL API
AND INTERFACES
Abstract form-filling
loop, concurrency
model, tool layer"]
end
L4 --> L3 --> L2 --> L1
end
subgraph IMPL["THIS IMPLEMENTATION"]
direction TB
subgraph CLI["`markform` CLI
Command-line interface
to all features"]
end
subgraph AGENT["AGENT TOOL INTERFACE
Tool API library>
(AI SDK tools)"]
end
subgraph HARNESS["EXECUTION HARNESS
Concurrent form-filling
agentic loop
(AI SDK)"]
end
subgraph ENGINE["CORE TYPESCRIPT APIS
Markdoc parser, serializer,
patch application,
validation (jiti for rules)"]
end
subgraph TEST["TESTING FRAMEWORK
Golden session testing
(.session.yaml transcripts)"]
end
CLI --> ENGINE
CLI --> HARNESS
AGENT --> HARNESS
AGENT --> ENGINE
HARNESS --> ENGINE
ENGINE --> TEST
end
SPEC ~~~ IMPL
style SPEC fill:#e8f4f8,stroke:#0077b6
style L1 fill:#caf0f8,stroke:#0077b6
style L2 fill:#caf0f8,stroke:#0077b6
style L3 fill:#caf0f8,stroke:#0077b6
style L4 fill:#caf0f8,stroke:#0077b6
style IMPL fill:#fff3e6,stroke:#fb8500
style ENGINE fill:#ffe8cc,stroke:#fb8500
style CLI fill:#ffe8cc,stroke:#fb8500
style AGENT fill:#ffe8cc,stroke:#fb8500
style HARNESS fill:#ffe8cc,stroke:#fb8500
style TEST fill:#ffe8cc,stroke:#fb8500
```
## CLI Commands
### Copy and Run Examples
```bash
# Copy all bundled examples to ./forms/
markform examples
# List available examples
markform examples --list
# Copy a specific example
markform examples --name movie-research-demo
```
### Browse and Run Forms
```bash
# Browse forms in ./forms/ and run one interactively
markform run
# Run a specific form directly
markform run forms/movie-research-demo.form.md
```
### Check Form Status
```bash
# Show fill progress with per-role breakdown
markform status my-form.form.md
```
### Inspect Forms
```bash
# View form structure, progress, and validation issues
markform inspect my-form.form.md
# Output as JSON
markform inspect my-form.form.md --format=json
```
### Fill Forms
```bash
# Interactive mode: fill user-role fields via prompts
markform fill my-form.form.md --interactive
# Agent mode: use an LLM to fill agent-role fields
markform fill my-form.form.md --model=anthropic/claude-sonnet-4-5
# Mock agent for testing (uses pre-filled form as source)
markform fill my-form.form.md --mock --mock-source filled.form.md
# Record fill session to JSON sidecar (tokens, timing, tool calls)
markform fill my-form.form.md --model=openai/gpt-5-mini --record-fill
```
### Export and Transform
```bash
# Export as readable markdown (strips Markdoc tags)
markform export my-form.form.md --format=markdown
# Export values as JSON
markform export my-form.form.md --format=json
# Export values as YAML
markform export my-form.form.md --format=yaml
# Dump just the current values
markform dump my-form.form.md
```
### Export JSON Schema
```bash
# Export form structure as JSON Schema (for validation, code generation, etc.)
markform schema my-form.form.md
# Pure JSON Schema without Markform extensions
markform schema my-form.form.md --pure
# Specify JSON Schema draft version
markform schema my-form.form.md --draft draft-07
```
### Apply Patches
```bash
# Apply a JSON patch to update field values
markform apply my-form.form.md --patch '[{"op":"set_string","fieldId":"name","value":"Alice"}]'
```
### Web Interface
```bash
# Serve a form as a web page for browsing
markform serve my-form.form.md
```
### Documentation Commands
```bash
# Quick reference for writing forms (agent-friendly)
markform docs
# Full specification
markform spec
# TypeScript and AI SDK API documentation
markform apis
# This README
markform readme
# See supported AI providers and example models
markform models
# See all commands
markform --help
```
## API Key Setup
Set the appropriate environment variable for your provider before running
`markform fill`. The CLI automatically loads from `.env.local` and `.env` files in the
current directory.
Supported providers:
| Provider | Env Variable | Native Web Search |
| --- | --- | :---: |
| openai | `OPENAI_API_KEY` | ✓ |
| anthropic | `ANTHROPIC_API_KEY` | ✓ |
| google | `GOOGLE_API_KEY` | ✓ |
| xai | `XAI_API_KEY` | ✓ |
| deepseek | `DEEPSEEK_API_KEY` | ✗ |
Run `markform models` to see available models.
See
[`src/settings.ts`](https://github.com/jlevy/markform/blob/main/packages/markform/src/settings.ts)
for defaults.
If unsure, try `gpt-5-mini` first as it’s fast and supports web search.
## Programmatic Usage
Markform exports a parsing engine, rendering functions, and AI SDK integration for use in
your own applications.
### Basic Parsing
```typescript
import { parseForm, serialize } from "markform";
// Parse a .form.md file
const form = parseForm(markdownContent);
// Access schema and values
console.log(form.schema.title);
console.log(form.responsesByFieldId);
// Serialize back to markdown
const output = serialize(form);
```
### High-Level Fill API
The simplest way to fill a form programmatically:
```typescript
import { fillForm } from "markform";
const result = await fillForm({
form: markdownContent,
model: "anthropic/claude-sonnet-4-5",
enableWebSearch: true,
recordFill: true, // Capture tokens, timing, tool calls
});
if (result.status.ok) {
console.log("Values:", result.values);
console.log("Tokens used:", result.record?.llm.inputTokens);
}
```
See the
[API documentation](https://github.com/jlevy/markform/blob/main/docs/markform-apis.md)
for options like parallel execution, callbacks, and checkpointing.
### Rendering API
Import from the `markform/render` subpath to render forms and fill records as HTML
fragments — the same output as `markform serve`, without pulling in CLI/server
dependencies:
```typescript
import {
renderViewContent,
renderFillRecordContent,
FILL_RECORD_STYLES,
FILL_RECORD_SCRIPTS,
} from "markform/render";
// Render a filled form as read-only HTML
const formHtml = renderViewContent(parsedForm);
// Render a fill record dashboard
const dashboardHtml = renderFillRecordContent(fillRecord);
```
Also exports `renderSourceContent`, `renderMarkdownContent`, `renderYamlContent`,
`renderJsonContent`, `escapeHtml`, `formatDuration`, and `formatTokens`.
See the
[API documentation](https://github.com/jlevy/markform/blob/main/docs/markform-apis.md#rendering-api)
for full details.
### AI SDK Integration
Markform provides tools compatible with the [Vercel AI SDK](https://sdk.vercel.ai/):
```typescript
import { parseForm } from "markform";
import { createMarkformTools, MarkformSessionStore } from "markform/ai-sdk";
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
// Parse form and create session store (tracks state across tool calls)
const form = parseForm(markdownContent);
const store = new MarkformSessionStore(form);
// Create tools the agent can call: inspect, apply patches, export
const tools = createMarkformTools({ sessionStore: store });
// Agent fills the form via tool calls until complete or maxSteps reached
const result = await generateText({
model: anthropic("claude-sonnet-4-5-20250929"),
prompt: "Fill out this form with appropriate values...",
tools,
maxSteps: 10,
});
```
**Available tools:**
| Tool | Description |
| --- | --- |
| `markform_inspect` | Get current form state, issues, progress |
| `markform_apply` | Apply patches to update field values |
| `markform_export` | Export schema and values as JSON |
| `markform_get_markdown` | Get canonical Markdown representation |
## Other Documentation
- **[Quick Reference](https://github.com/jlevy/markform/blob/main/docs/markform-reference.md)**
(or run `markform docs`) — Concise syntax reference (agent-friendly)
- **[Markform Spec](https://github.com/jlevy/markform/blob/main/docs/markform-spec.md)**
(or run `markform spec`) — Complete syntax and semantics
- **[API Documentation](https://github.com/jlevy/markform/blob/main/docs/markform-apis.md)**
(or run `markform apis`) — TypeScript and AI SDK APIs
- **[Design Doc](https://github.com/jlevy/markform/blob/main/docs/project/architecture/current/arch-markform-design.md)**
— Technical design and roadmap
- **[Development](https://github.com/jlevy/markform/blob/main/docs/development.md)** —
Build, test, and contribute
## FAQ
### Can you talk more about why forms are cool?
As a matter of fact, I can!
I’ve come to believe forms are a missing piece of the workflow problem with agents.
For deep research or complex multi-step workflows, key pieces need to be *precisely
controlled*, *domain-specific*, and *always improving*. You need precise documentation
on the key intermediate states and final output from an AI pipeline.
But you don’t want structure in a GUI (not token friendly) or code (hard to update) or
dependent on the whims of a thinking model (changes all the time).
Forms define these pieces and are easy to edit.
All other choices can be left to the agents themselves, with the structure and
validations enforced by the form-filling tools the agents use.
Another cool thing about forms: they get rid of the inefficiencies of conversation chat
history.
Often when an agentic loop is built, it just saves the chat history for context.
But a form is inherently more efficient: the harness itself can be stateless.
It just shares the partly-filled form with the agent, and it has full context in one
message. That’s what the agentic loop in this implementation does.
Finally, the meta-loop of *creating and improving* forms is easier to automate:
- To get started, you can ask a good coding model to convert any unstructured doc
describing a process to a form.
The model can also use the CLI or tools to validate and test it.
- Any time you have a workflow problem, you can ask an LLM to diagnose it and if
possible, go back and fix up the form with additional instructions or fields or checks
that would prevent the problem from happening again.
I suspect dynamic form structures like this could make complex deep research more
powerful. Just as you plan a spec before implementing with a coding agent, you could use
Markform to encode a research plan before dispatching agents to fill it.
### Is this mature?
No! I just wrote it.
The spec is a draft.
But it’s been useful for me already.
### Was it Vibe Coded?
This is 100% agent-written code and the planning specs are also 100% agent written,
using a mix of Opus 4.5, GPT 5.2, GPT-5 Pro, Gemini 3, and occasionally others.
But it’s not slop. It is written via a strongly spec-driven process, using rules from my
[Speculate](https://github.com/jlevy/speculate) repo and Steve Yegge’s
[beads](https://github.com/steveyegge/beads) for tracking work.
See
[my post](https://github.com/jlevy/speculate/blob/main/about/lessons_in_spec_coding.md)
for more thoughts on how this works.
And see
[the complete history of specs](https://github.com/jlevy/markform/tree/main/docs/project/specs/done)
for examples of how everything is done with specs.
Although I didn’t write much code, there was a *lot* of management, review, and
iteration on design decisions.
And yes, this README is written by me.
:)
### What are the goals of Markform?
- **Markform should express complex structure and validation rules for outputs:** Fields
can be arbitrary types like checkboxes, strings, dates, numbers, URLs, and lists.
Validation rules can be simple (min and max value, regexes), arbitrary code, or LLM
calls.
- **Markform is programmatically editable:** Field state should be updated via APIs, by
apps, or by agent tools.
- **Markform is readable by humans and agents:** Both templates and field values of a
form should have a clear text format (not a binary or obscure XML format only readable
by certain applications).
### How do agents fill in forms?
The data model and editing API let agents fill in forms.
This enables powerful AI workflows that assemble information in a defined structure:
- **Form content, structure, and field values are in a single text file** for better
context engineering.
This is a major advantage for LLM agents and for humans reviewing their work.
- **Incremental filling** means an agent or a human can take many iterations, filling
and correcting a form until it is complete and satisfies the validation rules.
- **Multiple interfaces for humans or agents** can work with the same forms.
You can interact with a form via a CLI, a programmatic API, from Vercel AI SDK or in
an MCP server used by an agent, or in web form UIs for humans.
- **Flexible validation** at multiple scopes (field/group/form), including declarative
constraints and external hooks to arbitrary code (currently TypeScript) or LLM-based
validation instructions.
- An **agent execution harness** for step-by-step form filling, enabling deep research
agents that assemble validated output in a structured format.
### What are example use cases?
- Deep research tools where agents need to follow codified processes to assemble
information
- Practical task execution plans with checklists and assembled answers and notes
- Analysis processes, like assembling insights from unstructured sources in structured
form
- Multi-agent and agent-human workflows, where humans and/or agents fill in different
parts of a form, or where humans or agents review each other’s work in structured ways
- A clean and readable text format for web UIs that involve filling in forms, supporting
strings, lists, numbers, checkboxes, URLs, and other fields
### Why use Markdoc as a base format?
Markdoc extends Markdown with structured tags, allowing AST parsing and programmatic
manipulation while preserving human and LLM readability.
See Stripe’s [Markdoc overview][markdoc-overview] and [blog post][stripe-markdoc] for
more on the philosophy behind “docs-as-data” that Markform extends to “forms-as-data.”
We could use XML tags, but Markdoc has some niceties like tagging Markdown AST nodes
(`{% #some-id %}`) so I decided to go with this.
### What editor settings work best?
**HTML comment syntax (recommended):** Regular Markdown mode works perfectly since
`` comments are standard Markdown.
**Markdoc syntax (`{% tag %}`):** Install
[Better Jinja](https://marketplace.visualstudio.com/items?itemName=samuelcolvin.jinjahtml)
and associate `.form.md` files with `jinja-md` mode:
```json
"files.associations": {
"*.form.md": "jinja-md"
}
```
## License
This project uses a dual licensing approach:
- **Markform Specification** ([`docs/markform-spec.md`](docs/markform-spec.md),
[`docs/markform-reference.md`](docs/markform-reference.md)):
[CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/). You may freely implement
this specification in your own software under any license.
- **Reference Implementation** (all code in this repository):
[AGPL-3.0-or-later](./LICENSE). [Contact me](https://github.com/jlevy) for commercial
licensing options.
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution terms.
[markdoc-overview]: https://markdoc.dev/docs/overview
[stripe-markdoc]: https://stripe.com/blog/markdoc