OMGDB treats Markdown as a first-class input. A .md file — a spec, a note, a piece of documentation — can be imported as a single document where the YAML-ish frontmatter becomes typed, queryable top-level fields and the heading structure is captured under _sections, each section carrying a stable id and byte offsets into the stored body. The raw Markdown body is stored too, so nothing is lost. This is the knowledge layer an agent reads from — and, through the md-set-section and md-patch-frontmatter commands, writes back to: notes and specs land as ordinary documents you can query by their frontmatter and edit section by section.
Import is exposed through the CLI command omgdb import-md. Internally it parses the file, builds a document, inserts it, and prints the new _id.
Importing a file
omgdb create app.omgdb
omgdb import-md app.omgdb docs spec.md
# -> prints the new document's _id as canonical JSON
The command takes three positional arguments: the store path, the target collection, and the path to the Markdown file. The whole file becomes exactly one document in the named collection.
| Argument | Description |
|---|---|
<path> | Store path, e.g. app.omgdb. |
<collection> | Collection the document is inserted into. |
<file> | Path to the .md file to parse and import. |
The document shape
A parsed Markdown file is converted into a document with three parts:
- Frontmatter fields — each
key: valueline from the frontmatter block is lifted to a top-level field, typed where possible. body— the raw Markdown after the frontmatter, stored verbatim as a string._sections— an array of section objects, one per ATX heading, in document order. Each records a stablesectionId, aslug, the headingleveland text, the section’stextbody, and four byte offsets intobody(startByte,endByte,textStartByte,textEndByte).
Frontmatter parsing
Frontmatter is recognized only when the file begins with a literal --- fence line and a matching closing --- fence follows. Inside the block, each non-empty line is split on its first : into a key and a value. The value is parsed as JSON when possible and otherwise kept as a string:
views: 10becomes an integer (I64).tags: ["rag", "db"]becomes an array.title: Hello Worldstays a string (unquoted prose is not valid JSON, so it falls back to a string).
Limitation: Frontmatter is a minimal line-based subset, not a full YAML parser. It splits on the first
:per line and parses each value as JSON-or-string. Block scalars, nested maps, and multi-line values are not supported. A file with no---fences yields no frontmatter fields, and the entire text becomesbody.
Sections and the source map
Every ATX heading (# through ######) starts a new section. A line is treated as a heading only when, after trimming leading whitespace, it begins with 1–6 # characters immediately followed by a single space. Each section records:
| Field | Description |
|---|---|
sectionId | Stable unique identifier derived from the heading slug. Duplicate headings get numeric suffixes: api, api-2, api-3. This is the handle section edits address. |
slug | The (non-unique) slug of the heading text. |
level | Heading level, 1 for # through 6 for ######. |
heading | The heading text, with the leading #s stripped. |
text | The body text under the heading, up to the next heading. |
startByte / endByte | Byte offsets of the section’s full source (heading line included) inside body. |
textStartByte / textEndByte | Byte offsets of just the section body source, after the heading line. |
The byte offsets are a source map: slicing body between a section’s offsets yields exactly that section’s original Markdown, so a section can always be mapped back to its precise position in the file — which is what makes surgical section edits possible.
Note: Despite the “section tree” phrasing,
_sectionsis a flat, ordered array. Heading levels are recorded in thelevelfield, but no parent/child nesting is built. Any content before the first heading is not captured in a section (it still lives inbody).
Example
Given this spec.md:
---
title: Hello World
views: 10
tags: ["rag", "db"]
---
# Intro
the intro text
## Details
more text
Importing it produces a document of this shape:
{
"title": "Hello World",
"views": 10,
"tags": ["rag", "db"],
"body": "# Intro\n\nthe intro text\n\n## Details\n\nmore text\n",
"_sections": [
{ "sectionId": "intro", "slug": "intro", "level": 1, "heading": "Intro",
"text": "the intro text\n", "startByte": 0, "endByte": 25,
"textStartByte": 8, "textEndByte": 25 },
{ "sectionId": "details", "slug": "details", "level": 2, "heading": "Details",
"text": "more text", "startByte": 25, "endByte": 47,
"textStartByte": 36, "textEndByte": 47 }
]
}
Note that title came through as a string, views as a number, and tags as an array — each frontmatter line was typed according to whether its value parsed as JSON.
Editing sections and frontmatter
Imported documents are not read-only snapshots. Two commands rewrite them through ordinary op-log-backed replacements — the edit is a replace op in the log like any other write, _id is preserved, and body and _sections are re-derived so the source map stays exact.
md-set-section
Replaces the body under one section, addressed by its stable sectionId:
omgdb md-set-section app.omgdb docs '{"$oid":"..."}' intro "updated intro text"
# or, for multiline content:
omgdb md-set-section app.omgdb docs '{"$oid":"..."}' intro --file new-intro.md
The heading line is preserved; the replacement text becomes the section body (a trailing newline is added if missing, so the next heading can never be joined onto the new text). The document is reparsed after the edit, so headings inserted by the replacement text become addressable sections of their own, with fresh offsets. An unknown section id fails with Markdown section `intro` was not found. The command prints the full replacement document.
md-patch-frontmatter
Inserts, replaces, or removes frontmatter fields without touching the body:
omgdb md-patch-frontmatter app.omgdb docs '{"$oid":"..."}' \
'{"title":"Updated","published":true}' --remove views
Removals apply first, then the patch’s key/value pairs are inserted or replaced (existing field order is preserved where possible). null is a normal stored value — use --remove (repeatable) to delete a field. The generated fields _id, body, and _sections are reserved: patching or removing them fails with frontmatter field `body` is reserved.
Both edits are also exposed over MCP as the write-scope tools markdown_set_section (path, collection, id, sectionId, text) and markdown_patch_frontmatter (path, collection, id, patch, optional remove), so an agent can maintain a knowledge base through the same audited path.
Querying frontmatter
Because frontmatter fields are stored as ordinary top-level fields, they are queryable like any other field — no special syntax. After import you can find on a frontmatter value:
omgdb find app.omgdb docs '{"title":"Hello World"}'
# result contains "Intro" and "_sections"
The same applies to the typed fields. A numeric frontmatter field works with the comparison operators, and an array field works with array operators:
omgdb find app.omgdb docs '{"views":{"$gte":10}}'
omgdb find app.omgdb docs '{"tags":"rag"}'
The _sections array travels with the document, so the heading structure is available to read back, and the raw body is always present for full-text or downstream processing.
Limitations
Limitation: Import is one-shot. OMGDB does not watch or re-sync the source
.mdfile after import — subsequent edits go throughmd-set-section/md-patch-frontmatter(or an ordinary replace), not the original file.
Note: Heading-aware chunking into embeddings is not part of import. Embedding-based retrieval is provided by the vector layer — see vector search. A field holding Markdown text can be declared as the
markdowntype in a validation spec.
See also
- Data model — how documents, types, and field paths work, including the conventions imported Markdown follows.
- Vector search — embedding and retrieval over imported content.
- Query operators — filtering on the frontmatter fields produced by import.