Geheugensynchronisatie zonder last-write-wins
· 5 min lezen
Twee machines, één set notities, en een ervan is bewerkt terwijl de andere sliep. Elk synchronisatiesysteem heeft hier een antwoord op, en de meeste antwoorden met een vergelijking van tijdstempels die stilletjes één kant weggooit. Voor een concept dat je opnieuw kunt typen is dat prima. Voor het bestand dat een agent vóór elke beurt leest is het dat niet, want wat er wordt weggegooid, is de regel die je schreef om te voorkomen dat iets nog eens gebeurt.
Daarom heeft geheugensynchronisatie in OHarness helemaal geen last-write-wins-pad. Er is driewegverzoening, en waar die geen beslissing kan nemen, is er een conflict dat op jou wacht.
Local-first, en als je wilt alleen lokaal
Notities staan in <project>/.oharness/memory/*.md. Ze werken zonder in te loggen, ze werken offline, en het is gewone Markdown die je in elke editor kunt bewerken. Niets aan de cloud is dragend: synchronisatie is een functie bovenop bestanden die zonder haar al compleet zijn.
Inloggen zet automatische synchronisatie aan; dat wordt binnen ongeveer vijftien seconden gedetecteerd terwijl het harness draait. De pagina Geheugen biedt aan om haar uit te zetten en om nu meteen te synchroniseren. Uit blijft uit na het herstarten van het proces en opnieuw inloggen, en blokkeert automatische en handmatige verzoeken totdat je haar weer inschakelt — een schakelaar die bij de volgende start stilletjes terugspringt, is geen schakelaar.
Eén eerlijke beperking, vermeld in de documentatie in plaats van later ontdekt: uitzetten annuleert lopende verzoeken, maar gegevens die de cloud al heeft geaccepteerd, kunnen niet worden teruggetrokken. Uit stopt de volgende upload; het verwijdert niet wat er al staat.
Drieweg, niet tweeweg
De verzoening heeft drie toestanden nodig, niet twee: wat hier staat, wat daar staat, en waarover beide kanten het laatst eens waren. Dat laatste is de basislijn, bewaard in ~/.oharness/memory-sync/, en die maakt het verschil tussen „deze verschillen” en „deze is gewijzigd”.
Vanaf daar zijn de gevallen onopvallend, en dat is precies de bedoeling:
- aan beide kanten ongewijzigd — overgeslagen, niet gelezen, niet geschreven
- aan één kant gewijzigd — naar de andere kant gekopieerd
- aan beide kanten gewijzigd — een conflict, getoond en aan jou overgelaten
Verwijderingen zijn tombstones in plaats van afwezigheden, omdat een ontbrekend bestand niet te onderscheiden is van een bestand dat nog niet is aangekomen, en het tweede behandelen als het eerste is hoe een synchronisatiesysteem je werk verwijdert op een traag netwerk.
Aan de cloudkant bewaart R2 onveranderlijke Markdown-revisies plus een manifest dat atomair wordt vervangen:
users/<server-verified-user-id>/projects/<project-id>/memory/
manifest.json
objects/<note-id>/<revision-id>.md
Schrijfacties naar het manifest gebruiken ETag-compare-and-swap, dus een gelijktijdige verouderde update wordt geweigerd in plaats van toegepast. Revisies zijn onveranderlijk, dus de verliezende kant van een conflict is nog steeds een object dat bestaat. En als het manifest niet is gewijzigd, wordt alleen het manifest opgehaald — het gangbare geval, een machine die de hele middag draait zonder iets te doen, kost één kleine leesactie.
De identiteit van een project is de root-commit in Git, waardoor hetzelfde project in twee clones op twee verschillende paden één project kan zijn. Projecten zonder Git vallen terug op een id op basis van het absolute pad en worden daardoor niet automatisch tussen apparaten gekoppeld. Dat is een echte beperking en geen ruw randje dat je wegpoetst: het alternatief is een heuristiek die af en toe twee ongerelateerde mappen samenvoegt, en van die fout bestaat geen goede versie.
Elke lokale geheugenmap is vastgepind aan het eerste account en de eerste cloudoorsprong die haar synchroniseerden. Als je een van beide wijzigt, wordt synchronisatie gepauzeerd en wordt je gevraagd een aparte map te gebruiken. Er is bewust geen herindeling met één klik, want de versie met één klik van die handeling is precies de versie die de notities van de ene persoon uploadt naar het account van een ander.
Wat het niet is
Twee dingen waarover je beter bot kunt zijn.
Het is niet end-to-end versleuteld. R2 is privé en alles gaat via TLS, maar de cloud kan de inhoud van notities lezen. Als dat de verkeerde afweging is voor wat jij opschrijft, zet synchronisatie dan uit en houd de bestanden lokaal; dat is een ondersteunde configuratie en geen afgeslankte.
Alleen geheugennotities worden gesynchroniseerd. Geen sessielogs, geen bestanden, geen inloggegevens, en niet het audit-spoor van permissies — dat laatste blijft op de machine die de beslissingen nam en wordt samen met de sessie verwijderd.
Er zijn ook grenzen, en die zijn van het soort dat bij een eerste versie hoort: 500 manifestitems per project inclusief tombstones, 128 KiB per notitie, 2 MiB per geserialiseerde snapshot. Oude revisies worden bewaard; garbage collection voor revisies en tombstones is nog niet geïmplementeerd. Verwijderde notities en verliezende conflictversies worden als back-up bewaard onder ~/.oharness/memory-backups/ en kunnen via de editor worden hersteld, al is er nog geen browser voor back-ups.
Waarom een plugin
Geheugen is een ingebouwde architectuurplugin, geen MCP-server. Het registreert zijn tool, zijn index in de systeemprompt per beurt, zijn pagina en zijn synchronisatie op de achtergrond via dezelfde PluginHost waar ook model-, tool- en auditplugins doorheen gaan — en { "id": "memory", "enabled": false } in plugins.json verwijdert alle vier.
Dat is de test waarvoor een pluginsysteem slaagt of zakt. Als de functies die standaard worden meegeleverd de runtime via een privédeur bereiken, is de publieke deur decoratie, en iedereen die ertegen schrijft, komt daar langzaam achter. De pagina-extensie blijft om dezelfde reden klein: geregistreerde descriptors en renderers die de host beheert, geen willekeurige frontend-JavaScript en geen iframe-SDK. Een oppervlak dat alles kan, is een oppervlak dat niemand achteraf nog kan veranderen.
OHarness is een open source agent-harness met een MIT-licentie. De referentie voor geheugen en synchronisatie documenteert de conflictregels, de opslagindeling en de grenzen van de configuratie volledig, en zelfgehoste synchronisatie behandelt hoe je het geheel op je eigen infrastructuur draait.