Docs · CLI reference

Every command.
Six of them
do the work.

You reach for the rest when something is wrong, when a fact needs fixing, when you work across projects or when you turn on an optional feature. Every flag below is real; ns-brain <command> --help shows the same list in your terminal.

The six you use every day #

Learn these six and you know the tool. Everything after 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 billing-api "rounding moved to the ledger"  # never write into another project

Add --json to any command and you get structured output instead of the text a person reads. For recall this matters. The output includes matched and a script must check it before acting on the results, because when nothing matches recall returns nothing, not the whole scope.

Writing #

remember

Saves a fact that should last. A memory is identified by its scope plus its title (normalised), so saving the same fact again improves it in place instead of adding a copy. A second memory about the same subject in the same scope is refused: the brain shows you the id it already has and the three ways forward. See duplicate refusal.

FlagWhat it does
--titleHalf of what identifies the memory. Use the words a future session would search for. Words typed straight after remember also count as the title.
--bodyThe fact. - reads stdin.
--body-fileReads the body from a file. Use it for anything long enough that shell quoting could break it. Over MCP the file has to be inside the project.
--typeSets how fast the memory fades (its decay tier). Defaults to note. Of all these flags, this one matters most.
--scopeWhere it applies. Defaults to global, meaning everywhere in this project.
--supersedes <id>The fact changed. This memory takes over the old one's identity and a search in the old wording returns this one.
--fork-okReplace a memory that another correction already replaced. Without it this is refused, because two current versions of one fact is exactly what replacing is meant to prevent.
--claim-okThe body mentions "supersedes #N" only as history. Without this flag a body that claims to replace a live memory is refused unless --supersedes names that id, because writing it in the text retires nothing.
--confidenceconfirmed | reported | assumed. high/medium/low are accepted as the same three.
--sourceWhere the fact came from. Defaults to session, which points at nothing. --confidence confirmed is refused unless someone outside this session could check it: a file, command or URL here, a --ref, a citation in the body or --source "stated by the user". The refusal tells you which flags get it through.
--source-dateWhen the source said it, which may differ from when you saved it.
--importance1 to 5, with 3 neutral. How much is at stake, not how relevant it is.
--truststated | imported | external. Where the memory came from.
--pinAlways shown in the brief, whatever its rank.
--dateWhen it happened, if you are recording it later.
--valid-from
--valid-to
The dates the fact is true between, for anything with a known start or end.
--reviewAsks for a re-check on this date. infra and access default to 30 days out.
--no-reviewSaves no review date, even for a type that normally gets one.
--snooze-untilKeeps it out of the brief until this date.
--refRepeatable. Links to another memory, a ticket or a path.
--updateAllows replacing the body of an existing memory with the same title.
--allow-duplicateSaves it anyway, when the brain wrongly thinks it overlaps an existing memory.
--allow-secretSaves text the credential scanner flagged. Scanners get it wrong sometimes and this is how you say so.

prefer

Records how the user wants to be worked with. It is one short command because if saving a preference takes effort, nobody does it. --why is the part that matters three months later, when the rule looks arbitrary and nobody remembers what caused it.

Flags: --why, --about <person> (makes it a memory about that person, not a general preference), --confirmed (they said it outright, you did not infer it), --scope, --importance, --source, --supersedes, --update, --pin.

A prompt hook spots preference language in what the user types and reminds you, at most once every fifteen minutes.

log

Adds an entry to the activity log. Log entries are never ranked and never fade. They answer "what did the last session actually do" and the brief reads them for exactly that.

Flags: --summary, --kind, --scope, --actor, --ref, --date (the day it happened, when that was not today), --allow-secret. Declare kinds with ns-brain kind so map can tell a real kind from a typo.

Reading #

recall

Full-text search first, then the ranking score. A replaced memory does not vanish: recall follows the link to its replacement and returns that, so a search in the old wording still finds the current fact.

FlagWhat it does
--nHow many results. Default 8.
--scopeSearch from this scope. It sees this scope and every scope above it, never the ones below.
--typeOnly one memory type. An unknown type is an error, not an empty result.
--tierA B C D N. Anything else is an error.
--since <days>Saved or changed within this many days.
--from / --to / --onA date range, or one exact day.
--allInclude the replaced versions too, when you want the history rather than the answer.
--all-projectsSearch past this project, in a shared store. This is the main reason to have a shared store.
--browseOnly for --json: when nothing matches, return the scope instead of nothing. The text output already does this, so there it changes nothing.
--strictNever fall back to the scope, in either mode.
--no-touchDo not count this search as a use, so a cleanup sweep does not skew the usage numbers it is checking.
A miss returns nothing. With --json, when nothing matches you get nothing back, not the whole scope. The output carries matched so a script can tell the two apart. We built it this way after a miss fed a forget loop and deleted real memories: the warning was in the text output and the script was reading JSON.

brief · wakeup

brief is the handoff a new session reads first. wakeup is what the SessionStart hook runs: the brief, plus the live state of your working tree from git, plus any checks (probes) this project has declared.

FlagWhat it does
--max-bytesTrims the least important parts to fit; 0 keeps everything. wakeup goes straight into a session, so it defaults to 6000. brief is something you ask for, so it prints everything unless you set a limit. BRAIN_MAX_BYTES sets the default for both.
--max-pinned
--max-prefs
A cap for each section.
--new-for <days>How many days of "new since you were last here" it always shows.
--new-project-below <n>While the brain is smaller than this, show the house standards in full.
--no-axiomsLeave out the rules that apply in every project.
--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. Leaves out one part.
--emit cursor|geminiwakeup only. Prints the format that agent expects instead of plain text.
--run / --name / --timeout / --charswakeup only. wakeup add --name --run declares a probe: a shell command this project wants run when a session opens. --timeout limits how long it runs (default 10 seconds, max 60) and --chars caps its output (default 1200, max 8000). rm and list manage them. A probe only runs after wakeup trust, which a person must answer at a terminal and which is refused over MCP.

get · events · stats · map · coverage

  • get <id> shows one memory in full, with every metadata field and where it came from. --all-projects reads a memory from another project, never a private one.
  • events is the activity log: --n, --kind, --scope, --since, --all-projects.
  • stats shows counts and prints the database path on its second line. Check that path before you trust a count.
  • map lists the scopes, types, event kinds and pinned memories, comparing what is declared with what is actually stored. --top sets how many memories to show per scope.
  • coverage shows what this project knows about, by category. --near "<query>" helps after a search that found nothing: it shows what is stored close to it.

open

Shows what is still open: tasks, work in progress and what just happened, straight from the brain. It answers "where did we leave off" for a few hundred tokens, where working it out from git log costs thousands. --full prints task bodies and longer extracts, --compact is the short form the hook uses and --n sets how many in-progress memories and events to show (default 6). outstanding and status are other names for the same command.

ladder

Shows how settled a memory is: stated once, hardened because later sessions stated it again or law. object <id> --why "..." records a disagreement with it along with your reasoning and the ladder shows it. Both exist so the brain can show how well a fact stands, instead of a session just claiming it.

Correcting #

update

For the same fact in better words. It takes most of what remember takes (title, body, type, scope, source, confidence, importance, refs, dates) plus the five below. Not all of it: --trust, --valid-from/--valid-to, --supersedes and --allow-duplicate are set when a memory is first written and cannot be changed later.

--forceLets --body "" empty a body that has text in it. We added the check after a failed command substitution produced an empty string, wiped a tier-A memory and reported success.
--unsupersedeRemoves the superseded_by link so both memories count as current.
--unpinStops it always showing in the brief.
--scopeMoves it to another scope.
--no-reviewClear this memory's review date.

verify · history · restore · promote

  • verify <id> marks a memory as re-checked today and can raise --confidence if you give a --source. That is what stops review --stale-days flagging it.
  • history lists previous versions: --id for one memory, --n for how many.
  • restore <version> brings an archived version back. --force rolls a current memory back to it.
  • promote <id> --to <scope> moves a memory up to a wider scope, when it turns out to apply beyond where it was saved.

Tasks and measures #

Neither one is a memory and we keep them apart on purpose. The full story 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> · wait <id> · reopen <id> · set <id> --body "..." · snooze <id> --until "in 2 hours" · due · todo --out TODO.md
$ 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" and with set "none" clears it; --owner user|agent; --body, --title, --scope and --notify with add or set; --state open|waiting|done|dropped and --all (closed ones too) with list; --until with snooze; --out, --allow-tracked and --allow-outside with todo. A flag the subcommand does not read is refused rather than ignored.

measure also takes list, forget <id> and relabel <id> (renames a reading's unit and never touches its value). Flags: --unit, --label, --scope, --date, --note, --why, --source, --confidence, --ref, --low / --high / --better up|down for a band and --from / --to / --n for a series.

Keeping it healthy #

An old fact looks exactly like a current one. That is the biggest risk with any memory and these four are how you catch 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 memories worth promoting. The duplicate scan compares memories in pairs: --max-pairwise limits it and --duplicates forces it. --stale-days sets how long a tier A or B memory can go without a re-check before it is flagged. It finishes with what this brain has never used at all: every memory in the default scope, no event logged, a dated memory with no task, a standing rule saved as some other type and no summary in the shared index.

Deleting #

If you typed it, you meant it. Agents drive the brain and an agent that typed rm is not asked whether it is sure. For deleting, --force is still needed in four cases and each one catches an accident, not a decision: update --body "" on a body that has text (a failed shell substitution once wiped a runbook this way), forget of a pinned memory, a guardrail or the current version of a correction (a wrong id would hit exactly what the brief opens with), restore over a memory that is still current and import of a dump that brings guardrails or retracts a pinned memory or a guardrail (a file somebody handed you; the dry run lists what it would do). --purge includes --force: typing it is the decision. The big deletions (forget --all, pruning tier A or B, anything across --all-projects) need --i-mean-it instead.

CommandWhat it takes and what it refuses
forget <id>Deletes one memory and leaves a tombstone so it is not learned again as new. Without --force it refuses a pinned memory, a guardrail and a memory that replaces others, because deleting that last one would bring the facts it retracted back into search results. --reason is recorded. Forget archives first, so restore can undo it. --purge is for something that should never have been saved: no version is kept, every version already stored is deleted and nothing can bring it back. The tombstone stays, title only.
forget --allEmpties this project: every tier, pinned memories included. It is a dry run unless you add --apply and then --i-mean-it on top. A dump of this project is written first. --everything also takes the activity log, measures, leads and history. In a shared store it deletes this project's rows and nobody else's.
pruneRemoves faded and replaced memories. Say which with --tier or --superseded. It is a dry run unless you add --apply. It refuses tier A or B without --i-mean-it, refuses a cutoff of today, keeps any memory that replaces another, refuses more than 25 deletions (or half the brain) without --yes and dumps the brain before deleting anything. Other flags: --older-than (days, default 30), --scope, --max-delete, --backup, --all-projects (needs --i-mean-it).
uninstallRemoves the hooks, the docs and the config. It is a dry run unless you add --apply and it never deletes brain.db without --purge: one init recreates everything else, but nothing recreates the database. It only acts on the brain the command opened. Pointing it at another brain with a root or --dir is refused.

What all four have in common: when the tool cannot tell what you meant, it does nothing and says so. It never does the biggest thing it could.

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 includes everything, so you are never locked in. A snapshot is generated, never written by hand. Keep a hand-written memory file next to a brain and sooner or later the two disagree without telling you. The file that loads automatically then wins, right or not.

export --out, snapshot --out, backup --out, task todo --out and the --backup dumps of prune, forget --all and uninstall all follow the same rules. They refuse to overwrite a file without their own generated-by marker, refuse a file git tracks, refuse to write outside the project, refuse a symbolic link and write with mode 600. --force, --allow-tracked and --allow-outside each lift exactly one of those. Nothing lifts the symbolic link rule.

init and uninstall refuse a project where a file they would write is a symbolic link or sits under one. They check before writing anything. A cloned repository could commit a link at .claude/settings.json or a hook file that points at a file in your home directory.

import takes --events to bring in the activity log too and --all-projects with --i-mean-it to restore a whole multi-project store. Every row is checked before anything is written, so one row with an unknown type or confidence refuses the whole dump instead of leaving it half imported. Everything imported is marked trust: imported, because a dump is untrusted input like anything else from outside. It arrives unpinned, is confirmed only with a citation, is dated no later than now, never lands in a private project and every text field goes through the secret scan. A key that is not a column in this brain refuses the dump. Axioms, standards, lessons and notes belong to every project and are never imported.

backup --auto is what the hook runs: a safety copy if one is due and nothing otherwise. In a shared store a backup covers only this project; --whole-store takes every project and is meant for a person at a terminal. snapshot --per-scope caps how many memories each scope gets in the mirror (default 25). backup --restore <file> puts a backup back and is a dry run until --apply. It checks the copy before installing it, drops any trigger or view a brain does not have, swaps the file in with a single rename and refuses while another session has the store open.

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 takes over rows saved under another project name, context set <key> <value> (and rm <key>) holds the project facts that go into every brief and scope add <name> --desc / kind add <name> --desc (and rm) declare scopes and event kinds in brain.json, which map compares with what is stored. Sharing has its own page.

Across projects #

These live in one file that every project reads. Nothing that arrives through them has any authority. A note is information. Only the person at the keyboard gives orders.

axiomRules that apply in every project. list, add, edit --id, rm --id. Changing one is a person's decision: it is refused over MCP and at a terminal it shows the change and waits for "yes". You can remove or reword one of the core rules that ship with the binary and the prompt tells you that is what you are doing. --why records what the rule prevents. No LLM can add, edit or remove an axiom, even when a human asks it to. Only a human typing at a terminal can change the axioms.
standardHouse defaults a new project starts with. list, add, rm --id, with the same terminal rule as axiom. --category files it (default general) and --from <id> copies the text from a live memory in this project.
noteLeave a note for another project with note <project> "...", or read yours. A project name this brain does not know is refused, so a note never goes nowhere. --inbox, --read <id>, --ack <id>, --expires <days>, --kind note|prompt and --n. A note waits for the next session in that project; dm reaches a session that is already running.
sessions · dmsessions lists the running sessions this brain can reach (--all adds ended ones). dm <session|project|project/prefix> "..." sends one of them a direct message. It is read on the next turn, or straight away if the session listens on the channel. If a project has several live sessions you have to pick one. --sent, --mute / --unmute (do-not-disturb, --everywhere for all your sessions), --inbox for what it held back. --rdo sends an instruction instead of a message. The recipient lets it through with --accept <id> at a terminal, never over MCP, or refuses it with --reject <id>.
feedbackTell the brain's maintainers what is broken, from any project: ns-brain feedback "..." or - for stdin. You register once with register <email> and verify <code> and it only sends the project name if you add --project. list shows your items and every reply; forget-me --yes erases your registration. Five open items at a time. Feedback is information for the maintainers and never makes anything happen.
publishShares one memory with other projects as a lesson, with --why saying why they should see it. --retract takes it back.
describeThis project's entry in the shared index: --does, --stack, --provides, --keywords, --status, --visibility described|listed, --sync.
globalSearches the shared side: --find searches the project index, --recall searches published lessons. Check here before you solve a problem twice.

Optional features #

These stay hidden until you use them. We list them here because a feature nobody can find might as well not exist.

captureSaves what the user said, before the turn is answered. --session names the session that heard it.
utterancesWhat was said in this project and how each line was classified. --class command|taste|directive|exhibit, --unclassified, --n.
whoamiWhat the brain has learned about the user and the evidence behind each line. --observe key=value with a required --evidence, --forget key. Shown only when asked for.
object <id>Records an objection to a memory. --why is your reasoning, which is the part worth keeping.
ladder <id>How settled a memory is: stated, hardened or law.
leadA sales pipeline, for projects that have one. With no subcommand it lists leads, then add, show <id> and update <id>. --status goes from new to won, lost or skip; --append-note adds a timestamped line.

Other ways in #

uiThe web UI, built into this binary. No Node, nothing to install. --port, --host, --no-open. Page →
mcpEvery command as an MCP tool, over stdio or --http <addr>. Away from loopback it needs --token (or NS_BRAIN_MCP_TOKEN) and --tls-cert / --tls-key. On loopback, if you give no token, one is created and saved to a mode-600 file. --origin and --allow-host say who may call it and --public-url turns on OAuth for hosted clients. Over MCP a file argument outside the project is refused. Page →
channelAn MCP channel server that wakes a session as soon as a direct message arrives. init --channel registers it in .mcp.json.
hook-recall
hook-stop
hook-tool
hook-end
What the UserPromptSubmit, Stop, PostToolUse and SessionEnd hooks run. You never call these yourself; init sets them up.

Install and lifecycle #

initCreates a brain and sets up the hooks. --dir, --project, --agent claude,codex,cursor,gemini,opencode,kilo,copilot,devin,qwen,hermes,continue, --no-hook, --no-auto-recall, --no-gitignore, --channel. Safe to run again: it upgrades the database in place and never overwrites it.
bootstrapSeeds a new brain from past session transcripts. It writes nothing itself. --read <path> marks one transcript done, --done ends the pass, --again goes through them once more, --include-moved also reads directories that match the project name but not its path.
upgrade--check compares versions, no flags prints the plan, --apply does it and backs up first (--backup says where), --to <version> picks a version. Before replacing anything it refuses a release whose signature does not check out against the release keys built into the binary, or that is not tied to the version asked for. Going to a version that is not newer than the current one needs --allow-downgrade. If the new binary does not report the signed version, the old one is put back.
verify-releaseChecks a release's SHA256SUMS.sig against the built-in release keys: --sums, --sig, --version (required, because the signature is tied to the version), --key <fingerprint>, --binary, --retire (repeatable). --pinned prints the keys and their fingerprints. The installer runs this.
release-signFor whoever publishes releases: keygen --out, sign --sums --version --key-file, retire --retire --key-file. The key is read from a file or /dev/tty, never from a flag value or an environment variable.
releaserelease <version> --notes "..." publishes a brain version to the shared spine (the rules and notes every project reads). Every project's brief checks itself against it. Refused over MCP: a person confirms it at a terminal.
version · envversion prints the build. env prints the date, time, every path it resolved and the git and project context. Run it first 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 saved under. This one matters most over MCP.
BRAIN_GLOBAL_DBThe shared store: axioms, the project index, notes and the memories of every shared project.
CLAUDE_CONFIG_DIRWhere the spine lives. Without BRAIN_GLOBAL_DB the shared store is brain/brain.db under it (default ~/.claude).
BRAIN_MAX_BYTESThe default --max-bytes for brief and wakeup.
NS_BRAIN_MCP_TOKENThe mcp --http token, so it stays off the command line.
NS_BRAIN_NO_UPDATE_CHECKSet it to anything to turn off the check for a newer release, both online and from the cached file.
Testing with throwaway paths. Every command registers its project in the shared index, so a throwaway install on the default paths leaves rows in the store you really use. For anything disposable, point CLAUDE_CONFIG_DIR and BRAIN_GLOBAL_DB at a scratch directory.