Radar de Notícias de IA — Arquitetura
OpenClaw · Arquitetura do agente

Radar de
Notícias de IA

Vigia as contas oficiais de IA no X, decide o que importa para cada grupo, e avisa na voz certa — código para a mecânica, uma LLM só para o julgamento.

📡 4 contas oficiais 💬 2 grupos WhatsApp 🔒 classificador isolado 59 testes automatizados

Em uma frase

O que este sistema faz

Quando a Anthropic ou a OpenAI anuncia algo no X — um modelo novo, uma capacidade, uma mudança de preço — o radar percebe em segundos, decide se aquilo interessa a cada um de dois grupos de WhatsApp, e, se interessar, escreve e posta um resumo na voz apropriada de cada grupo: caloroso e simples para a família que está aprendendo IA; técnico e direto para a equipe de desenvolvimento.

Roda sozinho, 24 horas por dia, num servidor. Ninguém precisa acompanhar o X. O trabalho todo é: captar o sinal, separar do ruído, e entregar no tom certo — sem repetir a mesma notícia e sem incomodar de madrugada.

A ideia central

Código decide a mecânica. A LLM decide o julgamento.

Este é o princípio que organiza tudo o resto — e foi uma virada consciente. A primeira versão deixava a LLM orquestrar o processo inteiro: decidir relevância, postar, confirmar que saiu, deduplicar. Parecia natural ("a IA é inteligente, deixa ela cuidar"), mas era frágil: a LLM é não-determinística e não tem como provar que uma mensagem foi entregue — ela só afirma que foi. O fluxo virava uma costura de linguagem natural, cheia de pontos onde algo se perdia ou se repetia.

A resposta foi separar as duas naturezas de trabalho. Há decisões que pedem julgamento — "isso é relevante para a família?", "como escrever isso de um jeito que a equipe de dev valorize?" — e a LLM é ótima nisso. E há decisões que pedem execução mecânica — "já mandei esse tweet?", "estamos dentro do horário?", "o envio deu certo?" — onde o que se quer é um protocolo determinístico, verificável e testável. Código é melhor nisso.

A LLM (agente radar) só…

Julga e escreve

Recebe um post, decide se é relevante para cada grupo, e — se for — escreve a mensagem pronta na voz daquele grupo. Devolve um JSON CLASSIFICACAO. Não posta, não deduplica, não confirma nada.

O worker (código) faz…

Todo o resto

Janela de horário, dedup, envio, tratamento de falha — em código determinístico, coberto por testes. O parecer da LLM é tratado como dado não-confiável e revalidado antes de qualquer uso.

Essa separação eliminou uma classe inteira de bugs: como a LLM não está mais no caminho crítico do estado, não há como uma reclassificação inconsistente "perder" uma publicação já em andamento — o código guarda o que decidir e não muda de ideia.

Como funciona

Do post ao grupo, em quatro estações

Cada post das contas oficiais atravessa este pipeline. A ideia-guia: a durabilidade vem primeiro — o post é gravado em disco antes de qualquer decisão, então nada se perde se o processo cair no meio.

01 · CAPTAÇÃO

twitterapi.io

A cada post novo de @AnthropicAI · @claudeai · @ClaudeDevs · @OpenAI, o provedor dispara um webhook para o servidor.

at-least-once
02 · RECEPÇÃO

Adaptador

Autentica, valida os tipos, filtra a conta, e grava o post no outbox (fila em disco) de forma atômica. Responde rápido ao provedor.

outbox durável
03 · CLASSIFICAÇÃO

Agente radar

O worker entrega o post ao classificador LLM isolado, que devolve, por grupo: relevante? e o texto pronto. O parecer é gravado no outbox.

só julga
04 · ENTREGA

Os grupos

Projeto IA — família, manchete calorosa 🍡
Sidlar Desenv — equipe, manchete sóbria 📡

voz por audiência

O ponto sutil está entre a estação 3 e a 4: quando o classificador devolve o parecer, o worker grava esse parecer no outbox antes de tentar enviar. Assim, se o envio para um grupo falha e a máquina reinicia, na retomada o worker reusa o parecer que já estava no disco — não pede uma nova classificação à LLM (que poderia mudar de ideia e abandonar o grupo que faltava). Classifica-se uma vez por notícia, não a cada tentativa.

O resultado

Como fica uma mensagem

A mesma notícia, escrita duas vezes pela LLM — cada uma na voz do seu grupo. Toda mensagem abre por uma manchete de uma linha que já diz o quê (nunca o quando: a entrega pode atrasar horas pela janela e pela fila), depois o corpo. O selo identifica de relance que é notícia: 🍡 para a família, 📡 para a equipe.

Projeto IA · família
🍡 Saiu modelo novo do Claude!
A Anthropic lançou o Claude Haiku 5, o modelo mais rápido e mais barato da linha. Já dá pra usar na API.
👉 anthropic.com/news
Sidlar Desenv · equipe
📡 Novo modelo: Claude Haiku 5
Anthropic lançou o Claude Haiku 5, descrito como o modelo mais rápido e mais barato da linha, disponível via API. anthropic.com/news

A manchete é redação, não um selo fixo colado pelo código — quem a escreve é a mesma LLM que escreve o corpo, respeitando a voz de cada grupo. Na família ela já entrega o "tipo" da notícia de graça ("Saiu modelo novo"); na equipe, o selo 📡 e o negrito nos termos técnicos dão o tom de comunicado.

As peças

Cada arquivo, uma responsabilidade

Componentes pequenos e testáveis, cada um com um papel claro. O estado do sistema é modesto: essencialmente uma lista de "o que eu já mandei".

Infra · Python

adaptador.py

A porta de entrada e o motor. Recebe o webhook, mantém o outbox durável e roda o laço do worker.

  • webhook: autentica, valida, filtra a conta (fail-closed)
  • outbox atômico (fsync) — não perde em deploy nem queda
  • laço do worker + fila morta (DLQ) + redrive
  • /health: denuncia trava, backlog e DLQ
Orquestração · Python

worker.py

A lógica determinística. Classifica uma vez, valida o parecer e entrega grupo a grupo.

  • janela de horário (8h–22h BRT)
  • valida o parecer da LLM (tipos, tamanho, mídia)
  • dedup por ID; envio at-most-once
  • mídia só de pbs.twimg.com
Estado · Python

dedup.py

A memória: quem já foi avisado. Um registro de IDs por grupo, gravado de forma atômica.

  • lista de tweet_id já enviados, por grupo
  • gravação atômica sob lock (flock)
  • corrupção falha alto — nunca republica no escuro
  • não expira: o mesmo post nunca sai duas vezes
Envio · Python

envio.py

A saída. Chama o message send do OpenClaw e responde uma pergunta só: dá pra parar de tentar?

  • sucesso ou timeout ambíguo → não reenviar
  • erro claro (gateway fora) → tentar de novo
  • sem "confirmação por log" — simples e honesto
  • mensagem e mídia vão como argumentos, sem shell
Ponte · Python

classificador.py

O elo com a LLM. Manda o post ao agente radar e extrai o CLASSIFICACAO da resposta.

  • chama o gateway (modelo openclaw:radar)
  • gateway fora ≠ notícia ruim: não gasta tentativa
  • parser robusto a marcador forjado no texto do post
Julgamento · LLM

A skill do radar

O critério, em linguagem natural: o que vale para cada grupo, em que voz. É o único lugar "inteligente".

  • filtro rígido por audiência ("na dúvida, cala")
  • escreve a mensagem pronta na voz do grupo
  • usa só o que está no post — não acessa links
  • devolve o CLASSIFICACAO por grupo
Isolamento (least privilege). O texto de um tweet é conteúdo de terceiro — nunca uma instrução. Por isso o agente radar roda separado do agente principal, num workspace próprio, com uma única ferramenta liberada: ler arquivo, e só dentro do próprio workspace. Sem banco, sem SSH, sem segredos, sem enviar mensagem. Mesmo que um post tente enganar o modelo, não há o que ele possa alcançar.

O contrato

Nunca duplica. Aceita perder — raramente.

Esta é a decisão mais importante do projeto, e ela é deliberada. O ideal seria "cada notícia chega exatamente uma vez" (exactly-once). Mas isso é impossível de garantir quando o destino — o WhatsApp — não oferece uma chave de idempotência: não há como enviar e ter certeza absoluta, atômica, de que chegou. Toda tentativa de forçar essa garantia (confirmar por log, reenviar na dúvida) abria um buraco novo.

Então escolhemos o caminho simples e honesto: at-most-once. Em caso de dúvida, o sistema não reenvia. A regra de envio cabe em uma linha: se o envio deu certo (ou deu timeout, e pode ter ido), marca como feito e nunca mais toca; se deu um erro claro de "não conectou", tenta de novo mais tarde. O estado que impede a repetição é aquela lista simples de IDs já enviados.

Para um radar de notícias, essa é a troca certa: uma duplicata visível irrita e mina a confiança; uma notícia perdida é rara, invisível, e você a descobre por outro canal. Melhor calar na dúvida.

✅ O que é garantido

o que o sistema sustenta sempre

Não duplica
Dedup por ID do tweet; um post já enviado a um grupo não sai de novo, mesmo reprocessado.
Não perde por descuido
O outbox grava em disco antes de responder; queda ou deploy não apagam a fila.
Não posta fora de hora
Fora de 8h–22h BRT o worker dorme; o post espera a manhã no próprio outbox.
Não vaza nem obedece o post
Classificador isolado; mídia só de pbs.twimg.com; texto vai como argumento, sem shell.

🔁 O que se aceita (raro)

falhas conscientes, não bugs

Duplicar num crash exato
Se a máquina cai no milissegundo entre enviar e anotar "feito", a notícia pode repetir. Inerente e raríssimo.
Ver 2× de duas contas
Se @claudeai e @ClaudeDevs postam a mesma coisa, o grupo vê duas vezes — o dedup é por post, não por assunto.
Rajada às 8h
Se muita coisa acumula de madrugada, sai em sequência quando a janela abre.
Perder na dúvida
Um timeout ambíguo do envio conta como "feito"; se por acaso não foi, aquela notícia se perde.

O laço do worker

O que acontece com cada post

Dentro da janela de horário, o worker consome o outbox item a item. Este é o percurso de um post relevante — e os desvios quando algo dá errado.

1Classifica uma vez. Se o post ainda não tem parecer, o worker chama o agente radar, valida o JSON de volta, e grava o parecer no outbox — antes de enviar.
2Para cada grupo relevante, confere a janela de horário e pergunta ao dedup: já mandei esse post pra esse grupo? Se sim, pula.
3Envia via message send. Deu certo (ou timeout) → marca feito e nunca reenvia. Terminou os dois grupos → o post sai do outbox.
Erro claro de envio (gateway/WhatsApp fora) → o post fica no outbox e é retentado com espera crescente. Persistiu demais → vai para a fila morta (DLQ).
A DLQ não é uma lixeira: é uma sala de espera. Depois de corrigir a causa (um token expirado, o gateway que voltou), um comando de redrive devolve os itens à fila normal — com o parecer preservado. Nada se apaga; tudo se reprocessa.

Quando algo quebra

Feito para falhar em voz alta

O oposto do perigoso não é "nunca falha" — é "nunca falha em silêncio". Cada modo de falha tem um sinal observável.

🌙 De madrugada

O worker dorme e os posts esperam no outbox. É o comportamento normal da janela — não é fila travada.

📉 Gateway fora

A classificação não gasta tentativa (a falha é do transporte, não do post). Ao voltar, a fila drena sozinha; o /health acusa "sem progresso".

☠️ Item envenenado

Um post que sempre falha esgota um contador e vai para a DLQ, sem travar os outros. A DLQ acende o /health (500).

🧭 Estado corrompido

Se o arquivo de dedup fica ilegível, o sistema para em vez de republicar no escuro — e o /health denuncia na hora.

A história

Nasceu simples, foi endurecido na crítica

O sistema atravessou uma re-arquitetura e nove rodadas de revisão adversarial (Codex). O padrão que emergiu: o caminho crítico resistiu; cada mecanismo novo precisou da própria rodada.

opção A

A LLM orquestrava tudo

Webhook → o agente decide, posta, confirma e deduplica. Uma revisão adversarial mostrou que a "costura por linguagem natural" perdia e duplicava sob concorrência e queda.

virada → opção D

Worker determinístico; LLM só classifica

O código assume a mecânica; a LLM fica com o julgamento. Some a classe de bugs de costura. Cada peça vira testável de verdade.

D17 — a grande simplificação

Exactly-once → at-most-once

Percebemos que perseguir "exatamente uma vez" era caro e impossível. Trocamos por "nunca duplica, aceita perder raro" e removemos a máquina de estados inteira — ⅔ do código e a maioria dos bugs sumiram com a superfície.

v6 → v11 · agora

Endurecimento por rodadas

Nove passadas adversariais. O núcleo (recepção, dedup, envio, janela) resistiu a ataque direto; os achados restantes eram de operabilidade — recuperação de falha, uma corrida rara. Cada um virou um teste.

59testes automatizados
9rodadas de revisão
do código, após simplificar
0acesso operacional do classificador