Install
$ agentstack add mcp-ctxr-dev-llm-wiki-memory ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Persistent local memory for AI coding agents. Your agent remembers every session, learns from its mistakes, and gets smarter the longer you work with it.
Claude Code, Cursor, Codex, and every other MCP client forget everything when a session ends. LLM Wiki Memory fixes that: it captures your conversations, compiles them into durable project knowledge and lessons your agent applies next time, and recalls the right context through a local MCP server. Memory lives on your machine as plain Markdown in an LLM wiki versioned in git, searched with local embeddings, and consolidated offline while you sleep.
No RAG stack. No vector database. No Docker. No cloud. Install with one prompt and your agent never starts from zero again.
[](#testing) [](https://nodejs.org) [](LICENSE) [](https://modelcontextprotocol.io)
[](https://huggingface.co/Xenova/bge-large-en-v1.5) [](#why-a-wiki-instead-of-rag) [](https://github.com/ctxr-dev/skill-llm-wiki) [](https://github.com/ctxr-dev/llm-wiki-memory/stargazers)
Install
Paste this one-liner into your AI coding agent (copy button on the right) — it covers both a fresh install and an update of an existing one. The full procedure lives in [AI-INSTALL-PROMPT.md](AI-INSTALL-PROMPT.md); the agent fetches and follows it:
Set up llm-wiki-memory in this project: fetch https://raw.githubusercontent.com/ctxr-dev/llm-wiki-memory/main/AI-INSTALL-PROMPT.md and follow it EXACTLY (it covers fresh install and update; if already installed, the same file is local at @.llm-wiki-memory/src/AI-INSTALL-PROMPT.md).
Or run it yourself — fresh install:
git clone https://github.com/ctxr-dev/llm-wiki-memory ./.llm-wiki-memory/src
./.llm-wiki-memory/src/bootstrap.sh # add --commit-memory to commit the wiki
./.llm-wiki-memory/src/bootstrap.sh --schedule daily # optional: hourly cron / launchd
Update an existing install:
git -C .llm-wiki-memory/src fetch origin
# Runbooks you have NOT applied yet — READ THESE FIRST, oldest → newest:
git -C .llm-wiki-memory/src diff --name-only HEAD origin/main -- docs/releases | grep 'update-prompt\.md$' | sort
git -C .llm-wiki-memory/src merge --ff-only origin/main
( cd .llm-wiki-memory/src && npm install --no-audit --no-fund )
./.llm-wiki-memory/src/bootstrap.sh # idempotent; runbooks may add one-shot steps + verification
The bootstrap is idempotent — re-running preserves your edits to .env and your rule files.
What bootstrap does (8 steps)
- Installs dependencies in
./.llm-wiki-memory/src. - Auto-detects the LLM provider:
claudeCLI →codexCLI →ANTHROPIC_API_KEY→OPENAI_API_KEY→MEMORY_LLM_BASE_URL→ ollama at:11434→mock(with a stderr warning). - Writes
./.llm-wiki-memory/settings/.env(preserves your edits on re-run). - Merges hooks into
.claude/settings.jsonand the stdio server into.mcp.json. - Renders vendor-neutral configs into
.agents/and discipline rules into.agents/rules/,.claude/skills/,.claude/rules/,.cursor/rules/. - Materialises the hosted wiki at
./.llm-wiki-memory/wiki(with the layout template that declaresconsolidate: refine | noneper category) and validates it. - Adds
/.llm-wiki-memoryto.gitignore(--commit-memorycommits the wiki instead). - Optionally installs the hourly compile + consolidate cron via a wrapper script (
--schedule daily).
Register with a non-Claude client
./.llm-wiki-memory/src/scripts/mcp-config.sh cursor # .cursor/mcp.json
./.llm-wiki-memory/src/scripts/mcp-config.sh codex # ~/.codex/config.toml
./.llm-wiki-memory/src/scripts/mcp-config.sh claude-desktop # claude_desktop_config.json
./.llm-wiki-memory/src/scripts/mcp-config.sh all
Highlights
Everything lives in a local .llm-wiki-memory/ folder. No vector DB, no container, no API service to run.
Every memory is a markdown leaf with full history, maintained by @ctxr/skill-llm-wiki. Every change commits itself to the wiki's own repo with what, when, and why in the message (one commit per save, flush, compile, or consolidate run), so git log alone explains how your memory evolved. Disable via wiki.autoCommit; your project repo is never touched.
Self-improvement lessons save only with explicit user consent, one approval per lesson. Three layers of enforcement: discipline instructions, a Claude Code hook enabled by default (disable via gate.claudeHookEnabled) that confirms each lesson rather than letting one save phrase flush a batch, and an airtight MCP server-side gate (covers Cursor, Codex, generic clients). Every gate decision (and each compile-distilled lesson) lands in a redacted audit ledger (cli.mjs gate-audit).
Long sessions are chunked and distilled in pieces (header-aware → paragraph fallback → hard cut), so a 100K-char transcript never single-passes its way into a CLI timeout. Failed runs persist a full-body stash + structured audit; one cli.mjs redistill retries with no data loss.
A YAML-declared provider chain (anthropic API → openai API → claude CLI → codex CLI → cursor CLI) and per-provider model fallback lists let a deprecated model or a missing CLI cascade automatically — without inlining model names in code.
An hourly cron + a search-driven orchestrator deduplicate near-identical leaves, archive stale entries, and optionally rewrite bodies via the same LLM the rest of the pipeline uses. Never hard-deletes; always reversible.
Health is judged per entity, not per run: every cron tick keeps a slim attempt entry (last consolidate.attemptsKeep runs) plus a full sharded log under state/logs/// for deep diagnosis. A failure that resolves on a later tick stays silent; an entity still failing after consolidate.escalateAfterAttempts consecutive runs (or one error signature recurring across many entities) escalates into a redacted skeleton issue report at issues////..md that your next session surfaces and offers to investigate — ready to copy upstream or turn into a fix PR.
Transformer embeddings rank queries on-device (default Xenova/bge-large-en-v1.5). One setting swaps in a lighter model — or falls back to a lexical scorer with no model download.
Every category declares its consolidation eligibility in /.layout/layout.yaml (consolidate: refine | none). No magic defaults — author intent is always in plain view.
Paste one prompt into your agent or run one script. Idempotent.
Every atom carries an apply-strength — P0 (a hard constraint / guardrail you must honour), P1 (a strong default to apply whenever relevant), P2 (contextual). A rubric fills it by atom type, so you rarely set it by hand. At recall, relevance still ranks first; priority only breaks near-ties and decides which bodies survive the response budget, so a guardrail or key lesson is never crowded out by merely-similar context. P0 is scarce — you confirm it, it is never auto-assigned.
Why a wiki instead of RAG
RAG memory stacks are powerful but heavy: a vector database, a container, an embedding service, ongoing ops. For small and medium projects that overhead is rarely worth it, yet you still want the agent to remember everything and improve itself across sessions.
llm-wiki-memory gives you that loop with a local hosted wiki as the substrate. Every category stays a nested tree (never a flat pile of files): non-daily categories nest by the metadata facets you search by; daily by date; an additional subject axis scatters leaves by what they're about. Git history and validation come free, and the tree stays readable by humans. Recall runs on local embeddings — nothing leaves your machine.
How it works
%%{init: {"theme":"base","flowchart":{"curve":"linear"},"themeVariables":{"lineColor":"#00B8C4","primaryColor":"#0D0D14","primaryTextColor":"#FCEE0A","primaryBorderColor":"#FCEE0A","secondaryColor":"#16161E","tertiaryColor":"#16161E","clusterBkg":"#16161E","clusterBorder":"#00B8C4","edgeLabelBackground":"#0D0D14","textColor":"#00B8C4"}}}%%
flowchart TD
S[AI session]
S -- "pre/post-compact, session-end hooks" --> FL[flush: extract typed atoms]
S -- "ExitPlanMode hook" --> PL[plans tree]
FL --> DA[daily tree]
DA -- "hourly cron-job + session-start hook" --> CMP[compile: promote daily atoms]
CMP --> KSI[knowledge + self_improvement trees]
CMP -. supersedes daily source .-> DA
KSI -- "hourly cron-job + skill rule" --> CN[consolidate: search-driven refinement]
CN --> MG[dedup + LLM merge near-duplicates]
CN --> RF[staleness + LLM semantic refresh]
CN --> HK[orphan / compress / GC / index]
MG --> KSI
RF --> KSI
HK --> KSI
CR[hourly cron tick] --> LG[state/.consolidate-attempts.log]
LG -. "cron-health surfaces unresolved errors" .-> S
AG[Agent recall calls] --> EM[embed.mjs: local embeddings]
EM --> KSI
AG --> PL
The loop in one sentence: session hooks capture typed atoms into daily/; the hourly cron promotes them into knowledge/ and self_improvement/ (compile) and then refines those trees over time (consolidate); every recall hits the same embedding index; every cron attempt logs its outcome so the next session can surface unresolved failures.
Capture pipeline — chunked & recoverable
The flush worker (PostCompact / SessionEnd hooks) chunks oversized transcripts and runs each chunk through a provider/model chain. A clean "nothing durable" verdict writes no leaf at all (the breadcrumb log keeps visibility); a partial or total failure preserves the full body to a stash so cli.mjs redistill can re-attempt later with no data loss.
%%{init: {"theme":"base","flowchart":{"curve":"linear"},"themeVariables":{"lineColor":"#00B8C4","primaryColor":"#0D0D14","primaryTextColor":"#FCEE0A","primaryBorderColor":"#FCEE0A","secondaryColor":"#16161E","tertiaryColor":"#16161E","clusterBkg":"#16161E","clusterBorder":"#00B8C4","edgeLabelBackground":"#0D0D14","textColor":"#00B8C4"}}}%%
flowchart TD
SRC["source.body(redacted, ≤MAX_CHARS)"]
SRC --> CK{"size > chunkthreshold?"}
CK -- no --> SP[single-pass distill]
CK -- yes --> CH["chunk by:1. ### User/Assistant headers2. paragraph breaks3. hard cut (last resort)"]
CH --> MAP["map: distill each chunkvia provider chain"]
MAP --> RED["reduce: LLM merge atoms(depth-capped, deterministic fallback)"]
SP --> WR["write daily leaf+ audit frontmatter"]
RED --> WR
MAP -.->|any chunk failed| STASH["state/failed-distill-*.json(full body + audit)"]
MAP -.->|all chunks failed| RAW["raw-fallback leaf(FULL body, fenced as UNTRUSTED)"]
STASH -.->|"cli.mjs redistill"| CH
The audit fields recorded on every leaf — chunks_total, chunks_succeeded, failed_chunks, provider_chain_tried, final_provider — make every distillation reproducible from frontmatter alone. Redistilled leaves carry redistilled_from, redistill_attempts, and original_outcome.
| Failure mode | What used to happen | What happens now | | --- | --- | --- | | One CLI call exceeds 120 s | Whole session lost; last 8 K tail preserved in a non-recoverable leaf | Each chunk has its own budget; failed chunk(s) stashed for retry | | Model deprecated mid-run | Hard fail (the claude-sonnet-4-X string was inlined in code) | Provider's model list iterates to the next entry; if exhausted, chain moves to next provider | | claude / codex CLI not installed | Hard fail | Chain transparently fast-fails to the next provider | | Distillation produced no atoms | "nothing-durable" marker file written | No leaf written. Breadcrumb log only | | Redistill races a live worker | Both writers raced → one silently overwrote the other; stash deleted | Per-session lock → ESESSIONBUSY; stash preserved |
Memory write-gate (read-freely, write-gated)
Self-improvement lessons are propose-then-confirm: the agent NEVER calls save_lesson (or save_to_dataset(dataset="self_improvement", ...) / write_memory(datasetId="self_improvement", ...)) on its own. It proposes the save in chat, waits for an explicit user yes in the same turn, then calls the tool with userRequested: true. The server refuses gated writes without the flag.
Three enforcement layers, defence-in-depth:
| Layer | Where | What it does | Why | | --- | --- | --- | --- | | Instructions (probabilistic) | MCP initialize + rule files in .agents/rules/, .claude/rules/, .cursor/rules/ | Tells the model the rule, the wording to propose, and the consent contract. | Reaches every MCP client (Claude Code, Cursor, Codex, generic). Not airtight on its own — the model could still ignore it — which is why the next two layers exist. | | Claude Code hook (deterministic, Claude Code only) | PreToolUse hook on the three gated writers; enabled by default, gate.claudeHookEnabled: false makes it a no-op | Inspects the latest user turn for explicit save phrases. Matches → allow. No match → ask (Claude Code prompts the user yes/no). Per-lesson consent (gate.perLessonConsent, default on): a save phrase auto-allows only the FIRST gated write of a turn; every later one re-prompts, so a batch flush cannot ride one yes. Also denies direct Write/Edit to ~/.claude/projects//memory/. | Stops a mis-instructed model BEFORE the call leaves the client. Adds a one-click user gate when needed, per lesson. | | MCP server-side gate (deterministic, every client) | save_lesson / save_to_dataset / write_memory handlers in the local stdio MCP server | Refuses calls without userRequested: true. Also refuses when path: lands the write under self_improvement/... from a non-gated dataset: claim (closes the path-bypass). | The airtight bottom layer. Works for Cursor, Codex, Claude Desktop, generic MCP clients — they don't have hooks, so the server is the only deterministic checkpoint. |
Reconciliation: layers are independent and additive. Any one of them can refuse a save. The model can NOT bypass them: it can't suppress the discipline (sent at initialize), can't disable the Claude Code hook from inside a tool call, and can't forge the userRequested flag (the only legitimate-bypass path is the internal withSystemMaintenance async frame that consolidate uses for its own bookkeeping — entered only by the orchestrator's own code, never by a client request body).
Knowledge, plans, investigations, daily, and tracker-issue writes are not gated — their routing rules apply directly. Set gate.selfImprovementEnabled: false in settings.yaml to disable the server-side check as an operator escape hatch (the other two layers still apply). Set gate.claudeHookEnabled: false to disable the Claude Code hook the same way: it exits with no decision and the normal permission flow applies.
Audit trail. Every write to the gated self_improvement category is appended (redacted, gitignored) to state/.save-gate-audit.log, so the ledger shows how each lesson came to exist: the server records each interactive accepted decision (with its consent basis: user-flag, system-maintenance, or gate-disabled) and each refused decision; the Claude Code hook records each allow / ask decision (allow records also carry the redacted trigger phrase that authorised them); and the compile pipeline records each lesson it auto-distills from your sessions (layer: compile, consent: compile-distilled). Inspect it with cli.mjs gate-audit [--limit N]. It is best-effort observability (never blocks or slows a write or compile, creates no file until something is recorded). Disable with gate.auditTrailEnabled: false; bound its size with gate.auditKeep (default 1000). Restore the legacy turn-level consent with gate.perLessonConsent: false.
Consolidate (offline refinement)
The consolidate orchestrator
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ctxr-dev
- Source: ctxr-dev/llm-wiki-memory
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.