* feat(config): add remote_mcp field + isThinClient() helper Adds a top-level optional remote_mcp config block to GBrainConfig (issuer_url, mcp_url, oauth_client_id, oauth_client_secret) for thin-client installs that consume a remote `gbrain serve --http` over MCP instead of running a local engine. isThinClient(config) returns true when remote_mcp is set; used by the CLI dispatch guard, doctor branch, and init re-run guard. The engine field stays as today (postgres|pglite); thin-client mode is a separate config field, NOT an engine kind extension (codex outside-voice review flagged the engine='remote' extension as overreach). GBRAIN_REMOTE_CLIENT_SECRET env var overrides the config-file value at load time so the secret can stay out of disk for headless agents. Foundation commit for multi-topology v1; no behavior change yet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(probe): outbound OAuth + MCP smoke probes Adds three pure async functions over the standard fetch API: - discoverOAuth(issuerUrl): GET /.well-known/oauth-authorization-server - mintClientCredentialsToken(tokenEndpoint, id, secret): POST /token - smokeTestMcp(mcpUrl, accessToken): POST /mcp initialize Discriminated 'ok=true' / 'ok=false + reason' return shapes so callers render error messages consistently. No SDK dependency to keep init's setup-flow scope tight; Lane B's mcp-client.ts will pull in the official @modelcontextprotocol/sdk Client for full session semantics. Used by both 'gbrain init --mcp-only' (Lane A's setup smoke) and runRemoteDoctor (Lane A's thin-client doctor checks). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(init): --mcp-only branch + re-run guard Adds 'gbrain init --mcp-only' for thin-client setup. Required flags (or env vars): --issuer-url OAuth root (e.g. https://host:3001) --mcp-url MCP tool dispatch path (e.g. https://host:3001/mcp) --oauth-client-id, --oauth-client-secret Pre-flight runs three smoke probes (discovery, token round-trip, MCP initialize) BEFORE writing the config — fail-fast on bad URL beats fail-late on bad credentials. On success, writes ~/.gbrain/config.json with remote_mcp set and NO local DB created. Re-run guard (A8): when ~/.gbrain/config.json already has remote_mcp, 'gbrain init' (any flag set) refuses without --force. Catches the scripted-setup-loop friction from the user-reported scenario where re-running setup-gbrain on a thin-client machine kept trying to re-create a local DB. Two URLs in config (issuer + mcp) instead of one because OAuth discovery + /token live at the issuer root while tool dispatch is at /mcp — they compose from a common base in practice but reverse-proxy setups need them explicit (codex review #2). Tests: 15 cases covering happy path, env-var-supplied secret stays out of disk, all four required-flag missing-error paths, three smoke-failure paths, network-unreachable path, and the four re-run guard variants (default/--pglite/--mcp-only without --force / with --force). Uses async Bun.spawn (NOT execFileSync) — sync exec deadlocks against in-process HTTP fixtures because the parent's event loop can't accept connections while sync-blocked on a child. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(doctor): runRemoteDoctor for thin-client mode Replaces every DB-bound check from runDoctor() with a tighter set scoped to 'is the remote MCP we configured actually reachable?'. Five checks: - config_integrity (URL fields well-formed) - oauth_credentials (secret resolvable from env or config file) - oauth_discovery (GET /.well-known/oauth-authorization-server) - oauth_token (POST /token client_credentials) - mcp_smoke (POST /mcp initialize) Output shape matches the local doctor's Check surface so JSON consumers can union the two without conditional logic. schema_version is 2 (matches local doctor). collectRemoteDoctorReport() is the pure data collector; runRemoteDoctor() is the print/exit wrapper. Tests pin the data collector so we don't have to intercept stdout / process.exit. Tests: 12 cases over a tiny in-process HTTP fixture covering happy path, every probe failure mode (404/parse/auth/network/server-error), malformed-URL config integrity, missing-secret short-circuit, and the env-var-overrides-config-file secret resolution. withEnv() helper used for env mutations to satisfy the test-isolation lint. Module is added but not yet wired into the CLI doctor branch; the wiring lands in the next commit (cli dispatch guard + doctor routing). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(cli): thin-client dispatch guard + doctor routing Adds a single canonical refusal at the top of handleCliOnly() for the 9 DB-bound commands when ~/.gbrain/config.json has remote_mcp set: sync, embed, extract, migrate, apply-migrations, repair-jsonb, orphans, integrity, serve Single dispatch check (not 9 sprinkled assertLocalEngine calls per codex review #1) — avoids the blast radius of letting commands enter connectEngine before the check fires. Refused commands exit 1 with a canonical error naming the remote mcp_url. doctor branch routes to runRemoteDoctor when isThinClient(config) returns true; falls through to the existing local-doctor flow otherwise. Wires the module added in the previous commit into the user-facing CLI surface. Safe commands (init, auth, --version, --help, etc.) still work in thin-client mode and are NOT in the refused set. Tests: 14 cases — 9 refused commands × 1 each, 2 safe commands, 1 doctor-routing assertion (fingerprints the thin-client output by 'mode:"thin-client"' in JSON), 2 regression tests asserting local config still passes through normally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(topologies): multi-topology architecture guide + setup skill Phase A.5 New docs/architecture/topologies.md covering three deployment shapes: 1. Single brain (today's default) 2. Cross-machine thin client (consume a remote brain over MCP) 3. Split-engine per-worktree (Conductor users with per-worktree code engines + shared remote artifacts brain) Each topology gets an ASCII diagram, when-it-fits guidance, and concrete setup recipes. Topology 3's alias-level routing footgun (wrong alias = silent wrong-brain writes) is called out explicitly per codex review #6. Topology 3 needs zero gbrain code changes — GBRAIN_HOME already overrides ~/.gbrain and 'gbrain serve --http --port N' already runs on any port. gstack composes these primitives on its side. skills/setup/SKILL.md gets Phase A.5 BEFORE the local-engine phases. Asks the user which topology fits, walks thin-client setup through 'gbrain init --mcp-only', skips Phases B/C/C.5/H entirely for thin clients (host's autopilot handles sync/extract/embed). README.md gets a one-line link to the topology doc from the Architecture section. llms-full.txt regenerated to include the new doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(e2e): thin-client end-to-end skeleton Spins up 'gbrain serve --http' against real Postgres, registers a client with read,write,admin scope, runs 'gbrain init --mcp-only' from a separate tempdir GBRAIN_HOME, exercises the canonical thin-client flows: - init --mcp-only succeeds against the live host - doctor reports mode: thin-client + all checks green - sync is refused with the canonical thin-client error - re-running init refuses without --force Tier B flows (gbrain remote ping / doctor) will be added alongside their Lane B implementation. Skips when DATABASE_URL unset (matches the e2e gate convention used across the suite). Async Bun.spawn (NOT execFileSync) so the test event loop stays responsive — execFileSync deadlocks against in-process HTTP fixtures because the parent's event loop can't accept connections while sync-blocked on a child process. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(doctor): doctorReportRemote core for thin-client + run_doctor op Adds three new exports to src/commands/doctor.ts that the run_doctor MCP op + gbrain remote doctor CLI both consume: - DoctorReport interface schema_version=2 stable shape - computeDoctorReport(checks) status + health_score math - doctorReportRemote(engine) focused 5-check thin-client surface doctorReportRemote runs: 1. connection (engine reachable + page count via getStats) 2. schema_version (engine.getConfig('version') vs LATEST_VERSION) 3. brain_score (the 5-component composite) 4. sync_failures (file-plane JSONL count from gbrainPath('sync-failures.jsonl')) 5. queue_health (Postgres-only: stalled active jobs > 1h) Engine-agnostic: works on both Postgres and PGLite via engine.executeRaw + engine.getConfig + engine.getHealth — no reliance on db.getConnection() which is Postgres-only. Deliberately a focused subset of the local doctor surface, NOT a full mirror. Generalizing to lint/integrity/orphans is filed as follow-up pending demand. Local doctor (runDoctor) is unchanged; operators on the host machine still get the full check set. schema_version=2 matches the local doctor's --json output schema, so JSON consumers can union the two without conditional logic. Tests: 11 unit cases against PGLite covering the 5-check happy path, schema version reporting (latest), PGLite-specific queue_health informational message, and the score+status math via computeDoctorReport. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(mcp-client): outbound HTTP MCP client over @modelcontextprotocol/sdk New src/core/mcp-client.ts wraps the official SDK's Client + StreamableHTTPClientTransport with OAuth client_credentials minting, in-process token caching with expires_at, and refresh-on-401 retry. Public surface: - callRemoteTool(config, toolName, args) tool call w/ auto-refresh - unpackToolResult(res) parse content[0].text JSON - RemoteMcpError discriminated by `reason` Token cache: module-level Map keyed by mcp_url. CLI processes are short-lived; the cache amortizes when one invocation makes multiple calls (gbrain remote ping submits then polls). Persisting to disk would be a credential-on-disk surface for marginal benefit since /token round-trip is sub-100ms. 401 retry: ONLY for mid-session token rotation (initial good token → stale → 401). If the FIRST mint fails auth, surface immediately as RemoteMcpError(auth) — retry won't help when credentials are wrong from the start. If a fresh-mint-after-401 still 401s, surface as RemoteMcpError(auth_after_refresh) which the CLI renders with a hint pointing the operator at gbrain auth register-client. Used by gbrain remote ping (submit_job + get_job poll) and gbrain remote doctor (run_doctor). Test-only _clearMcpClientTokenCache export for fixture isolation. Tests: 13 unit cases over an in-process HTTP fixture mimicking gbrain serve --http (OAuth discovery + /token + /mcp JSON-RPC handshake). Covers happy path, token cache reuse + force-refresh, args passthrough, config-error paths (no remote_mcp / no secret), token mint 401, network unreachable, tool isError envelope, and unpackToolResult parse failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(operations): add run_doctor MCP op (admin scope, HTTP-reachable) New op in src/core/operations.ts wraps doctorReportRemote() and returns the structured DoctorReport JSON over MCP. scope: 'admin' (system-state read; not for routine consumers) localOnly: false (reachable over HTTP) mutating: false (safe to call repeatedly) params: {} (no caller arguments needed) First read-only diagnostic op exposed over HTTP MCP. Used by gbrain remote doctor — the matching client-side renderer lives in src/commands/remote.ts. Precedent: doctor only. Generalizing run_lint / run_integrity / run_orphans to MCP is filed as follow-up work pending demand. Local doctor stays unchanged; this op is the operator-friendly subset for remote callers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(remote): gbrain remote ping + gbrain remote doctor Two thin-client convenience commands that round-trip through the host's HTTP MCP endpoint: - gbrain remote ping submit_job(autopilot-cycle) → poll get_job → exit when terminal. The "I just wrote markdown, tell the host to re-index" affordance. - gbrain remote doctor run_doctor MCP op → render the host's DoctorReport → exit 0/1 based on status. Both require a thin-client install (~/.gbrain/config.json with remote_mcp). Local installs get a clear error pointing at the local equivalents. Polling backoff (ping): 1s × 30s, then 5s × 5min, then 10s. Default cap 15min, configurable via `--timeout`. Without backoff, a 5-min cycle would burn 300 round-trips against the host's rate limiter. Payload uses `data: {phases: [...]}`, NOT `params:` — the submit_job op shape takes `data`. Codex review #8 catch. NO `repo` arg passed to autopilot-cycle — uses the server's configured brain repo. This sidesteps TODO #1144 (sync_brain repo-path validation for caller-controlled paths) entirely. src/cli.ts wires the `remote` subcommand into CLI_ONLY + the dispatch. Help (`gbrain remote --help`) and unknown-subcommand handling included. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(e2e): thin-client Tier B + scope-mismatch regression Extends the existing test/e2e/thin-client.test.ts with three new cases: 1. gbrain remote doctor returns the host's DoctorReport — pins the run_doctor MCP op round-trip. Asserts schema_version=2, all 5 check names present, connection + schema_version ok against a fresh host. 2. gbrain remote ping triggers autopilot-cycle and returns terminal state — pins the submit_job → poll → terminal wire path. Accepts any terminal state (success / failed / dead / cancelled / timeout) because autopilot on an empty no-repo brain may fail-fast in the sync phase. What this test pins is the JSON shape (job_id present, state populated), NOT cycle success on a no-repo fixture. 3. read+write client cannot call run_doctor — codex review #7 regression guard. Registers a separate client with `--scopes "read write"` (no admin), runs `gbrain remote doctor` against it, asserts exit 1 with auth/auth_after_refresh/tool_error reason. Keeps the verification flow honest: the canonical setup MUST require admin scope. `gbrain auth register-client` doesn't have --json, so the test parses the human output for "Client ID:" and "Client Secret:" lines via a helper. Test-level timeout bumped 60s → 120s for the ping wait + auth/init overhead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.29.2) v0.29.2 ships thin-client mode: gbrain init --mcp-only, gbrain remote ping/doctor, run_doctor MCP op, and the docs/architecture/topologies.md deployment guide. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 KiB
GBrain Deployment Topologies
GBrain supports three deployment shapes. They compose: a single user can mix
all three on the same machine without conflict, because every shape resolves
to "which ~/.gbrain/config.json is active right now?" and GBRAIN_HOME
controls that selection.
This page covers the three topologies, when each fits, and concrete setup
recipes. Pair this doc with docs/architecture/brains-and-sources.md (which
covers the in-brain organization axes) — that doc is about WHICH database;
this doc is about WHERE that database lives.
Quick decision tree
"I'm setting up gbrain..."
│
▼
Just for me, on one machine? ─── yes ───▶ Topology 1 (single brain)
│
no
│
▼
Will a remote machine host the brain
while my agent runs locally? ──── yes ───▶ Topology 2 (cross-machine thin client)
│
no
│
▼
Multiple Conductor worktrees that
shouldn't share a code index? ─── yes ───▶ Topology 3 (split-engine)
Topologies 2 and 3 stack: a thin-client install can also host per-worktree code engines, and a per-worktree code engine can also point its artifact brain at a remote server.
Topology 1 — Single brain (today's default)
┌────────────────┐
│ one machine │
│ ┌──────────┐ │
│ │ gbrain │──┼──→ ~/.gbrain/ → PGLite or Supabase
│ │ CLI │ │
│ └──────────┘ │
└────────────────┘
What you get: one local DB (PGLite for small brains, Supabase for ~1000+
files). All commands work directly against it. gbrain serve exposes it
to a single agent over MCP.
When it fits: solo use, single machine, one agent, no Conductor parallelism.
This is the default; gbrain init (no flags) gives you this.
Setup:
gbrain init # interactive — defaults to PGLite
gbrain init --pglite # explicit local
gbrain init --supabase # remote Supabase (recommended for 1000+ files)
Nothing else here is special. The other two topologies are variations on "who owns the DB" and "how does the agent talk to it."
Topology 2 — Cross-machine thin client
┌────────────┐ ┌──────────────────┐
│ neuromancer│ │ brain-host │
│ ┌────────┐ │ HTTP MCP / OAuth │ ┌────────────┐ │
│ │ Hermes │─┼───────────────────→│ │ gbrain │──┼──→ Supabase
│ │ agent │ │ │ │ serve --http│ │
│ └────────┘ │ │ └────────────┘ │
│ │ │ (with autopilot)│
│ no local │ │ │
│ gbrain DB │ │ │
└────────────┘ └──────────────────┘
What you get: the agent on one machine ("neuromancer") consumes a brain hosted on another machine ("brain-host") over HTTP MCP with OAuth. The agent's machine has NO local engine. All queries, searches, embeddings, and indexing happen on the host.
When it fits:
- Heavy brain (Supabase + autopilot) lives on a beefy machine; agents elsewhere just consume it.
- You want one source of truth across many machines.
- Spinning up a parallel local install would create source-ID contention or duplicate work.
The thin client's ~/.gbrain/config.json carries a remote_mcp field
instead of a local DB connection:
{
"engine": "postgres", // ignored — never used
"remote_mcp": {
"issuer_url": "https://brain-host.local:3001",
"mcp_url": "https://brain-host.local:3001/mcp",
"oauth_client_id": "neuromancer-...",
"oauth_client_secret": "..." // or set GBRAIN_REMOTE_CLIENT_SECRET
}
}
The CLI dispatch guard refuses any DB-bound command (sync, embed,
extract, migrate, apply-migrations, repair-jsonb, orphans,
integrity, serve) on a thin-client install with a clear error pointing
at the remote host. gbrain doctor runs a dedicated thin-client check set
(OAuth discovery, token round-trip, MCP smoke).
Setup
Step 1 — On the host (brain-host):
gbrain init --supabase # or --pglite, doesn't matter
gbrain serve --http --port 3001 # exposes /mcp + OAuth
gbrain auth register-client neuromancer \
--grant-types client_credentials \
--scopes read,write,admin # admin needed for ping/doctor
The register-client command prints a client_id and client_secret.
Note both. Scope must include admin — submit_job (used by
gbrain remote ping) and run_doctor (used by gbrain remote doctor)
both require it.
Step 2 — On the thin client (neuromancer):
gbrain init --mcp-only \
--issuer-url https://brain-host.local:3001 \
--mcp-url https://brain-host.local:3001/mcp \
--oauth-client-id <id> \
--oauth-client-secret <secret>
Pre-flight smoke runs three probes (OAuth discovery, token round-trip,
MCP initialize). If any fails, init exits with an actionable error. On
success, ~/.gbrain/config.json gets remote_mcp set and NO local DB
is created.
Step 3 — Configure your agent's MCP client.
For Claude Desktop / Hermes / openclaw, add a single MCP server entry
pointing at the host's mcp_url with the bearer token from register-client.
Example for Claude Desktop's ~/.config/claude/claude_desktop_config.json:
{
"mcpServers": {
"gbrain": {
"type": "url",
"url": "https://brain-host.local:3001/mcp",
"headers": { "Authorization": "Bearer <client_secret>" }
}
}
}
Step 4 — Verify.
gbrain doctor # runs thin-client checks (no local DB needed)
gbrain remote ping # triggers an autopilot cycle on the host (Tier B)
gbrain remote doctor # asks the host to run its own doctor (Tier B)
gbrain sync and friends will refuse with a clear thin-client error
naming the mcp_url. That's the correct behavior — those commands need
a local engine that doesn't exist here.
Re-run guard
Running gbrain init (no flags) on a machine that already has thin-client
config set refuses without --force. This catches the scripted-setup-loop
friction where an orchestrator keeps trying to create a local DB. Use
gbrain init --mcp-only --force to refresh thin-client config.
Storing the OAuth secret
Three storage paths in priority order:
GBRAIN_REMOTE_CLIENT_SECRETenv var (preferred for headless agents). When set, overrides whatever's in the config file. The init flow doesn't persist a config-file copy when the env var was the source.~/.gbrain/config.jsonwith 0600 perms (default for interactive setup; mirrors how Supabase keys are stored today).- macOS Keychain integration is on the roadmap; not in v1.
Topology 3 — Split-engine, per-worktree code + remote artifacts
┌──────────────────────────────────────────────────────┐
│ one machine │
│ │
│ ┌─ worktree A ──────────────┐ │
│ │ GBRAIN_HOME=A/.conductor │ │
│ │ gbrain serve --port 3001 │── PGLite (code A) │
│ └───────────────────────────┘ │
│ │
│ ┌─ worktree B ──────────────┐ │
│ │ GBRAIN_HOME=B/.conductor │ │
│ │ gbrain serve --port 3002 │── PGLite (code B) │
│ └───────────────────────────┘ │
│ │
│ ┌─ default ~/.gbrain ───────┐ HTTP MCP / OAuth │
│ │ gbrain serve --port 3000 │──────────────────────→ remote artifacts
│ └───────────────────────────┘ (Supabase / brain-host)
│ │
│ Agent's MCP config (Hermes / Claude Desktop): │
│ mcp__gbrain_code__* → http://localhost:3001 │
│ mcp__gbrain_artifacts__* → http://brain-host/mcp │
└──────────────────────────────────────────────────────┘
What you get: each Conductor worktree has its own per-worktree code index (local PGLite, disposable when the worktree dies). Artifacts (plans, learnings, transcripts) still live in a shared brain that all worktrees can see and write to.
When it fits:
- Multiple Conductor worktrees on one machine, all touching the same code repo.
- You don't want each worktree's code-import to clobber the others'
last_commit, source IDs, or symbol tables. - You DO want artifacts (plans, learnings, retros, transcripts) to be visible across worktrees.
How it works
GBRAIN_HOME selects which ~/.gbrain directory is active. Set per worktree:
export GBRAIN_HOME=/path/to/worktree-A/.conductor/gbrain
gbrain init --pglite
gbrain serve --http --port 3001
Each worktree's gbrain serve instance binds its own port and indexes its
own DB. Multiple gbrain serve processes coexist fine — they're separate
OS processes with separate config and separate connection pools.
The artifact brain runs as a separate gbrain serve instance with the
default ~/.gbrain (no GBRAIN_HOME override) — or remote, in which case
it's a Topology 2 setup.
The agent's MCP client config lists multiple servers, each with a unique
alias. Tool names are namespaced as mcp__<alias>__<tool>, so the agent
calls mcp__gbrain_code__search for code lookups and mcp__gbrain_artifacts__search
for artifact lookups.
CRITICAL: alias-level routing is manual
Topology 3 has no smart per-tool routing inside gbrain. The agent picks which brain to query when it picks the alias. A wrong alias writes (or queries) the wrong brain silently. This is intentional (explicit beats magic) but real:
- If the agent calls
mcp__gbrain_artifacts__put_pagewith code-shaped content, that page lands in the artifact brain forever. - If the agent calls
mcp__gbrain_code__searchfor a question that actually wants artifact context, the search comes back empty.
Mitigations:
- Name aliases clearly.
gbrain_codevsgbrain_artifactsis unambiguous;gbrainvsgbrain_localis not. - Document in your agent's system prompt or rules which alias goes where.
Be explicit about "code questions →
gbrain_code; everything else →gbrain_artifacts." - Pair Topology 3 with
gstack's per-worktree wiring (which sets the alias names + agent rules consistently across worktrees).
Setup (manual; gstack automates this side)
The gbrain side requires zero new code — GBRAIN_HOME and --port already
exist. Setup looks like:
# Start the artifact brain (default ~/.gbrain) on port 3000
gbrain serve --http --port 3000 &
# Start a per-worktree code brain on port 3001
export GBRAIN_HOME=/path/to/worktree-A/.conductor/gbrain
gbrain init --pglite
gbrain serve --http --port 3001 &
unset GBRAIN_HOME
Then configure the agent's MCP config with two entries (different aliases, different ports). For Claude Desktop:
{
"mcpServers": {
"gbrain_artifacts": {
"type": "url",
"url": "http://localhost:3000/mcp",
"headers": { "Authorization": "Bearer <token-A>" }
},
"gbrain_code": {
"type": "url",
"url": "http://localhost:3001/mcp",
"headers": { "Authorization": "Bearer <token-B>" }
}
}
}
The gstack-side wiring (per-worktree home setup, port allocation, automatic MCP config generation, gitignore for the per-worktree DB) is in the gstack repo's setup-gbrain skill — it composes these primitives, gbrain doesn't have to know about Conductor.
Combining topologies
The three shapes compose. A single machine can run:
- A thin-client default config pointing at a remote artifact brain (Topology 2).
- Plus per-worktree code brains under their own
GBRAIN_HOME(Topology 3). - Each worktree's
gbrain serveinstance is local; the agent's MCP config lists them alongside the remote artifact brain.
GBRAIN_HOME controls which config file is active for any one CLI
invocation. gbrain serve --port controls which port a server listens on.
The agent's MCP client picks the alias and thus the destination per tool
call. There's no global gbrain orchestrator that knows about all of them
simultaneously — that's by design.
When NOT to use these topologies
- Don't use Topology 2 if your agent only ever runs on the same machine
as the brain. A local
gbraininstall +gbrain serve(stdio) is simpler and faster. - Don't use Topology 3 if you only have one Conductor worktree at a time. Per-worktree engines exist to prevent contention; one-at-a-time use has no contention.
- Don't use a
remote_mcpthin client AND a local engine on the same machine in the sameGBRAIN_HOME. The dispatch guard refuses DB-bound commands whenremote_mcpis set. If you genuinely want both modes on one machine, useGBRAIN_HOMEto separate them (one home for the thin client, another for the local engine).
See also
docs/architecture/brains-and-sources.md— in-brain organization (brains vs sources axes).docs/mcp/CLAUDE_DESKTOP.mdand siblings — per-client MCP setup.gbrain init --helpandgbrain auth --helpfor command-level details.