Por que seu agente de AI continua perdendo a memória (e como resolvemos isso)
Seu agente de AI acabou de ter uma conversa de 30 minutos com um usuário. Eles discutiram requisitos de projeto, compartilharam preferências, tomaram decisões. Então o usuário digita /new para iniciar uma nova sessão.
O agente tenta consolidar essa conversa na memória de longo prazo. A chamada de LLM falha. Rate limit. Timeout. Ou o modelo retorna texto em vez de chamar a tool necessária.
A memória se foi. Trinta minutos de contexto, evaporados.
Isso acontece com mais frequência do que você imagina. Monitoramos isso em nossas instâncias do LemonClaw: a consolidação de memória tinha uma taxa de falha de ~15% em qualquer modelo individual. Para um recurso que deveria ser uma infraestrutura invisível, isso é inaceitável.
Como outros frameworks lidam com isso (eles não lidam)
A maioria dos frameworks de agentes de AI trata a consolidação de memória como uma simples chamada de LLM. Se funcionar, ótimo. Se não, a memória é perdida.
OpenClaw, o framework de agentes open-source mais popular, usa o mesmo modelo para consolidação e para a conversa. Uma chamada do Claude Sonnet que custa $0.003 e leva mais de 8 segundos, apenas para resumir um chat que o usuário nunca verá. Quando essa chamada falha (rate limit, timeout, erro do modelo), o framework registra um aviso e segue em frente. O contexto do usuário desapareceu.
nanobot, outro framework popular, tem a mesma arquitetura. Um modelo, uma tentativa, sem fallback. A função de consolidação nem sequer tem um timeout. Um upstream lento (erros Cloudflare 524 são comuns) bloqueia toda a sessão até que a conexão caia.
Nenhum dos frameworks separa a consolidação do modelo principal. Nenhum possui lógica de fallback para operações de memória. Nenhum distingue entre "a chamada da API falhou" e "a chamada da API teve sucesso, mas o modelo não fez o que pedimos".
Estes não são casos isolados. Com taxas de falha de 15% em qualquer modelo único, um framework executando 100 consolidações por dia perde a memória em 15 delas. Ao longo de uma semana, são 105 conversas onde o agente esquece tudo.
O problema é mais profundo do que a lógica de repetição (retry)
A correção óbvia é o retry com exponential backoff. Nós tínhamos isso. Ele lida bem com erros HTTP transitórios:
# Loop de repetição: backoff de 1s → 2s → 4s
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
Isso captura 429s e instabilidades de rede. Mas dois modos de falha passam despercebidos:
Modo de falha 1: O modelo não consegue executar tool calling. Alguns modelos, especialmente os menores rodando em mecanismos de inferência rápidos, ocasionalmente falham ao gerar chamadas de função válidas em prompts complexos. A API retorna um 200 com um ServiceUnavailableError envolto em um MidStreamFallbackError. Sua lógica de retry vê uma exceção, tenta o mesmo modelo novamente e recebe o mesmo erro.
Modo de falha 2: O modelo "tem sucesso", mas não chama a tool. O LLM retorna uma resposta perfeitamente válida. HTTP 200. Sem erros. Mas, em vez de chamar save_memory com dados estruturados, ele escreve um resumo em texto simples. Seu mecanismo de retry considera isso um sucesso. A função de consolidação verifica as chamadas de tool, não encontra nenhuma e desiste.
O segundo modo de falha é o mais insidioso. A camada de transporte acha que tudo funcionou. A camada de negócio sabe que não. Nenhuma quantidade de retries em nível HTTP corrigirá um modelo que não entende o esquema da sua tool.
Arquitetura de Fallback em Duas Camadas
Resolvemos isso com dois loops de fallback independentes operando em níveis diferentes:
Usuário envia /new
│
▼
consolidate() ─── Fallback da Camada de Negócio
│ "O modelo chamou save_memory?"
│ Não → tenta o próximo modelo na cadeia
│
▼
_chat_with_retry() ─── Fallback da Camada de Transporte
│ Erros HTTP → exponential backoff
│ Retries esgotados → percorre a cadeia de fallback
│
▼
Cadeia de fallback MODEL_MAP:
llama-3.3-70b ─$0.59/M─→ qwen3-32b ─$0.29/M─→ llama-4-scout ─$0.11/M─→ gpt-4.1-mini ─→ claude-haiku
(394 TPS) (662 TPS) (594 TPS) (confiável) (último recurso)
A Camada 1 lida com falhas de transporte. A Camada 2 lida com falhas de lógica de negócio. A cadeia de fallback é compartilhada entre ambas as camadas, definida uma única vez em um catálogo central.
Esta é uma abordagem fundamentalmente diferente de repetir o mesmo modelo. Quando um modelo falha ao chamar uma tool, repeti-lo com o mesmo prompt raramente ajuda. Mudar para um modelo diferente, com pesos diferentes e comportamento de tool calling diferente, ajuda.
O Catálogo de Modelos: Uma Única Fonte de Verdade
Cada modelo em nosso catálogo possui um campo opcional fallback apontando para o próximo modelo a ser tentado:
@dataclass(frozen=True)
class ModelEntry:
id: str
label: str
tier: str
description: str
fallback: str | None = None
hidden: bool = False # Oculto da lista /model para o usuário
MODEL_CATALOG = [
# Modelos visíveis ao usuário (16 modelos que os usuários podem alternar)
ModelEntry("claude-sonnet-4-6", "Claude Sonnet 4.6", "standard",
"Recommended", fallback="claude-sonnet-4-5"),
ModelEntry("gpt-4.1-mini", "GPT-4.1 Mini", "economy",
"Stable tool calling", fallback="claude-haiku-4-5"),
# Modelos de consolidação ocultos (apenas uso interno)
ModelEntry("llama-3.3-70b-versatile", "Llama 3.3 70B (Groq)", "economy",
"394 TPS", fallback="qwen3-32b", hidden=True),
ModelEntry("qwen3-32b", "Qwen3 32B (Groq)", "economy",
"662 TPS", fallback="llama-4-scout-17b-16e-instruct", hidden=True),
# ...
]
A flag hidden=True mantém os modelos internos fora do comando /model voltado para o usuário, permitindo que ainda participem das cadeias de fallback. Os usuários veem 16 modelos que podem escolher. O sistema usa 19. Os três modelos ocultos existem exclusivamente para tarefas de segundo plano, como a consolidação de memória, onde velocidade e custo importam mais do que a qualidade conversacional.
Este catálogo é a única fonte de verdade para todo o roteamento de modelos. Adicionar um novo modelo à cadeia de fallback significa adicionar apenas uma linha. Sem arquivos de configuração para sincronizar, sem variáveis de ambiente para atualizar, sem scripts de deploy para modificar.
Camada de Transporte: Fallback Encadeado com Detecção de Ciclos
O mecanismo de retry percorre a cadeia de fallback usando um conjunto de modelos visitados para evitar loops infinitos:
async def _chat_with_retry(self, kwargs, original_model):
# Fase 1: Exponential backoff no modelo primário
for attempt in range(3):
try:
response = await acompletion(**kwargs)
return await self._collect_stream(response)
except (RateLimitError, APIConnectionError, APIError) as e:
await asyncio.sleep(RETRY_DELAYS[attempt])
except AuthenticationError:
return LLMResponse(content="API key invalid.", finish_reason="error")
# Fase 2: Percorrer a cadeia de fallback
visited = {original_model}
current = original_model
while True:
entry = MODEL_MAP.get(current)
if not entry or not entry.fallback or entry.fallback in visited:
break
current = entry.fallback
visited.add(current)
# Resolver o gateway correto para este modelo
gw = self._resolve_gateway_for_model(current)
resolved = self._resolve_model(current, gateway=gw)
fb_kwargs = {**kwargs, "model": resolved}
# Ajustar api_base para o protocolo do modelo alvo
if gw and gw.default_api_base:
fb_kwargs["api_base"] = gw.default_api_base
try:
response = await acompletion(**fb_kwargs)
return await self._collect_stream(response)
except Exception:
continue # Tenta o próximo na cadeia
return LLMResponse(content="Service unavailable.", finish_reason="error")
O conjunto visited é crítico. Sem ele, uma cadeia como A→B→A entraria em loop eterno. Com ele, o mecanismo tenta cada modelo exatamente uma vez.
A resolução do gateway também é importante. Modelos diferentes precisam de formatos de API diferentes. Modelos Claude são roteados através de um gateway no formato Anthropic (sem o sufixo /v1). Modelos GPT passam por um gateway compatível com OpenAI (com /v1). Modelos Groq usam ainda outro endpoint. O mecanismo de fallback resolve o gateway correto para cada modelo na cadeia, evitando incompatibilidades de protocolo, como enviar requisições Anthropic para um endpoint OpenAI.
Este é um detalhe que a maioria dos frameworks ignora completamente. Eles assumem que todos os modelos falam o mesmo protocolo. Em produção, com 19 modelos em 4 formatos de API diferentes, essa suposição quebra imediatamente.
Camada de Negócio: Verificação de Tool Call
A função de consolidação adiciona seu próprio loop de fallback por cima:
async def consolidate(self, session, provider, model, **kwargs):
visited = set()
current_model = model
while current_model and current_model not in visited and len(visited) <= 3:
visited.add(current_model)
response = await asyncio.wait_for(
provider.chat(messages=messages, tools=SAVE_MEMORY_TOOL, model=current_model),
timeout=30,
)
if response.has_tool_calls:
# Sucesso: extrair e salvar memória
args = response.tool_calls[0].arguments
self.write_long_term(args["memory_update"])
self.append_history(args["history_entry"])
return True
# O modelo não chamou a tool — tenta o próximo na cadeia
entry = MODEL_MAP.get(current_model)
next_model = entry.fallback if entry else None
if next_model and next_model not in visited:
current_model = next_model
continue
return False # Sem mais fallbacks
return False
Isso captura o caso em que o _chat_with_retry retorna uma resposta bem-sucedida (HTTP 200, conteúdo válido), mas o modelo não usou a tool. A função de consolidação verifica por has_tool_calls e, se estiver faltando, passa para o próximo modelo na cadeia.
O wrapper de timeout (asyncio.wait_for) também aciona o fallback. Se um modelo demorar mais de 30 segundos (comum com erros Cloudflare 524 em upstreams lentos), a função captura o TimeoutError e tenta o próximo modelo em vez de bloquear a sessão do usuário indefinidamente.
Por que Groq para Consolidação
A consolidação de memória é uma tarefa de segundo plano. O usuário não vê a saída. Ele apenas precisa que funcione. Isso a torna uma candidata perfeita para modelos rápidos e baratos.
A maioria dos frameworks usa o mesmo modelo caro para tudo. Se você está usando Claude Sonnet para a conversa, também está usando Claude Sonnet para a consolidação de memória. Isso custa $3/M de tokens de entrada e leva mais de 8 segundos por consolidação, para uma tarefa que produz uma saída que nenhum humano lerá.
Nós desacoplamos totalmente a consolidação do modelo de conversa. A conversa usa qualquer modelo que o usuário selecionou. A consolidação usa uma cadeia dedicada de modelos hospedados no Groq:
| Modelo | Velocidade | Custo de Entrada | Custo de Saída |
|---|---|---|---|
| llama-3.3-70b-versatile | 394 TPS | $0.59/M | $0.79/M |
| qwen3-32b | 662 TPS | $0.29/M | $0.59/M |
| llama-4-scout-17b-16e | 594 TPS | $0.11/M | $0.34/M |
| gpt-4.1-mini (anterior) | ~150 TPS | $0.40/M | $1.60/M |
O modelo primário (llama-3.3-70b) consolida uma sessão de 60 mensagens em ~5 segundos. O padrão anterior (gpt-4.1-mini) levava mais de 8 segundos. O custo por consolidação caiu de ~$0.003 para ~$0.001.
O tradeoff: modelos Groq têm tool calling menos confiável em prompts complexos. É exatamente por isso que o fallback de camada dupla existe. Quando o llama-3.3-70b falha ao chamar a tool, o qwen3-32b assume. Se ele também falhar, o llama-4-scout tenta. Se todos os três modelos Groq falharem, o gpt-4.1-mini resolve com quase 100% de confiabilidade no tool calling.
Em produção, vemos o modelo primário ter sucesso em ~85% das vezes. A cadeia chega ao gpt-4.1-mini em menos de 2% das consolidações. A taxa de falha total é efetivamente zero.
Resultados em Produção
Fizemos o deploy disso em duas instâncias do LemonClaw e testamos com conversas reais do Telegram.
Primeiro deploy (apenas fallback de camada única):
Memory consolidation (archive_all): 56 messages
llama-3.3-70b-versatile → "Failed to call a function"
Falling back → qwen3-32b
qwen3-32b: LLM did not call save_memory, skipping
→ "Memory archival failed, session not cleared."
A camada de transporte capturou a primeira falha e acionou o fallback. Mas o qwen3-32b retornou texto sem chamar a tool. O fallback de camada única não conseguiu lidar com isso. Este é o cenário exato onde qualquer outro framework perderia a memória silenciosamente.
Segundo deploy (fallback de camada dupla):
Memory consolidation (archive_all): 60 messages
model=llama-3.3-70b-versatile → success
Memory consolidation done: 60 messages remaining
Mesmo modelo, mesmo volume de mensagens. Desta vez funcionou na primeira tentativa. A natureza intermitente da falha de tool calling é exatamente por que você precisa de uma cadeia de fallback em vez de um único modelo de backup.
Quando o modelo primário falha, a cadeia o captura:
llama-3.3-70b → tool call failed
→ consolidate() fallback → qwen3-32b
→ qwen3-32b didn't call tool
→ consolidate() fallback → llama-4-scout
→ llama-4-scout didn't call tool
→ consolidate() fallback → gpt-4.1-mini
→ gpt-4.1-mini called save_memory ✓
Memory consolidation done
Quatro modelos tentados, memória salva. O usuário vê "Nova sessão iniciada." e não tem ideia de que nada disso aconteceu.
A Lacuna de Arquitetura
Sistema de memória do LemonClaw vs. alternativas, recurso por recurso:
| Capacidade | Framework Típico de Agente AI | LemonClaw |
|---|---|---|
| Modelo de consolidação | Igual ao da conversa (caro, lento) | Cadeia de modelos independente, Groq-accelerated |
| Tratamento de falhas | Registra aviso, perde memória | Fallback de camada dupla, 5 modelos de profundidade |
| Fallback de transporte | Repete o mesmo modelo 3x | Fallback encadeado entre modelos diferentes |
| Fallback de lógica de negócio | Nenhum | Verificação de tool call + troca de modelo |
| Proteção de timeout | Nenhum (Cloudflare 524 bloqueia a sessão) | asyncio.wait_for(timeout=30) + fallback |
| Trunfamento de sessão | Nenhum (contexto cresce infinitamente) | Trunca mensagens antigas após consolidação |
| Busca no histórico | Nenhum | Janela deslizante HISTORY.md, pesquisável por grep |
| Modelos internos | Não suportado | hidden=True para modelos apenas de sistema |
| Prevenção de ciclo | Não necessário (sem cadeias) | Conjunto visited previne loops A→B→A |
| Resolução de gateway | Assume formato de API único | Gateway por modelo com detecção de protocolo |
Cada linha nesta tabela representa uma falha de produção que nós mesmos experimentamos ou observamos nos rastreadores de problemas de outros frameworks. O fallback de camada dupla, o catálogo de modelos ocultos, a resolução de gateway por modelo, o fallback acionado por timeout: nada disso existe no OpenClaw, nanobot ou qualquer outro framework de agentes open-source que examinamos.
O que aprendemos
"Requisição bem-sucedida" não é "tarefa bem-sucedida". Mecanismos de retry genéricos operam no nível HTTP. Eles não conseguem saber que uma resposta 200 com JSON válido é na verdade uma falha porque o modelo não usou a tool que você pediu. Operações críticas para o negócio precisam de seus próprios critérios de sucesso e sua própria lógica de fallback.
Modelos pequenos falham de forma diferente dos modelos grandes. Modelos grandes (GPT-4.1, Claude Sonnet) quase sempre chamam as tools quando solicitados. Modelos pequenos em mecanismos de inferência rápidos às vezes geram respostas que parecem válidas, mas ignoram completamente o esquema da tool. Isso não é um bug que você possa corrigir com prompt engineering. É uma lacuna de capacidade que requer mitigação arquitetural.
Teste com dados de produção, não dados sintéticos. Nosso teste inicial com 6 mensagens sintéticas passou em todos os modelos. A sessão real de 60 mensagens com histórico de tool calls, carimbos de data/hora e idiomas mistos falhou em dois dos três modelos Groq. A complexidade dos dados reais expõe modos de falha que dados de teste limpos nunca mostrarão.
LemonClaw é um framework de agentes de AI open-source com roteamento multi-modelo integrado, memória persistente e mais de 10 integrações com plataformas de chat. Todo o sistema de fallback de camada dupla descrito aqui está disponível na versão open-source. Rode no seu próprio servidor: github.com/hedging8563/lemonclaw
Precisa de mais de 300 modelos de AI através de uma única API key? lemondata.cc fornece acesso unificado a OpenAI, Anthropic, Google, DeepSeek, Groq e muito mais.
