notnessie

Architecture

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.

Monorepo layout

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.

Storage model (PostgreSQL + pgvector)

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.

Embeddings (local-first)

Semantic search uses Transformers.js with the Xenova/all-MiniLM-L6-v2 model, producing 384-dimensional vectors. Key properties:

!!! 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.

Hybrid search and re-ranking

Retrieval combines lexical and semantic signals, then re-ranks in JavaScript:

  1. Candidate pool (SQL). Postgres scores each memory on full-text rank (against the weighted tsvector) and pgvector cosine similarity to the query embedding, producing a candidate set.
  2. Re-rank (JS). Each candidate is scored with a normalized keyword score, the semantic similarity, recency (a 30-day half-life), confidence, a per-memory-type weight, pinned status, and overlap with any files mentioned in the task.

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.

Confidence

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.

Redaction and safety

A redaction layer runs before any write:

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.

Session summarization

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.

MCP server

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.

Dashboard

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.