Geheugen en synchronisatie
Geheugen is een ingebouwde architectuurplugin van oHarness (memory), geen MCP-server. Hij registreert de geheugentools, de index in de systeemprompt, terugzoeken per bericht, optionele extractie, de pagina Memory en een synchronisatieproces op de achtergrond — allemaal via dezelfde PluginHost als model-, tool- en auditplugins. De pagina-uitbreiding blijft klein: geregistreerde descriptors plus renderers die de host beheert (op dit moment memory), geen willekeurige frontend-JavaScript of een iframe-SDK.
Waar notities staan
Eén notitie per Markdown-bestand, met JSON-frontmatter:
---
{"id":"pricing-borderless-3fa","summary":"Pricing cards use fills, not borders","type":"feedback","tags":["design"],"createdAt":1789000000000,"updatedAt":1789000000000}
---
Cards on the pricing page are separated by fill and whitespace.
**Why:** the outlines were the loudest thing on a page of numbers.
**How to apply:** keep the border declared but transparent. See [[pricing-layout-a91]].
| Bereik | Map | Bevat |
|---|---|---|
project | <project>/MEMORY/ | Beslissingen, randvoorwaarden en verwijzingen die met het project meeverhuizen en -exporteren |
user | ~/.oharness/memory/user/ | Wie de gebruiker is en hoe die graag werkt, in elk project |
Elk geregistreerd project heeft .oharness/project.json. De stabiele id daarin houdt sessies aan de Home-kant, synchronisatiestatus en terugzoekstatistieken gekoppeld als de map wordt hernoemd of geëxporteerd. Een git-repository gebruikt in eerste instantie zijn root-commit als die id; andere mappen krijgen een willekeurige project-id. OHARNESS_HOME verplaatst gebruikersnotities en machinelokale status, maar verplaatst nooit de MEMORY/-map van het project.
memory.dir (of het oudere memoryDir) in de configuratie overschrijft de projectmap, relatief aan de projectroot. Kan de gebruikersmap niet worden aangemaakt, dan blijven projectnotities beschikbaar in <project>/MEMORY; alleen notities op gebruikersniveau en geheugenstatus aan de Home-kant zijn dan niet beschikbaar, en een waarschuwing meldt dat.
Typen
| Type | Standaardbereik | Wat het vastlegt |
|---|---|---|
user | user | De rol, expertise en werkvoorkeuren van de gebruiker |
feedback | project | Een correctie, of een aanpak die de gebruiker heeft bevestigd |
project | project | Een beslissing en de reden ervoor, een randvoorwaarde, lopend werk dat niet uit de code af te leiden is |
reference | project | Waar externe informatie te vinden is |
Alleen user-notities vallen standaard in het gebruikersbereik. Feedback valt standaard in het project, omdat de meeste correcties gaan over hoe er aan één codebase wordt gewerkt; een correctie die duidelijk overal geldt, wordt bewust in het gebruikersbereik gezet.
Notities die zijn geschreven voordat er typen bestonden, hebben er geen en blijven werken. type maakt alleen deel uit van de inhoudshash van een notitie als het aanwezig is, zodat bestaande notities de hashes houden die hun synchronisatiebasislijnen al bevatten.
Notities schrijven
Het model schrijft met remember, leest met memory en verwijdert met forget. remember uit de geheugenplugin gaat zonder vraag door in elke permissiemodus behalve plan, die het weigert zoals elke andere schrijfactie. Een ask- of deny-regel die remember noemt, geldt nog steeds, forget vraagt nog steeds, en een tool met de naam remember van een andere plugin krijgt zo'n uitzondering niet. Tags worden ingekort, gesplitst op komma's en spaties en ontdubbeld voordat de notitie wordt opgeslagen. Het Memory-deel van de systeemprompt zegt wanneer een notitie het schrijven waard is en wanneer niet. remember geeft de bestaande notities terug die het meest verwant zijn aan wat net is geschreven, markeert een waarschijnlijk duplicaat en noemt links die verwijzen naar notities die nog niet bestaan. Alles wat op een credential lijkt, wordt geweigerd. Notities wordt gevraagd tags te dragen voor de andere woorden die iemand zou kunnen gebruiken: synoniemen, en de term in de andere taal die in gebruik is, omdat terugzoeken op woorden matcht en niet op betekenis.
Notities linken in de tekst naar elkaar als [[note-id]]. Links worden uit de teksten afgeleid wanneer notities worden geladen; er wordt niets apart over opgeslagen, en het synchronisatieprotocol weet niet dat ze bestaan. Een link wordt eerst in het projectbereik opgezocht, dan in het gebruikersbereik. Een link naar een ontbrekende notitie blijft staan: meestal noemt die een notitie die iemand wilde schrijven.
Automatische extractie
Standaard uit, omdat elke ronde een modelaanroep is op het eigen tegoed van de gebruiker. Met memory.autoExtract.onStop geeft het einde van een run het gesprek sinds de vorige ronde aan het kleine model, zodra er sindsdien minstens minUserTurns echte gebruikersberichten zijn bijgekomen. Met onCompaction (of onStop) wordt het deel dat compactie op het punt staat te laten vallen en dat nog door geen ronde is gelezen eerst doorgegeven, hoe kort ook, omdat het de laatste kans is om het te lezen. Een hervatte sessie begint te tellen vanaf waar ze is hervat, in plaats van haar hele geschiedenis opnieuw te extraheren. Uit subagents wordt nooit geëxtraheerd.
De extractor krijgt de bestaande notities te zien die het meest verwant zijn aan het gesprek. De paar meest relevante komen met hun volledige tekst, binnen een groottebudget, en alleen die mogen worden bijgewerkt; de rest wordt per samenvatting genoemd om naar te linken. Een update vervangt de tekst, dus de vervangen versie wordt eerst als back-up opgeslagen in ~/.oharness/memory-backups/. Het antwoord van de extractor wordt als onbetrouwbaar behandeld: updates aan id's die hij niet te zien kreeg, vallen weg, links naar id's die hij niet te zien kreeg, verliezen hun haken, credentials worden weggelakt, en nieuwe notities in één batch verwijzen naar elkaar als [[new:N]] totdat ze een id hebben. De configuratie van een repository kan extractie uitzetten; alleen de eigen configuratie van de gebruiker kan haar aanzetten.
Extractie is pas klaar nadat de run die haar startte is afgelopen. De notities die ze heeft opgeslagen, worden daarom aan het begin van het volgende bericht gemeld: „saved N memories from this conversation”, in elke interface.
Terugzoeken
Twee routes brengen notities onder de aandacht van het model:
- De index. Aan het begin van elke run één regel per notitie, laatst bijgewerkt eerst, tot
memory.indexCharstekens. De rest wordt geteld, niet verloren; dememory-tool doorzoekt ze allemaal. - Terugzoeken per bericht. Elk gebruikersbericht krijgt een score tegen elke notitie, en de beste treffers worden aan dat bericht gehangen binnen
<recalled-memory>, met de notities waarnaar ze linken per samenvatting erbij. De interfaces tonen „recalled N memories”. Er wordt niets gehangen als niets de drempel haalt, wat meestal het geval is. Zijn er helemaal geen notities, dan wordt de index vervangen door twee regels die zeggen wanneer je er een schrijft, in plaats van de volledige richtlijnen.
De scoring is lexicaal en draait lokaal: woorden worden gestemd, CJK-tekst wordt opgesplitst in overlappende paren, en treffers in de samenvatting en tags wegen zwaarder dan treffers in de tekst. Een notitie moet in haar samenvatting of tags matchen om überhaupt te worden gehangen. Een notitie die één keer is gehangen, wordt niet opnieuw gehangen zolang een bericht dat haar draagt in de transcriptie staat, ook nadat de sessie is hervat of de app opnieuw is gestart; de id's worden teruggelezen uit de metadata van de berichten. Compactie maakt de notities vrij waarvan ze de berichten heeft weggesamenvat. Gehangen notities staan in een eigen blok van het gebruikersbericht, vastgelegd in de metadata van het bericht, zodat transcripties, sessietitels en de webgeschiedenis de eigen woorden van de gebruiker tonen. Subagents zoeken niet terug.
Als terugzoeken mislukt, houdt dat nooit het versturen van een bericht tegen.
Limieten
| Instelling | Standaard | Betekenis |
|---|---|---|
memory.maxNotes | 5000 | Per bereik. Daarboven worden de minst recent gewijzigde bestanden niet geladen en meldt een waarschuwing hoeveel; nieuwe notities worden geweigerd tot er een paar zijn verwijderd |
memory.maxNoteBytes | 131072 | Grootte van de tekst; de synchronisatieserver hanteert ook een eigen limiet |
memory.indexChars | 4000 | Tekens aan samenvattingen in de systeemprompt |
memory.recall.enabled | true | Terugzoeken per bericht |
memory.recall.perTurn | 3 | Notities die aan één bericht worden gehangen |
memory.recall.budgetChars | 2000 | Tekens die die notities mogen innemen |
memory.recall.minScore | 1.2 | Drempel om te hangen |
memory.autoExtract.onCompaction / onStop | false | Automatische extractie |
memory.autoExtract.minUserTurns | 3 | Kortere gesprekken worden overgeslagen |
Hoe vaak elke notitie is teruggezocht, wordt per machine bijgehouden in ~/.oharness/memory-usage/, buiten de notities, zodat terugzoeken nooit een synchronisatie-upload veroorzaakt. Het beïnvloedt de rangschikking een beetje en wordt nooit gesynchroniseerd.
Cloudsynchronisatie
- Notities werken altijd lokaal, ook uitgelogd en offline.
- Inloggen zet automatische synchronisatie standaard aan. Synchronisatie werkt in twee richtingen: lokale wijzigingen gaan omhoog, en wijzigingen op andere apparaten komen vanzelf binnen.
- De pagina Memory biedt 关闭云同步 en 立即同步. De schakelaar geldt voor beide bereiken. Uit blijft uit na herstarten en opnieuw inloggen, en blokkeert automatische en handmatige cloudverzoeken totdat je het expliciet weer aanzet. Lokale tools en bewerken blijven werken.
- Synchronisatie uitzetten annuleert lopende verzoeken. Data die Cloud al heeft geaccepteerd, kan niet worden teruggetrokken; synchronisatie uitzetten verwijdert geen kopieën in de cloud.
- Elk logisch project-/gebruikersbereik is vastgezet op het eerste account en de eerste Cloud-origin waarmee het synchroniseerde. Een ander account of een andere origin pauzeert de synchronisatie. De pagina biedt geen onveilige herkoppeling met één klik.
De pagina beheert ~/.oharness/plugins.json (OHARNESS_HOME overschrijft dit), onder runtimePlugins[id=memory].config.syncTargets[sha256(stableScopeKey)]:
{ "userId": "verified-account-label", "origin": "https://your-cloud.example", "enabled": false }
Zet geen R2-sleutels of Supabase-configuratie in dit bestand. De URL van Cloud blijft in cloud.json; R2-credentials bestaan alleen in de omgeving van de synchronisatie-deployment. Om de hele plugin uit te zetten (pagina, tools, prompts, terugzoeken en achtergrondwerk), voeg je { "id": "memory", "enabled": false } toe aan runtimePlugins; dat werkt bij de volgende keer dat een Harness wordt aangemaakt. Projectregistratie maakt nog steeds de standaard lege mappen MEMORY/ en ARTIFACTS/ aan. Voor wie de engine inbedt: harness.memory is de projectopslag en harness.memoryUserService die van de gebruiker; beide ontbreken als de plugin is uitgeschakeld. AgentOptions.memory blijft een inbeddings-API op lager niveau en krijgt terugzoeken zonder plugin-host.
Wanneer er wordt gesynchroniseerd
Eén coördinator per proces plant elke notitiemap in, hoeveel projecten een lokale webserver of de desktop-app ook open heeft. Elke map heeft één synchronisatiedienst in het proces, dus de eigen notities van de gebruiker worden één keer gesynchroniseerd en niet één keer per geopend project.
- Een lokale schrijfactie wordt ongeveer drie seconden later geüpload; een reeks schrijfacties is één ronde.
- Verder wordt een map elke minuut gecontroleerd, terugschakelend naar elke vijf minuten zolang er aan geen van beide kanten iets verandert.
- Je eigen notities worden altijd gecontroleerd. De notities van een project worden alleen gecontroleerd zolang het project in gebruik is (er wordt een bericht in verstuurd, of de pagina Memory is open) en dertig minuten daarna.
- Het proces houdt zich aan ongeveer twintig verzoeken per minuut, zodat er onder de limiet per account van de synchronisatiedienst ruimte blijft voor andere apparaten.
- Een
429pauzeert elke map tot het tijdstip in deRetry-After, en de pagina meldt dat er te vaak werd gesynchroniseerd en dat het opnieuw wordt geprobeerd, in plaats van de configuratie de schuld te geven.
立即同步 draait altijd meteen.
Wat de pagina toont
De schakelaar, „last synced” en fouten gelden voor beide bereiken: synchronisatie telt alleen als aan als die voor beide aan staat, de getoonde tijd is de oudste van de twee, en elke fout zegt of hij over projectnotities of over je eigen notities gaat. De pagina toont ook wat de laatste pull binnenhaalde (toegevoegd, gewijzigd, verwijderd) en, bij een gratis abonnement, de limieten van het abonnement.
Notities opruimen
De lijst 待整理 op de pagina is een controle die op deze machine draait, zonder model:
| Bevinding | Betekenis | Wat je kunt doen |
|---|---|---|
| Zelfde onderwerp | Twee tot zes notities in één bereik waarvan samenvattingen en tags grotendeels overlappen | Samenvoegen, of negeren |
| Kapotte link | Een [[id]] die geen notitie noemt | De notitie openen, of negeren |
| Ontbrekend bestand | Een projectnotitie noemt een relatief pad dat niet in het project staat | Openen, of negeren |
| Ongebruikt | Niet teruggezocht en niet bewerkt gedurende memory.consolidate.unusedDays (90) | Openen, of negeren |
| Te lang | Een tekst van meer dan 8.000 tekens | Openen, of negeren |
Een genegeerde bevinding wordt lokaal bijgehouden tegen de huidige versies van de notities, dus als je een ervan bewerkt, komt de bevinding terug.
Samenvoegen opent een editor met een concept: dat van het kleine model (生成合并建议) of alle originelen achter elkaar (手动合并). Toepassen behoudt de id van de oudste notitie, verwijdert de andere, laat elke [[deleted]]-link in dat bereik naar de behouden notitie wijzen, en maakt een back-up van elke notitie die het wijzigt. Een samenvoeging die is gepland op basis van notities die sindsdien zijn veranderd, wordt geweigerd.
Met cloudsynchronisatie aan wordt het bereik eerst gesynchroniseerd en wordt de samenvoeging eerst in de cloud vastgelegd en daarna op deze machine (commitFirst). Een tussentijdse bewerking vanaf een ander apparaat weigert dan de samenvoeging in plaats van een conflict te worden. De samengevoegde tekst moet binnen de limiet per notitie van de synchronisatiedienst passen. Met synchronisatie uit is de samenvoeging een gewone lokale bewerking.
Samenvoegen op de achtergrond staat standaard alleen aan voor betaalde abonnementen ("auto": "subscribers"): het draait als het laatste antwoord van de synchronisatiedienst zei dat het account, of een team waar het bij hoort, een betaald abonnement heeft, en blijft uit voor gratis accounts en zolang het abonnement onbekend is (cloudsynchronisatie uit, uitgelogd). true of false overschrijft dat:
{ "memory": { "consolidate": { "auto": false, "cooldownHours": 24, "maxMerges": 3 } } }
Als het aan staat, begint een ronde twee minuten na het opstarten, hooguit één keer per afkoelperiode, en voegt die met het kleine model tot maxMerges groepen notities over hetzelfde onderwerp samen, via dezelfde route als de pagina. Op de andere bevindingen handelt het nooit. Een projectconfiguratie kan het uitzetten, maar niet aanzetten.
Back-ups en herstellen
Er wordt een kopie naar ~/.oharness/memory-backups/ geschreven voordat een notitie wordt vervangen of verwijderd zonder dat iemand die versie koos: verwijderen op de pagina, forget, de cloudkant van een conflict kiezen, een automatische pull die een notitie wijzigt of verwijdert, een herschrijving door de extractor, en een herstel dat een nieuwere versie vervangt. De pagina Memory toont de laatste vijftig met wanneer en waarom, en herstelt elk ervan in de map waar het vandaan kwam; de notitie synchroniseert daarna als elke andere lokale wijziging. Back-ups die zijn geschreven voordat redenen werden vastgelegd, worden ook getoond en zijn ook te herstellen.
Opslag en afstemming
De synchronisatieserver biedt geauthenticeerde /v1/memory/identity, /snapshot en /commit. Hij slaat geen databaserijen op. R2 bevat onveranderlijke Markdown-revisies plus een JSON-manifest dat atomair wordt vervangen:
users/<server-verified-user-id>/projects/<project-id>/memory/
users/<server-verified-user-id>/memory/ user scope
manifest.json
objects/<note-id>/<revision-id>.md
Implementaties met een ander identiteits- of opslagsysteem zijn toegestaan; de exacte vorm van verzoeken, het hash-algoritme, het CAS-gedrag en de foutcodes staan in Synchronisatieprotocol.
De lokale basislijn (~/.oharness/memory-sync/) maakt driewegafstemming mogelijk: ongewijzigde notities worden overgeslagen en eenzijdige wijzigingen worden gekopieerd. Daarnaast bewaart de client een kopie van elke notitie zoals die het laatst is gesynchroniseerd, zodat een notitie die aan beide kanten is gewijzigd wordt samengevoegd in plaats van alleen gemarkeerd:
- de samenvatting en het type komen van de kant die ze heeft gewijzigd;
- tags worden als verzamelingen gecombineerd: wat een van beide kanten toevoegde, blijft, en wat een van beide kanten verwijderde, valt weg;
- teksten worden per regel samengevoegd, en bewerkingen die uit elkaar liggen, worden gecombineerd;
- een bewerking tegenover een verwijdering behoudt de bewerking, want opnieuw verwijderen is één klik en een verloren bewerking is weg.
Van beide versies wordt een back-up gemaakt vóór een samenvoeging. Wat niet te combineren is (twee verschillende samenvattingen of typen, bewerkingen aan dezelfde of aangrenzende regels, een samenvoeging die te groot is voor het abonnement, of een notitie zonder kopie van haar laatst gesynchroniseerde versie), wordt een conflict. Een conflict houdt alleen zijn eigen notitie tegen: al het andere wordt nog steeds geüpload en gedownload. De pagina Memory toont de laatst gesynchroniseerde versie, die van deze machine en die van de cloud, en biedt aan om die van deze machine te houden, die van de cloud te nemen, beide te houden (die van de cloud wordt een nieuwe notitie ernaast), of met de hand samen te voegen; wat opzij wordt gezet, krijgt een back-up, van welke kant het ook kwam. Er is geen overschrijven op basis van „laatste schrijfactie wint”. Verwijderingen zijn tombstones, en schrijfacties naar het manifest gebruiken ETag-compare-and-swap.
Batchwijzigingen die niemand notitie voor notitie beoordeelt (consolidatie), worden eerst in de cloud geschreven: elke betrokken notitie moet volledig gesynchroniseerd zijn, de commit bevat de manifestversie die is gecontroleerd, en pas als de dienst die accepteert, wordt de batch lokaal geschreven. Een tussentijdse wijziging vanaf een ander apparaat wordt door de dienst geweigerd in plaats van een conflict te worden.
Verzoeken noemen de hashes van de notities die deze machine heeft (have); de server leest die teksten niet en geeft ze niet terug, dus een synchronisatie waarbij weinig is veranderd, verstuurt weinig. Commits worden opgesplitst volgens het wijzigingsquotum dat de server aankondigt. Tombstones die ouder zijn dan het venster van de server, worden uit het manifest verwijderd. Een machine die daarna terugkomt en een notitie die ze het laatst gesynchroniseerd en ongewijzigd zag niet meer in de lijst vindt, verwijdert die notitie; een notitie die ze intussen heeft bewerkt, wordt een conflict. Twee notitie-id's die alleen in hoofdletters verschillen, worden geweigerd, omdat de bestandssystemen van macOS en Windows ze niet allebei kunnen bevatten.
Synchronisatie probeert tijdelijke fouten opnieuw na een afkoelperiode van 60 seconden; handmatige synchronisatie kan meteen opnieuw proberen. Conflicten wachten op de gebruiker. De pagina maakt back-ups van verwijderde notities, en van de verliezende lokale versies van conflicten, in ~/.oharness/memory-backups/; forget doet hetzelfde. Direct bewerken in het bestandssysteem blijft ondersteund, maar een gelijktijdige externe schrijfactie tijdens een samenvoeging wordt niet beschermd door de proceslokale schrijfvergrendeling; houd één draaiend proces per map aan.
R2 is privé en gebruikt TLS, maar dit is geen end-to-end-versleuteling: Cloud kan de inhoud van notities lezen. Alleen notities worden gesynchroniseerd, geen sessielogs, bestanden of credentials.
Quota en abonnementen
De server bepaalt zijn quota en kondigt ze in elk antwoord aan; clients houden geen kopie bij. Standaardwaarden: 5000 actieve notities per project en per gebruiker, 500 wijzigingen per commit, 128 KiB per tekst, 16 MiB aan teksten per antwoord, 90 dagen per tombstone. Een verlaagd quotum blokkeert alleen groei, dus een gebruiker die erboven zit, kan altijd verwijderen. Zie Je eigen synchronisatieserver draaien voor de variabelen.
Voor gratis accounts gelden daarbovenop strengere limieten: een aantal notities per project en per gebruiker, en een grootte per notitie, vastgelegd in src/memory-entitlements.json van de synchronisatiedienst. Betaalde accounts hebben geen abonnementslimiet, alleen het quotum hierboven. Het abonnement wordt gelezen uit de ent-claim die Cloud in het access-token schrijft: een eigen betaald abonnement van de gebruiker, of dat van een team waar die in zit, telt als betaald. Alleen cloudsynchronisatie is begrensd; notities op de machine niet. Een gratis account boven de limiet houdt elke lokale notitie en stopt met het uploaden van nieuwe, met een melding die uitlegt waarom.
Een account verwijderen
Een account verwijderen op de website wist alles wat het heeft gesynchroniseerd: sessiefeeds en geheugen in beide bereiken. De gateway roept POST /v1/admin/erase van de synchronisatiedienst aan met een sleutel die alleen hij heeft (OHARNESS_SYNC_ADMIN_KEY aan beide kanten), voordat het account zelf wordt verwijderd, en daarna nog een keer. De synchronisatiedienst schrijft eerst een markering voor het account en weigert vanaf dan zijn feed- en geheugenverzoeken, zodat een apparaat dat nog is ingelogd de data niet opnieuw kan uploaden. Notities en sessies op je eigen machines worden niet aangeraakt.
Een team verwijderen doet hetzelfde voor de gedeelde sessiegeschiedenis van het team: de gateway wist feed/team-<id>/ (de {team}-wisactie van de synchronisatiedienst) nadat het abonnement van het team is opgezegd en voordat de rijen ervan worden verwijderd, en daarna nog een keer. De synchronisatiedienst weigert vanaf dan de feeds van dat team, zelfs voor een token dat het nog noemt. Teams hebben geen geheugen.
Upgraden
Upgrade de synchronisatieserver vóór de clients. Een nieuwe client valt tegen een oude server terug op het oude protocol voor projectnotities zonder type. Hij kan notities op gebruikersniveau, of notities met een type, niet met een oude server synchroniseren. Een oude client die een notitie met type uit de cloud ontvangt, stopt met synchroniseren met een checksumfout totdat hij is geüpgraded.
Verificatie en deployment
Draai npm run test:memory, npm run test:sync-memory, npm run test:plugins, npm run typecheck en npm run build. script/memory-ui-demo.ts start een geïsoleerde, uitgelogde UI op 4319 met een tijdelijke homemap; hij leest nooit de credentials van de gebruiker. Ontbrekende R2-configuratie geeft een zichtbare synchronisatiefout zonder het lokale geheugen te breken. Herstart de lokale dev-server na wijzigingen in de broncode.