[feature] Define multi-machine vault convergence: a Markdown merge driver, node identity, and an explicit sync operation
Disclosure: I maintain omind, a separate Apache-2.0 project solving an adjacent problem (durable agent memory in a plain-Markdown Obsidian vault). I read claude-obsidian's source before filing and I'm linking omind's implementation as a worked reference — not proposing you adopt its code. Per CONTRIBUTING's evidence rule, every claim below points at a specific file.
Problem
A vault used from two machines has no defined convergence story today.
What exists: claude_obsidian/checkpoint.py commits one completed operation, and the
README scopes git to "development, releases, or an explicit knowledge checkpoint." So git
is present as a history mechanism, not a sync mechanism. There is no node identity, no
merge strategy, and no documented multi-machine workflow.
That leaves the user to git pull by hand, and the failure is not hypothetical:
Markdown conflict markers land inside vault pages. Git's default text merge on two machines that both appended to
wiki/log.md, or both edited different sections of the same page, produces<<<<<<<markers in a file Obsidian then renders as garbage. Every page in this product has a predictable section structure, which is exactly the case a custom merge driver handles well and the default driver handles badly.It collides with the transaction contract.
transaction.pyrecords a precondition SHA-256 for every target. Agit pullthat rewrites a page out from under a prepared bundle turns intoTransactionConflict— correct behavior, but the user is now holding a failed operation and a conflicted worktree with no guidance.Derived state has no rules.
.vault-meta/(chunks, BM25 index, tiling cache) is ignored runtime state. Nothing says whether it should be gitignored on sync, and if it is not, every machine fights overindex.jsonon every commit.
The single-machine design is coherent and I'm not asking you to abandon it. The ask is for the two-machine case to be defined — even if the definition is "don't."
Proposed solution
Concretely, smallest useful version first:
1. A merge driver for vault Markdown. Register via .gitattributes in the vault
template. Section-aware three-way merge: both sides' additions to distinct ## sections
merge cleanly; genuinely overlapping edits to the same lines still conflict, loudly. omind
does this in
src/omind/merge.py
(omind merge-driver) — a three-way hunk merge with a union fallback for append-only
sections, chosen so that concurrent appends on two machines converge to the same bytes
regardless of which side merges first. Symmetric convergence is the property to test for;
it is easy to get wrong and easy to pin.
2. Node identity. A per-machine id in the ignored runtime config so an operation and
its journal can record which machine produced it. omind keeps this in
mesh.py
(NodeConfig / new_node_id).
3. Explicit sync as one more reviewed operation. Not a background daemon — that would
fight the consent model. claude-obsidian.py sync --vault PATH planning fetch → merge →
push, previewing like every other mutating command and refusing to run while a transaction
lock is held.
4. At minimum, documentation. If the above is too much, a docs/multi-machine.md
saying "put the vault in git, gitignore .vault-meta/, expect conflicts in log.md,
recover with transaction recover" would close most of the risk. omind's equivalent is
docs/mesh.md.
Alternatives considered
- Syncthing / Dropbox / iCloud over the vault. What most Obsidian users do, and the worst option here: file-level sync with no merge semantics will interleave writes with an in-flight transaction and corrupt the journal. If the answer is "use a file syncer," the docs should at least say to stop the agent first.
- Obsidian Sync. Same problem, plus it doesn't know about
.vault-meta/. - Declare it single-machine. Legitimate! But say so explicitly in the README, because "your vault is a normal directory of Markdown" reads as an invitation to git it.
Scope
- A new script (
scripts/<name>) — merge driver +sync - Change to plugin manifest / hooks / setup scripts —
.gitattributesintemplates/vault/,bin/setup-sync.sh - Documentation only —
docs/multi-machine.md(viable as a standalone first step)
Compatibility
- Behavior change for existing v1.x vaults? No for the driver and docs (opt-in, and an
unregistered driver simply isn't used). A new
.gitattributesin the vault template affects new vaults only;migratecould add it additively. - New opt-in (
bin/setup-*.sh)? Yes. - New dependency? No — git is already required for checkpoints, and a merge driver is a plain executable git invokes with three paths.
Testing
Hermetic; no network needed, since git merges are local. Build two clones of a tmp_path
fixture vault, commit divergent edits in each, merge, and assert:
- distinct-section edits merge with no markers and no data loss;
- concurrent appends to
log.mdconverge to identical bytes in both merge directions (the symmetry property — this is the test that catches real driver bugs); - an overlapping same-line edit still conflicts rather than silently picking a side;
.vault-meta/never enters a commit;- a merge attempted while the transaction lock is held fails closed.
tests/test_concurrent_write.sh and tests/test_wiki_lock.sh already establish the shell
harness pattern this would follow.
Additional context
- omind's merge driver:
src/omind/merge.py - Its replication layer:
src/omind/mesh.py - Operator docs:
docs/mesh.md,docs/mesh-ops.md
Of the four things I'm filing, this is the one I'd argue matters most — a knowledge base that compounds is one people keep for years, and they will eventually own a second machine.
Source: AgriciDaniel/claude-obsidian