Tem um curso no meu SaaS chamado The Elixir of Life. Aula de literatura inglesa pro décimo ano no estado de Karnataka, na Índia, explicada em hindi e urdu. Apareceu no catálogo com o meu nome, como se eu tivesse escolhido.
Eu não cadastrei esse curso. Quem cadastrou foi um robô que estava procurando cursos da linguagem Elixir.

A IA errou feio de Elixir. Mas a culpa foi minha: o juiz respondeu a pergunta que recebeu, e eu esqueci de perguntar uma coisa importante.
Gravei um vídeo mostrando esse sistema por dentro. Os trechos abaixo vêm do código real do CourseShelf.
Primeiro eu aprovava tudo
O CourseShelf é meu catálogo de cursos gratuitos, a maioria do YouTube, com avaliações da comunidade. É feito em Elixir e Phoenix. Em junho de 2026 eram 111 cursos, entrando uns dois por mês. Catálogo manual cresce na velocidade de quem cadastra.
A primeira automação só procurava candidatos e abria uma issue no GitHub. Era uma tarefa agendada no Claude Code, rodando no meu computador. Quem importava era eu.
Na rodada de 10 de julho vieram dez candidatos. O agente também apontou buracos: não tinha curso de Python nem de TypeScript. No dia seguinte, os dez já estavam no catálogo. Eu aceitei todos.
Resolvi tirar a aprovação do meio e receber um e-mail semanal pra revisar depois. O robô saiu do meu computador e virou parte do app.
A busca não fica com a LLM
Todo sábado às três da manhã em São Paulo, o job pega as oito tags com mais cursos e busca cada nome seguido de full course no YouTube. A consulta considera vídeos longos do último ano e ordena pelos mais vistos.
Quem busca é a API do YouTube. Ela devolve IDs reais, em vez de eu pedir a uma LLM que se lembre de um vídeo e torcer pra ele existir.
Depois entram filtros sem IA: tirar o que já está no catálogo e o que tem menos de sessenta minutos. Cada tópico manda no máximo três candidatos ao juiz, intercalados com os outros tópicos. No fim, entram no máximo cinco cursos por semana.
| Limite | O que ele controla |
|---|---|
| Oito tópicos | O tamanho da busca |
| Três candidatos por tópico | Um assunto não ocupa toda a rodada |
| Só confiança alta | Veredito médio fica de fora |
| Cinco importações | O máximo de entradas erradas numa rodada |
| E-mail com o motivo | O que eu preciso revisar depois |
Esses números seguram o estrago. Um modelo melhor não substitui um teto de importações.
O juiz responde num formato fixo
Cada candidato vai pro modelo com título, canal, duração, data e descrição. A instrução é aceitar material estruturado que ensine um assunto e rejeitar podcast, palestra, entretenimento ou trailer de curso pago. Na dúvida, rejeitar.
Eu uso structured output pra receber três campos, em vez de interpretar uma resposta em prosa. Este é o schema real:
# courseshelf-v2/lib/course_shelf/courses/catalog_scout.ex
defp schema do
%{
type: "object",
additionalProperties: false,
required: ["is_course", "confidence", "reason"],
properties: %{
is_course: %{type: "boolean"},
confidence: %{type: "string", enum: ["high", "medium", "low"]},
reason: %{type: "string"}
}
}
end
Só entra quando is_course é verdadeiro e a confiança é high. Confiança aqui é a classificação do próprio modelo, não uma probabilidade calibrada de acerto.
O código usa DeepSeek pela OpenRouter e temperatura zero pra reduzir a variação. Isso não transforma o resultado numa função perfeitamente determinística, muito menos numa garantia de correção.
O teste passou. A pergunta estava incompleta
Antes de ligar o automático, rodei uma versão só de leitura: faz a busca e o julgamento, mas não importa nem manda e-mail. Foram treze candidatos, seis aceitos e sete rejeitados. Eu concordei com os treze. Entre os rejeitados, tinha até Peppa Pig aparecendo na busca de saúde.
Liguei. E depois entrou The Elixir of Life, uma aula de uma hora e dezoito minutos.
O juiz recebeu uma aula estruturada, explicando um texto inteiro linha por linha, e respondeu que era um curso. Estava certo nessa classificação. O problema aparece no prompt:
# courseshelf-v2/lib/course_shelf/courses/catalog_scout.ex
defp judge_prompt(candidate) do
"""
Title: #{candidate.title}
Channel: #{candidate.channel_title}
Duration: #{candidate.duration_minutes} minutes
Published: #{candidate.published_at}
Description:
#{truncate(candidate.description)}
"""
end
Cadê o tópico da busca? Não está ali. Eu nunca perguntei se aquele curso tinha relação com a linguagem Elixir.

O mais irônico é que a outra IA, responsável pelas tags, acertou: literatura inglesa e preparação pra prova. O sistema tinha informação suficiente pra perceber a incoerência, mas ninguém conectou as duas decisões.
A correção que eu proponho no vídeo é mandar o tópico junto e exigir que o juiz também avalie essa relação. O vídeo foi gravado com o problema ainda presente; não estou apresentando esse trecho como uma correção já entregue.
Aprovação humana ou carimbo?
O melhor argumento contra automatizar é a confiança no catálogo. Cada curso com meu nome parece uma escolha minha, e uma entrada errada desgasta isso.
Só que eu também vi como minha aprovação funcionava: dez candidatos, dez aceitos. Uma etapa humana que sempre diz sim pode virar só carimbo.
Eu prefiro explicitar o que limita a automação: fonte verificável, formato de resposta, filtros, teto de escrita e revisão com o motivo de cada decisão. E melhorar os testes com o erro que apareceu, porque treze exemplos corretos não cobrem todo o comportamento futuro.
Depois de aprovado, o candidato usa o mesmo importador de um cadastro humano. Entram os jobs de tags, dificuldade, embedding, resumo de comentários e resumo do canal. Não tem um atalho que pula as regras normais do produto.
O sistema errou, eu consigo explicar como o erro entrou e sei qual condição faltou. Essa foi a lição mais útil: se uma condição importa pra publicar no seu produto, ela precisa aparecer na decisão. A IA não vai adivinhar o requisito que você esqueceu de escrever.
Vídeo no YouTube: Automatizei meu SaaS com IA (e deu errado)
Conheça meu trabalho: https://bergholz.com.br/
Obrigado por ler até aqui.
