+8 1bc579916b v0.36.1.1 fix-wave: community PR triage + 28 atomic fixes (#1182)
* fix(sync): accept .tf / .tfvars / .hcl in CODE_EXTENSIONS

Terraform repos were invisible to `gbrain sync --strategy code` because
the three HCL-family extensions never reached the file walker. Silent
data loss — the user thinks the sync covered the repo but the IaC layer
was dropped on the floor.

detectCodeLanguage() returns null for these extensions, so the chunker
falls back to recursive (no tree-sitter grammar for HCL) — the same
path toml/yaml take.

Closes #878.

Co-Authored-By: johnybradshaw <johnybradshaw@users.noreply.github.com>

* fix(upgrade): run `bun update gbrain` from Bun's global install root

`gbrain upgrade --strategy bun` was failing on canonical
`bun install -g github:garrytan/gbrain` installs because `execSync('bun
update gbrain')` ran in the user's shell cwd. Bun's update operates on
whatever package.json it finds via cwd-walk, so a user not standing in
the global root got "No package.json, so nothing to update".

resolveBunGlobalRoot() returns the right directory:
1. `$BUN_INSTALL/install/global` when set (operator override).
2. `~/.bun/install/global` (Bun's documented default).
3. Walk up from realpath(argv[1]) looking for `node_modules/gbrain` —
   handles non-standard installs without trusting argv naming.

execFileSync replaces execSync (no shell), with cwd pinned. Error path
prints the exact `cd && bun update` recovery command instead of a vague
hint.

Closes #1029. Cherry-picked from PR #1032.

Co-Authored-By: mvanhorn <mvanhorn@users.noreply.github.com>

* fix(config): redact sensitive values in `config set` output (closes #892)

`gbrain config set openai_api_key sk-...` was echoing the full key to
stderr via `console.log('Set %s = %s', key, value)`. Shell scrollback and
tmux scroll buffers commonly retain stderr for hours; a screen-share or
shoulder-glance during set leaked the secret.

The `show` path already redacted but used a naive `.includes('key')`
substring check that would mask 'monkey' or 'parsekey' (no false-negative
but ugly).

Single source of truth: `isSensitiveConfigKey()` uses a word-boundary
regex (`(^|[._-])(key|secret|token|password|pwd|passwd|auth)([._-]|$)/i`)
so 'openai_api_key' matches but 'monkey' doesn't. `redactConfigValue()`
composes the postgresql:// URL redactor + sensitive-key check, used by
both `show` and `set`. Helpers exported for unit tests.

Closes #892. Cherry-pick of @sharziki's PR #918 (config.ts hunk only —
the extract.ts walker change in that PR is unrelated and tracked in #202).

Co-Authored-By: sharziki <sharziki@users.noreply.github.com>

* fix(oauth): throw InvalidTokenError so bearerAuth returns 401, not 500

`verifyAccessToken` was throwing bare `Error` on expired or invalid
tokens. The MCP SDK's `requireBearerAuth` middleware catches
`InvalidTokenError` and returns 401 with WWW-Authenticate; bare Error
falls through to 500. Result: legitimate clients with stale tokens hit
500-not-401, so token-refresh logic (which keys off 401) never fires.

Two call sites in verifyAccessToken: token-expired path and
invalid-token path. Both now throw InvalidTokenError. Existing tests
continue to pass because they assert on the throw, not the message class.

Closes #935. Cherry-picked from PR #1012.

Co-Authored-By: Aashiqe10 <Aashiqe10@users.noreply.github.com>

* fix(serve): return 405 on GET /mcp instead of 404

MCP Streamable HTTP spec says GET /mcp opens an optional SSE backchannel
for server-initiated messages. gbrain's transport is stateless and
doesn't push server-initiated messages, so per spec we MUST return 405
with Allow: POST, DELETE — not 404. Probing clients (claude.ai, etc.)
distinguish "endpoint exists, no SSE channel" from "endpoint missing"
on this status code; 404 makes them give up.

Cherry-picked from PR #1076.

Co-Authored-By: lukejduncan <lukejduncan@users.noreply.github.com>

* fix(doctor): resolve whoknows fixture from module location, not cwd

`gbrain doctor` warned about a missing whoknows fixture for every install
that wasn't standing in the gbrain source repo at run time — which is
everyone. The check used `process.cwd()` to locate the fixture, so any
real user (running doctor against `~/.gbrain`) saw a spurious warning.

`resolveWhoknowsFixturePath()` walks up from `import.meta.url` looking
for the source-repo signature (`src/cli.ts` + `skills/RESOLVER.md`),
respects `GBRAIN_WHOKNOWS_FIXTURE_PATH` env override (absolute or
cwd-relative), and returns null with an actionable warning when the
fixture can't be located.

Closes #969. Cherry-picked from PR #1034.

Co-Authored-By: mvanhorn <mvanhorn@users.noreply.github.com>

* fix(frontmatter): centralize --fix backups under ~/.gbrain/backups/

`gbrain frontmatter validate --fix` and `gbrain frontmatter generate
--fix` wrote `<file>.bak` siblings into the source tree. Users running
gbrain over a brain repo found .bak files scattered through people/,
companies/, etc. that broke gitignore expectations and showed up in
`git status` after every fix pass.

Backups now land under `~/.gbrain/backups/frontmatter/<run-id>/<rel>.bak`
with an iso-week-sorted run-id so a multi-fix session keeps the same
parent directory. Backup directory + per-file structure mirrored from
the original file's relative path. The .bak safety contract is intact
for both git and non-git brain repos.

Also adds `--include-catch-all` opt-in to `frontmatter generate` so the
default catch-all rule (`type: note`) is no longer applied to arbitrary
workspace documents that happen to live under a brain root.

Closes #902. Cherry-picked from PR #903.

Co-Authored-By: 100yenadmin <100yenadmin@users.noreply.github.com>

* fix(config): use path.isAbsolute() for GBRAIN_HOME on Windows

The GBRAIN_HOME validator rejected every valid Windows path (`C:\\Users\\...`,
`D:\\gbrain`, etc.) because it used `trimmed.startsWith('/')` to check for
absoluteness — only POSIX absolute paths pass that. `path.isAbsolute()` is
the cross-platform check.

Same fix for the `..` traversal check: split on both `/` and `\` so
Windows path separators don't sneak `..` through.

Closes #1019. Cherry-picked from PR #1083.

Co-Authored-By: sharziki <sharziki@users.noreply.github.com>

* fix(ai): warn only for the configured embedding provider, not all recipes

Gateway construction was warning on stderr for every recipe with an
embedding touchpoint missing max_batch_tokens — including providers the
brain isn't using. Users on Voyage saw noise about OpenAI / Google /
DashScope / etc. recipes that never get loaded.

Filter the warning to recipes whose provider id is referenced by
`embedding_model` or `embedding_multimodal_model` in the active config.
The structural protection against forgetting max_batch_tokens stays in
place for the recipes that actually run; the noise for unrelated recipes
goes away.

Cherry-picked from PR #1117.

Co-Authored-By: hnshah <hnshah@users.noreply.github.com>

* fix(sync): skip git pull when repo has no origin remote

`gbrain sync` ran `git pull` unconditionally and printed scary stderr
on every cycle for brains that have no `origin` remote (local-only
workflows, single-machine setups, brains initialized via `gbrain init
--pglite` against an arbitrary directory). The pull failed harmlessly
but the noise was confusing and made operators think sync was broken.

`hasOriginRemote()` probes `git remote get-url origin` with stdio
ignored; on failure (`no such remote`), skip the pull, print a single
informational line, and proceed with the local working tree.

Cherry-picked from PR #1119.

Co-Authored-By: hnshah <hnshah@users.noreply.github.com>

* fix(query): drain cache writes before CLI exit

The query cache write was fired with `void promise.catch(...)` — true
fire-and-forget. On a fast CLI invocation (`gbrain query <q>` exits in
~50ms), the process terminates before the cache write commits. Result:
the cache effectively never warms from CLI use; every query is a miss.

`awaitPendingSearchCacheWrites()` tracks each in-flight cache write in a
module-level Set. The CLI dispatcher awaits the set after `query`
finishes formatting output but before the process exits. MCP server path
unchanged (long-lived process, fire-and-forget remains correct).

Cherry-picked from PR #1125.

Co-Authored-By: hnshah <hnshah@users.noreply.github.com>

* fix(backlinks): dedupe (source, target) pairs within a single source page

A source page that mentions the same entity N times produced N
duplicate "Referenced in" lines on the target. `extractEntityRefs`
returns one EntityRef per occurrence, and the per-ref `hasBacklink`
check reads a snapshot of `target.content` that's frozen at outer
scope — so every iteration sees "no backlink yet" and appends another
gap. The cumulative effect on a long meeting note with multiple
mentions of the same person was visible in PRs landing 3-5 identical
Timeline entries.

Track seen target slugs per source page; cap gaps at one pair.

Cherry-picked from PR #967 with a current-master regression test
covering both markdown-link and Obsidian-wikilink formats in the same
source page.

Co-Authored-By: p3ob7o <p3ob7o@users.noreply.github.com>

* fix(dream): audit backlinks without mutating pages during cycle

The dream/autopilot maintenance cycle ran the backlinks phase in 'fix'
mode, which writes "Referenced in" timeline bullets into entity pages
every sync. The graph extractor + auto-link path is the canonical link
store during sync/dream/autopilot — the legacy filesystem fixer wrote
markdown that fought with both the user's manual edits and the graph
layer's own timeline.

Cycle now runs backlinks in 'check' mode (audit-only); the materializer
remains available via `gbrain check-backlinks fix` for users who really
want markdown backlinks committed to disk.

Cherry-picked from PR #1027.

Co-Authored-By: sliday <sliday@users.noreply.github.com>

* fix(autopilot --install): source ~/.zshenv before zshrc/bashrc

zshenv is the canonical place for env vars in zsh on macOS — zshrc is
sourced only for interactive shells, so vars exported in zshrc don't
reach a non-interactive subprocess like the autopilot wrapper. Users
who exported GBRAIN_DATABASE_URL, OPENAI_API_KEY, or ANTHROPIC_API_KEY
in zshrc and assumed autopilot would inherit them hit silent missing-
secret failures on the LaunchAgent.

Source ~/.zshenv first (always reaches non-interactive shells per zsh
docs), then fall back to ~/.zshrc / ~/.bashrc for users on other
profile conventions.

Cherry-picked from PR #966.

Co-Authored-By: p3ob7o <p3ob7o@users.noreply.github.com>

* fix(apply-migrations): return exit 0 on list/dry-run/up-to-date

`gbrain apply-migrations list`, `gbrain apply-migrations --dry-run`, and
the "All migrations up to date" path were returning from the async
function but never calling `process.exit(0)`. The CLI dispatcher in
cli.ts treated the implicit fall-through as exit 1 when the parent
process inspected status via shell scripts, breaking automation that
gates on `apply-migrations list && do-something`.

Three call sites: list, dry-run, and the no-op path. All three now
exit(0) explicitly.

Cherry-picked from PR #1062.

Co-Authored-By: nezovskii <nezovskii@users.noreply.github.com>

* fix(sync): scope auto-embed to source on incremental syncs

`gbrain sync --source-id X` triggered auto-embed for the affected slugs
but `runEmbed` ran with no `--source` flag, so it fell back to the
default source. For non-default-source syncs the page row lives at
(sourceId, slug) — the embed code saw "Page not found" for the right
slug under the wrong source, swallowed the error as best-effort, and
the sync result reported `embedded: 0` for the wrong reason.

`buildAutoEmbedArgs(slugs, sourceId)` is the new helper: when sourceId
is set, prepends `--source X`. Exported for the regression test.

Pairs with the upcoming source-id write-path audit (P1 #8). Cherry-picked
from PR #1120.

Co-Authored-By: hnshah <hnshah@users.noreply.github.com>

* fix(query): honor source_id with no-expand for cross-source search

Two related corrections:

1. `gbrain query --no-expand` parsed `--no-expand` as the literal key
   `no_expand` instead of negating the boolean `expand` param. Result:
   the flag was silently ignored and expansion always ran. Now any
   `--no-<key>` where `<key>` is a boolean param flips it false.

2. The `query` op's source-id resolution treated `ctx.sourceId` as
   authoritative, so an explicit per-call `source_id` was overridden by
   the federated read scope. Now per-call `source_id` wins;
   `source_id=__all__` is an explicit opt-out for local cross-source
   search.

Cherry-picked from PR #1124.

Co-Authored-By: hnshah <hnshah@users.noreply.github.com>

* fix(doctor): child-table orphan detection (closes #1063)

The autopilot orphans phase detects orphan PAGES (no inbound links via
page-graph) but never scans FK-child tables. After a bulk delete or a
pre-FK-migration code path, orphan rows can persist indefinitely in
content_chunks, page_versions, tags, takes, raw_data, timeline_entries,
or links — all declared ON DELETE CASCADE, so any orphan row is
unexpected.

`childTableOrphansCheck` enumerates 10 FK columns across 8 tables:
- 8 NOT NULL columns (cascade): any value not in pages.id is an orphan.
- 2 nullable SET NULL columns (links.origin_page_id, files.page_id):
  NULL is valid; only NOT-NULL-but-missing-in-pages counts.

Surfaces paste-ready cleanup SQL when orphans are found.

Cherry-picked from PR #1064.

Co-Authored-By: vincedk-alt <vincedk-alt@users.noreply.github.com>

* fix(autopilot,cycle): stop respawn-storm from steady-state 'partial' cycles

Two compounding bugs under KeepAlive=true:

1. Autopilot tripped its circuit breaker on cycle.status === 'partial',
   not just 'failed'. 'partial' means at least one phase warned/failed
   while others ran — a soft signal, not fatal. On every cycle that
   warned, autopilot logged a failure and the supervisor respawned the
   worker.

2. The orphans phase emitted 'warn' when `count > 20` orphan pages.
   That threshold was tuned for small dev brains; on any corpus past a
   few hundred pages it fires every cycle in steady state. Together
   with bug 1, this produced visible respawn storms.

Fix:
- Autopilot trips only on cycle.status === 'failed'.
- Orphans phase warns by ratio: orphans / total_pages > 0.5 (the real
  "your graph fell apart" signal), not by absolute count.

Cherry-picked from PR #1113.

Co-Authored-By: sergeclaesen <sergeclaesen@users.noreply.github.com>

* fix(ai): reject partial embedding responses before indexing

`embedSubBatch` only validated the FIRST embedding's dimension and never
asserted the response length matched the input length. If a provider
returned fewer embeddings than requested (rate-limit truncation,
malformed response, etc.), the gateway silently indexed an offset-shifted
result — every page after the missing index got the embedding of a
different page's chunk.

Two new guards:
1. `result.embeddings.length === texts.length` — fail loud if any count
   mismatch, with a paste-ready retry hint.
2. Validate dim on EVERY embedding, not just the first.

Cherry-picked from PR #926.

Co-Authored-By: 100yenadmin <100yenadmin@users.noreply.github.com>

* fix(serve): admin register-client supports auth_code + PKCE public clients

The admin dashboard's /admin/api/register-client endpoint hardcoded
client_credentials and ignored grantTypes, redirectUris, and
tokenEndpointAuthMethod. Result: you couldn't register a browser-based
PKCE client (claude.ai Custom Connector, Cursor, etc.) through the
dashboard — only confidential machine-to-machine clients worked.

Pass grantTypes / redirectUris through to registerClientManual. When
tokenEndpointAuthMethod === 'none', NULL out client_secret_hash so the
SDK's clientAuth middleware skips the hash-vs-plaintext compare that
would otherwise reject the no-secret PKCE flow.

Cherry-picked from PR #1077.

Co-Authored-By: lukejduncan <lukejduncan@users.noreply.github.com>

* fix(extract-facts): treat slugs:[] as no-op, not unscoped full-walk

`runExtractFacts` checked `opts.slugs && opts.slugs.length > 0` to
decide between scoped and full-brain walk. Both `undefined` (caller
omits → full walk intended) AND `[]` (sync no-op → zero work intended)
fall through to the same `else` branch and triggered
`engine.getAllSlugs()`.

On a multi-thousand-page brain, the unintended full walk exceeded
the autopilot-cycle ~600s timeout and dead-lettered the job — visible
in production as `[cycle.extract_facts] start` followed by silence
until `Autopilot stopping (cycle-failure-cap)`.

Use presence (`opts.slugs !== undefined`), not truthiness, to
distinguish the two modes. Empty array is a real incremental no-op.

Closes #1096. Three regression cases in test/extract-facts-phase.test.ts:
slugs=[] no-op, slugs=undefined still walks, slugs=['a'] walks just one.

Co-Authored-By: navin-moorthy <navin-moorthy@users.noreply.github.com>

* fix(serve): embed admin/dist into binary; serve from manifest (closes #1090)

Pre-fix, /admin returned 404 on every globally-installed binary because
serve-http.ts:780 resolved admin/dist via process.cwd(). The admin SPA
files are checked into git but `bun build --compile` does NOT embed
arbitrary directories — only assets imported via `with { type: 'file' }`
ESM imports land in the compiled binary.

Wire:

- scripts/build-admin-embedded.ts walks admin/dist/, emits
  src/admin-embedded.ts with one `with { type: 'file' }` import per
  file + a manifest map (request path → resolved path + mime).
  Auto-invoked by `bun run build:admin`.

- src/admin-embedded.ts is the auto-generated module. Bun resolves
  every file: import to a path that works at runtime inside the
  compiled binary (same pattern as src/core/chunkers/code.ts WASM
  imports).

- serve-http.ts switches to two-tier resolution: cwd-relative
  admin/dist for dev (Vite hot-rebuild), embedded manifest otherwise.
  Embedded path reads bytes lazily and caches per-asset for the
  lifetime of the process.

- scripts/check-admin-embedded.sh CI gate re-runs the generator and
  fails on drift (mirrors check-wasm-embedded.sh). PRs that rebuild
  admin/dist but forget to regenerate the embedded module fail loud.

- package.json wires build:admin-embedded + check:admin-embedded.

Closes #1090.

* test(source-id): lock in routing regression coverage (closes #891 #978 #1078)

Audit of every page write path (sync, embed, extract, dream, autopilot,
wikilinks, tags, chunks) confirmed that sourceId already threads
correctly through importFromContent → engine.putPage → SQL INSERT
since v0.18.0. The original bug reports from #891, #978, #1078 were
real at the time and got swept by the multi-source refactor; today's
master is correct.

This commit locks in that correctness with six PGLite regression cases
(no Postgres fixture needed; runs in CI everywhere):

1. importFromContent({sourceId:"work"}) lands at source_id=work, not
   the silent 'default' fallback.
2. Two sources hold the same slug independently.
3. Omitting sourceId falls through to 'default' (legacy contract).
4. Chunks land under the requested source.
5. Tags land under the requested source.
6. FK integrity smoke (originally #1078).

The earlier issue reports stay closed by the existing threading; this
suite ensures any future refactor of the write path can't silently
re-introduce the wrong-source-default bug. The 90-minute write-path
audit budget from the plan resolves here.

* fix(apply-migrations): unblock PGLite chain (closes #1100)

`gbrain apply-migrations --yes` was wedging on the v0.11.0 (Minions)
schema phase for PGLite installs. Two compounding bugs:

1. `apply-migrations` pre-flight schema-version warning connects to
   PGLite to read config.version, then disconnects. The brief lock
   hold races with downstream subprocess spawns that try to re-acquire
   it; the 30s lock timeout fires before the parent fully releases.
   Pre-flight is a *warning*; on PGLite it adds no information the
   orchestrators don't already handle. Skip the probe for PGLite.

2. v0.11.0 phase A spawned `gbrain init --migrate-only` as an execSync
   subprocess to apply schema migrations. PGLite is single-writer;
   the subprocess inherits HOME and tries to lock the same DB. On
   Postgres this works (concurrent connections OK); on PGLite it
   deadlocks. Route in-process for PGLite — create + connect +
   initSchema + disconnect directly, skipping the subprocess hop.
   Postgres keeps the legacy execSync path.

Verified: fresh PGLite install now walks the full migration chain
through v0.32.2 (Facts SoR) and lands "All migrations up to date" on
re-run.

Closes #1100.

* fix(serve): bootstrap token env override + suppress flag (closes #1024)

`gbrain serve --http` regenerated the admin bootstrap token on every
restart and printed it to stderr. In supervisor-managed production
deployments (LaunchAgent, systemd, k8s) every restart leaks the value
into log aggregators and rotates the access for any agent that paste-
copied it.

Two new knobs:

- **GBRAIN_ADMIN_BOOTSTRAP_TOKEN** env var: when set, used as the
  bootstrap secret instead of a fresh per-process token. Validated:
  must match `^[A-Za-z0-9_-]{32,}$` (32-char minimum), else refuse to
  start with a paste-ready generator hint. Failing closed beats
  silently accepting a weak token.

- **--suppress-bootstrap-token** CLI flag: suppresses the printed
  token line entirely. Operator takes responsibility for tracking the
  value out-of-band.

Startup banner now reflects the chosen source:
- `Admin Token: suppressed` when the flag is set.
- `Admin Token: from $GBRAIN_ADMIN_BOOTSTRAP_TOKEN` when env-sourced.
- Full token print only when both are absent (default behavior, dev
  installs).

Closes #1024.

Co-Authored-By: billy-armstrong <billy-armstrong@users.noreply.github.com>

* fix(config): migrate legacy 'provider' + 'model' to 'embedding_model'

Pre-v0.32 docs and some community templates used a config shape:

  { "provider": "voyage", "model": "voyage-4-large" }

The canonical shape (since the v0.31.12 gateway seam) is:

  { "embedding_model": "voyage:voyage-4-large" }

Users on the legacy shape hit silent fallthrough to the hardcoded
OpenAI default; sync + embed errored out with "OpenAI embedding
requires OPENAI_API_KEY" regardless of their actual provider config.

loadConfig() now translates the legacy keys at parse time:
- emits a one-line stderr nudge with the paste-ready canonical key
- preserves the rest of the config unchanged
- skipped when `embedding_model` is already set (forward-compat)

Closes #1086.

Co-Authored-By: jeunessima <jeunessima@users.noreply.github.com>

* chore(test): quarantine upgrade tests (process.env mutation)

PR #1032's cherry-picked tests use the static-snapshot + try/finally
pattern for env vars instead of the project's withEnv() helper. The
test-isolation lint catches process.env mutations outside withEnv to
prevent cross-test leakage in parallel runs.

Renaming to *.serial.test.ts (the quarantine convention) is the
documented out: runs sequentially, no cross-file race. A future cleanup
PR can migrate the tests to withEnv() and drop the quarantine.

* fix(test): update brain-writer .bak assertion for centralized backup path

The v0.36.x frontmatter backup change (bd60cdf6closes #902) moved
.bak files from sibling-of-source to ~/.gbrain/backups/frontmatter/...
The old test still asserted on the sibling path, so CI failed even
though the production behavior was correct.

Updated assertion contract: backup lands under the injected backupRoot
(test-isolated), the returned backupPath ends in .bak and exists, and
no sibling .bak is created next to the source file. The pre-fix
sibling-path is now a negative assertion.

* chore: bump version and changelog (v0.36.1.0)

v0.36.1.0 — community fix wave (28 atomic fixes + 22 PRs closed as
already-shipped + 14 issues triaged).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(fix-wave): close test gaps surfaced by post-ship audit

After the fix-wave shipped, an audit found 11 commits with no new test
file. Some were inherently structural (build pipelines, shell content)
or had existing test coverage that worked either way; others had real
regression risk with no guard. This commit closes the gaps that matter.

New regression tests for:

- OAuth `verifyAccessToken` throws `InvalidTokenError` (not bare Error)
  on both expired and unknown token paths. Pre-fix, the SDK's
  `requireBearerAuth` middleware fell through to 500 instead of 401 →
  client token-refresh logic never fired (#935).

- `loadConfig` translates legacy `{provider, model}` config shape to
  the canonical `embedding_model: <provider>:<model>`. 3 cases: pure
  legacy → migrated; canonical wins over legacy when both present;
  canonical-only is untouched. Pre-fix, Voyage/Cohere/Mistral users
  silently fell through to OpenAI (#1086).

- `configDir` rejects relative paths; rejects `..` segments via both
  separators (regression guard for the Windows path acceptance fix
  #1019 / cherry-pick #1083).

- `resolveBootstrapToken` (new exported helper extracted from
  `runServeHttp`). 9 cases: unset env generates fresh, valid env
  accepted, hyphens/underscores accepted, < 32 chars rejected, special
  chars rejected, whitespace trimmed, empty string rejected, 32-char
  boundary accepted, 31-char one-short rejected. Security-critical
  validation surface (#1024).

- GET /mcp returns 405 with `Allow: POST, DELETE` (E2E case in
  `serve-http-oauth.test.ts`). Pre-fix, claude.ai and other probing
  MCP clients saw 404 and gave up (#1076).

- apply-migrations `process.exit(0)` on list / dry-run / up-to-date
  paths. Source-shape assertion locks the rule in; shell scripts
  gating on `$?` work (#1062).

- Autopilot wrapper sources `~/.zshenv` BEFORE `~/.zshrc`. zshenv is
  the canonical place for env vars in non-interactive zsh; without
  this ordering, LaunchAgent subprocesses never inherit secrets
  exported in zshrc (#966).

- `test/fix-wave-structural.test.ts` consolidates source-shape
  regression guards for fixes whose behavior is hard to runtime-test
  without heavy mocking: query cache drain (#1125), admin embed
  manifest + handler (#1090), admin register-client PKCE branch
  (#1077), PGLite v0.11.0 phase A in-process routing (#1100), query
  `--no-expand` negation (#1124). 9 source-grep assertions.

Refactored `runServeHttp` to extract `resolveBootstrapToken` as a pure
helper. The boot path now consumes the helper's tagged-union result
({kind:'ok'|'error'}); side effects (`process.exit`, `console.error`)
moved to the caller. Unit-testable without spinning up Express.

Test counts: oauth 71 (was 69), config 20 (was 14), apply-migrations
19 (was 18), autopilot-install 5 (was 4), serve-http-bootstrap-token
9 (new file), fix-wave-structural 9 (new file). Net: +28 cases across
6 files; +1 new exported function with full coverage.

Remaining audit gaps (deferred):
- e82dda0a admin embed E2E (post-deploy curl smoke covers this)
- d93fa81d apply-migrations PGLite chain E2E (already smoke-tested
  manually in the original commit; subprocess test would be flaky in
  CI without DATABASE_URL gating)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test: close the two deferred E2E gaps from the post-ship audit

Both gaps now have real behavior coverage. No DATABASE_URL needed (PGLite
engine), so they run in standard unit CI alongside the rest of the suite.
Serial quarantine because both spawn subprocesses + bind ports / write
tmpdirs.

test/admin-embed-spawn.serial.test.ts (4 cases, ~6s wall-clock):
  - Spawns `gbrain serve --http` from a fresh tmpdir so `process.cwd()/
    admin/dist` does not exist — this forces the embedded-manifest
    branch (the one under test). Pre-fix, this exact setup hit 404.
  - GET /admin/ → 200 + SPA shell HTML (title + #root div), content-type
    text/html.
  - GET /admin/index.html → same body via explicit path.
  - GET /admin/agents → SPA fallback returns index.html for deep links.
  - GET /admin/api/stats → NOT 200 (regression guard: SPA fallback must
    not swallow /admin/api/* routes and silently return HTML to a JSON
    client). Closes #1090.

test/apply-migrations-pglite-spawn.serial.test.ts (3 cases, ~25s):
  - Seeds a fresh PGLite config in a tmpdir, runs `gbrain init
    --migrate-only` + `gbrain apply-migrations --yes --non-interactive`.
    Pre-fix this hit "GBrain: Timed out waiting for PGLite lock" because
    apply-migrations' pre-flight probe + v0.11.0's phase A subprocess
    both wanted the single-writer lock.
  - Asserts exit 0, no "Timed out" string, no "Phase A failed" string,
    brain.pglite file written.
  - Re-run case: idempotent — "All migrations up to date" exits 0
    (also locks in the #1062 exit-code fix end-to-end).
  - --list path exits 0 (third leg of the #1062 contract).
  Closes #1100.

Pinned bootstrap token via GBRAIN_ADMIN_BOOTSTRAP_TOKEN env so the
admin test doesn't have to scrape stderr; the startup banner format
is allowed to drift, the /health probe is the readiness contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): consolidate PGLite spawn test to one end-to-end pass

CI failed on test/apply-migrations-pglite-spawn.serial.test.ts (Ubuntu,
bun 1.3.14). The previous shape ran 3 tests × ~3 spawns each. Each
`bun run /abs/src/cli.ts` from a tmpdir cwd pays a full parse/transpile
cost (no near-cwd .bun cache); on Ubuntu CI that compounds past the
runner's per-test budget.

Consolidated to ONE test that exercises the full lifecycle in one
brain: init --migrate-only → apply-migrations --yes → re-run → --list.

Four spawns instead of eight. Local wall-clock: 32s → 11.5s. All four
assertion buckets preserved: no PGLite lock timeout, no Phase A
failure, brain.pglite written, idempotent re-run "All migrations up
to date" exits 0 (#1062 end-to-end), --list exits 0.

Per-test timeout 480_000ms as insurance against the runner's
--timeout=60000 default (bun's API spec: per-test wins).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(diag): dump apply-migrations output when CI exit != 0

The PGLite spawn test passes locally on macOS/bun 1.3.13 in ~11s
end-to-end but fails on Ubuntu/bun 1.3.14 in 4.92s with apply.exitCode
= 1 — fast enough that something is failing early, not timing out.
The runCli helper captured stdout+stderr but never printed them, so
the CI log only showed the bare assertion failure.

This commit prints the captured streams from BOTH init and apply
when the exit code mismatches expectation. After the next CI run we
can read the actual error message and diagnose the Ubuntu-specific
failure mode (likely BUN_INSTALL / HOME / PGLite WASM env quirk).
No behavior change; pure diagnostic output gate on failure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): shim `gbrain` on PATH for PGLite spawn test

Root cause of the Ubuntu CI failure: the v0.11.0 orchestrator's phase B
runs `execSync('gbrain jobs smoke')`. PGLite phase A now routes
in-process (the #1100 fix), but phase B and several follow-up phases
still shell out to the `gbrain` binary on PATH. Locally the binary
resolves via `bun link`; on CI Ubuntu it does not exist on PATH, so
execSync exits 127 → orchestrator returns 'failed' → apply-migrations
exits 1. Test failed at 4.92s with exitCode=1, well before any timeout.

Verified locally by removing ~/.bun/bin/gbrain to simulate CI:
  pre-shim:  apply.exitCode=1 (same as CI)
  post-shim: apply.exitCode=0 in 8.4s

The shim writes a tiny `gbrain` executable to a tmpdir that just
`exec`s `bun run <repo>/src/cli.ts "$@"`. Prepended to PATH for the
spawned subprocesses. Mirrors the production contract (gbrain on
PATH) without depending on `bun link` having run in the CI image.

Diagnostic dump from the previous commit stays — useful insurance for
the next time something silently fails inside a spawned binary.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: johnybradshaw <johnybradshaw@users.noreply.github.com>
Co-authored-by: mvanhorn <mvanhorn@users.noreply.github.com>
Co-authored-by: sharziki <sharziki@users.noreply.github.com>
Co-authored-by: Aashiqe10 <Aashiqe10@users.noreply.github.com>
Co-authored-by: lukejduncan <lukejduncan@users.noreply.github.com>
Co-authored-by: 100yenadmin <100yenadmin@users.noreply.github.com>
Co-authored-by: hnshah <hnshah@users.noreply.github.com>
Co-authored-by: p3ob7o <p3ob7o@users.noreply.github.com>
Co-authored-by: sliday <sliday@users.noreply.github.com>
Co-authored-by: nezovskii <nezovskii@users.noreply.github.com>
Co-authored-by: vincedk-alt <vincedk-alt@users.noreply.github.com>
Co-authored-by: sergeclaesen <sergeclaesen@users.noreply.github.com>
Co-authored-by: navin-moorthy <navin-moorthy@users.noreply.github.com>
Co-authored-by: billy-armstrong <billy-armstrong@users.noreply.github.com>
Co-authored-by: jeunessima <jeunessima@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 20:55:57 -07:00

GBrain

Your AI agent is smart but forgetful. GBrain gives it a brain.

Ask your agent about a meeting from six weeks ago, a paper you skimmed in March, an idea you jotted down late one night. GBrain finds it. And it knows the connections — who attended that meeting, who that paper's author has invested in, what other ideas in your brain it overlaps with.

On LongMemEval, the public benchmark for AI memory systems, the right answer lands in gbrain's top 5 results 97.6% of the time. Better than every comparable system that doesn't pay for a language-model call on every retrieval, at $0.50 per thousand queries.

For relational questions ("who works at this company?", "what did this founder invest in this quarter?"), gbrain's self-wiring knowledge graph makes the answer roughly 4× more relevant than plain vector RAG. The graph isn't hand-curated. Every page write extracts entity references and creates typed edges (works_at, attended, invested_in, founded, advises) with zero language-model calls.

Receipts on the evals →

I'm the President and CEO of Y Combinator, and I use this 16 hours a day. The production brain powering my OpenClaw and Hermes deployments has grown into the largest documented personal-AI knowledge graph in active use:

~100K total items in the brain
~16,000 people pages, auto-enriched
~5,000 companies
~8,000 concepts
~4,000 original essays + ideas + drafts
~3,500 daily notes
~31,000 media items (tweets, books, papers, interviews, films, articles, calls)
~2,200 LLM-conversation transcripts (ChatGPT, Claude, Perplexity, agent forks)
108 cron jobs running autonomously
273 skills in the live agent fork (35 from gbrain bundle + 238 user-built on top)

The agent ingests meetings, emails, tweets, voice calls, books, papers, and original ideas while you sleep. It enriches every person and company it encounters. It fixes its own citations, consolidates memory overnight, detects contradictions in your typed claims about people and companies, and flags trajectory regressions. You wake up and the brain is smarter than when you went to bed.

GBrain is those patterns, generalized. As my personal agent gets smarter, so does yours.

What it is, technically

  • Open-source (MIT) TypeScript CLI + MCP server. One binary, no service to host. Mac and Linux. Requires Bun 1.3+.
  • Local-first. Your brain lives in ~/.gbrain on your machine. Embedded Postgres (PGLite via WASM) so there's no database to install — ready in ~2 seconds. Or point at your own Postgres / Supabase for big brains.
  • Your data stays on disk. Embedding calls hit OpenAI / Voyage / Anthropic only if you configure those keys. Sync, search, and graph extraction all run locally with zero outbound traffic.
  • Speaks MCP. Plugs into Claude Code, Cursor, Windsurf, ChatGPT desktop, and any agent that consumes the Model Context Protocol — stdio for local, HTTP + OAuth 2.1 for multi-user.

LLMs: fetch llms.txt for the doc map, or llms-full.txt for the map with core docs inlined. Agents: start with AGENTS.md (or CLAUDE.md for Claude Code).


Install

Three paths, ordered by how much you're committing.

Path 1 — 60 seconds, just try it (standalone CLI)

git clone https://github.com/garrytan/gbrain.git
cd gbrain && bun install && bun link
gbrain init                     # brain ready in ~2 seconds (PGLite)
gbrain import ~/notes/          # index any directory of markdown
gbrain query "what themes show up across my notes?"

What you'll see — gbrain finds the relevant pages, ranks them by hybrid search, and prints the top hits with snippets:

$ gbrain query "what themes show up across my notes?"
Found 8 results in 124ms (hybrid: vector + keyword + RRF)

1. concepts/durable-systems.md           (score 0.91)
   "Software that survives its maintainers has three properties: ..."
2. essays/2025-thinking-out-loud.md      (score 0.87)
   "I keep coming back to the idea that the best companies ..."
3. meetings/2026-02-12-alice-call.md     (score 0.83)
   "Discussed how Alice's framework for ..."
...

Add gbrain extract all after import to build the knowledge graph (entity links + timeline edges). gbrain dream runs the full 9-phase maintenance cycle.

Path 2 — Plug it into Claude Code / Cursor (MCP)

# Local single-user via stdio MCP
claude mcp add gbrain "$(which gbrain) serve"

# Multi-user via HTTP MCP with OAuth 2.1 + admin dashboard
gbrain serve --http                              # prints admin bootstrap token
gbrain serve --http --bind 0.0.0.0 --public-url https://brain.example.com

Now ask Claude / Cursor "what did Alice and I discuss last quarter?" and it queries your brain. The HTTP server supports OAuth 2.1 (client_credentials, authorization_code with PKCE, refresh rotation, revocation, RFC 7591 DCR). Source-scoped clients (--source dept-x) tie write authority to one source; --federated-read S1,S2,S3 adds orthogonal read scopes. Loopback bind by default — pass --bind 0.0.0.0 to publish.

Path 3 — Full agentic install (~30 min)

If you want the nightly auto-enrichment, cron jobs, and overnight synthesis I described above, the brain needs an agent to drive it.

Then paste this into your agent:

Retrieve and follow the instructions at:
https://raw.githubusercontent.com/garrytan/gbrain/master/INSTALL_FOR_AGENTS.md

It clones the repo, installs GBrain, sets up the brain, scaffolds 35 skills, configures recurring jobs, and asks you for the API keys it needs. Budget ~30 minutes for the conversation.


Receipts on the evals

The lead's numbers come from published, reproducible eval runs. Sibling repo gbrain-evals has the full reports, corpora, and cross-system tables. Glossary so the metric jargon below doesn't get in the way:

  • R@5 — did the right answer land in gbrain's top 5? Higher is better.
  • P@5 — what fraction of the top 5 are actually correct? Higher is better.
  • No LLM in the loop — retrieval uses embeddings + keyword search + ranking, with no language model called per query. Cheaper, deterministic, reproducible.

LongMemEval — the standard public memory benchmark

500 questions across 6 question types, each against a haystack of ~50 conversation sessions. Built by Wu et al., the published yardstick for memory systems.

System R@5 LLM in retrieval? Notes
gbrain-hybrid 97.60% no this run, 2026-05-07
MemPalace hybrid+rerank (held-out) 98.4% yes 0.8pt ahead because they pay for an LLM call per query
MemPalace raw 96.6% no gbrain wins this head-to-head by 1pt
Hindsight 91.4% yes per their release
Stella (academic) ~85% no dense retriever
Contriever (academic) ~78% no dense retriever
BM25 (sparse) ~70% no keyword baseline

Mastra (94.87%) and Supermemory (~99%) publish on this dataset but measure end-to-end QA accuracy with an LLM judge, which is a different thing than "did retrieval find the right session." Not directly comparable; flagged honestly in the cross-system table. Reports + reproduction: 2026-05-07 LongMemEval.

BrainBench — relational queries (in-house corpus)

LongMemEval is conversation-style. The other half of the proof is relational questions over a structured knowledge base. We built a 240-page rich-prose corpus (80 people, 80 companies, 50 meetings, 30 concepts) and wrote 145 relational gold queries — "who attended this meeting?", "what's this person invested in?" — against it. Reproducible, deterministic.

Retrieval system P@5 Δ vs gbrain
gbrain (hybrid + knowledge graph) 49.1%
Vector + keyword RRF (graph turned off) 17.8% 31.4
Keyword search only (BM25) 17.1% 32.0
Vector-only RAG 10.8% 38.4

The knowledge graph layer is worth 31 points P@5 on these queries. Separable, measured, load-bearing. Flat across seven releases (v0.16 → v0.20) — zero retrieval regression while ops + infra changed underneath. Full report.

Curated content vs bulk swamp

Personal brains accumulate bulk content (tweet dumps, daily chat transcripts, archived articles). When a query phrase appears in both a short curated essay AND a long chat dump, which wins? Source-aware ranking keeps the essay on top. 93.3% top-1 hit (vs 80% on grep-only). Report.

Calibration loop — the brain that knows how you've been wrong

v0.36.1.0's calibration wave extracts gradeable claims from your prose, grades them against reality over time, and applies the resulting bias profile when the AI gives you advice. First published benchmark for AI memory systems that reason about user track records.

Eval Result Cost
cat14 — advice quality A/B 75% calibrated wins / 0% baseline wins / 25% tie ~$0.05 per run
cat15 — propose_takes F1 0.952 training / 0.922 holdout (gap 0.03, no overfitting) ~$0.10 per run

Voice gate 100%, force-fit prevention 100%. Hindsight introduced the concept as a skill demo without quantified evaluation; cat14 + cat15 stake out the category. The iteration log at cat14-calibration/ captures three same-day prompt variants where the eval caught two regressions before either could ship. Full report.

Prompt compression

Routing rule: the functional-area-resolver skill compresses a fat AGENTS.md into a two-layer dispatch table. A real-world 25KB resolver compressed to 13KB (48% the size) and gained 1317 percentage points of routing accuracy across Opus 4.7, Sonnet 4.6, and Haiku 4.5. The (dispatcher for: ...) clause is load-bearing — strip it (same compression, no dispatcher signal) and Sonnet's accuracy collapses to 41.7%. Cross-model receipts in this repo.

Run your own evals

# Public benchmark, in-process, ~30s warm
gbrain eval longmemeval ~/Downloads/longmemeval_s.jsonl

# Multi-model output quality gate (3 frontier models score the same output)
gbrain eval cross-modal --task "..." --output report.md

# Capture real query/search traffic as regression-eval data
GBRAIN_CONTRIBUTOR_MODE=1 gbrain eval export > snapshot.ndjson
gbrain eval replay --against snapshot.ndjson

# In-house BrainBench corpus + multi-adapter runner
cd ~/git/gbrain-evals && bun eval/runner/multi-adapter.ts

Full BrainBench corpus + per-category scorecards (Cats 113) in the sibling gbrain-evals repo.


What it does

1. Ingests everything, enriches automatically

Markdown sync from a git brain. Meeting transcripts (Whisper / Groq Whisper). EPUB / PDF books (book-mirror produces personalized chapter-by-chapter analysis). PDFs of academic papers with citation extraction. Articles via Reader / Perplexity / archive.is. Tweets (timeline + bookmarks). Voice notes. ChatGPT / Claude / Perplexity / agent-fork conversation exports. Email threads. Calendar events. Foursquare exports. Slack channel archives.

Every page write extracts entity references → typed links (attended, works_at, invested_in, founded, advises, mentions) with zero LLM calls. The graph wires itself.

2. Hybrid search that beats vector-only RAG

Per-query: vector + keyword + RRF fusion + multi-query expansion (Haiku) + source-aware ranking + reranking (ZeroEntropy, optional). Two-stage CTE so the HNSW index stays usable when source-boost re-ranks the top-K. Hard exclude prefixes (test/, archive/, etc.) at retrieval. Intent classifier auto-selects detail level by query type (entity / temporal / event / general). P@5 49.1%, R@5 97.9% on the BrainBench corpus.

3. Self-maintaining via the dream cycle

A 9-phase nightly cycle: lint → backlinks → sync → synthesize → extract → patterns → recompute_emotional_weight → embed → orphans (+ a purge phase for the destructive-guard lifecycle). Each phase is independently runnable via gbrain dream --phase <name>. Subagents fan out under a rate-leased Anthropic client; protected job names gate trust; per-job + per-cycle timeouts; auto-replay on stalled jobs. Conversation transcripts become brain pages with content-hash dedup so re-runs are no-ops.

4. Multi-source brains + temporal trajectory

gbrain sources add <id> --local-path <path> mounts additional brains alongside your host brain (team-published, CEO-class, departmental). Each source has its own RLS-isolated DB rows. OAuth clients can write to one source and read federated.

Author typed claims in the ## Facts fence: mrr=50000, arr=2000000, team_size=12, valuation_usd=15000000. gbrain eval trajectory companies/<slug> prints the chronological history with regressions auto-flagged. gbrain founder scorecard <entity> rolls up claim accuracy, consistency, growth direction, and red flags into a stable JSON contract.

5. Durable background work

gbrain agent run <prompt> submits LLM-loop subagents. gbrain jobs submit shell --params '{"cmd":"..."}' (operator-only) submits shell jobs. gbrain jobs supervisor start runs a self-restarting worker daemon with crash-cause classification, OOM detection, RSS watchdog, and per-cause metrics surfaced in gbrain doctor. gbrain autopilot --install registers the dream cycle as a launchd / systemd job.

Children + aggregator parents, child_done inbox for fan-in, per-job timeouts, idempotency keys, cascade-kill. Supervisor classifies exit codes into runtime_error / oom_or_external_kill / graceful_shutdown / clean_exit so 120 crashes/24h reads correctly instead of false-alarming on clean worker drains.


Multi-player and company brains

Three ways to share a brain across multiple agents, machines, or teammates. Most setups want pattern 1 (single server, multiple MCP clients). The other two patterns exist for specific reasons.

The GBrain server runs on the same machine or container as your primary agent (OpenClaw, Hermes, AlphaClaw). It publishes an MCP endpoint over HTTP with OAuth 2.1. Other agents — on other machines, in other apps, across teammates' laptops — connect as thin MCP clients.

# On the box that hosts your brain + primary agent:
gbrain serve --http --bind 0.0.0.0 --public-url https://brain.example.com
# Prints a one-time admin bootstrap token; paste it into the admin UI
# at /admin to register OAuth clients.

# On any other machine, set up a thin client:
gbrain init --mcp-only
# Walks you through pointing at the server URL + OAuth credentials.
# No local database, no scaffolded skills — just a routing shim.

Network: use Tailscale. Run the server on a Tailscale-private address so the brain is reachable from your other machines without exposing anything to the public internet. Public-internet exposure works fine (OAuth + loopback-bind-by-default + admin dashboard handle it), it's just a larger blast radius. For teams, Tailscale + OAuth is the cheap path to "my engineering team's agents all read from one shared brain."

What thin clients CAN do (subject to OAuth scope: read / write / admin):

  • search, query, think — full retrieval pipeline, including the knowledge graph + reranking
  • get_page, list_pages, put_page, delete_page, restore_page — page CRUD (put_page/delete_page require write scope)
  • find_experts (whoknows), find_trajectory, find_orphans, find_anomalies, find_contradictions — the analytical surfaces
  • get_recent_salience, get_brain_identity — observability
  • apply-migrations, restore_page — admin scope, OAuth-gated
  • Anything else marked localOnly: false in src/core/operations.ts

What thin clients CANNOT do (server-side only, by design):

  • sync_brain — needs filesystem access to the git brain repo
  • file_upload, file_list, file_url — server-side S3 / Supabase Storage credentials don't cross the trust boundary
  • get_recent_transcripts — raw .txt transcripts; privacy gate keeps these local
  • purge_deleted_pages — destructive, admin-only, local-only
  • code_traversal_cache_clear — local maintenance

If a thin client tries one of these, the server returns a clear "local-only" error pointing at the local CLI.

Source-scoped clients (v0.34+). A teammate's OAuth client can be tied to a single source with --source dept-x; its writes are isolated to that source's RLS-protected rows. --federated-read S1,S2,S3 adds orthogonal read scopes so the client can read across departments while writing to one canon. Useful when a team has a shared brain AND contributes back to a CEO-class brain.

Pattern 2 — Local PGLite for code search (GStack)

For an engineering agent that wants symbol-aware code search on the repo it's currently working in, run a local PGLite instance instead of hitting a central GBrain server. GStack supports this directly: one PGLite per worktree, indexed against that worktree's source tree.

# In the worktree you want to index:
gbrain init --pglite              # ~2 seconds, no server, no creds
gbrain import .                    # indexes the worktree's code
echo "$(pwd)" > .gbrain-source     # pins this PGLite to this worktree

/sync-gbrain (a GStack skill) keeps the PGLite current as you work. Code-aware queries (gbrain code-def, gbrain code-refs, gbrain code-callers) walk tree-sitter call-graph edges across 29 languages. Symbol-aware retrieval works better than grep for "where is X defined" / "what calls Y" queries.

Don't use this pattern for shared knowledge. PGLite is single-process, worktree-scoped, no concurrent writers. The right tool for "my agent needs the brain my team uses" is pattern 1.

Pattern 3 — Federated repos (advanced)

Multiple GBrain servers, each indexing the same brain repo from its own git clone. Each server has its own DB; they sync to the same markdown source of truth via gbrain sync.

Use this when:

  • You want a personal brain AND a team brain that share some content (the team's knowledge wiki) but diverge on private notes.
  • You're crossing a hard trust boundary (your personal brain is read-write to you; the team brain is read-only to most teammates, write-gated to a few).
  • The shared content is genuinely repo-shaped (markdown files in git), not "we both query each other's brains via MCP."

The mechanics: clone the same brain repo on each server, run gbrain init against each, configure both to sync from the same git remote. Pages land in both databases; the knowledge graph wires independently on each side from the same source markdown.

Most teams should NOT pick this. The MCP-client path (pattern 1) gives you one source of truth, one set of skills, one knowledge graph, and OAuth-gated multi-user access. Federated repos make sense when the shared content is the source of truth (a published team wiki) and the agents are operating against their own private overlays.

My personal setup runs both: a personal brain on my laptop and a team brain on a separate box, indexing two different git repos. The team brain is what teammates' thin MCP clients connect to; the personal brain stays local.


Skills

35 curated skills ship in the bundle. The agent installs them via gbrain skillpack scaffold <name> (one-time additive copy — you own the files after). See docs/guides/skillpacks-as-scaffolding.md for the model.

Always-on

Skill What it does
signal-detector Catches ideas, entities, and TODOs on every message — the always-on capture surface.
brain-ops Brain-first lookup before web search. Enforces the read-enrich-write loop.
repo-architecture File-placement decisions follow the primary subject, not the format.

Content ingestion

Skill What it does
idea-ingest Links / articles / tweets. Mandatory people page for the author.
media-ingest Video / audio / PDF / book with entity extraction.
meeting-ingestion Transcripts → attendee enrichment chaining.
voice-note-ingest Whisper → brain page with entity refs.
book-mirror EPUB / PDF → personalized chapter-by-chapter two-column analysis.
book-mirror-extreme Synthesis pass: lift the book's principles into your own framework.
brain-pdf Render any brain page as a publication-quality PDF.

Research + synthesis

Skill What it does
perplexity-research Bounded Perplexity calls with brain-side dedup.
academic-verify Citation verification + DOI resolution + claim cross-check.
strategic-reading Reading-list curation tuned to active concept clusters.
archive-crawler Crawl + extract from archive.is / archive-org bookmarks.
article-enrichment Article → people / companies / concepts links.
concept-synthesis Cross-source synthesis: 3+ pages on one concept → unified writeup.
data-research Email-to-tracker pipeline with parameterized YAML recipes.

Brain operations

Skill What it does
query The default query path. Walks intent classifier + hybrid search.
enrich Tier-escalating enrichment for people / companies / deals.
maintain Background brain hygiene: backlinks, citations, orphans.
citation-fixer Audit and fix citation format issues across the brain.
frontmatter-guard Validate every page's frontmatter against the contract.

Operational

Skill What it does
daily-task-manager Task lifecycle with priority levels (P0P4).
daily-task-prep Morning prep with calendar context + agenda generation.
cron-scheduler Schedule staggering, quiet hours, idempotency keys.
reports Timestamped reports with keyword routing.
cross-modal-review Quality gate via second model.
webhook-transforms External events → brain signals.
minion-orchestrator Background work: shell jobs + LLM subagents.

Identity + meta

Skill What it does
soul-audit 6-phase interview → SOUL.md / USER.md / ACCESS_POLICY.md / HEARTBEAT.md.
testing Skill validation framework.
skillify The meta-skill: turn any feature into a properly-skilled, tested unit.
skill-creator Create conforming skills with MECE check.
skillpack-check Agent-readable bundle health report.
skillpack-harvest Lift a proven skill from your fork back into gbrain (privacy-linted).
functional-area-resolver Two-layer dispatch for compressing AGENTS.md / RESOLVER.md.

Conventions (shared deps, installed alongside every skill)

  • _AGENT_README.md — agent operating contract: how to discover skills via frontmatter
  • _brain-filing-rules.md — file-placement rules (primary subject wins)
  • _output-rules.md — output quality standards (no LLM slop)
  • _friction-protocol.md — log user friction to ~/.gstack/friction/
  • conventions/ — cross-cutting rules (quality, brain-first, model-routing, test-before-bulk, cross-modal)

How it works

Architecture

Contract-first: src/core/operations.ts defines ~47 shared operations. CLI and MCP server are both generated from that one source. Engine factory dynamically imports the configured engine (pglite for zero-config local, postgres for Supabase / self-hosted).

Trust boundary: OperationContext.remote distinguishes trusted local CLI (remote: false) from untrusted agent-facing callers (remote: true). Security-sensitive operations tighten when remote: true is set.

Knowledge model

Every brain page is markdown with YAML frontmatter:

  • Frontmatter: name, type (person / company / deal / concept / ...), tags, optional sources:, triggers: (for skills), ## Facts fenced block with typed claims (mrr=50000, arr=2000000).
  • Compiled truth: stable section the agent updates with consensus knowledge.
  • Timeline: append-only event log, sentinel-separated (<!-- timeline -->).

Pages are chunked by recursive markdown structure (default) or LLM-guided (opt-in). 29 programming languages get tree-sitter semantic chunking with embedded WASM grammars and tiktoken token budgeting.

Knowledge graph

Every page write fires extract.ts:

  • Matches [Name](people/slug) markdown links AND [[people/slug|Name]] Obsidian wikilinks.
  • Heuristics infer link type: attended, works_at, invested_in, founded, advises, source, mentions.
  • Bulk-insert via unnest() arrays — 4-5 params regardless of batch size, sidesteps the 65535-param cap.

Query: gbrain graph-query alice --depth 2 --direction in returns who attended what with Alice, transitively.

Per-query pipeline:

  1. Query intent classifier (entity / temporal / event / general) selects detail level.
  2. Multi-query expansion via Haiku (deterministic, off by default in conservative mode).
  3. Hybrid: keyword (ts_rank × source-factor) + vector (HNSW + source-boost re-rank) + RRF fusion.
  4. Optional reranker (ZeroEntropy zerank-2 flagship or zerank-1).
  5. Source-aware ranking: curated content (originals/, concepts/, writing/) outranks bulk content (chat transcripts, daily/, media/x/). Hard-exclude test/, archive/, attachments/ by default.
  6. Dedup + token budget enforcement per mode.

Engines

  • PGLite — embedded Postgres 17.5 via WASM. Zero-config default. Single- file backup. Forward-reference bootstrap walks schema migrations cleanly across the v0.13v0.35 wave.
  • Postgres + pgvector — Supabase / self-hosted. pg_trgm + HNSW indexes. Migrations include 60+ schema bumps with sqlFor.postgres / sqlFor.pglite branches for engine-specific DDL.

gbrain migrate --to supabase / --to pglite does the round-trip.

Storage tiering

For brains crossing 100K files where bulk machine-generated content dominates the size: declare which directories are db_tracked (committed) vs db_only (DB-only, gitignored). gbrain sync manages .gitignore automatically; gbrain export --restore-only repopulates missing DB-only files from the database.

See docs/guides/storage-tiering.md for the config shape and per-tier defaults.


Commands

SETUP
  gbrain init [--pglite | --supabase]   Set up a brain. Picks search mode.
  gbrain migrate --to {supabase,pglite} Round-trip between engines.
  gbrain doctor [--fast] [--fix]        Health check + auto-repair.

CONTENT
  gbrain sync [--workers N]             Pull from the git brain.
  gbrain import <path>                  Index any directory of markdown.
  gbrain embed [--stale]                Compute / refresh embeddings.
  gbrain extract {links,timeline,all}   Build the knowledge graph.

SEARCH + QUERY
  gbrain query "<question>"             Hybrid search + RRF + reranking.
  gbrain search "<text>"                Lower-level: vector | keyword | hybrid.
  gbrain whoknows <topic>               Expertise routing across the graph.
  gbrain graph-query <slug>             Typed-edge traversal.
  gbrain orphans                        Pages with zero inbound links.
  gbrain salience [--days N]            Pages ranked by emotional + activity.
  gbrain anomalies [--lookback-days N]  Cohort-level outlier detection.

AGENTS + JOBS
  gbrain agent run "<prompt>"           Submit a subagent run.
  gbrain agent logs <id>                Tail the run's heartbeat + transcript.
  gbrain jobs submit shell --params ... Submit a shell job (operator-only).
  gbrain jobs supervisor start          Self-restarting worker daemon.

CYCLE + AUTOPILOT
  gbrain dream [--phase NAME]           Run the 9-phase brain cycle.
  gbrain autopilot --install            Register cycle as launchd / systemd job.

SKILLPACK
  gbrain skillpack list                 What's in the bundle.
  gbrain skillpack scaffold <name>      Copy a skill into your agent repo.
  gbrain skillpack reference <name>     Diff vs bundle. Or --all --since <ver>.
  gbrain skillpack migrate-fence        One-shot upgrade from pre-v0.36 model.
  gbrain skillpack harvest <slug> --from <host>
                                        Lift a proven fork skill into gbrain.

SERVE
  gbrain serve                          Stdio MCP (single-user, Claude Code).
  gbrain serve --http [--bind HOST]     HTTP MCP + OAuth 2.1 + admin dashboard.

EVAL + TRAJECTORY
  gbrain eval longmemeval <dataset>     LongMemEval benchmark, in-memory PGLite.
  gbrain eval cross-modal --task "..."  3 frontier models score the same output.
  gbrain eval whoknows <fixture>        Two-layer gate for whoknows accuracy.
  gbrain eval suspected-contradictions  Cached contradiction probe.
  gbrain eval trajectory <entity>       Typed-claim history + regression flags.
  gbrain founder scorecard <entity>     Claim accuracy / consistency / growth.

INTEGRATIONS + SOURCES
  gbrain integrations list              Recipe catalog (Reader, Perplexity, etc).
  gbrain sources {list,add,remove,...}  Multi-source brain management.
  gbrain auth register-client <name>    Mint an OAuth client for the HTTP MCP.

Run gbrain <command> --help for per-command options.


Docs


Origin story

I needed a brain that could remember every meeting, every founder encounter, every essay I'd ever drafted, every idea I wanted to chase later. Off-the-shelf knowledge tools (Roam, Obsidian, Notion) handle storage. None of them ingest a meeting, enrich the attendees, build the graph, fix the citations, and surface the connection three weeks later when it matters. So I built one. The first 12 days produced a working brain. The next 12 months produced 100,000 pages, a self-wiring knowledge graph, 108 cron jobs, and the multi-engine architecture that ships here.

GBrain is the generalized version of that work. You scaffold the skills. You bring your data. Your agent runs the patterns. The brain compounds nightly.


Contributing

PRs welcome. See CONTRIBUTING.md for the dev loop, test taxonomy, and how to ship. Privacy rules in CLAUDE.md are non-negotiable — no real names, fork names, or private references in public artifacts. The harvest CLI's privacy linter exists to catch accidental leaks before they merge.

License

MIT.

S
Description
No description provided
Readme MIT
160 MiB
Languages
TypeScript 97.4%
Shell 1.2%
JavaScript 0.9%
PLpgSQL 0.4%