Tracking 24/7: do agente que responde "quanto custa?" ao agente que avisa "baixou agora"
Tracking 24/7: do agente que responde "quanto custa?" ao agente que avisa "baixou agora"
Você montou o agente. Ele lê o catálogo, abre o Mercado Livre, devolve o preço. Bonito. O usuário pergunta uma vez, recebe a resposta, vai dormir.
Aí ele volta e pergunta de novo. E de novo. E quer saber se baixou. E quer ser avisado quando baixar. Pronto: agora você não tem mais um chatbot, você tem um problema de engenharia diferente. O agente precisa viver sozinho, acordar em horário marcado, decidir se algo importante mudou, e cutucar o usuário antes que ele cutuque você.
Neste post a gente sai do agente reativo (espera input, responde, dorme) e chega no agente agendado de produção: cron, webhook, notificação, idempotência, deduplicação de alerta e janelas de monitoramento que não estouram a fatura no fim do mês.
TL;DR
- O que é: arquitetura para transformar um agente reativo em um agente que monitora 24/7 e dispara alertas quando algo muda.
- Stack: Laravel scheduler + queue, Postgres com
unique constraintpara dedupe, Claude Agent SDK e/ou Routines para o ciclo de execução, webhook + push para a notificação. - Custo/Acesso: depende da cadência. Polling de minuto custa caro. Batch API da Anthropic corta 50% quando o trabalho não precisa ser real-time.
- Link útil: Claude Code — Run prompts on a schedule.
O salto: de chat reativo para agente que vive sozinho
Agente reativo é fácil de raciocinar. Tem turno. Tem usuário do outro lado. Tem contexto vivo na conversa. Quando ele erra, o usuário corrige. Quando ele alucina, o estrago é local. Custo é previsível porque o ciclo é "pergunta → resposta".
Agente agendado é outra fera. Não tem ninguém olhando. O contexto é frio: você precisa carregar estado de algum lugar (DB, cache, vector store) toda vez que ele acorda. O custo escala com a cadência, não com o uso. E o erro propaga em silêncio: o agente pode disparar 800 push notifications às 3 da manhã e você só vai descobrir quando o time de suporte abrir o WhatsApp.
Três sabores de "agente que vive sozinho":
- Cron fixo — acorda em horário marcado. Simples, previsível, mas ignora o mundo entre as execuções.
- Polling adaptativo — o agente decide quando acordar de novo. Bom quando a frequência depende do que ele observou.
- Webhook-driven — alguém de fora avisa que algo mudou. Zero polling, custo só quando há sinal real.
Os três coexistem no mesmo produto. O tracker de preço é cron, o disparo de alerta é webhook, o follow-up no usuário é adaptativo.
As três janelas de monitoramento
Cron fixo
A maneira mais óbvia. No Laravel:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
$schedule->job(new CheckPriceDrops)
->everyFiveMinutes()
->withoutOverlapping(10) // mata sobreposição se a anterior ainda rodando
->onOneServer() // se rodar em cluster, só 1 worker pega
->runInBackground();
}
withoutOverlapping() é o tipo de coisa que parece detalhe e não é. A documentação do Laravel deixa claro: sem ele, scrape de 5min que demora 7min vira tempestade de jobs concorrentes batendo no mesmo endpoint, e o ML te bloqueia em 20 minutos.
Cron é bom quando o ritmo é estável. Ruim quando você quer rastrear 50 mil produtos com cadências diferentes. Aí cada produto vira uma Job na fila, com sua própria cadência calculada por categoria.
Polling adaptativo
Se o agente é quem decide quando voltar a olhar, você economiza chamadas em horas mortas. O Claude Code expõe isso com o /loop sem intervalo: o modelo escolhe um delay entre 1min e 1h baseado no que viu na iteração anterior. A doc oficial descreve assim:
"Curtos quando uma build está terminando ou um PR está ativo, mais longos quando nada está pendente."
Aplicado ao tracker de preço: se o produto oscilou três vezes na última hora, vale apertar a frequência. Se está estável há 12 horas, vale recuar. Quem decide é o agente, com base em sinal, não em cron-cego.
Cuidado: deixar o LLM decidir o intervalo significa que o LLM está no caminho crítico do custo. Coloque um teto no harness (mínimo 5min entre execuções, máximo 6h) e nunca confie no modelo para respeitar isso sozinho.
Webhook-driven
A melhor janela é "não ter janela". Se o vendor expõe webhook, você não roda nada — espera o evento chegar.
Problema: webhook é entregue várias vezes. Provedor faz retry. Rede cai. O mesmo evento price_changed pode bater no seu endpoint 4 vezes em 30 segundos. Sem dedupe, são 4 push notifications no celular do usuário.
Idempotência e dedupe: o problema que ninguém vê até tomar
A regra do Svix é simples: todo evento tem um event_id estável, e o receptor mantém uma tabela com unique constraint nesse campo. Se o INSERT falhar com violação de unicidade, é duplicata, descarta.
// database/migrations/xxxx_create_processed_events_table.php
Schema::create('processed_events', function (Blueprint $table) {
$table->string('event_id')->primary();
$table->string('source');
$table->timestamp('created_at')->useCurrent();
$table->index('created_at'); // pra job de limpeza
});
// app/Http/Controllers/PriceWebhookController.php
public function handle(Request $request)
{
$eventId = $request->header('X-Event-Id');
try {
DB::table('processed_events')->insert([
'event_id' => $eventId,
'source' => 'mercado-livre',
]);
} catch (QueryException $e) {
// unique violation = duplicata, ack e segue a vida
return response()->noContent();
}
ProcessPriceChange::dispatch($request->all());
return response()->noContent();
}
TTL de 7 dias resolve 99% dos casos — a maioria dos provedores para de tentar antes disso. Job diário de limpeza:
$schedule->call(fn () => DB::table('processed_events')
->where('created_at', '<', now()->subDays(7))
->delete()
)->daily();
Volume alto? Troca a tabela por Redis com TTL nativo:
if (! Redis::set("event:{$eventId}", 1, 'EX', 604800, 'NX')) {
return response()->noContent(); // duplicata
}
NX só seta se não existir. EX 604800 expira em 7 dias. Uma chamada, atômico, sem job de limpeza.
Dedupe de alerta é diferente de dedupe de evento
Aqui mora a confusão clássica. Você pode ter recebido um único webhook do ML — único event_id, sem duplicata — e ainda assim disparar 14 alertas para o mesmo usuário, porque seu agente roda de 5 em 5 minutos e cada execução vê o preço abaixo do limite e quer avisar.
Dedupe de alerta tem chave diferente: produto + usuário + janela de tempo. Algo como:
$alertKey = sprintf(
'alert:%d:%d:%s',
$userId,
$productId,
now()->format('Y-m-d') // 1 alerta por dia, no máximo
);
if (! Redis::set($alertKey, 1, 'EX', 86400, 'NX')) {
return; // já avisei esse user hoje, sai
}
PushNotification::send($userId, $message);
Se o requisito for "1 alerta por queda significativa", a chave inclui o threshold cruzado: alert:{user}:{product}:below-100 e expira só quando o preço subir de novo acima do threshold (você apaga a chave manualmente nesse momento). É um state machine pequeno escondido numa chave de Redis.
Mão na massa: o tracker que não estoura custo
Passo 1: estado fora do prompt
A primeira tentação é mandar o histórico inteiro de preço para o modelo decidir se houve queda. Não faça isso. Cada execução vai ler 30 mil tokens de contexto à toa. O agente lê o last_known_price de uma tabela e decide com base em 2 números.
// app/Jobs/CheckPriceDrops.php
public function handle(): void
{
Product::watching()->chunk(100, function ($products) {
foreach ($products as $product) {
$current = $this->scraper->fetch($product->external_id);
if ($this->isSignificantDrop($product, $current)) {
ProcessAlert::dispatch($product->id, $current);
}
$product->update(['last_known_price' => $current]);
}
});
}
private function isSignificantDrop(Product $p, int $current): bool
{
if ($p->last_known_price === null) return false;
$drop = ($p->last_known_price - $current) / $p->last_known_price;
return $drop >= 0.10; // 10% de queda
}
Repare: a regra de "queda significativa" está em código PHP, não no prompt. Regra dura sai do LLM. O LLM entra só no que precisa de julgamento, como escrever a mensagem do alerta personalizada para o usuário, decidir se a queda parece glitch ou promoção real, ler a página e detectar mudança de modelo.
Passo 2: o agente na hora do alerta
Quando o pipeline detecta queda real, o agente entra em cena para enriquecer o alerta:
// app/Jobs/ProcessAlert.php
public function handle(ClaudeAgent $agent): void
{
$product = Product::find($this->productId);
$alertKey = "alert:{$product->user_id}:{$product->id}:below-{$this->threshold}";
if (! Redis::set($alertKey, 1, 'EX', 86400 * 7, 'NX')) {
return;
}
$message = $agent->run(
prompt: "Preço caiu de R\$ {$product->last_known_price} para R\$ {$this->newPrice}. "
. "Produto: {$product->title}. Escreva um alerta curto, sem hype.",
tools: ['web_search'], // pra cruzar com histórico do produto
);
PushNotification::send($product->user_id, $message);
}
O Redis::set NX aqui dobra como dedupe e como cache de "já avisei". Sai disso só se o preço voltar pra cima do threshold (em outro job).
Passo 3: cron no harness — Claude Code, Routines ou Laravel?
Tem três caminhos, cada um com trade-off claro. A própria doc da Anthropic compara:
| Cloud (Routines) | Desktop | /loop |
|
|---|---|---|---|
| Roda sem máquina ligada | Sim | Sim | Não |
| Acesso a arquivos locais | Não | Sim | Sim |
| Intervalo mínimo | 1h | 1min | 1min |
| Persistente entre restarts | Sim | Sim | Só com --resume |
Para um tracker em produção, a resposta é nenhum dos três puros: o Laravel scheduler roda o ciclo (de 5 em 5 minutos), e o agente entra só nas decisões que precisam de raciocínio. Cron do Claude Code é ótimo para babysitar uma execução durante o dev, mas a sessão expira em 7 dias e morre quando você fecha o terminal. Routines da Anthropic foram lançadas em abril de 2026 e resolvem o "sem máquina ligada", mas o intervalo mínimo de 1h não serve para alertar oscilação de preço em near real-time.
A regra prática: cron do produto fica no seu harness (Laravel, Rails, n8n, o que for). Cron do Claude Code fica para automação interna do dev — babá de PR, revisão noturna, smoke test.
Passo 4: webhook como atalho para custo
Se o ML manda webhook quando o preço muda, você desliga o cron de scrape e só reage:
Route::post('/webhooks/ml/price', [PriceWebhookController::class, 'handle'])
->middleware('verify.ml.signature');
Pulou o polling, pulou o custo de N execuções por hora, pulou o problema de rate limit no scrape. O agente só entra no momento em que há sinal real, e a fatura escala com mudança real de preço, não com tempo decorrido.
Custo: como não acordar com fatura de R$ 4 mil
Polling de 1 minuto em 50 mil produtos é 72 milhões de execuções por dia. Mesmo com regra dura em PHP e LLM só no alerta, você precisa de teto. Quatro coisas para fazer no dia 1:
- Tool failure rate em janela curta. Recomendação consolidada: se o tool de scrape começar a falhar, retry amplifica custo de LLM (mais contexto, mais turnos). Alarme de 5 em 5 minutos quando a taxa de erro passa de 10%.
- Batch API quando real-time não importa. Análise de tendência semanal, relatório consolidado, classificação de catálogo: nada disso precisa de resposta agora. A Batch API custa metade e processa em até 24h. Tráfego batch costuma ser 20 a 40% do gasto total, vale a separação.
- Sampling de logs. Logar cada chamada do agente em produção alta vira fatura de observabilidade maior que a do LLM. Sample 10 a 20% pra trace detalhado, conta agregada para o resto.
- Cache de prompt do sistema. Prompt do sistema do agente é estável entre execuções. Prompt caching da Anthropic corta 90% do custo dos tokens cacheados. Em tracker que executa 10 mil vezes por hora, é a diferença entre R$ 50/dia e R$ 500/dia.
Limitações e pontos de atenção
/loopmorre com a sessão. Não use para nada que precise rodar quando você fecha o laptop. A própria doc avisa: tasks só disparam enquanto o Claude Code estiver rodando e idle.- Routines têm 1h de intervalo mínimo. Se o produto exige cadência menor, é Laravel scheduler ou solução self-hosted, não cloud da Anthropic.
- Webhook sem queue é bug esperando acontecer. Receba, valide assinatura, dedupe, dispare job assíncrono. Processar inline trava o endpoint e o vendor te marca como não-saudável.
- LLM como decisor binário (alerta ou não) tem variância. Mesma entrada, decisões diferentes em runs distintos. Use o modelo para escrever o alerta e enriquecer o contexto. A decisão
dispara/não-disparafica em código determinístico. - Notificação repetida queima confiança. Usuário que recebe 3 alertas iguais marca o app como spam e desinstala. Deduplicação de alerta é parte do produto, não detalhe de infra.
Para o dev que quer ir mais fundo
Se você está montando um tracker, comparador de preço ou qualquer agente que precisa viver sozinho, dois conteúdos próximos do blog complementam este post:
- Comparador de preços com agente — a base reativa que vira o tracker proativo deste artigo.
- Harness de agentes em produção — como o ciclo de execução, retry e observabilidade entram no produto.
E se você quer construir produtos de IA com engenharia de verdade, sem prompt mágico e sem stack hype, o Clã Beer & Code é o ambiente onde a galera está mexendo nesse tipo de problema todo dia. Entra na lista de espera — o foco é exatamente esse: dev PHP/Laravel construindo IA aplicada com produto real.
FAQ rápido
Por que /loop do Claude Code não serve para tracker em produção?
Porque ele é session-scoped. A doc é explícita: tasks param de disparar quando o terminal fecha, e expiram em 7 dias mesmo com a sessão aberta. Para 24/7 real, vai de Routines (cloud) ou scheduler do seu framework.
Redis ou Postgres para dedupe?
Volume baixo (até centenas de eventos por minuto) e Postgres já tem unique constraint é suficiente, sem dependência nova. Volume alto e latência apertada vai de Redis com SET NX EX. Os dois aceitam o mesmo padrão de chave estável + TTL.
Por que separar regra dura (queda 10%) do LLM? Porque LLM custa, varia entre runs e não tem garantia formal. Regra de negócio dura precisa ser auditável, testável e barata. O LLM entra onde julgamento humano agregaria valor: redação do alerta, detecção de promoção fake, classificação semântica de motivo.
Quanto custa um tracker desses por mês? Depende muito da cadência e do tamanho do catálogo. Estimativa rasa pra 1.000 produtos com checagem a cada 15min e LLM só no alerta (~50 alertas/dia, prompt cacheado): faixa de R$ 30 a R$ 100/mês em modelos Haiku/Sonnet. Se você joga LLM em cada checagem, multiplica por 100.
Conclusão
O agente reativo é o "olá mundo" da era de IA aplicada. Funciona, impressiona na demo, e morre na primeira vez que o usuário pede pra ser avisado em vez de perguntar. O salto pro agente que vive sozinho não é prompt melhor: é arquitetura, com cron, webhook, dedupe, idempotência e custo controlado.
O próximo passo dessa tecnologia já está aparecendo: agente que não só avisa "baixou agora", mas age — abre o checkout, separa o cupom, aciona o cartão, abre ticket de suporte. Aí entra um problema novo (autorização, reversibilidade, audit trail), mas a fundação é a mesma: harness com janela de monitoramento, dedupe e custo controlado.
Quem domina isso constrói produto de IA de verdade. O resto fica preso no chat.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
Você também pode gostar
Hands-on: construindo um agente de ofertas em 80 linhas com Claude, tool use e um reranker
Tutorial reproduzivel em Python: agent loop com Claude, busca na web, rerank do Cohere e saida em JSON estruturado. Esqueleto de 80 linhas para voce expandir e levar para producao.
Chatbot não é agente: o teste dos 3 turnos que separa brinquedo de produto
Três perguntas simples sobre um produto real — preço hoje, reviews recentes, disponibilidade no CEP — quebram qualquer chatbot cru. O que separa brinquedo de produto não é o modelo. É o harness: a camada que transforma um LLM em agente confiável, com tool use, estado e validação contra o mundo real.
Multi-agent com Claude: separando search, judge e writer (e quando isso é overengineering)
Quando vale a pena quebrar o agente único em sub-agentes especializados (search, judge, writer) e quando isso vira complexidade desnecessária. Padrão de orquestração com Claude, custo real em tokens e quando voltar para single-agent.
LLM-as-a-Judge: avaliação automatizada do seu agente de ofertas sem abrir planilha
Como montar um juiz LLM que pontua cada resposta do agente contra uma rubrica objetiva: preço correto, link válido, sentimento de review coerente. Você sai do achismo e transforma iteração em ciclo mensurável.