A Markdown vault, addressed by block
SkillDocs & knowledgeWork a Markdown knowledge base, an Obsidian vault, a wiki, a docs tree, by BLOCK ADDRESS instead of by whole page. Use when the job is to find which page and which section says something, read that one section, add a line to an index or a log, or rewrite one section of a note: `geml find` answers with `file#address`, `geml get` hands back that block alone, and `geml set` / `geml add` change it in place while every other block, and the YAML frontmatter, stay byte-for-byte identical. Wikilinks, callouts and embeds are written through verbatim, so the file stays the Markdown Obsidian renders. No Obsidian process, no REST API, no plugin required. Triggers on: wiki, vault, knowledge base, second brain, ingest a source, update the index, append to the log, find which note says, edit one section of a note.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the A Markdown vault, addressed by block skill
What this skill tells your AI
The instructions your AI receives, as published by geml-spec/geml in integrations/obsidian/skills/geml-vault/SKILL.md and read by ahel’s review.
The pages stay Markdown. Nothing is converted. What changes is how you reach
into them: an address (#entities) instead of a line number, and a write
that touches one block instead of rewriting a file.
This buys three things grep + Read + Edit cannot give you:
- An address survives edits. A line number stops being true the moment
anything above it changes.
#entitiesdoes not. - A write needs nothing read first. No whole page pulled into context to
build an
old_stringthat has to match exactly. - It works offline. No Obsidian running, no Local REST API, no API key.
Read: locate, then take one block
geml find '<literal text>' <dir> --head # → file ⇥ #address ⇥ the matching line
geml get <file> '#address' --body # → that block, nothing else
geml list <file> # → every address in one page
find walks a directory for *.geml and *.md. It skips hidden
directories — a vault that hides sources in .raw/ must name that directory
explicitly.
Read a whole page only when list shows it is short enough that taking one
block saves nothing. Most notes are; index, log and cache pages are not, and
those are where this skill earns its place.
Write: change one block
geml list <file> # ALWAYS first — addresses AND the kind column
geml set <file> '#heading' --body --in - # a HEADING: --body replaces the section body
geml set <file> '#prose-addr' --in - # a PROSE block: NO --body, or it appends
geml add <file> --after '#id' --in - # insert a new block after one
geml add <file> --append --in - # append to the end (log pages)
--body and the block's kind must agree. On a heading it means "the section
below the heading line"; a prose block has no body of its own, so --body
writes after the prose instead of over it — silently, exit 0. geml list
prints the kind in its second column, which is why it comes first.
A write is re-parsed before it lands: a change that would break the document is refused, and nothing is written.
What you get back, verified by test/vault.test.mjs:
- frontmatter and every block you did not address are byte-for-byte unchanged;
> [!tip]callouts,[[wikilinks]],![[embeds]]and```dataviewblocks are written verbatim, unescaped — the page still renders in Obsidian.
The rules that keep this safe
Read references/invariants.md before the first write in a session. The six
that bite hardest:
- Never
setthe frontmatter block. Its closing---lives inside the block body; replacing the body deletes it and the page loses every property. Frontmatter is read-only through this skill. - Never write to an
@hashaddress.@…is a content hash and changes the moment the content does. Writes address#idonly. --bodyon a prose block appends instead of replacing. Match the flag to the kindgeml listreports:--bodyfor a heading, no--bodyfor prose. The wrong way round is refused on a heading and silent on prose.- Never
Writea page that already exists. Useset/add.Writeis for a page you are creating. - A block holding
[[Note#Heading]]cannot be written.[[file#id]]is GEML's own reference syntax and is checked on write; there is no document calledNote, onlyNote.md, so the write is refused.[[Note]],[[Note|alias]]and![[image.png]]are all fine, and[[Note.md#Heading]]is the spelling that passes — Obsidian follows it too. - A page with two identically-titled headings cannot be written — the
derived ids collide, and the guard judges the result, so a write to any
section of that page is refused. Check with
geml check <file>first.
Rules 5 and 6 are refusals — nothing is written, and the error says so. Rules 1, 2 and 3 are silent, which is why they have to be rules rather than error messages you would see.
Using this with an existing vault convention
references/claude-obsidian.md maps this onto the claude-obsidian plugin's
vault: its directory layout, the stable addresses in its index.md, which of
its skills' steps to replace, and the one file that must never be edited with a
tool.
Any other vault works the same way; the reference is an example, not a requirement.
Block history, when you want it
Off unless asked for. geml history save <page> snapshots a page and
geml revert <page> '#id' rolls back one block. The cost is one
<page>.md.gemlhistory sidecar per edited page, inside a directory that Obsidian
syncs and git tracks.
If the vault is already in git — most are, and claude-obsidian commits it on a
hook — this is a second, partial history of the same thing. Leave it off.
The link graph
scripts/vault-graph.mjs reports orphans and dead wikilinks, each dead link
carrying the block address that holds it — so the fix is a geml set on
that address, not a hunt through the page.
node integrations/obsidian/scripts/vault-graph.mjs <vault-dir> [more-dirs…]
Signals
- GitHub stars
- 26
- Forks
- 1
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
geml-vault- Source
- github.com/geml-spec/geml