BrunoP.Blog

Meu agente de IA entrou em loop e quase torrou o orçamento: o 'balde de fichas' que segura a conta

Um agente autônomo entrou em loop chamando ferramenta cara e virou um DoS financeiro contra mim mesmo. Te mostro o disjuntor clássico que todo sistema sério usa — visualizado como um balde de fichas pingando.

Esses dias eu deixei um agente de IA rodando sozinho de madrugada — daqueles que recebem uma tarefa, pensam, chamam ferramentas, leem o resultado e decidem o próximo passo. A ideia era nobre: ele ia organizar um monte de dados pra mim enquanto eu dormia. Acordei, abri o painel de custos da API e quase derrubei o café no teclado. O troço tinha entrado em loop — chamando, sem parar, uma ferramenta cara que faz uma consulta externa — e ficou a noite inteira metralhando a mesma chamada milhares de vezes.

Não foi um ataque de fora. Fui eu mesmo, sem querer, montando um DoS financeiro contra a minha própria conta. O agente não tinha noção de "freio": pra ele, se a tarefa não terminou, é só tentar de novo. E de novo. E de novo. Felizmente o estrago foi pequeno porque eu tinha um teto de gasto na própria plataforma — mas o susto me fez voltar pra um padrão de backend que eu já conhecia bem e que, sinceramente, deveria estar ali desde o primeiro dia: o token bucket, o "balde de fichas".

O perrengue novo de 2026: o agente que se ataca sozinho

A gente sempre pensou em rate limiting como defesa contra os outros: o bot que tenta logar mil vezes, o scraper que suga sua API, o engraçadinho do F5. Mas com agentes autônomos surgiu uma categoria nova de problema — o sistema que se ataca a si mesmo. Um loop mal fechado, uma condição de parada que nunca é satisfeita, uma ferramenta que devolve erro e o agente reinterpreta como "tenta de novo": pronto, você tem uma máquina perfeita de queimar dinheiro e estourar cota.

O detalhe cruel é que o custo é real e imediato. Cada chamada de ferramenta cara — uma busca paga, uma geração de imagem, uma consulta a um banco externo — tem preço. Multiplica por milhares de iterações por minuto e o prejuízo vira exponencial enquanto você dorme tranquilo achando que "está trabalhando".

O balde de fichas, explicado de verdade

O token bucket é provavelmente o algoritmo de rate limiting mais usado do mundo — está na borda da AWS, do Cloudflare, do nginx, de praticamente toda API séria. E a beleza dele é que cabe numa imagem mental simples: imagina um balde.

  • O balde tem uma capacidade máxima de fichas (digamos, 10).
  • Uma torneira pinga fichas novas dentro dele a uma taxa constante (por exemplo, 2 fichas por segundo).
  • Toda vez que uma requisição chega, ela precisa pegar uma ficha pra ser atendida.
  • Se tem ficha, a requisição passa e a ficha some. Se o balde está vazio, a requisição é recusada na hora.
  • O balde nunca transborda: quando enche, as fichas que pingariam a mais simplesmente se perdem.

É só isso. E essa simplicidade é exatamente o que dá a propriedade mais valiosa: ele permite rajadas curtas, mas limita a média. Se o balde está cheio e chegam 10 pedidos de uma vez, todos passam — ótimo pra picos legítimos. Mas se os pedidos continuam vindo mais rápido do que a torneira repõe, o balde esvazia e o excesso começa a bater na parede. É o disjuntor: deixa o uso normal fluir e corta o abuso, sem você ter que adivinhar um limite rígido de "X por segundo".

O loop do meu agente teria batido nesse muro depois de poucos segundos. Em vez de milhares de chamadas caras, ele teria conseguido só o punhado que o balde permite por janela de tempo — e cada recusa seria um sinal claro de "ei, tem algo errado aqui".

Por que não um contador simples?

A pergunta óbvia é: por que não só contar "máximo 100 por minuto" e zerar o contador a cada minuto? Esse é o fixed window, e ele tem um furo clássico: o problema da borda da janela. Se o seu limite é 100 por minuto, dá pra mandar 100 às 12:00:59 e mais 100 às 12:01:00 — 200 chamadas em dois segundos, dentro das regras. O token bucket não tem esse buraco porque ele raciocina sobre taxa contínua de reposição, não sobre blocos de tempo recortados.

Tem um primo dele chamado leaky bucket, que pensa ao contrário: as requisições entram num balde e vazam por um furo a uma taxa fixa, suavizando o fluxo de saída. O token bucket é mais permissivo com rajadas; o leaky bucket é mais rígido e constante. Pra segurar um agente em loop, eu prefiro o token bucket: ele tolera o trabalho legítimo em lote e só morde quando o ritmo passa do limite sustentável.

Curiosidades que eu gosto

  • Ele vive escondido na sua frente. Aquele cabeçalho Retry-After ou X-RateLimit-Remaining que você já viu numa resposta HTTP 429? Quase sempre é um token bucket (ou primo) dizendo "seu balde está vazio, volta daqui a tanto".
  • Dá pra implementar em ~15 linhas. Você nem precisa de uma thread pingando fichas. O truque é guardar só dois números — quantas fichas tinha e quando foi a última vez — e calcular "quantas pingaram desde então" na hora em que o pedido chega. Lazy refill. Elegante demais.
  • É a base do "burst" que você adora. Quando um serviço te deixa estourar o limite por alguns segundos antes de te frear, é o balde cheio sendo gasto. A capacidade do balde é o tamanho da rajada permitida.

Minha visão honesta

Depois desse susto, minha regra virou simples: todo agente autônomo nasce com coleira. Antes de soltar qualquer loop que chama ferramenta paga, eu coloco um token bucket na frente da chamada cara — e, de quebra, um teto absoluto de gasto por execução. Não é desconfiança do modelo; é higiene de engenharia. O mesmo motivo pelo qual a gente põe fusível na tomada não é achar que o aparelho é ruim — é que falhas acontecem, e o custo de não ter o freio é alto demais.

E aqui mora o ponto que eu mais quero passar: rate limiting não é "coisa chata de infra". É controle de custo, é previsibilidade, é o que separa um protótipo que vira boleto surpresa de um produto que você pode deixar rodando sem medo. Quando eu construo algo pra um cliente, esse freio entra junto — porque o pior bug não é o que quebra, é o que funciona perfeitamente fazendo a coisa errada milhares de vezes.

Chega de papo, sente o freio na mão 👇

Montei um simulador do balde aqui embaixo. Mexe na torneira (fichas por segundo) e na capacidade do balde, e dispara pedidos clicando. Quando quiser ver o pesadelo, liga o modo "agente em loop": ele vai metralhar requisições igual ao meu da madrugada. Olha o balde esvaziar, os pedidos baterem no muro (vermelho) e o contador de "custo economizado" subir — é exatamente esse dinheiro que o freio salva.

token-bucket.js

Simulação 100% local em JS, sem rede. O "preço" por requisição é fictício, só pra você sentir o estrago.

Se você ligou o modo loop e ficou olhando o balde tentar (e falhar em) acompanhar a metralhadora, você sentiu na pele por que esse padrãozinho é tão querido. Quatro ou cinco linhas de lógica que separam um agente bem-comportado de uma fatura que dá medo de abrir. É mais ou menos assim que eu trabalho: pego o sufoco real de uma madrugada e transformo numa peça de engenharia que você pode tocar. Se você tem um produto, um agente ou uma API que precisa rodar sem te dar susto no fim do mês, bora trocar uma ideia?

Vamos conversar sobre seu projeto