Memoria degli agenti: tipi e costi di archiviazione
Confronta memoria semantica, episodica e procedurale in una tabella: dimensioni, costi di archiviazione e re-embedding su un VPS, con esempi pratici.
Cosa sono i tre tipi di memoria degli agenti
I tipi di memoria degli agenti si dividono in tre categorie, ognuna delle quali incide in modo diverso sull'hardware che paghi: la memoria semantica conserva i fatti, la memoria episodica conserva ciò che è accaduto e la memoria procedurale conserva il modo di eseguire un'attività. La tabella seguente definisce ciascun tipo usando un esempio relativo a un server. Tutto ciò che segue riguarda l'aspetto che di solito non viene documentato: quanto costa archiviare ogni tipo e quanto costa ricostruirlo.
The data behind this chart
[
{
"label": "Semantic",
"what_it_holds": "Facts the agent should treat as currently true",
"server_example": "The database listens on 10.8.0.4:5432 and the nightly dump runs at 03:15 UTC"
},
{
"label": "Episodic",
"what_it_holds": "A record of one past event or session",
"server_example": "On 2026-08-11 the deploy failed because the disk was full, and rotating logs fixed it"
},
{
"label": "Procedural",
"what_it_holds": "How to carry out a task, as steps the agent can run",
"server_example": "The restore runbook: stop the service, load the dump, run migrations, start the service"
}
]I nomi derivano dalla psicologia umana, ma l'analogia è approssimativa. La distinzione è utile per un motivo pratico: i tre tipi hanno dimensioni e procedure di ripristino diverse, quindi inserirli tutti nello stesso vector store peggiora il risultato per ciascuno di essi.
La memoria semantica è ridotta e sarà necessario modificarla manualmente
Alcune centinaia di fatti sui propri server corrispondono a poche decine di kilobyte di testo. Lo spazio di archiviazione non è il problema. Il problema è la correzione. Un fatto errato nella memoria semantica resta errato in ogni risposta successiva dell’agente. Pertanto, l’archivio deve consentire di trovare un fatto tramite il nome, modificarlo e verificare che il vecchio valore sia stato rimosso.
È quindi indicato usare un archivio con chiavi: una tabella PostgreSQL con una chiave primaria oppure una directory di piccoli file Markdown gestiti con git. Entrambe le soluzioni consentono di eseguire una query, visualizzare il valore e modificarlo direttamente. La ricerca per similarità non offre questa garanzia, perché recupera i contenuti in base alla somiglianza invece che alla chiave. “Modificare la porta del database” diventa “trovare ogni blocco che menziona la porta del database”, senza poter dimostrare di averli trovati tutti. Mantenere i fatti indicizzati tramite chiavi. È possibile anche generarne gli embedding per ottenere un recupero più ampio, ma la copia indicizzata tramite chiave deve essere considerata la fonte autorevole.
I fatti obsoleti non si segnalano da soli. La porta cambia, ma la riga resta invariata; di conseguenza, l’agente continua a rispondere con un numero che era corretto a giugno. Una policy di verifica dell’obsolescenza e rimozione per la memoria dell’agente è l’altra metà di questa pagina ed è molto più economico progettarla quando la tabella è ancora piccola.
Perché la memoria episodica cresce senza limiti
La memoria episodica è un log e i log crescono. Ogni sessione, ogni chiamata a uno strumento e ogni comando non riuscito possono diventare un episodio. Un agente che scrive una riga per turno produrrà in un mese molte più righe di quante chiunque leggerà mai. Inoltre, il disco non è l'unico costo: ogni episodio indicizzato entra anche nell'indice che la ricerca deve attraversare.
Stabilisci la regola di conservazione il giorno in cui crei la tabella, quando l'eliminazione è ancora gratuita. Due domande risolvono la maggior parte dei casi. Primo: che cosa vale la pena scrivere? Di solito il riepilogo di una sessione sì, mentre l'output completo di una singola ls -la di solito no. Secondo: per quanto tempo deve essere conservata ogni classe di episodi? Ad esempio, gli episodi grezzi per 30 giorni e i riepiloghi delle sessioni per un anno.
Assegna a ogni riga degli episodi un timestamp created_at e una colonna source. Senza created_at non puoi eliminare i dati in base all'età. Senza source non puoi eliminare tutto ciò che proviene da una singola origine problematica. È esattamente ciò che serve quando scopri che una pagina web o un ticket ha inserito istruzioni nella memoria.
DELETE FROM episodes WHERE created_at < now() - interval '30 days';Esegui questa operazione tramite un timer systemd, quindi verifica che il numero di righe e le dimensioni della tabella cambino effettivamente. Una politica di conservazione che nessuno applica è solo un commento.
psql -d agentmem -c "SELECT count(*) FROM episodes;"
psql -d agentmem -c "SELECT pg_size_pretty(pg_total_relation_size('episodes'));"La memoria procedurale appartiene a un repository
La memoria procedurale descrive come l’agente esegue un’attività: uno shell script, un file skill o un runbook con passaggi numerati. È codice e il codice deve risiedere dove viene gestito il codice: in un repository git con revisione, versioni e diff leggibili.
Se si archivia un runbook come blocchi incorporati, si ottiene in risposta una copia approssimativa. Il recupero restituisce i blocchi con il punteggio più alto, quindi l’agente può eseguire il passaggio 2 e il passaggio 5 mentre il passaggio 3 non viene mai restituito; inoltre, nulla registra quale versione della procedura è stata eseguita. In git, git log risponde a entrambe le domande. Il costo di archiviazione è quasi nullo: questo è l’altro motivo per cui non conviene pagare i costi dei vettori per questo tipo di contenuto.
Il costo effettivo di un embedding su disco
The data behind this chart
[
{
"label": "bge-small-en-v1.5 (384 dims)",
"bytes_per_vector": 1544,
"mib_per_100k_rows": 147.2
},
{
"label": "bge-base-en-v1.5 (768 dims)",
"bytes_per_vector": 3080,
"mib_per_100k_rows": 293.7
},
{
"label": "bge-large-en-v1.5 (1024 dims)",
"bytes_per_vector": 4104,
"mib_per_100k_rows": 391.4
},
{
"label": "text-embedding-3-small (1536 dims)",
"bytes_per_vector": 6152,
"mib_per_100k_rows": 586.7
},
{
"label": "text-embedding-3-large (3072 dims)",
"bytes_per_vector": 12296,
"mib_per_100k_rows": 1172.6
}
]pgvector memorizza un vector usando 4 byte per dimensione più un header di 8 byte. Questo calcolo è quindi fisso e consente di pianificare lo spazio necessario prima di caricare i dati. Un vettore di 384 dimensioni occupa 1544 byte. Di conseguenza, 100,000 chunk occupano 147.2 MiB per i soli vettori. Lo stesso corpus con embedding a 3,072 dimensioni occupa 1172.6 MiB, con 12296 byte per riga. Il testo è lo stesso, ma lo spazio occupato è quasi otto volte maggiore.
Questa è soltanto la colonna dei vettori. Il testo dei chunk, la chiave primaria, l'overhead delle righe e l'indice si aggiungono a questo spazio. L'indice è l'elemento che spesso viene dimenticato. HNSW (hierarchical navigable small world, l'indice a grafo creato da pgvector) conserva una propria copia dei vettori a cui fa riferimento. Un archivio indicizzato occupa quindi complessivamente molto più del doppio dei valori indicati sopra. Misura il tuo ambiente invece di fare stime.
SELECT pg_size_pretty(pg_total_relation_size('memories')) AS total,
pg_size_pretty(pg_relation_size('memories_embedding_idx')) AS idx;La RAM determina la velocità percepita della ricerca, perché il grafo può essere attraversato rapidamente soltanto quando risiede in memoria. Quando l'indice supera la quantità di memoria che Postgres può riservargli, le ricerche iniziano a leggere dal disco e la latenza aumenta. La compilazione ha un proprio limite, maintenance_work_mem. Quando il grafo lo supera, la compilazione lo segnala e rallenta:
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 61440 tuples
HINT: Increase maintenance_work_mem to speed up builds.Esistono due modi per ridurre lo spazio occupato dallo stesso corpus. Scegli un modello più piccolo: 384 dimensioni occupano un quarto dello spazio di 1,536 dimensioni e, per ritrovare le proprie note, la differenza di accuratezza è spesso abbastanza ridotta da risultare accettabile. Oppure usa la precisione dimezzata. Il tipo halfvec usa 2 byte per dimensione più lo stesso header di 8 byte. In questo modo riduce quasi della metà sia la colonna sia il relativo indice.
È importante conoscere un limite prima di scegliere il modello. Ad agosto 2026 una colonna vector può essere indicizzata fino a 2,000 dimensioni. Un embedding di 3,072 dimensioni viene quindi accettato dalla colonna ma rifiutato dall'indice:
ERROR: column cannot have more than 2000 dimensions for hnsw indexhalfvec supporta indici fino a 4,000 dimensioni. La soluzione abituale consiste quindi nell'indicizzare il cast:
CREATE INDEX ON memories USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);Quando pgvector è preferibile a un servizio di memoria separato
Se il server esegue già Postgres, i vettori richiedono un solo pacchetto e una sola istruzione.
psql -V
sudo apt install postgresql-16-pgvectorCREATE EXTENSION vector;Il numero nel nome del pacchetto corrisponde alla versione major di Postgres: su Ubuntu 24.04 è 16. Leggere quindi psql -V prima di inserirlo.
Conservare la memoria nello stesso database consente di avere un unico backup per memoria e dati dell'applicazione nello stesso momento, un unico pool di connessioni e transazioni atomiche: il fatto e la riga che lo descrive vengono sottoposti a commit insieme oppure falliscono insieme. Un servizio separato non può garantire questo comportamento.
Passare a un servizio di memoria dedicato quando si verifica una di queste condizioni: il carico di ricerca compete con l'applicazione e richiede un server separato; più agenti su più host condividono la stessa memoria; oppure si desidera la logica di estrazione e deduplicazione inclusa in un prodotto completo. Questo è il motivo per scegliere un server Mem0 self-hosted. Per un singolo agente e un corpus di pochi milioni di chunk, pgvector sul server già in uso richiede meno gestione e presenta meno punti di errore. La scelta del motore e i requisiti di RAM di ciascun motore sono descritti in eseguire un database vettoriale su un VPS.
Cosa comporta il re-embedding quando si cambia modello
I vettori prodotti da due modelli diversi non sono confrontabili. Non è quindi possibile incorporare i nuovi ricordi con un nuovo modello e lasciare inalterate le righe precedenti. Una tabella con dati misti restituisce risultati senza senso, perché una distanza calcolata tra due sistemi di coordinate diversi non ha alcun significato. Cambiare modello significa eseguire nuovamente l'embedding dell'intero corpus.
Il costo comprende quattro componenti: i token, che generano un addebito API oppure richiedono tempo CPU e GPU sul proprio server; il tempo effettivo di esecuzione; lo spazio su disco necessario per entrambe le colonne durante il backfill; e la ricostruzione dell'indice al termine dell'operazione. L'ordine sicuro è aggiungere una nuova colonna, riempirla in batch, modificare la query, quindi eliminare la colonna precedente e il relativo indice.
Misurate la velocità sul vostro hardware invece di fidarvi di un valore pubblicato, perché l'embedding eseguito solo sulla CPU su un piccolo VPS è molto più lento rispetto allo stesso modello eseguito su una GPU. Misurate il tempo necessario per un chunk rappresentativo, quindi moltiplicatelo per le dimensioni del corpus.
ollama pull nomic-embed-text
time curl -s http://localhost:11434/api/embed \
-d '{"model": "nomic-embed-text", "input": "one representative chunk of your corpus"}' > /dev/nullUn modello di embedding locale memorizza i propri pesi sullo stesso disco dell'archivio della memoria; dove Ollama memorizza i modelli scaricati spiega dove viene utilizzato questo spazio.
Tutto questo è possibile solo se il testo sorgente viene conservato accanto a ogni vettore. Un archivio che contiene soltanto vettori non può eseguire nuovamente l'embedding, perché non resta alcun contenuto da fornire al nuovo modello. Se non potete rispondere alla domanda «quale testo ha prodotto questa riga?», il percorso di migrazione consiste in una ricostruzione completa a partire dalla sorgente originale del testo.
Cosa monitorare quando la memoria è attiva
Il costo non è l'unico aspetto che cambia quando l'archivio si riempie. I fatti meno recenti diventano obsoleti e gli episodi più vecchi sottraggono spazio ai risultati utili: il problema del pruning si ripresenta. Un archivio di memoria è anche un input scrivibile per il comportamento futuro dell'agent, quindi qualsiasi componente autorizzato a scrivervi può orientare in seguito il comportamento dell'agent. Se il testo delle pagine Web o dei ticket viene salvato nella memoria, leggi come funziona il memory poisoning degli agent prima di ampliare ciò che può scrivervi. Anche la dimensione del retrieval determina il consumo di token per ogni singola richiesta; è da qui che comincia mantenere prevedibile il costo operativo dell'agent.
FAQ
È necessario un database vettoriale per la memoria dell’agente?
Non per i fatti. La memoria semantica è ridotta e deve essere corretta per nome. Per questo è più adatta una tabella indicizzata o una directory di file Markdown in git: puoi visualizzare un singolo valore e modificarlo. Gli embedding giustificano il costo quando devi recuperare contenuti in base al significato all’interno di un corpus troppo grande da elencare. Questo riguarda in genere la memoria episodica e i documenti. Se esegui già Postgres, CREATE EXTENSION vector copre questo caso senza aggiungere un altro servizio da gestire.
Quanto spazio su disco occupa un archivio di memoria dell’agente?
I vettori hanno dimensioni prevedibili: 4 byte per dimensione, più un’intestazione di 8 byte in pgvector. Con 768 dimensioni, sono 293.7 MiB ogni 100,000 righe; con 384 dimensioni, sono 147.2 MiB. Devi poi aggiungere il testo dei chunk, l’overhead delle righe e un indice HNSW, che mantiene una propria copia dei vettori. Prevedi quindi almeno il doppio dello spazio calcolato per i vettori e misura il valore reale con pg_total_relation_size.
Dove deve risiedere la memoria procedurale?
In un repository git, sotto forma di script o file di skill eseguiti direttamente dall’agente. Una procedura richiede un recupero esatto e una cronologia delle versioni. La ricerca per similarità non offre né l’uno né l’altra. Un runbook suddiviso in chunk restituisce i frammenti con il punteggio più alto. Può quindi restituire il passaggio 2 e il passaggio 5 mentre manca il passaggio 3. Inoltre, non viene registrata la versione eseguita.
Qual è il costo della modifica del modello di embedding?
È necessario ricalcolare gli embedding dell’intero corpus, perché i vettori generati da modelli diversi non possono essere confrontati tra loro. Devi prevedere il costo dei token o il tempo GPU, lo spazio su disco per mantenere contemporaneamente le colonne vecchie e nuove e la ricostruzione dell’indice. Aggiungi la nuova colonna, popolala in batch, modifica la query in modo che la utilizzi, quindi elimina la colonna precedente. Tutto questo richiede che il testo sorgente sia stato conservato accanto a ogni vettore.