Sincronizzare la memoria senza che vinca l'ultima scrittura

· 5 min di lettura

Due macchine, un solo insieme di note, una modificata mentre l'altra era in standby. Ogni sistema di sincronizzazione risponde a questa situazione, e la maggior parte lo fa con un confronto di timestamp che scarta in silenzio uno dei due lati. Va bene per una bozza che puoi riscrivere. Non va bene per il file che un agente legge prima di ogni turno, perché ciò che viene scartato è la riga che hai scritto perché qualcosa non si ripetesse.

Per questo la sincronizzazione della memoria in OHarness non ha alcun percorso last-write-wins. C'è una riconciliazione a tre vie e, dove questa non può decidere, c'è un conflitto che aspetta te.

Prima di tutto in locale, e solo in locale se vuoi

Le note si trovano in <project>/.oharness/memory/*.md. Funzionano senza accesso, funzionano offline e sono semplice Markdown che puoi modificare con qualsiasi editor. Nulla del cloud è portante: la sincronizzazione è una funzionalità aggiunta sopra file che sono già completi senza di essa.

Accedere attiva la sincronizzazione automatica, rilevata entro circa quindici secondi mentre l'harness è in esecuzione. La pagina Memoria permette di disattivarla e di sincronizzare subito. La disattivazione persiste ai riavvii del processo e ai nuovi accessi, e blocca le richieste automatiche e manuali finché non la riattivi: un interruttore che torna indietro in silenzio al successivo avvio non è un interruttore.

Un limite onesto, dichiarato nella documentazione invece di essere scoperto più tardi: disattivare annulla le richieste in corso, ma i dati che il cloud ha già accettato non possono essere ritirati. La disattivazione blocca il prossimo caricamento; non elimina ciò che è già lì.

A tre vie, non a due

La riconciliazione richiede tre stati, non due: ciò che c'è qui, ciò che c'è là e ciò su cui entrambi i lati si sono trovati d'accordo l'ultima volta. Quest'ultimo è la baseline, conservata in ~/.oharness/memory-sync/, ed è ciò che fa la differenza tra "questi sono diversi" e "questo è cambiato".

Da lì i casi sono banali, ed è proprio questo il punto:

  • invariato su entrambi i lati — saltato, nessuna lettura, nessuna scrittura
  • modificato su un lato — copiato sull'altro
  • modificato su entrambi i lati — un conflitto, mostrato e lasciato a te

Le eliminazioni sono tombstone e non assenze, perché un file assente è indistinguibile da un file che non è ancora arrivato, e trattare il secondo come il primo è il modo in cui un sistema di sincronizzazione elimina il tuo lavoro su una rete lenta.

Lato cloud, R2 conserva revisioni Markdown immutabili più un manifest che viene sostituito in modo atomico:

users/<server-verified-user-id>/projects/<project-id>/memory/
  manifest.json
  objects/<note-id>/<revision-id>.md

Le scritture del manifest usano compare-and-swap con ETag, quindi un aggiornamento concorrente non aggiornato viene rifiutato invece che applicato. Le revisioni sono immutabili, quindi il lato perdente di un conflitto è comunque un oggetto che esiste. E quando il manifest non è cambiato, viene scaricato solo il manifest: il caso comune, una macchina che è rimasta accesa tutto il pomeriggio senza niente da fare, costa una piccola lettura.

L'identità di un progetto è il commit radice di Git, ed è ciò che permette allo stesso progetto, clonato due volte in due percorsi diversi, di essere un unico progetto. I progetti non Git ripiegano su un id basato sul percorso assoluto e quindi non vengono associati automaticamente tra dispositivi. È una limitazione reale, non un'imperfezione da nascondere: l'alternativa è un'euristica che occasionalmente unisce due directory non correlate, e di quel fallimento non esiste una versione accettabile.

Ogni directory di memoria locale è vincolata al primo account e alla prima origine cloud che l'hanno sincronizzata. Cambiare l'uno o l'altra mette in pausa la sincronizzazione e ti chiede di usare invece una directory separata. Non esiste deliberatamente una riassegnazione con un clic, perché la versione con un clic di quell'operazione è quella che carica le note di una persona nell'account di un'altra.

Cosa non è

Due cose su cui vale la pena essere schietti.

Non è crittografata end-to-end. R2 è privato e tutto passa via TLS, ma il cloud può leggere il contenuto delle note. Se per ciò che scrivi questo è il compromesso sbagliato, disattiva la sincronizzazione e tieni i file in locale, che è una configurazione supportata e non una versione degradata.

Si sincronizzano solo le note di memoria. Non i log delle sessioni, non i file, non le credenziali e non la traccia di audit dei permessi: quest'ultima resta sulla macchina che ha preso le decisioni e viene eliminata insieme alla sua sessione.

Ci sono anche dei limiti, ed è il tipo di limiti da prima versione: 500 voci di manifest per progetto, tombstone inclusi, 128 KiB per corpo di nota, 2 MiB per snapshot serializzato. Le vecchie revisioni vengono conservate; la garbage collection per revisioni e tombstone non è ancora implementata. Le note eliminate e le versioni perdenti dei conflitti vengono salvate in ~/.oharness/memory-backups/ e possono essere ripristinate dall'editor, anche se non esiste ancora un browser dei backup.

Perché un plugin

La memoria è un plugin di architettura integrato, non un server MCP. Registra il suo strumento, il suo indice nel prompt di sistema a ogni turno, la sua pagina e il suo sincronizzatore in background attraverso lo stesso PluginHost usato dai plugin di modello, di strumento e di audit — e { "id": "memory", "enabled": false } in plugins.json li rimuove tutti e quattro.

È la prova che un sistema di plugin supera o non supera. Se le funzionalità incluse arrivano al runtime da una porta privata, la porta pubblica è decorazione, e chiunque ci scriva sopra se ne accorge lentamente. L'estensione delle pagine resta piccola per lo stesso motivo: descrittori registrati e renderer gestiti dall'host, non JavaScript arbitrario per l'interfaccia e non un SDK per iframe. Una superficie che può fare qualsiasi cosa è una superficie che poi nessuno può cambiare.


OHarness è un harness per agenti open source, con licenza MIT. Il riferimento su memoria e sincronizzazione documenta per intero le regole sui conflitti, la struttura di archiviazione e i limiti di configurazione, e il server di sincronizzazione self-hosted spiega come eseguire il tutto sulla tua infrastruttura.