Everything gbrain does — the graph, the search, the nightly cycle, the calibration of your own predictions — falls out of one contract, enforced by a CI gate.
---type: persontags: [yc, ai-infra]---# Bob Smith Runs engineering at [[companies/acme]]. Met through [[people/alice]]. ## Facts <!--- gbrain:facts:begin ---> | # | claim | kind | confidence | visibility | valid_from | |---|--------------|------|------------|------------|------------| | 1 | Founded Acme | fact | 1.0 | world | 2017-01-01 | <!--- gbrain:facts:end ---> ## Takes <!-- gbrain:takes:begin --> | # | claim | kind | who | weight | since | |---|--------------------------|------|--------|--------|---------| | 1 | They'll raise a B by Q3 | bet | eugene | 0.7 | 2026-01 | <!-- gbrain:takes:end --> <!-- timeline --> ## Timeline - 2026-04-22 — pricing chat [Source: notes/2026-04-22]
Read this and the rest of the system becomes predictable.
The git repo of markdown files is the system of record. The database is a derived cache. We do not back up the database — we rebuild it from the repo.
docs/architecture/system-of-record.md
This isn't aspirational. scripts/check-system-of-record.sh runs in CI and
bans direct calls to insertFact, addLink,
addTimelineEntry, upsertTake anywhere outside a designated
reconciler. Code that writes a derived table without going through markdown first
fails the build.
Two consequences you feel immediately:
# Disaster recovery is boring — there is no "restore backup" steppsql -c 'DELETE FROM facts; DELETE FROM links; DELETE FROM takes;' gbrain sync# re-read markdown from the repogbrain extract all# rebuild every derived table, byte-identically# Multi-machine sync is git. That is the entire mechanism.git push# laptopgit pull# desktop — its DB rebuilds on next sync
Every table sits in exactly one of three buckets:
| Bucket | Tables | Rebuildable |
|---|---|---|
| FS-canonical | takes · facts · links · timeline_entries · tags | Yes — byte-identically |
| Derived | pages · content_chunks · page_versions | Yes — re-chunk + re-embed |
| Runtime only | oauth_tokens · minion_jobs · mcp_request_log · raw_data | No — but it isn't knowledge |
Five moving parts. Everything else is a reconciler between the repo and the index.
┌──────────────────────────────────────────────────────────────────┐
│ BRAIN REPO git · markdown · the system of record │
│ ~/brain/{people, companies, concepts, ideas, projects, media} │
└───────────────────────────┬──────────────────────────────────────┘
│ gbrain sync — walks the git diff,
│ parses frontmatter + fences
▼
┌──────────────────────────────────────────────────────────────────┐
│ POSTGRES / PGLITE derived index · ~45 tables │
│ pages · content_chunks (pgvector HNSW) · links · facts · takes │
│ timeline_entries · tags · sources · minion_jobs │
└───────┬───────────────┬───────────────┬──────────────┬───────────┘
▼ ▼ ▼ ▼
CLI MCP MINIONS DREAM CYCLE
gbrain … gbrain serve job queue gbrain dream
142 verbs 106 ops as crash-safe 23 phases
MCP tools subagents nightly cron
BrainEngine declares ~47 operations, implemented twice: PGLite (Postgres 17 in WASM, zero-config, single-writer, good to ~50K pages) and real Postgres + pgvector.
One 6,246-line file declares 106 named ops with params, OAuth scope, and a localOnly flag. CLI and MCP are both generated from it, so they cannot drift apart. Full inventory in Surface.
Every op context carries remote: boolean, enforced by the type system. Private fact rows are stripped by the chunker before embedding, so they never reach search at all.
A Postgres-native, BullMQ-shaped queue. The point is crash-safe subagents: LLM tool loops persist pending → done, so a crash resumes instead of losing work.
Plain markdown, tool-agnostic — 53 SKILL.md files behind 85 RESOLVER routes. Signal detection, ingest, citation repair, briefings, schema authoring. Recipes an agent reads — not code.
Your folder names aren't hardcoded — they come from the active pack. It threads through every read and write path, and its name+version is folded into the search cache key.
query
→ intent-aware rewrite
→ ┌ pgvector HNSW — max-pool: best chunk per page, so a page
│ surfaces on its strongest evidence
└ BM25 — websearch_to_tsquery, GBRAIN_FTS_LANGUAGE
→ reciprocal rank fusion
→ source-tier boost ← weight your own writing above saved articles
→ graph signals ← adjacency · cross-source · session demote
→ title / alias boost
→ ZeroEntropy reranker
→ results tagged evidence (why it matched)
and create_safety (exists / probable / unknown)
gbrain search "…" --explain prints per-stage attribution: base score, every
boost that fired, what it multiplied by. gbrain search diagnose traces which
layer surfaced — or missed — a specific page.
gbrain search returns pages. gbrain think runs the same retrieval,
then composes a cited answer plus gap analysis — what's stale, what's
uncited, where two pages contradict each other, what hole you should go fill.
That second verb is the whole reason to run this.
gbrain invents about sixteen nouns. Most confusion with the tool comes from collapsing two of them that look alike. Each below says what it is, where it physically lives, and what it is not.
One markdown file. The atom of storage.
Everything is a page: a person, a company, an essay, a saved article, a day.
A page carries a body (compiled_truth), frontmatter, a type, a
content hash, and a salience score. Deletes are soft — a page sits in a 72-hour
recovery window before purge actually removes it.
A page's address, shaped like a path.
people/bob-smith, writing/no-way-back,
inbox/2026-08-08-a91f2c04. The leading segment is what the schema
pack reads to decide a page's type.
Slugs are unique per source, not globally. The same
topics/ai can exist in two sources and be two different pages.
A named content repo inside one brain.
Your notes vault is one source. A codebase is another. An essay collection a
third. Every page row carries a source_id. A
.gbrain-source dotfile in a directory pins every command run there
to that source, so you never pass a flag.
A whole database. You can have several.
Your personal brain is one. A team's published brain that you mount is another. Separate pages tables, separate access control, separate lifecycle.
The rule that separates the two axes: if the data owner changes, it's a brain boundary. If the owner stays the same and only the topic changes, it's a source boundary.
How far a source has been read.
Three fields on the source row: the last git commit imported, when that happened,
and which chunker version did it. gbrain sync walks only the diff
since last_commit — that's what makes re-syncing a thousand-page
vault take seconds instead of minutes.
Bump the chunker version in a release and the mismatch forces a full re-walk
automatically. If a sync is interrupted it exits cleanly with
last_commit unchanged, so the next run re-walks the same
diff and content hashes skip whatever already landed.
Whether a source joins general search.
A per-source boolean. federated: true and its pages show up in every
unqualified query. false and the source is invisible unless you name
it — the right setting for draft writing, or anything you don't want bleeding
into unrelated answers.
Across brains there is deliberately no federation at all. The agent sees the list of brains and decides when to query each one. Keeping that out of SQL is what keeps access control and debugging sane.
The declaration of your brain's shape.
A YAML manifest naming the page types that exist, which folder prefixes map to each, which types get facts mined out of them, which participate in expertise routing, what edge types are legal — and which dream-cycle phases run at all.
Swap the pack and the brain re-interprets itself; swap back and nothing is lost.
The pack's name and version are folded into the search cache key, so results from
one pack can never leak into another. gbrain schema detect clusters
your actual filesystem into proposed types when none of the bundled packs fit.
Typed edges between pages. The graph.
Written by the auto-linker on every page save — pure pattern matching on
[[wikilinks]], zero LLM calls. An edge carries a
type (works_at, founded, discusses,
derived_from, supersedes) and the surrounding context.
Two grades exist and it matters: a real wikilink, versus a
mentions edge inferred from body text. The weak kind is
excluded from backlink-count ranking and only counts toward graph
traversal, so a name appearing everywhere doesn't inflate its own importance.
Point at a page that doesn't exist yet and a stub is created. That's how the graph grows without anyone curating it.
Flat labels from frontmatter.
The simplest primitive: a YAML array, reconciled into a table on import. No hierarchy, no types, no relationships. They filter and they feed the salience score — that's it.
One sharp edge: since v0.41.37 tag reconciliation is add-only. Removing a tag from frontmatter no longer removes it from the database, because enrichment writes tags of its own that a re-index must not wipe.
This is the distinction the codebase calls out by name as the category error. Both are claims. They differ in who said it, when it was captured, and how long it lives.
| facts — hot memory | takes — cold epistemology | |
|---|---|---|
| whose | Only yours. What you said. | Anyone's. Attributed by holder. |
| when | Real time, per conversation turn | Retrospective, extracted from pages |
| kinds | event · preference · commitment · belief · fact | take · fact · bet · hunch |
| carries | The claim, its kind, visibility | Holder, weight, since-date, source |
| read via | gbrain recall | gbrain takes list / search |
| lifecycle | Promoted overnight, then marked consolidated | Durable; graded and scored over time |
What you told the brain, captured as you say it.
“I have a call with Arya Thursday.” “I don't drink coffee.” “We decided to cut the second podcast season.” A cheap model pulls these out per turn and files them on the relevant entity page.
Each row carries visibility: world | private. Private text is
stripped by the chunker before embedding — it never becomes a
vector, never appears in a search result, and never leaves the machine through the
MCP surface.
Who believes what, how strongly, since when.
A take is a claim plus an owner. holder=self kind=bet
“the AI hardware wave peaks in 2027” at weight 0.7 is a different
object from the same sentence held by someone you interviewed. Four kinds:
an opinion, a verifiable fact, a dated prediction, an intuition.
Weights use 0.05 increments on purpose. The extraction guidance is explicit that 0.74 is false precision, that amplifying someone else's claim caps out around 0.55, and that self-reported numbers never reach the confidence of verified ones.
Because takes carry a weight and a date, they are the only thing in the system that can later be graded against what actually happened.
The reusable unit pulled out of long-form material.
A cheap model reads a transcript or article and extracts one to three atoms, each
typed from a closed list: insight, anecdote, quote, framework, statistic,
story angle, strategy angle, strategy, endorsement, critique, collection.
Each becomes its own page under atoms/{date}/.
This is the creator's primitive. Twelve podcast transcripts sitting inert become a few dozen individually addressable, retrievable pieces — the anecdote you half remember telling, findable without re-listening.
Atoms that recur, promoted into a real page.
Nightly, atoms are grouped by the concept they reference and tiered by how often the theme appears: ten or more is tier one, five is tier two, two is tier three. The top two tiers get a written narrative; the bottom gets a plain stub.
The tier is the finding. A theme reaching tier one means you have circled it ten separate times without noticing — which is a different and more useful signal than any single note.
Where unfiled things land, on purpose.
Two things share the name. gbrain capture writes to
inbox/YYYY-MM-DD-<hash> by default, so anything captured
without a destination clusters in one predictable place to triage later. And
~/.gbrain/inbox/ is a watched folder — anything dropped in from a
Shortcut, AirDrop, or Finder gets ingested.
The hash makes capture idempotent: the same text captured twice is one page, not two.
The two temporal page types.
Added for the Life Chronicle feature. An event under
life/events/ is something that happened on a date. A
diary entry under life/diary/ is what you thought about
it. Neither has facts mined out of it — they're read chronologically, not queried
for claims.
These power the chronicle verbs: what happened in a window, what happened on this day in previous years, what has changed since a date.
Two different things wearing one word.
The trust flag. Every operation runs with
remote: true|false, required by the type system. You at your own
terminal are local and see everything. Anything reaching the brain through MCP —
including an agent on this same machine — is remote, and gets private fact rows
and the entire takes fence stripped from its responses.
The install shape. A thin client is a gbrain with no local content at all, pointed at someone else's brain server over HTTP. Same commands, the data lives elsewhere. Local-only operations refuse with a specific hint rather than silently querying an empty database.
They are easy to confuse and they do completely different jobs.
Governed by gbrain.yml at the repo root. The split here is the useful lever:
storage: db_tracked:# version-controlled, human-curated- people/ - companies/ - concepts/ - ideas/ - projects/ db_only:# on disk + in the DB, auto-gitignored by sync- media/articles/# searchable, but never bloats the repo- media/x/# restore with: gbrain export --restore-only- meetings/transcripts/
Mark a directory db_only and its contents stay fully searchable while
never entering git. That is exactly the right home for hundreds of saved articles,
clipped threads, and transcripts — the bulk that you want retrievable but never want
to review in a diff.
The directory names come from the active schema pack. gbrain-base-v2 is the
default: 15 types, each declaring path_prefixes, whether facts get extracted
from it, and whether it participates in expertise routing.
| Type | Path prefixes | Extractable | Expert routing |
|---|---|---|---|
| person | people/ · person/ | no | yes |
| company | companies/ · products/ · orgs/ | no | yes |
| media | media/ | yes | no |
| writing | writing/ | yes | no |
| project | projects/ | no | no |
| concept | concepts/ | yes | no |
| atom | atoms/ | no | no |
| diary | diary/ | no | no |
| note | catch-all | yes | no |
Plus tweet, social-digest, analysis,
source, deal, email, slack,
event — and typed edges: works_at, founded,
invested_in, mentions, discusses,
derived_from, supersedes, redirects_to.
Never contains your knowledge. Wipe it and rebuild from the repo.
~/.gbrain/ ├── config.jsonbrain registry, embedding model, search mode├── brain/PGLite data dir, if using PGLite├── inbox/drop files here → ingested automatically├── autopilot.lock ├── advisor-history.jsonl └── audit/~25 weekly-rotated JSONL streams├── dream-budget-YYYY-Www.jsonl ├── batch-retry-YYYY-Www.jsonl ├── db-disconnect-YYYY-Www.jsonl ├── schema-mutations-YYYY-Www.jsonl ├── rerank-failures-YYYY-Www.jsonl └── … quality-probe, phantoms, shell-jobs, lock-renewal
gbrain doctor reads those audit streams to produce its health checks —
which is why a failure three days ago is still diagnosable today.
Two independent axes. A brain is a database. A source is
a repo inside it. One brain can hold your notes, a codebase, and an essay collection as
separate sources, each with its own sync state and a federated flag
controlling cross-source search. A .gbrain-source dotfile pins every command
run inside a directory to that source.
The install doc says eight phases. src/core/cycle.ts declares twenty-three,
in strict dependency order. Each is individually gated and can be run alone.
| # | Phase | Kind | What it does |
|---|---|---|---|
| 01 | lint | markdown | Frontmatter and structure hygiene |
| 02 | backlinks | index | Reconcile incoming links |
| 03 | sync | markdown | Git diff → database |
| 04 | synthesize | llm | Consolidate yesterday's conversations |
| 05 | extract | index | Materialise links and timeline |
| 06 | extract_facts | index | Rebuild facts from every ## Facts fence |
| 07 | extract_atoms | llm | Haiku pass: transcripts and articles → atom pages |
| 08 | resolve_symbol_edges | index | Two-pass code-symbol resolution |
| 09 | patterns | llm | Cross-session pattern detection |
| 10 | synthesize_concepts | llm | Atoms → tier-promoted concept pages |
| 11 | recompute_emotional_weight | index | Salience signal from takes and tags |
| 12 | consolidate | llm | Ephemeral memory → durable pages |
| 13 | propose_takes | llm | Scan your prose, propose gradeable claims to a review queue |
| 14 | grade_takes | llm | Retrieve evidence, judge model verdicts them (auto-resolve off by default) |
| 15 | calibration_profile | llm | Aggregate resolved takes into narrative bias statements |
| 16 | drift | llm | Judge soft-band takes against recent evidence (off, report-only) |
| 17 | conversation_facts_backfill | llm | Bulk fact extraction over long-form conversation pages (off) |
| 18 | enrich_thin | llm | Trickle-develops a few stub pages per source per tick (off) |
| 19 | skillopt | llm | Treats SKILL.md files as trainable parameters (off) |
| 20 | embed | index | Embed stale chunks |
| 21 | orphans | index | Orphan detection |
| 22 | schema-suggest | llm | Passive schema-evolution proposals |
| 23 | purge | index | Hard-delete soft-deleted pages past the 72h recovery window |
Phases hold a database lock, are scoped global or per-source, meter their own budget to
~/.gbrain/audit/dream-budget-*.jsonl, and run singly:
gbrain dream --phase extract_facts --once. Six are off by
default.
A dependency-ordered table hides what is actually happening. The phases group into six movements, and the ordering is load-bearing — each movement consumes the previous one's output.
① HYGIENE lint · backlinks
make the markdown well-formed and the link table symmetric
before anything reads it
│
② INGEST sync
git diff → DB. Everything downstream reads DB state, so this
is the barrier between what's on disk and what the brain knows
│
③ EXTRACT synthesize · extract · extract_facts · extract_atoms
structure · resolve_symbol_edges
prose → structure: links, timeline entries, the ## Facts fence
into the facts index, atoms out of transcripts, code symbols
resolved to chunks. Mostly cheap; extract_atoms is a Haiku pass
│
④ SYNTHESIZE patterns · synthesize_concepts
meaning · recompute_emotional_weight · consolidate
cross-session themes, atoms clustered into concept pages,
salience scoring, ephemeral → durable. Sonnet lives here
│
⑤ JUDGE propose_takes → grade_takes → calibration_profile → drift
opinion scan prose for gradeable claims → retrieve evidence → judge
model verdicts them → aggregate into a profile of your
standing biases. Three of the four default OFF
│
⑥ MAINTAIN conversation_facts_backfill · enrich_thin · skillopt
· embed · orphans · schema-suggest · purge
backfills, embedding refresh, orphan detection, schema
proposals, and finally the purge — last, deliberately, so the
rest of the cycle still sees soft-deleted pages
That gating is the real cost control. gbrain dream with movements ⑤ and ⑥ off
is a cheap structural cycle. With them on it is a nightly LLM job over your entire corpus.
Nothing in the marketing mentions them. The cycle reads your prose, extracts the claims you actually made, waits, grades them against what happened, and produces a profile of your standing biases. It turns a pile of opinions into a scored record of how your judgment performs.
Every essay, idea note, and diary entry you already have is input for this. Nothing else in a normal notes stack does it.
Scattered across four files and never assembled. The reference schedule runs 20+ jobs. The docs' own advice: don't. Here is what each one actually does, and which three you need.
Start here, because it explains the shape of everything else. gbrain has no
scheduler. A native scheduler loop inside gbrain jobs work "has been
on the roadmap since v0.11.1 but has not shipped" — that is the convention doc's own
wording. Something else owns when: crontab, launchd, Railway cron, the gateway.
gbrain supplies only what the trigger does.
WHEN WHAT────────crontab / launchd ──────► gbrain jobs submit <handler> (host scheduler) (Minions queue → durable worker)
The one exception is gbrain autopilot, a long-running daemon with its own
internal timer. It replaces the host scheduler for maintenance work, and it is the path
worth taking.
| Command | Phases | Nature | Cadence |
|---|---|---|---|
| gbrain sync | 1 | One-shot. Git diff → DB. No LLM. | every 15 min |
| gbrain autopilot | 6 | Daemon, adaptive interval. lint · backlinks · sync · extract · embed · orphans | continuous, ~300s tick |
| gbrain dream | 23 | One-shot, LLM-heavy. The full ALL_PHASES. | nightly |
The 6-vs-23 split is stated exactly once in the entire codebase, in autopilot's own
--help: "Runs the full maintenance cycle (lint + backlinks + sync +
extract + embed + orphans) on an interval. For a one-shot cron-triggered cycle, see
gbrain dream."
Autopilot keeps the index correct. It is plumbing — effectively free, no LLM calls in its six phases. Dream is where the thinking, and the bill, lives. Installing autopilot and assuming you have the compounding brain is the obvious mistake, and nothing warns you off it.
docs/guides/cron-schedule.md presents a seven-job schedule as one system. It
is not. Three are gbrain builtins; four are external Node scripts you
write, wire, credential and maintain yourself.
| Builtin | Trigger | The flow | Cost |
|---|---|---|---|
| live sync | */15 | walk git diff → parse frontmatter → chunk → upsert pages → extract links and timeline → embed only stale chunks | free |
| dream cycle | 0 2 * * * | the twenty-three phases, six movements | the whole bill |
| brain health | 0 6 * * 1 | gbrain doctor --json → warn checks → embed --stale sweeps whatever the incremental path missed | free |
| Recipe (not gbrain) | Trigger | The flow | Needs |
|---|---|---|---|
| email → brain | */30 | poll inbox → per sender gbrain search "<sender>" → create or update people/ page → append timeline → write digest | Gmail/IMAP creds |
| x → brain | */30 | pull timeline and bookmarks → a media/ page each → entity extraction → link into people/ | X API access |
| meeting sync | 0 10,16,21 * * 1-5 | pull transcripts → ingest as meeting pages → attendee propagation: every attendee's page gets a timeline entry pointing back | Granola/Zoom |
| calendar sync | 0 10 * * 0 | last 7 days of events → daily files → enrich attendees | Google Calendar |
| morning briefing | daily AM | search calendar attendees, deal status, open threads → compose → deliver, and drain the quiet-hours held queue | a delivery channel |
Attendee propagation, deal status, sender enrichment across thousands of people. That machinery exists because the brain behind it holds 24,585 people and 5,339 companies. A corpus of essays, saved articles, project notes and podcast transcripts has almost no person-entities in it.
Four of the seven reference flows have close to nothing to operate on, and each is a credential plus a script plus a failure mode.
agentTurn
agentTurn has a 300s timeout, no durability, no transcript. Minion jobs
survive a gateway restart, expose duration and token accounting via
gbrain jobs get, and accept mid-run steering.
The convention doc does the math: a 5-minute cron running 8-minute jobs stacks four overlapping copies at steady state. A key that is stable per slot makes an overlapping fire a noop at the queue layer.
:00
Max one job per five-minute slot, :05 through :50.
Everything firing at :00 is an explicit listed anti-pattern.
Default 11 PM–8 AM with a user-awake override. Held output lands in
/tmp/cron-held/ and must be folded into the next briefing or it
is silently lost. Timezone is travel-aware — the agent reads your calendar for
flights and shifts quiet hours to follow you.
One 3 AM ping and you'll disable the whole system.
# Postgres — fire-and-forget, dedup at the DBgbrain jobs submit <handler> \ --idempotency-key <handler>:$(date -u +%Y-%m-%dT%H:%M)# PGLite — run inline instead. The exclusive file lock blocks a# separate worker daemon, so there is nobody to hand the job to.gbrain jobs submit <handler> --params '{}' --follow
One more thing about handlers: gbrain only auto-rewrites cron entries whose
handler is a builtin — sync, embed, lint,
import, extract, backlinks,
autopilot-cycle. Anything else is host code you register yourself via
MinionWorker.register(). The shell job type is CLI-only and
explicitly rejected over MCP, so a remote agent cannot schedule arbitrary shell.
Before each subagent submit the meter estimates max cost from
model + max_output_tokens, accumulates across the cycle, and
refuses the next submit once cumulative exceeds the cap. Every submit is
appended to ~/.gbrain/audit/dream-budget-YYYY-Www.jsonl with the estimate and
the actual usage when reported back. A cap of zero or negative disables the gate.
The meter prices against ANTHROPIC_PRICING. Point the dream cycle's
subagent at an OpenRouter or OpenAI model and the gate is skipped with a single
BUDGET_METER_NO_PRICING warning per process — one line, in a log nobody
reads at 2 AM.
If you cap the dream cycle, keep its subagent on an Anthropic model. Otherwise the cap is decorative.
gbrain autopilot --install --max-rss 2048# launchd on macOSgbrain autopilot --status
Spawns gbrain jobs work, respawns on crash with a five-crash
cap, holds child RSS to 2048 MB. Single-instance lockfile at
~/.gbrain/autopilot.lock, with stale-lock takeover rather than a
permanent wedge.
Base 300s. After each tick it reads brain_score:
≥ 90 → interval × 2, < 70 → interval ÷ 2 (floor 60s), else
unchanged. A healthy brain is left alone; a degraded one is worked harder.
Minions dispatch needs minion_mode != off and
engine == postgres. On PGLite the cycle runs in the daemon process
itself. A hard exit mid-write kills WASM Postgres with a dirty WAL and can corrupt
the database, so SIGTERM is handled deliberately.
During idle and quiet hours (self_upgrade.mode=auto), with a boot-time
breadcrumb reconciliation so a crash-on-launch after an upgrade is recorded
known-bad rather than looping.
For a single laptop this beats hand-rolled crontab: one install buys supervision, locking and a memory ceiling. Given the 31 GB leak that froze this Mac in August, the RSS cap is not a small detail — set it explicitly rather than trusting the default. And note what autopilot never does: it does not run the dream cycle. That stays a nightly line of its own.
With a noto mirror and an agent source, per-source cron lines are
the legacy pattern. One consolidated line replaces them and auto-picks-up future sources
without a crontab edit:
*/5 * * * * gbrain sync --all --parallel 4 --workers 4 --skip-failed
Watch the connection budget: parallel × workers × 2 ≈ 32 connections during
the wave, since every per-file worker opens its own two-connection pool. Homebrew
Postgres defaults to max_connections = 100, so this is comfortable.
gbrain doctor surfaces the recommended line as a
sync_consolidation check once it sees two active sources.
And the anti-wedge pattern, for when a sync starts timing out:
gbrain sync --break-lock --all --max-age 1800 for src in $(gbrain sources list --json | jq -r '.[].id'); do timeout 600 gbrain sync --source "$src" --timeout 540 ||truedone
--timeout firing mid-import exits 0 with status partial and
leaves last_commit unchanged, so the next run re-walks the same diff and
content_hash short-circuits whatever already landed. The outer
timeout(1) does the OS-level kill 60s after gbrain's own graceful stop.
Not the reference schedule. Three lines and a daemon.
# Vault → git mirror. One-way; rsync never writes back into iCloud.*/15 * * * * rsync -a --delete \ "$HOME/Library/Mobile Documents/com~apple~CloudDocs/Noto/" \ "$HOME/brain/sources/noto/" \ && git -C "$HOME/brain" add -A \ && git -C "$HOME/brain" commit -q -m "vault sync $(date -u +%FT%TZ)" ||true# Nightly dream — structure only to start. Movements ①–④ on, ⑤–⑥ off.0 2 * * * gbrain dream >> ~/.gbrain/logs/dream.log 2>&1# Weekly health.0 6 * * 1 gbrain doctor --json >> ~/.gbrain/logs/doctor.log 2>&1
Plus gbrain autopilot --install --max-rss 2048 handling sync, extract, embed
and orphans continuously — which is why the */15 line above is only
rsync-and-commit. gbrain notices the new commits on its own tick.
What to deliberately not run, and why:
propose_takes → grade_takes → calibration_profile
deliberately, one at a time, with a budget cap set and an Anthropic subagent model,
after watching a week of dream logs. That is the feature most likely to be worth real
money on this corpus — which is exactly why it should be a decision, not a default.
Of the twenty-odd jobs a "production brain" runs, three plus a daemon is the right starting shape — and roughly what the reference doc would say if it were not written for a 146,000-page brain.
The primitives only make sense in motion. Each flow below names what you actually type, what the system does with it, and which primitives it touches on the way through.
The lowest-ceremony path in. No destination, no decision, no filing.
You type one line.
gbrain capture "the podcast's best episodes all start with
a disagreement, not a topic"
A page is written to disk at
inbox/2026-08-08-3f1a9c02 — the hash is of the content, so
capturing the same sentence twice produces one page, not two.
The next sync reads it, chunks it, embeds it. It is now retrievable — from a phrase you don't remember writing, months later.
Later, the overnight cycle may notice this sentence is a claim and queue it as a proposed take. You never had to decide that up front.
Two verbs, two different jobs. Reach for the wrong one and you'll misjudge the tool.
When you want raw material to skim — a quote, a citation, a page you know exists:
gbrain search "consumer AI ideas I dropped"
Returns ranked pages. No LLM cost. Each result carries an evidence tag saying why it matched.
When you want the answer:
gbrain think "which consumer AI ideas did I abandon,
and what was the common reason?"
Same retrieval, then a written answer with citations — and a note on what the brain doesn't know. That last part is the differentiator: it tells you when a page is stale, when two pages contradict, when there's a hole.
When a result surprises you, ask why it ranked where it did:
gbrain search "…" --explain# every boost that firedgbrain search diagnose "…" --target writing/no-way-back
This is where the graph actually grows, and it costs nothing. The trigger is a
line in your CLAUDE.md telling the agent to search first and write
decisions back.
You settle something out loud: “we're dropping the second podcast season, the transcripts are worth more as atoms.”
The agent calls put_page and writes a page containing a wikilink:
# decisions/2026-08-08-podcast-s2.mdDropping season two of [[projects/podcast]]. The dozen existing transcripts are more valuable mined as atoms than extended.
The auto-linker fires on the save. It finds [[projects/podcast]]
and writes a typed link row. Zero LLM calls —
this is pattern matching.
If that page didn't exist, a stub is created. Point at something absent and the graph grows a node for it.
From then on, anything asking about the podcast surfaces this decision through a backlink — not because the words matched, but because the two pages are factually connected.
The longest loop in the system and the one nothing else in a notes stack does. It only works because a take carries a weight and a date.
You wrote an essay months ago containing a real claim — “Hong Kong's tech scene won't recover without unlocking the tools, not the funding.” It is prose. Nothing is tracking it.
Overnight, propose_takes reads your prose and pulls out the
gradeable claims. It does not write them into your brain —
they land in a review queue.
You review, and the ones you accept get written into a ## Takes fence:
gbrain takes propose# accept / reject the queue
Now it is a take: holder self, kind
bet, a weight, a since-date.
Time passes and evidence accumulates in the brain.
grade_takes retrieves what's relevant and asks a judge model to
verdict the claim. Auto-resolve is off by default — it
proposes a verdict, you keep the call.
Across enough resolved takes, calibration_profile writes the
output that matters: two to four sentences describing your standing biases,
plus a Brier score. Not what you believe — how your believing
performs.
gbrain takes scorecard
Transcripts are the worst-shaped content in any vault: enormous, low signal density, impossible to skim. Atoms are the answer, and they're off by default.
Switch to a schema pack that declares the atom phases. The default pack doesn't, so nothing happens until you do this.
gbrain config set schema_pack gbrain-creator
Overnight, extract_atoms reads each transcript and pulls one to
three typed atoms — an anecdote here, a framework there, a
statistic worth reusing. Each becomes its own page. Capped at $0.30 per
source per run.
Then synthesize_concepts groups atoms by theme and tiers them by
recurrence. A theme you circled ten separate times becomes a tier-one
concept page with a written narrative.
The payoff is asymmetric. You can now find the story you half-remember telling without re-listening to an hour of audio — and the tier count tells you which of your themes are actually load-bearing.
The moment the brain stops being one pile. Drafts, client work, and anything half-formed belong behind a boundary.
Register the repo as a source inside the same brain:
gbrain sources add drafts --path ~/brain/sources/drafts gbrain sources add code --path ~/dev/some-project
Turn federation off for the one that should stay quiet:
gbrain sources drafts --federated false
Its pages now answer only when you name the source. Unqualified queries behave as if it isn't there.
Drop a .gbrain-source dotfile in each checkout. Now every command
you run inside that directory is scoped to it automatically — no flags, ever.
Each source keeps its own sync state, so they import independently and a slow one never blocks the rest.
The generative direction. Not "find me the page" but "make me a page" — out of pages that already exist, out of something new, or out of both. There is no single verb for this. There are five paths, and they differ on two axes: who does the synthesising, and where the new information is allowed to come from.
Pick the path by those two axes. This is the whole decision:
| Path | Synthesised by | New info from | Lands at |
|---|---|---|---|
| put_page | the agent | anywhere — you, the web, a session | any slug you choose |
| gbrain enrich | gbrain, one grounded call per page | nowhere — brain-internal only | the thin page, developed |
| enrich skill | the agent | web search | people/ · companies/ |
| brainstorm · lsd | gbrain, bisociation + judge | your own forgotten pages | ideas/<date>-…md |
| book-mirror | fan-out to Minions | one source document + you | media/books/<slug>-personalized.md |
The agent writes it. Claude Code composes the markdown and
put_page stores it. One call chunks the body, embeds it, reconciles
tags, and — when auto_link and auto_timeline are on —
extracts the wikilinks into graph edges and the dated lines into timeline
entries. The page is queryable on the next breath, not the next sync.
Provenance is server-stamped for anything remote. A write-scope
OAuth token cannot claim source_kind: capture-cli — only a trusted
local caller (the capture CLI, autopilot, the dream cycle) may set its own
provenance. Audit-trail spoofing is closed structurally rather than by
validation.
gbrain writes it out of itself. gbrain enrich takes
a thin stub and develops it into a real cited page by consolidating what the
brain already knows about that entity — scattered mentions, inbound-link
context, facts, the existing stub — in one grounded LLM call per page. Explicitly
no web lookup; that is the agent-driven skill's job, not this
one.
gbrain enrich --types note,project --order inbound-links \
--limit 20 --model anthropic:claude-haiku-4-5 \
--max-usd 2 --dry-run
Enriched pages carry enriched_at / enriched_by
frontmatter that survives write-through, and the recency guard reads it so a
re-run skips anything touched inside --reenrich-after.
gbrain writes it out of a collision. brainstorm
pulls a close-set for your question via hybrid search and a
far-set via a prefix-stratified domain bank, crosses them, and judges the
results on a five-axis rubric. Every idea cites its close and far slugs with a
0–1 distance score, so you can see how far the collision actually travelled.
Saves to a page by default.
gbrain lsd is the same engine with the distance dial maxed: bigger
far-bank, smaller close-set, forgotten pages preferred via a stale-bias
signal, and an inverted judge that rejects ideas scoring too
high on coherence — "too obvious, you'd have thought of this without LSD." Every
idea must invert at least one implicit axiom. Ephemeral unless you
--save.
gbrain brainstorm "what's the real bottleneck on X?"# ~$0.05–0.15gbrain lsd "the unspoken assumption in X" --save# ~$0.20–0.40
Nobody asks — it happens at 2 AM. The dream cycle produces pages
you never requested: synthesize_concepts clusters atoms and
tier-promotes them into concept pages, and
consolidate promotes hot facts into durable takes. This is the same
flow with the trigger removed, which is the entire argument for running the
nightly cycle at all.
Whichever path wrote it, four rules govern where it lands and whether it
should exist. These are stated as mandatory in
skills/_brain-filing-rules.md, and they are the difference between a
brain and a folder of generated text.
Not by format, not by source, not by the skill that ran. An article about a person
goes to people/, not sources/. A reusable thesis goes to
concepts/. The test: what would you search for to find this
again?
Not everything deserves a page. The instruction is blunt: when in doubt, DON'T create. A missing page can be created later. A junk page wastes attention and degrades search quality.
Every mention of an entity that has a page must produce a link both ways — new page to entity, entity back to new page. An unlinked mention is a broken brain. The graph is the intelligence.
Every fact carries an inline [Source: …]. Precedence runs: your direct
statements → compiled truth → timeline evidence → external lookup, lowest. When
sources conflict you record both citations. Never silently pick
one.
gbrain enrich takes a --min-context threshold: below that many
characters of retrieved context, the page is skipped, never fabricated.
The system would rather leave a stub thin than invent a plausible one.
That single default is what separates "the brain wrote me a page" from "the brain hallucinated me a page," and it is worth checking is still set before you point any of this at a corpus you intend to trust later.
gbrain lsd is the one built for exactly that pile. It prefers pages
you have forgotten, and it throws away any idea coherent enough that you would have
reached it unaided. A capture folder nobody revisits is not dead weight to it — it is the
raw material it specifically wants.
Two caveats before running it. gbrain enrich defaults to
--types person,company, and a corpus of essays and saved articles has
neither — pass the types your own schema pack actually declares. And the default save
path comes from the bundled pack's taxonomy, not yours, so check where a
--save actually lands before generating in bulk.
Facts flow to takes, one way, once a night. The consolidate phase groups
your hot facts by entity, deduplicates them against takes that already exist, promotes
the durable ones with proper attribution and weight, and marks the originals consumed.
Nothing flows back. A take is never demoted to a fact — which is why
gbrain recall (this week's conversation memory) and
gbrain takes list (the durable belief record) return genuinely different
things and both are worth asking.
Every CLI verb and every op, counted from source rather than quoted from the docs. The round numbers were all wrong, including mine.
| Claimed | Code says | |
|---|---|---|
| CLI verbs | ~90 | 142 distinct |
| Operations registry | ~120 | 106 |
| MCP tools over stdio | "30+" | 106 — unfiltered |
| MCP tools over HTTP | — | 96 |
localOnly ops | 34 | 10 |
| Skills | 63 | 53 files · 85 routes |
src/cli.ts builds the CLI from two disjoint sources.
src/mcp/server.ts builds MCP from one.
106 ops ──┬──► stdio MCP all 106 tools
│ buildToolDefs(operations) — no filter
│
├──► HTTP MCP 96 tools
│ serve-http.ts:1842 → operations.filter(op => !op.localOnly)
│
└──► CLI 48 verbs (ops with cliHints.name, not hidden, not shadowed)
+ 2 aliases (link-add, link-rm)
92 CLI_ONLY verbs ─────────► CLI only. No MCP equivalent. Ever.
Of the 106 ops: 51 declare a CLI name, 16 are explicitly
hidden from the CLI, and 39 have no CLI name at all — they
exist only as MCP tools. Three names (think, salience,
anomalies) appear in both lists; CLI_ONLY is checked first, so the
CLI runs a richer local handler while MCP callers get the leaner op.
Ninety-two verbs — install, sync, dream, autopilot, enrich, brainstorm, import/export,
doctor, eval, schema, book-mirror — have no MCP equivalent. An agent
wired over MCP can read and write the brain but cannot operate it. Every
derived-page path in Flows except put_page is CLI-only.
The stdio/HTTP gap matters too. stdio hands out all ten localOnly ops —
file_upload, file_url, file_list,
purge_deleted_pages, migrate_embeddings,
sync_brain, chronicle_backfill,
extraction_review, get_recent_transcripts,
code_traversal_cache_clear — which HTTP deliberately withholds. Defensible,
since stdio is a local subprocess. But "connect gbrain to Claude Code" and "connect
gbrain to ChatGPT" are materially different trust postures.
By scope: read 60 · admin 26 ·
write 17 · sources_admin 2 ·
agent 1. Only 32 are
mutating — this is overwhelmingly a read surface.
| Group | n | Ops |
|---|---|---|
| Page CRUD + versions | 10 | get_page · put_page · delete_page · list_pages · restore_page · purge_deleted_pages · get_versions · revert_version · resolve_slugs · get_chunks |
| Retrieval | 4 | search (BM25) · query (hybrid + multi-query expansion) · search_by_image · think (write-scope — it persists) |
| Graph + links | 7 | add_link · remove_link · get_links · get_backlinks · list_link_sources · traverse_graph · find_orphans |
| Tags + timeline | 5 | add_tag · remove_tag · get_tags · add_timeline_entry · get_timeline |
| Facts — hot memory | 3 | extract_facts · recall · forget_fact |
| Takes + calibration | 5 | takes_list · takes_search · takes_scorecard · takes_calibration · get_calibration_profile |
| Entity extraction + quarantine | 3 | extract_entities · extraction_pending · extraction_review |
| People / expertise / analysis | 6 | find_experts · find_contradictions · find_trajectory · find_anomalies · get_recent_salience · get_recent_transcripts |
| Life Chronicle (time) | 7 | chronicle_day · chronicle_on_this_day · chronicle_since · chronicle_last_seen · chronicle_backfill · volunteer_chronicle · volunteer_context |
| Ontology | 4 | ontology_get · ontology_propose · ontology_dimensions · ontology_conflicts |
| Code brain | 7 | code_def · code_refs · code_callers · code_callees · code_blast · code_flow · code_traversal_cache_clear |
| Schema packs | 9 | get_active_schema_pack · list_schema_packs · schema_stats · schema_lint · schema_graph · schema_explain_type · schema_review_orphans · schema_apply_mutations · reload_schema_pack |
| Jobs — Minions | 11 | submit_job · get_job · list_jobs · cancel_job · retry_job · get_job_progress · pause_job · resume_job · replay_job · send_job_message · submit_agent |
| Sources | 5 | sources_add · sources_list · sources_remove · sources_status · whoami |
| Skills catalog | 4 | list_skills · get_skill · list_brain_skillpack · advisor |
| Admin / ops | 9 | get_stats · get_health · run_doctor · get_brain_identity · get_status_snapshot · sync_brain · run_onboard · run_skillopt · migrate_embeddings |
| Files + raw data + ingest log | 7 | file_list · file_upload · file_url · put_raw_data · get_raw_data · log_ingest · get_ingest_log |
Everything gbrain can do to itself. None of it reachable over MCP.
| Group | n | Verbs |
|---|---|---|
| Index maintenance | 19 | sync · embed · extract · extract-conversation-facts · reindex · reindex-code · reindex-frontmatter · reindex-search-vector · edges-backfill · reconcile-links · check-backlinks · frontmatter · lint · orphans · maintain · integrity · repair-jsonb · cache · storage |
| Install / lifecycle | 17 | init · reinit-pglite · upgrade · post-upgrade · check-update · self-upgrade · onboard · connect · migrate · apply-migrations · features · providers · models · ze-switch · retrieval-upgrade · config · auth |
| Diagnostics / eval | 12 | doctor · status · eval · report · routing-eval · smoke-test · claw-test · friction · bench · integrations · skillpack · skillpack-check |
| Ingest | 9 | import · capture · export · files · publish · conversation-parser · transcripts · book-mirror · backfill |
| Knowledge reads | 9 | takes · recall · forget · salience · anomalies · calibration · quarantine · graph-query · pages |
| Generation | 8 | enrich · brainstorm · lsd · think · advisor · founder · skillify · skillopt |
| The cycle / daemon | 6 | dream · autopilot · watch · jobs · agent · remote |
| Code brain | 4 | code-def · code-refs · code-callers · code-callees |
| Schema | 3 | schema (22+ subverbs) · resolvers · check-resolvable |
| Sources / repos | 3 | sources · mounts · repos |
| Server | 2 | serve · call |
Note the shape: 48 of the 92 are maintenance and diagnostics. This is a system that spends most of its verb budget keeping its own index honest.
claude mcp add gbrain -- gbrain serve is not a small addition. Those 106 tool
definitions are paid for on every request, whether or not you touch the brain.
The ops you'd actually use for the Noto plan are about ten: search,
query, think, get_page, list_pages,
put_page, get_backlinks, traverse_graph,
recall, find_orphans. The rest is operational (belongs on the
CLI anyway), shaped for a corpus you don't have (code_*,
find_experts, ontology), or plumbing.
If the tool-list cost bites, the lever is gbrain serve --http behind a
scoped OAuth client: HTTP already filters localOnly, and scope-gating is
enforced per op, so a read-scope token collapses the surface to the 60 read
ops. There is no documented flag to hand-pick a tool subset for stdio.
macOS 26.5 Tahoe, Apple Silicon. Two things in the standard quickstart do not apply — both are handled below.
They all begin gbrain init --pglite. PGLite's embedded WASM engine is
incompatible with macOS 26.x on Apple Silicon — it crashes. PGLite is
also single-writer, so gbrain serve and gbrain sync fight over
the write lock. Two independent reasons to go straight to real Postgres.
Only libpq (the client) is installed today. This adds the server.
brew install postgresql@17 pgvector brew services start postgresql@17 createdb gbrain psql gbrain -c 'CREATE EXTENSION IF NOT EXISTS vector;'
From GitHub only. The npm package called gbrain is an unrelated project
that will shadow the real binary on your PATH.
bun install -g github:garrytan/gbrain gbrain --version# expect 0.42.74.0 or later
gbrain init silently applies tokenmax — the most expensive
corner of a 25× spread. Choose deliberately.
| Mode | Haiku 4.5 | Sonnet 4.6 | Opus 4.7 |
|---|---|---|---|
| conservative | $40/mo | $120/mo | $200/mo |
| balanced | $100/mo | $300/mo | $500/mo |
| tokenmax | $200/mo | $600/mo | $1,000/mo |
Figures are per 10,000 queries/month and scale linearly. A personal brain runs a few
hundred — divide by 20 or more. conservative is the right default here.
gbrain init gbrain config set search.mode conservative gbrain doctor
gbrain wants a git repo; iCloud Drive is not one, and git inside iCloud corrupts both. Mirror one way — the vault stays the only thing you ever write to, and you get free version history of your notes as a side effect.
mkdir -p ~/brain/sources/noto && cd ~/brain git init rsync -a --delete \ "$HOME/Library/Mobile Documents/com~apple~CloudDocs/Noto/" \ ~/brain/sources/noto/ git add -A && git commit -m "import vault"
Bulk captured material stays searchable but out of the repo. Write
~/brain/gbrain.yml:
storage:
db_tracked:
- sources/noto/Writings/
- sources/noto/Projects/
- sources/noto/Ideas/
- sources/noto/Daily Notes/
db_only:
- sources/noto/Captures/ # searchable, never in a diff
Ask three questions you genuinely cannot answer today. If think doesn't
beat ten minutes of manual searching, stop here — don't build the rest on faith.
gbrain import ~/brain/ --no-embed
gbrain embed --stale
gbrain think "what have I written about consumer AI ideas,
and which ones did I abandon and why?"
gbrain think "what recurring themes run through my essays?"
gbrain think "what did I save about note-taking systems,
and what's the through-line?"
Then paste the brain-first protocol into your global CLAUDE.md: search
the brain before answering or asking; write decisions back with put_page;
cite the page you used.
claude mcp add gbrain -- gbrain serve
Note: stdio MCP exposes the unfiltered registry — roughly 120 tools, not the "30+" the README advertises.
The auto-linker is pure pattern matching on wikilinks — zero LLM calls. A vault with
almost no links produces almost no graph. Check the size of the prize before spending
on it: doctor reports how many bare links would resolve.
gbrain doctor# look for link_resolution_opportunitygbrain config set link_resolution.global_basename true gbrain extract links --source db --dry-run | head -20 gbrain extract links --source db gbrain stats# links should now be > 0
Only after step 6 proved the value and step 8 measured the delta.
gbrain autopilot --install gbrain autopilot --status
Your existing note CLI stays the only writer to the vault — capture doesn't change at all. gbrain reads a mirror and writes only into its own source. Capture surface and query surface stay cleanly separated, and nothing an agent does can corrupt the notes you actually wrote.