opencode-memory: Memoria Persistente Locale per OpenCode
Come ho costruito un plugin di memoria persistente per OpenCode con ciclo di vita WRITE → DREAM → SURFACE, storage locale JSON senza database esterni e retrieval ibrido con benchmark su 90 casi.
Il problema
Gli AI agent ricominciano da zero a ogni sessione: il contesto della conversazione precedente è sparito. Le preferenze comunicate una volta vanno ripetute, le decisioni prese vanno ridiscusse, e le informazioni scoperte con fatica vengono perse.
Le soluzioni "classiche" richiedono un database vettoriale, embeddings, un servizio cloud. Volevo qualcosa di più radicale: memoria persistente locale, senza database esterni, senza embeddings obbligatori — un file JSON che sopravvive alle sessioni e un ciclo di vita chiaro che decide cosa ricordare, cosa consolidare e cosa iniettare nei prompt.
Nasce così opencode-memory, un plugin open source per OpenCode.
Il ciclo di vita: WRITE → DREAM → SURFACE
Il plugin organizza la memoria in tre fasi:
WRITE → DREAM → SURFACE
1. WRITE — memoria esplicita
L'agente (o l'utente) scrive fatti in modo esplicito tramite memory_write, con metadati che governano la durata e la visibilità:
- tier:
core(sempre iniettata),archival(su rilevanza),temporary(auto-expiry),pinned(mai soggetta a decay) - sensitivity:
local-onlyper fatti che non devono mai lasciare la macchina - ttlHours: scadenza automatica
Ogni fatto esplicito ha una provenienza auditabile: so chi l'ha scritto, quando, e con quale confidenza.
2. DREAM — consolidamento headless
Il pezzo più interessante. Quando una sessione resta inattiva, il plugin avvia una sessione figlia headless che rilegge la conversazione e ne estrae i fatti impliciti — ciò che l'utente ha deciso, le sue preferenze, le informazioni apprese.
Il consolidamento è robusto:
- Orphan GC: sessioni figlie abbandonate vengono ripulite automaticamente
- Crash recovery: un marcatore
inProgressscade dopo un timeout e la sessione riparte pulita - Deduplicazione semantica: cross-language (IT/EN), evita duplicati tra fatti espliciti e sognati
Ogni fatto sognato sa da quale sessione e da quali messaggi proviene, con un punteggio di confidenza — niente fatti fantasma.
3. SURFACE — iniezione selettiva
Non tutta la memoria finisce nei prompt: quella sarebbe solo rumore. La SURFACE è relevance-gated:
- pipeline lessicale per candidati rilevanti rispetto alla query corrente
- slot riservati ai fatti
core(sempre iniettati, oltre i match) - feedback dall'uso reale:
memory_usefulememory_irrelevantaggiustano il ranking in base a ciò che funziona davvero
E c'è memory_why: per ogni fatto iniettato puoi chiedere perché è stato iniettato — punteggio base, match, bonus, rank finale.
Zero database esterni
Niente ChromaDB, niente Redis, niente servizi cloud. La memoria vive in JSON locale con file locking (~/.local/share/opencode/memory), in tre file:
store/— i fattistate/— stato operativo del ciclo di vitasummary/— statistiche e riepilogo
È una scelta architetturale voluta: la memoria deve essere leggibile, versionabile e portabile. Un file JSON lo puoi ispezionare, copiare, fare il backup con rsync. Un database no.
Retrieval ibrido: lessicale + rerank opzionale
Il retrieval di default è puramente lessicale: match di keyword con punteggio, niente embeddings. L'embedding semantico è un upgrade opzionale (OPENCODE_MEMORY_RERANK=1): una fase di rerank headless su una finestra di candidati (default 30) riordina i risultati con un LLM locale o remoto.
Il rerank è progettato per non rompere mai il flusso:
- timeout 4s → fallback all'ordine lessicale
- cache per query (60s) → nessun costo ripetuto
- il ranking lessicale resta il percorso di base, sempre disponibile
Contraddizioni, non sovrascritture
Quando un nuovo fatto contraddice uno esistente, il plugin non sovrascrive in silenzio: marca il fatto come CONFLICTED e lo segnala nel Memory Inspector (/memory). La risoluzione è esplicita: memory_update corregge il fatto precedente con piena tracciabilità.
I dati falsi o obsoleti non spariscono di nascosto — vengono flaggati, auditati e risolti.
I numeri del benchmark
La suite di benchmark (bun run bench) copre 90 casi: parafrasi, cross-lingua IT/EN, sinonimi, distrattori, contraddizioni, duplicati, isolamento per progetto, fatti obsoleti, falsi positivi e no-match.
Recall@5: 100.0% (memorie attese trovate / attese)
Precision@5: 20.8% (massimo raggiungibile per questa suite)
MRR (primo hit): 95.0%
False-surface: 0.0% (nessuna iniezione su casi "dovrebbe essere vuoto")
Context overhead: 13.8 token/query (media, surface 8)I numeri chiave per un plugin di memoria: 100% di recall — non perdere ciò che conta — e 0% di false-surface — non inquinare i prompt con rumore.
Privacy
La memoria è sempre locale: JSON + lockfile, nessun servizio cloud del plugin. Due note:
- Durante il DREAM la conversazione passa dal provider LLM configurato in OpenCode — se è remoto, il testo va a quel provider, esattamente come qualsiasi altra richiesta OpenCode.
- I fatti
local-onlynon entrano mai nel blocco di memoria dei prompt (quindi mai verso provider remoti) e non finiscono nei prompt di consolidamento: si leggono solo conmemory_read.
Se vuoi zero trasmissioni: OPENCODE_MEMORY_OFF=1.
Cosa ho imparato
- La memoria senza embeddings funziona. Il match lessicale ben fatto batte l'embedding mal configurato. Il rerank semantico è un'aggiunta, non un prerequisito.
- La provenienza è tutto. Sapere perché un fatto è in memoria (fonte, sessione, confidenza) trasforma un blob opaco in uno strumento fidato.
- Il ciclo di vita conta più dello storage. WRITE → DREAM → SURFACE è il vero design: dove salvi è un dettaglio, come decidi cosa ricordare e cosa iniettare è il prodotto.
- Il feedback dell'uso reale batte gli iperparametri.
memory_useful/memory_irrelevantregolano il ranking dai risultati veri, non da benchmark artificiali. - I conflitti vanno gestiti, non ignorati. Flag e risoluzione esplicita > sovrascrittura silenziosa, anche quando costa più codice.
Dove trovarlo
- GitHub: github.com/cioffiAI/opencode-memory
- npm:
@cioffi_ai/opencode-memory(installabile in~/.config/opencode/opencode.json) - Benchmark:
bun run bench
Il plugin è open source (MIT) e installabile in tre righe di configurazione. La prossima tappa è la 2.0: memoria condivisa e scoped per agente, graph memory con relazioni tra entità e sync multi-dispositivo cifrata.