Memory-Sync ohne „Last Write Wins“
· 5 Min. Lesezeit
Zwei Rechner, ein Satz Notizen, einer davon bearbeitet, während der andere schlief. Jedes Sync-System beantwortet das, und die meisten mit einem Zeitstempelvergleich, der stillschweigend eine Seite verwirft. Für einen Entwurf, den man neu tippen kann, ist das in Ordnung. Nicht in Ordnung ist es für die Datei, die ein Agent vor jeder Runde liest, denn was dabei verworfen wird, ist die Zeile, die Sie geschrieben haben, damit etwas nicht noch einmal passiert.
Deshalb hat die Memory-Synchronisierung in OHarness überhaupt keinen Last-Write-Wins-Pfad. Es gibt einen Drei-Wege-Abgleich, und wo dieser nicht entscheiden kann, gibt es einen Konflikt, der auf Sie wartet.
Lokal zuerst – und auf Wunsch nur lokal
Notizen liegen in <project>/.oharness/memory/*.md. Sie funktionieren ohne Anmeldung, sie funktionieren offline, und sie sind schlichtes Markdown, das Sie in jedem Editor bearbeiten können. Nichts an der Cloud ist tragend: Die Synchronisierung ist eine Funktion, die auf Dateien aufsetzt, die auch ohne sie vollständig sind.
Mit der Anmeldung wird die automatische Synchronisierung eingeschaltet; das wird innerhalb von etwa fünfzehn Sekunden erkannt, während das Harness läuft. Die Memory-Seite bietet an, sie abzuschalten und sofort zu synchronisieren. „Aus“ bleibt über Prozessneustarts und erneute Anmeldung hinweg bestehen und blockiert automatische und manuelle Anfragen, bis Sie sie wieder aktivieren – ein Schalter, der beim nächsten Start still zurückspringt, ist kein Schalter.
Eine ehrliche Einschränkung, die in der Dokumentation steht, statt erst später entdeckt zu werden: Das Abschalten bricht laufende Anfragen ab, aber Daten, die die Cloud bereits angenommen hat, lassen sich nicht zurückholen. „Aus“ stoppt den nächsten Upload; es löscht nicht, was schon dort liegt.
Drei Wege, nicht zwei
Der Abgleich braucht drei Zustände, nicht zwei: was hier ist, was dort ist und worauf sich beide Seiten zuletzt geeinigt haben. Letzteres ist die Basis, abgelegt in ~/.oharness/memory-sync/, und sie macht den Unterschied zwischen „diese unterscheiden sich“ und „diese hier hat sich geändert“.
Von dort aus sind die Fälle unspektakulär, und genau darum geht es:
- auf beiden Seiten unverändert – übersprungen, kein Lesen, kein Schreiben
- auf einer Seite geändert – auf die andere kopiert
- auf beiden Seiten geändert – ein Konflikt, angezeigt und Ihnen überlassen
Löschungen sind Tombstones statt bloßer Abwesenheit, denn eine fehlende Datei ist nicht von einer Datei zu unterscheiden, die noch nicht angekommen ist, und den zweiten Fall als den ersten zu behandeln ist der Weg, auf dem ein Sync-System bei einem langsamen Netz Ihre Arbeit löscht.
In der Cloud hält R2 unveränderliche Markdown-Revisionen sowie ein Manifest, das atomar ersetzt wird:
users/<server-verified-user-id>/projects/<project-id>/memory/
manifest.json
objects/<note-id>/<revision-id>.md
Schreibvorgänge am Manifest nutzen ETag-Compare-and-Swap, sodass ein gleichzeitiges veraltetes Update abgelehnt statt angewendet wird. Revisionen sind unveränderlich, also ist auch die unterlegene Seite eines Konflikts noch ein Objekt, das existiert. Und wenn sich das Manifest nicht geändert hat, wird nur das Manifest abgerufen – der häufige Fall, ein Rechner, der den ganzen Nachmittag läuft und nichts zu tun hat, kostet einen kleinen Lesezugriff.
Die Identität eines Projekts ist der Git-Root-Commit; dadurch ist dasselbe Projekt in zwei Clones unter zwei verschiedenen Pfaden ein einziges Projekt. Projekte ohne Git fallen auf eine ID aus dem absoluten Pfad zurück und werden deshalb nicht automatisch geräteübergreifend zugeordnet. Das ist eine echte Einschränkung, keine raue Kante, die man überkleistern sollte: Die Alternative ist eine Heuristik, die gelegentlich zwei voneinander unabhängige Verzeichnisse zusammenführt, und für diesen Fehler gibt es keine gute Variante.
Jedes lokale Memory-Verzeichnis ist an das erste Konto und den ersten Cloud-Ursprung gebunden, die es synchronisiert haben. Ändert sich eines davon, pausiert die Synchronisierung und bittet Sie, stattdessen ein separates Verzeichnis zu verwenden. Es gibt bewusst keine Neuzuordnung per Klick, denn die Ein-Klick-Version dieses Vorgangs ist genau die, die die Notizen einer Person in das Konto einer anderen Person hochlädt.
Was es nicht ist
Zwei Dinge, bei denen man deutlich sein sollte.
Es ist nicht Ende-zu-Ende-verschlüsselt. R2 ist privat und alles läuft über TLS, aber die Cloud kann den Inhalt der Notizen lesen. Ist das für das, was Sie aufschreiben, der falsche Kompromiss, schalten Sie die Synchronisierung ab und behalten die Dateien lokal – das ist eine unterstützte Konfiguration, keine eingeschränkte.
Nur Memory-Notizen werden synchronisiert. Keine Sitzungsprotokolle, keine Dateien, keine Zugangsdaten und nicht das Berechtigungsprotokoll – dieses bleibt auf dem Rechner, der die Entscheidungen getroffen hat, und wird mit seiner Sitzung gelöscht.
Es gibt auch Grenzen, und zwar solche einer ersten Version: 500 Manifesteinträge pro Projekt einschließlich Tombstones, 128 KiB pro Notiztext, 2 MiB pro serialisiertem Snapshot. Alte Revisionen werden aufbewahrt; eine Garbage Collection für Revisionen und Tombstones ist noch nicht implementiert. Gelöschte Notizen und unterlegene Konfliktversionen werden unter ~/.oharness/memory-backups/ gesichert und lassen sich über den Editor wiederherstellen, allerdings gibt es noch keinen Backup-Browser.
Warum ein Plugin
Memory ist ein eingebautes Architektur-Plugin, kein MCP-Server. Es registriert sein Tool, seinen System-Prompt-Index pro Runde, seine Seite und seinen Hintergrund-Synchronisierer über denselben PluginHost, über den auch Modell-, Tool- und Audit-Plugins laufen – und { "id": "memory", "enabled": false } in plugins.json entfernt alle vier.
Das ist der Test, den ein Plugin-System entweder besteht oder nicht. Erreichen die mitgelieferten Funktionen die Laufzeit durch eine private Tür, ist die öffentliche Tür Dekoration, und alle, die gegen sie entwickeln, merken das nur langsam. Die Seitenerweiterung bleibt aus demselben Grund klein: registrierte Deskriptoren und vom Host verwaltete Renderer, kein beliebiges Frontend-JavaScript und kein iframe-SDK. Eine Oberfläche, die alles kann, ist eine Oberfläche, die danach niemand mehr ändern kann.
OHarness ist ein quelloffenes Agent-Harness unter MIT-Lizenz. Die Referenz zu Memory und Synchronisierung dokumentiert die Konfliktregeln, die Speicherstruktur und die Konfigurationsgrenzen vollständig, und Selbst gehosteter Sync beschreibt, wie Sie das Ganze auf Ihrer eigenen Infrastruktur betreiben.