{
  "markdown": "# memshelf-mcp\n\n> Put your agent's memory on a shelf, hand it the index.\n\n[![PyPI](https://img.shields.io/pypi/v/memshelf-mcp)](https://pypi.org/project/memshelf-mcp/)\n[![Python](https://img.shields.io/pypi/pyversions/memshelf-mcp)](https://pypi.org/project/memshelf-mcp/)\n[![ci](https://github.com/ignatenkofi/memshelf-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/ignatenkofi/memshelf-mcp/actions/workflows/ci.yml)\n[![security](https://github.com/ignatenkofi/memshelf-mcp/actions/workflows/security.yml/badge.svg)](https://github.com/ignatenkofi/memshelf-mcp/actions/workflows/security.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Docs](https://img.shields.io/badge/docs-ignatenkofi.github.io%2Fmemshelf--mcp-blue.svg)](https://ignatenkofi.github.io/memshelf-mcp/)\n[![Sibling: docshelf](https://img.shields.io/badge/sibling-docshelf--mcp-green.svg)](https://github.com/ignatenkofi/docshelf-mcp)\n\n```text\n                              _          _  __\n _ __ ___   ___ _ __ ___  ___| |__   ___| |/ _|\n| '_ ` _ \\ / _ \\ '_ ` _ \\/ __| '_ \\ / _ \\ | |_\n| | | | | |  __/ | | | | \\__ \\ | | |  __/ |  _|\n|_| |_| |_|\\___|_| |_| |_|___/_| |_|\\___|_|_|\n  ____________________________________________\n | INDEX >> | E-01 | E-02 | E-03 | E-04 | ... |\n |__________|______|______|______|______|_____|\n        memory shelves for AI agents\n```\n\n## What this is\n\nLong-running agent sessions burn tokens re-sending history and lose detail\nto lossy auto-compaction. **memshelf** applies the\n[docshelf](https://github.com/ignatenkofi/docshelf-mcp) pattern — tiny index\nin context, bodies fetched on demand — to the agent's own working memory:\n\n1. Closed conversation topics, research dumps, and bulky tool output are\n   offloaded to a local shelf as Markdown **episodes**.\n2. Each episode carries an LLM-written, contract-validated **digest** that\n   preserves decisions, rejected alternatives, artifacts, and open threads.\n3. The agent keeps only `INDEX.md` (kilobytes) + digests in context and\n   **recalls** exact sections via INDEX → episode → section slice over MCP.\n\nPositioning in one sentence: *claude-mem's loop, git's substrate, docshelf's\nnavigation* — episodic memory you can grep, diff, review, and carry between\nhosts. Private and local by default: the standard storage mode is a local\ngit repo with **no remote configured**. The tool is public; the memory\nnever is.\n\n## Measured, not promised\n\nOne week of dogfooding on the live shelf — full numbers and methodology in\n[`docs/demo.md`](docs/demo.md):\n\n| Measure | Result |\n|---|---|\n| Episodes on the shelf | 34 |\n| Standing cost in every session (INDEX + digests) | ~8.6K tokens |\n| Shelved mass those episodes replace | ~1.9M tokens — **≈220 : 1** |\n| One question answered from memory | ~1.8K tokens (INDEX + one episode) |\n| Recall test: fresh agent, INDEX path only | **5 / 5** — zero misses, zero over-fetch |\n\nTokens are counted as chars/4 everywhere, so the *ratios* are\nestimator-independent; absolute counts move with the tokenizer.\n\n## Quick start\n\nAs an **MCP server**:\n\n```bash\n# Claude Code\nclaude mcp add memshelf -- uvx memshelf-mcp\n```\n\n```jsonc\n// Claude Desktop (claude_desktop_config.json)\n{\n  \"mcpServers\": {\n    \"memshelf\": { \"command\": \"uvx\", \"args\": [\"memshelf-mcp\"] }\n  }\n}\n```\n\nAs a **Claude Desktop extension** — [`adapters/claude-desktop/`](adapters/claude-desktop/):\nan `.mcpb` bundle installed from *Settings → Extensions*, with a **Default\nshelf** setting so calls need not repeat the path. Nothing has to be installed\nalongside it — not even Python.\n\nAs a **Claude Code plugin** — [`adapters/claude-code/`](adapters/claude-code/):\na `/shelve` skill plus SessionStart / SessionEnd / PreCompact hooks.\n\nOr from the **shell** (`pip install memshelf-mcp`, Python ≥ 3.10) — the same\nloop, no MCP:\n\n```bash\nmemshelf init   --shelf ~/my-shelf --name \"My working memory\"\nmemshelf shelve --shelf ~/my-shelf --slug 2026-07-23-topic --kind topic \\\n  --digest \"What was decided, what was rejected and why, what stays open.\" \\\n  --section \"Decisions=What was decided, and what was rejected instead — one line each.\"\nmemshelf recall --shelf ~/my-shelf --id 2026-07-23-topic --section Decisions --log\nmemshelf stats  --shelf ~/my-shelf   # claimed + realized savings\nmemshelf doctor --shelf ~/my-shelf   # exit 1 on integrity errors\n```\n\n## Tool surface\n\nOne verb per job; the same names over MCP (`memshelf_*`) and in the CLI\n(long-form descriptions: [`docs/tools.md`](docs/tools.md)):\n\n| Tool | What it does |\n|---|---|\n| `init` | Create (or top up) a memory shelf: docshelf layout, fixed categories |\n| `shelve` | Offload one closed topic as a durable, indexed episode; `--amend` rewrites in place |\n| `lint_digest` | Validate a digest against the contract without touching the shelf |\n| `import` | Retro-shelve a whole exported dialog without pulling it through context |\n| `index` | Return the shelf INDEX — the small recall entry point |\n| `recall` | Fetch an episode by id, or a single `## Section` of it |\n| `search` | Grep the shelf; returns matching episodes |\n| `stats` | The shelf's token economy: standing cost vs shelved mass, claimed vs realized |\n| `advise` | What your context is made of and what you could put down — proposals only |\n| `rebuild` | Regenerate every derived file from the episodes |\n| `rollup` | Archive a period behind one digest-of-digests |\n| `purge` | Drop episodes past `retain_until`, then reindex — dry run by default |\n| `resolve` | Settle multi-writer conflicts: regenerate derived, union the recall log |\n| `doctor` | Diagnose: episode schema, digest contract at rest, secret shapes, index bloat |\n| `prune-splits` | CLI only — remove H2 split directories git never got (migration for #109) |\n\n## The rules the tools enforce\n\n**The digest is a contract, not a convention.** It is the only thing read at\nrecall before fetching a body, so a weak one devalues the whole episode.\n`lint_digest` runs the same validator as `shelve` with no side effects\n(`--strict` turns warnings into failures); errors block a shelve, warnings do\nnot — a pure reference digest legitimately carries no decision marker. A\nrejected digest is a feature: the tool prints exactly what to fix and writes\nnothing.\n\n**`--amend` re-runs the whole pipeline** — redaction, the digest contract,\ncomposition — so an amended episode is exactly as guarded as a fresh one,\nwhich a hand-edit of the file never is. Amending a slug that is not on the\nshelf is an error, not a create.\n\n**The episode is the source; everything else is output.** `ledger.tsv`,\n`INDEX.md`, `stats.svg` and each category's `.meta.json` are derived:\n`shelve` writes and commits the episode alone, `rebuild` renders the rest —\ndelete all four and `rebuild` restores them byte-identically. That is what\nmakes two sessions shelving in parallel a non-event: the merge is clean by\nconstruction. On a shared shelf, let a bot own the derived files on `main` —\nready-to-copy workflows in [`adapters/shelf-repo/`](adapters/shelf-repo/);\n`rebuild --adopt` migrates an older shelf once, `rebuild --check` is the\nCI guard.\n\nTwo consequences worth stating plainly, because getting them wrong costs a\nmerge conflict:\n\n* **`doctor` reports `no-ledger-row` and `stale-index` immediately after a\n  correct `shelve` — on every branch, `main` included.** Nothing is broken:\n  the episode is written, the derived files are not rendered yet. They clear on\n  the next `rebuild` — the bot's run, on a shelf that has one.\n* **Do not rebuild and commit the derived files by hand to silence them.**\n  That is exactly the conflict class the split removes: a hand-regenerated\n  `ledger.tsv`/`INDEX.md`/`stats.svg` meets the bot's, and the merge stops\n  being clean by construction. Wait for the renderer; on a shelf without a bot,\n  run `memshelf rebuild --shelf .` as its own step.\n\nIf those warnings persist for a *day* while episodes keep arriving, that is a\ndifferent state — the renderer is not lagging, it is stopped — and `doctor`\nsays so separately, as `derived-stale` at error severity. The day is counted\nfrom when the renderer could first see the work, not from the ledger's last\ncommit: an episode pushed minutes ago onto a shelf whose ledger has not moved\nsince yesterday says nothing about the renderer, and saying otherwise sent\nreaders to a manual `rebuild`, which is the conflict this whole split exists\nto avoid.\n\nThat arrival is read from this clone's reflog for the tracked upstream — the\none local record of *when the ref moved here*. A commit date is not a\nsubstitute: it says when the episode was written, and «shelve now, push when\nconfirmed» is a documented way to work, so the two can be a working day apart.\nWhere the reflog cannot say — a fresh clone starts an empty one, which is what\nCI and ephemeral agent sessions run in — `doctor` reports\n`renderer-wait-unknown` at the `unknown` level instead of picking a verdict:\nfrom there, «stopped» and «handed the work a minute ago» look the same, and\nthe renderer has to be judged where it can be observed, on its own job's run.\nOn a shelf with no upstream there is no renderer to be fair to, and the\nledger's own age stays the clock.\n\nThere was a third way to hold `stale-index` forever, and it is fixed rather\nthan documented: docshelf split any episode past 50 KiB into section files\nbeside it, `shelve` committed the episode alone, and from then on this working\ncopy rendered an INDEX no other checkout could produce — no rebuild could\nclear it, and `search` answered with addresses that existed on one machine\n(#109). `shelve` no longer splits. A shelf that already carries such\ndirectories keeps reporting them as `local-split-dir` until\n`memshelf prune-splits --shelf . --apply` removes them; the episode file holds\nevery section, so nothing is lost. It is a dry run without `--apply`, and a\nsplit directory that *is* committed is reported and left alone.\n\n**`advise` proposes, never writes.** It answers the question the project was\nfounded on — *a dead topic has been occupying 30K tokens for forty minutes*.\nThe tool cannot see your window, so you tell it what is in there:\n\n```bash\nmemshelf advise --shelf ~/my-shelf \\\n  --occupant 'auth refactor=42000,closed' \\\n  --occupant 'search dump=9000,idle=18' \\\n  --occupant 'Case B verdict=12000,live,episode=2026-07-22-case-b-verdict'\n```\n\nThree things keep it honest: it **counts itself** (INDEX + digests are in the\nreport, not left out of it), it **verifies** `episode=` claims before\nproposing a drop, and it **reports net** — a topic too small to pay for its\nown digest is not proposed at all.\n\n**Rollup shrinks navigation and nothing else.** When INDEX grows into a real\nshare of your window, `rollup` collapses a period into one digest-of-digests\nand moves the originals to `archive/` — still reachable by `recall` and\n`search`, every ledger row intact. The rollup digest is yours, not the tool's:\nsynthesizing a quarter is the part a tool cannot do.\n\n**`index-bloat` is not what a rollup is for.** INDEX lists your episodes, so\nits size grows with the shelf by design; its budget grows with the shelf too\n(`INDEX_BASE_TOKENS + INDEX_TOKENS_PER_ENTRY × listed`). Over budget therefore\nmeans entries are *overpriced*, never that there are too many of them — so\n`doctor` reports the cost of one line, and the fix is to trim it and\n`rebuild`. A rollup would remove entries and their allowance together and\nleave the price where it was. Having the two paired the other way is what made\n\"archive a third of your memory\" the standard way to silence a formatting\nproblem.\n\n**`purge` deletes the working tree, not history.** Retention is opt-in per\nepisode (`--retain-until`); `purge` is a dry run until `--apply` — and even\nthen git history still has the file. Real erasure is a deliberate\n`filter-repo` pass over the whole repository, never a side effect of a tool\ncall, and the purge report says so.\n\n**`resolve` regenerates derived paths, never merges them** — a derived file\nhas no history, only a current correct value. The one file it unions is\n`recall-log.tsv`, because a recall is an event, not a fact about the\nepisodes. Conflicting *episodes* are content, not mechanics: `resolve`\nreports them and steps aside.\n\nThe design rationale behind each rule lives in\n[`docs/DECISIONS.md`](docs/DECISIONS.md) and\n[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).\n\n## One memory, multiple AIs\n\nThe memory is **vendor-portable, and that is a measured fact**, not a design\nintention: the same live shelf has been read and cross-written by Claude Code\n(Anthropic) and Gemini CLI (Google) through one `shelf-spec` server —\nprotocol and field notes in [`docs/portability.md`](docs/portability.md).\n\n## Status\n\nM0 complete: the pattern was validated with zero code on a live shelf —\nretro-import of months of material, then a week of shelve-at-close\n([`docs/M0.md`](docs/M0.md)). M1 shipped the server/CLI that enforces it,\nplus the Claude Code plugin. Next milestones with exit criteria:\n[`docs/ROADMAP.md`](docs/ROADMAP.md); release history:\n[`CHANGELOG.md`](CHANGELOG.md).\n\n## Documents\n\nRendered site: <https://ignatenkofi.github.io/memshelf-mcp/> — including the\nweek-report infographic from the dogfood shelf.\n\n| Doc | What it covers |\n|---|---|\n| [`docs/MANIFEST.md`](docs/MANIFEST.md) | Problem, the bet, hero scenarios, principles, non-goals |\n| [`docs/tools.md`](docs/tools.md) | Tool reference: the long-form description of every `memshelf_*` tool (the MCP schema carries only the short one) |\n| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Episode format, digest contract, storage modes, triggers, MCP tool surface, portability model, privacy, failure modes |\n| [`docs/LANDSCAPE.md`](docs/LANDSCAPE.md) | Prior-art survey (2026-07), platform built-ins, positioning, risks |\n| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Milestones M0–M3 with exit criteria |\n| [`docs/DECISIONS.md`](docs/DECISIONS.md) | Decision log |\n| [`docs/M0.md`](docs/M0.md) | M0 experiment protocol and results: cases, token ledger, recall test |\n| [`docs/demo.md`](docs/demo.md) | Measured numbers from the dogfood shelf: compression, recall test, doctor findings |\n| [`docs/portability.md`](docs/portability.md) | One memory, multiple AIs: the cross-vendor experiment |\n| [`docs/examples/`](docs/examples/) | A worked episode file and a memory-shelf INDEX |\n| [`adapters/claude-code/`](adapters/claude-code/) | Claude Code plugin: `/shelve` skill + SessionStart/SessionEnd/PreCompact hooks |\n| [`adapters/claude-desktop/`](adapters/claude-desktop/) | Claude Desktop `.mcpb` extension: builder, bundle checker, default-shelf setting |\n\n## Origin\n\nDesigned as RFC-0001 in the docshelf-mcp repo\n([#42](https://github.com/ignatenkofi/docshelf-mcp/pull/42),\n[#43](https://github.com/ignatenkofi/docshelf-mcp/pull/43),\n[#44](https://github.com/ignatenkofi/docshelf-mcp/pull/44)); this repo is the\nproject's home from 2026-07-13 on. The docshelf copy is frozen as a\nhistorical snapshot.\n\n## Related projects\n\n- **[docshelf-mcp](https://github.com/ignatenkofi/docshelf-mcp)** — the\n  sibling project and storage layer: PDFs/Markdown → chat-project-friendly\n  document shelves with the same index-and-fetch economics\n  ([measured](https://github.com/ignatenkofi/docshelf-mcp/blob/main/docs/demo.md):\n  ~3.7K tokens vs 1.2M per question). memshelf was born as\n  [RFC-0001](https://github.com/ignatenkofi/docshelf-mcp/tree/main/docs/rfc/0001-memshelf)\n  in its repo and reuses its splitter/indexer/read/search verbatim.\n- The dogfood memory shelf is a private repo — by design\n  ([MANIFEST](docs/MANIFEST.md) principle 5).\n\n## License\n\nMIT — see [`LICENSE`](LICENSE).\n\n---\n\nmcp-name: io.github.ignatenkofi/memshelf-mcp\n",
  "bytes": 15683,
  "sha": "6d702030eee8002df3e22bdcf9278402973750befd14a5f8fbbbda7a9c8797ae",
  "repo_slug": "ignatenkofi/memshelf-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_ignatenkofi_memshelf_mcp_ef07034c/readme"
}