Torna al blog
5 min di letturaopencode-memorypluginopencodeai-agentstypescriptmemoria

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-only per 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 inProgress scade 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_useful e memory_irrelevant aggiustano 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 fatti
  • state/ — stato operativo del ciclo di vita
  • summary/ — 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:

  1. 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.
  2. I fatti local-only non entrano mai nel blocco di memoria dei prompt (quindi mai verso provider remoti) e non finiscono nei prompt di consolidamento: si leggono solo con memory_read.

Se vuoi zero trasmissioni: OPENCODE_MEMORY_OFF=1.

Cosa ho imparato

  1. La memoria senza embeddings funziona. Il match lessicale ben fatto batte l'embedding mal configurato. Il rerank semantico è un'aggiunta, non un prerequisito.
  2. La provenienza è tutto. Sapere perché un fatto è in memoria (fonte, sessione, confidenza) trasforma un blob opaco in uno strumento fidato.
  3. 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.
  4. Il feedback dell'uso reale batte gli iperparametri. memory_useful / memory_irrelevant regolano il ranking dai risultati veri, non da benchmark artificiali.
  5. I conflitti vanno gestiti, non ignorati. Flag e risoluzione esplicita > sovrascrittura silenziosa, anche quando costa più codice.

Dove trovarlo

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.