Erudition
Portable, typed, git-friendly memory for AI agents.
Claude Code's built-in memory is local-only and opaque. It lives inside Claude Code itself, isn't portable across machines, and isn't something you can casually read, diff, or edit by hand. Erudition is an MCP server that gives an agent read/write access to a memory store that's none of those things: a plain folder of markdown files, organized around a real taxonomy instead of a junk drawer, editable in whatever notes app you already use, and portable across every machine you own via git.
This README covers what Erudition does and how to install it. For the reasoning behind why it's built this way, and just as importantly, why it isn't built several other, more obvious ways, see WHY.md.
What it is
- An MCP server. Works with Claude Code, Claude Desktop, or any other MCP-compatible client, exposing nine tools (see Tools below).
- A markdown vault, not a database. Every memory is a
.mdfile with YAML frontmatter, in a folder you fully control. No proprietary format, no service to run, no account to create. Obsidian is a good viewer/editor for it, but Erudition doesn't require Obsidian. Any plain-text editor works. - A taxonomy, not a junk drawer. Memories are typed (
user/feedback/project/referenceby default), so an agent (and you) can tell "a fact about how I like to work" apart from "a fact about this specific project" at a glance.
What it isn't
- Not a hosted product. It runs locally, wherever your agent runs.
- Not a "chat with your notes" RAG plugin. Erudition is about agent memory (what an agent should remember across sessions), not general document search.
- Not a replacement for Claude Code (or Claude Code's own memory). It's a layer on top, and it's designed to stay small rather than grow into a competing agent runtime.
Quickstart
1. Install
git clone https://github.com/Ralf090102/Erudition.git
cd Erudition
pip install -e .
This installs the erudition command (the PyPI distribution is named erudition-mcp; the import path and CLI command stay erudition).
2. Point it at a vault
Erudition needs to know which folder is your vault:
export ERUDITION_VAULT_PATH="/path/to/my-notes"
That's a complete, valid setup on its own. If you want the auto-commit toggle or a custom taxonomy, write an erudition.toml instead (or alongside). Copy erudition.example.toml and see the Configuration table below for every option.
3. Connect it to your MCP client
For Claude Code, add it to your MCP server config:
{
"mcpServers": {
"erudition": {
"command": "erudition",
"env": {
"ERUDITION_VAULT_PATH": "/path/to/my-notes"
}
}
}
}
Restart your client. You should see all nine tools below available.
Erudition keeps a small search index at {vault}/.erudition/. It's a rebuildable cache, not real content, so add .erudition/ to the vault's own .gitignore.
Tools
| Tool | What it does |
|---|---|
read_note(path) |
Read a note's contents. |
list_notes(folder="") |
List markdown notes in a folder, recursively (empty string lists the whole vault). |
search_notes(query, folder="") |
Search note contents, backed by a rebuildable SQLite FTS5 index rather than a linear scan. This makes matching token-based, not raw substring: wid* matches "widgets," a bare idget doesn't the way a plain substring scan would. Supports FTS5 phrase ("exact phrase") and boolean (AND / OR / NOT) syntax. Falls back automatically to a plain linear scan if the local sqlite3 build lacks FTS5. Returns matching file paths and the lines that matched; if a query is still slow despite the index, the result includes a one-line note suggesting the staleness check itself might be worth revisiting. |
write_note(path, content, overwrite=False, dry_run=False) |
Write a note; refuses to clobber an existing one unless overwrite=True. dry_run=True writes nothing and returns a preview (new content, or a diff against what's there) instead. |
append_to_note(path, content, dry_run=False) |
Append to a note, creating it (and any parent folders) if it doesn't exist yet. |
list_memory_types() |
List the vault's configured taxonomy: valid type values for save_memory, and what each is for. |
save_memory(type, name, description, content, overwrite=False, dry_run=False) |
Save a typed memory (validated against the taxonomy), with standard frontmatter, indexed in MEMORY.md. |
sync_memory() |
Mirror the vault's memory folder out to a configured local memory directory. |
rebuild_search_index() |
Force a full rebuild of the search index from scratch. Rarely needed, since search_notes already self-heals from a cheap per-search staleness check; mostly a manual escape hatch for recovery or peace of mind after a large batch of external edits. |
The taxonomy
Erudition's default memory types, and what each is for:
| Type | What goes here |
|---|---|
user |
Who the person is: their role, goals, expertise. Lets an agent tailor how it explains things. |
feedback |
Corrections and confirmed approaches, like "don't do X, we tried it and it broke Y" or "yes, that approach was right." Stops an agent from repeating a mistake or re-litigating a settled call. |
project |
Ongoing context about why (decisions, deadlines, stakeholders) that isn't visible in the code or git history alone. |
reference |
Pointers to where information actually lives, e.g. "bugs are tracked in Linear project X," not a copy of the bugs themselves. |
This is a starting point, not a fixed schema. Define your own types under [taxonomy.*] in erudition.toml if your vault needs a different shape. See erudition.example.toml.
Saving and syncing memories
Beyond generic write_note, Erudition understands the taxonomy. save_memory validates the type you pass against the vault's configured types, writes the memory to Memory/{type}_{slug}.md with standard frontmatter, and keeps Memory/MEMORY.md as a lightweight index, one line per memory:
save_memory(
type="feedback",
name="Prefers terse commit messages",
description="User wants one-line commit messages, no bullet lists.",
content="Confirmed after three separate corrections. Applies to this repo only.",
)
If your agent also keeps its own local memory folder (Claude Code does), sync_memory() mirrors everything under the vault's memory folder out to it, so writing a memory once in the vault doesn't also require a manual second write elsewhere. Configure the destination via local_memory_dir (see Configuration below). sync_memory() errors clearly if it isn't set, rather than silently doing nothing.
Write safety: a permanent design choice, not a limitation
Erudition assumes one active writer at a time. Flat markdown files have no locking, so if a human and an agent (or two agent sessions) write to the same note within moments of each other, the later write silently wins. No merge, no conflict marker, no error.
This is deliberate, not an oversight. Closing that gap completely would mean row-level locking, which means becoming a database. That trades away the one thing that makes a markdown vault worth using over a SQLite-backed alternative: you can open, read, and edit every memory in any plain-text editor, no tooling required. Instead, Erudition offers durability without locking: an optional auto_commit setting that git-commits every write with a distinct, identifiable message (erudition: write <path>). It doesn't prevent a collision, but it means a collision is always recoverable via git log / git diff instead of silently gone.
If you run more than one agent session against the same vault at the same time, that's exactly the scenario this boundary is about. Treat "one writer at a time" as a real constraint, the same way you would with any file two processes might edit concurrently.
Separately from durability, write_note, append_to_note, and save_memory all accept dry_run=True: nothing gets written, and you get back a preview instead, either the full content for a brand-new note, or a unified diff against what's already there for an overwrite. Useful for checking what a change would actually do before committing to it, especially with overwrite=True.
Optional: Claude Code hook integration
This is Claude-Code-specific and entirely optional — it's a companion to the core MCP server above, not a change to it, and it doesn't do anything with other MCP clients. If you don't use Claude Code, skip this section.
Two problems this solves: remembering to read a "where we left off" note at the start of every session, and remembering to call sync_memory() after making changes. Claude Code has its own hook system (separate from MCP) for exactly this: SessionStart can inject context automatically when a session begins, and Stop runs after every turn.
Add this to your project's .claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "erudition hook session-start"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "erudition hook stop"
}
]
}
]
}
}
erudition hook session-start reads the note at session_start_note (defaults to Memory/CURRENT.md; see Configuration) and injects its contents as session context. erudition hook stop calls sync_memory(). Both are best-effort: a missing note or an unconfigured sync_memory is a silent no-op, never an error that could block a session or a turn.
Configuration
| Setting | Env var | erudition.toml key |
Default |
|---|---|---|---|
| Vault path | ERUDITION_VAULT_PATH |
vault_path |
(required, one or the other) |
| Auto-commit on write | ERUDITION_AUTO_COMMIT |
auto_commit |
false |
| Memory taxonomy | — | [taxonomy.*] |
the four types above |
| Memory folder | ERUDITION_MEMORY_FOLDER |
memory_folder |
Memory |
Local memory mirror (for sync_memory) |
ERUDITION_LOCAL_MEMORY_DIR |
local_memory_dir |
unset |
| Session-start note (for the optional hook) | ERUDITION_SESSION_START_NOTE |
session_start_note |
Memory/CURRENT.md |
| Config file location | ERUDITION_CONFIG |
— | ./erudition.toml if present |
An env var always wins over the config file for the setting it covers. Full example with every option: erudition.example.toml.
Development
pip install -e ".[dev]"
pytest
Status
Early stage. Config-driven vault path, generalized taxonomy, the write-safety toggle (including dry-run previews), the taxonomy-aware memory tools (save_memory, sync_memory, list_memory_types, append_to_note), a rebuildable FTS5 search index, and a real, repo-tracked test suite are all done. Not yet on PyPI. Expect breaking changes before a 1.0 release.
License
MIT. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file erudition_mcp-0.1.0.tar.gz.
File metadata
- Download URL: erudition_mcp-0.1.0.tar.gz
- Upload date:
- Size: 31.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
081bdb02d9cd0cc70c1b307a2fed6701f5bb4303ec1af2c8d5da5f74206b7bb2
|
|
| MD5 |
84503ef9f1798d15fc29204586f63a10
|
|
| BLAKE2b-256 |
fd10e6b5807fb76849b2a3ea3f751d0c281e6f86c17d0708c90084305779b3d4
|
File details
Details for the file erudition_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: erudition_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c270847464a4a529c4a3403ba0f80aa47e24bf63609dfd45651788ec069e4be
|
|
| MD5 |
f3c05e869cb64071c7718d3693eac68f
|
|
| BLAKE2b-256 |
2adcccc41849ea8a7dc211242e47e637f6398fd6f4e48376563515fda97ada4d
|