geml-spec/geml

geml

One format, two readers. People and AI agents now co-write the same document. Legible for people; addressable, verifiable, and versioned for machines. GEML is plain text — organized by one typed block for everything, remembered by a .gemlhistory sidecar.

미분류ai-agentscall-graphclaude-code

GEML — General Expressive Markup Language

*English | 中文*

GEML is a lightweight markup language with uniform, addressable blocks and standard verbs. Easy for humans to read, safe for agents to mutate. In agent-driven development and knowledge work, plain text and Markdown have no deterministic block boundaries: a program and a model trade the whole file in and the whole file back out — at best probing for it with line windows, and restating the original verbatim to rewrite it. Token cost grows with the length of the document, and the operation turns bloated. After a few rounds of rewriting, the copies excerpted elsewhere start to drift.

You can start without changing a thing. geml list, geml find and geml get address the Markdown you already have — nothing is converted, no new files, your .md stays .md:

geml list    README.md                          # every section, as an address
geml get     README.md '#key-features'          # read ONE section, not the file
geml set     README.md '#key-features' --body   # write one section back
geml replace README.md 'old text' 'new text'    # swap a string, told which block held it
geml history save README.md -m 'tighten intro'  # a revision into README.md.gemlhistory, beside the file

Only that section enters the agent’s context — a couple of KB, not the whole file, however long it grows. And every save is a revision you can read back or roll back, one block or the whole file.

▶ Try it in the Playground — no install, nothing to read first.

Need finer than a section — one block, one chart, one table? Let .geml stand in the middle ground: edit at that grain, and the --to md you ship never drifts from it.

A block has a name; the things inside it have a coordinate. A table's cell, a data block's leaf, a key in meta — each has a coordinate the structure already gives it, and get and set land on exactly that value.

geml get doc.geml '#fy[2]["Q1"]'                     # one cell
geml set doc.geml '#intake["fields"][1]["name"]'     # one leaf in the JSON

For people, it is plain text that reads clean; for agents, it is an addressable, verifiable, traceable, revertible "Doc-as-a-Base".

GEML is minimal. It is plain text — still clean with no renderer in sight; one block syntax for the whole language; addressable, verifiable, referenceable structure, natively.

Instead of a separate mini-syntax for each kind of content, GEML carries every kind in one container: the typed block. Code is a block. So are tables, diagrams, math, callouts, even metadata — and a run of prose can be one too (=== text), whenever you want it addressable. Extending it later is just as plain. The shape is the same every time, which makes the language easy enough to learn that it's hard to get wrong.

=== code {#hello lang=python}
print("hi")
===
geml get doc.geml '#hello'   # by name, just this block

Blocks have names so the verbs have somewhere to land — the full syntax is in the format in 1 minute.

Contents: What it solves · Why now · What's different · The format in 1 minute · Profiles · Get hands-on · With an LLM · Maturity & versions · The design · Roadmap · Take part ·

What it solves

Problems solved

  1. Context load and token bloat
  • Status quo: data formats like JSON/XML carry heavy wrapper tags and syntax symbols; Markdown lacks strict structural metadata and a reference mechanism.
  • Approach: tuned markup density and syntax overhead, reading and writing only the target block — context cost no longer grows with document length, keeping agent reads and writes lightweight.
  1. AST-level precision and parsing determinism
  • Status quo: unstructured text degrades over multiple rounds of LLM reads and writes — broken formatting, semantic drift, parsing hallucinations.
  • Approach: a deterministic grammar that maps directly to an abstract syntax tree (AST), so programs and LLMs perform atomic block-level create/read/update/delete.
  1. Document copy fragmentation
  • Status quo: multi-agent collaboration and shared pipelines pass content around by copy-paste, leaving multiple disconnected copies.
  • Approach: Single Source of Truth by design — standardized module references and data binding eliminate redundant copies and version divergence.

Key features

1. AST-level structured operations

  • Uniform node definitions; a document parses directly into a typed document tree (AST).
  • Agents pinpoint the target section, attribute or component; partial patches and idempotent updates replace whole-file rewrites. Writes land as byte splices with whole-document re-validation — the tree serves reading and validation, and every untouched byte is guaranteed unchanged.

2. Low-token reads and writes

  • What is saved is not markup characters — it is the part never read: #id hits one semantically complete block, and the rest never enters the context.
  • For the same semantics, markedly lower prompt-token cost: better model throughput, lower inference cost.

3. Single source of truth, modular references

  • Native cross-document, cross-fragment component references.
  • Change the source node once and every reference follows — no version skew.

4. Robust two-way reads and writes

  • One block shape for the whole language — easy to generate and hard to get wrong, a good match for mainstream LLM output distributions.
  • A strict validator with precise error locations and actionable repair feedback.

5. Profile-based domain extensibility

  • Zero dialect chaos: extend domain vocabularies (e.g. design styles, interactive forms, code graphs, media timelines) through declarative profile metadata without inventing new syntax or breaking parsers.
  • Static type and constraint checking with safe fallback to standard blocks in unknown environments.

Comparison

Dimension · Markdown · JSON / YAML · GEML Context cost (block-wise I/O) · High (whole file in and out) · High (whole file + syntax noise) · Minimal (only the target block) Precise AST operations · Weak (no strict semantic nodes) · Strong · Strong (built for agent reads and writes) Human readability · High · Medium · High Single-source references · Unsupported · Needs protocol extensions · Native (modular embeds) Domain extensibility · Fractured (proprietary syntax hacks) · Schema-dependent · Native Profiles (zero new syntax + verified) Write safety · Weak · Medium · Strong (a bad write is refused before landing + single-block revert)

Why the LLM era needs a brand-new text format

Because both the producer and the consumer of a document have changed.

In traditional software engineering, a document was either a static explanation for people to read, or a serialized data file for programs.

Today, people and AI agents collaborate on the same document at high frequency. When the agent becomes the document's "second reader and co-author", the old balance breaks for good:

  1. Context is scarce compute: every whole-document read or write burns an agent's limited attention window and reasoning budget;
  2. Human–machine collaboration needs an isomorphic carrier: people need to read it at a glance, agents need to read and write it precisely, block by block;
  3. Knowledge must have a single source of truth: scattered prompts and copy-pasted Markdown are destined to decay with every iteration.

Yet none of our existing text infrastructure was designed for this scene:

  • Markdown (typeset for people): no stable structural blocks, no machine keys. To change one parameter, an agent must read and write the whole text — wasting context budget across multi-turn loops, and inviting drift in both format and meaning.
  • JSON / XML (serialized for machines): full of wrapper syntax and structural noise — blocking natural human reading, while quietly eating expensive tokens in long contexts.
  • Scratch memory and scattered files (no single source of truth): context is torn across chat history and Markdown copies everywhere; a copy is drift from the moment it is made, and version skew and hallucinated distortion follow.

The root of all three failures is each tool's own virtue: Markdown's "never error, write anything" is what gives people their freedom to write — and exactly why a machine cannot trust the structure it reads back; JSON/XML's strict schema is what gives machines their certainty — and exactly why nobody writes prose in it. The virtue is the defect, which is why patches cannot fix this: bolting "a broken reference must fail the build" onto Markdown betrays its contract, and stripping the wrapper syntax from JSON denies its nature. When people and agents start co-writing the same text at high frequency, what is needed is not a compromise between the two poles, but a format that treats "readable by people" and "operable by machines" as one design constraint from day one.

The answer: "Doc-as-a-Base"

GEML invents no heavy new runtime. Borrowing from the REST architectural style of Dr. Roy Fielding's dissertation, it gives plain-text documents one standard set of operational semantics:

Old pain · The matching capability (the four laws) · What it buys developers and agents Changing one spot means rewriting the whole text · The Law of Addressing · Every block carries an #id; get/set reads and writes that block alone. What is never loaded cannot be broken — the context window stays yours. Copies everywhere, all drifting · The Law of Projection · === embed evaluates dynamically instead of copy-pasting; one definition at the source ends the labor of syncing copies. Bad formats / broken references pollute downstream · The Law of Validation · References and syntax are checked at build time; a bad write is stopped before it lands, with no waiting for human review. One bad edit forces a whole-file rollback · The Law of Rollback · The companion .gemlhistory reverts a single block atomically — no tearing down the page; a lightweight version safety net for agents.

A document no longer needs just a format — it needs a set of verbs. GEML keeps plain-text readability and adds deterministic block-level operations.
💡 Deep Dive:
If you are interested in the dilemma of engineering documents in the LLM era and why we need to redesign a plain-text format from the ground up, read our full article on the blog: "Why Do We Need a New Text Format in the Era of LLMs?"

What's different about GEML

GEML stays small on purpose — the thinking, what it refuses, and what is still open are in how we thought about the design.

The four capabilities were established a chapter ago — addressing, projection, validation, rollback. This chapter is where each format lands against them, and where GEML draws its boundaries.

How other formats compare

Each of the four has mature solutions in its own field; what's unusual is meeting all four in one plain-text format:

Family · What the state really is · Addressable / referenceable · Projectable / embeddable · Verifiable · History / traceability Word / Docs · Opaque state · ❌ No block-level keys; access via platform APIs · ❌ Copy-paste only · ❌ No checking at all · ⚠️ Platform server-side, not in the file Markdown / AsciiDoc · A stream of characters · ⚠️ Heading anchors or dialect ids; no read/write verbs · ⚠️ Dialect embeds (Obsidian ![[…]], include::) — break silently · ❌ Broken links fail silently · ❌ None in-format — external git required JSON / XML · Data serialization · ✔️ (id / schema) · ⚠️ XML only (XInclude, external) · ✔️ Via an external toolchain · ❌ None in-format — external git required GEML · Plain text + block structure · ✔️ A unique `#id` per block (referenceable natively) · ✔️ `=== embed`: a reference is a lookup (native) · ✔️ A build-time error · ✔️ `.gemlhistory` next to the file (traceable natively)

Item by item: vs. CommonMark · vs. XML and JSON · a 7-format capability matrix · the Markdown variants and tools in awesome-markdown (Chinese, an HTML page).

Coexisting with Markdown: GEML is the editing source of truth, Markdown is the delivered artifact. Project one way with geml --to md|html and ship .md or .html as before. Collaboration, not lock-in. *(Projection is lossy: block ids and table-bound charts don't survive it.)*

Don't take the table's word for it — re-run it. This is what I asked the model:

Based on your own experience editing the READMEs just now, describe the command steps you go through on a document (I saw you using grep and such), and whether you cache documents to save tokens — let's compare, and from that see which parts of GEML would actually earn their place.

What came back: what one edit costs and a real day replayed. Paste the question to your own model and see what it tells you. PS: I am still trying to work out whether the upstream chain (who calls this) and the downstream chain (what it calls) that codemap produces can pin down functions and call sites — and change project code — the same way. I will post a report when I have one.

The format in 1 minute

Typed blocks

One shape, every type. A block's basic syntax is === type [attributes] … === (where attributes like {#id .class key=val} are optional) — only the type (and how its body is read) changes:

=== code {lang=python}
print("hi")
===

=== note {.intro}
Parsed prose with *emphasis* and a [[#budget]] reference.
===

=== meta
title = "Budget plan"
===

A run of = (three or more) opens a block; an equal-length run closes it; longer fences nest inside shorter ones. A block that carries an #id can also close with the labeled fence === #id — no fence-length counting, which makes long blocks much harder to get wrong (nesting still requires a longer outer fence: a same-length bare === in the body closes the block early, labeled or not). The type decides how the body is read — raw (verbatim: code, diagram, math, table), flow (parsed prose with inline markup: note, text), or data (one key=val per line: meta); embed carries no body at all — its src= names the block it stands for — and every block may carry an attribute object {#id .class key=val}, where a .class is a *semantic* label, never a styling hook. The full inline grammar (emphasis, links, [[#id]] auto-references, media, footnotes, inline $math$) is in the spec.

Tables — two bodies, one model

Write a table visually:

=== table {#budget caption="Annual cost"}
| Plan  | Months | Rate |
|-------|-------:|-----:|
| Basic |      1 |   30 |
| Pro   |      2 |   30 |
===

…or as data. A table holds the facts; a `view` over it derives the computed columns and the summary row:

=== table {#fy25 format=csv header=1}
Segment,  Q1, Q2, Q3, Q4
Cloud,     8, 10, 12, 14
Platform,  5,  6,  7,  9
Services,  3,  4,  4,  5
===

=== view {#fy25-report src=#fy25 compute="FY [%.1f] = Q1 + Q2 + Q3 + Q4; n = 1" summary="Segment = 'Total'; FY [%.1f] = sum(FY); n = sum(n)"}
===

*Both table forms describe the same model. The FY column and Total row are computed at build time, by the view:*

Segment · Q1 · Q2 · Q3 · Q4 · FY · n Cloud · 8 · 10 · 12 · 14 · 44.0 · 1 Platform · 5 · 6 · 7 · 9 · 27.0 · 1 Services · 3 · 4 · 4 · 5 · 16.0 · 1 Total · · · · · 87.0 · 3

compute runs + - * / ( ) per row over columns; summary adds a foot row from the aggregates sum / avg / min / max / count (with arithmetic over them, e.g. weighted ratios); a trailing [printf] sets numeric display. n above is the row-count idiom — count tallies non-empty cells in one column, so a constant column summed is what counts rows.

Tables can also pull their data from an external CSV via src="regions.csv".

Math

=== math {#gauss caption="Gaussian integral"}
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
===

$$\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}$$