Seu agent de diagnóstico vai bem numa interação. Aí o mesmo alerta volta na semana seguinte e ele zera a memória. Não lembra que já investigou. Não lembra que a causa raiz era aquele cronjob que explode memória toda quarta às 3h da manhã.

Agent sem memória é como engenheiro que perde o caderno toda segunda. Sabe trabalhar. Só reaprende as mesmas coisas o tempo todo.

O mapa pro profissional de infra

Conceito de MemóriaO que fazEquivalente em infra
Short-term memoryContexto da conversa atualBuffer de request (dados in-flight)
Long-term memoryInformações que persistem entre sessõesDatabase, persistent storage
Episodic memoryLembranças de interações passadasLogs, audit trail
Semantic memoryConhecimento geral acumuladoKnowledge base, wiki
Working memoryO que está “ativo” na cabeça do agentCache, working set
StateConfiguração atual do agentEstado do pod, configmap
ConsistencyGarantia de que memória é corretaConsistency model do banco

Por que memória é difícil em agents

LLMs são stateless. Cada request é independente. O modelo não “lembra” nada entre chamadas. Toda memória é simulada via contexto.

Request 1: [system_prompt + mensagem_1] → resposta_1
Request 2: [system_prompt + mensagem_1 + resposta_1 + mensagem_2] → resposta_2
Request 3: [system_prompt + msg_1 + resp_1 + msg_2 + resp_2 + msg_3] → resposta_3

Percebe o problema? O contexto cresce a cada interação. Em algum momento, estoura o context window. É como se toda vez que você precisa lembrar de algo, tivesse que reler todos os seus logs desde o início.

Tipos de memória

1. Short-term memory (contexto da conversa)

É simplesmente o histórico de mensagens na conversa atual. Vive no context window do LLM.

class ShortTermMemory:
    def __init__(self, max_tokens=50000):
        self.messages = []
        self.max_tokens = max_tokens
    
    def add(self, role, content):
        self.messages.append({"role": role, "content": content})
        self._trim_if_needed()
    
    def _trim_if_needed(self):
        """Remove mensagens antigas quando context fica grande."""
        total = sum(count_tokens(m["content"]) for m in self.messages)
        while total > self.max_tokens and len(self.messages) > 2:
            # Mantém system prompt (primeiro) e última mensagem
            removed = self.messages.pop(1)  # Remove a segunda mais antiga
            total -= count_tokens(removed["content"])
    
    def get_messages(self):
        return self.messages

Trade-offs:

  • Simples e determinístico
  • Limitado pelo context window
  • Se trimma demais, perde contexto importante
  • Custo escala linearmente com tamanho

2. Long-term memory (persiste entre sessões)

Informações que o agent deve lembrar “pra sempre”. Tipicamente em banco de dados ou vector store.

from datetime import datetime, timedelta

class LongTermMemory:
    def __init__(self, db_connection, embedding_client):
        self.db = db_connection
        self.embedder = embedding_client
    
    def store(self, content, metadata=None):
        """Salva informação na memória de longo prazo."""
        embedding = self.embedder.embed(content)
        self.db.insert({
            "content": content,
            "embedding": embedding,
            "metadata": metadata or {},
            "created_at": datetime.now(),
            "access_count": 0,
        })
    
    def recall(self, query, top_k=5):
        """Busca memórias relevantes pra query atual."""
        query_embedding = self.embedder.embed(query)
        results = self.db.vector_search(
            query_embedding,
            top_k=top_k,
            min_similarity=0.7,
        )
        
        # Atualizar access_count (memórias usadas ficam "mais fortes")
        for result in results:
            self.db.increment(result["id"], "access_count")
        
        return results
    
    def forget(self, older_than_days=90, min_access=0):
        """Remove memórias antigas e nunca acessadas."""
        cutoff = datetime.now() - timedelta(days=older_than_days)
        self.db.delete_where(
            "created_at < ? AND access_count <= ?",
            [cutoff, min_access],
        )

Exemplos de o que guardar:

  • “O servidor web-prod-02 tem um memory leak no Java que precisa de restart semanal”
  • “O usuário Ricardo prefere respostas curtas com comandos diretos”
  • “Alertas de CPU em batch-workers-* são normais entre 02:00-04:00 (jobs de ETL)”

Pra fatos operacionais, eu não confiaria só em busca vetorial. Guarde também campos estruturados, timestamp da observação e origem do dado. Embedding ajuda a achar; não substitui source of truth.

3. Episodic memory (o que aconteceu antes)

Lembranças estruturadas de interações passadas. Como um log de incidentes que o agent pode consultar.

import json
from datetime import datetime

class EpisodicMemory:
    def __init__(self, db, embedding_client):
        self.db = db
        self.embedder = embedding_client
    
    def record_episode(self, trigger, actions, outcome, lessons):
        """Registra um episódio completo."""
        self.db.insert("episodes", {
            "trigger": trigger,
            "actions": json.dumps(actions),
            "outcome": outcome,  # "resolved", "escalated", "failed"
            "lessons": lessons,
            "timestamp": datetime.now(),
            "embedding": self.embedder.embed(f"{trigger} {outcome} {lessons}"),
        })
    
    def find_similar_episodes(self, current_situation, top_k=3):
        """Busca episódios similares ao cenário atual."""
        return self.db.vector_search(
            self.embedder.embed(current_situation),
            table="episodes",
            top_k=top_k,
        )

Uso no agent loop:

def agent_with_episodic_memory(task, episodic_memory, system_prompt):
    # Antes de agir, consultar episódios similares
    similar = episodic_memory.find_similar_episodes(task)
    augmented_prompt = system_prompt
    
    if similar:
        context = "Episódios anteriores similares:\n"
        for episode in similar:
            context += f"- Situação: {episode['trigger']}\n"
            context += f"  Resultado: {episode['outcome']}\n"
            context += f"  Lição: {episode['lessons']}\n\n"
        
        # Adicionar ao system prompt
        augmented_prompt = f"{system_prompt}\n\n{context}"
    
    # Executar agent normalmente...
    result = run_agent(task, augmented_prompt)
    
    # Depois: registrar o que aconteceu
    episodic_memory.record_episode(
        trigger=task,
        actions=result["actions_taken"],
        outcome=result["outcome"],
        lessons=result.get("lessons", ""),
    )

4. Semantic memory (conhecimento geral)

Base de conhecimento do agent. Runbooks, documentação, policies. Geralmente implementado via RAG.

A diferença é simples: semantic é “o que eu sei” (fatos), episodic é “o que eu vivi” (experiências).

Gerenciamento de estado

Além de memória (o que o agent lembra), existe estado (em que ponto da tarefa ele está).

State machine pra agents

import json
from datetime import datetime
from enum import Enum

class InvalidTransition(Exception):
    pass

class AgentState(Enum):
    IDLE = "idle"
    INVESTIGATING = "investigating"
    AWAITING_APPROVAL = "awaiting_approval"
    EXECUTING = "executing"
    VALIDATING = "validating"
    COMPLETED = "completed"
    FAILED = "failed"
    ESCALATED = "escalated"

class StatefulAgent:
    def __init__(self, agent_id, db):
        self.agent_id = agent_id
        self.db = db
        self.state = AgentState.IDLE
        self.context = {}  # dados acumulados durante a task
    
    def transition(self, new_state, reason=""):
        """Transição de estado com validação."""
        valid_transitions = {
            AgentState.IDLE: [AgentState.INVESTIGATING],
            AgentState.INVESTIGATING: [
                AgentState.AWAITING_APPROVAL,
                AgentState.EXECUTING,
                AgentState.FAILED,
                AgentState.ESCALATED,
                AgentState.COMPLETED,
            ],
            AgentState.AWAITING_APPROVAL: [AgentState.EXECUTING, AgentState.FAILED, AgentState.ESCALATED],
            AgentState.EXECUTING: [AgentState.VALIDATING, AgentState.FAILED, AgentState.ESCALATED],
            AgentState.VALIDATING: [
                AgentState.COMPLETED,
                AgentState.INVESTIGATING,
                AgentState.FAILED,
                AgentState.ESCALATED,
            ],
            AgentState.FAILED: [AgentState.INVESTIGATING, AgentState.ESCALATED],
            AgentState.COMPLETED: [],
            AgentState.ESCALATED: [],
        }
        
        if new_state not in valid_transitions.get(self.state, []):
            raise InvalidTransition(f"{self.state.value} -> {new_state.value} não é válida")
        
        previous_state = self.state
        self.state = new_state
        self._persist_state()
        self._emit_event(previous_state, new_state, reason)
    
    def _persist_state(self):
        """Salva estado pra recovery em caso de crash."""
        self.db.upsert("agent_states", {
            "agent_id": self.agent_id,
            "state": self.state.value,
            "context": json.dumps(self.context),
            "updated_at": datetime.now(),
        })
    
    def _emit_event(self, old_state, new_state, reason):
        print(f"[{self.agent_id}] {old_state.value} -> {new_state.value}: {reason}")

Recovery de estado

Se o agent crasheia no meio de uma task, precisa retomar de onde parou. Mesma lógica de checkpointing em pipelines de dados.

import json

def recover_agent(agent_id, db):
    """Retoma agent de onde parou após crash/restart."""
    saved = db.get("agent_states", agent_id)
    
    if not saved:
        return create_new_agent(agent_id)
    
    agent = StatefulAgent(agent_id, db)
    agent.state = AgentState(saved["state"])
    agent.context = json.loads(saved["context"] or "{}")
    
    # Decidir o que fazer baseado no estado salvo
    if agent.state == AgentState.INVESTIGATING:
        return resume_investigation(agent)
    if agent.state == AgentState.AWAITING_APPROVAL:
        return check_approval(agent)
    if agent.state == AgentState.EXECUTING:
        # Perigoso! Validar se ação foi completada ou não
        return verify_execution_status(agent)
    if agent.state == AgentState.VALIDATING:
        return resume_validation(agent)
    
    return agent

Consistência: o problema mais difícil

Memória em agents tem os mesmos problemas de consistência que bancos de dados distribuídos.

Problemas reais

Memória contraditória: o agent lembra que “servidor X tem 16GB de RAM” de uma interação antiga, mas o servidor já foi ampliado pra 32GB.

Memória desatualizada: um episódio antigo diz “reiniciar o nginx resolve”, mas a arquitetura mudou e agora tudo roda em container.

Memória corrompida: o agent “aprende” algo errado a partir de uma alucinação e usa essa informação no futuro.

Estratégias de mitigação

from datetime import datetime, timedelta

class ConsistentMemory:
    def __init__(self):
        self.store = {}
    
    def store_with_ttl(self, key, value, ttl_hours=168):
        """Memórias expiram. Fatos de infra mudam."""
        now = datetime.now()
        self.store[key] = {
            "value": value,
            "stored_at": now,
            "expires_at": now + timedelta(hours=ttl_hours),
            "confidence": 1.0,
            "source": "observation",  # ou "inferred", "told_by_user"
        }
    
    def recall_with_freshness(self, key):
        """Retorna memória com indicação de confiança."""
        entry = self.store.get(key)
        if not entry:
            return None
        
        if datetime.now() >= entry["expires_at"]:
            del self.store[key]
            return None
        
        # Decay confidence over time
        age_hours = (datetime.now() - entry["stored_at"]).total_seconds() / 3600
        freshness = max(0.3, 1.0 - (age_hours / (168 * 2)))
        
        return {
            "value": entry["value"],
            "confidence": entry["confidence"] * freshness,
            "age_hours": age_hours,
            "warning": "Informação pode estar desatualizada" if freshness < 0.5 else None,
        }
    
    def invalidate_on_conflict(self, key, new_observation, ttl_hours=168):
        """Se observação contradiz memória, invalida."""
        existing = self.store.get(key)
        if existing and existing["value"] != new_observation:
            now = datetime.now()
            self.store[key] = {
                "value": new_observation,
                "stored_at": now,
                "expires_at": now + timedelta(hours=ttl_hours),
                "confidence": 0.9,
                "source": "observation",
                "previous_value": existing["value"],
            }
            return {"conflict_detected": True, "old": existing["value"], "new": new_observation}
        
        return {"conflict_detected": False}

Patterns de memória em produção

1. Summarization (comprimir histórico)

Quando a conversa fica longa, resumir interações anteriores em vez de manter o histórico completo.

def summarize_old_context(messages, keep_recent=5):
    """Resume mensagens antigas, mantém recentes intactas."""
    if len(messages) <= keep_recent + 1:  # +1 pro system prompt
        return messages
    
    old_messages = messages[1:-keep_recent]  # Exclui system prompt e recentes
    
    summary = client.chat.completions.create(
        model="gpt-4o-mini",  # Modelo barato pra summarization
        messages=[
            {"role": "system", "content": "Resuma a conversa abaixo em 3-5 bullet points, mantendo fatos importantes e decisões tomadas."},
            {"role": "user", "content": format_messages(old_messages)}
        ]
    ).choices[0].message.content
    
    # Montar novo contexto: system + resumo + recentes
    return [
        messages[0],  # system prompt
        {"role": "system", "content": f"Resumo da conversa anterior:\n{summary}"},
        *messages[-keep_recent:]
    ]

Resumo ajuda a caber no contexto, mas também apaga detalhe. Eu não usaria summary como único registro de auditoria.

2. Memory as RAG

Tratar todas as memórias como documentos num vector store. O agent “lembra” buscando semanticamente.

from datetime import datetime

def agent_with_memory_rag(task, memory_store):
    # Buscar memórias relevantes pra tarefa atual
    relevant_memories = memory_store.search(task, top_k=5, min_score=0.75)
    
    memory_context = ""
    if relevant_memories:
        memory_context = "\n\nMemórias relevantes de interações anteriores:\n"
        for mem in relevant_memories:
            age = (datetime.now() - mem["created_at"]).days
            memory_context += f"- [{age} dias atrás] {mem['content']}\n"
    
    system_prompt = BASE_PROMPT + memory_context
    return run_agent(task, system_prompt)

Busca vetorial é ótima pra recall. Quando precisão importa, combine similaridade com metadata filters, TTL e alguma forma de revalidar a informação.

3. Structured memory (key-value com categories)

MEMORY_SCHEMA = {
    "server_facts": {
        # hostname → fatos sobre o servidor
        "web-prod-02": {"ram": "32GB", "known_issues": ["memory leak no Java app"]},
    },
    "procedures": {
        # situação → o que fazer
        "cpu_alta_batch_workers": "Normal entre 02:00-04:00 (ETL). Ignorar.",
    },
    "user_preferences": {
        # user → preferências
        "ricardo": {"verbosity": "low", "preferred_format": "commands_first"},
    }
}

O que levar pra segunda-feira

  • LLMs são stateless. Toda memória é construída por você. Sem infra de memória, o agent começa do zero toda vez.
  • Short-term é trivial (histórico de mensagens). Long-term e episodic requerem infra real (vector DB + persistence).
  • Memória envelhece. Fatos de infra mudam. Implemente TTL e decay de confiança.
  • State recovery é essencial pra produção. Agents que crasham no meio de uma ação precisam retomar com segurança.
  • Consistência é difícil. Memórias podem estar erradas ou desatualizadas. Preferir observação recente sobre memória antiga.

O próximo post vai pros padrões agentic: os building blocks que se combinam pra criar agents mais sofisticados.

Leitura complementar