Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

CLI Reference

The brain command drives the whole lifecycle: scaffold a master, compile slices, apply edits back, manage promotions, run the whole cycle on a schedule, and check integrity.
brain {init,refresh-protocol,rename-entities,relink,ingest,webhook,compile,writeback,held,promotions,shares,cycle,index,search,graph,facts,mcp,status,dashboard,doctor,triage} …
Every subcommand exits 0 on success and 1 on a handled error, with a human-readable message on stderr. main(argv) is importable and returns the same code, so the CLI is testable without a subprocess.
"Handled" covers both the errors brain raises itself (unknown person, held or failed write-back, invalid promotion, integrity error) and the environmental ones it doesn't author: a missing or unparseable _meta file, a path that can't be read or written, a full disk, a git that refuses. Those last ones name the path or the git command that failed, because that is what you act on.
An unexpected exception still prints a traceback and is not flattened into a one-line error. That is deliberate: a traceback means you have found a bug, and dressing it up as a handled failure would hide it. If a traceback reaches your logs, it belongs in an issue.
Two other exit codes: 2 for a usage error (argparse, or a missing flag a subcommand needs), and 130 if you interrupt with Ctrl-C.

brain --version

Ask an installed copy what it is. The only top-level flag; everything else is a subcommand.
brain --version # brain 0.2.0 brain --version # brain 0.2.0 (rev 69631f68e8c9)
The version comes from installed package metadata, never a string in the source, so it cannot drift from what was actually installed.
The rev suffix appears only when the version alone would not identify the code, which is the usual case for a deployment. A version names a release: it reads the same for every commit between one tag and the next. So if you installed from git — uv tool install git+https://github.com/joedanz/brainkit — two boxes provisioned weeks apart both honestly answer 0.2.0, and the commit is the only thing that tells them apart.
How it was installedWhere the revision comes from
From git (git+https://…)PEP 610 direct_url.json, which the installer wrote at install time
A container imageThe same direct_url.json, since the image installs from git. BRAINKIT_GIT_SHA overrides it, for an image that installs a working tree instead — see reference deployment
A released wheel (pip install brainkit)Nothing — and correctly so: the version already identifies the code exactly
An editable checkout (pip install -e .)Nothing — its commit is whatever HEAD is right now, which install metadata cannot know
brain mcp reports the bare version in its initialize response and never the revision. MCP clients may parse serverInfo.version, so a (rev …) suffix there would be a wire-format change rather than a convenience.

brain init

Scaffold a new company master vault.
brain init <dir> --company "Acme Co"
ArgumentRequiredMeaning
diryesDestination directory; must be empty or nonexistent.
--companyyesThe organization's display name, used in the seeded Home and Memory page titles — it does not name any folder.
--entitiesnoTop-level folder name for the restricted third-party tree (default Clients).
--entitynoSingular form for the entity type, used in guidance and folder names (default derived from --entities by stripping trailing "s").
--sharednoName of the shared top-level space everyone reads (default Company; fixed after init).
--charternoOne sentence naming what this company does — a subject, not a mission statement (e.g. "Bespoke luxury travel."). It renders into every generated protocol as "What this brain is for", giving the admission rules a subject to test relevance against. Max 400 characters; whitespace is collapsed to a single line. Omitted by default, in which case the relevance test falls back to the vault's own space list — a real subject, just a broader one — and brain doctor notes the unset charter at info.
Creates the space layout — including the seeded Company/Intel/ reference wiki (Destinations/, Providers/, Events/, Trends/, mapped in Intel/Home.md) — _meta/org.yaml and _meta/spaces.yaml, the _meta/promotions/{pending,approved,rejected}/ queue, the master-root AGENTS.md (assistant protocol), and an initial git commit.

brain refresh-protocol

brain refresh-protocol --master <master> [--write]
Rewrites the master's root AGENTS.md from the assistant protocol shipped with the installed brainkit. brain init writes that file once and nothing else ever rewrote it, so a vault created before a protocol improvement kept the older rules indefinitely — unlike compiled per-person vaults, which regenerate theirs on every cycle.
Without --write it only reports: exit 0 when the file is already current, 1 when it is missing or differs — and it prints the unified diff, so you can see whether the admission gate moved or a sentence was reworded before deciding. --write applies the rewrite. The default is report-only because the file is generated content an admin may nonetheless have edited by hand, and refreshing discards such edits.
brain doctor reports the drift as protocol-stale, at error when the file is missing or its admission gate is out of date, and at warn for any other drift. The split is deliberate: an out-of-date gate is fail-open — the assistant is admitting into the brain what this version decided does not belong — and no other check will ever notice, because doctor reads structure and never subject matter.

brain rename-entities

brain rename-entities --master <master> --entities Vendors [--entity vendor]
Renames the third-party tree of an existing vault: moves the folder (via git mv), rewrites the affected spaces.yaml rules, moves each person's request folder (ClientRequests/ -> VendorRequests/, rewriting the frontmatter key in pending requests), rewrites space:/target-path: values in pending share and promotion queues (decided history is never touched), writes _meta/config.yaml, and commits. Run it immediately after brain cycle, then run a cycle again: slices recompile under the new name. Edits still sitting in a stale slice under the old name are rejected fail-closed by that next cycle and reported. The shared space has no rename counterpart — its name is chosen at init.

brain relink

brain relink --master <master> OLD NEW [--write] [--verbose]
Keeps links working when a note is renamed or moved. OLD and NEW are paths inside the master. If OLD still exists, it is moved to NEW; if OLD is already gone and NEW exists (an agent renamed it), the links that now point nowhere are healed. Either way, every link that reached the note before still reaches it, and no other link is changed: bare names, full paths, headings, labels, embeds, relations and fact sources are all handled, and a link that reaches the note through an entity alias is left alone.
It only reports what it would change until you add --write, which makes one commit of exactly the notes it touched. It refuses when the new name already belongs to another note, when the two paths are in different spaces (use a promotion), when the path goes through a symlink, and when a pending promotion or a held edit names the note. A link is left alone, and counted, when some reader of the note holding it cannot see the note it points at: rewriting it could break the link for them and show them the name of a note they are not cleared to see. If anything fails while applying, everything is put back.
Run it right after brain cycle, then run a cycle again. If an agent edited an affected note since the last cycle, its version can bring the old link back; run the command again to fix it.

brain ingest

Write one provenance-stamped note into a person's Inbox/ in the master. The safe, server-side intake used by email, chat, voice, and upload channels — and by brain webhook for services that push.
brain ingest --master <master> (--person <id> | --from <email>) [--file <path>] [--title <t>] [--source <channel>] [--date <YYYY-MM-DD>] [--json]
FlagRequiredMeaning
--masteryesPath to the master vault.
--personone ofTarget person by id.
--fromone ofTarget person by their org.yaml email (unknown senders are refused). Mutually exclusive with --person.
--filenoRead the note body from this file; default is stdin.
--titlenoNote title. Defaults to the --file name, else the first non-empty line, else note.
--sourcenoIntake channel label (email/chat/voice/upload/manual). Default manual.
--datenoProvenance date. Default today.
--jsonnoEmit {"person_id", "rel_path", "title", "source", "committed", "ok"}.
The note lands at People/<id>/Inbox/<date>-<slug>.md — the path is constructed (never derived from input), then re-checked with can_write_path, so intake can only ever write into that person's own Inbox. Provenance metadata is rejected if it contains newlines (no frontmatter injection). Each intake is a separate git commit under Brain Ingest <ingest@brain.local>, distinct from write-back. Exactly one of --person/--from is required; passing neither or both is a usage error (exit 2). An unknown person/sender or an unwritable target exits 1.

brain webhook

Serve the authenticated webhook intake receiver — the always-listening counterpart to the channel recipes. External services (Fathom, Composio triggers, Zapier, Make, n8n) POST to /hook/<source-id>; each verified delivery becomes one brain ingest into the routed person's Inbox.
brain webhook --master <master> [--host <addr>] [--port <n>] [--max-body-mb <n>]
FlagRequiredMeaning
--masteryesPath to the master vault (sources come from _meta/webhook.yaml).
--hostnoBind address. Default 127.0.0.1; a non-loopback bind expects a TLS reverse proxy in front.
--portnoPort. Default 8766; 0 picks a free port.
--max-body-mbnoLargest accepted request body in MB. Default 5 (transcripts are large).
Sources are declared in _meta/webhook.yaml — server-only, never compiled into any vault. Each source sets a verify mode (standard-webhooks for HMAC-signed senders with replay protection, or token for a shared-secret header), names its secret's environment variable (secrets never live in the file), and routes deliveries either to a fixed person: or by route: sender-email lookup against org.yaml — unknown senders are refused. A source whose secret variable is unset refuses to start. Failed verification is 401, an unknown source path is 404, an unroutable or malformed payload is 400, and duplicate deliveries (by webhook-id) are acknowledged without re-ingesting. Compressed request bodies are refused; the body-size cap is enforced before buffering.

brain compile

Materialize per-person vaults from the master.
brain compile --master <master> --out <out> [--person <id>]
FlagRequiredMeaning
--masteryesPath to the master vault.
--outyesOutput root; each person's slice lands in <out>/<id>/.
--personnoCompile only this person instead of everyone.
Re-run on every master change. The compiler fails closed per person: if one person's vault fails to build, their previous slice stands, everyone else is still refreshed, and the command names each failure and exits 1.

brain writeback

Validate a person's edits and apply them to the master.
brain writeback --master <master> --vault <vault> --person <id>
FlagRequiredMeaning
--masteryesPath to the master vault.
--vaultyesThe person's edited (synced) vault.
--personyesWhose edits these are — determines the write scope.
Every changed path is checked against the person's write permissions on the server, one change at a time. Changes they may write are applied and committed as that person, and only those files: an admin's uncommitted edit elsewhere in the master is never swept into the commit. Changes outside their write scope are held: they are not applied, they are listed on stderr, and the command exits 1. The person gets a note at People/<id>/Inbox/held-edits.md, and the record at People/<id>/.held.json points to the vault commit that has the content (see brain held). OS clutter (.DS_Store, Thumbs.db, desktop.ini, ._*, *~, *.swp) is ignored at any depth. If git or the disk fails partway through, the master files it touched are put back and the command exits 1.

brain held

Show the edits held back from a person's sync.
brain held show <id> --master <master> --out <out>
Prints when the hold was recorded, which vault commit it points to, and for each held file what the person's vault had (or that it was deleted). There is no apply command: an admin copies what should be kept into the master by hand. A person has at most one open hold. A newer one replaces it, and deleting People/<id>/Inbox/held-edits.md from their vault clears it. If the vault was re-cloned or purged since, the command says the commit is no longer available. brain doctor lists every open hold as a held-edits warning, which goes to the admins' digest.

brain corrections

List, confirm or dismiss people's standing corrections.
brain corrections list [--person <id>] --master <master> brain corrections confirm <person> <slug> --by <id> --master <master> brain corrections dismiss <person> <slug> --by <id> --master <master>
A correction an agent writes to People/<id>/Corrections/<slug>.md only reaches that agent after someone confirms its exact rule: text. list shows each correction as pending, active or rejected (with why). confirm records the current text as confirmed, committed under the --by person's name; if the text changes later, it goes back to pending. dismiss deletes the correction. Only the person the correction belongs to, or an admin, can be --by. A rule is rejected, and can't be confirmed, if it is more than one line, longer than 280 characters, or contains a web address, a backtick, or a hidden character (an invisible, formatting or line-breaking character). People confirm their own corrections from their dashboard (see --corrections-master below); admins can use this command or the admin dashboard's Corrections tab.

brain promotions

Manage the promotion queue.
brain promotions {list,show,sweep,approve,reject} [id] --master <master> [--approver <who>] [--reason <text>]
ActionUsesMeaning
list--masterPrint pending promotions (id, from, target).
show <id>--masterPrint a pending promotion's metadata (mode, target, source) and, for mode: patch, a live unified diff of the proposed body against the current target — computed at review time, not from the queued draft.
sweep--masterPull agent drafts from People/*/Promotions/ into the pending queue. For mode: patch drafts, stamps base-hash (a sha256 of the target file, taken at sweep time) into the queued promotion.
approve <id>--master --approverPublish the promotion to its target space with provenance frontmatter.
reject <id>--master --reasonMove it to rejected/ with the reason.
approve requires --approver, and it must be a person id from _meta/org.yaml who is entitled to approve this promotion — an admin for any target, or a lead on team T for a Teams/T/ target; a blank name, an unknown one, or an ineligible colleague is refused. See Who approves. approve also re-validates the target path at publish time, so a tampered pending file cannot write outside the master root or into a private space.
Every promotion carries a mode, chosen when the agent drafts it:
ModeTarget mustOn approval
create (default)not already existwrites the whole note as a new file with provenance frontmatter
appendalready existadds the body under a --- divider, with an italic attribution line naming who promoted and approved it
patchalready exist, unchanged since sweepreplaces the file wholesale with the promotion's body (the complete revised page)
create and append fail closed on a target that doesn't meet their requirement — an approval can never silently create where it should update, or overwrite where it should add. patch fails closed if the target's sha256 no longer matches the base-hash stamped at sweep time: a hand edit, a bad merge, or another approval could have changed the page after this draft was queued, and re-checking at approval time (not draft time) is what catches it. Use brain promotions show <id> — or the dashboard's Promotions card — to review the live diff before approving a patch.
Every queue decision is its own git commit, scoped to just the files it touched: sweep and reject commit as Brain Promotions, and approve commits under the approver's identity — so git log answers "who published this, and when" the same way it does for ingest and write-back.

brain shares

Manage space shares — an owner granting or revoking a colleague, a team, or everyone (company-wide, read-only) access to a space they own.
brain shares {list,approve,reject,revoke} [id] --master <master> [--approver <who>] [--reason <text>] [--space <path>] [--subject <grant>]
ActionUsesMeaning
list--masterPrint pending share requests (id, from, space, share-with, access).
approve <id>--master --approverAmend spaces.yaml per the pending request; archive it to approved/.
reject <id>--master --approver --reasonMove it to rejected/ with the reason.
revoke--master --space --subjectRemove --subject (e.g. person:yuki) from --space's rule directly.
list, approve, and reject act on a pending share id, the same shape as brain promotions. revoke is different: there's no id, because it isn't a queued decision — it's a live admin action that takes the space and the subject to remove directly, and it doubles as the veto lever for a self-service-created client space (see Self-service client spaces) or any other grant an admin wants to pull immediately, with no request in the queue.
approve and reject both require --approver, a person id from _meta/org.yaml — a blank or unknown name is refused, the same base rule as brain promotions approve. Being a known person isn't enough on its own, though: the approver must also be eligible to decide this share — role:admin, the named person:<id> recipient, or someone with role: lead on the named team:<name> — or the command refuses. A share-with: everyone request is always decided by an admin; see Sharing a space for the full routing table and the in-vault Approvals seam that enforces it the same way. Both approve and revoke refuse to touch role:admin — it can never be removed from a rule. A rejection reason is collapsed to a single line before it's archived; a reason that's blank (or whitespace-only) is refused. Every decision is its own git commit: approve and reject under the approver's identity, revoke as Brain Shares.
This command manages the queue and direct revokes; it doesn't create requests. An owner asks for a share or revoke by writing into their own People/<id>/ShareRequests/, and brain cycle routes it — share requests land in the pending queue this command works, revoke requests from that folder auto-apply during the cycle instead of ever reaching a queue. See Sharing a space for the full request shape and the ownership rules brain cycle enforces on it.

brain cycle

Run the whole server-side loop once: write back every person's edits, provision any self-service client requests, route pending/auto-applied share requests and apply any delegated decisions on them, sweep agent drafts into the promotions pending queue, then recompile all vaults.
brain cycle --master <master> --out <out> [--json] [--index] [--spool-root <dir>]
FlagRequiredMeaning
--masteryesPath to the master vault.
--outyesCompiled-vault root — the same --out you pass to compile.
--jsonnoEmit a single JSON summary object on stdout instead of human lines.
--indexnoAfter recompiling, refresh each vault's search index (see brain index). Index failures warn but never fail the cycle.
--spool-rootnoFile notes saved over remote access (queued under <dir>/<person>/ by brain mcp --http --spool) into each person's Inbox, before write-back and the compile. A saved note that cannot be filed moves to <dir>/<person>/.rejected/ with a warning. Run the cycle as the user that owns the spool, or the folder is skipped with a warning. A broken spool never stops the rest of the cycle. Without this flag the stage is skipped.

The --json summary

FieldMeaning
writebacksPer-person write-back results: person_id, status (applied, partial (some applied, some held), held (nothing applied, something held), skipped, or error), applied count, held (one <kind> <path>: <reason> line per held change), error, and violations (why a person was skipped).
sweptPromotion drafts pulled into the pending queue this cycle.
compiledVaults recompiled this cycle.
compile_failuresPeople whose vault failed to build this cycle, as <id>: <reason>. Each keeps their previous vault; everyone else is refreshed. Any entry makes ok false.
pendingPromotions still awaiting approval after this cycle.
clients_createdSelf-service client spaces provisioned this cycle.
clients_rejectedClient requests refused this cycle — usually a routine name collision.
clients_tamperingOf those rejections, how many were an owner-mismatch — a tamper signal, not a routine outcome.
shares_queuedShare requests routed into _meta/shares/pending/ for approval.
shares_revokedRevoke requests auto-applied this cycle.
share_decisions_appliedDelegated decisions — recipient/lead Approvals/ notes — successfully approved or rejected this cycle.
share_decisions_refusedDelegated decisions refused this cycle — usually an ineligible decider or an already-decided share.
shares_tamperingShare/revoke requests from someone who doesn't own the space at cycle time, plus delegated decision notes with a forged owner — a tamper signal.
promotion_decisions_appliedDelegated promotion decisions — lead PromotionApprovals/ notes — successfully approved or rejected this cycle.
promotion_decisions_refusedDelegated promotion decisions refused this cycle — usually an ineligible decider, an already-decided promotion, or a target the decider doesn't lead.
promotion_tamperingDelegated promotion decision notes with a forged owner — a tamper signal.
indexedVaults reindexed (only meaningful with --index).
index_warningsPer-vault indexing warnings (only meaningful with --index).
triage_findingsDoctor findings routed to people's digests this cycle.
triage_digestsDigest notes written or removed this cycle.
triage_unroutedFindings with no eligible recipient (no admin configured).
triage_warningsWarnings from the triage step (skipped digests, git failures) — surfaced on stderr in text mode, never flips ok.
digest_writtenWeekly digest notes written this cycle (see below).
digest_removedWeekly digest notes removed this cycle because the week had nothing to report for that person.
digest_warningsWarnings from the weekly digest step — surfaced on stderr in text mode, never flips ok.
okfalse if any write-back held an edit or failed, a vault failed to compile, or a tampering signal fired — see below.

The weekly digest

Once a week, on the first cycle after Monday begins (UTC), each person gets People/<id>/Inbox/weekly-digest.md: a short summary of what changed in the notes they can read during the previous Monday–Sunday. It leads with facts that started or ended, then lists new, edited and removed or renamed notes, grouped by space and capped so a busy week stays readable. It leaves out the person's own edits and anything in a space they cannot read, and it is built from the master's git history, so no model is involved. A week with nothing to report writes nothing and removes last week's note. The person's agent reads it and tells them what matters.
The ordering is load-bearing: write-back lands each person's edits (including freshly synced promotion, client, share, and decision requests) in the master first. materialize_clients provisions any self-service client spaces next, so a freshly granted space is visible this same cycle. sweep_shares then routes share/revoke requests — queuing shares, auto-applying revokes — and sweep_approvals applies any delegated decisions sitting in People/*/Approvals/, re-checking eligibility against the live rule before touching anything. All of those steps can append to spaces.yaml, which is reloaded before the promotions sweep and the compile so they see the fresh rules. The promotions sweep runs after that, then sweep_promotion_approvals applies any delegated promotion decisions sitting in People/*/PromotionApprovals/ — re-checking eligibility against the live rule and refusing any decision that targets the shared space, even an admin's — and the recompile runs last so every vault reflects the fully updated master. A hold or a write-back failure never halts the cycle. Held edits are never applied, and the recompile puts the person's copy back; a person whose write-back failed is not recompiled that cycle, so their edits are retried on the next one. While the cycle works on a vault, it marks that vault busy (an uncommitted busy key in .brain-manifest.json), and git refuses the agent's pushes until the compile clears the mark. vault-sync keeps those commits and pushes them on its next run. Just before each person's compile, the cycle runs their write-back once more. After the compile, triage runs doctor and reconciles each person's People/<id>/Inbox/doctor-digest.md — the digests land in master this cycle and compile into vaults on the next. Triage warnings never flip ok.
ok is false when any write-back holds an edit or fails, when a person's vault fails to compile (see compile_failures), or when a client, share, or promotion request is flagged as tampering — an owner-mismatch client request, a share/revoke request from someone who turns out not to own the space when the cycle runs, or a delegated decision note (Approvals/ or PromotionApprovals/) with a forged owner. Routine outcomes don't flip it: a client request that loses a name collision (clients_rejected), a share request rejected for an ordinary reason (unknown recipient, already shared, nothing to revoke), or a delegated decision refused for an ordinary reason (an ineligible decider, an already-decided share or promotion, a promotion targeting a team the decider doesn't lead, or one targeting the shared space) is a normal user outcome, surfaced via the requester's or decider's own inbox note — not a security event. --index adds indexed and index_warnings to the summary; because retrieval is a convenience layer and compile is the security layer, a failed index only warns and never flips ok.
This is the one command to run on a schedule, from cron under flock (below). Do not run it from a master git hook: a hook fires in the middle of a cycle's own commits. It replaces running writeback, promotions sweep, and compile by hand. A person whose vault has never been compiled is skipped (not an error); the recompile creates it. A person whose manifest is corrupt is likewise skipped (with the reason in violations) rather than aborting everyone else's refresh — the recompile rewrites a clean manifest, so the next cycle recovers on its own.
# every 10 minutes, one at a time */10 * * * * flock -n /var/lock/brain-cycle brain cycle --master /srv/brain/master --out /srv/brain/compiled --json >> /var/log/brain-cycle.log 2>&1
brain cycle takes no lock of its own — run one at a time with flock -n (or your scheduler's equivalent). A cycle that outlasts its interval would otherwise overlap the next one, and two concurrent runs both write master and both rebuild the same vaults through the same .building/.old swap directories. -n makes an overlapping tick exit immediately rather than queue, so a slow cycle costs you one skipped refresh instead of a pile-up. Size the interval to your slowest cycle: --index and a large vault are the usual reasons it grows.

brain index

Build or refresh the hybrid search index for one compiled vault. See Retrieval for how the index inherits the compiler's boundary.
brain index --vault <vault> [--full] [--json]
FlagRequiredMeaning
--vaultyesA compiled vault (must contain .brain-manifest.json).
--fullnoRebuild from scratch instead of diffing the manifest.
--jsonnoEmit a JSON report (files_indexed, chunks_embedded, chunks_from_cache, mode, …).
Incremental by default: only files whose manifest sha changed are re-chunked, and only cache-miss chunks are embedded. Embeddings come from the configured provider (see Configuration); with none set, indexing is keyword-only — still fully searchable, just no semantic leg. A model change forces a full rebuild. Embeddings are cached in the shared cache (BRAIN_EMBED_CACHE, default ~/.cache/brain/embeddings.db); if that file is damaged it is rebuilt empty and the command prints a warning.
Sessions/ is excluded. It holds one summary per episode — what was decided and what passed the admission tests — which restates in narrative form what the same routing pass already filed as facts, actions and decisions. Indexed, one episode's content sits in the index twice, and the two copies double-vote in rank fusion — the failure doctor's dup-near check exists to name. Excluded, the files stay on disk, stay readable, and stay linkable by path or Obsidian wikilink; they are simply not search results, not backlink sources, and not nodes in the typed-relation graph. A wikilink pointing into Sessions/ — a fact line's [source:: [[the note]]], for instance — records as unresolved, the same as any other target the index does not hold. Vaults indexed before this change drop their session rows on the next brain index; no --full needed.

brain search

Query a compiled vault's index. Fuses keyword (FTS5) and semantic (vector) ranking with reciprocal-rank fusion.
brain search "<query>" --vault <vault> [--k 8] [--keyword-only] [--center <rel-path>] [--json]
FlagRequiredMeaning
queryyesThe search query (positional).
--vaultyesThe compiled vault to search.
--knoMax results (default 8).
--keyword-onlynoSkip the vector leg even if a provider is configured.
--centernoA note's relative path; results near it in the wikilink graph rank higher (see Retrieval).
--jsonnoEmit {"query", "mode", "hits": [...], "warnings"}.
Each hit carries the file path, heading path, a highlighted snippet, a fused score, and which legs matched. A missing index exits 1 with the exact brain index command to build it.

brain graph

Walk a note's typed relations in a compiled vault. Shows the neighborhood up to --depth hops, each edge labeled with its relation (up, down, same, prev, next) and why it exists (declared in frontmatter, an automatic inverse, or mined from folders, dates, or shared entity types).
brain graph "Clients/Acme/Kickoff.md" --vault out/joe --rel up --depth 3
FlagRequiredMeaning
noteyesRel path, or a filename stem (positional).
--vaultyesCompiled vault root.
--relnoRelation to follow, repeatable (default: all five).
--directionnoout (declared on this note), in (declared elsewhere, pointing here), or both (default).
--depthnoHops to walk. Default 1, max 5.

brain facts

Query time-stamped fact lines — current, as-of a date, or believed-on a date — bullets carrying a [from:: date] (and optionally [until:: date], [source:: …]) — from a compiled vault. See Facts and time for the grammar and the current/as-of/believed-on distinction.
brain facts --vault <vault> [--entity <name>] [--type <type>] [--as-of <date>] [--believed-on <date>] [--ended] [--json]
FlagRequiredMeaning
--vaultyesThe compiled vault to query.
--entitynoFilter to one entity, matched by rel path, title, or alias.
--typenoFilter to one entity type (e.g. client).
--as-ofnoFacts true on this date, using what the vault knows now. Default: today.
--believed-onnoAnswer from the vault's git history as of this date — what it would have said then, replayed at that commit. Mutually exclusive in effect with --as-of (this one wins if both are set).
--endednoInclude facts that have since been closed with [until::].
--jsonnoEmit {"facts": [...], "warnings": [...]}.
--as-of reads the current index (fast, reflects today's knowledge of the past); --believed-on re-parses the vault's files straight from git history at that date (no index needed, answers what was believed then). A vault with no index exits 1 for the default and --as-of forms; --believed-on needs only git.

brain mcp

Run a stdio MCP server over a vault so any MCP client can search it as a tool.
brain mcp --vault <vault>
Register it once with a client — for Claude Code:
claude mcp add brain -- brain mcp --vault ~/brain
It exposes six read-only tools: brain_search (hybrid search, with an optional center note for graph-aware ranking), brain_read (read one file by relative path, refusing symlinks, .. escapes, and anything outside a readable space), brain_links (a note's backlinks and outgoing wikilinks), brain_graph (a note's typed relations, each edge labeled with why it exists), brain_recent (recently changed notes, from the vault's git history), and brain_facts (time-stamped fact lines, current, as-of, or believed-on a date). It runs on the employee's device against their own vault clone, so it inherits the compiler's boundary. Plain brain mcp is stdio only — no HTTP, no OAuth.

Remote mode: --http

For clients that can only reach a server over HTTPS (Claude on the web and on phones), brain mcp --http serves one person's compiled vault over HTTP on loopback. It needs the optional extra (pip install 'brainkit[remote]') and sits behind brain mcp-router and an edge that signs each request with who it came from. See Remote Access.
brain mcp --http --vault <compiled/person> --person <id> --person-email <email> --port <n> [--spool <dir>]
FlagMeaning
--httpServe Streamable HTTP instead of stdio.
--person, --person-emailThe vault's owner. The process answers only to a request signed for this email, and refuses to start if the vault's manifest or folder name names someone else.
--portPort to listen on (loopback only; --host accepts only loopback addresses).
--spoolTurn on the brain_capture tool, which queues saved notes in this folder for brain cycle --spool-root.
--auth-issuer, --auth-jwks-url, --auth-audience, --auth-headerThe edge's identity settings. Each falls back to BRAIN_MCP_ISSUER, BRAIN_MCP_JWKS_URL, BRAIN_MCP_AUDIENCE, BRAIN_MCP_HEADER (configuration).
It refuses to start, with exit code 2 and a one-line reason, when the identity settings are missing, the extra is not installed, the signing keys can't be fetched from --auth-jwks-url, the bind address is not loopback, the vault is not a compiled vault or its manifest or folder name names someone other than --person, or the --spool folder is not a directory owned by the user running the process.

brain mcp-router

Route remote requests to each person's brain mcp --http. One per company. It checks the edge's signed identity, looks the email up in a route table, and forwards to that person's loopback port. It never opens a vault.
brain mcp-router --routes /etc/brain/mcp-routes.yaml [--port 8900]
The routes file is version: 1 plus routes: mapping each email to a port (1024–65535). The router reloads it when it changes and keeps the last good table if a new one is broken. It takes the same --auth-* settings as brain mcp --http. Unknown emails get a 403 no_route; a person whose process is down gets a 503 with Retry-After.

brain doctor

Check a master — and optionally its compiled vaults — for integrity problems that otherwise fail silently. Read-only: doctor never mutates anything. Offline by default too — every check reads local files only, so runs are deterministic and CI-safe unless you pass --net.
brain doctor --master <master> [--out <out>] [--json] [--net]
FlagRequiredMeaning
--masteryesPath to the master vault.
--outnoCompiled-vault root. When given, adds per-vault checks (tombstones, _meta/ leakage, manifest drift).
--jsonnoEmit {"ok": bool, "findings": [...]} on stdout instead of human lines.
--netnoAlso check whether the sources cited by stale pages still resolve (link-rot). Makes HTTP requests; off by default.
Findings carry one of three severities:
SeverityMeaningExamples
errorAn invariant is broken. Exits 1.A master-root AGENTS.md that is missing or whose admission gate no longer matches the shipped assistant protocol (protocol-stale — the assistant is admitting what this version decided does not belong; fix with brain refresh-protocol --write), malformed org.yaml/spaces.yaml, an unknown person: in a rule, a symlink in the master, a crashed-compile tombstone, an unreadable manifest, a note the OS won't let doctor read (unreadable-files — the compiler copies with the same permissions and dies on it, so nobody's vault rebuilds until it's fixed; doctor skips the file in every other check and says so here rather than crashing), a person's generated protocol at 95% of its size limit or over it (protocol-size), a generated protocol Hermes Agent would still refuse to load after brainkit works around the names it can (protocol-blocked — a shared space, entities folder or person id that matches Hermes's filter, a charter that does, or a master AGENTS.md that does; the file ships anyway and the agent runs without it until the name is changed), a credential pasted into a note or any other text file the compiler copies, such as a .env or a config export (secrets — a private key, an AWS access key id, a GitHub, Slack, Stripe live, OpenAI, Anthropic or Google API key, a Slack webhook URL, or a password written into a URL (placeholders such as your_password and localhost or example.com hosts are ignored); only formats with a fixed, recognisable shape are matched, so a random-looking string is never flagged on looks alone. Binary files and files over 2 MB are skipped. The finding names the kind, the note, the line numbers and how many people can read the note, never the value itself. It goes to the admins' digest only. Rotate the key with whoever issued it: deleting the line does not remove it from git history or from vaults already synced), or _meta/ leaked into a compiled vault.
warnProbably a mistake, but nothing leaks.An empty team/role, a rule path that matches nothing, a space no rule covers, a space readable by no one (its rule resolves to zero readers — e.g. a folder named Teams/Sales when the team id is sales, or a departed person's People/ folder — so its notes compile into no vault), a .md sitting loose under Teams//People//Clients/ (in no space, so compiled into no vault), a cross-space reference (a wikilink) or plain-text mention (a restricted space's name in shared prose) whose name is visible to readers who can't see its home space, a promotion draft the sweep will silently skip (a target path it can't validate, an unrecognized mode, or a mode: patch draft whose target is a symlink or doesn't exist), or a Company/Intel/ page that's fallen out of the wiki's conventions — its newest as of/captured citation over 12 months old, no dated citation at all (Home.md exempt), or a lingering <Name> — updates YYYY-MM.md addendum never folded back in, or a note with no links, typed relations, or facts connecting it to anything (unlinked-notes) — invisible to the graph leg of retrieval until someone links it (notes under an Inbox/ or a Corrections/ folder are exempt: the first is unprocessed capture, the second is a structured record that is never meant to be linked), or a standing correction that will not reach its author's agent (corrections-budget) — a rule beyond the 4,000-character protocol budget, one long enough that it can never render at all, a record with no rule: or an unusable from: date, a file under Corrections/ the loader will never pick up (wrong extension, or nested in a subfolder), or one that could not be read, or one withheld because Hermes Agent would refuse the whole protocol with it in (the finding names the pattern, never the rule) — every one of them means the person believes a correction took while their agent never sees it, so each is named rather than dropped, or a correction waiting for its person to confirm it (corrections-pending, sent to that person and the admins), a correction that can never be confirmed because of its shape (corrections-rejected), or a People/<id>/.corrections.json that can't be read (corrections-record, admins only — every correction for that person stays pending until it is fixed), or two notes a common reader sees with identical content (dup-exact), or a group of notes in one space that are near-duplicates of each other (dup-near — one finding per group however many notes it holds, naming a few of them; a match between two spaces is reported as a pair) — they double-vote in retrieval fusion; fold one into the other via a mode: patch promotion — or two notes sharing a title stem in spaces with a common reader (stem-collision): a bare [[stem]] wikilink resolves to whichever file sorts first, so rename one or link by full path (Inbox/, Sessions/, and per-person skeleton files are exempt from all three; paired Home.md landing pages are exempt from stem-collision), or two open fact lines about the same entity that are identical (fact-dup — a double-landed ingest; delete one via write-back) or that assign two values to the same attribute (fact-conflict — a forgotten [until::]; close the superseded line) — either way brain facts returns both and a reading agent gets a coin flip, or a page marked distilled: <URL or title> in frontmatter that has no dated citation or whose newest one is over 12 months old (citations — the citation rule outside Company/Intel/: because the full source deliberately never enters the vault, the citation is the only route back to a detail a distillation dropped), or a well-formed fact line carrying no [source::] (fact-uncited — the protocol requires both [from::] and a [source::] naming the episode that established the claim; lint_facts only ever checked that the dates parse, so an unattributable claim stood in the vault as a first-class fact), a person's generated protocol at 80% of its size limit or more (protocol-size), or a master-root AGENTS.md whose wording has drifted from the shipped assistant protocol while its admission gate still matches (protocol-stale — fix with brain refresh-protocol --write).
infoNormal state worth seeing.A vault with no charter: set in _meta/config.yaml (charter-unset — the relevance test falls back to the vault's space list; set one with brain init --charter), a name brainkit worked around so Hermes Agent keeps loading the protocol (protocol-blocked — a space counted in its folder's summary line instead of listed, or a person shown by their id), files awaiting write-back (vault drift), a count of promotions pending approval, one finding per self-service-created client space (clients — for admin review: rename, merge, or revoke), one finding per share request awaiting approval (shares), one finding per share decision made in-vault by a recipient or team lead in the last 30 days (shares — the delegated-decision audit trail, e.g. "Clients/Acme → person:mary approved by mary on 2026-07-23 (delegated)"), one finding per promotion decision made in-vault by a team lead in the last 30 days (promotions — the same audit trail, e.g. "Teams/sales/SOP.md (from bob) approved by mary on 2026-08-17 (delegated)"), and identical or similar notes held in spaces with no common reader (dup-exact pairs and dup-near groups as info — never a defect, since the copies meet in nobody's vault, but duplicated effort and a natural promotion candidate).
Exit is 1 if any error finding exists, else 0 — so doctor slots into CI or a pre-cron gate. Run it after setup and on a schedule.
Vault drift — a compiled file whose contents differ from the manifest baseline — is the normal "edits awaiting write-back" state, so it is reported as info, never an error.
The "distill, never archive" policy keeps a source's full text out of the vault on purpose, which makes the citation the only recovery path: when a distillation drops a detail, the fix is re-reading the original. Web link rot runs roughly 5–10%/year, so the pages most likely to need re-research are exactly the ones whose sources are most likely dead.
--net HEAD-requests the URLs cited by pages doctor already considers stale, and warns (link-rot) only when a page is stale and its source is gone — the compound signal meaning "re-research this now, while someone still remembers the context". Each finding includes a Wayback Machine lookup as a recovery hint. Neither half alone is actionable: a stale page with a live source is a scheduling problem, and a dead link under a fresh citation is usually just a moved URL.
Only stale pages go on the wire, so a healthy vault makes zero requests. "Dead" is claimed narrowly — HTTP 404/410, or a domain that no longer resolves. A 403 bot-block, a 429, any 5xx, and every timeout are treated as inconclusive and stay silent, because telling someone their only recovery path is gone is a claim worth being sure about.
--net is opt-in because doctor's contract is read-only and offline. brain cycle's triage pass never sets it, so scheduled runs stay deterministic.

brain triage

Route doctor findings to the people who can fix them, as one rolling digest note per person (People/<id>/Inbox/doctor-digest.md). Warn-level content findings (unlinked notes, orphans, stale or unfolded intel, uncited or stale distilled pages, duplicates, and duplicate or conflicting facts) go to the owner of the space they're about — a group of near-duplicate notes in one space is one line however many notes it holds; findings about shared spaces, unresolvable paths, or departed people — and error-level infrastructure findings — go to the admins, as do findings about the generated protocols (protocol-size, protocol-stale, protocol-blocked) at warn level too. A credential pasted into a note (secrets) goes to the admins only, never to the note's owner. Paths and fact statements from spaces a recipient cannot read are redacted from their digest; admins receive the full detail. The digest is machine-owned: identical findings skip the write (a fingerprint in the frontmatter), changed findings rewrite it, and an emptied set deletes it, so re-runs never multiply notes. Fixes flow back through the normal gates — write-back for a person's own spaces, mode: patch promotions for shared pages. brain cycle runs this automatically after each compile; the standalone verb exists for manual runs. Triage also keeps doctor's near-duplicate cache, _meta/cache/dedup.db, up to date: it holds a short summary of each note and the result of its secrets scan (the kind and line of anything found, never the value), so the next run only works these out for new and edited notes; brain doctor on its own reads that file but never writes it, and a master whose .gitignore does not cover _meta/cache/ gets no cache at all.
brain triage --master <master> [--out <out>] [--json]
FlagRequiredMeaning
--masteryesThe master vault.
--outnoCompiled output root — enables the compiled-vault checks, same as doctor --out.
--jsonnoStructured report: routed, digests_written, digests_removed, unrouted, warnings, ok.
Exit is always 0; problems surface as warnings (and doctor remains the failing check for CI).

brain status

Counts, freshness, and health at a glance — in the terminal. Read-only: index reads go through a read-only SQLite connection, so status can never create or migrate an index as a side effect. With an index present, the output includes facts and entities counts.
brain status --vault <vault> [--json] # user lens brain status --master <master> [--out <out>] [--json] # admin lens
FlagRequiredMeaning
--vaultone of the twoA compiled per-person vault. Shows notes/chunks by space, index freshness, files pending reindex, inbox/open-action counts, most-linked notes, and embedding coverage.
--masterone of the twoThe master vault. Shows people, spaces, the promotion queue, a read/write permission matrix, and all doctor findings.
--outnoCompiled-vault root (admin lens only). Adds a per-person table: vault size, note count, index age, and edits awaiting write-back.
--jsonnoEmit the full stats structure on stdout — the automation surface.
status is informational and exits 0 even with warnings; doctor remains the failing check for CI.
A vault without an index still reports space and note counts from the manifest, plus a hint to run brain index. Nothing here requires the embedding provider to be configured.

brain dashboard

Serve a live dashboard that updates in real time as the brain changes, or — with --html — write the original self-contained HTML snapshot (one file, inline styles and scripts, no network requests) for emailing or archiving. The user dashboard includes a Facts tab (filter by entity, type, or date), and the 2D graph rings typed entity pages by type.
# live server (default): starts on http://127.0.0.1:8765 and opens a browser brain dashboard --vault <vault> brain dashboard --master <master> --out <out> # static snapshot (original behavior): write one HTML file and exit brain dashboard --vault <vault> --html dash.html --open
FlagRequiredMeaning
--vault / --masterone of the twoSame two lenses as status.
--outnoCompiled-vault root (admin lens; enables per-person graph/query).
--html PATHnoWrite a static self-contained HTML file to PATH and exit, instead of serving.
--hostnoLive server bind address (default 127.0.0.1).
--portnoLive server port (default 8765; 0 picks a free port).
--no-opennoDo not open a browser when serving.
--opennoOpen the file in a browser (static --html mode only).
--corrections-master PATHnoThe master vault. Adds a Corrections tab where this person confirms or dismisses their own corrections. User lens and live server only; the vault folder must be named after its person. Refused unless --host is 127.0.0.1, ::1 or localhost.
Behavior change: a bare brain dashboard --vault X now serves the live dashboard rather than writing brain-dashboard.html. Scripts that relied on the old default file must pass --html brain-dashboard.html explicitly.
The live dashboard is organized into tabs. The user lens (--vault) shows:
  • Overview — a lead figure and, only when something is actually pending, a "needs attention" list (inbox, open actions, notes awaiting reindex) over notes-by-space, most-linked notes, and recent activity.
  • Inbox and Actions — the "what needs me" worklists: notes waiting in your Inbox (newest first) and every open - [ ] action across your Actions notes, each row opening the source note.
  • Graph — an interactive D3 force graph of the vault's wikilinks (zoom/pan/drag, click a note to see what links in and out, a legend to toggle spaces, a highlight box, and a 3D view toggle powered by three.js).
  • Query — hybrid keyword+semantic search with structured filter chips (space, unresolved links, pending reindex, modified-after, path substring, keyword-only) and a rendered note reader: Markdown with clickable [[wikilinks]], the note's backlinks (linked-from / links-to / unresolved), and a raw-text toggle. Keyboard: / focuses search, arrows move the highlight, Enter opens, Esc closes.
A Capture button (or c) is always in the header: jot a note straight into your Inbox. The composer names the exact destination (People/<you>/Inbox) before you save — under the user lens it writes into your compiled slice and write-back carries it to master on the next brain cycle; there is no way to write outside your own Inbox. From an open note you can also Propose to share, drafting a promotion for an admin — or, for a Teams/<team>/ target, that team's lead — to approve. The header carries a light/dark theme toggle (system by default, your choice persisted).
The admin lens (--master) adds tabs for People (per-person vault size, notes, index age, write-back drift), Permissions (a spaces × people read/write matrix), Promotions, Shares, and Doctor (integrity findings with severity badges). The Promotions tab is a live review surface: each pending item shows its destination, a warning naming exactly who will be able to read it, and its rendered body, with Approve / Reject (reason required) actions and a Sweep drafts button — the whole human gate the promotion queue exists for. Before approving you pick who you are from a dropdown scoped to whoever is entitled to approve that item — admins for anything, plus that team's leads for a Teams/<team>/ item (your last pick is remembered) — rather than typing a name, so approved-by is always both a real person id and an eligible approver. A count badge on the tab surfaces new arrivals from any tab. The Graph and Query tabs gain a person selector that reads the chosen person's compiled vault under --out; Capture can file into any person's Inbox via the hardened brain ingest primitive.
Pending promotions arrive in the same payloads: /api/stats and brain status --master --json carry a promotions_pending array (id, person_id, target_path, created, mode, eligible_approvers). eligible_approvers is the list of person ids that may_approve admits for that specific item — an admin for any target, plus a lead on team T for a Teams/T/ target — resolved server-side so the dashboard's dropdown filters on it rather than re-deriving the rule client-side. It is a convenience for the UI, never the gate: approve() re-checks eligibility whatever the caller believed.
Pending space shares are collected into the master stats alongside promotions: /api/stats and brain status --master --json both carry a shares_pending array (id, person_id, space, share_with, access, created), and POST /api/shares/{id}/{action} (approve/reject) is the same primitive brain shares uses. The live dashboard's Shares tab renders the queue with the same review posture as Promotions — each pending request names the space, who it's from, who it's being shared with, and the access level, with Approve / Reject (reason required) actions and an approver dropdown that remembers your last pick the same way — though it lists the whole org, since a share is decided by its recipient or their team's lead rather than by a per-item rule; an Overview attention item and a tab count badge surface new arrivals from any tab. The static --html snapshot's admin view renders the same queue in a Share requests section mirroring the Promotions table. brain status --master's human-readable output still carries no shares line (only its --json form does) — brain shares list and brain doctor (one info finding per pending share) remain the quickest terminal-only views of the queue.
As brain cycle/brain index run (or you commit to a vault), the page updates within a couple of seconds over a WebSocket — new notes spring into the graph and pulse, tiles retick, the connection dot stays green, and a disconnect shows a "data may be stale" notice until it self-heals.
Privacy stays structural: the user dashboard reads only the compiled vault, so it can only ever visualize what that person is allowed to see. Writes go exclusively through the same safe primitives the rest of the system uses — capture constructs a People/<you>/Inbox/… path (never caller input) and promotions still pass the human gate — so the dashboard can add to the brain without widening the trust boundary. The server binds 127.0.0.1, rejects cross-origin Host/Origin headers, and requires a same-origin JSON POST for every write (CSRF defense); there is no authentication, so a non-loopback --host (which prints a warning) exposes the vault to anyone who can reach the port. org.yaml/spaces.yaml remain read-only — edit them in git.

Exit codes

CodeMeaning
0Success.
1Handled error — unknown person or sender, a held or failed write-back, invalid/missing promotion, an unwritable ingest target, or (for doctor) an error-severity finding. The message is on stderr.
2Usage error from argument parsing (unknown subcommand, missing required flag).