Docs · CLI reference

Sixty-three commands.
Six of them are
the product.

The rest is maintenance you run when something is wrong, corrections, cross-project plumbing and domains that stay invisible until you use them. Every flag below is on the binary; ns-brain <command> --help is the same list at the terminal.

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.

FlagWhat it does
--titleHalf the identity. Write it in the words a future session would search for.
--bodyThe fact. - reads stdin.
--body-fileRead the body from a file, for anything long enough that quoting it is a hazard.
--typeSets the decay tier. The most consequential flag here.
--scopeWhere 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-okSupersede a memory another correction already superseded. Needed because two live successors to one fact is what supersession exists to prevent.
--confidenceconfirmed | reported | assumed. high/medium/low are accepted as the same three.
--sourceThe citation. Required to store something as confirmed; an uncited claim is stored as reported rather than refused.
--source-dateWhen the source said it, as distinct from when you stored it.
--importance1 to 5, 3 neutral. Stakes, not relevance.
--truststated | imported | external. Where the row came from.
--pinAlways in the brief, ranking irrelevant.
--dateWhen 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.
--reviewAsk to be re-checked on this date. infra and access default to 30 days out.
--no-reviewStore no review date even for a type that defaults to one.
--snooze-untilKeep it out of the brief until this date.
--refRepeatable. Link to another memory, a ticket, a path.
--updateAllow replacing an existing body on a title collision.
--allow-duplicateStore it anyway; the subject overlap is a false positive.
--allow-secretStore 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.

FlagWhat it does
--nHow many results.
--scopeSearch from here. Sees this scope and every one above it, never below.
--typeOne memory type only.
--tierA B C D N.
--since <days>Stored within this many days.
--from / --to / --onDate window, or one exact day.
--allInclude the superseded versions, for the trail rather than the answer.
--all-projectsWiden past this project, in a shared store. The reason a shared store is worth having.
--browseFor --json only: on a miss, return the scope instead of nothing. Human output already browses on a miss, so it changes nothing there.
--strictNever browse, in either mode.
--no-touchDo not count this as a use, so a maintenance sweep does not inflate the signal it is auditing.
It fails closed. With --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.

FlagWhat it does
--max-bytesTrim 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-axiomsLeave out the rules that hold everywhere.
--no-cursorDo not move the last-brief marker, so the next real session still sees what is new.
--no-tree / --no-probes / --no-briefwakeup only. Drop one part.
--emit cursor|geminiwakeup only. The envelope that host agent needs, instead of plain text.
--run / --name / --timeout / --charswakeup 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.
  • events is the activity trail: --n, --kind, --scope, --since, --all-projects.
  • stats is counts and it prints the database path on the second line. Check it before you trust a count.
  • map is the index of scopes, types, event kinds and pinned memories, declared against actually stored. --top sets how many memories per scope.
  • coverage is 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.

--forceAllow --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.
--unsupersedeClear the superseded_by link, leaving both memories standing.
--unpinOut of the brief.
--scopeRefile into another scope.
--no-reviewClear this memory's review date.

verify · history · restore · promote

  • verify <id> marks a memory re-checked today, optionally raising --confidence with a --source. This is what stops review --stale-days flagging it.
  • history is the archive of previous versions: --id for one memory, --n for how many.
  • restore <version> brings an archived version back. --force rolls 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.

CommandWhat 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 --allEmpties 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.
pruneDecayed 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).
uninstallRemoves 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.

axiomRules 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.
standardHouse defaults a new project inherits, by --category. --from <id> lifts the text out of a memory you already stored.
noteLeave 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.
publishShare one memory as a cross-project lesson, with --why other projects should see it. --retract takes it back out.
describeThis project's row in the shared index: --does, --stack, --provides, --keywords, --status, --visibility described|listed, --sync.
globalRead 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.

captureStores what the user said, before the turn is answered. --session names who heard it.
utterancesWhat was said here and how it was classified.
whoamiThe 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.
leadA sales pipeline, for the projects that have one.

Surfaces #

uiThe web UI, served out of this binary. No Node, nothing to install. --port, --host, --no-open. Page →
mcpEvery command as an MCP tool, over stdio or --http <addr>, with --token and --origin. Page →
hiveThe server edition. Its own section: people, credentials and the organisation. Setup page →
axonReaching 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.

CommandWhat 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 --roleLet 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>] --revokeSign 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.
nodesThe 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 / flushWrites made while the server was unreachable wait here in order; a refusal is reported and never retried.
dashboardA single-use link into the organisation's dashboard (approvals, credentials, billing). --json prints it instead of opening a browser.
machines / pending / approve-machine / passwordMachine 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 #

initCreate 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.
bootstrapSeed 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.
releasePublish the current brain version to the shared spine, with --notes. Every project's brief compares itself against it.
version · envversion 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 #

VariableWhat it overrides
BRAIN_DBThe database file this project uses.
BRAIN_HOMEThe brain directory, the one holding brain.json.
BRAIN_PROJECTThe project name a write is filed under. The one that matters most over MCP.
BRAIN_GLOBAL_DBThe shared store: axioms, the project index, notes.
CLAUDE_CONFIG_DIRWhere the spine lives.
NS_BRAIN_CREDENTIALSWhere Hive credentials are read from. Outside every project by default, mode 600.
Testing against throwaway paths. Every command registers its project in the shared index, so a throwaway install run against the defaults leaves rows in the store you actually use. Pin CLAUDE_CONFIG_DIR and BRAIN_GLOBAL_DB at a scratch directory for anything disposable.