21STARK
All posts
13 min read views

Markdown is memory, git is safety: 1,013 commits in three weeks, 930 of them autonomous

My second brain is 211 Markdown files in a git repo. Machines committed to it 930 times in three weeks, the engine on top was rewritten from scratch, and not one note was lost. The mechanism, the gates that make direct-to-main sane, and the one-commit blast radius.

In the past three weeks, 1,013 commits landed on my personal knowledge vault. First commit 2026-06-27, latest 2026-07-17. Exactly 21 days. 930 of those commits were made autonomously by a sync service while I was in meetings, asleep, or writing something else. 932 of them follow the machine format brain: update <topic>. Every single one is authored Aryeh Stark. Every single one is one git revert away from never having happened.

The thing they landed on is boring by design. 211 Markdown notes in a plain git repository. No database of record. No memory SaaS. No plugin that correctness depends on. Since late June, the engine on top of this vault has been written twice, roughly 20K lines of Go and then 86K lines of TypeScript, and the two implementations share exactly one thing: the file format. Not one note was lost.

Markdown is memory. Git is safety. Here is the mechanism, including the parts that broke.

The problem is writes, not recall

Everyone is shipping agent memory right now. Vector stores, memory APIs, assistants that promise to "remember you". Most of them share one design decision: the agent writes into state you cannot diff.

That is the wrong risk to accept. The hard problem in agent memory is not recall. It's writes. An agent that reads your notes and answers badly wastes one query. An agent that writes a wrong note poisons every future answer, silently, and the poison compounds. If you cannot review what was written and undo it precisely, you are running contamination with good marketing.

A drop of dark indigo ink dispersing into a clear glass tank of water, tendrils spreading below the surface against a bright white background.

I had a second constraint, older than the agent wave. Knowledge has to outlive tools. Note apps die, pivot, get acquired. Anything I trust with years of thinking must stay readable by cat after every tool around it is gone.

So the spec: agents write constantly, every write is reviewable and revertable, and the store survives any engine rewrite. That spec already has a battle-tested implementation. It is called a git repository full of text files.

One substrate, three codebases

The system is split in two: data and code.

The vault is the data: 211 Markdown notes, Obsidian-compatible but not Obsidian-dependent. Its own manifest says so: model: obsidian-compatible-not-dependent, plugins_required_for_correctness: false. Obsidian is a viewer here, not a dependency.

The code is a separate Go monorepo, three modules under one go.work: brain-cli (15,108 lines, the brain CLI plus a brain-mcp MCP server), brain-hub (4,772 lines, the always-on sync service), and vault-keeper (exactly 0 lines of Go: a design spec and an implementation plan, reviewed, not yet built. Specs ship before code in this house). 368 Go test functions and 11 ADRs across the fleet keep the decisions honest.

Why two repos? The root CLAUDE.md answers verbatim: "The vault is data, not code: it has a different change cadence, a different review model (gitleaks hard gate, direct-to-main capture), and must be usable with zero build tooling". So it stays separate. Correctness must never depend on the code being checked out next to the data.

The governing principle sits at the top of that same file:

The Markdown vault is the source of truth.
brain-cli provides intelligence and automation over it.
The MCP server reuses brain-cli's internals, so agents get the same behavior as the CLI.

Plus a fourth line making Obsidian optional: correctness must never depend on Obsidian plugins.

Every surface is the same brain

One set of internals, many doors. The brain CLI exposes 22 top-level commands. The brain-mcp server exposes 13 MCP tools. These are not two implementations kept in sync by discipline. The MCP server reuses the CLI's internal packages, so an agent calling upsert_note over MCP hits the same validation, the same link index, the same secret scan as me typing brain upsert in a terminal. When the CLI and the agent can never disagree, you stop debugging phantom drift.

The hub takes this one step further, and this is the decision I would defend in any design review. brain-hub never imports brain-cli's packages. It executes the binaries it builds from them. Every reusable package sits under Go's internal/, so importing is structurally impossible. The ADR's reasoning: the alternatives were coupling a cloud service to a zero-dependency local CLI, or freezing a public Go API nobody asked for. Instead, the process boundary is the API. The CLI surface (flags, JSON output, exit codes) "is the stable contract".

The exit codes are worth showing, because they encode the philosophy:

ExitMeaning
0ok
1validation problem
2config / vault problem
3secret-scan finding
4internal error
5, 6provider-fatal, push-conflict (vault-keeper spec)
7spend circuit-breaker (vault-keeper spec)

Look at exit 7. A reserved exit code for blowing the LLM budget. Cost overrun is a first-class failure mode, same tier as a corrupt vault.

Two more internals decisions that look like paranoia until you need them. First, the MCP server reloads the vault on every call. Caching parsed notes would serve stale data the moment Obsidian, git, or another agent touches a file, silently breaking the source-of-truth principle. On a single-user vault the reload costs nothing worth measuring, and one stale read breaks the only guarantee the whole system makes. Second, my notes mix Hebrew and English, so byte-offset string slicing corrupts them. Rune-safe truncation and fenced-block masking are dedicated helpers, and the rule is repeated in three CLAUDE.md files. A memory system that mangles your actual language is a demo, not a system.

930 autonomous commits, gated

The hub runs on GKE Autopilot. It keeps a live clone of the vault, captures from a personal Slack app, ingests GitHub webhooks, and syncs every 60 seconds. And it commits directly to main.

Read that again. An unattended service, fed by Slack messages and webhooks, pushed straight to main 930 times in three weeks, on the repo that holds my working memory. On a code repo I'd call that malpractice; my own workspace rule is branch plus PR for everything, no exceptions. The vault gets the opposite policy, and the reason is the asymmetry of harm.

Two gates, deliberately unequal. From brain-hub's ADR 0003, verbatim:

Secret scan = HARD gate, fail closed. Scan failure or findings → discard the uncommitted changes (checkout . && clean -fd) + Slack alert. A secret never reaches the remote, even at the cost of dropping a capture.

Validation = SOFT gate. Errors → commit + push anyway + Slack alert. Imperfect frontmatter is repairable from any client; a lost capture is not.

I call this rule scan-hard, validate-soft, and it generalizes: gate hardness must match harm reversibility. A leaked secret has you rotating credentials at midnight; broken frontmatter can wait for any client, any day. A poisoned capture is dropped, not quarantined, and the Slack alert carries the finding. Nothing disappears silently.

Third case: a rebase conflict. The hub refuses to be clever. Readiness goes 503, reads keep serving, a human reconciles. The ADR again: "auto-resolving conflicts on a knowledge vault risks silent data loss." A knowledge vault is exactly the place where a smart merge heuristic quietly eats a note you needed.

The policy itself lives in the vault, versioned with the data it governs:

# brain.yaml, in the vault repo (not the code repo)
git:
  commit_mode: task-end
  commit_message_format: "brain: update <area/project/topic>"
  require_secret_scan: true
  review_surface: [git-diff, session-summary]

The same file carries per-domain policy: work notes validate strict, personal notes loose. Cloud embedding egress is default-deny in code, with a single package owning the decision; every domain that wants cloud embeddings opts in explicitly, per-caller query egress is gated the same way (MCP yes, Slack yes, plain CLI no), and any cloud index build must contain a canary note. An index that should not exist announces itself.

What broke, and what the breakage proved

Those numbers all came from the happy path; the design earned its keep in the failures.

2026-07-11: monorepo merge and org rename, same day. The code started as three separate repos, and the killer was the cross-repo release dance: the hub checked out CLI source at build time. Merged into one monorepo, the Docker build reads sibling source directly. The GitHub org was renamed the same day, and every Go module path deliberately kept the dead org name so no import path would ever break. Names churn; data outlives them.

2026-07-17: the engine swap, performed live. Atlas is the second engine, a from-scratch LLM-native wiki engine in TypeScript (its spec places it in the spirit of Karpathy's "LLM Wiki"): 85,966 lines across 454 files, 50 CLI commands owned by a machine-checked contract registry, 1,502 declared test cases, built in 8 days across six PR-gated phases. Its design spec is explicit: it "reuses nothing from the existing 2nd-brain Go stack". The only shared thing is the vault format. Its creed reads like the thesis of this post, verbatim:

Markdown is the memory. SQLite is the operational projection. LanceDB is the retrieval projection. Git is the safety/audit mechanism. The LLM is a reasoning component, not the database.

Graduation day, on the real vault: 210 notes graduated, 0 refused, 0 quarantined, 3 renames. 199 notes chunked into 1,647 embedded chunks in 85 seconds, real embeddings. The index was rebuilt twice, deterministic, identical 199 and 1,647 both times. The acceptance test was a live question ("who runs the Cloud team") answered grounded and cited. And the line I care about most: the vault's HEAD did not move during the entire drive. The engine churned around the memory. The memory didn't notice.

The drive was not clean. The runbook turned out wrong six ways, each one a blocker: a fresh audit ledger colliding with a copied one at sequence zero, a migration that left output uncommitted plus a backup directory that made the rebuild refuse a partial snapshot, a migrate-before-rebuild ordering nobody wrote down, a missing egress capability key, a broker started before its inputs existed, and an apply-challenge nonce that expired during a two-hour gap. Six blockers. Zero data loss. Every fix was mechanical, because the entire failure surface was files, git, and explicit gates, not mystery state in someone's managed service.

And retrieval flat-out collapsed. At drive time, every FTS-weighted hybrid search config scored recall@10 around 0.49, because no full-text index had ever been built: the default path brute-force scanned with a tokenizer that had no stemming and no stop words, flooding the top results with junk. The gate still passed, on the vector-only fallback: recall@10 0.878, MRR 0.784, against thresholds of 0.85 and 0.70. One fix later, a real inverted index (stemming, stop words, ASCII folding) put default hybrid at 0.911 / 0.830.

Here is the point most memory products get backwards. The index broke badly and it cost one day, because in this architecture the index is a projection, not the truth. You can burn a projection to the ground and rebuild it from the text. Try that when your vector store is the truth.

The one-commit blast radius

Time to name the load-bearing idea. Every write an agent performs lands as exactly one reviewable, revertable git commit. I call it the one-commit blast radius. That is the entire rule, and everything above is scaffolding for it.

Claymation scene of steel tweezers lifting a single orange clay brick out of a long intact wall of white clay bricks on a bright background.

What it buys, mechanically:

  • A review surface for free. brain.yaml declares review_surface: [git-diff, session-summary]. I don't audit agents by trusting them. I read their diffs, the same way I read a junior engineer's PR. Post-hoc review with a guaranteed undo beats pre-approval gates you will stop reading by week two.
  • Undo for free. The vault-keeper spec says it in five words: "git history is the undo." 930 autonomous commits means 930 independent undo points, not one monolithic "restore from last night's backup" and a day of forensics.
  • Provenance for free. Machine-format commit messages (brain: update <topic>) make the log parseable. I can tell you which of the 1,013 commits came from the hub, from a CLI session, or from my own hands, with git log and nothing else.

Atlas hardens the same rule into enforcement. An agent proposes a typed change plan (17 operation types), the plan is validated, applied as section-level patches on a git branch behind a privilege-separated broker, and git approve and git rollback are privileged commands. The function that touches protected refs is unexported, so writing to main is structurally impossible from the agent's OS identity, and a signed, gapless, WORM-anchored audit ref records every run. Same floor, thicker walls: an agent write is a commit, and a commit can be undone.

The same idea also gives you a portable benchmark, call it the engine-swap test: could you rewrite your entire memory engine, in a different language, and lose nothing? I did it in 8 days, and the vault's HEAD never moved. If your answer is no, you don't own your memory. Your vendor does.

So when you evaluate any agent-memory product this year, ask exactly one question: when the agent writes something wrong, show me the diff and the revert. If the answer involves an export job or a support ticket, walk.

What to take

  • Put agent memory in plain text under git. Not nostalgia. It is the only store where review, undo, blame, and portability are already built, hardened by two decades of production use.
  • Version policy with the data it governs. Commit rules, scan rules, per-domain validation, egress opt-ins: one file, inside the vault, in history.
  • Match gate hardness to harm reversibility. Fail closed on the irreversible (secrets). Fail open with an alert on the repairable (frontmatter). Never auto-resolve conflicts on knowledge.
  • Treat every index as a disposable projection. If rebuilding it from the text scares you, your architecture is lying about where truth lives.
  • Enforce the one-commit blast radius. Then run the engine-swap test and see if you pass.

The part that lasts

brain-cli will die. Atlas will die too; I built it to be replaceable, so eventually it will be replaced. Whatever comes next will be smarter, faster, and mortal like the rest. The vault will not care. It opens in anything that reads text, and every mistake any engine ever makes inside it stays one commit away from gone.

Models are weather. Build your memory like ground.

Get in touch

I write about AI-first engineering on LinkedIn. Specs in, production out, nobody types code. Follow along there, or send a note.

hi@21stark.com · LinkedIn opens my profile, message me from there


Or send it from here