vault-memory
This MCP server exposes a local, read/write agentic knowledge layer over one or more Obsidian vaults, covering hybrid retrieval, graph navigation, memory/provenance, briefs, and task contracts.
Search & retrieval: semantic, BM25 full-text, hybrid RRF with optional rerank/recency/authority boosts, section-level search, memory recall, and OB1-compatible search/fetch.
Read & inspect notes: read full note + frontmatter, query frontmatter predicates, suggest frontmatter, list recent notes, vault stats, index runs, audit log.
Graph & document navigation: backlinks, forward links, broken links, outlines, document bundles, typed-edge expansion, Louvain clustering, and dossiers.
Write safely with provenance: create/overwrite notes with expected_hash, edit frontmatter, delete notes; MemorySink-guarded observations and supersede chains.
Compiled briefs & contracts: compile/get briefs, describe/instantiate/register YAML task contracts that agents can discover and run.
Embedding model management: list models, start shadow indexes, switch active models, vacuum orphaned embeddings.
Multi-vault & MCP resources: work across configured vaults; deprecated tools remain callable while MCP Resources expose vaults, models, recent, stats, backlinks, memory/brief/contract discovery.
Allows AI agents to query, retrieve, and write to Obsidian notes with provenance tracking, enabling agentic workflows over a local knowledge base.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@vault-memory Pull me a brief for tomorrow's 1:1 with Alice."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
vault-memory
Local-first, source-agnostic-ready agentic knowledge layer over your Obsidian notes, exposed to any MCP-aware agent.
See CHANGELOG.md for release history. Latest: v2.5.0 — additive over v1.x; the 23 v1 tool names + input schemas are preserved byte-identical.
30-second example
Install the CLI from npm, register a vault, and start the MCP server:
npm install -g @owrede/vault-memory
vault-memory add-vault "/path/to/your/obsidian/vault" --name notes
vault-memory servePoint an MCP-aware client at the vault-memory binary. For Claude Desktop, drop this
into ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"vault-memory": {
"type": "stdio",
"command": "vault-memory",
"args": ["serve"]
}
}
}Restart the client. Ask the agent something like: "Pull me a brief for tomorrow's
1:1 with Alice." The agent discovers the bundled meeting-prep task contract via
describe_contract, instantiates it with instantiate_contract, and the result —
attendees, recent shared notes, last decisions, open action items — is written into
your vault's _memory/_briefs/ folder with full provenance. The brief is a regular
note; you can read it, edit it, link to it, or delete it.
That is the full agentic-knowledge-layer loop: discover a contract, instantiate it, get a cited document back. Memory writes are labeled and provenance-tracked; user notes are never modified silently.
Related MCP server: Brainstem
What this is
vault-memory turns one or more Obsidian vaults into a queryable, agent-native knowledge layer running entirely on your machine. It indexes your notes with local embeddings (via Ollama), keeps the index live as you edit, and exposes the result to any MCP-aware agent — Claude Code, Claude Desktop, ChatGPT Custom Connectors, the MCP Inspector, or any other client speaking the Model Context Protocol.
v1.0.0 was a strong retrieval substrate: hybrid search (semantic + BM25 + RRF, optional cross-encoder rerank), live indexing, multi-vault, hash-protected writes. v2.0.0 evolves it into a full agentic knowledge layer — memory namespace with provenance, document-tree assembly (bundles, outlines, dossiers), graph-as-retrieval (typed edges, expand, cluster), a compiled-brief layer with a staleness daemon, and a task-contract DSL that any MCP-aware agent can discover and instantiate.
Obsidian is the v2 source connector; the same MCP tool surface backs any future
adapter (Notion, Logseq, ...) via the SourceConnector / DeliveryAdapter /
ChangeFeed seams introduced in Phase 1. The memory namespace is a non-negotiable
safety invariant: agents never write silently into user notes; every agent-authored
document carries provenance properties and lives in a labeled MemorySink.
Nothing leaves your machine. No cloud sync, no API keys, no telemetry.
Architecture
One SQLite database per vault under ~/.vault-memory/vaults/<name>.db. Layers L0
through L4 sit on top of an Adapter tier that abstracts the source-of-truth. v2.0.0
ships exactly one adapter implementation (obsidian-fs); v3.0.0 adds notion-api
without touching the layers above the seam.
+-----------------------------------------------------------------------------+
| L4 Compiled briefs + Task contracts (Phases 5, 6) |
| compile_brief . get_brief . instantiate_contract . staleness daemon |
+-----------------------------------------------------------------------------+
| L3 Assembly (bundles, outlines, dossiers, authority + staleness) |
| get_document_bundle . get_outline . search_sections . |
| assemble_dossier . authority + staleness signals (Phases 3, 4) |
+-----------------------------------------------------------------------------+
| L2 Memory namespace + provenance (Phase 2) |
| record_observation . recall . supersede . MemoryContract |
+-----------------------------------------------------------------------------+
| L1 Graph as retrieval (Phase 4) |
| typed edges . expand . cluster . backlink walks |
+-----------------------------------------------------------------------------+
| L0 Retrieval substrate (v1, behavior unchanged) |
| hybrid search (semantic + BM25 + RRF + rerank) . chunker |
+-----------------------------------------------------------------------------+
| Adapter tier (Phase 1) |
| SourceConnector . DeliveryAdapter . ChangeFeed . Registry |
+-----------------------------------------------------------------------------+
| Implementations |
| obsidian-fs (v2.0.0) | notion-api (v3, Phase 10) |
+-----------------------------------------------------------------------------+The Adapter tier is the only horizontal seam in the stack. Every layer above it
consumes canonical Document objects resolved through the registry; no upper layer
holds a reference to a concrete adapter module. L0 keeps direct database access
because it is substrate, not a consumer of Documents. See
docs/v2/ARCHITECTURE.md for the full layer model, the
adapter conformance suite, and the read/write data-flow diagrams.
What's new in v2
Phase 2 — Memory namespace + provenance. Three new tools (
record_observation,recall,supersede); labeledMemorySinkwrite guard; default folder sink at_memory/; superseded-doc handling. See docs/v2/MEMORY_CONTRACT.md.Phase 3 — Assembly + authority/staleness. Four new tools (
get_outline,search_sections,get_document_bundle,assemble_dossier); citation packets on every result; recency/authority rescore params onsearch_hybrid; superseded-doc filtering at SQL level. See docs/v2/PHASE-3-SIGN-OFF.md.Phase 4 — Graph-as-retrieval. Two new tools (
expand,cluster); typed edges table (wikilink,mention,frontmatter-ref,hyperlink);search_hybridgains an additiveexpandparam attaching the typed-edge neighborhood to each hit. See docs/v2/PHASE-4-SIGN-OFF.md.Phase 5 — Compiled brief layer + staleness daemon.
compile_brief,get_brief, andlist_briefs; per-briefsource_hashesmap; daemon marks briefs stale when any source's hash drifts. See docs/v2/PHASE-5-SIGN-OFF.md.Phase 6 — Task contract DSL + reference contracts. Declarative YAML contracts under
_contracts/<name>.yaml; three new tools (describe_contract,instantiate_contract,register_contracts_as_tools); three reference contracts (meeting-prep,project-status,code-review-brief). See docs/v2/PHASE-6-SIGN-OFF.md.Phase 7 — Obsidian plugin (default OFF). Variant-C visual contract editor, settings tab, secrets via OS keyring, manual reindex + stats panel, peer-MCP connectors. Adds 6 gated tools when enabled. See docs/v2/plugin/README.md.
Tool surface delta. 23 v1 tools become 32 canonical tools + 5 DEPRECATED entries in
tools/list(the 5 promoted list-style tools remain callable through v2.x with aDEPRECATEDnotice in theirdescription; removal scheduled for v3.0.0). The rawtools/listtherefore returns 37 entries; canonical (non-deprecated) count = 32. Plugin OFF is the baseline; +6 gated tools when enabled.Resources delta. 5 MCP Resources become 10 MCP Resources in v2.0.0 (
vaults,models,recent,stats,backlinksadded alongside the pre-existing memory/brief/contract resources).
Roadmap
Phase 9 — Pre-Phase-10 premise check (hard gate). Before any v3 code is
written, a dedicated phase verifies that the architectural premise for the v3
multi-source line still holds: all Phase 1 CI greps (no chokidar, no
gray-matter, no raw paths, no Claude leak, no obsidian:// literal outside
adapters) return zero hits on main; an adversarial-review sub-agent confirms
ADRs 001-004 remain unviolated by code shipped in Phases 2-8; the stub-adapter
conformance suite is green; capability-descriptor test coverage meets the
plugin-architecture threshold; the maintainer signs off explicitly. Without that
sign-off, no v3 code is written.
v3.0.0 — Notion connector + multi-source proof. Ship the first non-Obsidian source/delivery/change-feed adapter (Notion), promoting the adapter seams from "interfaces with one implementation" to a real plugin architecture. Resolve the 14 open ADRs (005-01x) covering identity stability, link resolution, property equivalence, granularity, write semantics, auth, watch, rate limits, embedding strategy, cross-source memory, caching, sync, Notion sinks, and capability discovery. Tracked requirements: NOT-01 through NOT-07; DMN-01 through DMN-03 (MCP daemon mode, v2.1.x or v3); TPC-01 through TPC-03 (third-party connectors, post-v3). Status: deferred — gated by Phase 9 sign-off.
Beyond v3 (ideas, not commitments). A v3.x postgres-fs storage adapter is
sketched in the roadmap for users whose vaults outgrow local SQLite, with explicit
non-goals: still single-user-runtime, not a managed service, not pgvector
evangelism. A v4.0.0 multi-user direction is anticipated in the roadmap so v2's
opaque DocIds, adapter seams, content-stable ChunkIds, and provenance-on-every-
agent-write read as deliberate choices in service of that path. Neither is
committed work. See .planning/ROADMAP.md for the full
phase plan and the v3/v4 deferred sections.
Install and docs
Guided install (recommended)
Ask your agent to "install vault-memory" (or run /vmem:install). The
installer asks two questions — which retrieval engine, and which vault(s) — then
installs every missing dependency for the chosen path, registers the vault(s),
builds the index, and wires the MCP server. See
docs/v2/CONTEXTFIT-BACKEND.md for the engine
comparison.
Choose a retrieval engine
vault-memory supports two engines, selectable per vault:
Ollama (vector / embeddings) — best semantic search; needs Ollama + an embedding model resident (GPU recommended).
ContextFit (CPU-only) — token-native BM25 + Semantic-IDs; no GPU, no model, ~41 MB deps. Ideal for resource-limited / non-GPU hosts (e.g. a Synology NAS). Requires the
contextfitCLI (pipx install contextfit).
Prerequisites
Node.js 22–25 (
>=22 <26) — runtime for the MCP server (brew install node@22). Node 26+ is not yet supported: the nativebetter-sqlite3dependency has no prebuild for the new ABI and building from source currently fails.One or more Obsidian vaults; an MCP-aware client.
Ollama engine only: Ollama on
localhost:11434(brew install ollama && brew services start ollama) + thebge-m3model (~1.1 GB,ollama pull bge-m3). Optional ONNX reranker (bge-reranker-v2-m3, ~570 MB) viabash scripts/download-reranker.sh.ContextFit engine only: Python 3.10+ and
pipx install contextfit.
Tested on macOS. Linux should work; Windows untested.
Manual install
npm install -g @owrede/vault-memory
# Ollama (default) vault:
vault-memory add-vault "/path/to/your/obsidian/vault"
# OR a CPU-only ContextFit vault (no Ollama/GPU):
vault-memory add-vault "/path/to/your/obsidian/vault" --backend contextfit
vault-memory serveThe add-vault command appends a [[vaults]] block to
~/.vault-memory/config.toml, writes a .mcp.json into the vault root, and runs
the initial index. Idempotent — re-running on a known path fills in whatever is
missing. Flags: --name <slug>, --backend ollama|contextfit, --write
(enable MCP writes; default read-only), --no-index (skip the initial index).
Documentation
Plugin install — docs/v2/plugin/INSTALL.md
Plugin README — docs/v2/plugin/README.md
Migration v1 to v2 — docs/v2/MIGRATION-V1-TO-V2.md
Architecture deep-dive — docs/v2/ARCHITECTURE.md
Memory contract — docs/v2/MEMORY_CONTRACT.md
Agent-agnostic statement — docs/v2/AGENT_AGNOSTIC.md
ADR index — docs/v2/adr/README.md
Changelog — CHANGELOG.md
SemVer-locked tool API per the v1.0.0 declaration. v2.0.0 is additive: the 23 v1
tool names + input schemas are preserved byte-identical, and the 5 list-style
tools promoted to MCP Resources remain callable through v2.x with a DEPRECATED
notice in their tool description (removal scheduled for v3.0.0). See
CHANGELOG.md for full history.
Document locks
Set the Boolean frontmatter field locked: true to protect a reviewed note:
locked: trueThe delivery adapters reject overwrites, updates, frontmatter changes,
deletion and supersede with { "ok": false, "reason": "document_locked" }.
The lock is checked against the currently stored document, so a matching
expected_hash, an omitted frontmatter payload or an incoming locked: false
cannot bypass it. Locked notes remain readable and searchable.
You can create a locked note or set the lock on an editable note. To unlock an
existing note, remove the field or set it to false directly in Obsidian or a
text editor, then read the note again to obtain its current hash. There is no
agent override. Only the Boolean true locks; the string "true", false,
null and an absent field do not.
The lock is an application-level editing rule. Existing read-only, path, MemorySink, provenance and concurrency protections still apply. Rejected operations leave the file, SQLite index, write audit and watcher suppression unchanged. External editors retain normal access to Markdown files.
Optional document editing
Enable the separately versioned edit module in config.toml:
[server]
features = ["document_edit"]This adds edit_document to MCP discovery. Call it with doc_id, the
expected_hash obtained by reading the document, and either
patch: {kind: "replace", old_text: "Old", new_text: "New"} or
patch: {kind: "section", heading_path: ["Project", "Decision"], content: "New decision\n"}.
Replacement text must occur exactly once; duplicate heading paths are refused.
Section edits replace that heading's body, including nested subsections, while
retaining the heading line and surrounding bytes. ATX headings are recognized
outside fenced code blocks; Setext headings are not supported.
Body-only edits preserve existing frontmatter comments and formatting. Same-text
edits still run delivery guards and hash checks but do not write or add an audit
entry. document_locked, hash_mismatch, ambiguous_target and
target_not_found are structured refusals. MemorySink provenance remains required.
Without the feature flag, the standard MCP catalog is unchanged.
Successful edits invalidate stale derived data and refresh chunks, sections,
edges and the configured retrieval backend before returning. If refresh fails,
the edit still returns ok: true with index_refresh: "pending" and a warning;
normal indexing/catchup retries the invalidated index. With no embedding model
registered, refresh builds the lexical index without contacting Ollama.
Compact v2 context
get_document_bundle, assemble_dossier and search_sections accept optional
projection, heading_paths and max_chars. Existing requests, including
projection: "full" without a budget, keep their previous response.
Use projection: "metadata" to omit body snippets. For selected source text:
{"doc_id":"obsidian-fs://work/project.md","projection":"sections","heading_paths":[["Project","Decisions"]],"max_chars":2000}Bundles and dossiers select from the anchor document; section search selects
matching paths from its query candidate sections. Each context.slices entry
contains the complete citation packet, exact body offsets and 1-based body line
range, text, original_chars and truncated. Sections include their nested
subsections. Offsets and line ranges refer to the complete source range even
when the returned text is shortened. Source hashes describe the full document.
One budget covers all body excerpts in the response. Section projection defaults
to 6000 UTF-16 code units and avoids splitting emoji surrogate pairs. Metadata
has a body budget of zero; citation fields and properties are retained separately.
budget_used, budget_limit, truncated and excluded show what was shortened
or omitted. max_chars: 0 excludes nonempty excerpts explicitly. With full
projection, an explicit budget limits the existing link/chunk snippets.
Missing selectors return target_not_found, duplicate source heading paths
return ambiguous_target, and invalid/missing selection arguments return
invalid_projection. For section search, missing means absent from the query's
candidate sections. No files or provenance are changed. No configuration flag
or database migration is required; the original 23 v1 input schemas are frozen
separately from additive v2 schema snapshots.
Categorized statements
Explicit Markdown list items such as - [fact] The rollout starts in June,
- [decision] Use a local model and - [preference] Write asynchronously are
indexed as statements within their source document. Indented continuation lines
belong to the same statement; nested list items stay separate. Code examples,
checkboxes and ordinary prose are ignored. Unknown explicit categories remain
available. This parser performs no inference or provenance promotion.
Pass include_observations: true to the three v2 assembly tools to retrieve
statements with category, full source citation, document hash and body line
range. The default responses stay unchanged. Selected section projections
include only statements within the selected ranges; metadata projection returns
counts and exclusions without statement text. Excerpts share the global body
budget and disclose truncation or exclusion.
SQLite migration 17 adds a derived statement table and a source-hash marker.
Existing sources initially report state: "unindexed"; normal indexing/catchup
fills the marker, including documents without statements. A source change not
yet indexed reports state: "stale" and returns no old statements. Direct writes
publish the note and statement snapshot together; delete/rename removes the old
rows. Repeated identical snapshots preserve statement IDs. Markdown remains
authoritative and its source, evidence and confidence are retained as-is.
record_observation continues writing whole MemorySink documents under its
existing provenance contract.
Domain relation roles
Declare directed roles in a Relations section, for example:
## Relations
- owns [[Atlas#Plan|Roadmap]]
- depends_on [[Budget]]Only complete explicit list declarations are roles. Ordinary wikilinks and
code examples carry no inferred role; unknown underscore-separated role names
are retained. Roles enrich Edge.rel on the existing wikilink edge, preserving
anchors, source body lines, parallel roles and unresolved targets. Markdown is
authoritative. Run a full reindex to backfill existing notes; no migration or
configuration flag is needed.
The v2 expand tool accepts rels: ["owns"] and direction: "outgoing" or
"incoming". Existing forward/backward remain equivalent aliases; omitted
direction stays both. Omitted roles retain all edges; an empty role array
selects none. Filters apply on every hop and combine with edge_types.
via discloses the role, body line, declaring source DocId and canonical hash;
the result packet still cites the reached document and preserves its provenance.
A changed declaring source returns stale_relation and excludes its indexed
role until reindexing. Traversal never invents an inverse role or confidence.
The original v1 graph-tool schemas stay unchanged.
Read-only schema candidates, validation and drift
Enable the separate module explicitly:
[server]
features = ["document_edit", "schema_inspection"]inspect_schema accepts mode: "infer" | "validate" | "diff", doc_ids, an
optional named MemoryContract and strict (default false). Infer returns
candidate-only field types/prevalence, category/role prevalence, sample size
and complete fresh source citations. It neither marks empirical fields required
nor saves or changes a contract. A supplied contract retains all of its explicit
required keys, including keys absent from the sample. The adapter's derived
wikilinks property is excluded from filesystem field profiles/validation;
citation properties retain their original adapter shape.
Validate and diff require an explicit contract. Registered MemoryContracts,
including default-memory-v1, use their existing Zod validators and required-key
lists. Missing required provenance, wrong types and cross-field violations are
always errors. Additional undeclared fields are warnings permissively and errors
with strict: true; passed means no errors. Diagnostics cite the source hash,
field path, and source line for body declarations. Structured headings, lists
and nested sections are inspected through a Markdown projection; body_projections
and line_basis: "rendered_markdown" mark its lines as rendered coordinates.
The source citation/hash remains the original document. Diff reports required missing
in any sampled document, unexpected fields and new observed types.
An unregistered name loads its existing _contracts/memory/<name>.yaml from the
single filesystem vault named by the samples. The existing process-wide named
MemoryContract cache remains in use; multiple vaults cannot select an ambiguous
disk fallback. Author contracts explicitly and version them yourself. Optional
observation_categories: [fact, decision] and relation_roles: [owns, depends_on]
restrict inspection vocabularies. Omitted vocabularies are unrestricted; declared
empty arrays allow no values. Unknown declarations become warnings or strict
errors and appear in category/role drift. These vocabularies apply to inspection;
they do not change MemorySink write validators or promote observations.
Inspections work on readonly/locked sources, mutate no files or SQLite, and
fail closed for unavailable samples. The existing suggest_frontmatter tool and
the default catalog remain compatible. No database migration is required.
License
MIT.
Business validity (F07)
Optional valid_from / valid_to define a half-open business interval, independently of required observed_at: start inclusive, end exclusive, missing or null bounds open. Use quoted ISO timestamps with an explicit timezone and at most millisecond precision. Retrieval defaults to the current time. v2 recall, search_sections, and get_document_bundle accept as_of; recall also requires include_superseded: true to retrieve superseded memories historically. Invalid bounds yield invalid_validity; imported invalid documents stay indexed with a diagnostic and are excluded from search. Migration 18 backfills UTC milliseconds without rewriting Markdown.
Individual categorized statements inherit document bounds. A trailing explicit annotation can override only those bounds: - [fact] January rule <!-- validity: {"valid_from":"2026-01-01T00:00:00Z","valid_to":"2026-02-01T00:00:00Z"} -->. Null resets a bound to open. Malformed annotations are excluded with invalid_validity. Source identity, hash, evidence and confidence remain those of the original document. Direct bundle reads retain the anchor with valid_at even outside its interval; their statement list is filtered at as_of, allowing explicit statement overrides. Search filters indexed bounds before limits; external edits become searchable after indexing.
Knowledge CLI and manuals (F08)
vault-memory search --vault main --query "Budget" --json searches the existing index. vault-memory read --doc-id "obsidian-fs://main/A.md" --projection metadata --json retrieves source identity and hash; --projection full includes up to 6000 characters with truncation metadata. Both support --as-of ISO. JSON stdout contains one complete object; diagnostics use stderr.
Enable server.features=["document_edit"] to use vault-memory edit --doc-id ID --expected-hash HASH --patch-file patch.json --json. The patch is the same validated replace/section payload as edit_document. No implicit stdin input. Exit codes: 0 success, 2 invalid input, 3 missing document/topic, 4 conflict or guarded refusal, 5 unavailable backend. Successful edits refresh the index; refresh failures retain the successful edit with index_refresh: pending.
vault-memory man edit_document --json and MCP template vault-memory://man/{topic} return the same versioned manual, including schema, activation and operation hints. Tool discovery now advertises operation annotations without changing input schemas. VM_CONFIG_DIR=/path/to/isolated-config selects both config.toml and the vaults/ database directory; default remains ~/.vault-memory. No client configuration is written.
Local ONNX embeddings (F09)
Ollama remains the default. Set [server] embedding_provider="onnx" and model_path="/absolute/local/model-directory" to enable CPU-only ONNX embeddings. The directory must contain a schema_version=1 manifest plus locally supplied, SHA256-pinned model.onnx, tokenizer.json and tokenizer_config.json. Nothing downloads automatically. The manifest declares model/revision/dimensions, mean pooling, normalization, max_tokens, pad_token_id, query_prefix, passage_prefix, output name and license. See tests/fixtures/tiny-embedding/manifest.json for the format (the tiny model is a deterministic test graph, not a useful language embedding). Only self-contained float32 hidden-state ONNX exports are supported; batch size is one per inference, input_ids/attention_mask and optional token_type_ids. Inputs truncate deterministically to max_tokens.
For multilingual-e5-small, the author's model card specifies query: and passage: prefixes, masked mean pooling and normalization; supply the chosen export's exact dimensions, revision and asset hashes. Model instructions, ONNX Runtime API. The manifest hash is included in the revision namespace, preventing silent mixing after tokenization changes.
On an existing index, build ONNX using start_shadow_index with the manifest model name (or full namespace); it returns the canonical modelName. Existing vectors and active model remain intact. Verify the shadow, then call switch_active_model explicitly. A direct reindex to a different ONNX identity is refused until this migration is complete. New vaults can index directly. ContextFit keeps its embedding-free path. Source-only CLI reads work without ONNX assets. No multilingual quality or speed claim is made: the default suite uses the original MIT tiny fixture; an optional local-model paraphrase test runs only with VM_ONNX_QUALITY_MODEL_PATH.
Explicit session lifecycle (F10)
Enable session_lifecycle in [server].features to expose the additive start_session and record_checkpoint tools and local CLI commands. Existing tools and default configuration stay compatible. This feature reads existing briefs and records explicitly supplied summaries; it does not capture conversations or change host configuration.
vault-memory session start --vault main --topic project --sink _memory/_briefs --max-chars 3000 --json
vault-memory session checkpoint --input checkpoint.json --jsonstart_session accepts vault, topic, optional brief sink, max_chars and as_of. It validates the brief's source chunk hashes against freshly read documents and the derived index. Missing/changed/unindexed sources make the result stale:true with an empty context body; citations remain available. A current brief's body is clipped to the character budget while retaining source IDs and hashes. No brief is compiled automatically. CLI vault selection is automatic only with one configured vault.
Checkpoint input:
{"session_id":"session-42","event_id":"checkpoint-1","sink":"memory","summary":"Implemented and verified the selected change.","source_doc_ids":["obsidian-fs://main/Project.md"],"observed_at":"2026-10-03T10:00:00Z"}The existing record_observation controller and DeliveryAdapter author the summary inside an existing, provisioned MemorySink. Provenance is source:agent, confidence:inferred, type:summary, with explicit observed_at, source evidence and session/event IDs. Sources must be readable. Sentinel and readonly guards remain active. Commands do not provision sentinels or load embedding assets.
The SHA256 session/event key is scoped to the sink. Repeating an identical event returns the canonical checkpoint with reused:true; changing its payload returns checkpoint_mismatch. Parallel events return checkpoint_in_progress for the contending caller: retry with the same IDs. SQLite migration 19 adds atomic derived reservations. On retry, canonical checkpoint properties reconstruct idempotence after index loss or a crash following an atomic Markdown write. Expired reservations are reclaimed only after the owning process has ended. A previously completed but deleted checkpoint returns checkpoint_missing; it is not recreated implicitly. CLI exit codes are 0 success, 2 invalid input, 4 checkpoint conflict, 5 configuration/backend error.
The optional portable host adapter is examples/hooks/neutral-session.mjs. Run it explicitly with a JSON event file:
{"schema_version":1,"event":"session_start","input":{"vault":"main","topic":"project","sink":"_memory/_briefs","max_chars":3000}}For a checkpoint, use event:"session_checkpoint" and place the checkpoint input above in input. Unknown fields, unsupported versions and transcript fields are rejected. node examples/hooks/neutral-session.mjs event.json invokes the built CLI with a 25-second deadline (VM_HOOK_TIMEOUT_MS:100–25000); VM_CONFIG_DIR can isolate configuration. Timeout emits hook_timeout: retry the same event because the process may have completed an atomic write just before termination. Only this explicitly versioned neutral payload is supported; configure host-specific event translation yourself. No Claude/Codex config is edited and no background loop is installed.
Reviewed conversation imports (F11)
Enable conversation_import in [server].features for the additive preview_conversation_import, commit_conversation_import and promote_conversation tools, and corresponding CLI commands. The initial supported input is neutral-v1: an array of {provider,external_id,messages:[{id,role,text,at?}]}. Roles are user, assistant, system, tool; IDs must be unique, optional times explicit ISO timestamps, attachments unsupported. No timestamps or user authorship are invented. Example:
[{"provider":"neutral","external_id":"chat-42","messages":[{"id":"m1","role":"user","text":"Two pilots"},{"id":"m2","role":"tool","text":"Tool result"}]}]vault-memory import --format neutral --input conversation.json --target obsidian-fs://main/imports/ --json > manifest.json
# Inspect the saved manifest before applying it:
vault-memory import --commit manifest.json --json
vault-memory promote-conversation --input promotion.json --jsonPreview is the default and writes no source or memory document. It captures each existing source's canonical expected hash (null for a new destination) and uses a deterministic SHA256 identity from provider/external ID for safe filenames. External IDs are never filesystem paths. The versioned manifest includes full rendered text, content hash, conversation identity, destination and explicit import time. Commit validates these fields against each other and refuses edited/inconsistent manifests. Existing exports require the captured hash; source changes or deletions after preview produce a conflict. Locks and readonly checks remain active. Identical canonical imports reuse their source without an additional audit write. An updated export retains the original imported_at; message timestamps remain unchanged.
Imported sources carry source:imported, import_source_id, import_hash, imported_at and validated conversation metadata. They must be outside MemorySinks. Commit uses the existing DeliveryAdapter; a batch reports completed per-document results if a later write fails, so inspect the result before retrying. Normal indexing builds searchable derived data; no model inference runs during import.
Promotion is a separate explicit write through record_observation into an existing provisioned sink:
{"doc_id":"obsidian-fs://main/imports/conversation-HASH.md","expected_hash":"CANONICAL_SOURCE_HASH","message_ids":["m1"],"claim":"Two pilots","mode":"quote","sink":"memory","observed_at":"2026-10-03T12:00:00Z"}The canonical source must still match its import metadata and caller hash; missing messages and fabricated quotes are rejected. quote requires exact selected text and records confidence:direct; inference records confidence:inferred. Both write source:agent with concrete DocId#message-id evidence, source hash and original roles. Tool messages remain tool messages. Repeating an explicit promotion creates another observation; promotion is not advertised as idempotent. Source imports and agent-derived claims remain separately inspectable.
Provider export parsers are enabled only against a documented, verified compatibility profile. OpenAI documents data export, but does not provide a stable JSON mapping schema or public versioned original fixture. Therefore --format chatgpt currently returns unsupported_format; branching or attachments are never silently flattened. Convert to neutral-v1 explicitly. The synthetic neutral fixture contains no private exported conversation. No existing v1 schemas, default tool catalog or global host configuration are changed. CLI exit codes:0 success,2 invalid/unsupported input,4 write conflict,5 configuration/backend error.
Available Tools
37 toolsassemble_dossierARead-onlyIdempotent
Resolve a {type, key} pair to an anchor document and walk its backlinks into a structured dossier: { anchor (citation packet), linked_documents (citation packets + relation), property_rollups (linked_count, linked_types, status_distribution) }. Strict properties.type match (D-03). The key matches the candidate's title OR any entry in properties.aliases (D-04). v2.0.0 returns relation:"wikilink" on every linked_documents entry (the v1 wikilinks table is the only edge source); Phase 4 (GRA-04) widens to typed edges. Superseded backlinks are NOT filtered — dossiers show the whole picture (CONTEXT D-04).
Inputs: key (required), type (required), vaults, max_chars, +3 more.
audit_logBRead-onlyIdempotent
Query the write audit trail for a vault. Filterable by note path, operation type, or time. Default limit 50.
Inputs: vault (required), op, limit, since, +2 more.
clusterARead-onlyIdempotent
Community detection over the typed-edge graph via Louvain modularity (Blondel et al. 2008) using graphology + graphology-communities-louvain. Deterministic: same input produces byte-identical cluster_id assignment via DocId-sorted node insertion + seeded RNG (vault-memory-cluster-v1). cluster_id = smallest member DocId per community. Hard-capped at 5000 nodes; pass force: true to override. Either query (composes search_hybrid + expand 1-hop) OR seed_doc_ids (uses provided seeds + induced 1-hop neighborhood); not both — passing both returns {ok:false, reason:'both_seeds_and_query'}. On the query path with multiple vaults configured, the vault field is required so search scope is deterministic; single-vault setups may omit it (returns {ok:false, reason:'vault_required'} otherwise). Returns per-cluster {cluster_id, size, members[], summary: {top_types, top_titles, edge_density}}. No LLM enrichment — summary fields are pure-deterministic computations (LLM enrichment is Phase 5 brief layer's job). _memory opacity inherited from expand() (Plan 04-03).
Inputs: method (required), force, query, vault, +2 more.
compile_briefADestructive
Compile a brief from caller-supplied source documents and write it to the briefs sink. Resolves the LLM via the D-10 capability-first ladder (MCP Sampling → local Ollama → caller prepared_text → structured error). Enforces D-11 wikilink emission per source (appends a ## Sources footer when the LLM omits them) and writes through DeliveryAdapter. On target collision, auto-supersedes the prior brief via the Phase 2 supersede chain (D-12).
Inputs: vault (required), target (required), purpose (required), source_doc_ids (required), +3 more.
delete_noteADestructive
Delete a note. Requires write_enabled=true AND expected_hash (no blind deletes).
Inputs: path (required), vault (required), expected_hash (required), client_id.
describe_contractARead-onlyIdempotent
Return the input JSON Schema + an auto-generated markdown summary for a contract (Q-DESCRIBE). Pure function — does not execute the contract. Summary lists Inputs / Sources / Sinks / Assembly (numbered) / write_back / Output Shape. Omit vault on single-vault setups; on multi-vault setups, pass vault to disambiguate (returns {ok:false, reason:'ambiguous_vault'} otherwise).
Inputs: name (required), vault.
expandARead-onlyIdempotent
Typed-edge BFS retrieval. Returns the typed-edge neighborhood of one or more seed documents as a flat array of citation packets, each carrying via: {seed_doc_id, hop, edge_type, direction} provenance. Hops hard-capped at 2 (v2.0.0). Default direction = 'both'. Filterable by edge_type and by document properties (strict equality, no operators). Memory-sink documents (_memory/...) surface only when they are already linked from a user note in the result set (per ADR-004 memory-namespace opacity rule). Frontmatter-ref edges are extracted heuristically: [[...]] syntax in any property value OR allowlisted property names (assignee, owner, project, related, parent, child, attendees, superseded_by) matched against note_aliases. include_superseded defaults to false (Phase 2 D-03 forward-only supersede). Unknown seed_doc_ids do not throw — they are returned in a warnings: [{seed_doc_id, reason: 'unknown_doc'}] array. Shortest path wins on dedup; ties broken by (seed_doc_id, edge_type, direction).
Inputs: hops (required), seed_doc_ids (required), rels, direction, +3 more.
fetchARead-onlyIdempotent
OB1-compatible fetch adapter. Resolves an opaque id (from search) to {id, title, text, url, metadata}. Backed by read_note.
Inputs: id (required).
find_broken_linksBRead-onlyIdempotent
List all wikilinks in a vault that point to non-existent notes.
Inputs: vault (required).
get_briefARead-onlyIdempotent
Look up a brief by target slug. D-13 decision tree: staleness dominates; age is independent; follow the supersede chain to the terminal brief. Returns null when the caller MUST recompile (stale + !allow_stale OR too_old + !allow_stale).
Inputs: vault (required), target (required), allow_stale, max_age_days.
get_document_bundleARead-onlyIdempotent
Document-tree retrieval. Returns a structured bundle for a single document: { anchor (citation packet + optional status/superseded_by), outline (section tree via buildOutlineTree — same shape as get_outline.root), backlinks (citation packets + property_snippet + relation:"wikilink"), forward_links (same shape; broken links omitted), recent_edits (≤10 most recent audit_log rows mapped to {at, op, client_id, is_memory_sink_write?}) }. Every citation packet is the full 8-field D-01 shape from src/memory/citation-packet.ts. v2.0.0 accepts only depth:1 (one-hop links); the field is zod-pinned to z.literal(1) for forward compatibility. recent_edits is keyed by the anchor's CURRENT note path — pre-rename history is preserved in audit_log but not surfaced here (Phase 4 widens). Unknown doc_id returns { isError: true, error: "doc_not_found", doc_id }.
Inputs: doc_id (required), as_of, depth, vaults, +4 more.
get_outlineARead-onlyIdempotent
Return the navigable section tree for a document. Each OutlineNode carries an anchor (the section's citation token), heading_path (root → leaf), heading_text, level, and chunk_ids (v1 chunk-table IDs in that section). Consume anchor + heading_path as the section-level half of the citation packet. Unknown doc_id returns an error response with {error:'doc_not_found', doc_id}.
Inputs: doc_id (required), vaults.
index_runsBRead-onlyIdempotent
List recent index runs for a vault — what was scanned, when, how long, errors.
Inputs: vault (required), limit.
instantiate_contractA
Execute a registered contract end-to-end. Zod-validates inputs against the contract's inputZodSchema (additionalProperties:false rejects typos). Resolves source/sink overrides per D-A4b default chain (explicit → config → contract literal → error if required); sinks are MemorySink-only per D-A4c (MEM-05 invariant un-bypassable). Runs each assembly step through verbDispatcher with template resolution + named-binding accumulation. write_back routes through DeliveryAdapter.write() (MEM-05 chokepoint). Returns the Q-OUTPUT bundle {steps, write_back} on success OR a structured InstantiateError envelope (12 sealed reasons per ADR-006 §Decision 7). Omit vault on single-vault setups; multi-vault setups require it (returns ambiguous_vault otherwise).
Inputs: name (required), vault, inputs, sink_overrides, +1 more.
list_backlinksARead-onlyIdempotent
Find all notes that link TO a given note. DEPRECATED since v2.0.0 — prefer MCP Resource vault-memory://backlinks/{vault}/{+docId} for agent discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.
Inputs: path (required), vault (required).
list_forward_linksARead-onlyIdempotent
List all wikilinks FROM a given note. Optionally include broken links.
Inputs: path (required), vault (required), include_broken.
list_modelsARead-onlyIdempotent
List all embedding models registered for a vault, with dim, active flag, and how many chunks have been embedded under each. Use before start_shadow_index / switch_active_model. DEPRECATED since v2.0.0 — prefer MCP Resource vault-memory://models/{vault} for agent discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.
Inputs: vault (required).
list_vaultsARead-onlyIdempotent
List configured vaults with their status (note count, last indexed run). DEPRECATED since v2.0.0 — prefer MCP Resource vault-memory://vaults for agent discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.
Inputs: no named parameters.
query_frontmatterBRead-onlyIdempotent
Filter notes by their YAML frontmatter. Supports equality, $in, $exists, $contains predicates. Multiple keys are AND-combined.
Inputs: vault (required), where (required), limit.
read_noteBRead-onlyIdempotent
Read the full content + frontmatter of a note by its vault-relative path.
Inputs: path (required), vault (required).
recallARead-onlyIdempotent
Retrieve memory documents from one or more labeled MemorySinks, filtered by provenance (min_confidence, types, max_age_days) and ranked by recency (observed_at DESC). Returns citation packets (doc_id, source_handle, title, heading_path, mtime, hash, display_url, properties) — the same 8-field shape Phase 3 assembly tools use. Superseded documents are hidden by default.
Inputs: query (required), sink, as_of, limit, +5 more.
recent_notesARead-onlyIdempotent
List recently modified notes (mtime DESC). Use for agent self-orientation: 'what has the user been working on lately?'. No vector search, just SQL. DEPRECATED since v2.0.0 — prefer MCP Resource vault-memory://recent/{vault} for agent discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.
Inputs: limit, since, vault.
record_observationB
Record a new memory observation under the labeled MemorySink for a vault. Required provenance properties (source, confidence, evidence, status, observed_at, type, superseded_by) are auto-filled from arguments; properties is an escape hatch for contract-allowed extras and overrides any sugar default (D-02 — caller-last merge). Writes route through DeliveryAdapter.write() and pass through the centralized provenance validator.
Inputs: type (required), claim (required), vault (required), evidence (required), +3 more.
register_contracts_as_toolsA
Explicit-control escape valve (D-A1) — scans the per-vault contract registry and updates the dynamic MCP tool list (registers new contracts as vm_ tools, unregisters removed ones) regardless of the [contracts.auto_register_tools] config gate. Always callable. Returns a per-vault diff of {registered, unregistered}. Omit vault to apply to every configured vault.
Inputs: vault.
searchARead-onlyIdempotent
OB1-compatible search adapter. Returns a flat list of {id, title, url, snippet} for connector ecosystems (ChatGPT Custom Connectors, Claude.ai, Deep-Research). Backed by hybrid (semantic+BM25+RRF) search. For richer output use search_hybrid.
Inputs: query (required), limit.
search_hybridARead-onlyIdempotent
Hybrid search: combines semantic (embedding) and BM25 (full-text) results via Reciprocal Rank Fusion. Best general-purpose query. Pass expand: {hops: 1} to auto-attach 1–2 hop typed-edge neighbors as expansions[] per hit (preserves ranking; runs after recency/authority rescore).
Inputs: query (required), rrf_k, top_k, expand, +9 more.
search_sectionsARead-onlyIdempotent
Section-level retrieval. Composes the v1 hybrid (semantic + BM25 + RRF) pipeline with a chunk-to-section promotion step: runs hybrid with an inflated top_k = limit × 5, promotes each chunk hit to its enclosing section, dedupes by (note, section anchor), scores each section as the MAX of its constituent chunks, tie-breaks by chunk_id_first ASC, and returns the top limit sections. Each hit carries an 8-field citation packet (D-01) with a non-empty section heading_path PLUS the section anchor, score, contributing chunk_ids, and an optional snippet from the best-scoring chunk. Use when you want WHOLE-SECTION context, not a chunk window.
Inputs: query (required), as_of, limit, vaults, +7 more.
search_semanticCRead-onlyIdempotent
Semantic search via embedding cosine similarity. Searches all vaults by default.
Inputs: query (required), top_k, vaults, exclude_paths.
search_textBRead-onlyIdempotent
Full-text BM25 search via SQLite FTS5. Best for exact-word and phrase matches.
Inputs: query (required), top_k, vaults, exclude_paths.
start_shadow_indexB
Backfill embeddings for a secondary (shadow) model over every chunk in the vault. The active model is untouched — search keeps working during the run. Idempotent (resumable). Run switch_active_model once complete to promote the shadow.
Inputs: model (required), vault (required), batch_size.
suggest_frontmatterARead-onlyIdempotent
Suggest frontmatter fields for a note based on folder-conventions, wikilink-neighborhood, and title/body content-heuristics. Returns {existing, suggestions, conflicts}. Two input modes: (1) existing note via {path}; (2) draft via {content, folder_hint, title}. At least one of path/content required. Suggestions sorted by confidence DESC; conflicts list disagreements between sources.
Inputs: vault (required), path, title, content, +1 more.
supersedeADestructive
Mark an existing memory document as superseded by a replacement document. Forward-only — the replacement doc is NOT touched; back-links are derived by the Phase 4 graph layer at query time. Atomic single OCC update on the OLD doc; sets status="superseded", superseded_by, and superseded_reason.
Inputs: doc_id (required), reason (required), replacement_doc_id (required).
switch_active_modelA
Atomically promote a registered model to active. Fails with ok:false / reason:'incomplete' if any chunk is missing a shadow embedding for the target model.
Inputs: vault (required), model_name (required).
update_frontmatterADestructive
Modify a note's frontmatter only. The body is preserved bytegenau. Merge DSL: scalar=set, {$unset:true}=delete, {$push:x}=array append, {$pull:x}=array remove.
Inputs: path (required), merge (required), vault (required), client_id, +1 more.
vacuum_embeddingsBDestructive
Drop orphaned embedding rows whose chunk_id no longer exists in the chunks table. Safe and idempotent; does not touch live data. Useful after migrations from pre-v0.7.0 schemas where chunk deletion did not always cascade to the derived layer.
Inputs: vault (required).
vault_statsARead-onlyIdempotent
Vault overview for agent self-orientation: note/word counts, top tags, top frontmatter keys, embedding model, last index run. Omit vault to get all configured vaults. DEPRECATED since v2.0.0 — prefer MCP Resource vault-memory://stats/{vault} for agent discovery. The tool remains callable through v2.x; removal scheduled for v3.0.0.
Inputs: vault.
write_noteADestructive
Atomically create or overwrite a note. Requires write_enabled=true. Use expected_hash for safe overwrites (read the note first, pass its hash). Omit expected_hash only when creating a new note.
Inputs: path (required), vault (required), content (required), client_id, +2 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 37 tools
Several search/retrieval tools overlap (search_semantic, search_text, search_hybrid, search, search_sections, fetch, recall) and five deprecated tools parallel MCP resources, causing potential misselection despite detailed descriptions differentiating them.
All tools use lowercase snake_case, but the pattern is not strictly verb_noun: several are single verbs (recall, expand, cluster, fetch, search) or nouns (audit_log, index_runs). Consistent style with minor deviations.
37 tools is too many for the scope, including five deprecated aliases, multiple overlapping search adapters, and several graph/retrieval functions. The surface is heavy and hard to navigate, exceeding the 25+ threshold for 'too many'.
Covers notes CRUD, multiple search modes, memory sinks, briefs, contracts, indexing, audit, and graph retrieval. Missing a body-only update or rename/move operation, but overall the lifecycle is well represented.
Maintenance
Related MCP Connectors
Agent-native notes, tasks, dev-docs, vaults, sync & handoffs. MCP + OpenAPI dual surface.
Token-efficient MCP memory for Markdown vaults. Tiered search, GraphRAG, AI memories.
Private-by-default, local-first memory/context/task orchestrator for MCP apps and agents.
Persistent memory, knowledge graph, task management and more for AI agents via MCP.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server for local-first RAG over Obsidian vaults, enabling AI agents to search and ask questions about notes with grounded citations.MIT
- AlicenseNot gradedqualityDmaintenanceLocal-first knowledge backend for AI agents that connects MCP hosts to an Obsidian-compatible vault with indexed retrieval, token-budgeted memory recall, and secure ingestion.1MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to persist, search, and evolve knowledge through a Markdown vault with a typed knowledge graph and MCP interface.15Apache 2.0
- AlicenseNot gradedqualityBmaintenanceDurable memory with sources, proof, history, and review for MCP-capable agents. It turns a Markdown/Obsidian vault into a local knowledge substrate, enabling agents to store, search, and retrieve governed knowledge.3,718 PyPI9AGPL 3.0