Un agente IA senza memoria durevole è un tirocinante brillante ma amnesico. Consegna una correzione pulita lunedì, dimentica la codebase entro martedì, e reintroduce la stessa regressione mercoledì. In CodeCourier abbiamo continuato a sbattere contro quel muro - finché non abbiamo smesso di trattare la memoria come una feature e iniziato a trattarla come il substrato centrale. Questo articolo è l'approfondimento ingegneristico su come funziona il nostro livello Contexts: la strategia di embedding, la pipeline di retrieval ibrido, il modello di scoping, il framework di eval, e le lezioni sorprendenti che abbiamo raccolto lungo la strada.
Lo sto scrivendo da builder a builder. Se stai progettando una memoria per un agente IA per un agente di coding, un agente di supporto, o un copilota interno, le stesse domande architetturali ti tenderanno un'imboscata. Le nomineremo, e mostreremo le risposte che abbiamo spedito in produzione.
1. Cos'è il contesto durevole per un agente IA?
Il contesto durevole di un agente è la conoscenza persistente, recuperabile, e delimitata che sopravvive attraverso le esecuzioni dell'agente e viene iniettata selettivamente nella finestra di contesto di un modello al momento dell'inferenza. Diversamente dal buffer di conversazione (che muore quando la sessione finisce) e diversamente dal fine-tuning (che congela la conoscenza nei pesi), il contesto durevole è uno stato caldo, modificabile, e indirizzabile che un agente può leggere, citare, e aggiornare senza riaddestramento.
In CodeCourier, un Context è l'unità di questa memoria durevole. Concretamente, è un pacchetto versionato di frammenti - paragrafi, esempi di codice, ADR, runbook, riassunti di Issue Sessions passate, policy di sicurezza - indirizzabili per ID, delimitati a un'organizzazione, un repository, una persona, o una sessione, e indicizzati per il retrieval ibrido (lessicale più semantico, con un reranker). Quando una nuova Issue Session parte dentro una sandbox, i Contexts giusti vengono selezionati, classificati, compressi, e fissati al prompt automaticamente.
L'abbreviazione che usiamo internamente: i pesi sono per le competenze, il contesto è per i fatti. Se la conoscenza cambia più velocemente della tua prossima esecuzione di addestramento, appartiene al livello di contesto.
2. Perché il RAG ingenuo si rompe per gli agenti di ingegneria
La ricetta ingenua - spezzettare tutto, farne l'embedding, memorizzare i vettori, fare una ricerca coseno top-K, aggiungere al prompt - funziona bene per un chatbot di documenti di marketing. Collassa rapidamente per un agente di ingegneria. Cinque modalità di fallimento che abbiamo misurato sul campo:
- Deriva semantica su query vicine al codice. Gli embedding densi valutano "authentication" e authorization come quasi-gemelli. L'agente recupera la policy sbagliata e scrive con sicurezza un middleware rotto. Su un golden set, il retrieval coseno ingenuo ha ottenuto recall@10 = 0,61 su query di autenticazione ambigue; la nostra pipeline ibrida l'ha portato a 0,87.
- I confini dei chunk sminuzzano la semantica. Un chunk da 512 token taglia attraverso un corpo di funzione, lasciando l'agente con la seconda metà di una transazione SQL e nessuna idea di cosa venisse annullato.
- Il top-K è opaco. Quando un agente allucina, serve un postmortem. La ricerca vettoriale pura non lascia alcuna traccia di audit oltre a "questi otto vettori erano i più vicini" - il che è infalsificabile e irreparabile.
- Nessuna sicurezza di scope. Gli embedding di un tenant trapelano nel retrieval di un altro tenant perché l'indice è globale. Il CISO lo scopre. Passi una brutta settimana.
- Marciume di recency. L'embedding di un ADR vecchio di sei mesi è altrettanto "vicino" di quello scritto ieri che l'ha sostituito. Senza segnali di freschezza, l'agente cita il cadavere.
Il RAG ingenuo è una demo. Una vera architettura RAG per agenti ha bisogno di retrieval ibrido, scoping strutturato, freschezza, citazioni, e un ciclo di eval che catturi le regressioni prima che vengano spedite.
3. La nostra architettura a colpo d'occhio
Immagina tre corsie verticali. A sinistra, una corsia di ingestione ingerisce i documenti sorgente - markdown, codice, trascrizioni, output di agenti precedenti - ed emette frammenti normalizzati. Al centro, una corsia di indicizzazione scrive quei frammenti in un indice invertito BM25 e in un indice vettoriale denso in parallelo, con ID di frammento condivisi. A destra, una corsia di retrieval serve le query dell'agente: dispiega ricerche lessicali e dense, fonde i candidati, li riclassifica con un cross-encoder, e restituisce un piccolo pacchetto citabile che sta nel budget del prompt.
End-to-end, la pipeline gira in sette fasi ordinate:
- Normalizza - rimuovi il rumore, canonizza gli spazi bianchi, rileva la lingua, allega i metadati sorgente (repo, path, author, timestamp).
- Dividi - chunking consapevole del codice con sovrapposizione; usiamo i confini AST per il codice, i confini di intestazione per la prosa.
- Embed - genera vettori densi per frammento; produci anche una firma lessicale sparsa.
- Indicizza - scrivi nel dense store (HNSW) e nello shard BM25, indicizzato per ID di frammento, con tag di scope.
- Recupera - per ogni query, esegui BM25 top-100 e denso top-100 in parallelo.
- Fondi e riclassifica - Reciprocal Rank Fusion fonde, poi un reranker cross-encoder valuta i top-50.
- Componi - applica filtri di scope, boost di freschezza, grafo delle citazioni, fissaggi manuali; comprimi e fissa al prompt dell'agente.
Ogni fase è osservabile indipendentemente. Ogni frammento che finisce in un prompt è tracciabile fino al documento sorgente, alla versione del chunker, alla versione del modello di embedding, e al punteggio di reranking. Questa verificabilità è ciò che rende il sistema debuggabile in produzione - ed è ciò che manca alle implementazioni RAG ingenue al primo giorno.
4. Strategia di embedding - modello, chunking, dedup
Tre decisioni dominano la qualità dell'embedding: quale modello, come dividere, e come deduplicare.
Scelta del modello
Usiamo un embedding generico da 1024 dimensioni per la prosa e un embedding specializzato per il codice per i file sorgente, memorizzati in namespace separati. Mescolare le modalità in un unico namespace ha degradato il recall di circa 9 punti sulla nostra eval interna; l'embedding del codice avvicina il contenuto a forma di funzione nel proprio spazio, mentre il modello di prosa gestisce meglio gli ADR e le policy. Rifacciamo l'embedding a ogni aggiornamento del modello e manteniamo la generazione precedente attiva durante una finestra ombra di 14 giorni.
Chunking consapevole del codice
Le finestre generiche da 512 token sminuzzano le funzioni. Dividiamo il codice lungo i confini AST - funzione, classe, istruzione di primo livello - e alleghiamo un'intestazione che porta il percorso del file e il simbolo che lo racchiude. La prosa si divide sulla gerarchia delle intestazioni. Entrambe le modalità mantengono una sovrapposizione di 64 token per preservare la semantica che attraversa i confini. La dimensione media del frammento si attesta a 320 token; il p95 a 780.
Dedup e frammenti canonici
I corpus di ingegneria sono pieni di quasi-duplicati: lo stesso esempio di codice incollato in tre runbook, lo stesso paragrafo rispecchiato da un wiki in un README. Calcoliamo una firma SimHash per frammento e collassiamo i quasi-duplicati (distanza di Hamming ≤ 3) in un unico frammento canonico con più puntatori sorgente. Questo ha ridotto il nostro indice del 34% su un tenant rappresentativo e ha migliorato la precisione del reranker perché il cross-encoder non spreca più capacità sui gemelli.
5. Retrieval ibrido - BM25 + denso + reranker
Il retrieval ibrido è il muro portante di una seria base di conoscenza di un agente. Gli embedding densi catturano la parafrasi; BM25 cattura gli identificatori esatti - nomi di funzioni, codici di errore, colonne di tabelle. Nessuno dei due da solo basta.
La nostra chiamata di retrieval, in pseudo-schema:
POST /contexts/retrieve
{
"query": "why does the rate limiter drop requests on burst?",
"scope": {
"org_id": "org_7Hk2",
"repo_id": "repo_courier-api",
"persona_id": "persona_backend-sre",
"session_id": "iss_2026-03-19-A91"
},
"budget_tokens": 3200,
"k_lexical": 100,
"k_dense": 100,
"rerank_top": 50,
"freshness_halflife_days": 90,
"min_score": 0.42
}
200 OK
{
"fragments": [
{
"id": "frag_8f21",
"doc_id": "adr_rate-limit-v4",
"version": 4,
"score": 0.913,
"tokens": 412,
"source": "runbooks/rate-limit.md#burst",
"updated_at": "2026-02-11T08:14:00Z"
}
],
"trace_id": "trc_8c1d"
}
Numeri a cui ci teniamo in produzione:
- recall@10 = 0,87 sul golden set di ingegneria (1.200 query etichettate), rispetto a 0,61 con solo il coseno.
- MRR@10 = 0,74 dopo il reranking cross-encoder.
- Latenza di retrieval p50 = 88 ms, p95 = 240 ms per un budget ≤ 4k token, reranking incluso.
- Tasso di allucinazione sulla risposta finale dell'agente (giudicato da un LLM-giudice con controlli a campione umani) è sceso dal 14,1% al 3,2% dopo che il reranker è stato spedito.
Il reranker è un piccolo cross-encoder. Valuta 50 candidati e teniamo ciò che sta nel budget di token dopo la compressione. La compressione è stupida di proposito - eliminiamo le intestazioni boilerplate, teniamo il percorso di intestazione, e non parafrasiamo mai il corpo. Gli agenti citano testo esatto o non citano nulla.
6. Scoping e permessi - repo, org, persona, sessione
La sicurezza dello scope non è negoziabile. Un singolo indice di embedding che serve più tenant è un incidente di esfiltrazione dati in attesa di un invito in calendario. Alleghiamo tag di scope a ogni frammento al momento della scrittura e li applichiamo al momento della query, non tramite post-filtraggio ma tramite partizionamento dell'indice al momento della query.
Quattro scope, valutati dal basso verso l'alto:
- Sessione - frammenti effimeri prodotti durante l'Issue Session corrente, visibili solo dentro quella sessione.
- Persona - frammenti legati a una Persona specifica (es. SRE backend, revisore di accessibilità frontend); orientano il retrieval verso il dominio della persona.
- Repository - conoscenza legata al codice, delimitata a un repo ed eredita la policy di accesso di quel repo.
- Organizzazione - conoscenza trasversale ai repo come policy di sicurezza e voce del brand, controllata dall'appartenenza all'organizzazione.
Al momento del retrieval, l'insieme di ACL del principal richiedente viene intersecato con l'insieme di scope di ogni frammento candidato. Un frammento senza sovrapposizione non viene nemmeno valutato. Trattiamo lo scope come una proprietà di correttezza, non come un feature flag, e lo testiamo come testiamo il codice di autenticazione. La disciplina è documentata nella nostra panoramica sulla sicurezza.
7. Ranking e freschezza - recency, citazioni, fissaggi
La rilevanza è una funzione di similarità, recency, autorevolezza, e intento dell'operatore. Il ranker finale è una miscela pesata:
score(f) = w_r * rerank(f, q)
+ w_f * exp(-age_days(f) / halflife)
+ w_c * citation_pagerank(f)
+ w_p * is_pinned(f, scope)
I pesi sono regolati per persona. I Contexts di un revisore di sicurezza pendono verso l'autorevolezza (grafo delle citazioni e fissaggi); un agente frontend che corregge un issue aperto pende verso la freschezza. Il grafo delle citazioni è costruito offline: quando un frammento collega o è citato da altri frammenti, accumula un'autorevolezza simile a PageRank. I fissaggi manuali vincono sempre - un tech lead può fissare un frammento in cima a uno scope, e il sistema obbedisce senza obiezioni.
La recency usa un decadimento a emivita, non un taglio netto. Un ADR vecchio di due anni può ancora emergere se non esiste nulla di più fresco; un ADR di una settimana sullo stesso argomento semplicemente domina. L'emivita è configurabile; il default è 90 giorni per i contenuti di ingegneria e 365 giorni per le policy.
8. Framework di eval - come misuriamo la qualità del retrieval
La qualità del retrieval è l'unico numero che conta, ed è il più facile con cui ingannare se stessi. Il nostro framework di eval poggia su tre livelli, ciascuno più costoso e più onesto del precedente.
- Golden set. 1.200 query etichettate con frammenti ideali curati da umani. Eseguito a ogni modifica della pipeline. Recall@k, MRR@k, nDCG@k, più suddivisioni per scope.
- Replay di regressione. Query di produzione reali (anonimizzate e con consenso) riprodotte contro la pipeline candidata; confrontiamo i frammenti recuperati e segnaliamo ogni query il cui top-3 cambia oltre una soglia di sovrapposizione di token.
- Eval di agente end-to-end. Esecuzioni di agente complete in sandbox isolate contro task valutati; giudichiamo gli output finali con un LLM-giudice e controlli a campione umani. Questo è l'unico livello che misura ciò che effettivamente spediamo.
Ogni PR che tocca il percorso di retrieval deve battere la pipeline precedente sul golden set o atterrare con una deroga scritta. La maggior parte dei team di ingegneria investe troppo poco qui. Abbiamo spedito versioni che sembravano ottime in una demo e hanno perso tre punti di recall in produzione. Non le spediamo più.
9. Decisioni di design sorprendenti
Scrivi meno, collega di più
I clienti hanno provato a scaricare interi export Notion e interi archivi Slack nei Contexts. Il recall è sceso. Il reranker è annegato nei quasi-duplicati e l'agente si è distratto. Abbiamo riscritto l'interfaccia per imporre frammenti minimi - la più piccola unità che cattura una decisione - e aggiunto la risoluzione dei link su richiesta. La lunghezza mediana del frammento è scesa da 1.400 token a 320. Il tasso di allucinazione è sceso con essa.
La struttura batte la prosa
I frammenti scritti come elenchi puntati di decisioni superano la stessa informazione resa come prosa scorrevole. La nostra ipotesi: l'agente tratta la struttura come impalcatura economica da analizzare e spende più attenzione sul contenuto. Incorporiamo questo nelle linee guida di scrittura nelle nostre guide.
Gli embedding sono un fattore dirimente, non un segnale primario
Controintuitivo per chiunque sia cresciuto con il RAG vettoriale puro, ma BM25 + filtraggio di scope fa la maggior parte del lavoro pesante. L'embedding denso dirime i pareggi e salva le query di parafrasi. Se dovessimo spedirne uno senza l'altro, terremmo BM25.
I contesti negativi funzionano
Abbiamo aggiunto frammenti negativi - anti-pattern espliciti e tranelli noti - e visto un calo misurabile nelle regressioni ripetute. Su una classe di bug di un cliente (retry silenziosi che mascheravano errori 5xx), gli incidenti ripetuti sono scesi del 41% dopo che il frammento di anti-pattern è stato fissato allo scope del repo.
Proprietari o marciume
Ogni frammento ha un proprietario umano nominato. Quando il sistema segnala un frammento come obsoleto - referenziato spesso ma non modificato in sei mesi - il proprietario riceve una sollecitazione nel suo workflow. Senza proprietari, l'indice degenera in documentazione zombie; con i proprietari, si accumula.
10. RAG ingenuo vs Contexts - confronto
| Dimensione | RAG ingenuo | CodeCourier Contexts |
|---|---|---|
| Retrieval | Solo coseno denso | BM25 + denso + reranking cross-encoder |
| recall@10 (golden set) | 0,61 | 0,87 |
| Latenza p95 | ~120 ms (senza reranking) | ~240 ms (con reranking) |
| Tasso di allucinazione (risposta finale) | 14,1% | 3,2% |
| Sicurezza dello scope | Post-filtro (con perdite) | Partizionamento dell'indice + intersezione ACL |
| Freschezza | Nessuna | Decadimento a emivita + fissaggi manuali |
| Verificabilità | Solo ID vettore | ID frammento + versione + sorgente + punteggio reranking |
| Disciplina di scrittura | Scarica e prega | Frammenti minimi + proprietari + versioni |
I vantaggi si accumulano. Una pipeline di retrieval più veloce e più precisa rende il prompt più piccolo, il che abbassa il costo di inferenza e la latenza di coda, il che ci permette di eseguire più passaggi per Issue Session, il che produce output di agente migliori, che rifluiscono nei Contexts come frammenti canonici.
11. Domande aperte e cosa arriva dopo
Non abbiamo finito. Tre fili sono attivamente in corso. I Contexts cross-team - permettere a un team di piattaforma di pubblicare un Context a cui i team a valle si abbonano senza forkare una copia - sono la feature più richiesta nella coda e la più difficile da azzeccare senza rompere le garanzie di scope. Il reranking condizionato dalla persona usa la Persona attiva come feature aggiuntiva del reranker, così la stessa query restituisce frammenti diversi per un SRE e un revisore di accessibilità. Il retrieval consapevole del workflow lega i Contexts al workflow builder così che un passaggio che modifica codice Stripe tiri automaticamente il runbook dei pagamenti senza che nessuno lo colleghi.
Se vuoi uno sguardo più approfondito sulla piattaforma stessa, la homepage di CodeCourier è il tour più veloce, e documentiamo pattern pratici nel blog di ingegneria. Se stai costruendo il tuo livello di memoria per un agente e vuoi confrontare gli appunti, mettiti in contatto tramite contact.
12. FAQ
Che cos'è la memoria di un agente IA?
La memoria di un agente IA è la conoscenza persistente e recuperabile che un agente usa attraverso le sessioni. È distinta dal buffer di conversazione (che è effimero) e dai pesi del modello (che sono statici). La memoria pratica di un agente combina il contesto durevole (fatti, decisioni, codice), il retrieval (BM25 + denso + rerank), e un modello di scoping che controlla chi vede cosa.
Perché non fare semplicemente il fine-tuning del modello sulla nostra codebase?
Il fine-tuning cuoce la conoscenza nei pesi, che è il posto sbagliato per fatti che cambiano ogni settimana. È anche costoso, lento da iterare, difficile da verificare, e impossibile da delimitare per tenant. Il contesto durevole vince su costo, freschezza, e governance. Usa il fine-tuning per le competenze (stile, formato, comportamento), non per i fatti.
Perché il retrieval ibrido è migliore della ricerca vettoriale pura?
Gli embedding densi eccellono nella parafrasi ma mancano gli identificatori esatti; BM25 azzecca gli identificatori ma manca la parafrasi. Fonderli con Reciprocal Rank Fusion e un reranker cross-encoder ha portato il nostro recall@10 da 0,61 a 0,87 su un golden set di ingegneria etichettato.
Quanto dovrebbe essere grande un frammento di contesto?
Più piccolo di quanto pensi. La nostra mediana è 320 token, il p95 è 780. I frammenti piccoli si riclassificano in modo più accurato, si compongono meglio, e lasciano più budget per il ragionamento proprio dell'agente.
Come impedite ai tenant di sovrapporsi tra loro?
Lo scope è applicato al momento della query tramite partizionamento dell'indice e intersezione degli ACL - non tramite post-filtraggio. I frammenti senza sovrapposizione di scope con il principal richiedente non vengono mai valutati. Testiamo lo scope come testiamo il codice di autenticazione.
Come misurate la qualità del retrieval?
Tre livelli: un golden set di 1.200 query con recall, MRR, e nDCG; replay di query di produzione anonimizzate con rilevamento di diff; ed esecuzioni di agente end-to-end valutate da un LLM-giudice con controlli a campione umani. Ogni PR che tocca il percorso di retrieval deve battere la pipeline precedente o atterrare con una deroga esplicita.
Contano di più gli embedding o BM25?
BM25 ha più peso nel nostro sistema di quanto la maggior parte dei team si aspetti. Se dovessimo spedirne uno solo, spediremmo BM25 + scoping. L'embedding denso si guadagna il posto sulla parafrasi e nel dirimere tra codice e prosa.
Cos'è un contesto negativo, e perché aiuta?
Un contesto negativo è un frammento che codifica un anti-pattern o un tranello noto. Fissato a uno scope, dice all'agente cosa non fare. Abbiamo misurato cali del 30-40% nelle regressioni ripetute su specifiche classi di bug dopo aver fissato il frammento di anti-pattern giusto.
Il livello di contesto è la parte di una piattaforma di agenti che trasforma l'ingegnosità estemporanea in intelligenza istituzionale che si accumula. Costruiscilo come infrastruttura - versionato, delimitato, con proprietari, e piccolo - e i tuoi agenti smettono di essere tirocinanti amnesici.