---
title: "Avaliação de Memória: Medindo Como a Memória de IA se Degrada ao Longo da Vida de um Projeto"
url: https://tiarebalbi.com/pt-br/blog/memory-evaluation-ai-project-lifecycle
markdown: https://tiarebalbi.com/pt-br/blog/memory-evaluation-ai-project-lifecycle.md
description: "Avaliação de ciclo de vida para memória de IA: recall, revisão e esquecimento controlado ao longo de pontos de mudança — não um snapshot estático."
author: "Tiarê Balbi Bonamini"
locale: pt-br
published: 2026-04-17
updated: 2026-05-03
category: "AI"
tags: ["ia", "llm", "memória", "avaliação", "kotlin", "spring-ai", "rag", "arquitetura", "agentes-de-longa-duração"]
translation: https://tiarebalbi.com/en/blog/memory-evaluation-ai-project-lifecycle
---
# Avaliação de Memória: Medindo Como a Memória de IA se Degrada ao Longo da Vida de um Projeto

A maioria dos times com quem trabalhei prende uma camada de memória nos seus assistentes de IA — um vector store, um job de sumarização, uma tabela de "fatos" — e então avalia uma vez, em um único ponto no tempo. O recall parece bom, a latência é aceitável, e sobem para produção.

Seis meses depois o assistente diz, com confiança, a uma nova contratada que o serviço de auth ainda roda em Node, que `payments-v1` é a fonte da verdade e que a Alice é dona do domínio de billing. Nada disso é verdade agora. Era verdade. A camada de memória nunca percebeu que deixou de ser.

Essa é a lacuna que quero fechar. Um sistema de memória útil não é avaliado pelo que ele lembra; é avaliado por se a sua representação do projeto acompanha a realidade à medida que o projeto muda. Se você está construindo um copilot de longa duração — para uma base de código, um time, uma conta de cliente — você precisa tratar memória da mesma forma que trata um cache: correção inclui invalidação.

No fim deste post, você terá um framework concreto para avaliar memória ao longo do ciclo de vida de um projeto, as quatro métricas que importam e os modos de falha em torno dos quais você deve projetar.

## Snapshots Não Capturam um Alvo em Movimento

A avaliação padrão de memória parece mais ou menos assim: popular o store com N fatos, fazer M perguntas, medir recall e precisão contra um conjunto de referência. Isso é um snapshot. Não diz nada sobre o que acontece quando o fato 17 se torna obsoleto na semana 12.

Projetos reais produzem um fluxo contínuo de eventos invalidadores:

* Um serviço é reescrito em uma linguagem diferente. Toda resposta específica de código ancorada na stack antiga está agora errada.
* Um time se reorganiza. "Pergunta para a Alice sobre billing" era verdade; agora isso roteia para uma pessoa que saiu da empresa.
* Uma decisão é revertida. Você escolheu Kafka, depois migrou para SQS, depois voltou. A memória que reflete qualquer um desses estados isoladamente é enganosa.
* Um requisito é abandonado. O assistente continua citando uma restrição com que ninguém mais se importa.
* Uma fronteira arquitetural muda. O módulo que a memória "conhece" não existe mais.

Cada um desses é uma mutação da verdade fundamental. Um benchmark de snapshot não os vê. Pior, o modo de falha padrão é silencioso: o sistema continua respondendo com confiança usando contexto desatualizado porque nada no pipeline de retrieval sinaliza um fato como expirado. As pontuações de relevância parecem normais. Os embeddings ainda batem. A resposta simplesmente está errada.

A parte difícil não é o retrieval. A parte difícil é que "a resposta certa" é um alvo móvel e a maioria dos harnesses de avaliação o congela. O LongMemEval (ICLR 2025) é um dos poucos benchmarks públicos que trata atualizações de conhecimento como uma capacidade de primeira classe — vale a leitura mesmo que você não adote o benchmark diretamente.

## Memória como uma Read Replica do Projeto

O modelo mental que uso é emprestado da replicação de bancos de dados: memória é uma read replica do projeto. O que você mede é lag de replicação, drift e divergência — não apenas throughput.

Em vez de um benchmark de ponto único, eu rodo uma **avaliação de ciclo de vida**. O harness de avaliação guia o sistema através de uma sequência de pontos de mudança e sonda em cada um. A cada passo ele faz quatro perguntas:

1. **Preservação** — Ele manteve fatos que ainda são válidos?
2. **Revisão** — Ele atualizou fatos que mudaram?
3. **Esquecimento** — Ele descartou fatos que estão obsoletos?
4. **Não-contaminação** — Respostas antigas estão vazando para as novas?

Essas quatro colapsam em quatro métricas que você pode acompanhar ao longo do tempo:

* **Frescor** — % de fatos recuperados cuja última validação cai dentro da janela aceitável de staleness para seu tipo.
* **Consistência** — % de fatos recuperados que não contradizem a verdade fundamental atual.
* **Cobertura** — % de fatos da verdade fundamental atual que são recuperáveis.
* **Taxa de esquecimento controlado** — % de fatos invalidados que foram removidos ou substituídos dentro de N pontos de mudança a partir do evento invalidador.

Note o que está faltando: recall bruto. Recall é um componente de cobertura, mas por si só recompensa o acúmulo. Um store que nunca esquece nada terá recall perfeito e consistência catastrófica.

## Construindo o Harness de Ciclo de Vida

### Modelando pontos de mudança

O primeiro artefato concreto é uma **timeline** — uma lista ordenada de eventos que mutam a verdade fundamental. Em testes, eu sintetizo; em produção, eu derivo a partir de fontes que já tenho (histórico de git, ADRs, organogramas, fechamentos de ticket).

```kotlin
sealed interface ChangeEvent {
    val id: String
    val timestamp: Instant

    data class FactAdded(
        override val id: String,
        override val timestamp: Instant,
        val fact: Fact,
    ) : ChangeEvent

    data class FactRevised(
        override val id: String,
        override val timestamp: Instant,
        val previous: Fact,
        val current: Fact,
    ) : ChangeEvent

    data class FactInvalidated(
        override val id: String,
        override val timestamp: Instant,
        val fact: Fact,
        val reason: InvalidationReason,
    ) : ChangeEvent
}

data class Fact(
    val subject: String,
    val predicate: String,
    val value: String,
    val domain: FactDomain,   // TECH, OWNERSHIP, DECISION, REQUIREMENT
)
```

Domínios importam porque a tolerância a staleness é específica do domínio. Um fato de ownership fica desatualizado em dias após uma reorganização. Uma decisão arquitetural pode ser válida por trimestres. Um requisito pode ser válido por anos. Tratá-los de forma uniforme é a raiz da maioria dos bugs de esquecimento-ávido-demais que vi.

### Rodando o harness

O avaliador reproduz a timeline, e após cada evento roda um conjunto de probes contra o sistema de memória. Uma probe é uma pergunta com uma resposta esperada ciente do tempo — a resposta é uma função de "o que é verdadeiro no timestamp T".

```kotlin
class LifecycleEvaluator(
    private val memory: MemoryService,
    private val probes: List<Probe>,
    private val groundTruth: GroundTruth,
) {
    fun evaluate(timeline: List<ChangeEvent>): List<StageResult> {
        val results = mutableListOf<StageResult>()

        timeline.forEach { event ->
            memory.apply(event)

            val metrics = probes
                .map { probe -> score(probe, event.timestamp) }
                .aggregate()

            results += StageResult(event, metrics)
        }

        return results
    }

    private fun score(probe: Probe, at: Instant): ProbeScore {
        val expected = groundTruth.resolve(probe, at)
        val actual = memory.answer(probe.question, at)

        return ProbeScore(
            freshness = freshness(actual.facts, at),
            consistency = consistency(actual.facts, expected),
            coverage = coverage(actual.facts, expected),
            contaminated = actual.facts.any { it.invalidatedBefore(at) },
        )
    }
}
```

A escolha importante aqui é que `memory.answer` retorna os **fatos** usados para produzir a resposta, não apenas o texto da resposta. Pontuar no nível do fato é o que permite distinguir "a resposta está certa pelos motivos errados" de correção genuína. Já vi sistemas pontuarem bem em similaridade de texto de resposta enquanto recuperavam evidências completamente desatualizadas — estavam a uma pergunta reformulada de quebrar.

### Forçando o sistema a esquecer

Esquecimento controlado é a métrica em que a maioria das implementações falha. A stack padrão de retrieval — embed, nearest-neighbor, return — não tem conceito nativo de invalidação. Você precisa de um sinal explícito.

Três mecanismos, em ordem crescente de invasividade:

1. **TTL por domínio.** Fatos de ownership expiram em 30 dias a menos que revalidados. Barato, ruidoso, mas pega a cauda longa.
2. **Links de supersessão.** Quando um evento `FactRevised` chega, escreva uma tombstone apontando o fato antigo para o novo. O retrieval filtra fatos com tombstone a menos que a consulta peça explicitamente por histórico.
3. **Propagação de invalidação.** Um evento `FactInvalidated` se espalha para fatos dependentes. Matar `payments-v1 é fonte da verdade` também deve invalidar `payments-v1 usa Postgres 13`.

Na prática, rodo os três. TTL lida com o drift que você não modelou, supersessão lida com as mudanças que você modelou, propagação lida com as cascatas. O harness de avaliação sonda especificamente cada modo de falha — um TTL perdido, uma supersessão perdida, uma cascata perdida — então você pode dizer qual camada está vazando.

A arquitetura publicada do Mem0 (abril de 2025) vai na direção oposta — extração single-pass, ADD-only, sem delete nativo. Funciona para fatos estilo chat que mudam devagar; para memória de projeto de longa duração é exatamente o design que falha no teste de ciclo de vida.

### Conectando ao Spring

Para serviços que já expõem memória através de uma API de retrieval baseada em Spring, o avaliador encaixa como mais um cliente. O harness se torna um job agendado que reproduz uma timeline sintética contra uma instância shadow de memória e publica métricas na mesma stack de observabilidade de tudo mais. Não há razão para construir um dashboard paralelo.

```kotlin
@Component
class MemoryHealthJob(
    private val evaluator: LifecycleEvaluator,
    private val timelineSource: TimelineSource,
    private val meter: MeterRegistry,
) {
    @Scheduled(cron = "0 0 * * * *")
    fun run() {
        val timeline = timelineSource.latest()
        val results = evaluator.evaluate(timeline)

        results.last().metrics.let {
            meter.gauge("memory.freshness", it.freshness)
            meter.gauge("memory.consistency", it.consistency)
            meter.gauge("memory.coverage", it.coverage)
            meter.gauge("memory.contamination", it.contaminationRate)
        }
    }
}
```

Frescor e consistência são os dois em que faço alertas. Cobertura tende a se mover lentamente e contaminação é um indicador antecedente para consistência — se a contaminação está subindo, a consistência vai seguir.

## Armadilhas e Casos Extremos

**Esquecimento excessivo.** Na primeira vez que você conecta TTLs agressivos, a cobertura despenca. Fatos de ownership expiram antes que o job de revalidação alcance; o assistente esquece quem é dono de qualquer coisa. O conserto é um estado de soft-expiry: fatos que passaram do TTL mas ainda não foram invalidados são recuperáveis com uma penalidade de confiança, não excluídos diretamente.

**Drift de embedding se fazendo passar por drift de memória.** Se você muda o modelo de embedding, tudo parece desatualizado. O avaliador precisa distinguir "o fato está desatualizado" de "a camada de retrieval mudou". Versione os embeddings e inclua a versão do embedding no registro do fato.

**Contradição silenciosa.** Dois fatos podem ser individualmente plausíveis e conjuntamente inconsistentes — o store afirma que o serviço X possui o domínio A _e_ que o serviço Y possui o domínio A. Retrieval ponto a ponto nunca vê o conflito. O avaliador deve rodar checagens de consistência sobre o conjunto recuperado, não apenas por fato.

**Vazamento de probe.** Se você cria probes uma vez e nunca atualiza, o avaliador começa a testar o projeto de ontem. Probes devem ser versionadas junto com a base de código e invalidadas nos mesmos sinais que os fatos que testam.

**O caso "ainda verdadeiro, só que menos verdadeiro".** Alguns fatos se degradam gradualmente — uma característica de performance, um tamanho de time, um padrão de tráfego. Eles nunca são explicitamente invalidados. Esquecimento controlado não ajuda aqui; você precisa de revalidação contínua contra observações frescas. Trate isso como um pipeline separado e não tente encaixá-lo no modelo de eventos.

**Não-determinismo na pontuação.** Se a consistência é pontuada por um juiz LLM, o drift dele próprio vai aparecer nas suas métricas. Fixe a versão do modelo juiz e refaça o baseline deliberadamente, não por acidente.

## Conclusões Práticas

* Avalie memória como um ciclo de vida, não como um snapshot. Cada ponto de mudança é um caso de teste.
* Acompanhe frescor, consistência, cobertura e esquecimento controlado. Recall bruto sozinho recompensa o acúmulo.
* Pontue no nível do fato, não no nível do texto de resposta. Resposta-certa-com-evidência-errada é um bug, não aprovação.
* Segmente por domínio de fato. Ownership, decisões, requisitos e fatos técnicos têm tolerâncias a staleness diferentes.
* Combine TTLs, links de supersessão e propagação de invalidação. Cada um pega uma classe diferente de drift.
* Rode o avaliador como um job agendado contra um store shadow e conecte suas métricas à sua stack de observabilidade existente.
* Versione suas probes. Um conjunto de probes congelado se transforma silenciosamente em um teste de regressão para o passado.

## Quando Investir em Avaliação de Ciclo de Vida

A camada de memória é um cache sobre a verdade fundamental do seu projeto. Como qualquer cache, ela é útil apenas na medida da sua história de invalidação. Avaliá-la uma vez, em um único ponto no tempo, diz quão bom foi o snapshot que ela tirou; não diz nada sobre quão rápido ela vai apodrecer.

Use este framework quando o projeto for de longa duração, a verdade fundamental for mutável e respostas erradas forem piores que "não sei" — que é a maioria dos contextos reais de engenharia. Pule-o quando a memória for de escopo de sessão ou quando o domínio subjacente realmente não mudar. O overhead não é grátis: um harness de ciclo de vida, uma fonte de timeline e políticas de staleness por domínio são investimento real de engenharia. Pague por eles onde o custo da confiança desatualizada for maior que o custo do harness.

A mudança de mentalidade é pequena mas estruturante. Pare de perguntar "ele lembra?" e comece a perguntar "o que ele lembra ainda bate com a realidade, e quão rápido ele percebe quando não bate?"
