Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Obsidian Multi-Agent Memory Stack

A self-hosted setup that turns a plain Obsidian vault into shared, persistent memory for several AI agents and every device I own, at the same time, without giving any single client exclusive ownership of the files.

I run more than one Claude agent against my own notes: one with a coding-assistant role in a terminal, one that does research and thinking, and one that acts as the vault's own curator. They all need to read and write the same notes without stepping on each other or on me editing the same files from my phone. Most personal-knowledge-base setups solve sync for one user on one app. This solves it for multiple write-capable agents plus multiple human devices, some of which can run the real Obsidian app and some of which can only reach a browser, sharing one canonical copy that nothing treats as read-only.

Architecture

flowchart TD
    subgraph Devices["Human devices"]
        Phone["Native Obsidian app\n(phone, laptop, desktop)"]
        Browser["Any browser,\nno app installed"]
    end

    Phone -->|LiveSync plugin,\nreal-time sync| CouchDB[(CouchDB)]
    CouchDB -->|materializes to a local folder| Bridge["Syncthing\n(workstation)"]
    Bridge -->|mirrors into the canonical copy| Vault["/srv/obsidian-vault\ncanonical flat-file copy"]
    Browser -->|reads/writes directly, no install| SilverBullet["SilverBullet\n(web app / PWA)"]
    SilverBullet --> Vault

    Vault --> MCP["obsidian-mcp\n(headless MCP server)"]
    MCP -->|append-only tool scoping| Agents["AI agents\n(curator + contributors)"]
    Vault --> Snapshot["git snapshot\n(nightly, only on change)"]
Loading

Four things had to be true at once, and each drove a different piece:

  1. AI agents need programmatic access without a GUI. obsidian-mcp is filesystem-native, not a plugin bridge into a running Obsidian process, so it works headless on a server that never opens the app. It exposes the vault as MCP tools.
  2. Some devices run the real Obsidian app; some don't. Phone and desktop clients get real-time sync through the Self-hosted LiveSync plugin talking to CouchDB. Anything without the app, a work laptop, a borrowed machine, gets SilverBullet, a browser-based editor that reads and writes the same flat files directly. Neither path is second-class.
  3. LiveSync's writes need to reach the one copy agents read. CouchDB's replication model materializes files into a local folder on whichever device runs it; it doesn't itself deliver them to the server's flat-file copy that obsidian-mcp, SilverBullet, and the git snapshot all read. A Syncthing instance closes that gap: it watches the LiveSync-materialized folder on a workstation and mirrors it into the server's canonical /srv/obsidian-vault, so that copy is never stale, without ever running the Obsidian desktop app (an Electron app) on the server itself.
  4. Multiple writers need a rule, not just a lock. File-level sync tools resolve concurrent edits with timestamps, not judgment. The actual safety comes from a written governance model above the filesystem layer, not from the sync mechanism, see CONSTITUTION.md.

Repo layout

  • infra/ — every config as a deployable template: the obsidian-mcp systemd unit and its two drop-ins, the Caddy reverse-proxy fix, CouchDB, SilverBullet, a Syncthing compose file, and the git-snapshot timer. Parameterized with .env.example files and placeholder paths, no real hostnames or credentials.
  • docs/SETUP.md — a device-agnostic walkthrough: deploy the server side once, then add any number of devices down either path (native app + LiveSync, or browser-only via SilverBullet), then register any number of agents under the append-only model.
  • CONSTITUTION.md — the governance document that makes shared write access by multiple agents safe: one curator with restructuring authority, append-only contributors, provenance on every write, conflicts flagged rather than silently overwritten.

Key design decisions

Append-only enforcement lives at the protocol layer, not agent discipline. obsidian-mcp's OBSIDIAN_TOOLS environment variable can deny specific tools per instance. Contributor agents run with !note_write,!note_delete,!note_move denied, so they can create, insert, and patch, but cannot overwrite or delete a whole note, whatever their system prompt says. The rule is enforced by what the process can call, not by asking the model nicely.

Two sync mechanisms, deliberately, not one compromise. CouchDB/LiveSync gives native apps real-time, conflict-aware sync built for exactly this use case. Syncthing's only job is bridging that into the server's own copy. Running one general-purpose sync tool for both would have meant picking between LiveSync's plugin-only reach and Syncthing's lack of a real per-note merge model, instead of getting each tool's actual strength.

A privacy layer that doesn't require the desktop app. A personal journal folder needs to stay out of what agents can discover, but the whole design goal was avoiding a GUI dependency. OBSIDIAN_EXCLUDE_PATHS gives an index-level exclusion, confirmed via the server's own vault_info tool reporting a non-zero excluded-note count, rather than a filesystem ACL that would need the app running to define.

Read-only browser access gets the same files, not a converted copy. SilverBullet reads the same Markdown that Obsidian and the agents use. No format migration, no export step, no second source of truth to keep in sync with the first.

Debugging log

Three real failures, kept because the actual operating experience is better evidence than a clean demo.

The reverse proxy 403'd for no visible reason. obsidian-mcp is built on the rmcp Rust SDK, which validates the inbound Host header against a hardcoded allowlist (localhost, 127.0.0.1, ::1) as DNS-rebinding protection, with no flag to extend it. Any proxy that forwards the original external Host header gets rejected. The fix was a small Caddy instance sitting between the public-facing proxy and obsidian-mcp, whose only job is rewriting the Host header back to loopback before forwarding (infra/caddy/Caddyfile).

The service was quietly eating a full CPU core. Load average sat around 3.3 on a 4-core box with nothing obviously wrong in the logs. The cause: the filesystem watcher was indexing its own index directory, a self-triggering re-index loop, compounded by watching churny dotfolders (.git/, .stfolder/) that change constantly without representing real content. The first fix widened the exclude list (cpu-fix.conf); on reflection the watcher wasn't buying anything this deployment needed, since agent access goes through MCP tool calls and the only external writer is already-synced files landing from Syncthing, so a second pass disabled it outright (no-watch.conf). Load average dropped to ~0.06, reclaiming roughly a quarter of the box.

LiveSync failed for hours with a misleading error. "invalid UTF-8 JSON" and "failed to obtain PBKDF2 salt" pointed at CORS, HTTP version negotiation, and the sync passphrase itself, none of which were the problem; all were tested and ruled out first. The actual cause only showed up in the client's own dev console: the plugin's "Database URI" field must be the bare host with no path, and the database name goes only in the separate "Database name" field. Putting the database name in both produced a malformed nested path that CouchDB's own document routing turned into a generic 400 instead of a clear error. Once diagnosed, first sync ran clean end to end.

Honest limitations

One canonical copy, not a distributed database. /srv/obsidian-vault on one server is the single source of truth. Git snapshots give rollback history and Syncthing/LiveSync give live redundancy across devices, but there's no automatic failover if that one server goes down. Acceptable for a personal vault; not the design to reach for at team scale.

Conflict resolution is timestamp-based, not semantic. Syncthing and LiveSync both resolve concurrent edits by last-write-wins plus a .sync-conflict-* file, not a real merge. Fine for a personal vault where simultaneous edits to the same note are rare; would need a different approach for genuinely concurrent multi-writer editing.

Semantic search is deliberately not part of this. Keyword search is sufficient at this vault's size. If that changes, the plan is to run embeddings locally rather than send vault content to a cloud API, but that's not built.

What this deliberately doesn't cover

CONSTITUTION.md's first principle draws a line: judgment goes in shared memory, mechanics stay local to whichever tool executes them. This repo is everything on the memory side of that line, where facts live, who can write them, how they reach every device and agent. It stops there on purpose. The other side, how a single agent turns a fact from this vault into a correct action without its own reasoning errors compounding across multi-step work, is a separate architecture (short version: probabilistic reasoning plans, deterministic scripts execute), and it deserves its own writeup rather than a section bolted onto this one.

Stack

obsidian-mcp (Rust), CouchDB 3.3, the Self-hosted LiveSync Obsidian plugin, Syncthing, SilverBullet, Caddy, systemd, git. Deployed on a self-managed Linux server, Tailscale for private network access, no public ports anywhere in the chain.

About

Self-hosted architecture for one Obsidian vault shared as persistent memory across multiple AI agents and every device, over obsidian-mcp, CouchDB/LiveSync, Syncthing, and SilverBullet, with deployable templates and a device-agnostic setup guide.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages