The six that carry the work #
Learn these and you have the tool. Everything below this section can wait until you need it.
$ ns-brain brief # what this project believes. Read it first. $ ns-brain recall "nginx timeout" # before you grep $ ns-brain remember --title "..." --body "..." --type gotcha $ ns-brain prefer "commit messages stay lowercase" --why "corrected twice" $ ns-brain log "deployed 26081201 to the edge box" $ ns-brain note ecg "same filter fixed our drift" # never write into another project
--json works on every command and returns an envelope rather than the human rendering. For recall that matters: the envelope carries matched and a caller acting on results has to check it, because a miss returns nothing rather than the whole scope.
Writing #
remember
Stores a durable fact. Identity is scope plus normalised title, so re-remembering a fact refines it in place instead of growing a second copy. A second memory about the same subject in one scope is refused, with the id already there and the three ways out. See duplicate refusal.
| Flag | What it does |
|---|---|
| --title | Half the identity. Write it in the words a future session would search for. |
| --body | The fact. - reads stdin. |
| --body-file | Read the body from a file, for anything long enough that quoting it is a hazard. |
| --type | Sets the decay tier. The most consequential flag here. |
| --scope | Where it applies. Defaults to global, which means everywhere in this project. |
| --supersedes <id> | The fact changed. The identity moves here and recall of the old wording answers with this. |
| --fork-ok | Supersede a memory another correction already superseded. Needed because two live successors to one fact is what supersession exists to prevent. |
| --confidence | confirmed | reported | assumed. high/medium/low are accepted as the same three. |
| --source | The citation. Required to store something as confirmed; an uncited claim is stored as reported rather than refused. |
| --source-date | When the source said it, as distinct from when you stored it. |
| --importance | 1 to 5, 3 neutral. Stakes, not relevance. |
| --trust | stated | imported | external. Where the row came from. |
| --pin | Always in the brief, ranking irrelevant. |
| --date | When it happened, for a fact being recorded after the fact. |
| --valid-from --valid-to | The window the fact is true in, for anything with a known start or end. |
| --review | Ask to be re-checked on this date. infra and access default to 30 days out. |
| --no-review | Store no review date even for a type that defaults to one. |
| --snooze-until | Keep it out of the brief until this date. |
| --ref | Repeatable. Link to another memory, a ticket, a path. |
| --update | Allow replacing an existing body on a title collision. |
| --allow-duplicate | Store it anyway; the subject overlap is a false positive. |
| --allow-secret | Store text the credential scanner flagged. It is a scanner, so it has false positives and this is how you say so. |
prefer
How the user wants to be worked with. One short command, because friction is the reason this never gets recorded. --why is the half that matters in three months, when the rule looks arbitrary and nobody remembers what prompted it.
Flags: --why, --about <person> (makes it a relationship memory rather than a preference), --confirmed (they stated it outright rather than you inferring it), --scope, --importance, --source, --supersedes, --update, --pin.
A prompt hook watches for preference language in the user's own words and reminds you, at most once every fifteen minutes.
log
Appends to the activity trail. Events are never ranked and never decay: they are the raw material for "what did the last session actually do", which the brief reads.
Flags: --summary, --kind, --scope, --actor, --ref, --allow-secret. Declare kinds with ns-brain kind so map can tell a real kind from a typo.
Reading #
recall
FTS5, then the bounded composite score. Superseded memories do not disappear: recall follows the link forward and answers with the successor, so a query in the outdated wording still returns the current fact.
| Flag | What it does |
|---|---|
| --n | How many results. |
| --scope | Search from here. Sees this scope and every one above it, never below. |
| --type | One memory type only. |
| --tier | A B C D N. |
| --since <days> | Stored within this many days. |
| --from / --to / --on | Date window, or one exact day. |
| --all | Include the superseded versions, for the trail rather than the answer. |
| --all-projects | Widen past this project, in a shared store. The reason a shared store is worth having. |
| --browse | For --json only: on a miss, return the scope instead of nothing. Human output already browses on a miss, so it changes nothing there. |
| --strict | Never browse, in either mode. |
| --no-touch | Do not count this as a use, so a maintenance sweep does not inflate the signal it is auditing. |
--json a miss returns nothing rather than the whole scope and the envelope carries matched so a script can tell the difference. That rule exists because a miss once fed a forget loop and destroyed real memories: the warning was in the human output and the script was reading JSON.
brief · wakeup
brief is the session handoff snapshot. wakeup is what the SessionStart hook runs: the brief, plus the working tree read live from git, plus this project's own declared probes.
| Flag | What it does |
|---|---|
| --max-bytes | Trim from the least important end to fit. This is the number that stops a brief becoming a 73KB file. |
| --max-pinned --max-prefs | Caps per section. |
| --new-for <days> | How much "new since you were last here" to always show. |
| --new-project-below <n> | Show house standards in full while the brain is still this small. |
| --no-axioms | Leave out the rules that hold everywhere. |
| --no-cursor | Do not move the last-brief marker, so the next real session still sees what is new. |
| --no-tree / --no-probes / --no-brief | wakeup only. Drop one part. |
| --emit cursor|gemini | wakeup only. The envelope that host agent needs, instead of plain text. |
| --run / --name / --timeout / --chars | wakeup only. Add, name and bound a probe: a shell command this project wants run at session open, with its output capped. |
get · events · stats · map · coverage
get <id>shows one memory in full, with every metadata field and its provenance.eventsis the activity trail:--n,--kind,--scope,--since,--all-projects.statsis counts and it prints the database path on the second line. Check it before you trust a count.mapis the index of scopes, types, event kinds and pinned memories, declared against actually stored.--topsets how many memories per scope.coverageis what this project knows about, by category.--near "<query>"answers the question a search that found nothing leaves open: what is held near this.
open
What is outstanding: open tasks, work in flight and what just happened, answered from the brain. It is the answer to "where did we leave off" and costs a few hundred tokens where re-deriving it from git log costs thousands. --full prints task bodies.
ladder
Where a memory stands: stated once, hardened by being stated again across sessions, or law. object <id> --why "..." records a disagreement against it, with the reasoning and the ladder reflects it. Both exist so a fact's standing is something the brain can show rather than something a session asserts.
Correcting #
update
Same wording problem, better words. Most of what remember takes — title, body, type, scope, source, confidence, importance, refs, dates — plus the five below. Not everything: --trust, --valid-from/--valid-to, --supersedes, --everywhere and --allow-duplicate belong to the moment a memory is written and are not editable afterwards.
| --force | Allow --body "" to empty a body that is not empty. It exists because a failed command substitution once produced an empty string, blanked a tier-A memory and reported success. |
| --unsupersede | Clear the superseded_by link, leaving both memories standing. |
| --unpin | Out of the brief. |
| --scope | Refile into another scope. |
| --no-review | Clear this memory's review date. |
verify · history · restore · promote
verify <id>marks a memory re-checked today, optionally raising--confidencewith a--source. This is what stopsreview --stale-daysflagging it.historyis the archive of previous versions:--idfor one memory,--nfor how many.restore <version>brings an archived version back.--forcerolls a live memory back to it.promote <id> --to <scope>moves a memory up to a wider scope, once it turns out to apply beyond where it was filed.
Tasks and measures #
Neither is a memory and the separation is deliberate. The long version is on its own page.
$ ns-brain task add "rotate the RAG box cert" --when "every monday 9am" $ ns-brain task list · done <id> · drop <id> · snooze <id> --until "in 2 hours" $ ns-brain measure add p99 412 --unit ms $ ns-brain measure limit p99 --high 500 --better down --why "slo" $ ns-brain measure series p99 --from 01/06
task flags: --when takes "in 3 hours", "tomorrow 9am", "every monday 9am", "daily 17:00"; --owner user|agent; --body; --state open|done|dropped; --all for closed ones; --notify; --until to snooze one occurrence; --out to write the todo somewhere.
measure flags: --unit, --label, --date, --note, --why, --source, --confidence, --ref, --low / --high / --better up|down for a band and --from / --to / --n for a series.
Hygiene #
Old truth reads exactly like current truth. That is the failure mode that matters most here and these four are the answer to it.
$ ns-brain review --stale-days 90 # load-bearing facts nobody has re-checked $ ns-brain doctor # dangling links, cycles, index drift $ ns-brain doctor --fix # repair the repairable, name the rest $ ns-brain map # scopes and kinds, declared against actual $ ns-brain reindex · vacuum # rebuild FTS, checkpoint the WAL
review also reports duplicates and promotion candidates. The pairwise duplicate scan is bounded by --max-pairwise and can be forced with --duplicates; --stale-days sets how long a tier A or B memory may go unverified before it is flagged.
Deleting #
A command issued is a command meant. The brain is driven by agents, and an agent that typed rm is not asked whether it was sure. --force survives on exactly three things, and each one catches an accident the caller did not intend rather than a decision: update --body "" against a body that is not empty (a failed shell substitution once blanked a runbook that way), forget of a pinned memory or a guardrail (a wrong id, and the brief opens with those), and restore over a memory that is still live. Everything else, --purge included, happens on the first command.
| Command | What it takes and what it refuses |
|---|---|
| forget <id> | One memory, leaving a tombstone so it is not relearned as new. Refuses pinned memories and guardrails without --force and refuses a memory that supersedes others, because nulling those links republishes facts retracted on purpose. --reason is recorded. Forgetting archives first, so restore can undo it; --purge is for the value that should never have been stored: no version is kept, every version already held is deleted, the read audit is scrubbed of the text and nothing can bring it back. The tombstone stays, title only. |
| forget --all | Empties this project, every tier, pinned included. Dry run unless --apply, then --i-mean-it on top, with a project-filtered dump written first. --everything takes the activity log, measures, leads and history too. In a shared store it deletes this project's rows and nobody else's, which is the difference that matters. |
| prune | Decayed and superseded rows. Dry run by default, refuses tiers A and B, refuses a same-day cutoff, caps at 25 deletions without --yes and dumps the brain before deleting anything. --older-than, --tier, --scope, --superseded, --max-delete, --all-projects (needs --i-mean-it). |
| uninstall | Removes the hooks, the documents and the config. Dry run unless --apply and never deletes brain.db without --purge: everything else it removes is one init from being recreated and the database is not. |
The pattern under all four: when the tool cannot tell what you meant, it does nothing and says so, never the widest possible action.
Export, import, backup, snapshot #
$ ns-brain export --out brain.json # everything: provenance, links, events $ ns-brain import dump.json --dry-run # report before writing $ ns-brain backup # timestamped, every table read off the schema $ ns-brain backup --list --verify # prove the safety copies actually read $ ns-brain snapshot --out MEMORY.md # generated markdown mirror
Export is complete and there is no lock-in anywhere in this. A snapshot is generated, never authored: a brain living beside a hand-written memory file guarantees a silent contradiction and the auto-loaded file wins regardless of which one is right.
export --out, snapshot --out and backup --out all refuse to overwrite a file that does not carry their own generated-by marker, refuse a path git is tracking, refuse to write outside the project and write mode 600. --force, --allow-tracked and --allow-outside each unlock exactly one of those.
import takes --events to bring the activity log too and --all-projects with --i-mean-it to restore a whole multi-project store. Unknown --type or --tier values are errors rather than silent empty results. Everything imported is stamped trust: imported, because a dump is untrusted input like anything else arriving from outside.
backup --auto is what the hook runs: a safety copy if one is due and nothing otherwise. backup --restore <file> puts one back, dry run until --apply.
Where the memories live #
$ ns-brain share # dry run: what would move $ ns-brain share --apply # into the shared store, old file kept aside $ ns-brain share --off --apply # back to a file of its own $ ns-brain private # in the shared file, out of everyone else's search $ ns-brain private --off --yes # readable again, retroactively $ ns-brain stats # which file am I actually on
Also here: absorb pulls a standalone brain.db in as a project, relabel claims rows filed under another label, context set <key> <value> holds the project facts injected into every brief and scope add <name> --desc / kind add <name> --desc declare the scopes and event kinds in brain.json that map reconciles against what is stored. The whole subject has its own page.
Cross-project #
These live in one file every project reads. Nothing arriving through any of them carries authority. A note is information and only the person at the keyboard is obeyed.
| axiom | Rules that hold in every project. add, edit --id, rm --id, on the first command; removing or rewording one of the core set that ships with the binary is done and said, not refused. --why records what it prevents. |
| standard | House defaults a new project inherits, by --category. --from <id> lifts the text out of a memory you already stored. |
| note | Leave a note for another project, or read yours. --inbox, --read <id>, --ack <id>, --expires <days>, --kind note|prompt. This is one of exactly two ways to reach another project; the other is telling the user. |
| publish | Share one memory as a cross-project lesson, with --why other projects should see it. --retract takes it back out. |
| describe | This project's row in the shared index: --does, --stack, --provides, --keywords, --status, --visibility described|listed, --sync. |
| global | Read the shared side: --find searches the project index, --recall searches published lessons. Ask this before solving a problem twice. |
Opt-in domains #
Invisible until you use them and named here because a feature nobody can find is a feature nobody has.
| capture | Stores what the user said, before the turn is answered. --session names who heard it. |
| utterances | What was said here and how it was classified. |
| whoami | The user model and the evidence behind every line of it. --observe key=value with a required --evidence, --forget key. Shown on demand rather than volunteered. |
| object <id> | Record an objection to a memory. --why is the reasoning, which is the half worth keeping. |
| ladder <id> | Where a memory stands: stated, hardened, or law. |
| lead | A sales pipeline, for the projects that have one. |
Surfaces #
| ui | The web UI, served out of this binary. No Node, nothing to install. --port, --host, --no-open. Page → |
| mcp | Every command as an MCP tool, over stdio or --http <addr>, with --token and --origin. Page → |
| hive | The server edition. Its own section: people, credentials and the organisation. Setup page → |
| axon | Reaching a brain that is not on this machine through a relay: init, issue, install, relay, enrol. Retired on Christopher's machines in favour of Tailscale; still in the binary for installs that use it. Page → |
| hook-recall hook-stop | What the UserPromptSubmit and Stop hooks run. You do not call these by hand; init wires them. |
The hive: people, credentials, the organisation #
Every ordinary command above routes to the server when brain.json names one, so day to day there is nothing new to learn. What ns-brain hive adds is the part a single machine never had: who you are, which brain and who else is on it. A credential is kept per brain (a server and an organisation on it) in the machine's own credentials.json, never in a project; a project names its server and its organisation in brain.json; a machine that holds several brains on one server is asked which, never guessed.
| Command | What it does |
|---|---|
| login --server <url> | Shows a code. Somebody with admin on the organisation approves it and the credential lands here. --token stores one directly (CI, agents). Adds a brain; never replaces another organisation's credential on the same server. |
| approve <CODE> --email --node --role | Let somebody in: to which node, as what (reader, contributor, curator, admin, owner). --guardrail and --axiom pass on the two authorities that are not ranks, from an owner who holds them. Approving a person again updates their grant. |
| logout --server <url> [--org <name>] --revoke | Sign this machine out for good: the token is revoked on the server, then forgotten here. --machine <name> revokes an enrolled machine you no longer hold, from one you do. |
| status [--org] | Is the server there, which organisation and person this credential is, what is queued and every brain this machine holds a credential for. |
| nodes | The part of the organisation's tree this credential reaches from this project. |
| join [--node <id>] [--apply] [--again] | Move this project's memories, tasks and log onto the server. Dry run first; the local file is never emptied; --again re-sends memories only. The node defaults to the project's own, else the organisation's. For a project with no brain yet, init --hive <url> attaches it instead. |
| leave [--apply] | Bring this project back to a local brain, chains and pins intact. A copy: nothing is deleted on the server. |
| queue / flush | Writes made while the server was unreachable wait here in order; a refusal is reported and never retried. |
| dashboard | A single-use link into the organisation's dashboard (approvals, credentials, billing). --json prints it instead of opening a browser. |
| machines / pending / approve-machine / password | Machine enrolment: what is enrolled, what waits, admit one and the password that enrols a new machine. |
| settings --node <id> [--retention type=days] [--sensitivity] [--audit-days] | Read or set a node's retention per memory type, its sensitivity and how long read-audit rows are kept. Admin on the node. |
| retention [--apply] | Delete what the organisation's rules say is past its life. Dry run first; a held node is skipped. |
| erase --email <who> [--apply] | A leaver: their authored memories go, rows that retract others stay anonymised, their attribution and credentials go everywhere in this organisation. Dry run first. |
| hold --node <id> --reason / hold --release <id> | A legal hold freezes a node against retention and erasure; it prints its id, which --release takes. |
Two things the local edition does not have to decide. Deletion is curation on a hive: a contributor cannot forget, a curator can and a colleague's restore brings a mistaken forget back. And global --find searches the organisation's project index (names and one-line summaries, never memories), so a team can be found and asked without a grant on its project; global --recall searches the lessons projects have published.
Install and lifecycle #
| init | Create a brain and wire the hooks. --dir, --project, --agent claude,codex,cursor,gemini, --no-hook, --no-auto-recall, --no-gitignore. Idempotent, migrates in place, never overwrites the database. |
| bootstrap | Seed a new brain from past session transcripts. Writes nothing itself. --read <path> marks one done, --done ends the pass, --again goes through once more, --include-moved reads directories matching the name but not the path. |
| upgrade | --check compares versions, no flags prints the plan, --apply does it and backs up first, --to <version> pins one. |
| release | Publish the current brain version to the shared spine, with --notes. Every project's brief compares itself against it. |
| version · env | version prints the build. env prints date, time, every resolved path, git and project context. The first thing to run when something is not where you expected. |
Environment #
| Variable | What it overrides |
|---|---|
| BRAIN_DB | The database file this project uses. |
| BRAIN_HOME | The brain directory, the one holding brain.json. |
| BRAIN_PROJECT | The project name a write is filed under. The one that matters most over MCP. |
| BRAIN_GLOBAL_DB | The shared store: axioms, the project index, notes. |
| CLAUDE_CONFIG_DIR | Where the spine lives. |
| NS_BRAIN_CREDENTIALS | Where Hive credentials are read from. Outside every project by default, mode 600. |
CLAUDE_CONFIG_DIR and BRAIN_GLOBAL_DB at a scratch directory for anything disposable.