* 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 (bd60cdf6— closes #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): -e82dda0aadmin embed E2E (post-deploy curl smoke covers this) -d93fa81dapply-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>
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.
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
~/.gbrainon 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.txtfor the doc map, orllms-full.txtfor the map with core docs inlined. Agents: start withAGENTS.md(orCLAUDE.mdfor 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.
- OpenClaw — deploy AlphaClaw on Render (one click, 8GB+ RAM)
- Hermes Agent — deploy on Railway (one click)
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 13–17 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 1–13) 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.
Pattern 1 — One GBrain server, many MCP clients (recommended)
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 + rerankingget_page,list_pages,put_page,delete_page,restore_page— page CRUD (put_page/delete_pagerequirewritescope)find_experts(whoknows),find_trajectory,find_orphans,find_anomalies,find_contradictions— the analytical surfacesget_recent_salience,get_brain_identity— observabilityapply-migrations,restore_page— admin scope, OAuth-gated- Anything else marked
localOnly: falseinsrc/core/operations.ts
What thin clients CANNOT do (server-side only, by design):
sync_brain— needs filesystem access to the git brain repofile_upload,file_list,file_url— server-side S3 / Supabase Storage credentials don't cross the trust boundaryget_recent_transcripts— raw.txttranscripts; privacy gate keeps these localpurge_deleted_pages— destructive, admin-only, local-onlycode_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 (P0–P4). |
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, optionalsources:,triggers:(for skills),## Factsfenced 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.
Search
Per-query pipeline:
- Query intent classifier (entity / temporal / event / general) selects detail level.
- Multi-query expansion via Haiku (deterministic, off by default in
conservativemode). - Hybrid: keyword (
ts_rank× source-factor) + vector (HNSW + source-boost re-rank) + RRF fusion. - Optional reranker (ZeroEntropy
zerank-2flagship orzerank-1). - Source-aware ranking: curated content (
originals/,concepts/,writing/) outranks bulk content (chat transcripts,daily/,media/x/). Hard-excludetest/,archive/,attachments/by default. - 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.13–v0.35 wave.
- Postgres + pgvector — Supabase / self-hosted.
pg_trgm+ HNSW indexes. Migrations include 60+ schema bumps withsqlFor.postgres/sqlFor.pglitebranches 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
- Architecture:
docs/architecture/ - Skills as scaffolding (v0.36):
docs/guides/skillpacks-as-scaffolding.md - Search modes + cost matrix:
docs/eval/SEARCH_MODE_METHODOLOGY.md - Storage tiering:
docs/guides/storage-tiering.md - Embedding providers (14 recipes):
docs/integrations/embedding-providers.md - Eval capture (BrainBench-Real):
docs/eval-bench.md - MCP per-client setup:
docs/mcp/ - Two-repo pattern (agent vs brain):
docs/guides/repo-architecture.md - Skill development cycle:
docs/guides/skill-development.md - Per-agent install guide:
AGENTS.md,CLAUDE.md,INSTALL_FOR_AGENTS.md - Full changelog:
CHANGELOG.md
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.