Minnessynkronisering utan att sista skrivningen vinner
· 5 min läsning
Två datorer, en uppsättning anteckningar, den ena redigerad medan den andra sov. Varje synkroniseringssystem har ett svar på det, och de flesta svarar med en jämförelse av tidsstämplar som i tysthet kastar bort den ena sidan. Det fungerar för ett utkast du kan skriva om. Det fungerar inte för filen en agent läser före varje tur, eftersom det som kastas bort är raden du skrev för att något inte skulle hända igen.
Minnessynkroniseringen i OHarness har därför ingen väg där sista skrivningen vinner. Det finns trevägsavstämning, och där den inte kan avgöra finns en konflikt som väntar på dig.
Lokalt i första hand, och bara lokalt om du vill
Anteckningarna ligger i <project>/.oharness/memory/*.md. De fungerar utan inloggning, de fungerar offline och de är vanlig Markdown som du kan redigera i vilken editor som helst. Ingenting i molnet är bärande: synkroniseringen är en funktion ovanpå filer som redan är kompletta utan den.
När du loggar in slås automatisk synkronisering på, vilket upptäcks inom ungefär femton sekunder medan harnessen körs. Sidan Minne erbjuder att stänga av den och att synkronisera direkt. Avstängt läge består över omstarter av processen och ny inloggning, och blockerar automatiska och manuella förfrågningar tills du slår på den igen — en strömbrytare som i tysthet återgår vid nästa start är ingen strömbrytare.
En ärlig begränsning, angiven i dokumentationen i stället för att upptäckas senare: att stänga av avbryter pågående förfrågningar, men data som molnet redan har tagit emot kan inte dras tillbaka. Avstängt stoppar nästa uppladdning; det raderar inte det som redan finns där.
Trevägs, inte tvåvägs
Avstämningen behöver tre tillstånd, inte två: det som finns här, det som finns där och det som båda sidor senast var överens om. Det sista är baslinjen, som sparas i ~/.oharness/memory-sync/, och det är den som gör skillnaden mellan ”de här skiljer sig” och ”den här har ändrats”.
Därifrån är fallen odramatiska, vilket är poängen:
- oförändrad på båda sidor — hoppas över, ingen läsning, ingen skrivning
- ändrad på en sida — kopieras till den andra
- ändrad på båda sidor — en konflikt, som visas och lämnas åt dig
Raderingar är gravstenar snarare än frånvaro, eftersom en fil som saknas inte går att skilja från en fil som ännu inte har kommit fram, och att behandla det andra som det första är hur ett synkroniseringssystem raderar ditt arbete på ett långsamt nätverk.
På molnsidan håller R2 oföränderliga Markdown-revisioner plus ett manifest som ersätts atomiskt:
users/<server-verified-user-id>/projects/<project-id>/memory/
manifest.json
objects/<note-id>/<revision-id>.md
Skrivningar till manifestet använder ETag-baserad compare-and-swap, så en samtidig inaktuell uppdatering avvisas i stället för att tillämpas. Revisioner är oföränderliga, så den förlorande sidan i en konflikt är fortfarande ett objekt som finns. Och när manifestet inte har ändrats hämtas bara manifestet — det vanliga fallet, en dator som har kört hela eftermiddagen utan något att göra, kostar en liten läsning.
Ett projekts identitet är Git-rotcommiten, vilket är det som gör att samma projekt i två kloner på två olika sökvägar kan vara ett projekt. Projekt utan Git faller tillbaka på ett id baserat på den absoluta sökvägen och mappas därför inte automatiskt mellan enheter. Det är en verklig begränsning snarare än en ojämnhet att släta över: alternativet är en heuristik som ibland slår ihop två orelaterade kataloger, och det finns ingen bra version av det misstaget.
Varje lokal minneskatalog är låst till det första konto och det molnursprung som synkroniserade den. Ändras något av dem pausas synkroniseringen och du ombeds använda en separat katalog i stället. Det finns medvetet ingen omtilldelning med ett klick, eftersom ettklicksversionen av den åtgärden är den som laddar upp en persons anteckningar till en annan persons konto.
Vad det inte är
Två saker som är värda att säga rakt ut.
Det är inte end-to-end-krypterat. R2 är privat och allt går över TLS, men molnet kan läsa innehållet i anteckningarna. Om det är fel avvägning för det du skriver ner, stäng av synkroniseringen och behåll filerna lokalt, vilket är en konfiguration som stöds och inte ett sämre läge.
Bara minnesanteckningar synkroniseras. Inte sessionsloggar, inte filer, inte autentiseringsuppgifter och inte behörighetsspåret — det sista stannar på den dator som fattade besluten och raderas tillsammans med sin session.
Det finns gränser också, och de är av första-versionen-sorten: 500 manifestposter per projekt inklusive gravstenar, 128 KiB per anteckningstext, 2 MiB per serialiserad ögonblicksbild. Gamla revisioner behålls; skräpsamling för revisioner och gravstenar är ännu inte implementerad. Raderade anteckningar och förlorande konfliktversioner säkerhetskopieras under ~/.oharness/memory-backups/ och kan återställas via editorn, även om det ännu inte finns någon vy för att bläddra bland säkerhetskopior.
Varför ett plugin
Minne är ett inbyggt arkitekturplugin, inte en MCP-server. Det registrerar sitt verktyg, sitt systempromptindex per tur, sin sida och sin synkronisering i bakgrunden via samma PluginHost som modell-, verktygs- och granskningsplugins går igenom — och { "id": "memory", "enabled": false } i plugins.json tar bort alla fyra.
Det är det test som ett pluginsystem antingen klarar eller inte. Om funktionerna som följer med i lådan når körtiden via en privat dörr är den offentliga dörren dekoration, och alla som skriver mot den upptäcker det långsamt. Sidtillägget hålls litet av samma skäl: registrerade deskriptorer och renderare som ägs av värden, inte godtycklig JavaScript i gränssnittet och inte ett iframe-SDK. En yta som kan göra vad som helst är en yta som ingen kan ändra i efterhand.
OHarness är en agent-harness med öppen källkod, MIT-licensierad. Referensen för minne och synkronisering dokumenterar konfliktreglerna, lagringsstrukturen och konfigurationsgränserna i sin helhet, och egen synkserver beskriver hur du kör alltihop på din egen infrastruktur.