This page describes how NotNessie is built: the monorepo layout, the storage model, embeddings, hybrid search, and session summarization. It reflects the implementation as it stands; the original product brief is preserved as the Original brief page.
NotNessie is a pnpm monorepo of TypeScript (ESM) packages targeting Node >= 20. Builds use tsup, tests use Vitest, and lint/format use Biome.
packages/
core/ @notnessie/core — domain types, Postgres pool + schema,
repository, redaction, confidence,
embeddings, hybrid search, context packs,
summarizer, service wrappers
mcp/ @notnessie/mcp — McpServer factory, the nine tools,
stdio + Streamable HTTP transports
cli/ @notnessie/cli — the `notnessie` bin (Commander),
init templates, hooks
dashboard/ @notnessie/dashboard — Next.js App Router UI, server actions
examples/
demo-repo/ a pre-wired repo with seeded memory for the demo
Dependencies flow inward: core has no dependence on the others; mcp and
cli depend on core; cli also depends on mcp (to launch the server); the
dashboard reads core directly through server actions. Only core, mcp, and
cli are publishable; the dashboard is private and launched via notnessie dev.
Memory lives in PostgreSQL with the pgvector extension. NotNessie creates and migrates its own schema idempotently — there are no manual migration steps. The core tables:
| Table | Holds |
|---|---|
projects |
One row per repository: id (a hash of the root path), name, unique root_path. |
memories |
Typed memory with confidence, tags[], applies_to_files[], pin/delete flags, a generated weighted tsvector, and a vector(384) embedding (HNSW cosine index). |
memory_sources |
Provenance per memory: kind, session, task, files, command. |
verified_commands |
Unique per (project, command), with last exit code and timestamp. |
task_summaries |
Task, summary, files changed, commands run, outcome, new TODOs. |
A project’s identity is the SHA-256 hash of its repository root path, so the
same checkout always maps to the same project regardless of how you invoke the
CLI. Memory IDs are prefixed nanoids (mem_, task_, cmd_, src_, proj_).
Deletes are soft by default (a flag, restorable); --hard removes the row
permanently. Saves with the same (project, type, lower(title)) upsert in
place, merging tags and files, so re-saving a known fact updates rather than
duplicates it.
Semantic search uses Transformers.js with the Xenova/all-MiniLM-L6-v2
model, producing 384-dimensional vectors. Key properties:
NOTNESSIE_EMBEDDINGS=0) or the model cannot load (offline, missing native
deps), embed returns null, writes store a NULL embedding, and search
falls back to keyword-only ranking. No code path changes.!!! note “First-run model download”
The model downloads on first use, so the first embedding call (and full
semantic search) needs network access. Transformers.js relies on
onnxruntime-node’s native binary; in a pnpm 10 checkout that build is
deferred until approved with pnpm approve-builds. Until then, search runs
keyword-only.
Retrieval combines lexical and semantic signals, then re-ranks in JavaScript:
tsvector) and pgvector cosine similarity to the
query embedding, producing a candidate set.With no query — a general project pack — ranking falls back to pinned, then confidence, then recency, giving a sensible default context pack. Context packs render as Markdown by default or structured JSON on request.
Each memory carries a confidence score derived heuristically from its source kind (an explicit MCP save is more trusted than an auto-captured note) and its memory type. Confidence feeds both ranking and the dashboard, where it is shown as a first-class signal so you can judge machine-written notes.
A redaction layer runs before any write:
.env dumps.Hooks add a second layer of caution: PostToolUse only captures recognized,
non-destructive, secret-free build/test commands, and no hook ever stores full
transcripts or raw terminal output.
The Stop and PreCompact hooks turn a session into memory. Summaries are
generated with the Claude Agent SDK; when the SDK or its auth is not
available to the hook process, a heuristic fallback extracts decisions,
changed files, verified commands, failed approaches, and TODOs instead. Either
way, the result is stored as structured memory subject to the same redaction and
configuration rules.
The @notnessie/mcp package builds an McpServer and registers the
nine tools. It serves over stdio (what Claude Code launches)
and over a stateless Streamable HTTP transport for other clients. The server
migrates the schema per process and caches the resolved project per root.
The dashboard is a Next.js App Router application styled with Tailwind. It is
a curation surface — a ledger you scan and correct — with overview stats, a
memory list with type filters, memory detail with an edit form, a source viewer,
search, open questions, verified commands, task summaries, and a deleted-memories
view. Edits, deletes, restores, and pins are server actions that write directly
to Postgres through @notnessie/core. It runs locally via
notnessie dev.