Configuration record · 9 Aug 2026

gbrain, as shipped vs. as running here

Stock gbrain assumes a local repo, an Anthropic API key, and one machine. This brain lives in iCloud across four devices, bills LLM work to a Claude subscription, and answers WhatsApp. Every difference below is a decision with a reason — and three of them were forced by bugs rather than chosen.

01At a glance

Ten dimensions. Six are deliberate design choices; three were forced by defects in stock gbrain; one is still unresolved.

1,017
markdown files in the vault
5,884
chunks embedded (100%)
3
files patched vs. upstream
19 / 687
atoms extracted so far
DimensionStock gbrainThis brain
Installglobal bun install -gsource clone + local branch
DatabasePGLite, embeddedHomebrew Postgres 17 + pgvector
Vaulta local git repoiCloud Drive, 4 devices
Git dir.git beside the vault--separate-git-dir outside iCloud
Chat modelAnthropic API keyclaude-cli subscription
EmbeddingsOpenAI text-embedding-3-smallQwen3-8B @ 1024 (MRL) via OpenRouter
RerankeroffCohere rerank-4-fast
Schema packgbrain-basegbrain-everything
Processesone autopilot, worker inside it3 services, worker split out
Interfacesstdio MCPstdio + scoped HTTP over Tailscale

02Install & update changed

Stock installs an opaque global copy. We run from source so local changes survive upgrades.

Why. gbrain upgrade replaces a global install wholesale, taking any local edit with it. From a source clone it instead runs git pull --ff-only — which fails on a branch carrying local commits. That failure is the desired behaviour: it is the signal to rebase, not a bug to fix.

StockHere
Methodbun install -g github:garrytan/gbraingit clone~/src/gbrainbun link
Local changeslost on upgradetwo commits on branch local
Updategbrain upgradegit fetch origin && git rebase origin/master
Self-upgradeopt-in autonotify only — never swaps the binary unattended

03Storage engine forced

Not a preference — PGLite does not run on this OS.

Why. PGLite's embedded WASM engine is incompatible with macOS 26 (Tahoe) on Apple Silicon; it crashes during engine init. gbrain's own install docs prescribe native Postgres as the workaround. Two install snags were local: keg-only libpq had been force-linked and blocked the link step, and the resulting failed postinstall left the data directory uninitialised.

StockHere
EnginePGLite (zero-config, <1000 files)PostgreSQL 17.10 + pgvector 0.8.6
Tuningn/ashared_buffers 2GB · work_mem 32MB · maintenance_work_mem 512MB
Schemasame migrationsversion 125, RLS on 61/61 tables

04Where the markdown lives changed

The single most consequential difference, and the source of the hardest bug.

Stock gbrain assumes one machine and a local repo. The requirement here was that Noto stays a reader and writer across MacBook Pro, Air, iPhone and iPad — so the vault must be in iCloud, and everything else follows from that.

StockHere
Locationany local directory~/Library/Mobile Documents/…/Brain
Git.git inside the vault--separate-git-dir=~/.gbrain/brain.git — iCloud never sees a .git to churn on
Migrationn/a930 files copied + 66 diary pages split from daily notes; original Noto vault untouched as a permanent archive
Sourcesnamed sources per repoone default source; Noto deliberately not registered, to avoid duplication
The consequence that cost a day. ~/Library/Mobile Documents is TCC-protected. A launchd agent has no permission grant and no TTY to prompt for one, so every read returns Operation not permitted — silently. Background services could not read the vault at all, while the same commands run by hand worked perfectly, because the terminal already holds Full Disk Access. This is why the vault choice and the process topology are not independent decisions.

05Models & retrieval changed

Zero Anthropic API spend; embeddings and reranking on one OpenRouter key.

TouchpointStock defaultHere
Chat / all tiersanthropic:* — needs an API keyclaude-cli:claude-sonnet-4-6 — subscription-billed
Embeddingsopenai:text-embedding-3-small @1536openrouter:qwen/qwen3-embedding-8b @1024
Rerankernot configuredopenrouter:cohere/rerank-4-fast
Atom extractionanthropic:claude-haiku-4-5 — hardcodedclaude-cli:claude-haiku-4-5-20251001
Why 1024 and not the native 4096. pgvector's HNSW index caps at 2000 dimensions. At 4096 the vectors would store fine but be unindexable, silently degrading every search to a sequential scan. Qwen3-Embedding is Matryoshka-trained, so a 1024-prefix is a valid embedding rather than a lossy fragment — verified rather than assumed: a related pair scores 0.869 at 1024 vs 0.854 at 4096, unrelated 0.396 vs 0.365. Separation holds.
Trap worth remembering. extract_atoms does not fall through models.default like every other dream task — it carries its own hardcoded Anthropic default. So gbrain models showed every touchpoint correctly routed to claude-cli while this one silently pointed at a provider with no key, failing every batch.

06Schema pack & synthesis changed

The pack decides not just page types, but which cycle phases run at all.

The default gbrain-base family does not declare the extract_atoms or synthesize_concepts phases, so they are skipped on every cycle — silently, with no error. Only gbrain-creator and gbrain-everything declare them.

Stock (base-v2)Here (everything)
Page types1730
Atom / concept phasesnot declared → skippeddeclared → running
Calibration domainsbase only+ deal_success, architecture_calls
Why the switch was safe. gbrain-creator extends gbrain-base (v1), where media/ is not extractable — a naive switch could have re-typed 816 pages into a non-extractable type and produced zero atoms anyway. It didn't, because all 816 carry type: source explicitly in frontmatter from Noto's capture pipeline, and frontmatter beats path inference. Verified after: 1,017 pages, 100% typed, identical distribution.
Precedence does not match the docs. The schema_pack docstring claims the DB config plane outranks ~/.gbrain/config.json. In practice gbrain schema active reports Source: home-config — the file wins. Trust that command, not the documented order.

07Process topology changed

Stock runs one process. Here it is three, and the split is load-bearing.

ServiceRoleNote
com.gbrain.autopilotdispatcher only — --no-workerticks every 150s
com.gbrain.workerexecutes the job queueown service, own log file, --max-rss 8192
com.gbrain.serve-httpHTTP MCP for remote clientsbound to Tailscale only
Why the worker is separate. Two reasons. Autopilot's built-in supervisor pipes the child's stdout, and a full pipe with nobody draining it blocks the worker on write(). And --max-rss is ignored when passed to autopilot (it hardcodes clamp(0.5×RAM, 4096, 16384) = 16GB here) but honoured on a standalone worker — so the split also buys an 8GB watchdog, which matters on a machine that froze at 31GB in August.
Two environment fixes both services need. ~/.zshenv and ~/.zshrc are read-only nix/home-manager symlinks, so API keys cannot live there — each wrapper sources ~/.config/gbrain/credentials.env directly. And launchd's PATH omits /opt/homebrew/bin, so claude resolved to not found and every chat-backed job failed; both wrappers now pin GBRAIN_CLAUDE_CLI_BIN absolutely and fix PATH.
Re-running gbrain autopilot --install regenerates its wrapper and silently drops both fixes. The durable answer is to move the keys and the PATH entry into the home-manager config.

08Interfaces changed

Stock exposes stdio to one agent. Here, three consumers with different privileges.

ClientTransportScope
Claude Codestdio — gbrain servefull 106 ops
Hermes (WhatsApp)HTTP /mcp over Tailscaleread write, 11 of 96 tools
MacBook AirHTTP /mcp over Tailscaleread only
Why HTTP for Hermes even though it runs on the same Mac. stdio would hand an agent reachable from a phone the full surface — including delete_page and hard purges — with no attribution. Over HTTP, scope is enforced at the op layer and every call is logged: a read-only token gets insufficient_scope: Operation put_page requires 'write' scope.
Do not use /admin/api/api-keys for a scoped client. It destructures only name; a scopes field is silently ignored, and the auth layer grants such tokens full admin ("grandfather in"). Scoping exists only on the OAuth client-credentials path. A key issued as read-only wrote a page successfully during testing.
Tool curation. Injecting all 96 tools into every WhatsApp turn bloats context and makes routing worse. Hermes gets 11: search / query / think / recall, get / list / put page, three chronicle tools, and graph traversal. delete_page, forget_fact and every admin tool are excluded — the scope permits writes, but an agent on a phone should not be able to destroy pages.

09The patch set 3 files

One register entry, deliberately small, carried as separable commits.

HunkFileKind & upstream fate
A + Bchronicle/config.ts, chronicle/backstop.tsPreference — lets diary pages produce timeline events. Config-gated, default off, so stock behaviour is preserved. Likely never upstreamed.
Cai/dims.tsUpstream bug. The Qwen3 Matryoshka branch matched only Ollama's colon-tag naming, so on OpenRouter's dash form the dimensions parameter was never sent. Send as a PR; delete when merged.
A fourth hunk was withdrawn. A reranker touchpoint was added to the OpenRouter recipe on the belief it had none. It already had one — better, listing rerank-4-pro and an NVIDIA option. The added block was a duplicate object key that JS silently shadowed, so it was dead code the whole time. The false negative came from grepping for rerank:, which is not a substring of reranker:.

10What is still open

ItemStateNext
Full Disk Accessblocking background services cannot read the vaultgrant FDA to ~/.bun/bin/bun, then verify the atom count climbs past 19
Optimize Mac Storageon — 0 files evicted todayturn off; iCloud could otherwise hollow the vault into placeholders
Atom backlog19 of 687 pagesdrains once the worker can read the vault
Takes190 proposals, 0 promotedopt in via takes.bootstrap_enabled
Facts0 — and correctfacts live in ## Facts fences on entity pages; none exist until entity extraction runs
Git remotenonevault has no off-machine backup yet
Noto repointstill on the old vaultEugene's call on timing
One inert landmine. The DB config plane still holds embedding_model = dashscope:text-embedding-v3, superseded by config.json. It has no effect — but it contradicts the live setting, and stale config is exactly what turns a ten-minute debug into an hour-long one.