AI-agents en mijn Obsidian vault: van WebDAV naar een headless LiveSync daemon
In mijn eerste artikel beschreef ik hoe ik Obsidian LiveSync heb opgezet met self-hosted CouchDB. In het tweede artikel legde ik uit hoe ik een AI-agent via een CLI direct aan die CouchDB koppelde. Dit derde artikel is het vervolg én de correctie: waarom die directe CLI-aanpak op termijn niet volstond, welk onderzoek ik heb gedaan, waarom ik ben overgestapt op een lokale vault-kopie met een headless LiveSync daemon, en hoe dat in de praktijk bevalt.
Het probleem: AI-agents en een LiveSync vault
Een AI-agent zoals OpenCode of Claude Code is een headless proces: geen Obsidian-app, geen GUI, geen plugin-draaier. Toch wil ik dat die agent notities in mijn persoonlijke kennisbank kan lezen, schrijven en bewerken — en dat die wijzigingen netjes via LiveSync naar al mijn apparaten synchroniseren.
De kernvraag is steeds dezelfde geweest: via welke poort krijgt een headless proces toegang tot de vault?
Ik heb drie fundamenteel verschillende antwoorden geprobeerd:
- Via het bestandssysteem (WebDAV) — leidt tot race conditions met LiveSync
- Rechtstreeks via CouchDB (obsidian-vault-cli) — werkt, maar blijft een vreemde eend in de bijt
- Via een lokale kopie + LiveSync daemon (livesync-cli) — de oplossing die nu draait
Fase 1: WebDAV — en waarom dat faalde
De eerste poging was de meest voor de hand liggende: de vault is uiteindelijk gewoon een map met markdown-bestanden op de Nextcloud-server. Waarom die map niet gewoon via WebDAV benaderen?
Mijn agent had een skill die via curl-commando’s bestanden schreef naar de Nextcloud WebDAV share. Dat werkte aanvankelijk, maar het brak op een fundamenteel punt:
De race condition. LiveSync synchroniseert ook naar diezelfde bestanden op schijf. Agent schrijft
notitie.mdvia WebDAV → LiveSync ziet het nieuwe bestand → probeert het naar CouchDB te uploaden → tegelijk probeert LiveSync replicatie van andere apparaten binnen te halen → conflict, soms dubbele of corrupte bestanden.
De les uit fase 1: schrijf niet langs LiveSync heen op het bestandssysteem. Als LiveSync de eigenaar is van die map, moet alles wat ernaartoe schrijft door LiveSync zelf gaan.
Fase 2: rechtstreeks naar CouchDB (obsidian-vault-cli)
De logische volgende stap: helemaal niet via het bestandssysteem, maar rechtstreeks in de database, precies zoals de LiveSync plugin dat doet. Ik vond obsidian-vault-cli, een headless CLI die dezelfde livesync-commonlib bibliotheek gebruikt als de officiële plugin. Hij verbindt direct met CouchDB via PouchDB en handelt alle chunking, hashing en encryptie transparant af.
Dit was een grote vooruitgang en daar ging mijn vorige artikel ook over. De CLI werkte: lezen, schrijven, patchen, grep — alles direct in de database, zonder race conditions. Maar hoe langer ik ermee werkte, hoe duidelijker de beperkingen werden:
Beperking 1: tekst-only
De CLI behandelt de vault als een set tekstdocumenten. Media — afbeeldingen, PDF’s, audio — kon ik er niet fatsoenlijk mee beheren. Een vault die vol staat met ![[images/….webp]]-links heeft daar wel degelijk behoefte aan.
Beperking 2: geen lokale bestanden
Er is geen echte bestandsstructuur op de Mac. De agent kan niet gewoon read, write, glob en grep gebruiken op een normale map — de tools die hij op élk ander project gebruikt. Alles moest via de CLI. Dat is omslachtig en niet hoe de agent het liefst werkt.
Beperking 3: encryptie-bug
Zoals beschreven in het vorige artikel: de CLI ging standaard uit van encryptie aan, terwijl mijn vault encrypt: false heeft. Dat veroorzaakte File seems to be corrupted!-fouten tot ik de boel patchte. Het werkte daarna, maar het toonde dat dit een onderhouden, niet-officiële tool is.
Beperking 4: geen daemon-gedrag
De CLI is pull-based: de agent roept hem aan, de CLI doet een eenmalige operatie. Er is geen continue watcher die veranderingen van andere apparaten binnenhaalt terwijl de agent met de vault bezig is. En — cruciaal voor de praktijk — de CLI lockt de lokale PouchDB-database zolang hij draait, waardoor niets anders er tegelijkertijd mee bezig kan zijn.
Het onderzoek: welke opties zijn er eigenlijk?
Voordat ik koos heb ik systematisch de opties onderzocht. Samengevat:
| Optie | Werkt | Beperking |
|---|---|---|
| WebDAV (Nextcloud) | Nee | Race conditions met LiveSync |
| obsidian-vault-cli | Ja | Tekst-only, geen lokale map, lockt DB, niet-officiële tool |
| Lokale Obsidian GUI + plugin | Ja | Vereist een draaiende GUI-sessie — juist wat ontbreekt op een headless Mac |
| Headless LiveSync CLI | Ja | Moest nog onderzocht worden — bleek de winnaar |
De sleutelontdekking was dat de LiveSync-auteur zelf een CLI heeft: self-hosted-livesync-cli in de officiële obsidian-livesync repository. Dezelfde codebase als de plugin, dezelfde auteur, dezelfde logica — maar dan headless. En die CLI blijkt veel meer te kunnen dan alleen tekst: hij onderhoudt een volledige lokale PouchDB-database plus een gespiegelde bestandsmap, en kan als daemon draaien die via een chokidar-watcher continu het bestandssysteem bewaakt.
De keuze: lokale kopie + daemon
Mijn keuze werd daarmee duidelijk. In plaats van een CLI die rechtstreeks met CouchDB praat, draait er nu een lokale kopie van de hele vault op de Mac, die bidirectioneel gesynchroniseerd wordt door een headless LiveSync daemon:
CouchDB (Nextcloud server)
↕ LiveSync-replicatie
livesync-cli daemon (PouchDB lokaal)
↕ chokidar-watcher
~/Vault/KB ← gewone map met markdown + media
De AI-agent ziet niets van al deze infra: voor hem is ~/Vault/KB gewoon een map vol bestanden waar hij zijn normale tools op loslaat — read, write, edit, glob, grep. De daemon pusht elke wijziging naar CouchDB en trekt veranderingen van andere apparaten naar binnen. Precies de workflows die een agent gewend is, zonder enige speciale CLI.
Waarom dit de juiste keuze is
- Normale tooling: geen aparte CLI, geen CouchDB-queries. De agent gebruikt dezelfde tools als op elk ander project.
- Media als gewone bestanden: afbeeldingen, PDF’s en audio liggen in
Images/en worden via![[naam.ext]]-links gerefereerd — precies zoals Obsidian het zelf doet. - Officiële codebase: zelfde auteur en bibliotheek als de plugin die op mijn andere apparaten draait. Geen eigenheim-workaround.
- Continue sync: een daemon haalt veranderingen van andere apparaten binnen terwijl de agent werkt, in plaats van eenmalige pull-commando’s.
- Eén bron van waarheid op schijf: er is maar één lokale map, en LiveSync is daar de eigenaar van. Geen WebDAV-race, geen dubbele schrijfkanalen.
De implementatie
Stap 1: de CLI bouwen
self-hosted-livesync-cli blijkt niet op npm te staan. Ik heb hem daarom uit de officiële source gebouwd:
git clone https://github.com/vrtmrz/obsidian-livesync ~/src/obsidian-livesync
cd ~/src/obsidian-livesync
npm install --no-audit --no-fund
npm run build --workspace self-hosted-livesync-cli
# → src/apps/cli/dist/index.cjs
Stap 2: configuratie en lokale structuur
De CLI werkt met een database-directory en een vault-directory. Die houd ik bewust gescheiden — de PouchDB-data mag nooit als gewone bestanden in de vault terechtkomen:
~/.livesync/KB-db/ ← PouchDB data + settings.json
~/Vault/KB/ ← de vault zoals de agent (en Obsidian) hem ziet
Via init-settings maak ik een settings.json aan en vul ik de CouchDB-credentials in (URI, user, password, database), plus liveSync: true en syncOnStart: true.
Stap 3: remote unlocken
De remote CouchDB-database bleek “vergrendeld” te zijn voor dit nieuwe apparaat — een beveiliging van LiveSync die nieuwe nodes expliciet moet accepteren. Oplossing:
node dist/index.cjs ~/.livesync/KB-db -V ~/Vault/KB unlock-remote
# [Verification] Remote Database: UNLOCKED
# [Verification] Current Device Node ID: ACCEPTED
Daarna kon de eerste sync draaien, gevolgd door een mirror-opdracht die de hele remote vault als bestanden naar ~/Vault/KB schreef: 244 van de 244 bestanden, inclusief .heic, .jpg, .pdf, .mp3, .gif, .webp en markdown.
Stap 4: de daemon als LaunchDaemon
Een daemon die de vault continu bewaakt moet bij het opstarten van de Mac mee starten, ook zonder ingelogde GUI-sessie. Op deze Mac draait alleen LoginWindow — geen Aqua-sessie — dus een gewone LaunchAgent mislukt (fout 125). Daarom koos ik voor een LaunchDaemon in het system-domain:
<!-- /Library/LaunchDaemons/nl.eddydevink.livesync-kb.plist -->
<key>UserName</key><string>eddy</string>
<key>ProgramArguments</key>
<array>
<string>/Users/eddy/.livesync/livesync-kb-wrapper.sh</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
Met daarnaast:
- Een wrapper-script dat het draaien van een tweede instantie weigert,
HOMEcorrect zet, en — cruciaal op deze Mac — het Node-binary her-codesigns vóórexec. LaunchDaemons die/opt/homebrew/bin/nodedirect aanroepen breken nabrew upgrade node(codesign-exit 9); de wrappercodesign -f -s -vóór het exec. - Logs naar
~/.livesync/livesync-kb.logvoor troubleshooting. - Bootstrappen met
sudo launchctl bootstrap systemen herstarten metlaunchctl kickstart -k.
De daemon draait nu continu en meldt in de log: [Daemon] Synchronisation completed: 246/246 en [Daemon] LiveSync active.
Valkuilen onderweg
De implementatie verliep niet zonder hobbels. De interessante lessen:
Configuratie-mismatch (usePluginSyncV2)
De eerste sync faalde met Configuration Mismatch Detected. De lokale config had usePluginSyncV2: true, de remote database niet. De fix: beide op false zetten (en handleFilenameCaseSensitive: false). Het is de moeite waard om dit veld te checken als je ooit een synchronisatiefout krijgt — de CLI is hier strenger dan de plugin.
Hoofdlettergevoeligheid van bestandsnamen
Met handleFilenameCaseSensitive: false (de default) worden remote document-IDs gelowercased. Mijn lokale Sync-test-agent.md verscheen op remote als sync-test-agent.md. Voor de daemon geen probleem — het is een consistente mapping — maar het is goed om te weten dat het lokale en remote bestand niet altijd dezelfde schrijfwijze hebben.
Delete-semantiek: tombstones
Verwijderen werkt — maar anders dan je verwacht. Als ik lokaal een bestand wis, verwijdert de daemon het niet fysiek uit CouchDB, maar markeert het document als deleted: true (een “tombstone”). Dat is bewust zo: andere apparaten moeten de verwijdering kunnen oppikken en lokaal ook het bestand weghalen. Pas als elk apparaat dat heeft gedaan is de verwijdering compleet. Fysiek opschonen in de database is dus niet aan de daemon, maar een handmatige operatie.
De database-lock
Zolang de daemon draait, is de lokale PouchDB vergrendeld. Je kunt dus geen handmatige livesync-cli sync-commando’s ernaast draaien. Dat is prima zolang de daemon het doet — maar het verklaart waarom je bij handmatig debuggen eerst de daemon moet stoppen.
Hoe het in de praktijk bevalt
Dit is inmiddels de dagelijkse werkwijze, en het bevalt uitstekend:
- De agent behandelt de vault als een gewoon project. Ik zeg “zoek in mijn vault naar notities over Tailscale en vat ze samen” — de agent gebruikt gewoon
globengrepop~/Vault/KB, leest de notities, en schrijft eventueel een nieuwe. Geen speciale CLI, geen leercurve. - Media werken gewoon. Afbeeldingen, PDF’s en audio zijn bestanden die de agent kan verplaatsen, hernoemen en refereren via de vertrouwde
![[naam.ext]]-links. - Alles synchroniseert automatisch. Een notitie die de agent schrijft verschijnt binnen enkele seconden in Obsidian op mijn telefoon en desktop. En andersom: een notitie die ik op mijn telefoon maak, staat binnen enkele seconden op schijf voor de agent.
- De delete-flow klopt. Bestanden die ik elders verwijder verdwijnen netjes uit de lokale map; bestanden die de agent lokaal wist worden als tombstone naar de andere apparaten gepropageerd.
- Het systeem is robuust. De daemon draait continu, herstart zichzelf via KeepAlive, en logt netjes. Na een
brew upgrade node— de klassieke code-signing valkuil — bleef de wrapper het werken door het her-codesignen.
De obsidian-vault-skill in de agent is compleet herschreven: niet langer “gebruik deze CLI tegen CouchDB”, maar simpelweg “de vault is een lokale map; gebruik je normale bestandstools; de daemon doet de rest.” De skill documenteert de conventies (media in Images/, filenaam-only links, tombstones) en de troubleshooting (daemon-status, log, herstart), maar de agent hoeft er in de praktijk nauwelijks naar te kijken.
Conclusie
De evolutie van mijn AI-agent-vault-integratie kent drie fases:
- WebDAV — logisch maar fout: race conditions met LiveSync op het bestandssysteem.
- Directe CouchDB-CLI — werkte, maar tekst-only en met een eigenheim-API die niet de tools waren die de agent al kent.
- Lokale kopie + headless LiveSync daemon — de eindbestemming: de agent werkt met gewone bestanden, LiveSync is de eigenaar van de map, en de officiële codebase van de plugin-auteur doet al het zware werk.
De grootste winst zit in de eenvoud: de AI-agent hoeft niets te weten van CouchDB, LiveSync, chunking of encryptie. Voor hem is de vault gewoon een map. En dat is precies hoe een goede abstractie zou moeten werken.