Skip to main content
Glama

obsidian-agent-runtime

CI

Turn a plain markdown vault (an Obsidian vault, or any folder of .md notes) into persistent, searchable memory for Claude Code.

Models are interpreters; the vault is the memory. Context windows end, sessions get compacted, and you may switch models or runtimes. Notes on disk survive all of that, stay human-readable and editable in Obsidian, diff cleanly in git, and can be read by any other agent. This package supplies the small amount of glue that makes Claude Code treat a vault that way:

Piece

What it does

vault_search MCP server

SQLite FTS5 index with BM25 ranking, chunked per ## section; incremental reindex; stdio MCP server exposing vault_search and vault_reindex.

CLAUDE.md adapter

A thin runtime adapter that @-imports the vault's own README.md (the canonical rules) and adds only Claude Code-specific facts.

SessionStart hook

Loads the saved working context at startup, resume, /clear and after compaction.

PostToolUse hook

Re-indexes the affected note after successful Write/Edit tool calls. Other changes (shell, Obsidian, git) are picked up before a later search once the refresh interval has passed (default 60 s).

vault-recall / vault-memory subagents

Memory reads and writes run in subagents, keeping intermediate tool output out of the main conversation.

Idle watcher

Parks an idle long-running session's transcript into the vault, then /clears it. Includes an on_park hook point for your own post-processing.

It is deliberately small: one dependency for MCP, one for YAML frontmatter, no embeddings, no background daemon. The indexer and MCP server make no network requests of their own; when Claude Code reads retrieved notes, that content goes to your configured model provider like any other context.

Quickstart (about 5 minutes)

Requires Python 3.11+ with SQLite FTS5 support (standard CPython builds include it), and Claude Code installed and signed in for step 2. Commands assume a POSIX shell.

git clone https://github.com/tardigraderesearch/obsidian-agent-runtime
cd obsidian-agent-runtime
python3 -m venv .venv && . .venv/bin/activate
pip install .

# 1. Index and search the bundled fictional demo vault from the shell
obsidian-agent-runtime index  --vault demo-vault
obsidian-agent-runtime search --vault demo-vault --top 3 "capacitive probes"

Expected output:

Indexed: 16 files / 56 chunks. Unchanged: 0. Pruned: 0. (0.00s)
DB: ~/.cache/obsidian-agent-runtime/demo-vault-<hash>.db
- [[REFERENCE/decisions-log.md]] > 2026-02-20: capacitive soil probes (semantic, bm25=-5.42)
  Chose **capacitive** over resistive **probes** for [[PROJECTS/greenhouse-controller]]. Reason: resistive **probes** corrode within weeks in…
- [[REFERENCE/soil-moisture-sensors.md]] > Capacitive (semantic, bm25=-4.18)
  …Needs per-**probe** calibration (dry and wet reference readings) because boards vary.
- [[PROJECTS/greenhouse-controller.md]] > Decisions (semantic, bm25=-3.89)
  - **Capacitive** **probes**, not resistive. See [[REFERENCE/decisions-log]].

The 17th file, STATE/transcripts/..., is excluded on purpose (see below).

# 2. Wire a copy of the demo vault into Claude Code
cp -r demo-vault ~/oar-demo
obsidian-agent-runtime init ~/oar-demo
cd ~/oar-demo && claude

Approve the vault MCP server when prompted, then try:

  • "What am I working on?" (answered from the injected working context)

  • "Why did we choose capacitive probes?" (the agent spawns vault-recall, which calls vault_search)

  • "Remember that Priya is returning the oscilloscope on April 20." (the agent spawns vault-memory, which updates PEOPLE/priya.md or REFERENCE/workshop-tools.md; the PostToolUse hook makes the edit searchable immediately)

oar is a short alias for obsidian-agent-runtime.

Related MCP server: markdown-vault-mcp

Architecture

               Claude Code session (cwd = vault)
  ┌──────────────────────────────────────────────────────────────┐
  │ CLAUDE.md ──@import──> README.md (vault's own rules)         │
  │                                                              │
  │ SessionStart hook ──> injects STATE/working-context.md       │
  │                                                              │
  │ main agent ──spawns──> vault-recall  (read-only)  ─┐         │
  │            ──spawns──> vault-memory  (read/write) ─┤         │
  │                                                    │ MCP     │
  │ PostToolUse(Write|Edit) ──> reindex one file       │ stdio   │
  └─────────────────────────────────┬──────────────────┼─────────┘
                                    │                  v
                                    │      obsidian-agent-runtime serve
                                    │        vault_search / vault_reindex
                                    v                  │
        ~/.cache/obsidian-agent-runtime/<vault>.db  <──┘  (SQLite FTS5, BM25)
                       ^
                       │ incremental index (mtime+size, then sha1)
              markdown vault on disk  <── Obsidian, git, other agents
                       ^
                       │ STATE/transcripts/*.md
        idle_watcher.py (systemd timer) ──> on_park hook ──> /clear via tmux

Index and search details

  • Chunking: one chunk per ## section (preamble keeps the # title); sections over 8 KB are sub-split on ###, then on paragraphs. Hits point at the section, not the file.

  • Columns and weights: title (5), heading (3), links (wikilink targets, 2), tags (frontmatter tags, 2), body (1). Porter stemming plus unicode61 tokenizer.

  • Query handling: plain words are quoted and ANDed; if nothing matches, the search falls back to OR and reports "match": "any". "phrases", prefix*, AND/OR/NOT/NEAR pass through.

  • Note types: a type: frontmatter key wins; otherwise SKILLS/... is procedural, anything in a journal/ folder is episodic, the rest semantic. Filter with note_type (MCP) or --type (CLI).

  • Incremental: files whose mtime and size are unchanged are skipped without being read; a touched-but-identical file is detected by sha1 and not re-chunked; deleted files are pruned. The server also runs an incremental pass before a search if the last one is more than OAR_REFRESH_SECONDS (default 60) old, so edits made in Obsidian show up.

  • Skipped by default: .obsidian/, .trash/, .git/, .claude/, node_modules/, any folder named credentials/, dotfiles, sync-conflict copies, and STATE/transcripts/ (raw session dumps are staging data, not memory). Change the prefix list with --exclude / OAR_EXCLUDE.

  • Index location: outside the vault ($XDG_CACHE_HOME or ~/.cache), one DB per vault path, so Obsidian sync and git never see a binary database.

Wiring into Claude Code by hand

obsidian-agent-runtime init <vault> writes all of this for you (it skips any file that already exists, with no merging, and --force overwrites those files entirely, so back up an existing .mcp.json, .claude/settings.json or CLAUDE.md first). To do it manually:

.mcp.json in the vault root:

{
  "mcpServers": {
    "vault": {
      "type": "stdio",
      "command": "/path/to/venv/bin/python",
      "args": ["-m", "obsidian_agent_runtime", "serve"],
      "env": { "OAR_VAULT": "/path/to/vault" }
    }
  }
}

.claude/settings.json:

{
  "permissions": { "allow": ["mcp__vault__vault_search", "mcp__vault__vault_reindex"] },
  "enabledMcpjsonServers": ["vault"],
  "autoMemoryEnabled": false,
  "hooks": {
    "SessionStart": [
      { "hooks": [{ "type": "command",
        "command": "\"/path/to/venv/bin/python\" \"$CLAUDE_PROJECT_DIR/.claude/hooks/session_context.py\"" }] }
    ],
    "PostToolUse": [
      { "matcher": "Write|Edit|MultiEdit",
        "hooks": [{ "type": "command",
          "command": "\"/path/to/venv/bin/python\" \"$CLAUDE_PROJECT_DIR/.claude/hooks/reindex.py\"" }] }
    ]
  }
}

Then copy templates/hooks/*.py to .claude/hooks/, templates/agents/*.md to .claude/agents/, and adapt templates/CLAUDE.md into the vault root. The adapter assumes the vault has a README.md describing its layout and a filing rule; see demo-vault/README.md for a minimal example.

Configuration

Variable

Used by

Default

OAR_VAULT (or OBSIDIAN_VAULT_PATH)

CLI, server, hooks

current directory (hooks: $CLAUDE_PROJECT_DIR)

OAR_DB

CLI, server, reindex hook

~/.cache/obsidian-agent-runtime/<vault-name>-<hash>.db

OAR_EXCLUDE

indexer

STATE/transcripts (comma-separated prefixes, added to the default)

OAR_REFRESH_SECONDS

server

60 (-1 disables auto-refresh)

OAR_CONTEXT_FILES

SessionStart hook

STATE/working-context.md (comma-separated)

OAR_CONTEXT_MAX

SessionStart hook

4000 characters per file

Idle watcher (optional)

For a long-lived session (for example one kept running in tmux and reached remotely), watcher/idle_watcher.py runs every 10 minutes from a systemd user timer:

  1. finds the running Claude Code session whose working directory is the vault;

  2. once its last message is --idle-min minutes old (default 180), writes the new part of the conversation to STATE/transcripts/YYYY-MM-DD-HHMM-<slug>-<id>.md (user turns and the assistant's visible replies; tool chatter dropped);

  3. calls on_park(transcript_path), a documented no-op hook point. Pass --on-park "CMD" or set OAR_ON_PARK to run CMD <path> (a summarizer that distills the transcript into notes, a notifier, a backup job, ...);

  4. sends /clear to the tmux target, so the next conversation starts clean and the SessionStart hook re-injects the working context.

Run these from the cloned repository root (the quickstart left you in the demo vault). The watcher needs Linux, tmux and a systemd user manager.

cd /path/to/obsidian-agent-runtime
mkdir -p ~/.config/systemd/user
watcher/start-session.sh ~/my-vault assistant          # tmux session "assistant" running claude
python3 watcher/idle_watcher.py --vault ~/my-vault --tmux-target assistant --dry-run --force
cp watcher/systemd/oar-idle-watcher.* ~/.config/systemd/user/   # edit paths first
systemctl --user daemon-reload && systemctl --user enable --now oar-idle-watcher.timer

It is stdlib-only and tracks previously parked content in a local state file to avoid duplicate parking.

Limitations

  • Lexical search only. BM25 does not know that "watering" and "irrigation" are related. The recall subagent compensates by trying several phrasings, which works well for a personal vault but is not semantic retrieval. Embeddings are a possible future option.

  • No write tools in the MCP server by design. Writes go through Claude Code's own Write/Edit tools (visible, permissioned, diffable). Filing quality depends on the vault's README rules and on the model following them.

  • Single writer assumed. Nothing locks notes against concurrent edits by two agents.

  • The idle watcher depends on Claude Code's on-disk session layout (~/.claude/sessions/*.json, ~/.claude/projects/<dir>/<id>.jsonl), which is not a public API and may change between releases. /clear delivery needs tmux. The --match option reads /proc (Linux).

  • Secrets. Folders named credentials/ are skipped, but anything else in the vault is indexed and readable by the agent. Keep secrets out of notes.

  • CI tests Python 3.11 and 3.12 on Linux; also run locally on 3.14. Not yet tested inside an interactive Claude Code session beyond the hooks and MCP server. macOS should work; Windows is untested.

Development

cd /path/to/obsidian-agent-runtime
.venv/bin/python -m pip install -e '.[test]'
.venv/bin/python -m pytest -q

License

MIT. See LICENSE.

How this was built

AI is used here as a tool to speed up implementation. I design the system and define what "working" means. When issues are found, they're identified, and ARI — my own agent system — fixes them against those checks.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    Local-first Markdown vault retrieval for agents. Read-only MCP stdio server exposing search, get, status, and doctor over Obsidian-compatible Markdown with hybrid BM25/vector/wikilink/title retrieval and first-class CJK support.
    11
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    A generic Markdown vault MCP server with FTS5 full-text search, semantic vector search, frontmatter-aware indexing, incremental reindexing, and non-markdown attachment support that exposes search, read, write, and edit tools.
    44
    230 PyPI
    35
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Local MCP server for querying and maintaining a Markdown vault. Provides full-text search, backlinks, note retrieval, and optional confined write tools, without sending the whole vault to the client context.
    22
    211 PyPI
    1
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Enables semantic and keyword search over Obsidian-style Markdown knowledge bases by registering any folder as a vault, with support for incremental indexing, raw source reads, and cache-backed embeddings over MCP stdio.
    12
    5
    Apache 2.0