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.1brain {init,refresh-protocol,rename-entities,relink,ingest,webhook,compile,writeback,held,promotions,shares,cycle,index,search,graph,facts,mcp,status,dashboard,doctor,triage} …
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._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.2 for a usage error (argparse, or a missing flag a subcommand needs), and 130 if you interrupt with Ctrl-C.brain --version12345brain --version # brain 0.2.0 brain --version # brain 0.2.0 (rev 69631f68e8c9)
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 installed | Where the revision comes from |
From git (git+https://…) | PEP 610 direct_url.json, which the installer wrote at install time |
| A container image | The 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 init1brain init <dir> --company "Acme Co"
| Argument | Required | Meaning |
dir | yes | Destination directory; must be empty or nonexistent. |
--company | yes | The organization's display name, used in the seeded Home and Memory page titles — it does not name any folder. |
--entities | no | Top-level folder name for the restricted third-party tree (default Clients). |
--entity | no | Singular form for the entity type, used in guidance and folder names (default derived from --entities by stripping trailing "s"). |
--shared | no | Name of the shared top-level space everyone reads (default Company; fixed after init). |
--charter | no | One 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. |
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-protocol1brain refresh-protocol --master <master> [--write]
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.--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-entities1brain rename-entities --master <master> --entities Vendors [--entity vendor]
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 relink1brain relink --master <master> OLD NEW [--write] [--verbose]
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.--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.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 ingestInbox/ in the master. The safe, server-side intake used by email, chat, voice, and upload channels — and by brain webhook for services that push.1brain ingest --master <master> (--person <id> | --from <email>) [--file <path>] [--title <t>] [--source <channel>] [--date <YYYY-MM-DD>] [--json]
| Flag | Required | Meaning |
--master | yes | Path to the master vault. |
--person | one of | Target person by id. |
--from | one of | Target person by their org.yaml email (unknown senders are refused). Mutually exclusive with --person. |
--file | no | Read the note body from this file; default is stdin. |
--title | no | Note title. Defaults to the --file name, else the first non-empty line, else note. |
--source | no | Intake channel label (email/chat/voice/upload/manual). Default manual. |
--date | no | Provenance date. Default today. |
--json | no | Emit {"person_id", "rel_path", "title", "source", "committed", "ok"}. |
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/hook/<source-id>; each verified delivery becomes one brain ingest into the routed person's Inbox.1brain webhook --master <master> [--host <addr>] [--port <n>] [--max-body-mb <n>]
| Flag | Required | Meaning |
--master | yes | Path to the master vault (sources come from _meta/webhook.yaml). |
--host | no | Bind address. Default 127.0.0.1; a non-loopback bind expects a TLS reverse proxy in front. |
--port | no | Port. Default 8766; 0 picks a free port. |
--max-body-mb | no | Largest accepted request body in MB. Default 5 (transcripts are large). |
_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 compile1brain compile --master <master> --out <out> [--person <id>]
| Flag | Required | Meaning |
--master | yes | Path to the master vault. |
--out | yes | Output root; each person's slice lands in <out>/<id>/. |
--person | no | Compile only this person instead of everyone. |
1.brain writeback1brain writeback --master <master> --vault <vault> --person <id>
| Flag | Required | Meaning |
--master | yes | Path to the master vault. |
--vault | yes | The person's edited (synced) vault. |
--person | yes | Whose edits these are — determines the write scope. |
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 held1brain held show <id> --master <master> --out <out>
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 corrections123brain corrections list [--person <id>] --master <master> brain corrections confirm <person> <slug> --by <id> --master <master> brain corrections dismiss <person> <slug> --by <id> --master <master>
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 promotions1brain promotions {list,show,sweep,approve,reject} [id] --master <master> [--approver <who>] [--reason <text>]
| Action | Uses | Meaning |
list | --master | Print pending promotions (id, from, target). |
show <id> | --master | Print 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 | --master | Pull 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 --approver | Publish the promotion to its target space with provenance frontmatter. |
reject <id> | --master --reason | Move 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.mode, chosen when the agent drafts it:| Mode | Target must | On approval |
create (default) | not already exist | writes the whole note as a new file with provenance frontmatter |
append | already exist | adds the body under a --- divider, with an italic attribution line naming who promoted and approved it |
patch | already exist, unchanged since sweep | replaces 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.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 shares1brain shares {list,approve,reject,revoke} [id] --master <master> [--approver <who>] [--reason <text>] [--space <path>] [--subject <grant>]
| Action | Uses | Meaning |
list | --master | Print pending share requests (id, from, space, share-with, access). |
approve <id> | --master --approver | Amend spaces.yaml per the pending request; archive it to approved/. |
reject <id> | --master --approver --reason | Move it to rejected/ with the reason. |
revoke | --master --space --subject | Remove --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.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 cycle1brain cycle --master <master> --out <out> [--json] [--index] [--spool-root <dir>]
| Flag | Required | Meaning |
--master | yes | Path to the master vault. |
--out | yes | Compiled-vault root — the same --out you pass to compile. |
--json | no | Emit a single JSON summary object on stdout instead of human lines. |
--index | no | After recompiling, refresh each vault's search index (see brain index). Index failures warn but never fail the cycle. |
--spool-root | no | File 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. |
--json summary| Field | Meaning |
writebacks | Per-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). |
swept | Promotion drafts pulled into the pending queue this cycle. |
compiled | Vaults recompiled this cycle. |
compile_failures | People whose vault failed to build this cycle, as <id>: <reason>. Each keeps their previous vault; everyone else is refreshed. Any entry makes ok false. |
pending | Promotions still awaiting approval after this cycle. |
clients_created | Self-service client spaces provisioned this cycle. |
clients_rejected | Client requests refused this cycle — usually a routine name collision. |
clients_tampering | Of those rejections, how many were an owner-mismatch — a tamper signal, not a routine outcome. |
shares_queued | Share requests routed into _meta/shares/pending/ for approval. |
shares_revoked | Revoke requests auto-applied this cycle. |
share_decisions_applied | Delegated decisions — recipient/lead Approvals/ notes — successfully approved or rejected this cycle. |
share_decisions_refused | Delegated decisions refused this cycle — usually an ineligible decider or an already-decided share. |
shares_tampering | Share/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_applied | Delegated promotion decisions — lead PromotionApprovals/ notes — successfully approved or rejected this cycle. |
promotion_decisions_refused | Delegated promotion decisions refused this cycle — usually an ineligible decider, an already-decided promotion, or a target the decider doesn't lead. |
promotion_tampering | Delegated promotion decision notes with a forged owner — a tamper signal. |
indexed | Vaults reindexed (only meaningful with --index). |
index_warnings | Per-vault indexing warnings (only meaningful with --index). |
triage_findings | Doctor findings routed to people's digests this cycle. |
triage_digests | Digest notes written or removed this cycle. |
triage_unrouted | Findings with no eligible recipient (no admin configured). |
triage_warnings | Warnings from the triage step (skipped digests, git failures) — surfaced on stderr in text mode, never flips ok. |
digest_written | Weekly digest notes written this cycle (see below). |
digest_removed | Weekly digest notes removed this cycle because the week had nothing to report for that person. |
digest_warnings | Warnings from the weekly digest step — surfaced on stderr in text mode, never flips ok. |
ok | false if any write-back held an edit or failed, a vault failed to compile, or a tampering signal fired — see below. |
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.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.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.12# 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 index1brain index --vault <vault> [--full] [--json]
| Flag | Required | Meaning |
--vault | yes | A compiled vault (must contain .brain-manifest.json). |
--full | no | Rebuild from scratch instead of diffing the manifest. |
--json | no | Emit a JSON report (files_indexed, chunks_embedded, chunks_from_cache, mode, …). |
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 search1brain search "<query>" --vault <vault> [--k 8] [--keyword-only] [--center <rel-path>] [--json]
| Flag | Required | Meaning |
query | yes | The search query (positional). |
--vault | yes | The compiled vault to search. |
--k | no | Max results (default 8). |
--keyword-only | no | Skip the vector leg even if a provider is configured. |
--center | no | A note's relative path; results near it in the wikilink graph rank higher (see Retrieval). |
--json | no | Emit {"query", "mode", "hits": [...], "warnings"}. |
1 with the exact brain index command to build it.brain graph--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).1brain graph "Clients/Acme/Kickoff.md" --vault out/joe --rel up --depth 3
| Flag | Required | Meaning |
note | yes | Rel path, or a filename stem (positional). |
--vault | yes | Compiled vault root. |
--rel | no | Relation to follow, repeatable (default: all five). |
--direction | no | out (declared on this note), in (declared elsewhere, pointing here), or both (default). |
--depth | no | Hops to walk. Default 1, max 5. |
brain facts[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.1brain facts --vault <vault> [--entity <name>] [--type <type>] [--as-of <date>] [--believed-on <date>] [--ended] [--json]
| Flag | Required | Meaning |
--vault | yes | The compiled vault to query. |
--entity | no | Filter to one entity, matched by rel path, title, or alias. |
--type | no | Filter to one entity type (e.g. client). |
--as-of | no | Facts true on this date, using what the vault knows now. Default: today. |
--believed-on | no | Answer 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). |
--ended | no | Include facts that have since been closed with [until::]. |
--json | no | Emit {"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 mcp1brain mcp --vault <vault>
1claude mcp add brain -- brain mcp --vault ~/brain
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.--httpbrain 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.1brain mcp --http --vault <compiled/person> --person <id> --person-email <email> --port <n> [--spool <dir>]
| Flag | Meaning |
--http | Serve Streamable HTTP instead of stdio. |
--person, --person-email | The 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. |
--port | Port to listen on (loopback only; --host accepts only loopback addresses). |
--spool | Turn 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-header | The edge's identity settings. Each falls back to BRAIN_MCP_ISSUER, BRAIN_MCP_JWKS_URL, BRAIN_MCP_AUDIENCE, BRAIN_MCP_HEADER (configuration). |
--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-routerbrain 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.1brain mcp-router --routes /etc/brain/mcp-routes.yaml [--port 8900]
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 doctordoctor never mutates anything. Offline by default too — every check reads local files only, so runs are deterministic and CI-safe unless you pass --net.1brain doctor --master <master> [--out <out>] [--json] [--net]
| Flag | Required | Meaning |
--master | yes | Path to the master vault. |
--out | no | Compiled-vault root. When given, adds per-vault checks (tombstones, _meta/ leakage, manifest drift). |
--json | no | Emit {"ok": bool, "findings": [...]} on stdout instead of human lines. |
--net | no | Also check whether the sources cited by stale pages still resolve (link-rot). Makes HTTP requests; off by default. |
| Severity | Meaning | Examples |
error | An 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. |
warn | Probably 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). |
info | Normal 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). |
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.info, never an error.--net)--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.--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 triagedoctor 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.1brain triage --master <master> [--out <out>] [--json]
| Flag | Required | Meaning |
--master | yes | The master vault. |
--out | no | Compiled output root — enables the compiled-vault checks, same as doctor --out. |
--json | no | Structured report: routed, digests_written, digests_removed, unrouted, warnings, ok. |
0; problems surface as warnings (and doctor remains the
failing check for CI).brain statusstatus can never create or migrate an index as a side effect. With an index present, the output includes facts and entities counts.12brain status --vault <vault> [--json] # user lens brain status --master <master> [--out <out>] [--json] # admin lens
| Flag | Required | Meaning |
--vault | one of the two | A compiled per-person vault. Shows notes/chunks by space, index freshness, files pending reindex, inbox/open-action counts, most-linked notes, and embedding coverage. |
--master | one of the two | The master vault. Shows people, spaces, the promotion queue, a read/write permission matrix, and all doctor findings. |
--out | no | Compiled-vault root (admin lens only). Adds a per-person table: vault size, note count, index age, and edits awaiting write-back. |
--json | no | Emit 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.brain index. Nothing here requires the
embedding provider to be configured.brain dashboard--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.123456# 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
| Flag | Required | Meaning |
--vault / --master | one of the two | Same two lenses as status. |
--out | no | Compiled-vault root (admin lens; enables per-person graph/query). |
--html PATH | no | Write a static self-contained HTML file to PATH and exit, instead of serving. |
--host | no | Live server bind address (default 127.0.0.1). |
--port | no | Live server port (default 8765; 0 picks a free port). |
--no-open | no | Do not open a browser when serving. |
--open | no | Open the file in a browser (static --html mode only). |
--corrections-master PATH | no | The 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. |
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.--vault) shows:- [ ] action across your Actions notes, each row opening the source note.[[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.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).--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./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./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.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.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.| Code | Meaning |
0 | Success. |
1 | Handled 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. |
2 | Usage error from argument parsing (unknown subcommand, missing required flag). |