One server for your agent's memory.
Everything Acheron can do: install it, point it at your notes, and let search and the knowledge graph do the compounding.
// one call, reranked, no search loop
{
"query": "how do we handle sync conflicts?",
"ranking": "model",
"fallback": false,
"results": [
{ "title": "Decision — CRDT merge",
"model_rank": 1 },
{ "title": "Sync Engine",
"model_rank": 2 },
{ "title": "2025-11 Sync storm",
"model_rank": 3 }
]
}
Markdown is the database. Everything else is derived.
Acheron is one agent-facing MCP server in front of Basic Memory. Your notes stay plain markdown in ordinary folders; Basic Memory keeps a local index over them; Acheron gives every agent one clean surface with one search verb. Delete the index and it rebuilds from the files — nothing is trapped.
1 One surface, one search verb
The engine exposes many overlapping ways to search. Acheron absorbs them into context_search — query, filters, dates, tags, similarity — and reranks the results with a decision model. Every other tool is proxied under its original name, so nothing breaks.
2 Ranking is best-effort, retrieval never blocks
If the decision model errors or rate-limits, results come back in native order with fallback: true. A ranking outage degrades ordering — it never takes the agent offline. provider: none turns reranking off entirely.
Prefer motion? Two scrubbable explainers walk the same paths: the eleven-step search path (including the model-failure fallback) and the storage layer (file → index → relations graph).
Running in about five minutes.
Two processes: the storage engine (Basic Memory, over HTTP) and Acheron itself (stdio, launched by your MCP client). Reranking is optional; without a key everything still works, in native order.
Prerequisites
Python 3.12+, uv, Basic Memory with at least one project of markdown notes. Nothing else is required. Reranking is optional: turn on the engine's built-in reranker for a no-key improvement, add a free OpenRouter key for Mercury Decide, or point ACHERON_PROVIDER=custom at any endpoint that speaks the System One API, including a local server.
Install and start the engine
uv sync
scripts/start-bm-http.sh # engine on http://127.0.0.1:8765
# or: scripts/install-launchd.sh (starts at login, kept alive, macOS)Give it notes
Point a Basic Memory project at any folder of markdown — an Obsidian vault, a notes directory, a docs tree. Registering the folder is the whole import.
bm project add notes ~/Notes # indexes existing files at add time
bm project index notes # catch-up, if files arrived later; safe to rerun
bm status # confirm: "indexed and settled"Wire it into an agent
Add Acheron as an MCP server. Do not also configure the engine's own MCP entry — this server proxies it, and two search tools is a per-turn decision tax.
# ~/.config/opencode/opencode.jsonc
"acheron": {
"type": "local",
"command": ["uv", "run", "--directory", "/path/to/acheron", "acheron"],
"enabled": true
}
claude mcp add --scope user acheron -- /path/to/acheron/.venv/bin/acheron
{
"mcpServers": {
"acheron": { "command": "/path/to/acheron/.venv/bin/acheron" }
}
}
Ask something
From the agent, one call:
context_search(query="what did we decide about caching?")First run: pass tags, a status, or a date filter without a query and results come back in native order — ranking: "native".
Search that degrades gracefully.
context_search is the only search the agent ever sees. It takes a query, any of the engine's filter parameters, or both — then decides how to answer.
Query → reranked
The engine's hybrid search returns candidates; a decision model (Mercury Decide by default) scores each 0–3 for usefulness and reorders them. It's ordering signal, not recall — the right note is already in the list; the model decides where.
Filters only → native passthrough
tags, note_types, after_date, valid_at, min_similarity and friends ride through untouched. No model call, no delay, deterministic order — perfect for listings and structured lookups.
| Method (16-question demo benchmark) | Top-1 | Top-3 |
|---|---|---|
| Native hybrid (engine default) | 7 | 12 |
| Engine's built-in local reranker (fastembed) | 12 | 14 |
| Mercury Decide (free tier) | 14 | 15 |
| Perplexity Decider v1.1 27B | 14 | 15 |
| Jev 1.13 | 13–14 (two runs) | 15 |
| Kev 0.8B, local via llama.cpp | 8 | 12 |
| Mercury stacked on the built-in reranker | 15 | 16 |
Caveats, stated up front: 16 questions, hand labels, one synthetic corpus — differences of 1–3 are noise. The engine's reranker is strong and free. Acheron's value is the single surface, the fail-open contract, model portability, and a benchmark you can rerun (uv run python eval/benchmark.py --replay eval/demo/candidates.json).
Every answer says how it was ranked: ranking: "model" | "native" and fallback: true when the model was unavailable. Agents can trust the result and the provenance — no silent surprises.
Twenty-five tools, one door.
Everything the engine exposes arrives on one server. Write tools keep their safety annotations (destructiveHint and friends), so client permission prompts keep working. Two overlapping search verbs are folded away; so are the ChatGPT-only connector adapters.
| Group | Tools | Use it for |
|---|---|---|
| Search | context_search | The only search: query, filters, or both |
| Read | read_note read_content view_note cat tail recent_activity | Open notes, files, recent changes — full or sliced |
| Graph | build_context | Walk links and backlinks around a note (1–3 hops) |
| Write | write_note edit_note move_note delete_note | Create, edit in place, move, remove |
| Browse | list_directory ls find grep | Directories, globs, metadata queries, content search |
| Schema | schema_infer schema_validate schema_diff | Learn and enforce note shapes as your corpus matures |
| Projects | create_memory_project list_memory_projects list_workspaces delete_project | Register corpora, route between them |
| System | basic_memory_diagnostics man | Health checks and the built-in manual |
One project per corpus. In place, never a copy.
A corpus is a folder tree registered as a Basic Memory project. Keep personal and work notes in separate projects and search can span them at once — or be routed to one.
What lives where
- The folder is canonical. Obsidian, an editor, a script, an agent — anything that writes markdown is a valid write path.
- The index is disposable. SQLite plus embeddings, rebuilt from the files. Move the folder and you lose nothing.
- Wikilinks become edges.
[[Note]]references are parsed into relations;build_contextwalks them. Tags and frontmatter become filters. - Renames: keep the old name in
aliases:so search still finds the note.
Watcher behavior, verified
After a project's first index pass, new and changed files are picked up automatically — no sync command, no export step. Save a note and your agent can find it in under a minute. Files that arrive before that first pass need one bm project index <name> to catch up; reruns are safe.
bm project add work ~/Work/notes
bm project add home ~/Vault
bm project index workSearch defaults to the project the caller names, else the configured default (ACHERON_DEFAULT_PROJECT). Cross-project queries are supported by the engine — one call can span every indexed corpus.
The memory skill teaches agents to write well.
Search only pays off if the corpus is written to be found: linked, typed, concise, deduplicated. skills/acheron-memory/ packages that discipline as three procedures an agent can run.
S Seed
Jumpstart a folder: inventory with corpus_scan.py, import in place, then enrich in batches — entity notes, links, hubs — every auto-created note flagged needs_review until confirmed.
R Remember
Mid-session capture: a decision with its reason, a durable fact, a lesson. Search before write, link what exists, confirm what was saved. At most five new notes per session.
W Wrap
End-of-session distillation: one session note, hub updates, proposed links. Never a transcript dump. The corpus grows by compounding, not by volume.
≈ corpus_scan.py
A dependency-free audit: file inventory, duplicate titles, broken wikilinks, orphans. Run it before and after any enrichment pass. --strict exits non-zero when links break.
cp -r skills/acheron-memory ~/.claude/skills/ # or your agent's skills directory
python3 skills/acheron-memory/scripts/corpus_scan.py links ~/NotesWorks two ways: through the MCP tools when connected, or directly on markdown files when not. The packaged skills/acheron-memory.zip suits clients that accept skill uploads.
Getting the most out of it.
Six patterns that turn Acheron from "search over notes" into a compounding knowledge system. Each one is small on purpose.
Seed an existing vault
one-time · ~an hour for a large vaultPoint at the folder, let the skill inventory it, and enrich in checkpointed batches. Additive edits only — frontmatter, links, hub notes — so a failed batch leaves a working corpus.
bm project add vault ~/ObsidianVault && bm project index vault
# then ask your agent: "seed the vault project with the acheron-memory skill"
Run the session loop
daily · compoundingMid-session, the agent captures decisions and facts as they land. At the end, it wraps: one session note, hubs updated, review flags proposed. Nothing important lives only in a chat log.
"remember this" → search, link, write, confirm
"wrap up" → session note + hub updates + open proposals
Answer from the graph, not just the page
every queryTwo-step reads beat one-step reads: search finds the page; build_context hands back its neighborhood — the decision it implements, the people it concerns, the incidents it caused. Link generously and the neighborhood gets rich.
context_search(query="sync conflicts")
build_context(url="concepts/sync-engine", depth=2)
Keep corpora separate, search them together
setup onceOne project per coherent corpus — work, personal, a team wiki. Route by naming the project, or let the default handle the common case. Cross-project search returns hits from all of them in one call.
ACHERON_DEFAULT_PROJECT=vault acheron
Go fully offline
a flagNo key, no network: reranking off, native order, everything else identical. Good for air-gapped runs, cost control, and comparing what the model actually buys you.
ACHERON_PROVIDER=none uv run acheron
Schedule consolidation, not capture
monthly-ishCapture is cheap and constant; merging, promotion, and link repair are occasional passes. Run the audit, review the needs_review queue, promote session lessons into concept notes. Ten minutes, and the corpus stays navigable.
python3 maintain_vault.py review # (or your corpus's equivalent)
python3 skills/acheron-memory/scripts/corpus_scan.py links ~/Notes
It runs quietly. Here's how to keep it that way.
No telemetry and no Acheron daemon beyond the storage engine. By default, when Acheron finds an OpenRouter key (the OPENROUTER_API_KEY variable or OpenCode's stored key), each reranked search sends your query and each candidate's title and snippet to OpenRouter, capped at 500 characters for the query, 200 per title and 500 per snippet. With ACHERON_PROVIDER=custom the same text goes to the endpoint you configure instead, and stays on your machine if that endpoint is a local server. Set ACHERON_PROVIDER=none to send nothing. The operational surface is small on purpose.
Service lifecycle (macOS)
The engine runs under launchd: starts at login, kept alive, logs to ~/Library/Logs/basic-memory-http.log. Overridable via BM_BIN, BM_PORT, LABEL.
scripts/install-launchd.sh
launchctl kickstart -k gui/$(id -u)/local.basic-memory-http # restartHealth checks
bm status shows per-stage readiness — files observed, relations extracted, embeddings built, pending counts. If a stage is short, bm project index <name> finishes it; it is incremental and safe to rerun.
Model & cost
Mercury Decide's free tier allows 20 requests/minute — benchmark runners pace themselves (--delay 3.2). provider: none makes no model calls; Perplexity Decider and Jev are paid presets, and custom reaches any other endpoint.
Index hygiene
Ignored folders and removed files are pruned on sync. If a corpus is ever rebuilt, delete the index and let the watcher re-derive it — the markdown is never at risk.
Symptom → cause → fix.
| Symptom | Usually means | Fix |
|---|---|---|
context_search returns no candidates |
Engine down at startup, or the project has no index yet | scripts/start-bm-http.sh; then bm project index <name>; restart the client to re-proxy tools |
| Proxied tools missing, but search works | Acheron started while the engine was down — tools are discovered at startup | Restart Acheron (the client's MCP entry); search keeps working meanwhile |
| New files not searchable | Project never had its first index pass, so the watcher isn't tracking it | bm project index <name> once; thereafter wait under a minute before searching for new files |
| Results look unordered | Model unavailable (check fallback: true), or a key isn't set |
Set OPENROUTER_API_KEY (or ACHERON_DECISIONS_KEY for a custom endpoint), or accept native order deliberately with provider: none |
| Links broke after a rename | Wikilinks point at old titles | Add old titles to aliases:; find the rest with corpus_scan.py links |
| Two search tools in the client | The engine's own MCP entry was configured alongside Acheron | Remove the engine entry — Acheron proxies it by design |
Env vars, commands, glossary.
Configuration
| Variable | Default | Meaning |
|---|---|---|
ACHERON_BM_URL | http://127.0.0.1:8765/mcp | Engine HTTP MCP endpoint |
ACHERON_PROVIDER | mercury | mercury · perplexity · jev · custom · none |
ACHERON_MODEL | provider default | Decision model id; required for custom |
ACHERON_DECISIONS_URL | OpenRouter | Decisions endpoint; required for custom; https unless local |
ACHERON_DECISIONS_KEY | none | Key for a non-OpenRouter endpoint; omit for a local server |
ACHERON_DEFAULT_PROJECT | none | Project scope when the caller passes none |
CLI cheat sheet
bm project add <name> <path> # register a folder (indexes existing files)
bm project index <name> # catch-up / repair; incremental, safe to rerun
bm project list # all projects and paths
bm status # per-stage readiness for the current project
bm reindex --project <name> # rebuild search index and embeddings
bm orphans # notes with no relations — wiring opportunitiesGlossary
| Term | Meaning here |
|---|---|
| MCP | Model Context Protocol — the standard your agent uses to call tools |
| Project | A registered folder of markdown; the unit of indexing and routing |
| Permalink | A stable address for a note (e.g. vault/notes/decision-caching) that survives file moves |
| Forward reference | A [[wikilink]] — parsed into a relation edge at index time |
| Decision model | The System One model that scores search candidates (Mercury by default) |
| Fail-open | Ranking failure degrades order and says so; retrieval still answers |
| Proxied tool | An engine tool re-exposed by Acheron under its original name and schema |