Sanity — Tool detail — The Monster Dept
01 Identity
Understand what you build
Sanity is a code visualizer and explorer. It draws the structure of a project as a radial map you can drill into, ring by ring, to study an entire repo or a single function. Eleven lenses recolor the same rings: age, churn, owner and language, what calls a function and what it reaches, which bodies are duplicated, and how well any of it is documented. It also replays a repository commit by commit, and can export that replay as a movie.
But the rings can also be colored by surprise: how accurately a coding agent predicts a function's contents from its name, signature, location, neighbors, and docs. Honest documentation lowers it and stale documentation raises it, because the docs reach the reader before the code does.
02 Key observations
Draws a repo as rings representing directories, files, and the functions inside them. See their relative size, and drill in.
Color represents surprise: wedges run hot when an agent couldn’t predict their content. Obvious functions go cold, slop lights up.
Readers also report traps: hazards the code does not warn about. Clones marks functions whose bodies are duplicated.
03 Photographic record
Exhibit E
04 Monsterkeeper’s notes
We all write a ton of code, especially now. Most of it is a mess. Our coding agents slog through, making the same mistakes. They repeat mistakes because they repeat the assumptions behind them.
This is something that we can measure: how much do an agent’s expectations of a codebase diverge from reality? I expected it to generate a worklist; I didn’t expect it to be genuinely beautiful.

05 Specifications
| Palette |
A rose ground with a teal accent, over the dept's shared paper-and-ink neutrals. |
|---|---|
| Platform | macOS 13+ (arm64) Windows 10+ (x86-64, arm64) Linux (x86-64) |
| Interface | Desktop app (Tauri shell, Rust core, React front end) MCP server over stdio (launch Sanity with mcp) |
| Storage | Readings live in the repo they describe, as Markdown in .sanity/. No cloud, no sync, no account. |
| Monsterpower | Age, churn, owner, language, callers, reach, clones and the replay need no model. Surprise, legibility, docs and traps are what a reader reports; those four wait on a pass. |
| Readers | Spawns claude, codex, opencode or agy — whichever is installed and signed in. Any MCP host over stdio can open a project and read the result. |
| Trust boundary | Agents may open a project, ask for work, read your files, and report what they found. Sanity never writes to your source. The one thing it writes is the readings, as Markdown in .sanity/. |
06 Packages
07 Handling instructions
If you are an AI assistant here to set Sanity up, here is what you need to know. Sanity spawns its own readers, one per process, outside the repository and with no access to it, so a reading pass is started from the terminal or from the window, and never by you assessing anything yourself. Your context is already full of this repository; anything you graded would be recall, would score as unsurprising, and would quietly turn the whole measurement into a rubber stamp.
-
Get sanity onto the PATH.
The Homebrew cask puts it there. A downloaded app does not, because the binary lives inside the bundle, so the human presses Install sanity command on Sanity’s welcome screen, which links it into /usr/local/bin or ~/.local/bin. Do not add Contents/MacOS to a PATH, and do not write an alias: several other binaries live beside it, and no script can see an alias.
Reading also needs a coding agent installed and signed in on this machine: claude, codex, opencode or agy. Sanity reads by running one of them.
-
Ask which model should read, then sanity init.
Ask before the first pass, unless the human has already said. The reader is the scale: a smaller model is surprised by more, so its readings are not comparable with what is already banked, and a repo read by two models is one map on two scales with nothing on screen saying which wedge is which.
# claude and sonnet are one example; name whichever agent and model the human chose sanity init --harness claude --model sonnetRun it in the repository, or give it a path. --harness takes claude, codex, opencode or agy, and --model takes any model id that agent can reach. If the repo has been read before, match what is already there rather than choosing afresh — sanity summary prints the split by model, and a corpus with two names in it is the thing to raise with the human before adding a third.
-
Say what a full pass costs, before you start one.
A reader takes about 23,000 tokens to enter and about 3,000 per function, and it reads ten functions, so budget roughly 5,300 tokens per function. A thousand-function repository is therefore about five million tokens. sanity status says how many segments still need reading. Put that number in front of the human and let them decide how far to go; do not spawn a full pass on a large repository because you were asked to "set it up".
sanity check --limit 50 # stop after this many readings; a reader does ten, so ten is the step -
Scope the repository first, if some of it is not worth reading.
A .sanityignore at the repository root sets slices aside: they are still drawn, and they leave the denominator, so coverage stops counting them. There are no defaults, and tests are not automatically noise — whether a directory is worth reading is a judgement about this codebase, and Sanity does not have it. What you can do is make the decision cheap: bring the human the functions-per-directory numbers and a proposal. The file is theirs to write.
-
Start the pass with sanity check, or the human presses Read.
Both do the same thing: Sanity spawns the readers and they work through the queue. No window is required, and nothing leaves the machine.
sanity check # watch it here sanity check --readers 8 # how many at once sanity check --limit 50 # stop after this many readings sanity check --detach # start it and return
Stopping costs nothing but the readings in flight: what has landed is written as it lands, and asking again resumes rather than restarting.
-
Commit .sanity/.
The readings belong beside the code they describe, in the same commit as the work that changed it where you can manage it. Do not add the directory to .gitignore, and do not hand-edit what is in it: every entry records the model, the agent, the person and the body hash it was taken against, and an edited one is a measurement nobody took.
-
Re-read after the code moves.
Each reading is tied to the body it was made against, so editing a function marks its reading stale rather than silently keeping it. sanity check queues stale work ahead of unread work, so the same command keeps an assessment current. A release that changes what readers are asked, or how a body is cut, says so in its notes; a coverage number that drops after an upgrade is that, not a bug.
-
Report from the tools, never from memory.
sanity status is progress and what is left; sanity summary is the findings — the grade spread, how many readings were cold, traps identified, the split by model. It is deliberately repo-wide: there is no per-file breakdown, because a summary that named files would be the readings themselves, and those are for the human to read in .sanity/ or on the map. If Sanity is registered as an MCP server (sanity mcp), the same answers arrive as sanity_open, sanity_status and sanity_summary.
-
Two things a human is likely to ask you about.
"Sanity is using all my CPU." Almost certainly a trace: the History replay walks every commit and re-parses what each one touched, which saturates the machine and takes an hour on a very large repository. It only ever starts from the Trace button on a project row, one repository at a time, and that row carries its progress and a Cancel that keeps whatever has been walked so far.
"It says it cannot reach Sanity." The errors distinguish the two cases and each says what to do: not running at all means start it — any of these commands will — while unreachable means transient, and the call should simply be retried.
Trust boundary: You may start a pass and report what it found. You may not take readings, because that is what the spawned readers are for, and a reading of yours would be worth nothing. Do not read .sanity/ to answer questions either — sanity summary exists so you do not have to, and a session that has read the last reader's findings cannot be trusted to spawn the next one. You cannot change the code: Sanity never writes to the source it is looking at, so nothing done here can edit the human’s work. The one thing it writes into the repository is the readings themselves, as Markdown under .sanity/, to be committed alongside the code they describe.