Version 0.1.0 · Draft · https://md.memorysmith.app
A profile of Markdown for knowledge vaults: plain .md files, linked to each other, written by people and by agents, and read by software that has to build a graph and an index out of them without deciding what the content means.
This profile defines which notation a conforming implementation reads, and what it does with it. It is a profile, not a new syntax: every form specified here is taken from CommonMark, from GitHub Flavored Markdown, or from the vault editors that established it. Nothing is invented.
The profile covers three things and nothing else:
The third point is not an appendix. Most of what goes wrong when writing into a knowledge base is not a notation typed wrongly, it is a notation the author believed in: a link that was expected to become a connection, a line of metadata that was expected to become a category. A profile that only lists what works is half a profile.
It does not specify storage, transport, authentication, an API or a file layout. It does not specify how a vault is organised, what a note should contain, or which frontmatter attributes a given vault ought to use. Those belong to the vault, never to the format.
The keywords MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119.
An implementation may take any of three roles, and conformance is stated per role:
| Role | What it does | Conformance means |
|---|---|---|
| Reader | Renders a note for a person | It renders every notation of §5 as specified |
| Indexer | Derives links and attributes from a note | It extracts exactly what §3 and §4 specify, and nothing from anywhere else |
| Writer | Produces notes | It emits only notation this profile declares |
An implementation MUST state which roles it claims. An implementation that claims the Indexer role MUST NOT derive meaning from any part of a note other than the two readers defined in §3 and §4.
| Ring | Specification | Status here |
|---|---|---|
| Base | CommonMark 0.31.2 | Normative. An implementation MUST support it in full |
| Extended | GFM 0.29-gfm: tables, task list items, strikethrough, extended autolinks, disallowed raw HTML | Normative. An implementation MUST support it in full |
| Vault | This document, §3 to §6 | Normative |
The vault ring is where implementations of Markdown usually diverge in silence. Everything in it is specified here with a syntax, an example and an effect, and every entry has a machine-readable counterpart in profile.json and at least one case in the conformance suite.
A link is a reference from one note to another note of the same vault. Links are what turn a folder of files into a graph, and they are the first of the two places an Indexer is allowed to read.
| Form | Syntax | Becomes an edge |
|---|---|---|
| Wikilink | [[Target note]] |
Yes |
| Wikilink with alias | [[Target note\|what the reader sees]] |
Yes |
| Wikilink with anchor | [[Target note#Section]] |
Yes |
| Embed | ![[Target note]], ![[Target note#Section]] |
Yes |
| Relative Markdown link | [what the reader sees](target-note.md) |
Yes |
| External link | [text](https://example.org/page) |
No |
An Indexer MUST resolve every form above by the following rule, and by no other:
https:, mailto:, any [a-z][a-z0-9+.-]*:) or begins with //, it is external. It MUST NOT become an edge.#. What precedes it is the path; what follows is the anchor.../decisions/lei-14133.md and lei-14133 resolve to the same target. An edge is between notes, never between folders, and honouring the path would break the link the moment a note changed folder..md or .mdx extension MUST be removed.The slug is produced deterministically:
. or , between two digits is removed, so Lei 14.133 becomes lei-14133 and not lei-14-133.ç becomes c).a-z nor 0-9 with a single -.-, truncate to 80 characters, and trim any - the truncation left at the end.The same title MUST always produce the same slug.
Several links to the same target in one note are one edge. An embed and a plain link to the same target are also one edge: the graph does not distinguish transclusion from reference, not even by counting.
A link whose target has no note is not an error and MUST NOT be discarded. It is a pending link: it is kept, it is reported as pending, and it resolves on its own if a note with that slug is later created. Discarding it would make the graph lie precisely while a vault is being written, which is when it is consulted most.
A link inside a code span or a fenced code block is an example, not a reference. An Indexer MUST NOT extract it.
![[target]] is the embed form. For an Indexer it is identical to [[target]] in every respect. For a Reader, see §5.3.
Frontmatter is a block at the top of the file delimited by ---, written in a YAML subset. It is part of neither CommonMark nor GFM; this profile specifies it because it is the only place a vault declares attributes about a note, and the second and last place an Indexer is allowed to read.
The block MUST begin on the first line of the document with --- and end at the next line consisting of ---. Everything between the delimiters is frontmatter; everything after them is the body.
The frontmatter MUST NOT take part in the searchable text of the body. Keeping it there would make every note match its own metadata.
An Indexer MUST support this subset of YAML and no more:
---
key: value
list: [one, two]
block:
- one
- two
---
Scalars, inline lists and dash lists. One layer of matching quotes around a value is stripped. Nesting, anchors, multi-line scalars and typed tags are not part of this profile: an implementation MAY store them, and MUST NOT derive anything from them.
No attribute name is special. An Indexer MUST NOT hold a list of known keys, and MUST classify each attribute by the shape of its value:
| Shape | Kind | Indexed |
|---|---|---|
2026-09-03, or an ISO 8601 date-time whose first 10 characters are a date |
date |
Yes, canonicalised to YYYY-MM-DD |
true, false, yes, no, case-insensitive |
boolean |
Yes, canonicalised to true / false |
| A single value of at most 40 characters | enum |
Yes |
| A list whose every value is at most 40 characters | list |
Yes, each value on its own |
| A list written with one single item | list |
Yes. The kind comes from the form that was written, never from how many items it happens to hold: an attribute MUST NOT change kind on the day a second value is added to it |
| Anything longer than 40 characters | — | No. Above that a value is prose, not a category |
| An empty value | — | No |
That is what lets the vocabulary belong to the vault: a vault that starts writing norma: federal gets norma:federal as a filter the same day, with no configuration anywhere.
An implementation SHOULD additionally stop indexing an attribute whose distinct values grow past a cardinality ceiling of its choosing, and MUST document the ceiling if it does. An attribute whose value is unique per note is prose that happens to be short, and it gives itself away through use rather than through a name.
The profile reserves four attribute names. They are always written in en-US; an implementation MAY translate the label it shows and MUST NOT translate the bytes in the file. Every other attribute keeps the name whoever wrote the note gave it, in whatever language they wrote it.
| Key | Shape | Effect |
|---|---|---|
aliases |
list of short values | Alternative spellings of this note. They MUST join the search index as spellings of the note. They MUST NOT resolve wikilinks (§3.2 resolves by title slug only) |
tags |
list of short values | Subjects of this note, filterable and countable like any other list attribute |
created |
ISO 8601 date | The date the author states the note was created. See §4.6 |
updated |
ISO 8601 date | The date the author states the content was last revised. See §4.6 |
A reserved key whose value does not have the expected shape MUST NOT be an error. It degrades to an ordinary attribute and is classified by §4.3 like any other. This profile never validates content.
title is not reservedThe title of a note is structural: it is what the note is called, and what §3 resolves against. A title key in the frontmatter is an ordinary attribute; it MUST NOT rename the note and MUST NOT take part in resolution. In practice it is prose that is unique per note, and the cardinality ceiling of §4.3 stops indexing it on its own.
created and updated are what the author says about the content. An implementation that also knows the real history of the file — a revision log, an audit trail, a version control system — MUST treat that history as authoritative for the file, and MUST NOT present a frontmatter date in its place. The two answer different questions, and showing one as the other produces a product that disagrees with itself in front of the reader.
Dates are canonicalised to YYYY-MM-DD, which is ordered lexicographically. A query over a date attribute MUST match by prefix:
created:2026 the whole year
created:2026-09 the whole month
created:2026-09-03 the day
Prefix, not substring: created:09 MUST NOT match 2026-09-03.
A Reader renders a note for a person. Everything in this section is display: none of it produces an edge, an attribute or an index entry, and none of it changes the bytes of the note.
> [!warning] What can go wrong
> The body of the notice.
A blockquote whose first line begins with [!type] is a callout. The type is the word between [! and ], case-insensitive; the rest of that line, if any, is its title. A Reader MUST render it as a callout rather than as a quotation with a marker in front of it, and MUST NOT render the marker as text.
An optional + or - immediately after ] marks the callout as foldable, expanded or collapsed respectively. A Reader MAY honour it.
The type vocabulary is open. The five types of GitHub alerts — note, tip, important, warning, caution — MUST be recognised; a Reader SHOULD render an unknown type as a generic callout rather than as plain text.
A [!type] inside a code fence is not a callout. A conforming Reader therefore has to recognise callouts on the parsed document, not on the raw string.
A fenced code block whose info string is mermaid is a diagram. A Reader SHOULD render it. A Reader that cannot MUST fall back to showing the source as a code block, never to hiding it.
![[target]] and ![[target#section]] are embeds. A Reader SHOULD render the content of the target in place: the whole note for the plain form, the named section for the anchored form.
Expansion MUST be limited to one level: an embed found inside embedded content is rendered as a link to its target. Without that limit, two notes that embed each other hang the page.
A Reader SHOULD impose a ceiling on how many embeds one page expands, and MUST render the ones past the ceiling as links rather than dropping them.
An embed MUST NOT be expanded anywhere but on the reading surface. What an interface renders is display; what a tool returns is the note. A reader of the raw note gets the ![[...]] that was written.
A Reader MUST render a wikilink as a navigation to the resolved note, using the alias as the visible text when one is present. A wikilink whose target does not exist yet MUST be visibly distinguished from a resolved one, and MUST NOT be rendered as a broken link or hidden.
GFM task list items MAY be interactive. A Reader that lets a person toggle one MUST write back exactly the one character that changed, and MUST NOT rewrite, reformat or re-serialise the rest of the note.
Each of these is written by somebody every day, in some tool, with an effect this profile deliberately does not give it. An implementation MUST NOT assign them the meaning described as absent, and SHOULD tell whoever writes one what to write instead.
#subject#subject written in the body of a note carries no meaning in this profile. It is neither an edge nor an attribute. It is stored and returned exactly as written, and a Reader MUST render it as plain text, with no label, no colour and no link.
Write tags: in the frontmatter to group notes by subject. Write [[subject]] to connect a note to a subject that deserves a note of its own.
Two established lineages read # in incompatible ways: as a link (Roam, Logseq) and as file metadata (Obsidian). There is no standard to inherit, so this profile chooses neither and says so. Its reason: the curation vocabulary belongs in the frontmatter, where the vault declares it, and reading the body for meaning would require a third reader of content, which §1.4 forbids.
Anything with a scheme or a host refers to the world, not to the vault. Use external links freely as sources; do not expect a connection.
A value longer than 40 characters is read and discarded (§4.3). A summary belongs in the body, where it is searchable, and not in an attribute that would become a category of one.
==highlight==, %%comment%%, ^block-id, $math$, #tag, raw HTML: notation absent from this document is not part of the profile. An implementation MAY render it and MUST NOT claim conformance on account of it, and MUST NOT derive meaning from it.
profile.json carries every notation of §3 to §6 as data: an identifier, the ring, which reader decides it, the syntax, a worked example, the observable effect, and whether it is recognised. It is validated by schema/profile.schema.json.
It exists so that a specification and an implementation cannot drift apart in prose. An implementation SHOULD build its own documentation, and anything it teaches an agent, from this file rather than from a copy of it.
tests/conformance.json is the executable half of this document: each case is a Markdown input and the links and attributes a conforming Indexer produces from it. Cases whose expectation is nothing are as important as the others.
An implementation claiming the Indexer role SHOULD run the suite in its own continuous integration. See tests/README.md for the format.
This profile carries a version of its own, independent of any implementation. It follows Semantic Versioning: a notation added is a minor version, a notation removed or an effect changed is a major version, and while the version is 0.x a breaking change may arrive in a minor one.
Each released version is published at a stable URL under https://md.memorysmith.app, and the unversioned root always serves the latest.
A document written in this profile is text/markdown. When the variant is declared per RFC 7763 and RFC 7764:
Content-Type: text/markdown; charset=utf-8; variant=MemorySmith
The variant is not registered with IANA at this version.
| Implementation | Roles | Profile version | Known deviations |
|---|---|---|---|
| MemorySmith.app | Reader, Indexer, Writer | Tracking 0.1.0 | 1 open |
An implementation is listed here when it runs the conformance suite in public. Conformance is the suite passing, never a claim in a README.