{"slug": "show-hn-memnest-local-first-memory-shared-by-pi-claude-code-and-codex", "title": "Show HN: Memnest, local-first memory shared by pi, Claude Code and Codex", "summary": "Memnest, a new local-first memory tool for AI coding agents, lets pi, Claude Code, and Codex share durable memory across sessions via a single MCP contract. The Rust-based service, which runs entirely on-device with SQLite storage and local embeddings, offers hybrid BM25 and HNSW search, project isolation, and an AES-256-GCM secret vault, with Linux installers available via curl.", "body_md": "Your AI coding agent forgets everything when the session ends. Memnest keeps that memory on your machine and hands it back to the next session, through one small tool contract that pi, Claude Code, Codex, and other MCP clients all speak.\n\n| Capability | What it means |\n|---|---|\n| Durable memory | Decisions, preferences, and corrections you save on purpose, not a chat log dump. |\n| Conversation history | Redacted user and assistant text, kept verbatim and searchable, with no LLM summarization. |\n| Hybrid search | Local BM25 keyword matching and HNSW vector similarity over both kinds of memory. |\n| Project isolation | One directory's memory stays in its own workspace. `playbook` carries rules shared everywhere. |\n| Secret vault | Credentials live in an AES-256-GCM store, separate from anything searchable. |\n\nOne Rust service handles tool calls, prompt-time recall, and transcript capture on separate data paths. SQLite is the source of truth; the Tantivy and HNSW indexes beside it are derived and rebuildable. Nothing here calls an LLM, and embeddings run locally with `intfloat/multilingual-e5-base`\n\n.\n\nWriting and reading are separate paths over the same store. A write is durable before it is searchable, and a read merges two independent rankings rather than trusting either one.\n\n```\nflowchart TD\n    subgraph read[\"Read path\"]\n        direction TB\n        R1[\"query plus cwd\"] --> R2[\"scope: this workspace<br/>and playbook\"]\n        R2 -->|\"exact words\"| R3[\"BM25 keyword hits\"]\n        R2 -->|\"meaning\"| R4[\"vector similarity hits\"]\n        R3 --> R5[\"RRF fusion, k=60\"]\n        R4 --> R5\n        R5 --> R6[\"MMR reranking,<br/>lambda=0.5\"]\n        R6 --> R7[\"results\"]\n    end\n\n    subgraph write[\"Write path\"]\n        direction TB\n        W1[\"memory_remember, hook, or watch\"] --> W2[\"redact credential-shaped text\"]\n        W2 --> W3[\"embed locally with e5\"]\n        W3 --> W4[\"one SQLite transaction:<br/>record plus index job\"]\n        W4 -->|\"exact words\"| W5[\"Tantivy BM25 index\"]\n        W4 -->|\"meaning\"| W6[\"HNSW vector index\"]\n        W5 --> W7[\"clear the index job\"]\n        W6 --> W7\n    end\n```\n\nBoth indexes exist because they fail differently. BM25 finds an exact token like a port number or a crate name but misses a paraphrase; vector similarity finds the paraphrase but can drift past the literal string you actually typed. Which one you need is only known at query time, so a write pays for both.\n\nThe index job is what makes a missing index recoverable: it is written in the same transaction as the record and cleared only after both indexes are durable, so an interrupted write is replayed at startup rather than lost.\n\nLinux x86_64 and aarch64 users can install the latest release without Rust. The script verifies the archive checksum, installs the binary, registers a user systemd service, and checks its health.\n\n```\ncurl -fsSL https://raw.githubusercontent.com/Blue-B/memnest/main/core/scripts/install.sh \\\n  -o /tmp/memnest-install.sh\nbash /tmp/memnest-install.sh --user\n```\n\nReview the downloaded script before running it. A system-wide service is available with `--system`\n\nand requires `sudo`\n\n.\n\nBuilding from source needs Git and a Rust toolchain with 2024 edition support. Running the resulting binary needs neither.\n\n```\ngit clone https://github.com/Blue-B/memnest.git\ncd memnest/core\ncargo build --release\ninstall -m755 target/release/memnest ~/.local/bin/memnest\nmemnest --data-dir ~/.memnest\n```\n\nThat last line runs the service in the foreground. To register it as a background service instead, hand the installer the binary you just built:\n\n```\ncd .. && core/scripts/install-linux.sh --user --bin core/target/release/memnest\n```\n\nWindows and WSL use `install-windows.ps1`\n\nand `install-wsl.ps1`\n\nin the same directory.\n\nOne address serves the HTTP API and the Streamable HTTP MCP endpoint:\n\n```\nhttp://127.0.0.1:3111        HTTP API\nhttp://127.0.0.1:3111/mcp    MCP endpoint\n```\n\nStarting the service downloads nothing. The embedding model arrives on the first operation that needs it, meaning the first write or the first search runs slower than the rest. Run `memnest --warmup-embedding`\n\nto pay that cost up front.\n\nService setup for Linux, WSL, and Windows, plus backup, restore, and retention, is in [ docs/operations.md](/Blue-B/memnest/blob/main/docs/operations.md). Retrieval quality, latency, and rejected CJK and reranker experiments are recorded in\n\n[.](/Blue-B/memnest/blob/main/docs/retrieval-benchmarks.md)\n\n`docs/retrieval-benchmarks.md`\n\nEach harness exposes different extension points, so the wiring differs while the service, the data, and the tool contract stay the same:\n\n| Harness | Prompt-time recall | Memory tools | Transcript capture |\n|---|---|---|---|\n| pi | Autocontext, from the extension | Registered by the extension | `memnest watch` |\n| Claude Code | `memnest hook` on `UserPromptSubmit` |\nMCP | `memnest watch` |\n| Codex | `memnest hook` on `UserPromptSubmit` |\nMCP | `memnest watch` |\n| Other MCP clients | Depends on the client | MCP | Not applicable |\n\nThe sections below show how to set up each path.\n\nPoint an MCP client at the running service:\n\n```\n{\n  \"mcpServers\": {\n    \"memnest\": { \"url\": \"http://127.0.0.1:3111/mcp\" }\n  }\n}\n```\n\nStreamable HTTP is recommended because every client shares one server and one data directory. Use stdio only when that process owns the store. A second writer for the same data directory is rejected instead of racing the indexes:\n\n```\n{\n  \"mcpServers\": {\n    \"memnest\": {\n      \"command\": \"/absolute/path/to/memnest\",\n      \"args\": [\"--mcp\", \"--data-dir\", \"/home/you/.memnest\"]\n    }\n  }\n}\npi install npm:pi-memnest\n```\n\nThe npm package contains the pi adapter, not the memory engine, so start the core service first. The extension registers the five memory tools, adds workspace-scoped Autocontext, and provides `/memnest`\n\nfor status. Vault tools are opt-in. See [ pi-extension/README.md](/Blue-B/memnest/blob/main/pi-extension/README.md).\n\nThe HTTP API is available without MCP. [ adapters/generic-http](/Blue-B/memnest/blob/main/adapters/generic-http) contains a dependency-free JSONL reference adapter.\n\nAll hosts use five memory tools:\n\n```\nmemory_remember\nmemory_search\nmemory_get\nmemory_update\nmemory_delete\n```\n\nThe vault API is initialized locally, but model-facing secret tools are hidden by default. Set `MEMNEST_EXPOSE_SECRET_TOOLS=1`\n\nfor a trusted agent process to add four tools:\n\n```\nsecret_set\nsecret_get\nsecret_list\nsecret_delete\n```\n\nSearch is workspace-scoped. A client passes an absolute `cwd`\n\n, an explicit `project`\n\n, or `project=all`\n\nfor a deliberate cross-project search. Delete moves a memory to trash instead of erasing it immediately.\n\nAn inferred workspace ID is a stable hash of the normalized absolute working directory, so the path never becomes the public collection name and `/work/client-a/api`\n\ncannot mix with `/personal/api`\n\n. An inferred search covers that workspace plus `playbook`\n\n.\n\nCollections named after a directory basename stay readable as a legacy alias, but only while a single registered workspace owns that name. The moment a second `api`\n\nworkspace appears, the ambiguous alias is disabled for both rather than guessing where the old rows belong. Pass an explicit `project`\n\nwhen you mean a named legacy collection.\n\nSaving with `supersedes=<id>`\n\nmust replace an active memory in the same scope. Both changes land in one SQLite transaction and the old row moves to the hidden `_superseded`\n\ncollection.\n\nStructured facts, rules, provenance, and corrections skip semantic content deduplication so their metadata survives. The `confidence`\n\nand `verified_at`\n\nfields stay client assertions and earn no automatic ranking bonus.\n\n`memnest hook`\n\nreads a host prompt event from stdin and prints a small workspace-scoped context block. If the working directory is unknown or the service is unavailable, it prints nothing and does not block the prompt. Retrieved text is marked as untrusted reference data. Transcript results are labeled as conversation evidence, and embedded markup is escaped before injection.\n\nClaude Code and Codex share the same hook shape, so one configuration serves both. Claude Code reads `~/.claude/settings.json`\n\n, while Codex reads `~/.codex/hooks.json`\n\nor an inline `[hooks]`\n\ntable in `config.toml`\n\n.\n\n```\n{\n  \"hooks\": {\n    \"UserPromptSubmit\": [\n      {\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"memnest hook\" }\n        ]\n      }\n    ]\n  }\n}\n```\n\nCodex skips a new or edited hook until you review and trust it with `/hooks`\n\n.\n\n`memnest watch`\n\nis the single transcript capture path for pi, Claude Code, and Codex:\n\n```\nmemnest watch\nmemnest watch --once\nmemnest watch --backfill\n```\n\nIt stores visible user and assistant text after credential redaction. It skips system and developer prompts, reasoning, reminders, tool calls and results, images, and subagent sidechains. Long turns are split into ordered searchable chunks. Repeated utterances stay distinct, while retries of the same transcript event remain idempotent.\n\nThe watcher follows the known transcript directories and stores offsets in `<data-dir>/watch-state.json`\n\n. A file offset advances only after all chunks were stored or repaired. `--backfill`\n\nimports earlier history; the default starts from new transcript data.\n\nMemnest keeps its state under the selected data directory, normally `~/.memnest`\n\n:\n\n```\nmemory.db       SQLite source of truth: memories, workspace registry, the\n                encrypted secrets table, and pending index work\ntext_index/     Tantivy BM25 keyword index, derived from memory.db\nvectors/        HNSW similarity index over e5 embeddings, derived from memory.db\nmodels/         local embedding model\nmaster.key      key that decrypts the secrets table\narchive/        plaintext JSONL of hard-deleted memories\nwatch-state.json\n```\n\n`memory.db`\n\nis the only original. The two indexes are caches: every write lands in SQLite first, and pending index jobs then update `text_index/`\n\nand `vectors/`\n\n. Deleting either directory is safe, and the service rebuilds it from the database. `memory.db`\n\nis not rebuildable, so back it up together with `master.key`\n\n; without the key the secrets table cannot be decrypted.\n\nService state is readable as JSON. `/health`\n\nreports liveness and the last lifecycle run, and `/stats`\n\nreports collection sizes, disk use, and search latency since startup. Query text is never stored, so nothing you searched for is kept on disk.\n\nThe server binds to `127.0.0.1`\n\nby default. A non-local bind is refused unless `MEMNEST_TOKEN`\n\nis non-empty, and clients must then send `Authorization: Bearer <token>`\n\n.\n\nRegular memory text is local but not encrypted at rest. Credential-shaped strings are redacted before storage, and the legacy `raw_chunk`\n\nfield is not writable through public memory operations. Secrets belong in the vault, not in searchable memory. New stores create `<data-dir>/master.key`\n\nwith private permissions and use AES-256-GCM. New ciphertext is bound to its secret key or server name, while legacy `$enc$`\n\nrows remain readable. Startup fails closed when stored ciphertext does not match the available key. Back up `master.key`\n\nseparately.\n\nDeletion is not erasure. A deleted memory sits in trash for 30 days, and when trash is finally hard-deleted the full record is appended in plaintext to `<data-dir>/archive/YYYY-MM.jsonl`\n\n. Set `MEMNEST_ARCHIVE=0`\n\nto stop writing those files, and remove the existing `archive/`\n\ndirectory yourself if the text must be gone.\n\nDo not expose port 3111 directly to the internet. The rest is in [ SECURITY.md](/Blue-B/memnest/blob/main/SECURITY.md).\n\n| Directory | Role |\n|---|---|\n`core/` |\n\n`pi-extension/`\n\n`adapters/`\n\nOnly `core/`\n\nholds the engine. Everything above it is a transport translator, and everything below it is a file on your disk.\n\n```\nflowchart TB\n    subgraph hosts[\"Hosts\"]\n        H1[\"pi\"]\n        H2[\"Claude Code\"]\n        H3[\"Codex\"]\n        H4[\"other MCP clients\"]\n    end\n\n    subgraph bridges[\"Transport translators\"]\n        B1[\"pi-extension/<br/>tools and Autocontext\"]\n        B2[\"memnest hook<br/>prompt-time recall\"]\n        B3[\"memnest watch<br/>transcript capture\"]\n        B4[\"adapters/generic-http\"]\n    end\n\n    subgraph engine[\"core/ (the only engine)\"]\n        C1[\"server: HTTP and MCP\"]\n        C2[\"redaction and crypto vault\"]\n        C3[\"search: BM25, vectors, RRF, MMR\"]\n        C4[\"storage: SQLite and index queue\"]\n    end\n\n    subgraph disk[\"Your disk\"]\n        D1[\"memory.db\"]\n        D2[\"text_index/\"]\n        D3[\"vectors/\"]\n        D4[\"master.key\"]\n    end\n\n    H1 --> B1\n    H2 --> B2\n    H3 --> B2\n    H4 --> B4\n    H1 --> B3\n    H2 --> B3\n    H3 --> B3\n\n    B1 --> C1\n    B2 --> C1\n    B3 --> C1\n    B4 --> C1\n\n    C1 --> C2\n    C2 --> C4\n    C1 --> C3\n    C3 --> C4\n    C4 --> D1\n    C4 --> D2\n    C4 --> D3\n    C2 --> D4\n```\n\nDevelopment checks:\n\n```\n(cd core && cargo test --locked -- --test-threads=1)\n(cd pi-extension && npm install && npm run build && npm run smoke)\n(cd adapters/generic-http && node test.mjs)\n```\n\nWhy the engine is built this way, including what was rejected, is in [ docs/design-decisions.md](/Blue-B/memnest/blob/main/docs/design-decisions.md). Engine attributions are in\n\n[. Contributions follow](/Blue-B/memnest/blob/main/core/THIRD_PARTY_NOTICES.md)\n\n`core/THIRD_PARTY_NOTICES.md`\n\n[.](/Blue-B/memnest/blob/main/CONTRIBUTING.md)\n\n`CONTRIBUTING.md`\n\nMIT © Blue-B", "url": "https://wpnews.pro/news/show-hn-memnest-local-first-memory-shared-by-pi-claude-code-and-codex", "canonical_source": "https://github.com/Blue-B/memnest", "published_at": "2026-08-30 03:04:35+00:00", "updated_at": "2026-08-30 03:22:04.429992+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-infrastructure"], "entities": ["Memnest", "Claude Code", "Codex", "pi", "SQLite", "Tantivy", "HNSW", "intfloat/multilingual-e5-base"], "alternates": {"html": "https://wpnews.pro/news/show-hn-memnest-local-first-memory-shared-by-pi-claude-code-and-codex", "markdown": "https://wpnews.pro/news/show-hn-memnest-local-first-memory-shared-by-pi-claude-code-and-codex.md", "text": "https://wpnews.pro/news/show-hn-memnest-local-first-memory-shared-by-pi-claude-code-and-codex.txt", "jsonld": "https://wpnews.pro/news/show-hn-memnest-local-first-memory-shared-by-pi-claude-code-and-codex.jsonld"}}