Skip to content

Repository files navigation

ndx

Per-project memory palace, episodic memory across Claude Code sessions, an issue tracker, and command hooks — all backed by redb. Single Rust binary, one optional embedding-model download, no background daemon.

Features

  • Recall palace — per-project structured memory with rooms, drawers, 4-layer retrieval ladder (identity → Do-Not-Repeat → wake-up → room-filtered → hybrid search), local embeddings via fastembed + all-MiniLM-L6-v2
  • Hybrid search — semantic (cosine) + lexical (BM25) fused via RRF; wins over either alone on both exact identifiers and synonyms
  • Episodic memory — indexes Claude Code session transcripts for full-text search across past sessions, with cross-references between sessions and the files they touched
  • Issue trackerndx issue add | list | close | … builds on top of drawers in the reserved _issues_ room; status, milestone, and closed-at live as drawer metadata
  • Command hooks — PreToolUse on Bash injects CLI syntax hints, filters noisy output, logs events, and auto-injects wake-up context once per Claude session; PreToolUse on Read flags repeated reads of the same (path, mtime) so Claude works from existing context instead of re-reading; PreCompact re-injects wake-up before context compaction; SessionStart/SessionEnd auto-mine the palace
  • File listingndx list and ndx find walk the project tree (gitignore-aware), with optional token estimates per file. Content search is delegated to ripgrep / Claude's Grep tool — no in-binary trigram cache.
  • Cross-referencing — bridges session memory and recall palace ("which sessions touched this file?", "which drawers came from this commit?")
  • Subagent-friendly — pure CLI interface works from any context, including Claude Code subagents and team members
  • Claude-curated quality — slash commands (/ndx-chore, /ndx-recall-classify, -score, -dedupe, -contradict, -summarize) delegate judgment work to Claude instead of brittle heuristics

Architecture

Direct-access embedded storage; no daemon, no socket:

CLI (ndx recall *, ndx issue *, ndx xref drawer/drawer-session/git)
  └─ direct access ──► {project}/.ndx/recall.redb
                          ├─ drawers + BM25 index + embeddings
                          ├─ rooms + links + metadata
                          ├─ file/session/commit cross-references
                          └─ wake-up injection state

CLI (ndx memory *, ndx xref file/session, ndx scan)
  └─ direct access ──► ~/.ndx/memory.redb (sessions, events, agents)

CLI (ndx list / find)
  └─ walkdir over the project tree (gitignore-aware) — no index, no cache

The embedding model (all-MiniLM-L6-v2, ~90 MiB) downloads on first semantic operation and caches in ~/.ndx/models/.

Installation

Quick install

curl -fsSL https://raw.githubusercontent.com/siy/ndx/master/install.sh | bash

Downloads a prebuilt binary from GitHub Releases (macOS ARM64/x86_64, Linux x86_64/aarch64). Falls back to building from source if no prebuilt is available. Installs to ~/.local/bin/ndx, downloads 289 command manifests, registers the PreToolUse + PreCompact hooks, and installs 7 slash commands. Restart Claude Code after install.

From source

Requires Rust 1.70+.

git clone https://github.com/siy/ndx.git
cd ndx
cargo build --release
cp target/release/ndx ~/.local/bin/
ndx install

Per-project setup

cd /path/to/your/project
ndx init                    # adds .ndx/ to .gitignore, appends ## ndx section to CLAUDE.md
ndx recall init             # creates the recall palace (.ndx/recall.redb)

Slash commands (/ndx, /ndx-chore, /ndx-recall-*) live globally in ~/.claude/commands/ after ndx install and are visible from every project Claude Code opens — ndx init does not copy them per-project. If you have an older project with stale per-project copies (from before this changed), run ndx init --clean-up to remove them; git-tracked copies are preserved with an explicit git rm instruction.

Recall Palace Workflow

The recall palace is a per-project structured memory that stores decisions, rationale, architecture, and context — everything that disappears when a session ends. Here's the recommended lifecycle:

0. Introduce ndx to Claude

After ndx init, Claude Code sees the /ndx slash command which documents the full CLI surface. This is the discovery mechanism — Claude reads it and knows what ndx can do. From that point on, Claude can proactively suggest ndx commands, use recall search to find context, and run the maintenance skills without manual prompting. The tight integration flows from this one file.

1. Seed the palace

# Derive drawers from past Claude Code sessions about this project
ndx recall mine --from-memory

# Optionally mine specific high-value files (not the whole tree)
ndx recall mine --project --path CHANGELOG.md
ndx recall mine --project --path docs/architecture.md

Tip: Don't mine the entire repo blindly. Code files and large doc trees produce thousands of paragraph fragments that just duplicate the content. Mine session memory for decisions/rationale, and cherry-pick the files that capture why, not what.

2. Classify and score (via Claude)

/ndx-recall-classify        # assign rooms to unclassified drawers
/ndx-recall-score           # set importance (1-10) on default-5 drawers

Claude reads each drawer's text, proposes a room (e.g. architecture, decisions, people, tools), and scores importance. You review. Expect to delete noise drawers (assistant narration, markdown separators) during classification.

3. Search and retrieve

ndx recall search "why did we choose JWT"      # hybrid semantic + lexical
ndx recall wake                                 # L0 identity + L1 top drawers (for prompt injection)
ndx recall get --room decisions --limit 10      # all decisions, ranked by importance

The PreToolUse hook automatically injects wake-up context (L0 + Do-Not-Repeat + L1) on the first Bash command of each Claude session — no manual wake needed. The PreCompact hook re-injects the same wake-up block whenever Claude compacts its context (manual /compact or automatic at the context limit), so palace context survives compaction intact.

Do-Not-Repeat channel — drawers in the reserved _do_not_repeat_ room render above L1 in every wake-up regardless of importance, capped by [wakeup] dnr_cap in identity.toml (default 20). Use it for corrections and hard rules ("never use X here", "deployment must always Y"). When the count exceeds the cap, an overflow line points at /ndx-chore to consolidate. ndx recall status shows the active rule count.

4. Maintain over time

/ndx-recall-dedupe          # merge near-duplicates after large mines
/ndx-recall-contradict      # flag stale vs current claims
/ndx-recall-summarize       # generate per-room summaries

5. Hand over to the next session

/ndx-recall-handover        # Claude reflects on what it learned, saves as memories

This closes the loop: each session leaves the next one smarter. Mining, classification, and scoring compound — the palace gets more useful the more you use it.

CLI

File listing commands

ndx list and ndx find walk the project tree using the ignore crate, respecting .gitignore. No daemon, no on-disk index, no cache. For content search, use ripgrep / Claude's Grep tool — they're faster and more capable than any cache ndx could maintain, and removing that surface keeps ndx focused on memory.

ndx list                                # list project files
ndx list --path src/ --pattern "*.rs"   # filter by prefix and glob
ndx list --sort modified                # sort by modification time
ndx list --tokens                       # append rough token-cost column (size / per-extension ratio)
ndx list --json                         # JSON: path, size, modified, tokens — always includes tokens

ndx find "**/*.toml"                    # find files matching glob
ndx find "src/**/*.rs" --sort modified
ndx find "**/*.rs" --tokens             # token cost per matched file
ndx find "**/*" --json                  # structured output for scripts and Claude

ndx status                              # memory statistics

Token estimates are size_bytes / ratio_for_extension, deliberately rough — useful for "which of these files is cheapest to read", not exact tokenization. Code (.rs, .py, .go, …) tokenizes at ~3.0; prose (.md, .txt) at ~3.8; whitespace-heavy (.json, .yaml, .toml) at ~4.5; default 3.5.

Memory commands

Direct access to the global memory database — no daemon needed.

ndx memory search "database migration"          # search session transcripts
ndx memory events "docker"                       # search command event log
ndx memory list                                  # recent sessions
ndx memory list --project /path/to/project       # filter by project
ndx memory stats                                 # session/event/agent counts
ndx memory session <session-id>                  # full session details
ndx memory context                               # recent project context
ndx memory subagents "search query"              # search subagent transcripts
ndx memory tree <session-id>                     # session + subagent tree

All memory commands accept --limit N.

Recall palace commands

Per-project structured memory ({project}/.ndx/recall.redb). Drawers are atomic memory units grouped into rooms, retrievable via a 4-layer ladder plus hybrid semantic + lexical search. Direct access, no daemon.

Lifecycle

ndx recall init                             # create the palace
ndx recall status [--json]                  # counts, schema, embedding model, last mine
ndx recall reembed [--force]                # backfill embeddings (downloads model if needed)

Mining — fill the palace

ndx recall mine --from-memory [--since 2026-01-01]   # derive from global session memory
ndx recall mine --from-chroma <path>                 # import from a mempalace ChromaDB
ndx recall mine --project [--path <dir>]             # walk the project, paragraph-chunk text files

All mine modes are idempotent via BLAKE3 content-hash dedup; re-running yields added: 0, deduped: N.

Retrieval — the 4-layer ladder

ndx recall wake [--force]                   # L0 identity + L1 top drawers → stdout (wake-up text)
ndx recall get --room <name> [--limit N]    # L2 metadata retrieval
ndx recall search "query" [flags]           # L3 hybrid (default), --semantic, --lexical
ndx recall search "query" --room decisions --limit 5

L3 defaults to hybrid search: fastembed cosine similarity (top-50) fused with Okapi BM25 (k1 = 1.2, b = 0.75, top-50) via Reciprocal Rank Fusion (k=60). Semantic catches synonyms; lexical catches exact identifiers. Neither alone is sufficient.

Drawers

ndx recall drawer list [--room X] [--limit N] [--pending <op>] [--json]
ndx recall drawer show --id N [--json]
ndx recall drawer add "text" [--room X] [--importance N] [--source-file F]
ndx recall drawer update --id N [--room X] [--importance N] [--text "..."]
ndx recall drawer rm --id N                # full cascade across all indexes
ndx recall drawer link --from A --to B --kind <references|contradicts|supersedes|derived_from>
ndx recall drawer unlink --from A --to B [--kind <kind>]

Rooms and identity

ndx recall room add <name> [--title T] [--description D]
ndx recall room list | show <name> | rename <old> <new> | rm <name>
ndx recall identity show [--merged]         # render merged global + per-project identity.toml
ndx recall identity edit [--project]        # $EDITOR on the identity file (creates template)

Claude-curated maintenance (slash commands)

The palace stores everything raw. Quality is curated via five slash commands that delegate judgment to Claude Code and round-trip through ndx recall drawer update|link|rm --json:

Command Purpose
/ndx-recall-classify assign rooms to unclassified drawers
/ndx-recall-score set meaningful importance on default-5 drawers
/ndx-recall-dedupe merge near-duplicates (cluster by content-hash prefix)
/ndx-recall-contradict flag contradictions and link via LinkKind::Contradicts
/ndx-recall-summarize generate per-room summary drawers in the reserved _summary_ room

Each skill fetches a batch via ndx recall drawer list --pending <op> --limit N --json, decides what to do, and writes back with individual update commands.

Cross-reference commands

ndx xref file src/main.rs               # find sessions that touched this file
ndx xref session <session-id>           # list files touched by a session
ndx xref drawer src/auth.rs             # find palace drawers referencing a file
ndx xref drawer-session <session-id>    # drawers derived from a session
ndx xref git <commit>                   # drawers referencing files changed in a commit (cached)

Project lifecycle

ndx project show                                # current project resolved from cwd's project.toml
ndx project list [--json]                       # distinct project tags + drawer counts
ndx project rename <old> <new>                  # rewrite every drawer's tag in one txn
ndx project move-drawer <id> --to <name>        # one-off retag for a single drawer
ndx project move-drawer <id> --global           # promote a drawer to cross-project visibility
ndx project rm <name> --yes                     # cascade-delete every drawer in a project

HTTP/JSON gateway

ndx serve exposes the global palace over HTTP for remote agents — build bots, dashboards, browse-only operators that need palace access without local filesystem access. Authentication is bearer-token; scopes are r (read-only) or rw (full).

# Token file at ~/.ndx/tokens.toml (or pass --auth-tokens FILE)
cat > ~/.ndx/tokens.toml <<EOF
[[token]]
value = "your-bearer-token-string"
scope = "rw"
label = "build-bot"
EOF

ndx serve --listen 127.0.0.1:7531        # default
ndx serve --listen 0.0.0.0:7531          # remote-accessible (use a TLS reverse proxy)

Endpoints (read; require any valid token):

  • GET /palace/status — schema, drawer/room/link counts, embedding model
  • GET /drawers[?room=&project=&limit=&offset=&all_projects=] — list drawers
  • GET /drawers/:id — single drawer
  • GET /drawers/:id/history — audit log
  • GET /search?q=&mode=hybrid|lexical|semantic&room=&project=&limit=
  • GET /wake?project= — L0+DnR+L1 wake-up text
  • GET /issues[?status=open|closed|all&milestone=&project=]
  • GET /projects — distinct project tags + drawer counts

Endpoints (write; require scope = "rw"):

  • POST /drawers — body {text, room, importance, source_file?, project?, global?}
  • PATCH /drawers/:id — body {room?, importance?, text?}
  • DELETE /drawers/:id
  • POST /drawers/:id/rollback — body {to_version: N}
  • POST /issues — body {title, body?, milestone?, importance?, source_file?, link_drawers?, project?}
  • PATCH /issues/:id — body {milestone?, importance?}
  • POST /issues/:id/close — body {fix?, commit?, link_drawer?}
  • POST /issues/:id/reopen
  • DELETE /issues/:id

Single-process invariant: redb's file lock prevents concurrent ndx serve or CLI access to the same ~/.ndx/palace.redb. Run one server; route CLI usage through it via curl, or run the CLI on the same host while the server is stopped.

Client-side --remote mode

CLI subcommands route to the HTTP gateway (instead of opening the local palace) when a remote endpoint is resolved. Resolution precedence:

  1. --remote URL --token TOK flags on the invocation
  2. $NDX_REMOTE and $NDX_TOKEN environment variables
  3. ~/.ndx/remote.toml:
    url   = "http://palace.example.com:7531"
    token = "your-bearer-token"

Output is JSON when remote (the gateway's wire format) and human-formatted when local. Covered: recall status, recall search, recall wake, recall drawer list/show/history/add/update/rm/rollback, issue list/add/close/reopen/rm, project list.

Audit log + rollback

Every drawer mutation (Create / Update / MetadataPatch / ProjectMove / Delete) writes a row to the per-drawer audit log inside the same redb transaction, capturing the pre-image plus the actor + timestamp. Actor is auto-resolved from $NDX_ACTOR[actor] name in identity.toml$USER/$LOGNAME/$USERNAME.

ndx recall drawer history --id N                       # walk versions oldest-first
ndx recall drawer history --id N --json                # structured form for scripts
ndx recall drawer rollback --id N --to-version V       # restore the pre-image stored at version V
                                                       # (rollback itself records a new history entry)

Issue tracker

A per-project issue log built on top of drawers. Issues live in the reserved _issues_ room; their structured state — status (open/closed), closed_at, milestone — is stored under namespaced issue.* keys in the drawer's metadata map, so tags and other future attributes can be added without schema migrations.

ndx issue add "fix the timeout" --body "happens on staging" --milestone v0.9.0 --importance 8
ndx issue list                                    # default: open
ndx issue list --status closed --milestone v0.9.0 # filter by status and milestone
ndx issue list --status all
ndx issue show <id>
ndx issue update <id> --milestone v0.10.0         # bulk: change milestone or importance
ndx issue close <id> --fix "raised timeout to 30s" --commit abc1234 [--link-drawer N]
ndx issue reopen <id>                             # status → open; close trailer kept as history
ndx issue rm <id>
ndx issue milestones                              # group by milestone, show open/closed counts

Closing an issue stamps issue.closed_at and appends a structured trailer (**Closed YYYY-MM-DD** — fix. Commit: sha.) to the drawer text — fully searchable via BM25/semantic, and ndx xref git <sha> picks up the commit reference. The optional --link-drawer records a derived_from edge from the issue to a rationale drawer (typically the closing session's most recent), preserving the fix narrative. Drawer machinery (search, links, importance, mining, classify/score) all keep working unchanged on issues.

ndx recall status shows Open issues: N.

Maintenance commands

ndx scan                                # scan memory (sessions, events, agents)
ndx install                             # download manifests, register hooks, install global skills
ndx init [path] [--clean-up]            # wire ndx into a project (CLAUDE.md, .gitignore)
                                        # --clean-up removes pre-existing project-local skill copies

Command Hook

ndx registers four Claude Code hooks on ndx install: PreToolUse on Bash (manifest lookup + wake-up), PreToolUse on Read (repeated-read detection), PreCompact (re-inject wake-up before context compaction), SessionStart and SessionEnd (auto-mining).

PreToolUse Bash — Phase A — Syntax injection: Before execution, injects CLI syntax hints (key flags, preferred invocations) from YAML manifests into the agent's context.

PreToolUse Bash — Phase B — Output filtering: Pipes output through noise filters (regex patterns) and truncation (max lines), reducing context window usage.

PreToolUse Bash — Phase C — Event logging: Records every command directly to the memory database for cross-session search.

PreToolUse Read — Repeated-read detection: Captures the file's mtime at hook time and counts past Read events for the same (session, path, mtime). When the upcoming Read would be the third of identical content (no Edit or Write bumped mtime between reads), emits an additionalContext line: ndx: this session has read <path> N times — work from existing context instead of re-reading. An external or Claude-driven edit changes mtime and resets the count automatically.

Manifest format

ndx uses the same YAML manifest format as kcp-commands. Manifests are resolved with a three-tier lookup:

  1. .kcp/commands/<key>.yaml — project-local (check into repo)
  2. ~/.kcp/commands/<key>.yaml — user-level customizations
  3. ~/.ndx/commands/<key>.yaml — bundled (downloaded by ndx install)

How It Works

  1. The recall palace ({project}/.ndx/recall.redb) holds drawers, BM25 postings, embeddings, and links in redb. Direct access from the CLI; no daemon mediates.
  2. The global memory database (~/.ndx/memory.redb) indexes Claude Code session transcripts from ~/.claude/projects/. ndx scan ingests new transcripts; the SessionEnd hook does the same incrementally.
  3. ndx list and ndx find walk the project tree on each invocation via the ignore crate (gitignore-aware). No on-disk file index. For content search, use ripgrep / Claude's Grep tool.
  4. The hook subcommand resolves manifests and responds in <20ms.

Data Storage

Database Location Contents
Recall palace {project}/.ndx/recall.redb Drawers, rooms, links, embeddings, BM25 index, xrefs, wake state
Per-project identity {project}/.ndx/identity.toml Optional per-project identity override (TOML)
Global memory ~/.ndx/memory.redb Sessions, events, agents, cross-references
Global identity ~/.ndx/identity.toml Base identity file (TOML), merged with project override
Embedding model ~/.ndx/models/ Cached all-MiniLM-L6-v2 ONNX model (~90 MiB, downloaded on first use)
Manifests ~/.ndx/commands/*.yaml Command syntax and filter definitions

License

MIT. See LICENSE.

Acknowledgments

ndx's episodic memory, command manifest, and recall palace features are inspired by and compatible with:

  • kcp-commands — Command syntax injection and output filtering for AI coding agents. Created by Cantara. ndx uses the same YAML manifest format and downloads the same bundled manifests (289 commands covering git, docker, kubectl, cloud CLIs, build tools, and more).

  • kcp-memory — Episodic memory daemon for AI coding sessions. Created by Cantara. ndx implements equivalent session transcript parsing and CLI interfaces in Rust.

  • Knowledge Context Protocol — The KCP specification that defines the manifest format and integration patterns.

  • mempalace — The structured memory palace concept (wings, rooms, drawers, 4-layer retrieval ladder, raw verbatim storage) that inspired ndx's recall subsystem. Created by milla-jovovich & Ben Sigman. ndx re-implements the useful ideas in Rust on top of redb and fastembed, deliberately omitting mempalace's AAAK compression layer and MCP server. The 4-layer retrieval ladder, the importance-weighted taxonomy, and the all-MiniLM-L6-v2 embedding choice (for benchmark parity) come directly from mempalace.

kcp-commands and kcp-memory are licensed under Apache 2.0. mempalace is licensed under MIT. ndx is an independent Rust implementation of the same concepts and protocols. The YAML manifest files downloaded by ndx install are redistributed from kcp-commands under their original Apache 2.0 license.

About

Fast file index with trigram search, recall palace (structured episodic memory with hybrid semantic + lexical search), session memory, and command hooks for AI coding agents. Single Rust binary.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages