Drawing 01

Claude Brain

Persistent, cross-device memory for an AI collaborator — git-backed, concurrency-safe, reachable from voice and phone.

Context

Claude starts every session blank. I use it from Claude Code on a Mac and a Linux desktop, from claude.ai in the browser and the phone app, and in voice mode. Claude Code’s auto memory is scoped to one project on one machine. claude.ai’s memory is opaque and I can’t take it with me.

I wanted one collaborator on every surface, one that knows who I am and what we’ve built together. I started the brain on 2026-06-14 and use it daily. The brain is private; its design is public as Brain Kit, a template repo and a Claude Code plugin marketplace.

Problem

  • Durable. It has to outlive any service I run for it.
  • Every surface. Recall and writes from Claude Code, web, phone and voice, through official channels.
  • Concurrent. Several sessions run at once, often on one machine, and nearly every write touches the catalog file.
  • Safe. A brain leak must never be a credential leak.
  • Cheap on context. It can’t be loaded whole. Recall has to be selective.
constraint
The markdown files in git are the source of truth. Any retrieval layer is a disposable index on top. I set this on day one: a service can rot, files in git don’t.

What I built

Version one was too much. The first build added a layer over the repo: transcript capture to a VPS, Gemini embeddings, a Qdrant vector store, an n8n distiller and a recall MCP server. On 2026-07-08 I tore it down and rebuilt as Brain 2.0. The vector store mostly re-found facts the notes already held. I judged that keyword recall plus query expansion by the model covered about 90% of real queries on a 9 MB text corpus. The 14 VPS-only transcripts were repatriated first; nothing was lost. Vectors come back only if the corpus grows about 100× or keyword misses start to hurt.

Brain 2.0: the repo is the brain. Markdown notes, one fact per file.

  • Recall. A bootloader CLAUDE.md loads into every Claude Code session. It says: skim INDEX.md (a one-line description per note, 120 characters max), then open only the notes that match.
  • Writes. Since 2026-08-11 every write from a clone gets its own git worktree and is pushed to main at once. Without that, sessions on one device share one working copy, and concurrent edits to INDEX.md silently drop one side. Now remote main serializes writes. A push is an atomic ref update, the loser rebases, and a real collision surfaces as a conflict.
  • Sync. A SessionStart hook (since 2026-08-20) fast-forwards the clone and injects one status line, such as STALE or unpushed writes waiting. It always exits 0 and can’t hang, so it never blocks a session.
  • Web and phone. GitHub’s hosted MCP connector on claude.ai.
  • Secrets. Pointers only, never values. Gitleaks scans every push.
wt=$(brain-write.sh open)               # detached worktree at the newest main
# edit the note and its INDEX.md line inside $wt
brain-write.sh publish "$wt" "message"  # commit, push to main, rebase on a lost race

Voice needed its own server. Voice mode got connectors on 2026-07-23. By voice, the GitHub connector call succeeded and the model got nothing back. The GitHub MCP server returns a file as a text confirmation plus an embedded resource carrying the body. Text surfaces unwrap the resource. The voice pipeline passes on only the confirmation. No prompt fixes that.

So I built brain-remote, an MCP server that re-serves the repo as plain text. v1.0 shipped on 2026-08-17 and voice recall worked the same day. v1.1 followed that day with brain_write. A validation gate checks path lists, frontmatter, secret patterns and size. Then one atomic commit carries the note and its INDEX.md line through GitHub’s Git Data API, with a non-force ref update retried up to three times. There is no delete tool: voice and irreversible actions don’t mix. v1.2.1 put etiquette into the server’s initialize instructions, because voice clients talked over in-flight tool calls.

On 2026-08-25 I moved it to Cloudflare Workers without touching the write logic. Auth is a secret path segment, since the claude.ai connector form offered no header auth on my plan. A wrong path gets a bare 404. It ships in Brain Kit beside two plugins: brain (sync hook, write script, skills) and brain-remote (registers the server for sessions without a clone).

Claude Code, claude.ai and voice mode reach one private GitHub repo. Claude Code goes through a local clone with a worktree per write. claude.ai web and phone go through GitHub’s MCP connector. Voice goes through the brain-remote MCP server on Cloudflare Workers, which commits through the Git Data API. Recall reads INDEX.md first, then the matching notes.Claude CodeMac · Linuxclaude.aiweb · phonevoice modeclaude.ailocal cloneSessionStart syncworktree per writebrain-remoteMCP · Cloudflare Workersguarded atomic commitsone git repoGitHub · privaterecall:INDEX.md first,then the notespush to mainGitHub MCP connectorGit Data API
Fig. 1 Three surfaces, one repo. Voice goes through brain-remote because the voice client drops file bodies.

How I knew it worked

failure
On 2026-07-08 I marked cross-device recall as confirmed. It wasn’t. claude.ai was answering from a memory paragraph I had seeded, while the connector got 404s on the private repo. Its GitHub App had been authorized but never installed. It surfaced when a cloud session couldn’t load the brain. I fixed it and verified it on 2026-07-26 with a SHA-checked fetch of INDEX.md. Since then a pass needs a verbatim quote or a SHA.

Adversarial review. A two-agent review of the write script’s port to Brain Kit found two gaps: offline writes weren’t recallable until the next sync, and a sync comment would have lost the write if followed. On 2026-08-11 I verified every path in a sandbox against a bare origin, including a lost push race, chained offline writes and a busy clone.

Reviewed agent builds. I build with Claude Code: a written plan, a subagent and a review per task, a final review over the branch. On brain-remote v1.1 the final review caught a truncating reader on the write path before merge. It would have silently corrupted INDEX.md. On v1.2 every fix round traced back to a bug in the plan, not the implementer.

Live probes. v1.1, v1.2, the Workers port and v1.5.0 each ended with real writes against the live server, checked, then reverted. The Workers port added 16 contract tests mirroring the original suite.

Context cost. v1.5.0 cut brain_index (bootloader plus catalog) from 71k to 45k characters. A session that already holds the bootloader gets the catalog only.

126/126tests green on the Workers port
211brain-remote tests at v1.5.1
71k → 45kbrain_index characters, v1.5.0

Dogfooding. Two SessionStart hooks on one clone (the old one and the plugin’s, mid-migration) interleaved writes to .git/FETCH_HEAD, and git pull --ff-only failed. The fix: fetch, then a separate merge --ff-only, one retry per step.

What I’d change

Skip the vector layer. I built it before I had evidence I needed it. Files plus an index would have been the right first build.

Hooks over rules. The bootloader used to say consider a journal entry. A soft rule fires only when the model remembers, and one day the entry got written only because I asked. Since 2026-09-11 a Stop hook blocks the end of a turn when the brain has commits today and no journal entry. I’d move rules into hooks sooner.

Move the path secret. claude.ai connectors now accept request headers, so the credential can leave the URL. That change is parked.