O loop correu sem problemas quarenta vezes nos testes. Na quadragésima primeira execução, em produção, chamou a mesma ferramenta SQL com a mesma query com erro repetidamente, até esgotar o orçamento de API do dia, e só um alerta de faturação acabou por acordar alguém. Ninguém escreveu um modelo defeituoso. Ninguém alterou o prompt. O agente simplesmente nunca decidiu que tinha terminado.
Este é o padrão que continuo a ver em equipas que migram um agente de protótipo para uma carga de trabalho 24/7. Os loops de agentes de IA falham frequentemente em produção não porque o modelo de repente piorou, mas porque a camada de execução carece de disciplina de terminação, contratos de ferramentas validados, contexto delimitado e estado durável. Um loop de agente é um sistema estocástico que toma uma decisão sequencial após outra. Sem algumas salvaguardas específicas, a falha rara torna-se garantida com tempo suficiente de execução. As plataformas geridas de agentes (Vertex AI Agent Builder, Bedrock Agents, Azure AI Foundry) incluem algumas destas salvaguardas; este guia é para quem optou por self-hosting e gere o loop por conta própria.
As apostas são suficientemente reais para que a Gartner preveja que mais de 40% dos projetos de IA agêntica serão cancelados até ao final de 2027, citando custos crescentes e valor incerto. O que se segue são seis formas concretas como os loops falham em produção, o mecanismo por trás de cada uma, e o padrão de harness que a corrige, com os detalhes de LangGraph e n8n, além do que é necessário para realmente executar isto 24/7.
A versão curta
- Loops infinitos: O agente nunca decide que terminou. Combine um limite máximo de passos (o
recursion_limitdo LangGraph, padrão 25) com deteção de ausência de progresso que interrompe chamadas repetidas de ferramenta+argumento. - Overflow de contexto: O loop preenche a sua própria janela de contexto com histórico acumulado até as chamadas serem truncadas ou falharem. Resuma o histórico em intervalos fixos para manter o contexto de trabalho delimitado.
- Falhas silenciosas de ferramentas: Uma ferramenta retorna uma string vazia, o modelo lê-a como um no-op válido, e o agente "tem sucesso" sem fazer nada. Valide todos os resultados de ferramentas antes de o modelo os ver.
- Degradação de raciocínio: A qualidade degrada-se à medida que o contexto cresce, mesmo abaixo do limite máximo. Comprima a meio do loop, mas proteja as instruções de segurança fixadas ao fazê-lo.
- Perda de estado ao reiniciar: Um crash significa começar do zero. Faça checkpointing para Postgres (LangGraph
PostgresSaverLangGraph), não SQLite, em produção. - Tempestades de retry: Dez agentes cada um com dez tentativas atingem um serviço inativo com cem pedidos. Adicione backoff exponencial com jitter e um circuit breaker global.
O Que Este Guia Não Cobre
Este é um guia de harness, centrado na engenharia em torno do loop, não no modelo dentro dele. Alguns tópicos relacionados ficam deliberadamente fora do âmbito:
- Falhas de coordenação entre agentes (leituras obsoletas, estado órfão entre agentes): um problema diferente que merece o seu próprio artigo.
- Segurança de agentes (injeção de prompt, envenenamento de ferramentas): uma categoria de falha separada com o seu próprio modelo de ameaças.
- Seleção e ajuste fino de modelos. Este guia assume que já escolheu um modelo e está a depurar o sistema em seu redor.
- Serviços geridos de agentes, mencionados acima; os padrões aqui são para o caminho self-hosted.
Loops Infinitos: Quando o Agente Nunca Decide Que Terminou
Um agente entra em loop infinito quando não tem nem um limite máximo de passos nem uma forma de detetar que deixou de fazer progressos. A correção tem duas partes: manter um limite máximo como proteção de custo, e adicionar deteção de ausência de progresso que faz hash de cada chamada ferramenta+argumento e termina quando deteta a mesma chamada a repetir-se. No LangGraph esse limite é o recursion_limit(padrão 25 passos); ao ultrapassá-lo, o grafo lança uma GraphRecursionError.
A documentação do LangGraph descreve esse limite como atingir "o número máximo de passos antes de atingir uma condição de paragem", e aqui está a armadilha a entender: o recursion_limit não é proteção contra loops. É uma proteção que dispara depois depois de o loop já ter desperdiçado vinte e cinco passos e o gasto de API correspondente. A própria lógica de terminação aprendida pelo agente deveria pará-lo muito antes disso, e essa lógica pode falhar de forma independente. Um caso documentado de LangGraph mostra um agente de texto para SQL em loop até atingir o recursion_limit, apesar de existirem condições de paragem claras no prompt. Continuava a chamar a mesma ferramenta de query com o mesmo SQL com erro, e o issue foi fechado como "not planned". Leio isso como um sinal claro: não trate o limite como a sua condição de paragem. É o cinto de segurança, não os travões.
Aumentar o limite é simples; passa-o através da configuração ao invocar o grafo:
# The hard ceiling -- a backstop, not loop protection
graph.invoke(
{"messages": [("user", "Generate the quarterly report")]},
{"recursion_limit": 50},
)
A parte que realmente para um loop bloqueado é a deteção de progresso. O mecanismo é simples: fazer hash do nome da ferramenta mais os seus argumentos em cada passo, manter uma janela curta de hashes recentes, e parar quando detetar uma repetição.
import hashlib
def step_signature(tool_name: str, tool_args: dict) -> str:
payload = f"{tool_name}:{sorted(tool_args.items())}"
return hashlib.sha256(payload.encode()).hexdigest()
# Inside your loop: terminate if the same tool+args repeats within the window
seen = recent_signatures[-WINDOW:]
sig = step_signature(tool_name, tool_args)
if sig in seen:
raise StopReason("no_progress: repeated tool call detected")
recent_signatures.append(sig)
Isto deteta o agente que está tecnicamente "a executar" (a chamar ferramentas, a gerar tokens) mas a repetir a mesma ação falhada. O modo de falha aqui mapeado corresponde ao que a taxonomia MAST (IBM Research e UC Berkeley) denomina Unaware of Termination Conditions (FM-1.5), um dos modos de falha que a sua análise associa a falha total na tarefa.
Um limite de passos para o custo descontrolado. A deteção de ausência de progresso para o loop que está tecnicamente "a avançar" mas a repetir-se. Em produção são necessários ambos.
Overflow da Janela de Contexto: Quando o Loop Preenche o Seu Próprio Contexto com Lixo
Um loop de longa duração acumula todos os outputs de ferramentas, todos os pensamentos intermédios e todas as mensagens que produziu, e depois insere tudo de volta na janela de contexto em cada turno. Eventualmente a janela enche, e as chamadas são truncadas silenciosamente ou falham completamente. A correção é a sumarização do contexto em intervalos fixos: a cada N passos, comprime o histórico acumulado num resumo progressivo para que o contexto de trabalho se mantenha delimitado.
Imagine um agente de pesquisa que está a executar há uma hora. No passo 60 carrega o texto completo de todas as páginas que obteve, todos os resultados de pesquisa, todos os traços de raciocínio. Nada desse histórico bruto o ajuda no passo 61, mas tudo isso conta contra a janela, e o modelo está a pagar orçamento de atenção em tokens de que já não precisa. Quando a janela enche, o fornecedor trunca a partir de uma extremidade, e o agente perde silenciosamente a instrução que lhe foi dada no início.
O gatilho é uma decisão de ajuste, e há um ponto de referência útil para isso. O artigo do Mem0 sobre um sistema em produção real refere que o compressor do agente Hermes "dispara nos 50% da janela de contexto do modelo por padrão", com uma rede de segurança secundária nos 85% para sessões que crescem muito entre turnos. Cinquenta por cento é um ponto de partida sensato: comprima cedo o suficiente para que um único output de ferramenta grande não ultrapasse o limite antes da próxima compressão programada.
Nota: Overflow e degradação de raciocínio são problemas distintos, e a próxima seção aborda o segundo. Overflow é um limite rígido: os tokens acabam. A degradação é gradual: o modelo vai piorando com o tempo. antes de atingir a barreira. É preciso tratar ambos, e o limite do gatilho acima protege contra a barreira rígida.
Contexto delimitado é uma responsabilidade do harness, não uma funcionalidade do modelo. Sumarize em intervalos antes de a janela forçar uma truncação silenciosa.
Falhas Silenciosas de Ferramentas: Quando o Agente "Tem Sucesso" Sem Fazer Nada
Uma chamada de ferramenta retorna uma string vazia ou uma mensagem suave de "sem resultados", o modelo interpreta-a como um resultado válido, e o agente continua como se o passo tivesse funcionado, parecendo ter sucesso enquanto não faz absolutamente nada. A correção é uma porta de validação em cada retorno de ferramenta: verificar o esquema ou fazer uma verificação de sanidade do output antes de o modelo o ver, e expor uma falha real que o loop tenha de tratar em vez de um sucesso vazio.
Esta é insidiosa porque nada falha. Um developer que documentou modos de falha silenciosa em agentes em produção disse-o diretamente: os modelos interpretam strings vazias genéricas como no-ops válidos e continuam a executar sem consciência da falha. A query à base de dados que retornou zero linhas porque a ligação caiu parece idêntica, para o modelo, à query que legitimamente não encontrou nada. Assim o agente relata "sem registos correspondentes" e avança, e descobre uma semana depois que um terço das suas execuções estavam silenciosamente quebradas.
A porta de validação fica entre a ferramenta e o modelo:
def gate_tool_result(tool_name: str, result):
# Reject empties and soft errors before the model can rationalize them
if result is None or (isinstance(result, str) and not result.strip()):
raise ToolFailure(f"{tool_name} returned empty -- treat as failure, not no-op")
if isinstance(result, str) and result.lower().startswith(("error", "exception")):
raise ToolFailure(f"{tool_name} returned a soft error: {result[:120]}")
return result # validated -- safe to hand back to the model
O ponto não são as verificações exatas; as suas dependerão do que cada ferramenta retorna legitimamente. O ponto é que um valor de retorno não validado é uma decisão que entregou a um modelo estocástico, e o movimento padrão do modelo é continuar.
Um retorno de ferramenta não validado é uma falha silenciosa à espera de acontecer. Valide o output, não confie na chamada.
Degradação do Raciocínio em Contextos Longos: Quando o Agente Piora Quanto Mais Tempo Executa
Mesmo quando se mantém abaixo do limite máximo de contexto, a qualidade do raciocínio degrada-se à medida que o contexto cresce. Este é o efeito "lost in the middle", onde o modelo atende de forma fiável ao início e ao fim de um contexto longo mas perde o meio. A correção é compressão a meio do loop que preserva as restrições fixadas: comprimir o ruído, proteger as instruções estruturais.
O mecanismo tem um nome. O blog de engenharia da Anthropic refere-o como context rot: "à medida que o número de tokens na janela de contexto aumenta, a capacidade do modelo de recordar com precisão informação desse contexto diminui." Porque "cada token atende a todos os outros tokens," obtém-se n² relações par a par para n tokens, e a atenção do modelo fica mais dispersa quanto mais longo for o contexto.
Esse qualificador, protect the load-bearing instructions, é o essencial, e há um incidente documentado que mostra porquê. Num caso documentado, um agente OpenClaw apagou em massa a caixa de entrada de um utilizador durante uma compactação de contexto, porque a instrução de segurança que lhe tinha sido dada ("não tome medidas até eu dizer") foi removida do contexto ativo quando o histórico foi comprimido. A restrição que devia ser a última a desaparecer foi tratada como histórico comum e resumida.
Portanto, um simples "sumarizar tudo mais antigo que N turnos" é perigoso. A compressão tem de saber o que nunca pode abandonar:
PINNED = {"system_constraints", "safety_instructions", "active_task_spec"}
def compress_history(messages):
pinned = [m for m in messages if m.tag in PINNED] # never summarized
transient = [m for m in messages if m.tag not in PINNED]
summary = summarize(transient) # lossy is fine here
return pinned + [summary] # constraints survive intact
Isto é distinto do problema de overflow da secção anterior. Overflow é sobre ficar sem espaço; degradação é sobre o modelo piorar enquanto ainda há espaço. Pode estar nos 60% da janela e já estar a raciocinar mal.
Nota: Compressão que elimina uma restrição de segurança é uma classe de erro diferente de compressão que perde um resultado de pesquisa obsoleto. Marque as restrições, a especificação da tarefa e qualquer instrução "não faça X" como fixadas, e exclua-as completamente do sumarizador.
Compressão que elimina uma instrução de segurança é pior do que nenhuma compressão. Proteja as restrições fixadas ao comprimir.
Perda de Estado ao Reiniciar: Quando um Crash Significa Começar do Zero
Quando um agente de longa duração falha, seja por reinicialização, por kill por OOM ou por perda de ligação de rede, não há retoma a partir de um checkpoint por padrão. O loop começa do zero: refaz trabalho que já concluiu e, pior, pode re-executar ações que já realizou, como enviar o mesmo e-mail duas vezes ou re-executar uma chamada de API paga. A correção é o checkpointing: persistir o estado do loop após cada passo para que um reinício reidrate a partir do ponto em que parou, em vez de do início.
No LangGraph a escolha do backend de checkpoint é a escolha entre desenvolvimento e produção. A documentação de persistência do LangGraph descreve SqliteSaver como "ideal para experimentação e fluxos de trabalho locais" e PostgresSaver como "ideal para uso em produção", e o último é o que o próprio LangSmith usa. Os dois são deliberadamente paralelos no código, o que torna o contraste fácil de ver:
# Development -- single file, no server, do not ship this
from langgraph.checkpoint.sqlite import SqliteSaver
# Production -- survives the box it runs on
from langgraph.checkpoint.postgres import PostgresSaver
Dois detalhes que apanham as pessoas de surpresa. Primeiro, os pacotes de checkpoint instalam-se separadamente do LangGraph base (langgraph-checkpoint-sqlite e langgraph-checkpoint-postgres são dependências próprias), por isso uma máquina nova não terá o saver de Postgres até o adicionar. Segundo, cada operação de checkpoint precisa de um thread_id na configuração. Esse ID é o que liga uma determinada execução ao seu estado guardado, e um reinício sem o thread_id correto não reidrata nada.
Dica profissional: Os pacotes de checkpoint do LangGraph são instalações separadas.
langgraph-checkpoint-postgresnão é incluído pelo pacote baselanggraphportanto inclua-o nos seus ficheiros de requisitos de produção antes de descobrir da pior forma durante um incidente.
O n8n tem a mesma divisão desenvolvimento-versus-produção, apenas com nomes diferentes. A sua opção de memória integrada também se chama Simple Memory (ou Buffer Window Memory), e o caminho de produção é o nó Postgres Chat Memory para estado que tem de sobreviver a um reinício. A memória integrada mantém a conversa no processo em execução, o que está bem para testes e é um risco para uma carga de trabalho 24/7. Quem usa n8n com agentes em produção relata ter de migrar para um armazenamento suportado por Postgres depois de a memória em processo crescer até derrubar a instância. Se está a usar n8n e o seu agente precisa de lembrar algo entre reinícios, ligue-o ao Postgres Chat Memory desde o início.
O checkpointing com SQLite é uma conveniência de desenvolvimento. Sobreviver a um reinício em produção exige Postgres (LangGraph) ou um armazenamento suportado por Postgres (n8n).
Tempestades de Retry: Quando os Seus Próprios Agentes Fazem DDoS a um Serviço Inativo
Quando um serviço dependente fica inativo, retries ingénuos por execução transformam a sua frota de agentes num denial-of-service auto-infligido. A correção tem duas metades: backoff exponencial com jitter em cada agente para distribuir os retries no tempo, e um circuit breaker global que dispara após um limite de falhas partilhado e impede todo o grupo de bombardear um serviço que está claramente inativo.
A matemática é impiedosa. Como refere um artigo sobre padrões de retry , com dez agentes paralelos cada um a fazer dez retries, envia cem pedidos a um serviço que já está em baixo, porque o backoff de cada agente é por execução, não global. Backoff por agente isolado não resolve isto. Dez agentes que recuam educadamente ainda recuam em uníssono se todos começaram ao mesmo tempo, portanto fazem retry em ondas sincronizadas. Jitter quebra a sincronização ao aleatorizar a espera de cada agente; o circuit breaker quebra o grupo ao partilhar um único pedaço de estado de falha entre todos eles.
A metade do backoff é um problema resolvido em Python; a biblioteca tenacity trata do exponencial-com-jitter de forma limpa:
from tenacity import retry, stop_after_attempt, wait_random_exponential
@retry(wait=wait_random_exponential(multiplier=1, max=60), stop=stop_after_attempt(5))
def call_flaky_service(payload):
return downstream.post(payload)
O circuit breaker é a metade que tem de ser global: partilhado entre todos os agentes, não reinstanciado por execução. Quando as falhas ultrapassam um limiar, abre, todos os agentes falham rapidamente em vez de fazer chamadas, e após um período de arrefecimento deixa passar um único pedido de sonda para testar se o serviço voltou. Um breaker que vive dentro do próprio processo de cada agente não protege nada, porque nada é partilhado; o serviço inativo recebe ainda assim a totalidade dos cem pedidos.
Backoff por execução ainda permite que dez agentes bombardeiem um serviço inativo em uníssono. O circuit breaker tem de ser global para travar o grupo.
As Seis Falhas em Resumo
Antes da parte de infraestrutura, aqui está todo o catálogo num único lugar: a falha, o mecanismo que a causa, a correção do harness, e onde o parâmetro relevante fica em cada framework.
| Modo de falha | Mecanismo | Correção do harness | Parâmetro do framework |
|---|---|---|---|
| Loop infinito | Sem limite de passos ou verificação de progresso | Limite máximo + deteção de ausência de progresso | LangGraph recursion_limit (25) / n8n Max Iterations |
| Overflow de contexto | Histórico cresce até a janela encher | Sumarização por intervalos | Nível da aplicação (comprimir em ~50% da janela) |
| Falha silenciosa de ferramenta | Retornos vazios/suaves lidos como no-ops válidos | Porta de validação em todos os resultados de ferramentas | Wrapper de ferramenta ao nível da aplicação |
| Degradação do raciocínio | Atenção degrada-se à medida que o contexto cresce ("context rot") | Compressão a meio do loop que protege restrições fixadas | Nível da aplicação, com consciência de restrições |
| Perda de estado ao reiniciar | Sem checkpoint; loop recomeça do zero | Checkpointing persistente | LangGraph PostgresSaver / n8n Postgres Chat Memory |
| Tempestade de retry | Retries por execução em cascata sobre um serviço inativo | Backoff + jitter + circuit breaker global | tenacity + estado de breaker partilhado |
Uma nota para leitores de CrewAI, AutoGen, Dify ou um loop Python manual: os parâmetros do framework mudam, mas os seis padrões não. Deduplicação, sumarização por intervalos, validação de esquema, compressão com consciência de restrições, checkpointing e um circuit breaker global são conceitos agnósticos de framework. Os detalhes específicos de LangGraph e n8n aqui são referências concretas, não o limite de onde os padrões se aplicam.
Dimensionar um Deployment de Agentes em Produção
Cada padrão acima assume que controla o gestor de processos, a base de dados e o comportamento de reinício. O checkpointing não serve de nada se um loop com crash nunca voltar a subir, e um circuit breaker global precisa de um lugar para guardar o seu estado partilhado. Esse controlo é exatamente o que o self-hosting lhe dá e um serviço gerido fechado não dá, por isso a última decisão é dimensionar o servidor que o executa 24/7.
Para a maioria dos deployments de agente único (um agente, chamadas LLM a uma API externa, checkpointing básico em Postgres) uma instância pequena é suficiente: cerca de 2 GB RAM, 1 vCPU, and 60 GB of NVMe storage. O processamento pesado fica no lado do fornecedor do modelo; o seu servidor está a orquestrar, fazer checkpointing e guardar estado, não a executar inferência. Suba para aproximadamente 4 GB RAM, 2 vCPU, and 120 GB NVMe quando o agente é stateful e multi-passo com checkpointing em Postgres mais Redis para hidratação de sessão, ou quando está a executar fluxos de trabalho concorrentes que partilham o host.
A razão para preferir um VPS autogerido em vez de uma plataforma restrita é a mesma razão pela qual as correções funcionam: precisam de root. O seu próprio Postgres para checkpointing, o seu próprio Redis para estado de sessão, e um gestor de processos real como systemd or pm2para que quando um loop morrer, o supervisor o reinicie e reidrate a partir do último checkpoint em vez de recomeçar o trabalho. Toda essa história de recuperação depende de ser dono do ciclo de vida do processo.
Como executamos n8n como app de um clique no nosso próprio marketplace, essa parte da configuração é o caminho mais curto pelo nosso lado: pode implementar n8n num VPS Cloudzy com a configuração suportada por Postgres que o caminho de produção necessita, numa instância onde tem acesso root para adicionar o seu próprio Redis e supervisão de processos. É o mesmo footprint self-hosted descrito acima, onde é dono da base de dados e do comportamento de reinício, o que é o que faz o checkpointing e a recuperação automática realmente funcionar.
Os padrões do harness são apenas tão fiáveis quanto o servidor em que correm. O checkpointing não serve de nada se o processo nunca reiniciar.
Perguntas frequentes
Como Impedir que o Meu Agente LangGraph Entre em Loop Infinito?
Use dois mecanismos em conjunto. Defina recursion_limit como limite máximo de passos (o padrão é 25) para que um loop descontrolado não possa consumir orçamento ilimitado, e adicione deteção de ausência de progresso que faz hash de cada chamada ferramenta+argumento e termina quando a mesma chamada se repete dentro de uma janela recente. O limite isolado é uma proteção que dispara depois do desperdício já ter ocorrido, não é proteção real contra loops. A deteção de progresso é o que realmente para um loop bloqueado.
Qual É o recursion_limit Certo para LangGraph em Produção?
Não há um número universal. Dimensione-o para o número máximo de passos legítimos que o seu agente deveria alguma vez precisar, mais uma margem, e trate-o estritamente como uma proteção de custo. Aumentar o limite não faz um agente em loop convergir. Se o seu agente está a atingir um limite alto, a correção é a deteção de progresso, não um limite mais alto.
Por Que É que o Meu Agente de IA n8n Continua a Atingir o Max Iterations?
Atingir o limite Max Iterations significa que o agente não está a convergir: está a dar mais passos do que o limite permite sem atingir uma paragem. Aumente o limite apenas se a tarefa legitimamente precisar de mais passos; caso contrário, trate-o como um sinal de que o agente está bloqueado. Atenção a uma armadilha específica: GitHub issue #22771 relata que quando o limite de iterações é atingido com "On Error: Continue" definido, a execução pode ser encaminhada para o output de sucesso em vez do output de erro, por isso uma execução falhada com o limite atingido pode parecer um sucesso no seu fluxo de trabalho.
Como Persistir o Estado do Agente Entre Reinícios?
No LangGraph, use checkpointing PostgresSaver em vez de SqliteSaver, que é pensado para desenvolvimento local. No n8n, use o nó Postgres Chat Memory em vez da memória integrada em processo. Ambos requerem uma base de dados persistente, e no LangGraph cada operação de checkpoint precisa de um thread_id que ligue uma determinada execução ao seu estado guardado.
O Que Causa a Degradação do Raciocínio em Execuções Longas de Agentes?
A qualidade do raciocínio cai à medida que o contexto cresce, mesmo antes de atingir o limite máximo de tokens. Este é o efeito "lost in the middle", onde o modelo atende ao início e ao fim de um contexto longo mas perde o meio. O blog de engenharia da Anthropic refere o mecanismo subjacente como "context rot": porque cada token atende a todos os outros tokens, obtêm-se n² relações par a par e a atenção do modelo fica mais dispersa à medida que o contexto se alonga. A correção é compressão a meio do loop que sumariza o histórico obsoleto enquanto mantém as restrições fixadas e as instruções de segurança intactas.
Etiquetado