Noto works as a capture system and fails as a retrieval one — 23 wikilinks across 1,076 files is not a knowledge base, it is a pile. This is the record of what we decided, what we rejected, what we had to patch, and what will go wrong.
gbrain is a retrieval-and-synthesis daemon over a markdown corpus, exposed to agents over
MCP. Markdown in git is the system of record; Postgres is a derived cache that can be
dropped and rebuilt. The two things it adds that nothing in the current stack does are
gbrain think — a cited answer plus an explicit note on what
the brain doesn't know — and a knowledge graph that wires itself on every write.
The recommendation is phases 0–1 only: stand up Postgres, migrate the vault layout, index it, wire the MCP server into Claude Code. Roughly four hours, and the only real bill is embeddings — chat runs on the Claude subscription. Everything beyond that — the entity backfill, the takes-and-calibration subsystem — is a separate decision made after the first one has proven itself.
| Phase | What it delivers | Steps | Gate before moving on |
|---|---|---|---|
| 0 — Prove it | Postgres, source clone, git init, vault migration, schema, config, first sync. Ends with three real questions asked of your own corpus. | 1–8 | The kill criterion. If think doesn't beat ten minutes of grep, stop. |
| 1 — Live with it | MCP into Claude Code, PATCH-001 + event extraction, autopilot, nightly dream (movements ①–④), weekly doctor, HTTP server with scoped clients for Hermes and the Air. | 9–13 | A week of ordinary use. Does the brain-first habit stick? |
| 2 — Earn the graph | LLM entity backfill over the ~220 non-Capture files, then link_resolution.global_basename. This is what makes the +31.4 P@5 claim apply to your corpus rather than someone else's. |
— | Re-run the three questions and measure the delta. |
| 3 — Compound | Dream movements ⑤–⑥ (propose_takes → grade_takes → calibration_profile), the self-knowledge pass, additional sources. |
— | Only after Phase 2's delta justifies it. |
Phases 0–1 are the recommendation. They are one evening plus a week of living with the result, and the only real bill is the one-time embedding run. Phases 2 and 3 are separate decisions with their own gates — 2 costs a few hours of agent time, 3 is the part with genuine recurring draw on your Claude quota.
One honest note on the boundary: PATCH-001 sits in Phase 1 because you decided you want event extraction, but it is the weakest-paying item there — it mines 3,436 words of real diary content. It is a bet on future volume once WhatsApp capture is frictionless, not on what exists today. Deferring it to Phase 3 would be equally defensible.
After the first import, ask three questions you genuinely cannot answer today: what have I written about consumer AI ideas, and which did I abandon and why? · what recurring themes run through my Writings? · what did I save about note-taking systems, and what's the through-line?
If gbrain think does not beat ten minutes of manual grep on all three,
stop at the gate. Do not build the rest on faith.
Not the full INSTALL_FOR_AGENTS.md path — 53 skills, the whole dream cycle,
OpenClaw/Hermes as designed. That is built for a 146,000‑page brain holding 24,585
people and 5,339 companies. This corpus is a thousand pages of essays, saved articles and
project notes with almost no person‑entities in it. Most of that machinery — person
dedup, expert routing, deal tracking — would have nothing to operate on.
Measured against the live vault on 2026‑08‑08:
| Measure | Value | Reading |
|---|---|---|
| Markdown files | 1,076 | 11.9 MB · ~1.57M words |
Captures/ | 816 | 76% of the vault — saved articles, threads, video summaries |
Daily Notes/ | 132 | 78% are empty templates. 21 files hold real content — 3,436 words total |
Writings/ | 37 | Original essays, mostly Chinese |
Projects/ · Ideas/ | 38 · 14 | 13 project notes + 25 podcast files; 14 thematic buckets |
| Wikilinks, entire vault | 23 | There is no graph. None. |
| Files with any tag | 13 | No faceting either |
This reframes the problem. It is not a discipline failure — it is structural. The
noto-notes skill has four intents (daily capture, append, route, answer) and
none of them create structure. Routing appends to an existing note but
never adds a link or a tag. So the vault grows monotonically with zero connective tissue,
and the only way in is lexical search plus memory of what you saved.
1.57 million words of your own thinking that you cannot ask a question of. That gap is precisely and only what gbrain closes.
gbrain's advertised +31.4 point P@5 lift comes entirely from its graph. But the auto-linker is pattern matching on wikilinks, not entity extraction — and the vault has 23. On import you get the graph-disabled variant, which is the thing that lift is measured against.
Hybrid vector + keyword search and think synthesis still work, and are still
a large upgrade over nothing. The graph is a later project, not a flag.
The requirement that drove the design: Noto stays a reader and a writer across all four devices, and the vault itself is where gbrain's markdown lives — not a copy of it.
ALL FOUR DEVICES MacBook Pro — runs nonstop iPhone ─┐ iPad ──┤ Postgres 17 + pgvector Air ──┼── Noto ──► iCloud vault ──► gbrain sync · dream · autopilot Pro ──┘│Hermes (co-located)│▲││git --separate-git-dir││(objects live OUTSIDE iCloud)└────────────┘ atoms/ · wiki/ · reports/ · life/events/
| Artifact | Lives | Synced | Rebuildable |
|---|---|---|---|
| The vault — markdown, the system of record | iCloud Drive | all 4 devices | It is the source |
| Postgres — pages, chunks, embeddings, links, facts, takes | Pro only | no | Yes — sync + extract all |
~/.gbrain/ — config, locks, logs, audit JSONL |
Pro only | no | Yes — regenerated |
~/.gbrain/noto.git — git object store |
Pro only | no | no — the exception |
Everything machine-local is derived and disposable except git history,
which exists only on this Mac and has no upstream. Since the object store sits inside the
otherwise-disposable ~/.gbrain, the fix is to give history an upstream: push to
a private remote on the same cron as the sync. One line, and ~/.gbrain becomes
genuinely safe to delete again.
git init --separate-git-dir splits the two halves of a repo. The
working tree — your notes, the thing git actually tracks — stays in iCloud
where every device sees it. The git directory — thousands of tiny loose
objects, independently synced, subject to placeholder eviction — moves to local disk. What
remains in the vault is a one-line .git pointer file that never changes.
pmset reports sleep 1, but MacCommandCenter.app
holds a persistent caffeinate -i -s -w asserting both
PreventUserIdleSystemSleep and PreventSystemSleep — 39
continuous hours at time of checking, on AC, 1d 15h uptime. An earlier version of this
record planned a wake schedule and weighed moving the daemon to a VPS. Both were
solving a non-problem; both are gone.
Residual condition worth knowing rather than acting on: -s holds only on AC,
and the assertion is owned by a process, not by power policy. If interfaces ever fail
overnight, pmset -g assertions is the first check.
| Destination | What lands there | Synced |
|---|---|---|
| The vault | Pages — atoms, concepts, brainstorm saves, dream originals, drift reports, life events, enriched pages. Plus ## Facts / ## Takes fences inside existing notes, and timeline lines. |
yes |
| Postgres | The derived index, plus DB‑only state: the take_proposals queue, minion_jobs, dream_verdicts, calibration_profiles, mcp_request_log, oauth_tokens. |
no |
~/.gbrain/ |
Config, locks, logs, audit JSONL, import checkpoints, eval receipts, git objects. | no |
The middle row matters more than it looks. propose_takes writes to a
database queue, not to markdown — the only thing that ever edits a takes fence in
the vault is you running gbrain takes propose --accept N. Grading, calibration
profiles, contradiction verdicts and job state are all DB-only.
Not separate files. The entity's own markdown gets a ## Facts and a
## Takes heading, each wrapping an HTML-comment-fenced table. The markers
render as nothing, so in Noto you see a heading and a table. Row numbers are
append-only, so cross-page references of the form slug#N stay
valid forever. Facts land on the entity's page, not on the page where you said them.
There is no global "write everything under one folder" setting — output paths are mostly hardcoded in the phases, not driven by the schema pack:
| Producer | Path | Configurable |
|---|---|---|
extract_atoms | atoms/<source-date>/… | hardcoded |
synthesize_concepts | wiki/concepts/… | via pack prefixes |
brainstorm · lsd --save | wiki/ideas/… | hardcoded |
drift | reports/drift-<date> | hardcoded |
| chronicle events | life/events/… | hardcoded |
dream synthesize | <output_root>/originals/… | yes |
enrich | the existing page, in place | no — by design |
gbrain enrich rewrites an existing thin page where it already lives.
Point it at an ideas stub and it rewrites your file. Not containable by configuration —
the control is whether you run it, and with which --types.
Rather than author a custom schema pack pointing at existing folder names, the vault adopts
gbrain-base-v2's conventions. Most of it is a rename.
| Today | Files | Becomes | Type | Note |
|---|---|---|---|---|
Captures/ | 816 | media/ | media | extractable: true — 816 atom candidates when ever enabled |
Projects/ | 13 | projects/ | project | clean rename |
| Podcast transcripts | 12 | stay in projects/podcast/ | source via frontmatter | folder for organisation, type for extractable: true |
| Podcast episode notes | 13 | projects/podcast/ | project | your own work, not consumed media |
Writings/ | 37 | writing/ | writing | singular — writings/ matches nothing |
On series/ | 1 | notes/ | note | forgoes writing/'s 1.4× boost + 365d decay |
Ideas/ | 14 | notes/ | note | generic notes, not concepts |
deep-research/ | 2 | wiki/analysis/ | analysis | |
root .md + logs | 22 | notes/ | note | |
Daily Notes/ | 132 | life/diary/<date>-<hash8> | diary | split into thoughts; 103 empty templates dropped |
Chats/ | 14 | — | — | dropped from the brain; files kept on disk |
Measured 2026‑08‑09: of 132 files, 103 are empty templates (frontmatter plus four unanswered journalling prompts), 8 are thin, and 21 hold real content — 3,436 words total, about 0.2% of the vault. Today's is empty. The prompt-journalling habit did not stick.
So the migration splits 21 files into one page per thought at
life/diary/<note-date>-<hash8> and drops the 103
templates — near-identical documents that would chunk, embed and compete in
retrieval while carrying nothing. Deprecating the daily-note mechanism also stops
manufacturing one noise page per day. The earlier plan called this folder immovable; that
constraint was noto-agent's compiled-in path, and it dies with the mechanism.
gbrain's boost and decay maps key on an older path convention than the pack's own
prefixes — they come from Garry's brain layout, not from gbrain-base-v2. A
concept page at the pack's wiki/concepts/ gets no 1.3× boost;
a diary at life/diary/ gets no demote. Both maps are
overridable (GBRAIN_SOURCE_BOOST, gbrain.yml recency:) and
must be set explicitly whichever layout you pick.
A path_prefixes entry is an inference rule — "a file here gets this
type unless frontmatter says otherwise" — not a storage requirement. frontmatter.type
overrides it, so organisation and semantics never have to agree.
Type governs what the extraction phases do to a page —
extractable, primitive defaults, expert routing.
Path governs ranking, git tracking and some eligibility gates — the
source-boost and recency-decay maps, db_only, the facts rescue prefixes.
Conventional directories do both at once, which is why they feel like one thing. It is
also why migrating to the pack does not automatically buy the tuned retrieval: the pack
declares wiki/concepts/ as a type prefix while the boost map keys
concepts/ as a path.
Renaming a thousand files inside iCloud — in a vault that already contains conflict copies
(2026-08-05.md and 2026-08-05 2.md both exist) — is the riskiest
operation in this plan. git init and commit the vault as-is first,
then migrate with git mv as a separate commit. git revert undoes
it. Doing the rename before git init throws away the only undo you had.
Three interfaces are wanted: WhatsApp capture through Hermes, vault edits triggering
reprocessing, and an API for apps not yet built. Almost none of this needs building —
gbrain serve --http already ships it.
| Route | What it is | Auth |
|---|---|---|
| POST GET /mcp | the MCP surface — 96 tools (10 localOnly ops filtered) | OAuth 2.1, per-op scope |
| POST /ingest | raw webhook write, ≤1 MB, rate-limited, queues a job | bearer, write |
| /admin/api/register-client | register a client, DCR-style | admin |
| /admin/api/rescope-client | change scopes, token TTL | admin |
| /admin/api/agents/spend | per-client LLM spend | admin |
| /admin/api/requests | request log, live job feed | admin |
/ingest for fire-and-forget
capture, /mcp with read for search ·
think · recall, which makes WhatsApp a read interface
too. It does not replace noto-agent: the dividing line is would you want
to see this file in Noto?
gbrain watch is a conversation-turn volunteer, despite the name). Autopilot's
~300 s adaptive tick is free and probably fine; gbrain sync trigger --source noto
--priority high fired from launchd WatchPaths gets it to seconds.
Debounce is mandatory — iCloud rewrites files constantly.
read and structurally cannot write; a compromised app is revoked
without touching the others.
stdio would give it all 106 tools and no auth. HTTP gives three things stdio cannot: per-client spend attribution, a request log of what an autonomous agent did to your brain, and scope enforcement — the 10 withheld ops are file operations and hard purges Hermes should not have.
Claude Code stays on stdio: that is you at the keyboard, it benefits from the full
surface, and it is already attributable. Bind the HTTP server to the Tailscale
interface, not 0.0.0.0.
| Decision | Chose | Over |
|---|---|---|
| Where the markdown lives | The Noto vault, directly | A one-way rsync mirror |
| Vault layout | Migrate to gbrain-base-v2 | Custom pack matching existing names |
| Git object store | ~/.gbrain/noto.git | A path outside the runtime dir |
| How many hosts | One — the Pro, running nonstop | Both Macs; a VPS daemon |
| Storage engine | Homebrew Postgres 17 + pgvector | PGLite (every quickstart's path) |
| Daily notes type | diary, via pack prefix | note; retyping as conversation |
| Chronicle events | Repurposed — things done and things that happened | Leaving them meeting-shaped and off |
| Self-knowledge synthesis | A separate pass with our own prompt | The stock atom pipeline |
| Nightly cycle scope | Movements ①–④ to start | All 23 phases from day one |
| Local modifications | A carried patch set, source clone | Editing the global install |
| Hermes transport | HTTP with a scoped client | stdio MCP |
| LLM provider | claude-cli — the Claude subscription | Anthropic API key; Codex + ChatGPT |
auto_chronicle off, as an earlier version of this record decided.kind vocabulary is already life-shaped — meal · solo · travel · work · milestone alongside meeting · call. No prompt rewrite needed. Only the diary exclusion blocks it, and that exclusion is a policy written for multi-user company brains.auto_chronicle on, and life/events/ pages appearing in the vault on every device.extractable: true on diary to mine recurring themes.virality_score. Pointed at a diary, that ranks your feelings by tweetability. The pack's prompt_template escape hatch is scaffolded but never loaded into an LLM call, so it cannot be swapped by config.wiki/concepts/. Same output shape, prompt fully ours, works today, no fork.hostinger-openclaw; running a second index on the Air.noto-agent needs no bridge.Every deviation from stock gbrain must survive upstream updates. That constraint shapes the install method and the patches themselves.
Install from a source clone (git clone + bun link),
not bun install -g. gbrain detects a bun-link install and upgrades
by running git pull --ff-only — which fails cleanly on a branch
carrying local commits rather than destroying them. That failure is correct behaviour here,
not a bug: it is the signal to rebase. Leave self_upgrade.mode unset.
operations.ts is 6,246 lines and changes most releases.core/chronicle/backstop.ts — one hunk, ~4 lines, in a ~40-line file that rarely changes.chronicle/eligibility.ts (a shared pure predicate with tests pinning diary_excluded) plus operations.ts (6,246 lines). That was the first design; rule 3 killed it.diary_excluded and chronicle.include_diary is set, proceed. Stock behaviour when unset.Backfilling the 21 daily notes that hold real content is not part of the patch — the backstop fires on real imports, so forcing a re-import triggers extraction for all of them. A one-off command, per rule 4.
One patch is the target steady state. If the register reaches four or five entries, the question stops being "how do we rebase these" and becomes "should we be running a fork."
| Risk | Severity | Mitigation |
|---|---|---|
| iCloud "Optimize Mac Storage" evicts file contents. Sync reads a placeholder, sees empty content, overwrites a good page with an empty one. | high | Turn it off on the host, or brctl download the vault before syncing. Already a known failure mode in the noto-notes skill. |
| Renaming ~1,000 files inside iCloud, in a vault that already contains conflict copies. | high | Commit the vault as-is first; migrate with git mv as a separate revertible commit; other devices idle; Optimize Storage already off. |
| The facts backstop fires on the bulk import. It defaults on, its model defaults to Sonnet, and it runs from the sync post-import hook — ~1,076 pages, most eligible. | high | facts.extraction_enabled false before the first sync; re-enable deliberately after. Confirm against a dry run. |
Budget caps do not apply at all. The meter filters on the anthropic: prefix, so claude-cli: falls through the same bypass a non-Anthropic model would. |
medium | Treat the cap as decorative under a subscription; the real ceiling is quota. Only meaningful if you switch back to anthropic: API billing. |
| Half the corpus is Chinese and Postgres has no Chinese stemmer. The keyword half of hybrid search degrades; vector + reranker carry it. | medium | Measure early — query a known Chinese essay by exact phrase. Untested on this corpus. |
| Rate limits replace cost as the constraint. With chat on the subscription, a nightly cycle over 1,000 pages draws on the same quota used interactively in Claude Code. Nothing in gbrain measures this. | medium | Start with movements ①–④ only; watch for interactive throttling before widening the cycle. |
The default search mode is still the expensive corner — init auto-applies tokenmax. The 25× dollar spread mostly dissolves on a subscription, but token volume and reranker calls remain real. |
medium | Set deliberately at init. For a few hundred queries a month, conservative. |
| Patch-set rot. An undocumented local edit is how a patch set decays into an unmaintainable fork. | medium | The register is the contract; the five shaping rules keep conflicts cheap; the tripwire at 4–5 entries forces the question. |
| One host means one failure domain. A runaway now takes down WhatsApp capture and every app, not just gbrain — this Mac was frozen once by a 31 GB leak. | medium | autopilot --max-rss 2048; conservative Postgres shared_buffers. Relocating stays cheap if it recurs. |
Folder names mean nothing to the default pack if the migration is half-done — writings/ is not writing/. |
low | Verify with gbrain schema stats after migrating; the untyped-page warning fires above 10%. |
| 106 MCP tools land in every Claude Code session — not the "30+" advertised — paid for on every request. | low | Accept, or use HTTP with a read-scope token, which collapses the surface to the 60 read ops. |
Steps 1–8 are one evening. The gate at step 8 is the kill criterion — everything after it is a second decision.
brctl download the vault; confirm other devices idlebrew install postgresql@17 pgvector, start it, create the database with shared_buffers at 1–2 GBbun link — not bun install -g. Add upstream remote. Then gbrain init against Postgres and gbrain doctorgit init --separate-git-dir=~/.gbrain/noto.git in the vault; commit as-is — the restore point; add a private remotegit mv per the map. Daily Notes/ stays. Commit as a separate revertible commitgbrain sources add noto --path "$VAULT"; gbrain.yml marking media/ as db_only; add daily-notes/ as a diary prefixGBRAIN_SOURCE_BOOST and recency: explicitly; point chat touchpoints at claude-cli:; facts.extraction_enabled false for the import; auto_chronicle truethink doesn't beat ten minutes of grep, stop hereclaude mcp add gbrain -- gbrain serve; paste the brain-first protocol into the global CLAUDE.mdgbrain config set chronicle.include_diary true; force a re-import of the diary pages to backfill eventsgbrain autopilot --install --max-rss 2048 — the cap is the mitigation for a leak that already froze this machine oncegbrain dream, movements ①–④; weekly gbrain doctor; re-enable facts.extraction_enabledgbrain serve --http bound to Tailscale; register scoped clients for Hermes and the Air; confirm agent pages appear in Noto on the phoneThree cron lines and a daemon, not the twenty-job reference schedule. No email, X, calendar or meeting collectors — four credentials and four scripts serving entity types this corpus barely has. No morning briefing until there is something to brief on.
| Item | Estimate | Billed to |
|---|---|---|
| Initial embedding — 1.57M words, CJK-inflated to ~3–5M tokens | $0.10–0.50 once | API key |
| Reranking (ZeroEntropy), per query | fractions of a cent | API key |
gbrain think, per query | — | subscription |
| Chronicle event backfill — 21 real daily notes | — | subscription |
brainstorm · lsd, per run | — | subscription |
| Dream cycle, nightly | — | subscription |
| Postgres + pgvector, local | $0 | — |
claude-cli dispatches through the local claude binary's OAuth
session instead of an API key — the provider exists precisely so subscribers can run
subagent dispatch against a subscription rather than per-token billing.
Total real spend for this system is embeddings plus reranking. Claude has
no embedding model, so an OpenAI/Voyage/ZeroEntropy key is still required — and that is
the only bill.
The budget meter filters on the anthropic: prefix, so claude-cli:
falls through the same bypass a non-Anthropic model would — the cap is decorative here.
And a nightly cycle over a thousand pages draws on the same quota you use interactively in
Claude Code, which nothing in gbrain measures.
The 25× search-mode spread mostly dissolves as a dollar figure, but token volume still
presses on quota and tokenmax still drives billed reranker calls. Setting the
mode deliberately at init stays worthwhile — for a different reason than before.
improvement-log/ belongs in the brain at all, or stays a workflow artifact outside it.noto-notes routing intent already reads three candidate notes during its search, so proposing a wikilink costs nothing extra.
Every claim here is from github.com/garrytan/gbrain at
v0.42.74.0, shallow-cloned and read directly — not from the README.
Vault figures measured live 2026‑08‑08; machine state verified
2026‑08‑09. Companion documents: the multi-device architecture, the interface
design, the patch register, and the operator's field manual.