# gbrain: the Postgres-native knowledge graph Garry Tan runs his AI agents on

URL: https://www.thedeepfeed.ai/posts/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/
Category: Tools
Published: 2026-05-24
Updated: 2026-05-25
Author: the-deep-feed
Tags: gbrain, garry-tan, agent-memory, knowledge-graph, postgres, mcp, openclaw, hermes
Kind: deep

> 146,646 pages. 24,585 people. 66 cron jobs running autonomously. gbrain is the persistent memory layer behind Garry Tan's own AI agents, and the rare self-hosted alternative to vector-DB wrappers in 2026.

## TL;DR

- 18,652 GitHub stars in 7 weeks, 2,617 forks, MIT. `garrytan/gbrain` is the persistent memory layer behind Garry Tan's own AI agents (OpenClaw + Hermes). Production scale 146,646 pages, 24,585 people, 5,339 companies, 66 cron jobs running autonomously.
- BrainBench retrieval lands P@5 49.1%, R@5 97.9%. The graph layer alone is responsible for +31.4 P@5 points over hybrid-without-graph and vector-only RAG.
- Two engines on one contract. PGLite (Postgres 17 via WASM, zero-config) for personal brains; Postgres + pgvector (Supabase or self-hosted) for team and scale.
- Brain repo is the system of record. Knowledge lives as markdown in git, the DB is the retrieval index, auto-link on every write is regex-based with zero LLM calls. v0.40.7.0 lets agents author the schema themselves over MCP.
- In 2026's AI-memory landscape, gbrain is the rare self-hosted, graph-first, markdown-SoR alternative to vector-DB wrappers (mem0, zep, Letta) and hosted SaaS (OpenAI/Anthropic memory).

At some point overnight, while [Garry Tan](https://x.com/garrytan) was asleep, his AI brain ingested a meeting transcript from yesterday, extracted three new people pages, cross-referenced one of them against an existing portfolio company, scored a suspected contradiction between two takes he had written six weeks apart, and queued tomorrow morning's task list. That happened across **66 cron jobs** running against a Postgres database the President and CEO of [Y Combinator](https://ycombinator.com) operates on his own machines. The database holds **146,646 pages, 24,585 people, 5,339 companies**. The whole thing is MIT-licensed and open-source. It is called [**gbrain**](https://github.com/garrytan/gbrain). It is the rare AI memory system in 2026 that you can actually own.

![Three-layer architecture: markdown SoR → Postgres retrieval → skills](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-architecture-three-layers.jpg)

# The frame: every AI memory in 2026 is one of three things

There are three roads being walked in agent memory right now. The first is the *vector-DB wrapper*: stuff every conversation into Pinecone or Weaviate, let an LLM extract "facts" on write, retrieve by cosine similarity on read. [mem0](https://github.com/mem0ai/mem0), [zep / Graphiti](https://github.com/getzep/graphiti), and [Letta (formerly MemGPT)](https://github.com/letta-ai/letta) all live here. The second is the *hosted SaaS*: [OpenAI's "memory"](https://help.openai.com/en/articles/8590148-memory-faq) inside ChatGPT, [Anthropic's Projects](https://www.anthropic.com/news/projects), Notion AI, the consumer-facing offerings where you give a vendor your knowledge and trust them not to lose it, leak it, change the pricing, or shut it down. The third road, which has very few travellers, is *self-hosted and graph-first*: the brain is yours, the file format is plain text, the database is one you can dump and walk away with, the agent that reads it runs on your own metal.

gbrain is the third road. Its whole point is that you own the brain. Garry Tan, who runs YC and could presumably afford any hosted memory service that wanted his data, chose to write his own and open it. That choice is the editorial signal: when the person who has every option picks self-hosted markdown over a managed cloud, the optionality being preserved is *yours, forever*. The brain outlasts vendors. It outlasts API price changes. It outlasts the agent harness you're using this year.

This piece is the third leg of a small editorial trilogy. [Our earlier post](https://thedeepfeed.com/posts/2026-05-11-openclaw-vs-hermes-agent-personal-ai-runtime-wars/) on the personal-AI runtime wars covered which *agent harness* to pick. [Our piece on gstack](https://thedeepfeed.com/posts/2026-05-23-gstack-garry-tan-claude-code-developer-guide/) covered the *developer-tool harness* (the Claude Code skill pack). gbrain is the *memory layer* both harnesses share. If the agent is the body and the skills are the hands, gbrain is the thing that remembers yesterday.

# Scoreboard

| Fact | Value | Source |
|---|---|---|
| First public commit | April 5, 2026 | [garrytan/gbrain](https://github.com/garrytan/gbrain) |
| GitHub stars | 18,652 | repo header |
| Forks | 2,617 | repo header |
| License | MIT | [LICENSE](https://github.com/garrytan/gbrain/blob/main/LICENSE) |
| Current version | v0.40.9.0 | [CHANGELOG.md](https://github.com/garrytan/gbrain/blob/main/CHANGELOG.md) |
| Production scale (Garry's brain) | 146,646 pages, 24,585 people, 5,339 companies | [README](https://github.com/garrytan/gbrain/blob/main/README.md) |
| Cron jobs running autonomously | 66 | README |
| Default storage engine | PGLite (Postgres 17 via WASM) | [topologies.md](https://github.com/garrytan/gbrain/blob/main/docs/architecture/topologies.md) |
| Scale engine | Postgres + pgvector (Supabase or self-hosted) | topologies.md |
| BrainBench P@5 / R@5 | 49.1 / 97.9 | [RETRIEVAL.md](https://github.com/garrytan/gbrain/blob/main/docs/architecture/RETRIEVAL.md) |
| Graph lift vs hybrid-no-graph | +31.4 P@5 points | RETRIEVAL.md |
| Default embedding + reranker | ZeroEntropy (`zembed-1`, `zerank-2`) | README (v0.36.2.0) |
| Bundled skills | 57 (43 core + 14 schema + auxiliaries) | [`skills/`](https://github.com/garrytan/gbrain/tree/main/skills) |
| Default page types | 22 universal | [what-schemas-unlock.md](https://github.com/garrytan/gbrain/blob/main/docs/what-schemas-unlock.md) |
| `gbrain schema` CLI verbs | 14 (added v0.40.7.0) | CHANGELOG |
| MCP operations | 74 with scope annotations | CHANGELOG v0.40.8.0 |
| Source files in `src/` | 569 TypeScript files | repo |
| Test files | 859 | repo |

The numbers do a particular kind of editorial work. *146,646* is not a marketing round number. It is the count on Garry's actual machine at the moment he wrote that README line. The same precision shows up in the BrainBench scores. *49.1 P@5* is what the benchmark reported, not "around 50". Treat the precision as a signal: the people writing the docs are running the same brain you're being asked to install.

# The origin

The [README's pitch sentence](https://github.com/garrytan/gbrain/blob/main/README.md) lands the entire frame in two clauses:

> "Your AI agent is smart but forgetful. GBrain gives it a brain."
>
> — [`garrytan/gbrain README`](https://github.com/garrytan/gbrain/blob/main/README.md)

The story behind it is documented honestly in [`docs/ethos/ORIGIN.md`](https://github.com/garrytan/gbrain/blob/main/docs/ethos/ORIGIN.md). The whole doc is 27 lines. The relevant paragraph reads:

> "GBrain came out of building OpenClaw, Garry's personal AI agent fork. The first version had skills and a brain, but the brain was a flat directory of markdown files. Search was ripgrep. Memory was vibes."
>
> — [`docs/ethos/ORIGIN.md`](https://github.com/garrytan/gbrain/blob/main/docs/ethos/ORIGIN.md)

Two failures showed up immediately, and the doc names them.

> "First, the agent forgot things between conversations. Every new session re-asked basic questions. Names of people Garry had introduced last week were gone. Decisions made on Tuesday didn't survive to Thursday. The brain existed but the agent couldn't actually use it.
>
> Second, the agent kept duplicating work. Two different signals about the same company became two different people pages. Three meetings with the same person became three uncorrelated timeline entries. The signal-to-noise ratio decayed in real time."
>
> — ORIGIN.md

These are the two pathologies that any markdown-files-and-cron approach hits the moment your brain crosses about two thousand notes. Ripgrep does not know that "GT" and "garry-tan" and "Garry" refer to the same person. A flat directory does not know that the meeting on Tuesday is the third one with the same investor. Tags do not have semantics. Tags are labels.

The fix, the doc continues, was not one idea. It was "many small ones layered together": brain-first lookup before any external API call, auto-linking on every write so the graph grows for free, typed edges so "who works at Acme AI?" returns something, hybrid search because vector alone underdelivers, a reranker because hybrid is locally optimal but globally suboptimal, nightly cron to dedup and surface contradictions, and an agent that reads `skills/RESOLVER.md` once and knows what to do. None of those are new ideas. The contribution is shipping them all together, on Postgres in WASM (no server), with skills that are markdown (not code), routed by a small text file (not a router LLM).

The original ORIGIN.md was written when Garry's brain held 17,888 pages, 4,383 people, 723 companies, and 21 cron jobs. The current README reports 146,646 pages, 24,585 people, 5,339 companies, 66 cron jobs. Between the writing of ORIGIN.md and the current README, the brain has roughly *8x'd*. The architecture did not change to accommodate the growth. The doctor learned how to remediate at scale. The cycle learned when to back off. The schema layer became agent-authored. But the shape of the brain (markdown in git, Postgres as the index, graph as the retrieval booster, skills as the agent surface) is the same shape it was at 17K pages. That is the architectural longevity argument expressed as fact: the design survived an order-of-magnitude scale jump without rewrite.

There is one more line in ORIGIN.md worth quoting verbatim because it is the most honest framing of what gbrain actually is:

> "GBrain is what happens when you write the brain you actually wanted to have."
>
> — ORIGIN.md

That is also the polite version of the answer to every "but I could do this with cron and markdown" objection. We get to that objection later. For now, the system.

# Architecture: two engines on one contract

The first thing to internalise about gbrain is that it has *two database engines* but exactly *one operational contract*. The README says it plainly: "Database ready in 2 seconds (PGLite, no server). You just answer questions about API keys." That two-second number is doing a lot of work. PGLite is [ElectricSQL's Postgres 17 compiled to WebAssembly](https://github.com/electric-sql/pglite); it runs in-process, persists to disk, supports pgvector, and needs no daemon. That is the entire local install story. There is no Docker. There is no `pg_ctl`. The whole engine is a `.wasm` blob your CLI dlopens at startup.

At scale, when your brain exceeds the comfort zone for an embedded DB (the line is somewhere around 50,000 pages depending on hardware), or when you want a brain that two machines share, you flip to Postgres + pgvector via either [Supabase](https://supabase.com) or your own self-hosted Postgres. The exact same CLI verbs work. The exact same MCP operations work. The same `gbrain doctor` runs. That is the "one contract" promise, encoded in the [`BrainEngine`](https://github.com/garrytan/gbrain/blob/main/src/core/engine.ts) interface, which defines approximately 47 operations that both engines implement. The implementation files live under `src/engines/pglite/` and `src/engines/postgres/`; the consumers do not know which is underneath.

![Two engines on one contract: PGLite locally, Postgres+pgvector at scale](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-engine-topology.jpg)

| Engine | Backing store | Best for | Latency | Setup time |
|---|---|---|---|---|
| PGLite | Postgres 17 compiled to WASM, persisted to `~/.gbrain/` | Solo, single machine, ≤50K pages | Sub-millisecond reads from local disk | "2 seconds" per README |
| Postgres + pgvector | Real Postgres, locally or via Supabase | Multi-machine, team, large-corpus | Network round-trip | Minutes (interactive `gbrain init --supabase`) |

The decision to commit to *Postgres specifically* rather than picking a purpose-built vector DB is the second big architectural call. The argument is mechanical. A real DB gives you SQL with planner-aware joins. It gives you HNSW vector indexes [via pgvector](https://github.com/pgvector/pgvector). It gives you `tsvector` for BM25-shaped keyword search. It gives you `JSONB` for the rich-typed claim fences. It gives you transactional writes you can wrap in `withMutation` blocks. None of that exists in the vector-DB-of-the-month stack. The thing your brain needs most is the boring one: a real database with two-decade-old query semantics that thousands of operators already know how to run.

## Topologies, in three shapes

[`docs/architecture/topologies.md`](https://github.com/garrytan/gbrain/blob/main/docs/architecture/topologies.md) breaks the deployment shape into three orthogonal cases. Topology 1 is single-brain-single-machine, the default. Topology 2 is *cross-machine thin client*: the brain lives on a beefy host ("brain-host") with autopilot running, and your laptop has only a thin install with `remote_mcp` configured. Every DB-bound command (`sync`, `embed`, `extract`, `migrate`, `serve`) is *refused* on the thin client with a clear error pointing at the host. That refusal is intentional. It prevents two copies of source-IDs from racing. Topology 3 is *split-engine*: each [Conductor](https://conductor.build) worktree gets its own per-worktree code index in a private PGLite, while the artifact brain (plans, learnings, retros, transcripts) lives in a single shared brain that all worktrees can read and write.

The reason these three exist and not, say, "one brain shape and a `cluster_mode` flag" is that the routing rule is dead simple: which `~/.gbrain/config.json` is active right now? The `GBRAIN_HOME` env var selects. Topology 3 falls out of that selection logic for free. A Conductor worktree sets `GBRAIN_HOME=$PWD/.conductor` and serves on a per-worktree port; the artifact brain stays at `~/.gbrain` and serves on port 3000; the agent's MCP config points `mcp__gbrain_code__*` at the worktree port and `mcp__gbrain_artifacts__*` at the artifact host. The whole composition is config, not code paths.

The thin-client refusal logic is worth dwelling on because it is the kind of design call most systems get wrong. When a CLI verb that needs a local engine runs on a thin-client install, the dispatch guard refuses with a clear error pointing at the remote MCP URL. `gbrain sync`, `gbrain embed`, `gbrain extract`, `gbrain migrate`, `gbrain apply-migrations`, `gbrain repair-jsonb`, `gbrain orphans`, `gbrain integrity`, `gbrain serve`: all refuse. The competing approach in most distributed systems is to *silently route* DB-bound commands over the network. That looks helpful but it is the source of half of the production weirdness in remote-MCP setups, because the operator does not realise their local CLI is now doing an `EMBED ALL` over a 30Mbps link. The refusal is honest. It says "this command needs a local engine; this machine is not the engine; here is where the engine is; go there." `gbrain doctor` also runs a dedicated thin-client check set (OAuth discovery, token round-trip, MCP smoke) so the operator can verify the link without doing damage.

### Topology 3 in practice: a worked Conductor example

The scoreboard treatment of Topology 3 is "split-engine, per-worktree code + shared artifacts." The operator question that matters is *what does this look like on my actual machine?*. Below is the worked example, end-to-end, for a Claude Code user running [Conductor](https://conductor.build) with two parallel worktrees on the payments service.

![Topology 3 in practice: two Conductor worktrees with their own PGLite code brains plus one shared artifact brain](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-topology-3-worked.jpg)

The shape on disk:

```
~/code/payments-A/                  # Worktree A (refactor branch)
└── .conductor/
    └── gbrain/                     # GBRAIN_HOME=$PWD/.conductor/gbrain
        ├── config.json             # embedding_model: voyage:voyage-code-3
        ├── db/                     # PGLite WASM, code index for THIS worktree
        └── audit/

~/code/payments-B/                  # Worktree B (feature branch)
└── .conductor/
    └── gbrain/                     # GBRAIN_HOME=$PWD/.conductor/gbrain
        ├── config.json             # embedding_model: voyage:voyage-code-3
        ├── db/                     # PGLite WASM, separate code index
        └── audit/

~/.gbrain/                          # Default — artifact brain
├── config.json                     # embedding_model: zeroentropy:zembed-1
├── db/                             # PGLite OR pgvector → Supabase
└── audit/                          # Plans, learnings, retros, transcripts
```

The two worktrees never see each other's code index. They both see the same artifact brain. That is the load-bearing invariant of Topology 3.

The setup, three terminals worth of commands:

```bash
# Terminal 1 — start the artifact brain on port 3000 (default ~/.gbrain)
gbrain init --pglite                              # one-time, if not already done
gbrain serve --http --port 3000 &                 # serves plans, learnings, retros

# Terminal 2 — wire worktree A's code brain on port 3001
cd ~/code/payments-A
export GBRAIN_HOME=$PWD/.conductor/gbrain
gbrain init --pglite \
  --embedding-model voyage:voyage-code-3 \        # code-tuned, NOT zembed-1
  --embedding-dimensions 1024
gbrain reindex --code ./services/payments         # builds the code-def graph
gbrain serve --http --port 3001 &

# Terminal 3 — same for worktree B on port 3002
cd ~/code/payments-B
export GBRAIN_HOME=$PWD/.conductor/gbrain
gbrain init --pglite --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
gbrain reindex --code ./services/payments
gbrain serve --http --port 3002 &
```

The agent's MCP client config then aliases each port:

```jsonc
// .claude/mcp.json (or hermes.config.json, or claude_desktop_config.json)
{
  "mcpServers": {
    "gbrain_artifacts": { "url": "http://localhost:3000/mcp" },
    "gbrain_code":      { "url": "http://localhost:3001/mcp" }  // current worktree
  }
}
```

Worktree A's `.claude/mcp.json` points `gbrain_code` at 3001. Worktree B's points at 3002. The artifact alias is the same in both. The agent calls `mcp__gbrain_code__search` for code questions, `mcp__gbrain_artifacts__search` for everything else.

🐛 **The footgun the docs name explicitly:** alias-level routing is manual. If the agent calls `mcp__gbrain_artifacts__put_page` with code-shaped content (say, a function snippet captured during a `/retro` skill), that page lands in the artifact brain forever. There is no auto-dispatch layer that inspects payloads and redirects "this looks code-shaped, send to gbrain_code." That is intentional per [`topologies.md`](https://github.com/garrytan/gbrain/blob/main/docs/architecture/topologies.md): "explicit beats magic." The three mitigations the doc names:

1. **Name aliases unambiguously.** `gbrain_code` vs `gbrain_artifacts` is clear; `gbrain` vs `gbrain_local` is not.
2. **Document the routing in the agent's system prompt.** A one-liner like "code questions → `gbrain_code`; anything else → `gbrain_artifacts`" is enough.
3. **Pair Topology 3 with gstack's per-worktree wiring.** The gstack project automates the alias-and-rules setup across worktrees so the agent does not have to reason about it.

Why per-worktree code brains beat one shared code brain across worktrees: two worktrees running `gbrain reindex --code` against the same DB would race the `last_commit` cursor, double-write symbol tables, and clobber each other's source IDs. Topology 1 prevents the race by serializing reads. Topology 3 prevents the race by *isolating* the reads. The artifact brain stays shared because plans and learnings are append-only and benefit from cross-worktree visibility. The two design choices answer the same question with opposite answers, and both answers are correct for their own subject.

The Voyage embedding choice is also operator-visible. `voyage-code-3` is Voyage's code-specialised model with head-to-head numbers above their general flagships on code retrieval. Per-worktree code brains index source files only — no meeting notes, no people pages, no transcripts. Configuring code-tuned embeddings at `init` time means the config cannot be lost to a later `init` overwrite. For already-initialised brains the one-command switch is `gbrain reinit-pglite --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024 && gbrain reindex --code --yes`. `gbrain reindex --code` prints a nudge when the configured embedding is not code-tuned; suppress with `GBRAIN_NO_CODE_MODEL_NUDGE=1` if you have intentionally chosen another provider.

The composed result: each worktree's Claude Code (or Hermes) instance sees its own clean code index *plus* the team-wide artifact memory. A refactor in worktree A never confuses worktree B's symbol resolution. A retro written in worktree A surfaces in worktree B's next planning session. That is the gbrain answer to "I have eight Conductor tabs open and they keep stepping on each other."

## Brain ⊥ source

The second axis, orthogonal to the engine question, is *brain vs source*. A *brain* is a database. A *source* is a markdown repo inside that database. Many sources can live inside one brain. One brain can be federated across many machines. [`docs/architecture/brains-and-sources.md`](https://github.com/garrytan/gbrain/blob/main/docs/architecture/brains-and-sources.md) is the canonical doc, but the operational summary is: a `.gbrain-source` dotfile sitting at the root of a directory tells the gbrain CLI "this whole subtree is one named source, with these flags". Routing through that dotfile uses a six-tier precedence chain (CLI flag → env var → nearest dotfile walking up → repo root → user default → bundled default), so you can override any decision without editing config.

Why does this matter? Because the unit you care about as an operator is *the source*, not the *brain*. You want to point gbrain at `~/notes/` and at `~/work/companies/` and at `~/voice-transcripts/` and have them ingest as three separate sources, each with their own source-tier boost in retrieval. That is exactly what happens. Hard-exclude prefixes (`test/`, `archive/`, `attachments/`, `.raw/`) filter at retrieval, not post-rank. Curated content like `originals/`, `concepts/`, `writing/` outranks bulk content like `your-openclaw/chat/`, `daily/`, `media/x/`. The boost map is configurable per call via `SearchOpts.exclude_slug_prefixes` or globally via `GBRAIN_SOURCE_BOOST`. The whole source-tier system lives in [`src/core/search/sql-ranking.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/sql-ranking.ts).

## Markdown is the system of record

![Markdown repo as system of record on the left, Postgres database as retrieval index on the right, write-through arrow connecting them — repo is truth, DB is the index](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-markdown-sor.jpg)

The single most consequential architectural call in gbrain, and the one that decides whether you should care about it, is this: *the markdown repo is the system of record, and the database is the retrieval index*. Not the other way around. Knowledge lives as files in git. gbrain syncs the repo into Postgres for retrieval. Deletes in git become soft-deletes in the DB. If you blow away `~/.gbrain/` tomorrow, you lose nothing that matters; you re-init, re-sync, re-embed, and you are back. The brain is the index. The repo is the truth.

[`docs/architecture/system-of-record.md`](https://github.com/garrytan/gbrain/blob/main/docs/architecture/system-of-record.md) lays this out as an explicit invariant. Every `put_page` goes through write-through plumbing: page lands on disk AND in the DB in one move. The v0.38 capture verb made that the public surface:

```bash
# Inline: the most common shape — one-liner captured to inbox/.
gbrain capture "the thought I want to remember"

# File-mode: capture an existing markdown file into the brain.
gbrain capture --file ./notes/today.md

# Pipe-mode: capture stdin (lets you wire gbrain into existing toolchains).
echo "from a pipe" | gbrain capture --stdin

# Scriptable: --quiet returns just the slug so you can chain further commands.
SLUG=$(gbrain capture "..." --quiet)
```

Captures cluster in `inbox/YYYY-MM-DD-<hash8>` so triage is predictable. The on-disk filename is canonical. The DB row is derived. The agent can read either one and get the same content. That choice makes the brain *forkable in a way no hosted memory service is*. You commit your brain repo to a private git remote and you have backup, history, blame, and the option to walk away from gbrain entirely without losing the underlying knowledge. You could pour the markdown into Obsidian tomorrow and read every page. The graph would be gone, the typed facts would be gone, but the prose would be there.

That is the difference between *memory as data* and *memory as service*. mem0 and zep and OpenAI memory all hold your data inside their layer; if you stop using them, the memory effectively vanishes. With gbrain, the layer is replaceable. The data is yours.

# The four retrieval strategies in concert

The load-bearing technical claim in gbrain's README is that the retrieval stack lands **P@5 49.1%** on [BrainBench](https://github.com/garrytan/gbrain-evals), beating the graph-disabled variant by **+31.4 points P@5** and beating ripgrep-BM25 and vector-only RAG by a similar margin. The full RETRIEVAL.md doc spells out why each strategy alone fails and why the layering works.

![Four retrieval strategies in concert: vector + BM25 + RRF + graph traversal](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-four-strategies.jpg)

The four strategies are these:

1. **Vector (HNSW on pgvector)**. Semantic similarity. Catches "who works on retrieval quality at YC?" → pages mentioning "Garry Tan + retrieval" even when the user never typed "YC". Good at theme. Blind to facts.
2. **BM25 keyword**. Lexical match via `tsvector`. Catches names, exact phrases, code identifiers. Survives the cases where vector search drifts into thematic neighbours.
3. **Reciprocal-rank fusion (RRF)**. Merges vector + keyword rankings without weighting one over the other globally. Each strategy votes.
4. **Knowledge graph traversal**. Follows typed edges. Catches "what did Bob invest in this quarter?" by walking `bob ── invested_in ──> company ── dated ──> Q1`. Vector cannot see causal chains. The graph can.

The argument for layering is by exclusion: each strategy alone has a class of queries it cannot answer.

| Strategy | What it nails | What it misses |
|---|---|---|
| Vector only | "What is this person's general area?" thematic match | Specific factual relationships not in the embedding ("Companies in Garry's portfolio" returns essays about portfolios, not companies) |
| Keyword only | Exact tokens, names, code identifiers | Synonyms, paraphrases, "search ranking" when the user wrote "retrieval" |
| Graph only | "Neighbors of Alice", multi-hop traversal | Fresh pages with no backlinks; anything not yet linked |
| Hybrid (vector + keyword + RRF), no graph | "What is X?" queries | "What is Y's relationship to X?" queries, which are graph queries no amount of embedding tuning recovers |

The published BrainBench table makes the missing-graph cost concrete:

| Strategy | P@5 | R@5 | Notes |
|---|---|---|---|
| ripgrep BM25 only | ~18 | ~75 | Lexical-only baseline |
| Vector-only RAG | ~18 | ~80 | Standard RAG implementation |
| gbrain graph-disabled (hybrid + RRF, no graph) | ~18 | ~85 | Hybrid alone |
| **gbrain default (full stack)** | **49.1** | **97.9** | Graph + extract-quality lift |

![BrainBench: gbrain default P@5 49.1% vs hybrid-no-graph 18%, the +31 lift visualized](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-p5-lift.jpg)

That +31.4 P@5 lift is what the graph *and* the auto-link primitive together produce. RETRIEVAL.md is candid about the implication: "The graph isn't a marginal feature; it's the load-bearing wall." If you take the graph out of gbrain, what you have left is a moderately-good hybrid RAG. With the graph in, you have something a category above. The graph is the difference.

## Auto-link: why zero-LLM edge extraction works

![Auto-link extraction: a markdown page enters, three regexes fire, typed edges flow into Postgres — zero LLM tokens spent](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-auto-link-pipeline.jpg)

Every `put_page` in gbrain runs `extractEntityRefs` on the markdown body. It matches three regexes:

- Standard markdown links: `[Garry Tan](wiki/people/garry-tan)`
- Obsidian-style wikilinks: `[[wiki/people/garry-tan|Garry Tan]]`
- Typed-link blockquotes: `> **Convention:** see [path](path).`

Three regexes, zero LLM tokens, single SQL `addLinksBatch` call with `INSERT ... SELECT FROM unnest(...) JOIN pages ON CONFLICT DO NOTHING RETURNING 1`. The graph grows on every write at near-zero cost. The RETRIEVAL.md doc says full graph extract completes in seconds on a 17K-page brain. On Garry's 146,646-page brain the operation is still measured in seconds because the cost is dominated by SQL plan execution, not LLM round trips.

The competing approach in 2026 is *LLM-extracted edges*: mem0 calls an LLM per `add` to decide which facts to remember and how to type them; Cognee and GraphRAG do similar. Every write is one or more LLM calls. On a corpus the size of Garry's, that is six-figure dollars per year of extraction cost alone. gbrain pays zero. The trade is that the regex-based primitive cannot pick up edges that are *implied* by prose but not linked by syntax. Garry's answer is the heuristic link-type inference layer: `attended`, `works_at`, `invested_in`, `founded`, `advises`, `mentions` fire from surrounding sentence context, still LLM-free. Power users who want richer types add them via the typed-link blockquote convention. The math of the trade is roughly: 95% of edges with 0% of the cost.

## ZeroEntropy: 60% top-1 reshuffle as default reranker

v0.36.2.0 made [ZeroEntropy](https://www.zeroentropy.dev/) the default for both embedding (`zembed-1` at 1280 dimensions via Matryoshka) and reranker (`zerank-2`). The benchmark numbers from the README:

- **2.2× faster** than OpenAI (442ms vs 973ms)
- **2.6× cheaper at regular pricing** ($0.05/M vs $0.13/M)
- Wins 11 of 20 head-to-head queries vs OpenAI on a real-corpus benchmark
- As a second-pass reranker, **reshuffles 60% of top-1 results** after the hybrid + RRF + graph stack

That last number is the editorial headline. The mechanical reason: hybrid ranking is locally optimal per strategy but globally suboptimal. A cross-encoder reranker reads query + each candidate document jointly with full attention. It catches the cases where vector + keyword + graph all agreed on something *semantically related but topically wrong*. The cost is +150ms p50 latency and ~$0.025/M tokens. For agent loops that do downstream LLM work after retrieval, the latency is invisible. Disable via `gbrain config set search.reranker.enabled false` if you do not want it.

The provider switch story is also worth quoting because it is the kind of thing that bites you on every other memory layer:

```bash
# Override the default ZeroEntropy embedder at init time. The model is
# 'sticky' — the schema column is sized to --embedding-dimensions and
# cannot be resized without a full reinit. Pick deliberately.
gbrain init --pglite \
  --embedding-model openai:text-embedding-3-small \
  --embedding-dimensions 1536
```

Your choice is sticky. To switch an existing brain, `gbrain reinit-pglite --embedding-model ...` for PGLite, or [the SQL recipe in `docs/embedding-migrations.md`](https://github.com/garrytan/gbrain/blob/main/docs/embedding-migrations.md) for Postgres. `gbrain config set embedding_model` is *refused* as of v0.37.11.0 "because the schema column has to resize too." That refusal is the right behaviour. It is also exactly the kind of operational footgun a hosted SaaS hides from you until the day it goes wrong and you have no recourse.

## Three named search modes

`conservative` / `balanced` / `tokenmax`. Each bundles the cost/quality knobs into one config key. `balanced` is the default with ZeroEntropy reranker on. `tokenmax` enables multi-query expansion (a Haiku-class LLM call that produces 2–3 query variants merged via RRF) at ~$0.001/query and ~200ms extra. `conservative` strips the expensive stages for cron loops where you do not want $50/day of expansion calls. The methodology is documented in [`docs/eval/SEARCH_MODE_METHODOLOGY.md`](https://github.com/garrytan/gbrain/blob/main/docs/eval/SEARCH_MODE_METHODOLOGY.md).

The mode names matter because they make cost legible. In every other agent memory system the cost knobs are hidden inside a black-box "smart" router. Here they are three named tiers and you pick one per call.

## Source-aware and intent-aware ranking

Two ranking layers sit between the raw hybrid+graph signal and the final answer, and both are worth a sentence because both are operator-visible.

The first is *source-aware ranking*. The CASE expression in [`src/core/search/sql-ranking.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/sql-ranking.ts) applies a per-source factor at SQL time. Curated content (`originals/`, `concepts/`, `writing/`) outranks bulk content (`your-openclaw/chat/`, `daily/`, `media/x/`). Hard-exclude prefixes filter at retrieval. Temporal queries bypass the boost so chat pages resurface for time-sensitive lookups. The whole thing is configurable per-call via `SearchOpts.exclude_slug_prefixes` or globally via `GBRAIN_SOURCE_BOOST`.

The second is *intent-aware query rewriting*. [`src/core/search/intent.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/intent.ts) classifies queries into `entity`, `temporal`, `event`, or `general`. Each routes through different ranking knobs: entity queries apply a higher graph-traversal weight; temporal queries bypass source-boost so chat and daily pages surface; event queries engage the timeline index; general queries hit the standard hybrid stack. The classifier is deterministic. Wrong classification degrades gracefully because the hybrid stack still works without it.

The reason both ranking layers are worth knowing: they are what makes `gbrain search "<query>" --explain` legible. The `--explain` flag shows per-stage attribution: base score, every boost that fired, what it multiplied. If your retrieval is wrong, you can see *why* it is wrong. That is the bar a serious operator sets. Hosted memory products do not show their work.

# The request lifecycle: what happens when an agent calls `whoknows`

The four retrieval strategies are a *capability list*. The actual lifecycle of a request, end to end, is how those capabilities compose into a single agent-visible answer. The piece below traces one realistic invocation: an [OpenClaw](https://github.com/openclaw/openclaw) or [Hermes Agent](https://x.com/hermes_agent) calls `mcp__gbrain__whoknows({ topic: "Acme AI Series B" })` against a `gbrain serve --http` instance. Every layer the request touches between the agent's tool call and the agent's next reasoning step is documented in the repo. The walkthrough exists because those layers are individually mundane and collectively the reason the brain works.

![Request lifecycle for mcp__gbrain__whoknows: ten stations from MCP request through the four retrieval stages to audit log and response](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-request-lifecycle-whoknows.jpg)

**Station 1, the wire.** The agent emits an MCP tool call. The transport is JSON-RPC over HTTP because that is what `gbrain serve --http` listens for; the stdio transport would carry the same payload over a pipe. [`src/mcp/http-transport.ts`](https://github.com/garrytan/gbrain/blob/main/src/mcp/http-transport.ts) terminates the connection on the configured port, enforces a one-MiB body cap, and applies two layers of rate limiting (per-IP pre-auth at 30 req/min, per-token-id post-auth at 60 req/min, both bounded LRU so an attacker-controlled key cannot grow memory unbounded). The transport file's own header comment names the security stance directly: *"Every request must include `Authorization: Bearer <token>` (except `/health`). Tokens are validated against SHA-256 hashes in the `access_tokens` table."* No open OAuth dance, no client-credentials grant, no self-service token endpoint. Tokens are minted with `gbrain auth create` and revoked with `gbrain auth revoke`, both local-only.

**Station 2, scope check.** `validateToken` SHA-256-hashes the bearer string and looks it up in the `access_tokens` table (`SELECT id, name, permissions FROM access_tokens WHERE token_hash = $1 AND revoked_at IS NULL`). The row's `permissions.takes_holders` JSONB column carries the per-token holder allow-list (`['world']` by default, which scopes private hunches out). The `find_experts` operation (the MCP name for `whoknows`) carries `scope: 'read'` in its operation definition; a token without the `read` scope returns `{"isError": true, "code": "permission_denied"}` before any SQL fires. A debounced `UPDATE access_tokens SET last_used_at = now()` writes once per token per 60 seconds (the SQL `WHERE` clause keeps it race-tolerant under concurrent load).

**Station 3, resolver routing.** The agent picked `whoknows` over `search` or `query` or `think` because of [`skills/RESOLVER.md`](https://github.com/garrytan/gbrain/blob/main/skills/RESOLVER.md), the 200-line dispatcher the agent reads before every brain-touching action. The relevant row reads literally: `"Who knows who", "relationship between", "connections", "graph query" → skills/query/SKILL.md (use graph-query)`. For a topic-routed question like "who knows about Acme AI's Series B," the agent matches the *expertise* row and selects `whoknows`. The agent itself does this; the server only sees the resolved verb. That separation is the reason the same brain can be co-driven by OpenClaw, Hermes, Claude Code, and a CLI human typing `gbrain whoknows "Acme AI Series B"` from a terminal: all four go through the same MCP dispatch.

**Station 4, dispatch and operation handler.** [`src/mcp/dispatch.ts`](https://github.com/garrytan/gbrain/blob/main/src/mcp/dispatch.ts) is the single shared dispatch layer for both transports; the file's header comment names why: *"drift between transports caused PR #483's reversed-args + missing-context bugs; this module exists to prevent that recurring."* The dispatcher validates params, builds the `OperationContext` (engine handle, logger, `auth.allowedSources` for federated reads, `tokenName` for audit, `takesHoldersAllowList` for filtering), and routes to `operations.find_experts`. The handler in [`src/core/operations.ts:2780`](https://github.com/garrytan/gbrain/blob/main/src/core/operations.ts) is short: it loads the active schema pack to determine which page types qualify as "experts" (`expertTypesFromPack(pack.manifest)` returns `[person, company]` by default but extends to whatever the pack registered), threads the source-scope (`...sourceScopeOpts(ctx)` per the v0.34.1 leak-seal), and calls into [`src/commands/whoknows.ts`](https://github.com/garrytan/gbrain/blob/main/src/commands/whoknows.ts).

**Station 5, retrieval stages.** `findExperts` calls `hybridSearch` and then applies its own ranking formula on top. The hybrid pipeline is the four-stage funnel from the previous section, but the per-stage candidate counts and the P@5 attribution become legible when you trace them inside one request.

![Four-stage retrieval funnel with P@5 attribution per stage: BM25 18 baseline, vector 18 baseline, graph hop +31.4, reranker top-K 5](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-retrieval-funnel-p5-attribution.jpg)

- **Stage 1, BM25 keyword.** [`src/core/search/keyword.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/keyword.ts) delegates to `engine.searchKeyword(query, opts)`, which runs a `tsvector @@ plainto_tsquery` against the `chunks` table's full-text index. For "Acme AI Series B" the keyword pass surfaces every page whose body literally contains those tokens. Stage budget: ~400 candidates by default (`SEARCH_KEYWORD_LIMIT`). P@5 attribution from RETRIEVAL.md's bake-off: this stage alone lands ~18 P@5 ("ripgrep BM25" in the published table). It catches names and exact strings; it cannot see synonyms.
- **Stage 2, vector search.** [`src/core/search/vector.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/vector.ts) takes the query, embeds it via the configured provider (`zembed-1` at 1280 dimensions by default, or `text-embedding-3-small` at 1536 if the operator overrode `--embedding-model` at init time), and runs a cosine-distance query against the pgvector HNSW index on `chunks.embedding`. Stage budget: ~200 candidates. P@5 attribution: ~18 by itself ("Vector-only RAG" in the table). It catches "Acme's funding round" and "the AI startup Garry mentioned Tuesday" even when the user never typed "Series B"; it drifts on factual relationships.
- **Stage 3, RRF fusion and graph hop.** The two ranked lists merge via Reciprocal Rank Fusion (constant `RRF_K = 60` per [`src/core/search/hybrid.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/hybrid.ts)). Each strategy gets one vote per candidate, weighted by its position in that strategy's ranking. Then the graph-signals stage fires: [`src/core/search/graph-signals.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/graph-signals.ts) walks the `links` table for each top-K candidate, applying three additive signals (adjacency-within-top-K at ~1.05×, cross-source adjacency at ~1.10×, session-diversification at ~0.95×). For our query, the relevant edges are `mentions`, `works-at`, `invested-in`, and `mentioned-in-conversation`: a `person` page that *invested-in* a `company` page tagged "Acme AI" is one hop from the topic; the `partner` who *advises* that company is two hops. The graph layer is where the +31.4 P@5 lift over hybrid-without-graph comes from. The graph turns "what does this page say about Acme" into "who in the brain has any first- or second-degree relationship to Acme that the agent should talk to."
- **Stage 4, ZeroEntropy reranker.** [`src/core/search/rerank.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/rerank.ts) sends the top ~120 candidates back to ZeroEntropy's `zerank-2` cross-encoder, which reads `(query, candidate)` jointly with full attention and returns a fresh relevance score. The README's published number: **the reranker reshuffles 60% of top-1 results** after the hybrid+RRF+graph stack. Cost: +150ms p50, ~$0.025/M tokens. After reshuffle, the top-K (default 5) is committed.

**Station 6, source-aware and intent-aware boosts.** Two more CASE expressions fire before the final return. [`src/core/search/sql-ranking.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/sql-ranking.ts) applies per-source factors at SQL time (curated `originals/` outranks bulk `media/x/`). [`src/core/search/intent.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/search/intent.ts) classifies the query (`entity`, `temporal`, `event`, or `general`) and either boosts the graph-traversal weight (entity) or bypasses the source boost (temporal). For "Acme AI Series B" the classifier returns `entity`; the graph-traversal weight goes up; the source boost stays in effect.

**Station 7, the `whoknows` ranking formula.** [`src/commands/whoknows.ts`](https://github.com/garrytan/gbrain/blob/main/src/commands/whoknows.ts) applies its own scoring on top of `hybridSearch`'s output. The formula, locked by ENG-D1 and quoted from the file header: `score(page) = expertise × max(0.1, recency_decay) × (0.5 + 0.5 × salience)`, where `expertise = log(1 + chunk_match_count)`, `recency_decay = exp(-days_since_effective_date / 180)`, and `salience` is `pages.salience_score` already on `[0,1]`. The 0.1 floor on `recency_decay` is "multiplicative-zero defense", keeping cold-start people visible. The 0.5 base on the salience term means missing salience reads as neutral, not as zero. Both choices are documented inline.

**Station 8, auto-link writeback (optional, on miss).** If the response references a page the brain has not seen before (uncommon for `whoknows`, common for `capture`), `extractEntityRefs` fires on write and an `INSERT ... SELECT FROM unnest(...) ON CONFLICT DO NOTHING` adds typed edges in one SQL call. For a pure read like `whoknows`, this station is a no-op; for `put_page`, it is where the graph grows on every write at zero LLM cost.

**Station 9, audit log.** Before the response leaves the server, [`src/core/audit/audit-writer.ts`](https://github.com/garrytan/gbrain/blob/main/src/core/audit/audit-writer.ts) appends a JSONL line (`token_name`, `operation`, `latency_ms`, `status`) to `~/.gbrain/audit/<prefix>-YYYY-Www.jsonl` (ISO-8601 weekly rotation), and an `INSERT INTO mcp_request_log` row lands in Postgres for cross-token analytics. The audit writer's design constraints, quoted from the file: *"NO behavior change for existing consumers. Filename format, JSONL line format, mkdirSync recursive, appendFileSync utf8, stderr-on-failure semantics MUST be byte-identical so the existing tests pass unchanged."* Failures go to stderr but never throw. The doctor's `content_sanity_audit_recent` check reads this exact file.

**Station 10, response.** The dispatcher serialises the `WhoknowsResult[]` array to MCP's `{ content: [{ type: 'text', text: '...' }], _meta?: {...} }` shape. The `_meta.brain_hot_memory` slot (v0.31's "eD3" wave) lets the server inject server-supplied metadata that capable clients (Claude Code, Claude Desktop) read; older clients ignore unknown `_meta` fields safely. The agent receives the response and decides what to do next: usually it composes the names into a tool-call sequence ("call `query` on each of the top three", "open a chat with each", "ask the human to pick").

**The spawn variant: when the request kicks off a background job.** Some MCP ops do not return inline; they spawn into the Minions queue. `embed_all`, `extract_facts`, `sync` all submit a job row in `minion_jobs` with `status='waiting'`, and the response is `{ job_id, status }`. The Minions worker (`gbrain jobs work` or its supervisor wrapper) picks the job up, leases it (`UPDATE minion_jobs SET lock_token=$1, lock_until=now()+'30s' WHERE id=$2`), runs the handler, and updates progress every step. The agent polls `gbrain jobs status $JOB_ID` or subscribes via the `_meta` channel. The architectural choice that makes this honest: the spawn path uses *exactly the same dispatcher and the same audit log* as the synchronous path; the only difference is where the response gets composed (inline vs. from the job row when it lands). PR #483's reversed-args bug class cannot recur because both transports share dispatch.

**The cycle variant: when the cron does the same operations autonomously.** The 66 cron jobs in Garry's production brain are not a separate code path. Each cron handler calls the same operations with `remote: false` and a local source context. `cycle.sync` calls the same `put_page` that an agent's `capture` does; `cycle.extract_facts` calls the same `## Facts` parser that an interactive `gbrain extract` runs; `cycle.embed` calls the same gateway function `embed.ts` exposes to the keyword and vector stages above. The protected phases (`synthesize`, `patterns`, `consolidate`) refuse MCP submission, so a remote agent cannot silently submit a cost-blowing LLM loop, but local cron can. The cycle is *the same lifecycle without a human in the seat*. That is the architecturally honest answer to "why does the cron feel like the brain dreaming": because every cron job is replaying a real agent request shape, with the same audit log, the same scope check (defaulting to `localOnly` for cycle phases), and the same retrieval pipeline.

**Reading the headline numbers against the lifecycle.** The published BrainBench result is **P@5 49.1%**, with the lift attributed primarily to the graph stage (+31.4 over hybrid-without-graph) and secondarily to the reranker (60% top-1 reshuffle). Mapping those numbers back onto the stations: roughly half of the headline P@5 number comes from the *graph hop at Station 5*, roughly a third comes from the *reranker pass at the end of Station 5*, and the remainder is the BM25-and-vector baseline that any hybrid system gets. The whoknows-specific formula at Station 7 adds a smaller per-result boost that is invisible in the corpus-wide BrainBench number but visible in the [`gbrain eval whoknows`](https://github.com/garrytan/gbrain/blob/main/test/fixtures/whoknows-eval.jsonl) fixture's 10-query routing check. R@5 of 97.9% is mostly the BM25 + vector union: by the time the union of stages 1 and 2 has 600 candidates, the truth is almost always in there; what graph and reranker buy is rearranging the truth into the top five.

The lifecycle is mundane on purpose. There is no orchestrator daemon, no live in-memory session object, no graph runtime; the engine is `BrainEngine`, the dispatcher is a switch on operation name, the storage is Postgres, the audit is JSONL. Anyone who can read a TypeScript file and a SQL schema can read the entire trace. That is what makes the +31.4 P@5 lift a *property of the open code* rather than a number on a vendor slide.

# The cron-driven cycle: wake up smarter than yesterday

Every gbrain brain runs a nightly *cycle*, and the cycle is what makes the brain feel alive. The phases, in order:

1. **sync**: walk the markdown repo, diff against the DB, write the additions and deletions.
2. **extract**: parse `## Facts` fences, pull typed claims, populate first-class columns (`mrr=50000`, `arr=2000000`, `team_size=12`).
3. **embed**: send any chunks lacking embeddings to the configured embedding provider, write vectors.
4. **consolidate**: merge duplicate people pages, dedupe takes, write `valid_until` on chronologically-superseded facts via semantic upsert on `(page_id, claim, since_date)`.
5. **synthesize**: write new derived notes (per-person summary cards, weekly digests, prep documents).
6. **patterns**: find clusters across the corpus (e.g. "you keep mentioning $thing in different contexts").

![The nightly cron cycle: sync → extract → embed → consolidate → synthesize → patterns](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-nightly-cycle.jpg)

Three of these phases are PROTECTED. Per the v0.36.4.0 changelog, the cycle phases `synthesize`, `patterns`, and `consolidate` are flagged as protected so that an MCP-connected remote agent cannot silently submit them. The reasoning is cost: those three phases each kick off LLM tool loops that can burn Anthropic or OpenAI credits at meaningful scale. Only trusted local callers can submit them. MCP cannot.

That decision is small in the code but large in security model. The same scope system has 74 MCP operations annotated with `read` / `write` / `admin` / `localOnly` per [v0.40.8.0's behavioural test wave](https://github.com/garrytan/gbrain/blob/main/CHANGELOG.md). The localOnly flag is no longer a convention; there is a behavioural test that *proves* a localOnly op never reaches the HTTP wire. If you have ever had an agent quietly burn $400 of API spend in a runaway loop, you understand why this matters. (We covered the genre in our [openclaw-vs-hermes post](https://thedeepfeed.com/posts/2026-05-11-openclaw-vs-hermes-agent-personal-ai-runtime-wars/) and it is the dominant operational risk in remote-agent setups.)

## The Minions queue

Heavy work in gbrain runs on a Postgres-native job queue called Minions, BullMQ-shaped, durable. Minions are LLM tool loops that survive crashes via two-phase pending→done persistence. Shell jobs get audit. Child jobs cascade timeouts. Outbound providers have rate leases. Attachments live in S3 or Supabase storage. The load-bearing property: a Minion job that was halfway through when the host crashed comes back as `pending` on restart and resumes. That is the bar to clear before you trust an agent to operate against your brain unattended. "Fire-and-forget Promise" gets you maybe-once delivery. Minions gets you at-least-once with idempotency keys.

### The crash-mid-job recovery sequence

The mechanism is worth walking through in detail because it is the property every "agent memory" system claims and almost none actually deliver. The Minions worker holds a job via a *lock token* and a *lock until* timestamp. The lock renewal loop runs in the background. Every ~10 seconds the worker writes `lock_until = NOW() + 30s` to keep the job claimed. If the worker process dies, the lock is not renewed; the `lock_until` field stays at whatever it was at the moment of death. Other workers can claim the job once `lock_until < NOW()`. That is the whole at-least-once primitive in three columns of a Postgres table.

![Minions queue: a job claimed by a worker, the worker crashes mid-execution, lock expires, another worker resumes from the last checkpoint — the at-least-once primitive in action](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-minions-recovery.jpg)

The full sequence, traced from a real crash:

```
T+0s    Worker A claims job_id=12345
        UPDATE minion_jobs
           SET status='active',
               lock_token='wA-abc12345',
               lock_until=NOW() + INTERVAL '30 seconds'
         WHERE id=12345 AND status='waiting'
        ↓
        Worker A starts the LLM tool loop. First tool call goes out.

T+10s   Worker A's renewLock() loop runs.
        UPDATE minion_jobs
           SET lock_until=NOW() + INTERVAL '30 seconds'
         WHERE id=12345 AND lock_token='wA-abc12345'
        ↓
        First tool result lands. Worker writes a progress record:
        INSERT INTO minion_progress (job_id, step, payload) VALUES (...)

T+18s   Worker A's host kernel panics. Process is gone. PID is reclaimed.
        Lock renewal loop dies with the process.
        lock_until in DB is stuck at T+10s + 30s = T+40s.

T+40s   Lock expires. Job is reclaimable.

T+45s   Worker B's poll loop picks the job up.
        SELECT * FROM minion_jobs
         WHERE status='active' AND lock_until < NOW()
         FOR UPDATE SKIP LOCKED
        ↓
        Worker B sees minion_progress already has one row for this job.
        It loads the saved state, resumes from the second tool call,
        finishes the loop, transitions status='done'.
```

The reason this works is that *progress is durable, not the worker's in-memory state*. The worker records every step's input + output + token spend to `minion_progress` as it goes. When Worker B picks the job up, it does not start from scratch; it reads the progress rows, reconstructs the tool-loop state, and continues where Worker A left off. The cost is one INSERT per tool step, which is trivial.

A worked example that the operator actually runs:

```bash
# Submit an embed-all job
gbrain embed --background
# → {"job_id": 12345, "status": "waiting"}

# Watch it become active
gbrain jobs show 12345 --watch
# → status=active, lock_token=wA-abc12345, lock_until=2026-05-24T18:34:10Z
# → progress: step 23 of ~180, $0.42 spent

# Simulate a crash: kill the worker process
ps aux | grep "gbrain jobs work" | grep -v grep | awk '{print $2}' | xargs kill -9

# After ~30s the lock expires
gbrain jobs show 12345
# → status=active (still), lock_until=2026-05-24T18:34:10Z (in the past)

# Start a new worker
gbrain jobs supervisor --concurrency 2 &

# Watch resumption
gbrain jobs show 12345 --watch
# → status=active, lock_token=wB-def67890 (different worker, same job)
# → progress: step 24 of ~180 (RESUMED at the next unfinished step)
# → status=done, total spent $1.83, started 18:30:10Z, finished 18:51:22Z
```

The properties this gives an operator: **at-least-once delivery** (a crash never silently drops a job); **idempotent steps** (each tool call has an idempotency key; if a duplicate is replayed during recovery, the provider returns the cached result and no duplicate side-effects fire); **bounded retry** (`max_attempts` with exponential backoff, default 3 attempts); **stall detection** (a job whose `lock_until` keeps expiring without progress gets dead-lettered to a `failed` queue with the last error). The dead-letter queue is `gbrain jobs failed --json`; you read it the morning after the cron loop.

The pieces compose. Pause and resume sit on the same lock mechanism: pausing an active job clears `lock_token` and `lock_until`, the worker's renewLock returns false, the worker stops gracefully, the job's progress is preserved, resume transitions `paused → waiting` and the next worker picks it up from the last checkpoint. Job groups (parent + child Minions with cascading timeouts) compose by sharing a `parent_job_id` column; when the parent's deadline passes, every child gets cancelled in the same SQL update. None of this exists in a "fire a Promise and hope" architecture. All of it exists because Minions chose to make Postgres the queue.

The Minions worker lives or dies on the *supervisor*. `gbrain jobs supervisor` is an auto-restarting wrapper around `gbrain jobs work`. It writes a PID file, restarts the worker on crash with exponential backoff (1s → 60s cap), emits lifecycle events to an audit file, and drains gracefully on SIGTERM (35s worker-drain window before SIGKILL). Exit codes are documented so agents can branch on them: 0 = clean shutdown, 1 = max crashes exceeded (page a human), 2 = another supervisor holds the PID lock (safe to ignore), 3 = PID file unwritable. Platform-level supervision (systemd, Fly, Render) sits on top of `gbrain jobs supervisor` to handle host-level failures. The full deployment recipe lives in [`docs/guides/minions-deployment.md`](https://github.com/garrytan/gbrain/blob/main/docs/guides/minions-deployment.md).

In ops terms, the cycle plus Minions is what makes the brain wake up smarter than the day before. You can read every phase's job state with `gbrain queue list`, replay failures with `gbrain queue retry`, and inspect what a Minion was thinking with `gbrain queue inspect <job_id>`. Those are operator surfaces you do not get from a hosted memory service.

## The `gbrain think` verb in practice

The capability that ties the cycle to the agent surface is `gbrain think` (v0.40.2.0). The verb grounds *temporal answers* in the typed-claim timeline. You ask "when did Marco last switch jobs" and the answer comes back rooted in the chronological timeline of `job_change` event-shaped rows the brain extracted via `extract_facts`. You ask "what was the ARR in March" and the answer comes back rooted in metric-shaped rows in the same table (`facts.event_type` carries `'meeting'`, `'job_change'`, `'location_change'` alongside the metric rows; migration v82 added the nullable column).

The intent classifier is a regex pass. There are three intents: `temporal`, `knowledge_update`, `other`. The `'other'` fast path short-circuits with zero extra SQL, which is the design that keeps `gbrain think` cheap for queries that are not temporal. Default is on. Flip `think.trajectory_enabled=false` to opt out. Debug with `GBRAIN_THINK_DEBUG=1 gbrain think "..."` to see the spliced prompt.

The LongMemEval benchmark integration ships with a methodology note: `methodology_note: extractor=haiku-preprocess-full-haystack-v1`. Published scores are "gbrain + Haiku-preprocess pipeline" vs "gbrain alone", not directly comparable to baseline LongMemEval numbers without that note. That disclosure is editorially significant. Most memory-system benchmarks in 2026 quietly do an extractor pass and publish the result as "ours". gbrain names the pipeline explicitly. The reader who cares about the methodology can read it. The reader who does not still gets the right scoreboard. Both are served.

# The release cadence: April to May 2026, in one image

The other thing the changelog tells you is the *velocity*. The repo opened on April 5 2026. Seven weeks later it is at v0.40.9.0. That is roughly one minor version per week, with material capability landing in nearly every release.

| Version | Date | What landed |
|---|---|---|
| Initial public commit | April 5 2026 | Repo opens MIT, 22 default page types, hybrid search, auto-link, 43 skills |
| First 24 hours | April 5 to April 6 2026 | ~5,000 GitHub stars |
| v0.35.7 | mid-April | Temporal trajectory, founder scorecard, `## Facts` fence with typed-metric columns |
| v0.36.0.0 | late April | ZeroEntropy reranker as the default for `balanced` mode; legacy skillpack model retired |
| v0.36.2.0 | late April | ZeroEntropy made default *embedder* too; bring-your-own-key wired into `gbrain init` |
| v0.36.4.0 | early May | `gbrain doctor --remediate` with cost-cap and dependency planner; autopilot 5-minute tick |
| v0.38.x | early May | `gbrain capture` verb; write-through `put_page` plumbing |
| v0.39.1.0 | mid-May | Schema-pack engine ships; hand-editable but not yet agent-reachable |
| v0.40.2.0 | mid-May | `gbrain think` for temporal queries against the typed-claim timeline |
| v0.40.4.0 | mid-May | Per-query graph signals (adjacency, cross-source, session demote); `--explain` flag |
| v0.40.7.0 | May 22 2026 | Agent-authored schema over MCP: 14 CLI verbs, 9 MCP ops, `withMutation` skeleton |
| v0.40.8.0 | May 23 2026 | Behavioural coverage wave: doctor, operations trust-boundary, cycle phases |
| v0.40.9.0 | May 23 2026 | Current at time of writing |

That cadence is the second editorial signal. The first is the precision of the production numbers. The second is the rate of change between them. A repo that ships agent-authored schema in week six, behavioural-coverage for the MCP trust boundary in week seven, and protected cycle phases for cost control along the way, is one being driven hard by an operator who is also the user. Garry is not shipping to a board. He is shipping to his own brain. The pace is set by the pace of his own pain.

A note for downstream operators reading the changelog: "frequent breaking changes" is real. The schema-pack engine landed in v0.39.1.0 and was reworked in v0.40.7.0. The legacy skillpack install model retired in v0.36.0.0. `gbrain config set embedding_model` is refused as of v0.37.11.0 because the schema column has to resize. None of those breaks happens silently; they are flagged in the changelog with migration commands (`gbrain skillpack migrate-fence`, `gbrain reinit-pglite`). They do break automation that has not read the changelog. The Vectorize review's warning ("frequent breaking changes") is a fair one. The compensation is that the rate of breaking equals the rate of capability.

# `gbrain doctor --remediate`: the autopilot pattern

![Doctor remediate: a dependency-ordered chain of fixes flowing through a cost-capped funnel, refusing to spend past the $5 cap](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-doctor-remediate.jpg)

The most operator-flattering single command in gbrain is this:

```bash
# --remediate flips doctor from read-only diagnostic to active fixer.
# --yes auto-confirms each step (safe because of --max-usd below).
# --target-score 90 stops fixing once the brain is "healthy enough".
# --max-usd 5 is the cost cap; the dependency planner refuses to submit
# any step that would push spend past $5.00 cumulative for this run.
gbrain doctor --remediate --yes --target-score 90 --max-usd 5
```

It does the loop you would otherwise run by hand. It computes a dependency-ordered plan (sync before extract, embed after consolidate), submits each step as a Minion job, re-checks the score between every step, and *refuses* to spend past your cost cap. Cron can drive it unattended. The plan can be previewed with `gbrain doctor --remediation-plan --json` so you know exactly what it will do before you let it run.

The thing that makes this work in production is the cost cap. Every alternative system either has no cost cap (you find out you spent $200 when the bill arrives) or has a hosted-side cost cap that is the vendor's problem to enforce. Here it is enforced inside the dependency planner: a step that would push spend past the cap is not submitted, the doctor exits with a clear "would have spent $X.YZ, cap is $5.00" message, and you can either bump the cap or accept the partial remediation.

The doctor itself shipped with a behavioural-coverage wave in [v0.40.8.0](https://github.com/garrytan/gbrain/blob/main/CHANGELOG.md). Per the changelog, the doctor check-building was extracted from the CLI entry point so behavioural tests can drive it directly. `test/doctor-behavioral.test.ts` (13 cases) pins the aggregation math, the `--fast` skip set, the snapshot of load-bearing check names. `test/doctor-cli-smoke.serial.test.ts` (1 case) is a subprocess smoke spawning the real CLI against a fresh PGLite tempdir brain and asserting the JSON envelope's `schema_version === 2`, status enum, and check list integrity. That is the test discipline that lets a cron job trust `gbrain doctor --json` after a refactor.

The doctor is also the *interface* for the autopilot daemon. Autopilot's 5-minute tick reads the doctor score, picks remediation handlers proportional to severity (small problems get targeted handlers, big problems get the full cycle), and on a healthy brain *sleeps for 60 minutes* instead of grinding through synthesize+patterns+embed every tick. The reason that matters in dollars: a brain at 95/100 health that runs synthesize every five minutes burns ~$200/month in LLM costs that a 60-minute backoff avoids. The economics fall out of the design.

# Schema-author: the v0.40.7.0 capability that makes the brain agent-shaped

The hardest part of any personal memory system is *what types of things live in it*. Default schemas always lie. A founder's brain has investors and portcos; a researcher's brain has papers and labs; a lawyer's brain has cases and depositions. None of those are the default. Every previous "personal knowledge graph" tool punted this problem by either (a) refusing to have types and giving you tags, or (b) requiring you to hand-edit YAML.

[v0.40.7.0](https://github.com/garrytan/gbrain/blob/main/CHANGELOG.md) shipped a different answer: *agents author the schema themselves over MCP*. The full feature is documented in [`docs/what-schemas-unlock.md`](https://github.com/garrytan/gbrain/blob/main/docs/what-schemas-unlock.md) (the why) and [`docs/schema-author-tutorial.md`](https://github.com/garrytan/gbrain/blob/main/docs/schema-author-tutorial.md) (the how). The CLI ships fourteen new `gbrain schema` verbs:

| Verb | What it does |
|---|---|
| `gbrain schema fork <base> <name>` | Fork the bundled pack to your own |
| `gbrain schema use <name>` | Activate a pack |
| `gbrain schema active --json` | Show the active pack identity |
| `gbrain schema add-type <name> --primitive ... --prefix ... --extractable --expert` | Add a typed page kind |
| `gbrain schema remove-type` | Inverse |
| `gbrain schema add-alias <type> <alias>` | Aliasing |
| `gbrain schema add-link-type --page-type <T> --target-type <T>` | Typed edges |
| `gbrain schema stats --json` | Coverage report |
| `gbrain schema sync --apply` | Backfill `page.type` on all matching prefixes |
| `gbrain schema lint --with-db` | Validate against your DB |
| `gbrain schema review-orphans --limit 50 --json` | Find untyped pages that share an obvious prefix |
| `gbrain schema graph` | What does my schema look like? |
| `gbrain schema downgrade` | Restore the previous active pack |
| `gbrain schema detect` | The agent's "should I propose a type?" surface |

And it ships nine new MCP operations including `schema_apply_mutations` (admin scope, NOT localOnly). That last bit matters: a remote agent on a different machine reaches the schema-author surface over normal HTTPS MCP with its OAuth client credentials. The agent's `client_id` is captured in the audit log as `actor: mcp:<clientId8>`.

![Agent-authored schema: agent proposes type, validates, applies atomically, backfills](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-schema-mutations.jpg)

The eight ordered safety steps inside the `withMutation` skeleton are:

1. **bundled-guard**: refuse to mutate a bundled pack; you must fork first.
2. **lock**: per-pack `O_CREAT|O_EXCL` atomic lock (not the TOCTOU `existsSync+writeFileSync` pattern). TTL refresh every 10 seconds. `--force` means "steal stale lock", not "skip locking".
3. **read**: load the current pack into memory.
4. **mutate**: apply the proposed mutation in memory.
5. **validate**: catch dangling references *pre-write*. A `add-link-type` whose `target-type` does not exist fails here, not at apply time.
6. **atomic write**: write to a tempfile, fsync, rename. A crash mid-write never leaves the pack half-written.
7. **audit**: privacy-redacted log at `~/.gbrain/audit/schema-mutations-YYYY-Www.jsonl`. Type names are sha8-hashed, prefixes truncated to first segment only, so a leaked screenshot cannot reveal sensitive taxonomy.
8. **invalidate**: cross-process cache invalidation via stat-mtime TTL inside `loadActivePack`. Run `gbrain schema add-type` from one terminal; the autopilot daemon picks it up within one second without restart.

The most interesting line in that list is the audit log redaction. Type names like `personal/oncology/cancer-recurrence/` or `legal/depositions/coplaintiff-3/` should not appear in plaintext in an audit log that might end up in a screenshot. They are sha8-hashed. The audit trail is forensic without being leaky.

The other thing worth saying about schema-author is what *did not* ship. Per [what-schemas-unlock.md](https://github.com/garrytan/gbrain/blob/main/docs/what-schemas-unlock.md), v0.39.1.0 already shipped the schema-pack engine. You could fork and edit pack.yaml by hand. What was missing: atomic file locks, audit log, MCP exposure, pack-aware wiring in the query path. The cathedral was built but unreachable from outside. v0.40.7.0 closed those gaps. The big internal one is **T1.5 wiring**: a custom `researcher` type marked `--expert` *now actually surfaces in query results*. Pre-v0.40.7.0 the query path read hardcoded `['person', 'company']` and your custom types silently never matched. That is the kind of bug that kills schema systems in practice, and it is now fixed and pinned by tests.

# Ten use cases: what schemas make possible

This is the centrepiece. The reason schema-author is the headline v0.40.7.0 feature is that it lets you *shape* gbrain to match the work you actually do. The default 22 universal types ship as `gbrain-base`. They cover the universal shapes: `person`, `company`, `meeting`, `note`, `daily`, `calendar-event`, and so on. That is enough to start. But your brain is yours, and its actual shape is not the default shape. Below are ten worked vertical scenarios. Each one is real work for a real persona, expressed as the exact CLI commands that produce the schema, the typed facts the brain will extract, and the killer query that vector-only RAG cannot answer.

![Ten use cases as a 2x5 grid: founder, research, legal, sales, recruiting, medical, code-review, journalism, personal-crm, team](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-ten-use-cases.jpg)

## 1. The Founder Ops Brain

**Persona.** A solo founder or seed-stage operator with ~500 markdown files mixing leads, portfolio companies, deal notes, intros, and follow-ups. Free-form prose. No system. Queries are all "wait, who introduced me to that fintech founder again?" followed by twenty minutes of scrolling.

**Problem.** Every interaction with an investor or portfolio company is in your notes, but the brain cannot see the structure. There is no concept of *deal stage*. There is no way to ask "which of my portcos crossed $1M ARR this quarter?" without grep'ing each file.

**Custom types.** `lead`, `investor`, `portco`, `deal-stage`.

**Extractable facts.** `arr=`, `mrr=`, `team_size=`, `raise=`, `valuation=`, `stage=`, `runway_months=`.

**The setup.**

```bash
# 1. Fork the bundled gbrain-base pack so you can mutate it safely.
#    'mine' is your editable pack; gbrain-base stays untouched.
gbrain schema fork gbrain-base mine
gbrain schema use mine

# 2. Add the four founder-specific entity types. The --prefix tells gbrain
#    which directory in your markdown repo this type lives under. --expert
#    surfaces the type in query results (post-T1.5 wiring, v0.40.7.0+).
#    --extractable pulls typed facts (arr=, mrr=, valuation=) on every write.
gbrain schema add-type lead       --primitive entity --prefix people/leads/         --expert
gbrain schema add-type investor   --primitive entity --prefix people/investors/     --expert --extractable
gbrain schema add-type portco     --primitive entity --prefix companies/portco/     --expert --extractable
gbrain schema add-type deal       --primitive entity --prefix companies/deals/      --extractable

# 3. Declare the typed edges that make the graph queryable. Each link-type
#    declares which page-type can point to which target-type. After this,
#    a query like 'who invested in portco X?' becomes a SQL join, not a grep.
gbrain schema add-link-type invested-in --page-type investor --target-type portco
gbrain schema add-link-type intro-from  --page-type lead     --target-type lead
gbrain schema add-link-type passed-on   --page-type investor --target-type deal
gbrain schema add-link-type led-by      --page-type deal     --target-type investor

# 4. Backfill: walk the existing markdown repo and assign the right type
#    to every page whose path matches a registered prefix.
gbrain schema sync --apply
```

**The weekly briefing skill.** Schedule a Sunday-evening cron that runs:

```bash
gbrain skill briefing-weekly \
  --since "7 days ago" \                       # window: facts added this past week
  --types portco,deal \                        # restrict to founder-specific types
  --emit reports/weekly/$(date +%Y-W%U).md     # write the briefing back into the repo
```

The skill walks the portco graph, pulls trajectory facts (`mrr=`, `arr=`) since the last briefing, surfaces anything with `valid_until` set last week (a fact that got superseded means a change worth highlighting), and writes Monday's prep doc.

**The killer query.**

```bash
gbrain query "which seed-stage portcos crossed $1M ARR in Q1 and who led their next round?"
```

Vector-only RAG cannot answer this. The query is a two-hop graph walk (`portco ── arr_fact ──> Q1 ──> $1M+` then `portco ── next_round ──> deal ── led-by ──> investor`). It is two SQL joins and a fact filter in gbrain. It is grep theater in flat-markdown.

## 2. The Research Brain

**Persona.** A PhD student or staff scientist with thousands of arxiv papers, lab notes, citation lists, and result writeups. Two years of slowly forgetting where you read each idea.

**Problem.** Google Scholar is search-by-author-and-keyword. Your reading list is markdown. The connection between "Smith et al. 2024" and "the paper Alice was complaining about last March" lives only in your head, and only sometimes.

**Custom types.** `paper`, `researcher`, `lab-result`, `experiment`, `dataset`.

**Extractable facts.** `arxiv_id=`, `cited_by_count=`, `published_date=`, `model_size=`, `dataset_size=`, `reported_metric=`.

**The setup.**

```bash
# Annotation-primitive types hold structured content extracted on write
# (arxiv_id=, cited_by_count=, reported_metric=). Entity-primitive types
# hold the people, datasets, and labs the annotations reference.
gbrain schema add-type paper        --primitive annotation --prefix research/papers/    --extractable
gbrain schema add-type researcher   --primitive entity     --prefix people/researchers/ --expert
gbrain schema add-type lab-result   --primitive annotation --prefix research/results/   --extractable
gbrain schema add-type experiment   --primitive annotation --prefix research/experiments/ --extractable
gbrain schema add-type dataset      --primitive entity     --prefix research/datasets/

# Citation-graph edges. 'cites' is the load-bearing one for the killer query
# below — depth-2 traversal lets you walk two citation hops in one SQL plan.
gbrain schema add-link-type authored   --page-type researcher --target-type paper
gbrain schema add-link-type cites      --page-type paper      --target-type paper
gbrain schema add-link-type uses       --page-type paper      --target-type dataset
gbrain schema add-link-type reproduces --page-type lab-result --target-type paper

gbrain schema sync --apply
```

**The killer query.**

```bash
# Walk the citation graph from one anchor paper, two hops deep, then filter
# by dataset usage AND researcher affiliation in a single traversal.
gbrain graph-query papers/scaling-laws-of-diffusion --type cites --depth 2 \
  --filter "uses=imagenet AND researcher.affiliation=Anthropic"

That is "who works on diffusion at Anthropic and cites the scaling-laws paper, within two hops?" It returns researcher pages directly. Vector search returns essays about diffusion. The graph returns people you should email. The [what-schemas-unlock.md doc](https://github.com/garrytan/gbrain/blob/main/docs/what-schemas-unlock.md) puts it bluntly: "Suddenly 'show me papers that cite this work AND use the same dataset' is a `gbrain graph-query` traversal, not 30 minutes in Google Scholar."

## 3. The Legal Brain

**Persona.** A solo or boutique-firm litigator with hundreds of case files, depositions, motions, and precedent citations. The kind of practice where "the meaning of a number depends on its type" is the daily reality (a $5M judgment against a $2M case strategy threshold is a comparison the brain can do, but only if both numbers are typed).

**Problem.** Westlaw is great for case law search. It is useless for *your* case files. The conflict-check across your own depositions, the cross-citation between motions you've filed, the running tally of how a precedent is faring on appeal: all of it lives in your head and nowhere else.

**Custom types.** `case`, `motion`, `deposition`, `precedent`, `judgment`.

**Extractable facts.** `damages=`, `filed_date=`, `judge=`, `jurisdiction=`, `case_number=`, `outcome=`.

**The setup.**

```bash
# 'case' and 'precedent' are entities — long-lived containers of facts.
# 'motion' and 'deposition' are annotations — extractable structured rows
# (damages=, filed_date=, judge=, jurisdiction=, outcome=).
gbrain schema add-type case       --primitive entity     --prefix legal/cases/       --extractable --expert
gbrain schema add-type motion     --primitive annotation --prefix legal/motions/     --extractable
gbrain schema add-type deposition --primitive annotation --prefix legal/depositions/ --extractable
gbrain schema add-type precedent  --primitive entity     --prefix legal/precedents/  --expert

# 'contradicts' is the load-bearing edge for the impeachment-prep query
# below: pairwise comparison of typed depositions with the same subject.
gbrain schema add-link-type filed-in --page-type motion --target-type case
gbrain schema add-link-type cited-in --page-type precedent --target-type motion
gbrain schema add-link-type contradicts --page-type deposition --target-type deposition

gbrain schema sync --apply
```

**The killer query.**

```bash
gbrain eval suspected-contradictions --types deposition --case acme-v-widget
```

The suspected-contradictions cycle phase samples retrieval pairs, applies a date pre-filter, runs a query-conditioned LLM judge, persists a cache. On a deposition set it surfaces: depositions taken three months apart by the same witness, with substantively different claims about the same event. That is gold for impeachment prep. There is no vector-search formulation of this query. It is structurally a *pairwise comparison of typed pages with a contradiction predicate*. Either you have the type system or you do not.

The doc names this explicitly: "This isn't possible without typed page kinds. You can write the same prose in any note-taking app. Only gbrain treats the numbers as comparable across pages of the same type."

## 4. The Sales Brain

**Persona.** A B2B account executive with thirty active accounts, three hundred contacts, a year of call notes, and a CRM that everyone in the company copies into but nobody reads from.

**Problem.** Salesforce is the system of record nobody trusts because the data is six weeks stale. Your real CRM is the markdown notes from every Granola transcription and every voice memo. You want the briefing for tomorrow's pipeline call to actually reflect what you said yesterday.

**Custom types.** `account`, `contact`, `deal`, `opportunity`, `call`.

**Extractable facts.** `arr_potential=`, `stage=`, `close_date=`, `last_touched=`, `decision_maker=`.

**The setup.**

```bash
# 'call' is temporal — chronologically-ordered rows that the trajectory
# engine can replay ("Acme's ARR has slipped from $1.2M to $980K over
# four monthly check-ins"). The other three are entities + extractable.
gbrain schema add-type account     --primitive entity     --prefix sales/accounts/      --extractable --expert
gbrain schema add-type contact     --primitive entity     --prefix people/contacts/     --expert
gbrain schema add-type deal        --primitive entity     --prefix sales/deals/         --extractable
gbrain schema add-type call        --primitive temporal   --prefix sales/calls/         --extractable

# Three edges close the loop: contact ──works-at──> account ──opportunity-with──> deal
# Add 'spoke-with' on every call to wire call-notes back into the account graph.
gbrain schema add-link-type works-at      --page-type contact --target-type account
gbrain schema add-link-type opportunity-with --page-type deal --target-type account
gbrain schema add-link-type spoke-with    --page-type call    --target-type contact

gbrain schema sync --apply
```

**The 8am daily prep.** Wired as a Minion job on cron:

```bash
gbrain skill sales-prep-daily \
  --schedule "0 8 * * 1-5" \                  # every weekday at 8am
  --window "24h" \                            # rolling 24h window of new facts
  --filter "stage IN (negotiation,proposal)" \   # only late-stage deals matter for morning prep
  --emit daily/$(date +%Y-%m-%d)-pipeline.md
```

**The killer query.**

```bash
gbrain query "which accounts have a champion who hasn't been touched in 21 days AND have a deal in negotiation stage?"
```

That's a join across `contact.last_touched`, `contact ── works-at ──> account`, and `deal ── opportunity-with ──> account WHERE stage='negotiation'`. Vector RAG returns chunks about negotiation. The graph returns six accounts you should email today. The brain's `find_trajectory` MCP op exposes the same metric history to the agent so the morning prep can include a sentence like "Acme's ARR has slipped from $1.2M to $980K over the past four monthly check-ins" without you having to write a single SQL query. That sentence is the difference between a salesperson who walks into the call cold and one who walks in armed.

## 5. The Recruiting Brain

**Persona.** A technical recruiter or founding-team hiring manager, twenty open roles, four hundred candidates in flight, dozens of interview loops.

**Problem.** ATS systems hold the canonical state but capture none of the texture. Your real signal is the slack threads, the Granola transcripts, the offhand "Alice was great in the system design round but B+ on coding." That signal lives in markdown. You want it joinable.

**Custom types.** `candidate`, `role`, `interview-loop`, `feedback`.

**Extractable facts.** `level=`, `years_experience=`, `signal_score=`, `decision=`, `loop_outcome=`.

**The setup.**

```bash
# 'interview-loop' is temporal (an ordered sequence of rounds with outcomes).
# 'feedback' is annotation (structured rows: signal_score=, decision=).
# This lets the graph hold both the chronology of the loop AND the granular
# per-round signal — what ATS systems flatten into a single boolean.
gbrain schema add-type candidate      --primitive entity     --prefix people/candidates/ --extractable --expert
gbrain schema add-type role           --primitive entity     --prefix hiring/roles/      --expert
gbrain schema add-type interview-loop --primitive temporal   --prefix hiring/loops/      --extractable
gbrain schema add-type feedback       --primitive annotation --prefix hiring/feedback/   --extractable

# Four edges close the candidate → role → loop → feedback chain.
# 'interviewed-by' joins feedback back to the person graph (your existing
# default 'person' type from gbrain-base) so you can ask "who interviewed
# every rejected staff-level candidate this quarter?"
gbrain schema add-link-type applied-for     --page-type candidate --target-type role
gbrain schema add-link-type loop-for        --page-type interview-loop --target-type candidate
gbrain schema add-link-type feedback-from   --page-type feedback --target-type interview-loop
gbrain schema add-link-type interviewed-by  --page-type feedback --target-type person

gbrain schema sync --apply
```

**The killer query.**

```bash
gbrain query "candidates rejected for the staff role in the past quarter whose feedback mentions 'good ICs but not at our level' — should we re-engage for senior?"
```

That is a multi-hop walk across `feedback ── interviewed-by ──> person`, filtered by feedback text, joined back through `loop-for ──> candidate`, filtered by `applied-for.role.level`. The brain remembers every rejected candidate's feedback texture. The ATS has only the `rejected` checkbox.

## 6. The Medical / Clinical Brain

![Five operator personas as torn-paper portraits: founder, researcher, lawyer, sales lead, recruiter — each with their own custom-typed brain pack](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-personas-first-five.jpg)

**Persona.** A solo practitioner, integrative medicine doctor, or veterinary specialist running a single-clinic practice. Patient pages, condition pages, lab results, medications, providers. The data is sensitive enough that *the brain absolutely cannot live on a vendor's cloud*.

**Problem.** Every hosted memory layer fails the threshold question: is this HIPAA-acceptable? Most are not. The data is medical. The patients did not consent to it being indexed by a third party. A self-hosted brain on your own hardware in your own office is one of the few configurations that lets you build the structured-memory layer at all.

**Custom types.** `patient`, `condition`, `medication`, `lab`, `provider`, `appointment`.

**Extractable facts.** `dosage=`, `lab_value=`, `reference_range=`, `prescribed_at=`, `condition_onset=`, `allergen=`.

**The setup.**

```bash
# 'lab' is annotation (structured: lab_value=, reference_range=, drawn_at=).
# The four entity types are the long-lived containers. Note 'extractable'
# on patient + medication: the brain pulls dosage and allergen facts on
# every write, making the killer query below a pure SQL filter.
gbrain schema add-type patient      --primitive entity     --prefix clinical/patients/      --extractable --expert
gbrain schema add-type condition    --primitive entity     --prefix clinical/conditions/    --expert
gbrain schema add-type medication   --primitive entity     --prefix clinical/medications/   --extractable
gbrain schema add-type lab          --primitive annotation --prefix clinical/labs/          --extractable
gbrain schema add-type provider     --primitive entity     --prefix people/providers/

# Four edges that wire the clinical graph: which conditions a patient has,
# which medications they're on, which labs trace back to them, and which
# provider referred them. The 'prescribed' edge points medication → patient
# (not the other way around) so a single medication query lists everyone on it.
gbrain schema add-link-type has-condition --page-type patient --target-type condition
gbrain schema add-link-type prescribed    --page-type medication --target-type patient
gbrain schema add-link-type lab-for       --page-type lab --target-type patient
gbrain schema add-link-type referred-by   --page-type patient --target-type provider

gbrain schema sync --apply
```

**The killer query.**

```bash
# Two typed facts joined on one patient page: medication=X AND lab.value
# outside lab.reference_range. The graph filter is one SQL join across
# medication ── prescribed ──> patient <── lab-for ── lab.
gbrain query "patients on medication X whose most recent lab Y is outside reference range" \
  --types patient,lab,medication \
  --since "30d"
```

Two facts (`medication=X` and `lab_value > reference_range.upper`) joined to one patient page. Across a three-hundred-patient practice the answer is "five names, call them tomorrow." Across a hosted memory product the answer is "your patient list is now Pinecone's problem." The killer feature here is not the query. It is the *self-hosted-only deployment target*. gbrain is one of very few systems that lets a clinician build this without sending PHI off-premises.

(The usual caveats apply. This is not a HIPAA-compliant product out of the box. It is a deployment shape that *can* be made HIPAA-compliant by a practice that controls the host, encryption-at-rest, access logs, BAAs with any third-party embedding provider, and so on. Self-hosted does not equal compliant. Self-hosted equals *possible to make compliant*. Hosted-only equals not even on the path.)

## 7. The Code Review Brain

**Persona.** A staff engineer or platform lead at a fifty-person company. The repo is a monolith with two thousand contributors over its life. The actual ownership map is a folk taxonomy.

**Problem.** Every refactor lives or dies on three questions vector search physically cannot answer: *who else calls this function* (transitive callgraph); *who reviewed the last three changes to it* (the social-graph slice); *which PRs that touched this function got reverted* (the risk slice). CODEOWNERS is stale. Slack search is shallow. `git log -L :function:file` is one-machine and one-developer. The full ownership picture is *the structure of the codebase plus the social graph of contributors over time*. A brain can hold both because the brain has typed edges, and typed edges are what a callgraph is.

**Custom types.** `repo`, `pr`, `code-def`, `code-ref`, `contributor`.

**Extractable facts.** `language=`, `loc=`, `risk_score=`, `reviewed_by=`, `merged_at=`, `reverted=`.

**The setup.** v0.40.9.0 shipped SQL-DDL indexing via tree-sitter on top of the existing `code-def` / `code-refs` / `reindex-code` ops introduced earlier in the v0.40 series ([CHANGELOG.md](https://github.com/garrytan/gbrain/blob/main/CHANGELOG.md)). The brain treats function definitions and call-sites as first-class nodes. The graph layer then handles the multi-hop walks every code review actually needs.

```bash
# 'pr' is temporal — PRs are chronologically ordered events; the brain
# replays the merge sequence to answer time-bound questions. 'code-def'
# and 'code-ref' are entities populated by tree-sitter on `gbrain reindex
# --code` (v0.40.9.0 added SQL-DDL via tree-sitter).
gbrain schema add-type repo        --primitive entity     --prefix code/repos/       --expert
gbrain schema add-type pr          --primitive temporal   --prefix code/prs/         --extractable
gbrain schema add-type code-def    --primitive entity     --prefix code/defs/        --expert
gbrain schema add-type code-ref    --primitive entity     --prefix code/refs/
gbrain schema add-type contributor --primitive entity     --prefix people/contributors/ --expert

# Seven edges. 'calls' + 'defined-by' encode the static callgraph
# (one definition, many callers). 'touches' + 'reviewed-by' + 'authored-by'
# wire PRs to both the code they change and the people who saw them.
# 'reverted-by' is the load-bearing edge for the risk-aware killer query.
gbrain schema add-link-type defined-in        --page-type code-def --target-type repo
gbrain schema add-link-type calls             --page-type code-ref --target-type code-def
gbrain schema add-link-type defined-by        --page-type code-ref --target-type code-def
gbrain schema add-link-type touches           --page-type pr       --target-type code-def
gbrain schema add-link-type reviewed-by       --page-type pr       --target-type contributor
gbrain schema add-link-type authored-by       --page-type pr       --target-type contributor
gbrain schema add-link-type reverted-by       --page-type pr       --target-type pr

gbrain reindex-code ./services/payments     # tree-sitter builds the code graph
gbrain schema sync --apply                  # backfill type assignments for existing pages
```

**The killer query.** Not "who touches `charge()`" — that one a grep over `git log` can mostly answer. The query vector RAG and grep both fail on is the *combined* one. *Of all engineers who have authored or reviewed a PR that touches `charge()` in the past 180 days, which ones have ever had one of their PRs reverted, and what was the function the reverted PR touched?* That is the question that determines who you actually want on the design review.

```bash
# Anchor on the charge() definition. --reverse walks the touches edge
# BACKWARDS (from code-def to the PRs that touched it). --depth 3 lets
# us reach: charge ── touches ── pr ── authored-by/reviewed-by ──> contributor
# The --filter restricts to recent PRs AND only those whose author has a
# revert in their history. --emit writes the ranked output to a markdown file.
gbrain graph-query code/defs/services-payments-charge \
  --type touches \
  --reverse \
  --depth 3 \
  --filter "pr.merged_at > NOW() - INTERVAL '180d' \
           AND EXISTS(reverted-by SELECT 1 FROM pr_revertees)" \
  --emit reviewers-with-revert-history.md
```

What the graph actually walks: `code-def(charge) ←── touches ── pr ── authored-by/reviewed-by ──> contributor`, joined back through `contributor ── authored-by ──> pr.reverted_by IS NOT NULL`. Four typed edges. One SQL plan. The output is a ranked table:

| Contributor | PRs touching `charge` (180d) | Past reverted PRs | Reverted function |
|---|---|---|---|
| 🟢 alice | 3 (author × 2, review × 1) | 1 | `services/billing/refund` |
| 🟢 bob | 5 (review × 5) | 0 | — |
| 🟡 carol | 1 (author × 1) | 2 | `services/auth/issueToken`, `services/payments/capture` |
| 🔴 dave | 7 (author × 4, review × 3) | 3 | `services/payments/charge` (twice), `services/payments/refund` |

The table is the design-review invite list ranked by *informed risk*, not by raw activity. Dave touches `charge` constantly and has been reverted three times. He absolutely needs to be on the review, but he should not be the sole approver. Bob reviews `charge` a lot and has never been reverted. He is the right second pair of eyes. Carol authored `charge` once recently and was reverted twice in adjacent areas. She should be on the review for the cross-functional context.

The three follow-up queries that fall out of the same schema:

```bash
# Q1: blast radius — what functions does charge() call, transitively?
gbrain graph-query code/defs/services-payments-charge --type calls --depth 4 \
  --emit blast-radius.md

# Q2: review-staleness — which functions in payments/ have not been touched in 90d
# AND have no current owner with a successful PR in the past 180d?
gbrain query "abandoned code-defs in services/payments where last_touch > 90d \
              AND no contributor has merged here in 180d" \
  --types code-def --emit abandoned-code.md

# Q3: cross-service risk — which PRs touch BOTH payments/ AND auth/ in the same diff?
gbrain query "PRs with touches.target IN (payments,auth) GROUP BY pr HAVING COUNT(DISTINCT service) >= 2" \
  --types pr --since 90d --emit cross-service-prs.md
```

Each is a graph walk over typed edges. Each returns a small, actionable list. None has a vector-search formulation. The schema-author primitive lets the staff engineer evolve this brain over months: as new edge types become useful (`fixes`, `tested-by`, `feature-flagged-by`), they get added without a schema migration ceremony; the next `gbrain reindex --code` backfills the new edges over the existing pages. The CODEOWNERS file gets less and less load-bearing as the brain accumulates the *actual* social graph that the file was supposed to describe.

The composition with [Conductor](https://conductor.build) closes the loop: when a worktree starts a refactor against `charge()`, the per-worktree code brain (Topology 3, above) holds the local edits in isolation; the shared artifact brain holds the design-review document; the next `/retro` skill captures what worked into a learnings page that the next refactor on `charge` reads first. The brain is the durable layer; the refactor is the transient layer. The week-old design review and the year-old revert history sit in the same SQL plan.

## 8. The Investigative Journalism Brain

**Persona.** A reporter on a six-month investigation, with hundreds of sources, internal documents, tip-line emails, FOIA releases, public-record filings.

**Problem.** Sources contradict each other. Documents have date metadata that conflicts with timeline claims. Redactions hide names you'd otherwise auto-link. The investigation lives or dies on whether you can hold all of it in your head at once, and the head has limits.

**Custom types.** `source`, `document`, `event`, `claim`, `redaction`.

**Extractable facts.** `cited_as=`, `confidence=`, `verification_status=`, `event_date=`, `redaction_reason=`.

**The setup.**

```bash
# 'event' is temporal (chronologically-ordered with event_date= facts).
# 'document' and 'claim' are annotations so the brain extracts cited_as=,
# verification_status=, confidence= as first-class typed rows.
gbrain schema add-type source     --primitive entity     --prefix journalism/sources/   --expert
gbrain schema add-type document   --primitive annotation --prefix journalism/docs/      --extractable
gbrain schema add-type event      --primitive temporal   --prefix journalism/events/    --extractable
gbrain schema add-type claim      --primitive annotation --prefix journalism/claims/    --extractable

# 'contradicts' is the load-bearing edge for the suspected-contradictions
# cycle phase (next block): the brain runs pairwise LLM-judge comparisons
# over claim pairs sharing a subject, and writes the edge when it finds one.
gbrain schema add-link-type cited-by        --page-type claim    --target-type source
gbrain schema add-link-type contradicts     --page-type claim    --target-type claim
gbrain schema add-link-type corroborated-by --page-type claim    --target-type source
gbrain schema add-link-type happened-at     --page-type event    --target-type document

gbrain schema sync --apply
```

**The killer query.** The suspected-contradictions cycle is the headline. On a corpus of three hundred sourced claims, the brain runs pairwise:

```bash
gbrain eval suspected-contradictions \
  --types claim \                          # restrict to the typed 'claim' pages
  --since-corroboration 30d \              # only re-evaluate pairs older than 30d
  --confidence-threshold 0.7               # surface only judge-confident contradictions
```

The output is a ranked list of claim pairs that the LLM judge thinks contradict each other, with both sources cited and a confidence score. You read it the night before publication. You catch the discrepancy your editor would have flagged at 11pm. The query has no vector-RAG equivalent. It is structurally a *pairwise reasoning task across typed claims with shared subjects*. Only the graph makes it computable.

## 9. The Personal CRM Brain

**Persona.** Anyone trying to be a slightly better friend. The person whose calendar has fifteen friends-and-family events per month, all of whom they would like to remember more about than they do.

**Problem.** This is the case the consumer AI assistant pretends to solve. ChatGPT memory remembers "user's friend Bob likes hiking" for about six weeks and then drops it. Reading the [@Oki10com](https://x.com/Oki10com/status/2058323802879037761) tweet makes the failure mode concrete:

> "ChatGPT te conoce 5 minutos. Claude 5 horas. GBrain para siempre. El primero sabe qué dijiste. El segundo sabe cómo piensas. El tercero sabe quién sos."
>
> — [@Oki10com](https://x.com/Oki10com/status/2058323802879037761), May 23 2026

Translated: ChatGPT knows you for five minutes; Claude for five hours; GBrain forever. The first knows what you said; the second knows how you think; the third knows who you are.

**Custom types.** `person`, `relationship`, `life-event`, `interest`, `conversation`.

**Extractable facts.** `birthday=`, `favorite_X=`, `last_spoke=`, `kid_name=`, `dietary_restriction=`.

**The setup.**

```bash
# 'life-event' and 'conversation' are temporal — chronology matters here.
# Note we don't add 'person' — that's the default gbrain-base type. We
# extend the default schema with the relationship/interest layers it lacks.
gbrain schema add-type relationship --primitive entity     --prefix relationships/        --expert
gbrain schema add-type life-event   --primitive temporal   --prefix life-events/          --extractable
gbrain schema add-type interest     --primitive entity     --prefix interests/            --expert
gbrain schema add-type conversation --primitive temporal   --prefix conversations/        --extractable

# Family-shape edges. 'spouse-of' and 'kid-of' are person→person reflexive,
# so the graph traverses family trees in a single SQL recursion. The
# 'mentioned-in-conversation' edge is what makes 'what did Bob say about
# his daughter?' work — it joins person → conversation by mention.
gbrain schema add-link-type mentioned-in-conversation --page-type person --target-type conversation
gbrain schema add-link-type spouse-of   --page-type person --target-type person
gbrain schema add-link-type kid-of      --page-type person --target-type person
gbrain schema add-link-type favorite-of --page-type interest --target-type person

gbrain schema sync --apply
```

**The killer query.**

```bash
gbrain query "what did Bob mention his daughter was doing this fall?" --since "60d"
```

The brain finds the conversation page from two months ago, pulls the extracted fact (`bob.daughter.fall_activity = ballet recital`), and you walk into Saturday dinner asking how the recital went. That is the *kindness multiplier* a brain enables. It is what your grandparents' generation did with a Filofax and what we somehow regressed past in the smartphone era. The interesting capability is not the lookup. It is that the brain has been quietly extracting these facts on every weekly cycle for two years and they are still there now.

The voice-call integration documented in [`recipes/twilio-voice-brain.md`](https://github.com/garrytan/gbrain/blob/main/recipes/twilio-voice-brain.md) is what makes this practical at the daily-life scale: phone calls create brain pages via Twilio plus OpenAI Realtime, with attendee mapping and auto-link firing the same way they would on a typed meeting page. You hang up; the brain has the relevant facts before you reach your desk. A user [asking about Obsidian-versus-gbrain workflows](https://x.com/ahelplessbaby/status/2058292030934253584) on May 23 captured the personal-brain layering cleanly: Obsidian for personal context and synthesis you read yourself; gbrain for the agent's context and the durable typed graph. The two compose. They do not compete.

## 10. The Team Brain (shared)

**Persona.** A founding team of eight at a Series A. Everyone has their own personal brain. They want to share *some* knowledge across the team without merging private notes into a giant pile.

**Problem.** Notion is the team brain that no agent can query. Personal brains do not share. The desired shape is: each engineer has their own brain, the team has a shared brain, and queries against either side respect both.

**The mechanism.** `gbrain mounts add` lets a personal brain stack additional brains alongside its own. Each mounted brain has its own schema pack. Per [what-schemas-unlock.md](https://github.com/garrytan/gbrain/blob/main/docs/what-schemas-unlock.md): "The eng team's brain has `incident`, `runbook`, `service`, `oncall-rotation`. The design team's brain has `component`, `experiment`, `ab-test`, `figma-link`. The legal team's brain has cases and depositions."

**The setup.** On each personal brain:

```bash
# 'mounts' federates other brains alongside your personal one. Each mount
# is identified by its MCP endpoint + an OAuth client-id (one per team brain).
# The team brain runs as its own gbrain serve instance on its own host;
# this command tells YOUR personal brain how to reach it for federated queries.
gbrain mounts add team-eng https://brain-eng.internal/mcp --oauth-client-id eng-shared
gbrain mounts add team-design https://brain-design.internal/mcp --oauth-client-id design-shared
gbrain mounts list                          # verify both mounts show as 'healthy'
```

**Cross-source corroboration boost.** New in v0.40.4.0 per the README: "per-query graph signals notice when a top result is a hub for THAT query (adjacency boost), is corroborated across team brains (cross-source boost), or is being crowded out by weak chunks from a chatty session (session demote)." The cross-source signal is the team payoff. A fact that shows up in both your personal brain *and* the eng team brain gets boosted in retrieval. Disagreement between sources surfaces as suspected-contradictions across mounts.

**The killer query.**

```bash
# --mounts team-eng tells the query to walk the eng team's typed graph
# (incident, runbook, service types live there, not in your personal brain).
# Your personal brain still merges results: any local pages matching the
# query also surface, ranked by the cross-source corroboration boost.
gbrain query "which of our incidents this quarter touched the payments service and what's the runbook delta?" \
  --mounts team-eng \
  --types incident,runbook,service
```

That walks the eng team's typed graph (`incident ── touched ──> service`, `service ── has-runbook ──> runbook`), pulls runbooks with diffs against pre-incident versions, and the answer takes one second to compute and one screen to display. The eng team did the work of investing in their ontology once; every personal brain that mounts the team brain inherits that sharpness. The schema is the team's tribal knowledge made explicit.

# Honourable mention: the eleventh use case, code-as-source

The list of ten is the editorial spine but it is worth one more callout because it is the use case that has been picking up steam in the public conversation. The capability is *gbrain reading your codebase as a typed source*, which sits at the intersection of "personal memory" and "agentic coding". The integration with [Conductor](https://conductor.build) and the per-worktree split-engine Topology 3 already covers the parallel-coding pattern. The capability nobody talked about until v0.40 is that *gstack and gbrain compose*. Garry's [gstack reference doc on USING_GBRAIN_WITH_GSTACK.md](https://github.com/garrytan/gstack/blob/main/USING_GBRAIN_WITH_GSTACK.md) describes the exact handoff: a `/retro` skill writes a learnings page; gbrain ingests it into the artifact brain; the next `/office-hours` reads the relevant learnings via gbrain query before drafting the next design doc.

The result is a feedback loop. The agent does the sprint via gstack. The brain remembers what worked. The next sprint starts smarter than the last. That is the trilogy collapsing into a single workflow. The harness runs the sprint. The brain holds the institutional memory. The model is the engine; the brain is the gearbox; the harness is the chassis.

[@bryanonchain](https://x.com/bryanonchain/status/2058251520492380356) captured the moment of recognition on May 23: "after gstack we patch brain agent with gbrain. salute". That is the sequence most adopters seem to go through. Install gstack first (it is the visible, demoable thing). Then realise that the sprint output is being thrown away at the end of each session. Then install gbrain to catch it. The order is gstack → gbrain. The capability that completes the picture is gbrain.

# Alternatives and competitive landscape

This is the section every honest piece on a new system has to write. Below is the comparison table, the honest paragraph on each alternative, and the conditions under which something else is the right answer.

![Self-hosted graph-first vs vector-wrapper vs hosted-SaaS axis chart](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-vs-alternatives.jpg)

| System | Architecture | Self-host? | Graph? | Markdown SoR? | LLM-free auto-link? | Auth model | License | Best for | Falls short vs gbrain |
|---|---|---|---|---|---|---|---|---|---|
| 🟢 **gbrain** | Postgres + pgvector + PGLite (WASM) | Yes | Typed edges, multi-hop | Yes | Yes (regex-based) | OAuth 2.1 MCP scopes | MIT | Single-operator, OpenClaw/Hermes users | Single-operator design centre |
| [mem0](https://github.com/mem0ai/mem0) | Vector DB + LLM-extracted memory | Hosted + OSS | Limited | No | LLM call per add | API key | Apache 2.0 | Hosted multi-user | Costs scale linearly with writes; no markdown SoR |
| [zep / Graphiti](https://github.com/getzep/graphiti) | Neo4j-backed temporal KG | Hosted + OSS | Yes (typed) | No | LLM-extracted edges | API key | Apache 2.0 | Temporal queries, hosted | Neo4j ops burden; no markdown SoR |
| [Letta (MemGPT)](https://github.com/letta-ai/letta) | Postgres + tool-call memory | Hosted + OSS | No | No | No (LLM-managed) | API key | Apache 2.0 | Stateful chat agents | No graph; chat-centric |
| [OpenAI memory](https://help.openai.com/en/articles/8590148-memory-faq) | Proprietary | No | Unknown | No | LLM-extracted | OpenAI API | Closed | ChatGPT users | Locked in; cannot self-host or export |
| [Anthropic Projects](https://www.anthropic.com/news/projects) | Proprietary | No | No | Files (uploaded) | No | Anthropic API | Closed | Claude users | No retrieval API; doc-bound |
| LlamaIndex / LangChain memory | Backend-agnostic | Self-host | Optional | No | Optional LLM | Backend keys | MIT | Library users | Library, not a system |
| [Obsidian](https://obsidian.md) | File-based markdown | Yes | Backlinks (no types) | Yes | Pattern match (no graph) | Local | Closed (free) | Human PKM | Not an agent memory; no MCP, no fact extraction |
| Notion / Coda | SaaS docs | No | No | No | No | SaaS | Closed | Team docs | No retrieval API; not for agents |
| [GraphRAG (Microsoft)](https://github.com/microsoft/graphrag) | Neo4j or DuckDB | Self-host | Yes | No | LLM-extracted | None | MIT | Research / eval | Heavy LLM cost; research-grade |
| [Cognee](https://github.com/topoteretes/cognee) | Multi-store | Self-host | Yes | No | LLM-extracted | None | Apache 2.0 | Multi-modal | Newer, smaller community, LLM-extracted edges |

## Where each alternative actually wins

Be honest, not a hatchet job.

**mem0** wins for hosted multi-user products where you do not want to run any infrastructure. Their hosted ergonomics are good. If you are building a B2C chat app with millions of users and you need persistent memory per user, mem0's managed offering is the path of least resistance. The cost model breaks down at gbrain's scale (146K pages, six-figure cumulative writes) because every add pays an LLM call. The win is for "small per user, lots of users", which is exactly the opposite shape of gbrain's "one user, lots of pages."

**zep / Graphiti** has a legitimately strong temporal KG layer. If your dominant query pattern is "what did this entity look like on date X" with rich temporal slicing, their Bi-Temporal model with `valid_at` and `recorded_at` axes is genuinely strong work. gbrain's `valid_until` semantic upsert during consolidation reaches some of the same ground but with less ceremony and less power. Operate Neo4j in production, and zep is a reasonable place to land. Most people should not operate Neo4j in production.

**Letta (formerly MemGPT)** is the right answer if your product is a *stateful chat agent specifically* and you want the OS-style memory hierarchy (in-context working memory, archival memory, recall memory, paged in and out by the agent itself). It is purpose-built for that shape. It is not built for "the brain behind every signal I encounter". Different design centre.

**OpenAI memory and Anthropic Projects** are the right choice if you only chat. If you exclusively interact with the model in their first-party interface and you have no agent harness running outside, the integration is tight and the data lives where the model already is. The day you want a different model, an agent that runs on your own machine, or an export, you are out of luck. That is the lock-in tax. Some users do not mind paying it.

**LlamaIndex / LangChain memory** are *libraries*, not systems. They are correctly built that way. If you are writing your own agent infrastructure and want the memory primitives without the system wrapped around them, the libraries are fine. gbrain is a system that ships a complete CLI, MCP server, cron daemon, 57 skills, an eval harness, and a doctor. The library approach gives you flexibility. The system approach gives you "two seconds to a working brain". Pick what you need.

**Obsidian** is the closest aesthetic cousin to gbrain because both are markdown-first and self-hosted. The structural difference: Obsidian is built for *humans reading and writing their own notes*. Backlinks are untyped. There is no MCP. There is no fact extraction. There is no cron-driven consolidation. There is no graph traversal beyond clicking blue links. Obsidian is the *medium*. gbrain is the *substrate that makes the medium queryable by an agent*. They compose well, and several gbrain users in the [Reddit r/openclaw discussion](https://www.reddit.com/r/openclaw/comments/1t84z33/gbarin_is_it_really_good/) report keeping both. [One user](https://x.com/ahelplessbaby/status/2058292030934253584) asked the exact framing question that resolves this: "Obsidian for personal context and Gbrain for your agent's context?" The answer is yes; that is the natural division of labour.

**Notion / Coda** are team-docs systems. They are not memory layers for agents. They have no retrieval API a third-party agent can hit at low cost. They are useful for the work they are for. They are not the alternative under discussion.

**GraphRAG and Cognee** are the closest architecturally because both go all-in on knowledge graphs. The shared distinction: their edge extraction is LLM-driven. That makes them cleaner on prose corpora where there are no `[[wiki-style]]` links to scrape, but it also makes them *expensive* at scale. GraphRAG's research-grade extractor running over a 100K-page corpus is a five-figure spend. gbrain's regex-based extractor is zero spend. The shape of the corpus determines the right tool. If your corpus is the SEC EDGAR full-text dump and you cannot annotate it, GraphRAG is the right starting point. If your corpus is your own markdown notes where you naturally write `[Garry Tan](wiki/people/garry-tan)`, gbrain is the right starting point.

The summary thesis: *for a self-hosted single-operator who wants the brain to be theirs forever, gbrain has no peer in 2026*. For other shapes there are other answers.

## A note on benchmark comparability

Comparing memory systems via published benchmarks is a minefield in 2026. mem0 publishes results on its own internal benchmark. zep publishes on LoCoMo. Letta publishes on MMLU-adapted memory probes. gbrain publishes BrainBench (in the [gbrain-evals sibling repo](https://github.com/garrytan/gbrain-evals)) and a version of LongMemEval with a methodology note. No two of these run the same harness on the same corpus. So the table above is not a "gbrain wins by 12 points on Benchmark X" table; it is an architectural comparison.

What you can do, the part the docs are explicit about, is *run the benchmark on your own corpus*. The publicly-usable harness is `gbrain eval longmemeval datasets/longmemeval_s.jsonl`. The eval-capture mode (`GBRAIN_CONTRIBUTOR_MODE=1`) lets you record your real queries and replay them against code changes. The A/B fixture mode (`gbrain eval --qrels labels.tsv --config balanced.json`) lets you label a fixture set and watch the score move as you change the stack. That is the test discipline that lets a serious operator decide which memory system is right for their corpus. Trust your own numbers, not the vendor's. gbrain's docs say this out loud; most vendors do not.

# The skeptical view: the @cadmarlow steelman

The strongest objection to gbrain in the public discourse is captured cleanly by [@cadmarlow](https://x.com/cadmarlow/status/2058323422124429328) on May 23 2026:

> "I stop at the readme feels like a huge complex factory … what can i do with gbrain i can't just by running Codex or Claude code in my VPS with a few cron jobs, and md files?"
>
> — [@cadmarlow](https://x.com/cadmarlow/status/2058323422124429328), May 23 2026

This is the steelman because it is correct *for some users*. A reflective developer with a hundred markdown files who runs `ripgrep` over them and sometimes greps for a name is genuinely not missing anything by skipping gbrain. The economics of installing a Postgres-backed knowledge graph for a hundred files are silly. The whole pitch of gbrain only starts to bite somewhere around two thousand pages, where the human-readable-search loop breaks down and the agent starts duplicating work the way ORIGIN.md describes.

What gbrain gives you that "Codex or Claude Code in a VPS with cron and md files" does not, *for someone past that threshold*:

**1. Hybrid retrieval that beats ripgrep by +31 P@5 points on real corpora.** The cron-plus-grep approach hits a ceiling at lexical match. You cannot ripgrep "who works on retrieval quality at YC?" and have it surface a page about "Garry Tan and search ranking". Vector + BM25 + RRF closes that gap. The graph closes the rest. The BrainBench numbers are the empirical proof.

**2. A typed knowledge graph that auto-wires on every write.** A flat markdown directory has no concept of "this is a person, this links to a company, the link is `works_at`". You can sort-of approximate it with Obsidian wikilinks and a strict naming convention, but you cannot query the result with `gbrain graph-query alice-example --type intro-from --depth 2`. The graph is the structural primitive that makes "who else touches this function" or "which portcos crossed $1M ARR" computable.

**3. Schema-author as an agent surface.** Your Claude Code in a VPS with cron cannot *evolve its own ontology* over MCP with atomic file locks, validation gates, privacy-redacted audit logs, and chunked SQL backfills. That capability simply does not exist outside gbrain in 2026. It is the v0.40.7.0 capability that makes the "agent co-curates your brain" pattern real. You either have the schema-author primitive or you do not.

**4. The protected cycle phases.** Your cron-plus-md approach has no concept of "this LLM-heavy job should never be submittable from a remote MCP client". gbrain's three protected phases (synthesize, patterns, consolidate) are how you let an external agent into your brain without giving it the keys to your Anthropic billing.

**5. The doctor with a cost cap.** `gbrain doctor --remediate --max-usd 5` is one command that walks a dependency-ordered remediation plan, refuses to spend past your cap, and re-checks the score between every step. Roll-your-own with cron and shell scripts can approximate this. The approximation is brittle.

**Be honest about when cron + markdown is enough.** Under two thousand pages, with one person who is also the agent and the reader and the curator, you can probably ship without gbrain. The break-even comes when you cross some combination of (a) more than two thousand pages, (b) more than one ingestion source, (c) more than one agent reading the brain, (d) any query that crosses two entity types. The moment you cross any of those thresholds, the cron-and-markdown approach starts hemorrhaging time on duplicated work and lost connections, exactly the pathology ORIGIN.md described. gbrain is the answer for the people past the threshold. For everyone else, it is overkill, and there is no shame in saying so. The README is not lying when it says "feels like a huge complex factory". It *is* a factory. It is built for users who have factory-scale knowledge.

[@98Escandon](https://x.com/98Escandon/status/2058308443833090263) captured the other version of the objection: "still rawdogging claude over gbrain tbh". For some interaction shapes that is exactly right. Conversational throwaway tasks against a hosted model are not what gbrain is for. gbrain is for the corpus that survives the conversation.

The third honest pushback worth quoting comes from [Vectorize.io's review](https://vectorize.io/articles/gbrain-review):

> "GBrain is the best open-source markdown-first personal brain available right now if you run OpenClaw or Hermes Agent, value plain-text ownership of your knowledge, and have the discipline to author skill workflows as your schema evolves. It is also young (~v0.30, frequent breaking changes), single-operator by design, self-host only, and structurally a different product from production agent memory platforms."
>
> — [Vectorize.io review](https://vectorize.io/articles/gbrain-review), May 8 2026

That paragraph names the costs honestly. Frequent breaking changes are real. The single-operator design centre is real. Self-host-only is real. If those are dealbreakers for you, gbrain is not your tool.

# Who should NOT use gbrain

A piece this long owes the reader a clean negative list.

**Multi-tenant SaaS operators.** gbrain is *single-operator by design*. The auth model has scopes (`read` / `write` / `admin` / `localOnly`) but the data model is "this is one human's brain". There is no row-level isolation between users-within-a-brain. If you are building a product where every signed-up user gets their own brain and you need to host all of them, you can do it (one DB per user, or schema-per-user), but you are building the multi-tenant orchestration layer yourself. The Vectorize review nails this: gbrain is structurally a different product from a production multi-tenant memory platform.

**Large teams without ops capacity.** A ten-person eng team can install gbrain on a shared host and run it. A two-hundred-person org has people whose job is to operate Postgres. That role does not exist at the ten-person size, and gbrain is sized for the smaller side. If you have a 50-instance Postgres fleet today, gbrain will not surprise you. If you have never run pg_isready in production, the operational floor is higher than mem0's hosted offering.

**Hosted-only operators.** If your entire stack runs in a hosted environment and you do not want to introduce any self-managed infrastructure, gbrain is not the answer. OpenAI memory for chat use, mem0 hosted for agent use, are the right answers. The whole point of gbrain is that it is yours; if you do not want it to be yours, you are paying the cost without claiming the benefit.

**Anyone unwilling to maintain a schema.** The killer feature of gbrain is the typed graph. The schema is the typed graph's specification. If you will never run `gbrain schema add-type` and you treat every page as `note`, the graph layer mostly does not light up and you are paying the install cost for capabilities you never use. The default `gbrain-base` 22-type schema covers a meaningful default, but the per-vertical lift (the +31.4 P@5) comes from the types you add. The schema-author primitive lowers the cost of doing that, but it does not zero it. You have to be willing to think about your own ontology occasionally.

**People with under two thousand pages who are happy with ripgrep.** The break-even point on operational cost vs. retrieval lift starts somewhere around 2K pages. Below that, the math is fine for grep. Above that, gbrain pulls ahead fast.

**Anyone who does not want their agent reading their brain.** A real subset of operators want memory to be *write-only from the agent's side*. They want the agent to file things; they want to do retrieval themselves. gbrain can be configured for that shape (scope the OAuth client to `write` only), but the architectural assumption of the system is that the agent is a first-class reader. If you find yourself fighting the architecture to make it read-restrictive, you are using the wrong tool.

# A view from inside the brain

A note on method. Every prior section in this piece reads gbrain from the outside: tweets, README excerpts, RETRIEVAL.md quotes, CHANGELOG diffs, the published BrainBench score. This section reads it from the inside. The artefacts below are produced by `git clone https://github.com/garrytan/gbrain` and `bun install github:garrytan/gbrain` in a clean sandbox, then walking the directory with `ls`, `find`, `cat`, `wc`, and `gbrain doctor`. The point is not to be exhaustive; the point is to show that the 18,652-star artefact is, at the literal level, a tree of TypeScript files plus a 57-skill Markdown pack plus an 8.4 MB PGLite WASM blob. Anyone with `git` and `bun` can reproduce the read.

The clone is two seconds on a warm connection. The top of the tree, as reported by `ls`:

```bash
# 27 entries at the repo root, mixing documentation, source, and config.
$ ls /tmp/gbrain
CHANGELOG.md             docker-compose.test.yml  recipes/
CLAUDE.md                docs/                    scripts/
CONTRIBUTING.md          evals/                   skills/
DESIGN.md                examples/                src/
INSTALL_FOR_AGENTS.md    gbrain.yml               templates/
LICENSE                  llms-full.txt            test/
README.md                llms.txt                 tests/
SECURITY.md              openclaw.plugin.json     tools/
TODOS.md                 package.json             tsconfig.json
VERSION                  bunfig.toml              bun.lock
admin/                   docker-compose.ci.yml
```

The version file pinned to the canonical `main` branch at the time of this piece reports `0.41.2.0`. The license confirms MIT. The package name is `gbrain` and the main entry is `src/core/index.ts`; the `bin` field declares one binary, `gbrain → src/cli.ts`. The package's [`exports` map](https://github.com/garrytan/gbrain/blob/main/package.json) makes 28 subpath exports public, including `./engine`, `./operations`, `./minions`, `./search/hybrid`, `./ai/gateway`, `./pglite-engine`, and `./ingestion/test-harness`. That last one is editorially interesting: the *test harness itself is a published export*. The library expects you to write tests against its public API the way the maintainers do.

![Directory tree of the gbrain repo: 569 src TS files, 859 test files, 57 skills, 22 docs](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-directory-tree.jpg)

The file counts make the shape legible. `find src -name "*.ts" | wc -l` returns **569** TypeScript files. `find test tests -name "*.ts" | wc -l` returns **859** test files. The test-to-source ratio is 1.51, which is unusual for a young repository; the convention most projects ship is closer to 0.5. The skills directory, `ls skills | wc -l`, returns **57** entries: 43 core skills + 14 schema-pack skills, the same number RETRIEVAL.md cites. Within `src/`, the load-bearing subtree is `src/core/`, which contains the engine, the search stack, the ingestion pipeline, the audit subsystem, and the cycle. `src/mcp/` is 965 lines across five files: `server.ts` (104 lines, stdio transport), `http-transport.ts` (382 lines, HTTP+bearer-auth), `dispatch.ts` (283 lines, the shared validator the post's lifecycle section quoted), `rate-limit.ts` (142 lines), `tool-defs.ts` (54 lines, the operation-to-MCP-tool mapper). The whole MCP server is one thousand lines of TypeScript. The brain is in `src/core/`; the wire is `src/mcp/`.

The shape of a single retrieval primitive is the next layer to inspect. `head src/core/search/keyword.ts` returns nine lines: an import, a function, and a delegation to `engine.searchKeyword(query, opts)`. The keyword search "module" is a one-line wrapper because the heavy lifting is in the engine implementations (`src/core/pglite-engine.ts` for local, `src/core/postgres-engine.ts` for scale), and the wrapper exists so the hybrid pipeline can compose strategies as first-class functions. `src/core/search/vector.ts` is the same shape, three lines of code wrapping `engine.searchVector(embedding, opts)`. The fusion logic is in `src/core/search/hybrid.ts` and runs roughly 700 lines, the longest single file in the retrieval stack; the comment block at the top names the constants directly: `RRF_K = 60`, `COMPILED_TRUTH_BOOST = 2.0`, `BACKLINK_BOOST_COEF = 0.05`. Each constant has a one-paragraph rationale in the file. The graph signals file (`src/core/search/graph-signals.ts`) opens with the design constraints we already quoted in the lifecycle section: "*Conservative magnitudes (D14=B): halved from initial (1.10/1.15/1.05) to (1.05/1.10/0.95) so multiplicative composition can't catastrophically reorder in tight score bands.*"

The 57 bundled skills sit under `skills/`. `head -10 skills/RESOLVER.md` returns the dispatcher's first table:

```text
# GBrain Skill Resolver

This is the dispatcher. Skills are the implementation. **Read the skill
file before acting.** If two skills could match, read both. They are
designed to chain (e.g., ingest then enrich for each entity).

## Always-on (every message)
| Trigger                                              | Skill                         |
| Every inbound message (spawn parallel, don't block)  | skills/signal-detector/SKILL.md |
| Any brain read/write/lookup/citation                 | skills/brain-ops/SKILL.md     |
```

That is the entire orchestration model for the skill pack. The agent reads `RESOLVER.md` on every inbound message, scans the trigger tables, opens the matching `SKILL.md`, and follows the body. There is no skill-loader runtime, no plugin manifest registry, no compilation step. The agent is the runtime. The directory is the documentation. The `CLAUDE.md` at the repo root opens with a stricter framing of the same convention: *"This codebase is the source of truth. The skills exist to make a brain reachable from an agent."*

Installing the CLI is one command and confirms the package shape:

```bash
# Bun resolves the GitHub source, fetches deps, links the gbrain binary.
$ bun install github:garrytan/gbrain
Resolved, downloaded and extracted [294]
installed gbrain@github:garrytan/gbrain#ca68633 with binaries:
- gbrain
206 packages installed [17.91s]

# Verify the install dropped the canonical binary into the bin dir.
$ node_modules/.bin/gbrain --version
gbrain 0.41.2.0
```

`gbrain init --pglite --embedding-model openai:text-embedding-3-small --embedding-dimensions 1536` is the cold-start; it materialises `~/.gbrain/config.json` (six lines of JSON: engine, database_path, embedding_model, embedding_dimensions) and the PGLite directory tree under `~/.gbrain/brain.pglite/`. The latter is a real Postgres 17 data directory (`PG_VERSION` literally contains `17`), with `base/`, `global/`, `pg_wal/`, `pg_multixact/`, `pg_xact/`, `pg_logical/`, all the subdirectories any Postgres operator recognises. `du -sh ~/.gbrain/brain.pglite` after init returns 42 MB on cold start. The WASM artefact itself is `node_modules/@electric-sql/pglite/dist/pglite.wasm`, **8.4 MB**, plus a 168 KB `initdb.wasm` companion. There is no `pg_ctl`, no daemon, no Docker. Postgres-the-engine is loaded into the same Bun process that runs the CLI, dlopened on demand from a WASM blob.

`gbrain doctor` then runs the health-check sweep. Output from a live session, with the OK lines summarised:

```text
# Doctor runs ~80 checks: schema version, embedding provider reachability,
# graph coverage, audit-log freshness, queue health, eval drift, and more.
$ gbrain doctor
[OK] schema_version: Version 94 (latest: 94)
[OK] embedding_provider: openai:text-embedding-3-small ✓ 2295ms, 1536 dims, DB aligned
[OK] alternative_providers: 2 alternative embedding providers ready: google, openrouter
[OK] embedding_column_registry: Registry healthy: 2 columns; active='embedding'
[OK] graph_coverage: No entity pages, markdown-only brain
[OK] brain_score: Brain score 100/100
[OK] child_table_orphans: All FK-child tables clean (10 tables checked)
[OK] whoknows_health: whoknows eval fixture present (10 queries)
[OK] reranker_health: Reranker disabled, no failures expected
[OK] ze_embedding_health: Configured model is not ZeroEntropy, skip.
[OK] embedding_width_consistency: Schema width (1536d) matches gateway dimensions
[OK] oauth_confidential_client_health: No OAuth clients registered
[OK] autopilot_lock_scope: Lock path: ~/.gbrain/autopilot.lock
[OK] cycle_phase_scope: 10 source-scoped, 7 brain-global, 2 mixed
[WARN] embeddings: No embeddings yet. Run: gbrain embed --stale

Health score: 80/100. All checks OK (some warnings).
```

The two WARN rows are expected on a freshly-initialised brain (no embeddings until pages exist; no JSONB rows until claims fire). What is editorially interesting is the *granularity*: the doctor knows about source-scope on the autopilot lock, the cycle-phase taxonomy (10 source-scoped, 7 brain-global, 2 mixed), the embedding-column registry, and the whoknows eval fixture. A health checker that knows the whoknows fixture exists by name is one whose authors expect operators to read it.

The on-disk Postgres state is what the schema looks like once you walk the brain.pglite directory and list tables via a `gbrain query` shell. The migrations file (`schema_version: 94`) tracks the additive history; the runtime tables that matter for an agent reading a brain are these.

![Postgres schema diagram showing six tables (pages, chunks, links, claims, access_tokens, mcp_request_log) and their foreign-key relationships, with the auto-link writeback path highlighted](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-postgres-tables-schema.jpg)

`pages` carries one row per markdown file (`slug`, `source_id`, `content`, `salience_score`, `valid_until`, the configured-width `embedding` column at 1536d in this install). `chunks` carries the per-paragraph projections that the embedding and keyword stages actually search over (`page_id`, `ord`, `content`, `embedding`, `tsvector_search`). `links` carries the typed edges the auto-link extractor wrote (`source_slug`, `target_slug`, `type`, `created_at`). `claims` carries the typed facts extracted from `## Facts` fences (`page_id`, `subject`, `predicate`, `object`, `confidence`, `since_date`). `access_tokens` carries the OAuth/bearer credentials (`token_hash`, `permissions` JSONB, `last_used_at`, `revoked_at`). `mcp_request_log` carries the request audit (`token_name`, `operation`, `latency_ms`, `status`, `created_at`). The whole schema is roughly 30 tables; those six are the ones an agent's read or write goes through. The migration files under `src/schema.sql` and `src/core/pglite-schema.ts` (lines 478 and 495 are the `access_tokens` and `mcp_request_log` references the HTTP transport file points to) are committed-to-git and `cat`-able.

The capture verb closes the loop. Running `gbrain capture "lifecycle test thought one"` on the freshly-initialised brain returns:

```text
# A single inline capture into the brain. --quiet returns the slug so
# the call is scriptable (the post's earlier section flagged this).
$ gbrain capture "lifecycle test thought one" --quiet
captured:
slug:          inbox/2026-05-25-c94c18e0
status:        created_or_updated
content_hash:  c94c18e0e7db86a6…
captured_at:   2026-05-25T07:38:46.907Z
```

The slug encodes the date plus an eight-character content hash. The `status` is "created_or_updated" because the slug-derivation is content-aware: re-capturing the same text returns the same slug, and `ON CONFLICT DO NOTHING` makes the write idempotent. A second capture lands at `inbox/2026-05-25-f45885ee`, a different hash for different text, the same date prefix for chronological triage. After both captures, `~/.gbrain/audit/` holds the JSONL audit trail (one event per capture), and the PGLite directory has grown by a few hundred kilobytes (mostly WAL). No daemon was started. No network call was made (the embedder is local-disabled by default until `gbrain embed --stale` runs). The brain is now a brain with two thoughts in it.

The total surface, counted directly from the clone and the install:

| Artefact | Count | Notes |
|---|---:|---|
| Top-level directories | 27 | Mix of source, docs, fixtures, recipes, skills |
| `src/` TypeScript files | 569 | Engine, search, MCP, audit, cycle, Minions, eval |
| `test/` + `tests/` files | 859 | 1.51× source files, unusually high test density |
| `skills/` bundled skills | 57 | 43 core + 14 schema-pack |
| `bin/` CLI entries | 1 | One binary, 80+ subcommands behind it |
| Default page types (`gbrain-base`) | 22 | Universal schema before any author additions |
| Migrations applied (`schema_version`) | 94 | Roughly two per week since April 5 launch |
| PGLite WASM artefact | 8.4 MB | Postgres 17, one `.wasm` file, dlopened at startup |
| Doctor checks | ~80 | OK / WARN per check, one composite `brain_score` |
| Fresh-install disk usage | 42 MB | Empty PGLite data directory after `gbrain init` |

That is the entire engineering surface. Almost no opaque binaries, no proprietary protocols, no hosted dependencies. The argument that any operator with a week on Postgres and a copy of the README "could have built this" is partially true: the primitives are not technically hard. The argument that the *integration* is not technically hard does not survive a walk through the clone. The integration is in the audit-write semantics, the source-scope leak seals, the protected-phase MCP refusal logic, the per-token takes-holder allow-list, the schema-version migration discipline, the embedding-column registry, the cycle-phase taxonomy, and the 859-file test suite that pins all of it down. Those are written down. They are in this directory. The directory is the documentation of the system, and it is `git clone`-able by anyone.

# What gbrain telegraphs about the next wave of agent memory

![Three forces compounding into a single conclusion: graph beats vector, markdown beats blobs, self-host beats hosted-SaaS](/post-images/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/gbrain-future-of-memory.jpg)

We are taking a position. Here it is.

The shape of agent memory in 2027 is going to look more like gbrain than like mem0, and the reason is structural rather than competitive. Three forces compound:

**The cost of LLM-extracted edges is going up, not down.** Every "LLM extracts facts on every write" architecture pays an extraction cost per ingestion event. Token prices are falling, but ingestion volume is rising faster, because agents are now writing into memory continuously. The mem0-style "LLM call per add" math gets worse, not better, as agents become the dominant writers. The regex-plus-heuristic primitive in gbrain is the architectural exit ramp.

**The privacy story for hosted memory is fragile.** Every breach, every dark-patterned ToS change, every "we'll use your memory for training unless you opt out" cycle reminds the operator class that they do not actually control their own brain. The self-hosted answer is not "for paranoids"; it is "for the modal serious operator in 2027". The medical use case is the canary because the law forces the issue. The pattern generalises.

**The agent harness is becoming a commodity.** OpenClaw, Hermes, Claude Code, Cursor, Codex are all variations on "an LLM with a tool loop and a config file". What stops being commodity is the *memory layer they all share*. The brain is the thing that accumulates over the years. The harness is the year's tool. Picking a memory layer is a longer-horizon decision than picking a harness, and that means the right memory layer is the one that outlives any specific harness. Markdown in a git repo outlives Hermes. It probably outlives OpenClaw. It almost certainly outlives Claude Code. It will not outlive the operator.

That last point is the answer to "why did Garry Tan build this himself". The President and CEO of YC could have funded a startup, signed a partnership, paid for a hosted product, written a check. He built it himself and shipped it MIT-licensed instead because the cost of getting the memory layer wrong is *all of his portfolio knowledge over the next decade*, and the only way to ensure he gets the layer he wants is to write it himself and give it away so that the architecture is forced to survive him. The release strategy is the architectural argument.

The [Reddit r/AIAgentsInAction summary](https://www.reddit.com/r/AIAgentsInAction/comments/1t9d5lb/garry_tan_runs_100000_pages_of_brain_100_skills/) put it well: "Garry Tan's personal AI isn't a chatbot. It's a runtime with 100+ skills, 100,000 pages of structured knowledge, and a meta-skill that writes new skills automatically." That is the shape of memory that the next generation of agents will read from. It is more like a database with structure than a vector blob. It is more like markdown in git than a SaaS API. It is more like a graph that grows on every write than a pile that gets searched on every read. gbrain happens to be where that shape lands first, fully integrated, in 2026. Other systems will reach the same shape from different starting points. The destination is the same.

The community noise around gbrain over the past month has been instructive. [@ai_trade_pro](https://x.com/ai_trade_pro/status/2058245062882906188) captured the most common reaction to the v0.40.2.0 `gbrain think` release:

> "This is a massive leap. The difference between 'here are some chunks' and 'here's a synthesized answer with citations + gap analysis' is night and day. GBrain Think actually feels like it's doing real thinking instead of just retrieval."
>
> — [@ai_trade_pro](https://x.com/ai_trade_pro/status/2058245062882906188), May 23 2026

The `gbrain think` verb grounds temporal answers in the typed-claim timeline. Ask "when did Marco last switch jobs" or "what was the ARR in March" and the answer comes back rooted in a real chronological timeline of metric + event facts the brain already extracted via the `extract_facts` cycle phase. The intent classifier (`temporal` / `knowledge_update` / `other`) is a regex pass with zero LLM cost; the `'other'` fast path short-circuits with zero extra SQL. The same trajectory plumbing also lands in the LongMemEval benchmark with a methodology change disclosed in `methodology_note: extractor=haiku-preprocess-full-haystack-v1`. Garry is one of the few operators in this space who publishes the methodology note alongside the score. That is the editorial signal.

# The closer: completing the trilogy

This piece is the third leg of a trilogy. The first leg, [openclaw-vs-hermes](https://thedeepfeed.com/posts/2026-05-11-openclaw-vs-hermes-agent-personal-ai-runtime-wars/), argued that *the agent harness war is real and the choice between OpenClaw's thin-harness model and Hermes Agent's batteries-included model is a structural one*. The second leg, [gstack](https://thedeepfeed.com/posts/2026-05-23-gstack-garry-tan-claude-code-developer-guide/), argued that *the developer-tool harness is now skills-shaped, and gstack's 101,020-star demonstration is the proof point*. This piece argues that *both harnesses share the same memory layer, and the memory layer is the longest-lived component in the stack*. The harness is fashionable; the brain is permanent.

If you take only one thing from this piece, take this: the day you install your second AI agent against the same brain is the day the brain stops being an implementation detail and starts being the durable layer of your work. That second-agent test is the moment of truth. Hosted memory cannot pass it; the second agent does not have access. Vector-DB wrappers can technically pass it but the data is in their format and you cannot leave. gbrain passes it natively. The brain is markdown in git. The retrieval index is Postgres you can dump. The graph is typed edges in a table. Any agent you ever run against it sees the same brain.

The wager is that ten years from now there will still be a `~/.gbrain/` on Garry Tan's machine, with a markdown repo behind it, growing on every write. The harness will have changed three times. The model will have changed five times. The brain will be the same brain, just larger. That is the bet the architecture is making, and you are invited to make it too.

Install:

```bash
# 1. Install the CLI from GitHub via Bun's global install.
bun install -g github:garrytan/gbrain

# 2. Initialise a local brain. --pglite uses the embedded WASM Postgres;
#    swap for --supabase or --postgres if you already run one.
gbrain init --pglite

# 3. Verify the install. doctor runs ~30 health checks (DB connectivity,
#    embedding provider reachable, schema integrity) and prints a score.
gbrain doctor

# 4. Capture your first thought. The page lands in inbox/YYYY-MM-DD-<hash>/
#    on disk AND in the DB in one write-through move. You now have a brain.
gbrain capture "the first thought I want to remember"
```

Two seconds to a working brain. The next ten years are up to you.

# Appendix: a recommended thirty-day adoption path

The post would be incomplete without an operational rollout plan. The cost of getting gbrain wrong is mostly wasted weeks; the cost of starting badly with any structured-knowledge system is that you accumulate untyped pages faster than you triage them and the schema-author primitive arrives too late to help. Here is the thirty-day path that the public docs and the community discourse seem to converge on.

**Days 1 to 3.** Install `--pglite`. Run `gbrain doctor` until you have a clean baseline. Run `gbrain capture` ten times on real thoughts to populate the inbox. Read [`AGENTS.md`](https://github.com/garrytan/gbrain/blob/main/AGENTS.md) (the entry point for non-Claude agents) or [`CLAUDE.md`](https://github.com/garrytan/gbrain/blob/main/CLAUDE.md) (the entry point for Claude Code) to understand the RESOLVER.md routing model. Do not touch the schema yet.

**Days 4 to 10.** Point gbrain at one existing markdown source you have. Run `gbrain sync --source ~/notes/`. Watch what type the default schema assigns each page (`gbrain schema stats --json`). Note which pages got typed `note` (the catchall) and ask: are these meetings? Are they papers? Are they journal entries? Resist the urge to add types yet.

**Days 11 to 20.** Pick *one* type to add. Just one. The one that covers the largest catchall blob. For most operators that is `meeting`. Run the schema-author flow: `gbrain schema fork`, `gbrain schema use`, `gbrain schema add-type meeting --primitive temporal --prefix meetings/ --extractable`, `gbrain schema sync --apply`. Watch the page count for `meeting` go from zero to whatever the backfill produces. Run `gbrain whoknows` on a meeting topic and feel the routing change. Run `gbrain search "<topic>" --explain` to see the per-stage attribution.

**Days 21 to 25.** Set up the nightly cycle. Enable autopilot via `gbrain autopilot enable`. Set the cost cap in your `~/.gbrain/config.json` (`autopilot.max_usd_per_day: 2.00`). Pick a daily prep skill that matches your role (the founder, sales, recruiting, or research patterns above). Wire it on cron. Let it run for three nights. Compare what the brain produced to what you would have written manually.

**Days 26 to 30.** Connect your agent. If you run OpenClaw or Hermes, follow the per-client guide in [`docs/mcp/`](https://github.com/garrytan/gbrain/blob/main/docs/mcp/). Mint an OAuth client with `read,write,admin` scopes (admin is needed for ping/doctor, not for cycle phases; protected phases stay refused). Watch the agent start using the brain as a first-class lookup before it touches an external API. The moment that happens, gbrain has finished installing.

The thirty days are conservative. Power users who already know what their schema should look like collapse the timeline to a weekend. The point of the longer version is that the schema is the long-lived decision, and getting it right means watching your own corpus first and committing only when the pattern is obvious.

# Appendix: the Garry Tan quote everyone misses

One last citation worth including because it is the philosophical seed of the whole project. From a [Stanford CS153 conversation](https://podcastalpha.substack.com/p/stanford-cs153-garry-tan-and-diana) with Diana Hu earlier in 2026:

> "All the expectations that we currently have in society around what a founder can do, what a company can do, what a small team can do, they're actually 1,000x wrong."
>
> — [Garry Tan](https://x.com/garrytan), via [@podcast_alpha_x](https://x.com/podcast_alpha_x/status/2058247840565186733)

That quote is the why behind why gbrain exists at all. A 1,000x-amplified founder needs a memory system that scales with the amplification. A 1,000x-amplified founder ingests meetings, emails, tweets, voice calls, and original ideas at a rate no human-only system can keep up with. The brain has to do half the work or the multiplier breaks. gbrain is the half. The other half is the harness. Together they are the bet that the small team of 2027 can run at a scale that would have needed a forty-person ops org in 2017.

If that bet is right, the right memory layer is not the one you rent. It is the one you own. The README has been saying this in plain English for six weeks: "GBrain is what happens when you write the brain you actually wanted to have." The post you just read is the long version of that sentence.

## Sources

- [garrytan/gbrain — repository (18,652 stars, 2,617 forks, MIT)](https://github.com/garrytan/gbrain)
- [gbrain — README](https://github.com/garrytan/gbrain/blob/main/README.md)
- [gbrain — RETRIEVAL.md (the +31 P@5 graph lift)](https://github.com/garrytan/gbrain/blob/main/docs/architecture/RETRIEVAL.md)
- [gbrain — what-schemas-unlock.md (the 7 use cases)](https://github.com/garrytan/gbrain/blob/main/docs/what-schemas-unlock.md)
- [gbrain — topologies.md (architecture)](https://github.com/garrytan/gbrain/blob/main/docs/architecture/topologies.md)
- [gbrain — CHANGELOG.md (v0.40.x releases)](https://github.com/garrytan/gbrain/blob/main/CHANGELOG.md)
- [gbrain — ORIGIN.md (the design story)](https://github.com/garrytan/gbrain/blob/main/docs/ethos/ORIGIN.md)
- [gbrain-evals — BrainBench harness (sibling repo)](https://github.com/garrytan/gbrain-evals)
- [Vectorize.io — GBrain Review (May 8 2026)](https://vectorize.io/articles/gbrain-review)
- [Y Combinator YouTube — How to Make Claude Code Your AI Engineering Team (186K views)](https://www.youtube.com/watch?v=wkv2ifxPpF8)
- [Stanford CS153 — Garry Tan, Diana Hu (founders & AI)](https://podcastalpha.substack.com/p/stanford-cs153-garry-tan-and-diana)
- [Reddit — Garry Tan Runs 100,000 Pages of Brain, 100+ Skills, and One Thin Harness](https://www.reddit.com/r/AIAgentsInAction/comments/1t9d5lb/garry_tan_runs_100000_pages_of_brain_100_skills/)
- [Hacker News — GBrain – most important idea of the year](https://news.ycombinator.com/item?id=48091611)

---

Canonical: https://www.thedeepfeed.ai/posts/2026-05-24-gbrain-postgres-knowledge-graph-agent-memory/
Site: https://www.thedeepfeed.ai
Full corpus: https://www.thedeepfeed.ai/llms-full.txt