# GBrain on the VPS: install + Hermes MCP wiring (2026-08-06)

GBrain (`github:garrytan/gbrain`, ~27.8k stars, actively maintained) is a
PGLite/Postgres-backed vector+keyword knowledge engine with an MCP server,
explicitly built for OpenClaw/Hermes agents. On this deployment it sits on
top of the existing Syncthing-synced vault (`/root/.hermes/vault`) as a
retrieval engine — a THIRD memory layer alongside built-in memory/skills and
the direct vault MCP (port 9123), not a replacement for either. Rob's framing:
"Obsidian = static storage layer, GBrain = intelligent compute/retrieval on top."

## Install (read-only /root applies — see installing-clis-on-readonly-home)

```bash
export BUN_INSTALL=/root/.hermes/bun
curl -fsSL https://bun.sh/install | bash
export PATH="$BUN_INSTALL/bin:$PATH"
export BUN_INSTALL_GLOBAL_DIR=/root/.hermes/bun/install/global
bun install -g github:garrytan/gbrain        # NEVER npm registry — the npm
                                             # 'gbrain' package is a squatter
export GBRAIN_HOME=/root/.hermes/gbrain-home # honored; avoids wrapper
gbrain init --embedding-model openai:text-embedding-3-large \
  --path /root/.hermes/gbrain-home/brain
gbrain doctor --json                          # verify
```

- Bun global installs sometimes block the postinstall schema migration;
  recovery per repo issue #218: `gbrain apply-migrations --yes`.
- `gbrain init` prompts nothing with `GBRAIN_NO_ONBOARD_NUDGE=1`.
- Multiple embedding providers env-ready (OPENAI + OPENROUTER keys both in
  `/root/.hermes/.env`) makes init abort with "disambiguate by passing
  --embedding-model" — pin one explicitly.

## ⚠️ Cost governance (mandatory operator step, per GBrain's own AGENTS.md)

`gbrain init` silently defaults to search mode `tokenmax` — the MOST expensive
tier. Per-query cost @ 10K queries/mo: conservative $40–200, balanced
$100–500, tokenmax $200–1,000 depending on model (25× corner spread).
GBrain's own install protocol says the agent MUST present this matrix and let
the operator choose. Fix after init:

```bash
gbrain config set search.mode conservative   # Rob chose this 2026-08-06
```

Default embedding provider is ZeroEntropy (paid) since v0.36.2;
`openai:text-embedding-3-large` works and reuses the existing OPENAI_API_KEY.
Embedding is NEVER free/local — "no token costs" only means no Hermes-side
tokens; embeddings bill to the provider key. Overnight consolidation/dream
cycle additionally calls a chat model — keep it OFF until retrieval proves out.

## Vault import + freshness

```bash
gbrain import /root/.hermes/vault/   # 56 pages, 116 chunks, ~20s
gbrain embed --all                    # first time
gbrain embed --stale                  # incremental thereafter
```

Doc-suggested `*/15 cron full import` is wasteful; `embed --stale` is the
incremental path. Freshness cron via the Hermes cron tool was deferred until
the MCP connection is confirmed live.

## Wiring into Hermes (mcp_servers, not Claude mcpServers JSON)

Hermes reads `mcp_servers:` in `/root/.hermes/config.yaml` (docs:
/user-guide/features/mcp). Generic guides showing a `{"mcpServers": ...}` JSON
block are Claude-Code format — wrong here.

- The `patch`/`write_file` tools REFUSE to write config.yaml (agent guard).
  `hermes config set mcp_servers.gbrain.command <path>` works BUT
  `config set` writes scalars as strings — `args: '["serve"]'` lands as a
  quoted string and Hermes will fail to spawn it. Fix requires a manual edit
  to a real YAML flow list `args: ["serve"]` + gateway restart.
- GBrain needs env (PATH, GBRAIN_HOME) on every invocation → stdio wrapper
  script at `/root/.hermes/bin/gbrain-mcp`:

  ```bash
  #!/usr/bin/env bash
  export PATH="/root/.hermes/bun/bin:$PATH"
  export GBRAIN_HOME="/root/.hermes/gbrain-home"
  export GBRAIN_NO_ONBOARD_NUDGE=1
  exec /root/.hermes/bun/bin/gbrain "$@"
  ```

  `mcp_servers.gbrain.command` points at the wrapper, `args: ["serve"]`.
- Verify the server independently before touching Hermes: speak JSON-RPC over
  stdio (`initialize` → `notifications/initialized` → `tools/list`); a healthy
  serve exposes ~106 tools (get_page, query, search, traverse_graph, ...).
- Adding an MCP server requires a GATEWAY restart for discovery — which cannot
  be done from inside a gateway session (runtime guard). Agent preps, user
  runs the restart.

## Division of labor (agreed with Rob)

- Built-in memory/skills: agent-curated facts + procedures (8K governance).
- Vault MCP (9123): literal read/write for Claude Desktop.
- GBrain: semantic/hybrid retrieval over the synced vault.
Sprawl risk is real — GBrain is on a one-week eval. Retrieval quality caveat
seen on day one: a query for "RRR positioning" ranked Job Seeker docs above
the actual positioning file (keyword-vs-semantic tension); needs the
consolidation passes / tuning before trusting rankings.

## Live verification + the routing insight (2026-08-06, post-wiring)

**End-to-end test PASSED via Telegram**: asked an obscure question whose
answer is NOT in pinned memory ("what did the Stripe JD notes say about comp
bands?") — the agent reached for GBrain unprompted and returned sourced,
correct numbers ($191.4K–287.2K Campaign Ops, $145K–217.6K PMM-Growth).
Earlier same-session tests where the agent "answered correctly" were NOT
wiring tests: the RRR ad-spend rule lives in pinned memory, so the agent
answered from context with zero tool calls. **The lesson for evaluating ANY
retrieval layer: correct answers prove nothing when the fact is pinned —
the only valid test is a question whose answer exists ONLY in the vault.**
Tool-routing also degrades with weaker models; the Telegram fallback chain
(kimi-k3 → minimax-m3) routes tools worse than frontier models.

**PGLite single-writer constraint (architectural, drives cron design):**
exactly ONE process may hold the brain DB. While the gateway's `gbrain serve`
is up (24/7), ALL CLI commands (`stats`, `import`, `embed`) refuse with
"database is already open through gbrain serve (MCP, PID N)", and even a raw
stdio JSON-RPC probe from a second process dies with BrokenPipe (the child
exits before `initialize`). Lock file:
`/root/.hermes/gbrain-home/brain/.gbrain-lock` (PID-recorded; stale locks
survive crashes). **Consequences:**
- Re-index (`import` + `embed --stale`) requires either stopping the gateway
  first, or issuing the ops through the MCP server itself. Check whether the
  MCP tool list even exposes import/embed — the ~106 tools seen are
  read/query + page CRUD (get_page/put_page/query/search/traverse_graph/...);
  bulk refresh ops were NOT visible. `gbrain --help` shows `sync --watch`
  and a cron-friendly `dream` command, but both are CLI → same lock problem.
- Decision deferred to Rob: brief gateway-stop refresh windows vs
  on-demand/manual re-index only. No freshness cron was created on day one.
- The source guide's system-crontab `*/15 bun run cli.js import` was never
  adopted — it assumed multi-process Postgres, not PGLite.

**Pre-flight vetting of third-party guides paid off (Rob's standing rule,
re-confirmed):** the guide that proposed GBrain was vetted line-by-line
against the environment before adoption — caught stripped command URLs
(`curl -fsSL | bash`, `git clone` with no repo), Claude-format `mcpServers`
JSON (wrong for Hermes), an outdated `init --pglite` flag, a wasteful 15-min
full-import cron, and the already-running Syncthing layer the guide proposed
installing fresh. Claim/verified-status table is the presentation format
Rob responds to.
