My own GitHub: Git, CRDT, Markdown

Vox:
gritzko
Day:
Fri 07 Aug 2026
For:
public

There have been many attempts to make git store project metadata. Indeed, GitHub has Issues and Wikis, Fossil has a bug tracker, wiki, forum and chat. The metadata is critical to a project, but bare git can't store it. The main obstacles are two: structured data and merges. First, we have to deal with semi-structured data, not just source code or text. Second, that data must survive merges and other revision control adventures.

Look at the current LLM-assisted coding practices. Most often, people have a folder of Markdown files that boots up LLM agents. It may be wiki-structured, issue-tracker-structured or not structured much. Either way, you can not get too far by reexplaining everything to every new Claude again. Normally, you would put that folder into a git repo. But can it be more structured rather than less? It would be annoying to grep for open tickets each time. It would be even more annoying to merge a dozen unfamiliar tickets on each pull.

Almost every attempt to solve these problems relies on a structured CRDT format. I myself authored several CRDT formats, and I track the field closely. One way is to use a dedicated native-CRDT format, e.g. RDX or Automerge. The Beagle project started as an RDX based SCM, CRDT top to bottom. A more pragmatic way is to cram CRDT metadata into a regular git repo. In particular, git-bug, Radicle and git-meta do that trick. The problem is that native-CRDT becomes incompatible with git, while git-CRDT abuses the normal git machinery to do distributed database operations. Most mainstream databases can not even do CRDT. And git is a terrible database.

For Beagle, that dilemma was solved by picking the third path. The very need for a CRDT store grew from the use of expensive tree-sitter parsers. Once those got replaced with fast Ragel-based tokenizers, git's blob store was OK again. No need to store the AST, as Ragel can tokenize any source file instantly on read. All the diffing, blaming and merging is now token-level CRDT over git-stored blobs. That is coarser than character-level tracking real-time editors use (e.g. Google Docs). Still, so much sharper than line-level Unix/git legacy diffs. As a free bonus, Beagle is now syntax aware, enough for merge, syntax hili, structured search and so on. All these things are implemented in libdog while Beagle is the malleable upper part of the thing.

But CRDT merges of Markdown do not solve the structure problem. What if I want to find all the open tickets assigned to me? Markdown has no "fields" or "attributes". So I added them! In fact, I use StrictMark, a dialect of Markdown with a formal grammar. As one might guess, it is stricter:

  1. all block-level formatting goes at the beginning of a line in 4-char blocks,
  2. all inline formatting is single-char bracketed.
Beagle: the ticket list view
Beagle: the ticket list view

So I added headers to StrictMark, like

    Due: Sat 8 Aug 2026

Agents picked up this syntax instantly. Then, I made Beagle parse and store the values. A folder with Markdown files became a database in an instant. The outcome:

  1. it is a folder of Markdown files,
  2. which are stored in git blobs,
  3. in a 100% git-compatible revision control system,
  4. merge is CRDT,
  5. everything is queryable like a database.

As of now, I have 1000 tickets in the repo. Every ticket on the screen has its worktree state, CI state, commit history state shown. Still, all the operations are instant. Having 1mln tickets would not change much, big-O wise. I have my own local GitHub to collaborate with agents. I can push and pull to/from github.com. As long as I do my merges locally, it is all CRDT.

Solved.


The true nerd part

As an academic exercise, let's do key CRDT data structures in StrictMark. Is consistency 100% guaranteed? Of course not, because we have multiple levels of consistency here: from bytes to tokens to language AST to correct build to all tests blinking green to actually having a correct program in the end. That is a universe of requirements, some of them unrealistic or mutually contradictory. Still, let's see how key data structures can be represented in StrictMark.

CT/RGA vector is trivial, as libdog merges versions with the chronofold data structure, which is CT/RGA. For Beagle, any part of this text is a vector of tokens. On merge, some anomalies are still possible. For example, two concurrently inserted tokens can merge in the byte stream and become one syntactical token in the result. This case Beagle indicates and marks as a conflict: concurrently inserted segments touch each other. Higher-level inconsistencies are possible even with no CRDT-level conflict though. The merged code is never guaranteed to be semantically valid. Still, most of the friction is removed here.

CRDT sets can be represented in CT/RGA if tokens get sorted by some criteria, e.g. alphabetically. Then, plain merges will be 100% correct, while merges of concurrent overlapping changes can potentially break the order as the CRDT layer is unaware of our sorting criteria. That can be solved by making the parser tolerant to element reordering and duplication.

CRDT maps, again, deal perfectly fine with non-overlapping changes, while, e.g., a concurrent value rewrite in a key-value pair may bring some chaos, like Key: Value1Value2. Again, the only way forward is to make the parser tolerant and normalize on every write. Frankly, this concurrent LWW conflict has no good solution -- there is no criterion to pick one of two causally unlinked options.