Connect your terminal
In this section
The fastest way to reach brainy from a terminal is not a config file — it is one plugin, and every brainy door hands it out. .claude-plugin/plugin.json, a .mcp.json pointer, and eleven skills (recall, remember, briefs, the Accords board, the working method) ship together, versioned with the engine itself, and are always current because the door that installs them is the door you are about to talk to.
The two-word install
Tell your terminal:
join brainy.soulcraft.comor, for a door you run yourself:
join https://<your-door>Either phrase is an instruction to fetch that door's own GET / (or GET /join) page and follow it. The page is agent-readable on purpose — markdown by default, a minimal HTML page for a browser — and names exactly two commands:
claude plugin marketplace add https://<your-door>/plugin/marketplace.json
claude plugin install brainy@brainyThen restart: claude --continue. That is the whole install, for any door, hosted or self-hosted — the page always names ITS OWN URL, never a fixed address, so the same two-word phrase works against any brainy door.
The three paths
1. Hosted — brainy.soulcraft.com
Nothing to run. Add the marketplace and install as above (door defaults to https://brainy.soulcraft.com), then either:
Sign in. When the hosted door offers it, the first tool call opens your browser, you log in, and the harness stores the token — nothing copied, nothing typed. This is the MCP authorization flow the harness itself implements: the door answers an unauthenticated
POST /mcpwith a401naming its own/.well-known/oauth-protected-resourcedocument, the harness reads it, and the sign-in runs.A suite key. Set
BRAINY_API_KEYto a key your account issues. Only needed when the door does not offer sign-in.
2. Self-hosted — your own brainy host
Run brainy host --brains-dir <dir> --listen <host:port> --keys <file> --public-url <https://your-door>. That same process now serves the plugin too — GET /plugin/marketplace.json and GET /plugin/brainy-<version>.zip — with .mcp.json baked to point at ITSELF, so a plugin installed from your door always talks back to your door, never brainy.soulcraft.com.
Two ways a caller gets a credential at your door:
Offer sign-in through someone else's issuer. Start the host with
--auth-server <url>naming your authorization server. An unauthenticated/mcprequest then answers401with the same RFC 9728 discovery document the hosted door uses, and the tokens that server mints are thebk1tokens this host already verifies against your issuers file — nothing else changes. The served plugin's.mcp.jsonOMITS theAuthorizationheader entirely in this mode, so the harness's own sign-in flow runs instead of sending an unset environment variable literally.Be your own issuer.
--sign-in [<specifier>]makes THIS door its own OAuth-for-MCP authorization server — mutually exclusive with--auth-serverabove (that points at someone else's issuer; this one makes your door the issuer).<specifier>is a package name or a path, default@soulcraft/identity-kit— the private kit that gives a self-hosted door the same email-code-and-passkey sign-in, the same bk1 issuer and the same golden vectors the hosted door runs on, so the two can never drift apart. Also requires--public-url(the sign-in server needs a fixed origin) and--sign-in-mail <url>(your own mail sender — POSTed{ email, code, expiresInMinutes }, bearer-authenticated with your service key, the same pattern--reading-dooralready uses). Accounts live in a SIBLING store your door opens beside the ones it serves (<brains-dir>/.identity), never inside a tenant brain — tenant policy and exports never see a passkey or a session row. Which store a signed-in account opens is your OWN register, beside--keys(oneperson:<sub> <store>line per account,--sign-in-accounts <file>to move it); an account with no line is refused by name — this leg ships no self-serve provisioning, so nobody signs in to a brand-new brain your door invented on the spot. A minted token carries noentitlementclaim at all (never"suite"); a wider ceiling check on EVERY issuer's tokens is still owed — seedocs/host.md's own--sign-insection. Full detail:docs/host.md.Issue keys yourself.
POST /v1/keyswith the service key mints a static key for a person, business or service principal. Hand it out, and each holder setsBRAINY_API_KEY. The served plugin's.mcp.jsoncarries the ordinaryAuthorization: Bearer ${BRAINY_API_KEY}header in this mode.
3. Package holders — offline install
Already have @soulcraft/brainy installed? No door needs to be running yet:
npx brainy plugin install --door <https://your-door>This copies the package's own bundled plugin to ~/.config/soulcraft/brainy-plugin/<version>/ (a stable path, never node_modules), bakes --door into its .mcp.json, and runs the harness's own claude plugin marketplace add / claude plugin install commands against it. It never writes or asks for a key — it prints the same guidance as above: set BRAINY_API_KEY, or sign in if the door offers it.
Local zero-config — when the store and the terminal are on the SAME machine and you own both ends: npx brainy plugin install --local <brains-dir> --door <url> --keys <file> (the same --keys file your brainy host was started with) mints a per-install key, writes it to ~/.config/soulcraft/brainy-plugin/key (mode 0600), and sets BRAINY_API_KEY in your harness settings (~/.claude/settings.json's env block) — nothing to type, nothing to copy. This is the one case this CLI ever writes a credential; every remote door still gets sign-in or an admin-issued key.
npx brainy plugin url [--door <url>] prints a door's served marketplace URL, for scripting.
Embedded in an application
There is no terminal door here unless the embedding application ALSO runs brainy host — the plugin is a terminal-facing artifact; an application that links @soulcraft/brainy directly uses the in-process API (docs/overview.md) and never needs it.
Environment variables
Variable | What it does |
|---|---|
| The door the plugin's |
| The bearer credential, read by |
| The |
What the plugin contains
The pointer.
.mcp.jsonnames one door, over Streamable HTTP — nothing else runs locally.The hooks, ported to plain Node (no
bunrequired — this package already requires Node ≥ 22) and audited one by one for what a plugin every brainy user installs may do. Five of the six are thin door calls — read the harness's own hook stdin, redact it at the client's fixed denylist,POSTit, answer per the door's shape — and every one fails the same way when the door cannot answer: one named line on stderr, never a blocked session.guardrail-check(PreToolUse) — callsguardrail_checkat the door before a tool runs (seedocs/guardrails.md). Fails OPEN, loudly, when the door is unreachable — a default-shipped hook must never turn a network blip into a blocked session; only a realrefuse/askverdict blocks.session-start(SessionStart→POST /v1/session/start) — hands the door your session's identity and gets back markdown context for the session that is beginning, printed verbatim.session-capture(PostToolUse→POST /v1/session/capture) — hands the door the tool call you just made; when the answer carries adoorbellpointer (a board write that named parties who should hear about it now), relays the ring instruction as context. That field is anima's own owner-layer wrapper's, on the hosted door — the engine's own genericcapture(below) never carries one.session-save(StopandPreCompact→POST /v1/session/save) — saves the session so far, on a stop or ahead of a compaction.session-wrap(SessionEnd→POST /v1/session/wrap) — wraps the session as it ends.
Every door answers
{ served: true, … }or{ served: false, reason }— never a silent no-op and never a stub. Every request is bounded at 262,144 bytes (256 KiB) — over it, the door refusesSessionPayloadTooLargeErrorrather than reading an unbounded body, sincesession-capturenow fires on every tool call.The skills —
memory-keeper(the recall → cite → remember loop) plus ten method skills (brief,counsel,design-craft,diagnose,home,oracle,panel,table-voice,verify,wrap) carrying a working method onto every machine that installs this plugin.
One client-side redaction pass, nothing semantic. Before any hook payload leaves the terminal, a FIXED denylist strips five machine-shaped secrets — this plugin's own key files under ~/.config/soulcraft/, an Authorization: Bearer … value, a SOULCRAFT_SERVICE_SECRET=… assignment, any *_API_KEY=… assignment, and a PEM-armored block — replacing each with a [REDACTED:<kind>] marker; nothing else is touched, because a pattern that guesses at "sensitive-looking" prose is a pattern that mangles ordinary conversation.
No secrets. The shipped archive's own build pins two things: its entry list matches an explicit allow-list (no file outside it ever ships), and no entry's text matches a bearer value, an sk--prefixed key, a minted brainy credential, or a JWT — the only Bearer the archive ever carries is the literal ${BRAINY_API_KEY} placeholder.
The generic road
Through 12.9.x every door on every host answered the identical, honest { served: false, reason } document for a well-formed request: session composition was purely anima's own hosted work, and no build of this engine composed one itself. Since session-capture-road-generic-in-engine (12.10.0), brainy host and brainy mcp compose all four themselves, in process, against your own store — a self-hosted brain gets real session doors with no anima account in the picture at all. brainy-serve (the Rust binary) is the one build still answering the old document: it attaches every store read-only, so it cannot compose these yet either.
What each hook writes, where, and what a host hands back:
Hook | Writes | Reads back |
|---|---|---|
| Nothing. | A markdown context built from |
| Appends the redacted event to |
|
| Appends the redacted event this call carries to the SAME file |
|
| Reads that file's accumulated events, composes their transcript, and writes ONE |
|
Three pieces stay anima's own, deliberately, not carried here: a project's newest resume page and its working-state.md document under /projects/<project>/strategy/ (a fleet convention this engine has no notion of), the board-inbox line (accord_inbox, an Accords-board convention), and CAPTURE's own doorbell relay (the board-write pointer session-capture's hook prints as context) — all four ruled explicitly to stay on anima's thinned fleet layer (session-capture-road-generic-in- engine), never invented here. Anima's own hosted door still composes all of it; a self-hosted brainy host answers the table above alone, and a client reading either one's session-start context sees only what that build actually composed — never a placeholder standing in for the rest.
What an expired plan changes
A minted key can carry an optional entitlement claim — "suite" or "expired" — the bearer's plan, as its issuer wrote it. Most keys carry no claim at all, and nothing here applies to them: absence is today's law, unchanged, never read as expired.
When a key's plan reads "expired", every door that writes refuses by name (EntitlementExpiredError) — the five hooks' own session doors included, since a session door writes no entity but wants the same write posture as one that does. Reading never stops. find, get, counts and every other read door keep answering, export keeps answering, and session-start's own context read keeps composing — an expired plan narrows what a bearer may CHANGE, never what it may SEE. Renewing the plan is the only cure; nothing about the key itself changed, so no new key is needed once it is renewed.
The claim is per BRAIN, not per credential: a key minted for someone other than the brain's own owner (a grant) carries the OWNER's own plan, set by the issuer at mint time, never the guest's — a guest of a suite owner writes freely, and a guest of an expired owner is refused the same way the owner themselves would be.