<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Jonathan Muszkat — Blog (PT)</title>
    <link>https://me.jonymusky.com/pt/blog</link>
    <description>Notas sobre Next.js, agentes de IA e como é liderar engenharia em uma startup.</description>
    <language>pt-BR</language>
    <atom:link href="https://me.jonymusky.com/pt/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title><![CDATA[Os juros compostos de um ATS: seu próximo processo não deveria começar do zero]]></title>
      <link>https://me.jonymusky.com/pt/blog/ats-compound-interest-talent-base</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/ats-compound-interest-talent-base</guid>
      <pubDate>Mon, 05 Oct 2026 12:00:00 GMT</pubDate>
      <description><![CDATA[Se você contrata há cinco anos, deveria ter cinco anos de vantagem acumulada. A maioria dos times não tem, porque usa o ATS como um quadro em vez de uma base de talentos. Onde o conhecimento vaza, o que Ashby, Greenhouse, Manatal, Spott e Selenios estão fazendo a respeito, e os hábitos que tornam cada processo mais fácil que o anterior.]]></description>
      <category>recruiting</category>
      <category>ai-agents</category>
      <category>leadership</category>
      <content:encoded><![CDATA[<p>A maioria dos times que sai de uma planilha e passa para um ATS continua usando como se fosse uma planilha. Um quadro com colunas, um candidato por card, cards movidos para a direita até alguém ser contratado e todo o resto ser arquivado. Funciona. As vagas são preenchidas. O problema não é o que a ferramenta faz por você durante um processo. O problema é tudo o que você está perdendo enquanto usa dessa forma.</p>

<p>Cada processo seletivo deveria facilitar o próximo.</p>

<ul>
<li>Cada candidato que você entrevistou.</li>
<li>Cada screening.</li>
<li>Cada avaliação.</li>
<li>Cada conversa.</li>
<li>Cada motivo pelo qual alguém avançou, ou não.</li>
<li>Cada pessoa que era excelente, só que não para aquela vaga.</li>
<li>Cada candidato que não estava disponível oito meses atrás e talvez esteja hoje.</li>
</ul>

<p>Tudo isso deveria virar um ativo da empresa. Se você contrata há cinco anos, deveria ter cinco anos de vantagem acumulada.</p>

<h2>Cinco anos contratando, cinco anos de vantagem</h2>

<p>Recrutamento tem o mesmo formato dos juros compostos. Um único processo gera uma pequena quantidade de conhecimento reutilizável: uma dúzia de pessoas que você chamaria de novo com prazer, algumas notas sobre por que o mercado recusou sua faixa salarial, uma avaliação que mostra o que "sênior" realmente significava para aquele time. Sozinho, parece nada. Guardado e somado ao próximo processo, e ao seguinte, deixa de parecer nada.</p>

<figure>
<svg viewBox="0 0 750 340" width="100%" role="img" aria-labelledby="ats-c1-t ats-c1-d" style="display:block;height:auto;max-width:100%;font-family:var(--font-sans, 'Helvetica Neue', Arial, sans-serif)">
<title id="ats-c1-t">Candidatos qualificados que você pode contatar no primeiro dia de cada processo</title>
<desc id="ats-c1-d">Modelo ilustrativo de dez processos em cinco anos. Com uma base de talentos, o pool do primeiro dia cresce de 0 para cerca de 64 pessoas qualificadas. Começando do zero toda vez, fica em torno de 3.</desc>
<line x1="64" x2="610" y1="288.0" y2="288.0" style="stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="54" y="292.0" text-anchor="end" style="fill:var(--fg-mute, #6f6e66);font-size:12px">0</text>
<line x1="64" x2="610" y1="213.7" y2="213.7" style="stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="54" y="217.7" text-anchor="end" style="fill:var(--fg-mute, #6f6e66);font-size:12px">20</text>
<line x1="64" x2="610" y1="139.4" y2="139.4" style="stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="54" y="143.4" text-anchor="end" style="fill:var(--fg-mute, #6f6e66);font-size:12px">40</text>
<line x1="64" x2="610" y1="65.1" y2="65.1" style="stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="54" y="69.1" text-anchor="end" style="fill:var(--fg-mute, #6f6e66);font-size:12px">60</text>
<text x="64.0" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">1</text>
<text x="124.7" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">2</text>
<text x="185.3" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">3</text>
<text x="246.0" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">4</text>
<text x="306.7" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">5</text>
<text x="367.3" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">6</text>
<text x="428.0" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">7</text>
<text x="488.7" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">8</text>
<text x="549.3" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">9</text>
<text x="610.0" y="308" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">10</text>
<text x="94.3" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">ANO 1</text>
<text x="215.7" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">ANO 2</text>
<text x="337.0" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">ANO 3</text>
<text x="458.3" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">ANO 4</text>
<text x="579.7" y="328" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:11px;letter-spacing:.04em">ANO 5</text>
<text x="64" y="14" style="fill:var(--fg-mute, #6f6e66);font-size:12px">Pessoas qualificadas para chamar no dia um</text>
<polyline points="64.0,288.0 124.7,276.9 185.3,276.9 246.0,276.9 306.7,276.9 367.3,276.9 428.0,276.9 488.7,276.9 549.3,276.9 610.0,276.9" style="fill:none;stroke:var(--fg-mute, #6f6e66);stroke-width:2;stroke-linejoin:round;stroke-linecap:round"/>
<circle cx="64.0" cy="288.0" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 1: 0 pessoas</title></circle>
<circle cx="124.7" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 2: 3 pessoas</title></circle>
<circle cx="185.3" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 3: 3 pessoas</title></circle>
<circle cx="246.0" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 4: 3 pessoas</title></circle>
<circle cx="306.7" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 5: 3 pessoas</title></circle>
<circle cx="367.3" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 6: 3 pessoas</title></circle>
<circle cx="428.0" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 7: 3 pessoas</title></circle>
<circle cx="488.7" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 8: 3 pessoas</title></circle>
<circle cx="549.3" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 9: 3 pessoas</title></circle>
<circle cx="610.0" cy="276.9" r="4" style="fill:var(--fg-mute, #6f6e66);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 10: 3 pessoas</title></circle>
<polyline points="64.0,288.0 124.7,248.1 185.3,212.5 246.0,180.6 306.7,152.1 367.3,126.5 428.0,103.7 488.7,83.3 549.3,65.1 610.0,48.7" style="fill:none;stroke:var(--accent, #ff6600);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round"/>
<circle cx="64.0" cy="288.0" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 1: 0 pessoas</title></circle>
<circle cx="124.7" cy="248.1" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 2: 11 pessoas</title></circle>
<circle cx="185.3" cy="212.5" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 3: 20 pessoas</title></circle>
<circle cx="246.0" cy="180.6" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 4: 29 pessoas</title></circle>
<circle cx="306.7" cy="152.1" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 5: 37 pessoas</title></circle>
<circle cx="367.3" cy="126.5" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 6: 43 pessoas</title></circle>
<circle cx="428.0" cy="103.7" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 7: 50 pessoas</title></circle>
<circle cx="488.7" cy="83.3" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 8: 55 pessoas</title></circle>
<circle cx="549.3" cy="65.1" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 9: 60 pessoas</title></circle>
<circle cx="610.0" cy="48.7" r="4" style="fill:var(--accent, #ff6600);stroke:var(--bg, #f6f6ef);stroke-width:2"><title>Processo 10: 64 pessoas</title></circle>
<text x="622.0" y="52.7" style="fill:var(--fg, #1d1d1b);font-size:13px;font-weight:600">Base de talentos</text>
<text x="622.0" y="68.7" style="fill:var(--fg-mute, #6f6e66);font-size:12px">~64 pessoas</text>
<text x="622.0" y="272.9" style="fill:var(--fg, #1d1d1b);font-size:13px;font-weight:600">Do zero</text>
<text x="622.0" y="288.9" style="fill:var(--fg-mute, #6f6e66);font-size:12px">~3 pessoas</text>
<text x="337.0" y="328" text-anchor="middle" style="fill:none"></text>
</svg>
<figcaption>Modelo ilustrativo, não são dados de clientes. Dois processos por ano, cerca de 12 candidatos fortes por processo que vale a pena chamar de novo, e 20% deles por ano ficando inacessíveis ou deixando de ser relevantes. A linha laranja mantém essas pessoas; a linha cinza só mantém o que um recrutador lembra do último processo.</figcaption>
</figure>

<p>Vale notar duas coisas nesse gráfico. A primeira é a distância: no décimo processo, um time abre uma vaga com sessenta e poucas pessoas qualificadas que já conhece, já avaliou, com notas sobre cada uma. O outro abre com três nomes e um anúncio de vaga. A segunda é o formato da linha cinza. Ela nunca sobe. Dez processos de trabalho e o ponto de partida é o mesmo do primeiro dia.</p>

<p>É isso que acontece quando cada nova vaga começa publicando de novo, esperando candidaturas e lendo currículos do zero. A vantagem que você deveria ter construído simplesmente desaparece.</p>

<h2>O problema do Excel não é o Excel</h2>

<p>É fácil culpar a planilha, e ela merece parte da culpa. Não tem uma busca digna do nome, não tem histórico por pessoa, não tem ligação entre o currículo, a entrevista e a decisão. Mas os times que migraram para um ATS de verdade e mantiveram os mesmos hábitos têm o mesmo problema com uma interface mais bonita.</p>

<p>O problema real é que seu processo de contratação gera informação todos os dias, e você está deixando a maior parte desse conhecimento vazar. Ele vaza em lugares específicos:</p>

<ol>
<li><strong>A rejeição sem motivo.</strong> "Arquivado" não diz nada ao próximo recrutador. "Backend forte, queria 100% remoto, revisitar se abrirmos vagas remotas" diz tudo.</li>
<li><strong>A entrevista que vive na cabeça de alguém.</strong> O melhor sinal de todo o processo é a conversa, e normalmente é a parte que nunca é registrada.</li>
<li><strong>O medalha de prata.</strong> A pessoa que ficou em segundo lugar para uma vaga é, por definição, um candidato pré-qualificado para a próxima vaga parecida. A maioria dos sistemas arquiva essa pessoa junto com quem não passou na primeira triagem.</li>
<li><strong>O "agora não".</strong> Alguém que tinha acabado de começar num emprego, estava se mudando ou tinha outra proposta. O timing era o único problema, e ninguém anotou quando tentar de novo.</li>
<li><strong>O processo que começa com um anúncio de vaga.</strong> Se o primeiro passo de uma nova vaga é publicá-la em vez de buscar no que você já tem, a base não está sendo usada mesmo quando existe.</li>
</ol>

<p>Nenhuma dessas é uma falha de software. São momentos em que o processo produziu algo valioso e nada capturou isso.</p>

<h2>O ciclo</h2>

<p>Um bom ATS não deveria ser só o lugar onde você gerencia candidatos. Deveria ser uma base de talentos que fica mais valiosa a cada processo. Isso só acontece se o processo for um ciclo, não uma linha:</p>

<figure>
<svg viewBox="0 0 720 300" width="100%" role="img" aria-labelledby="ats-c2-t" style="display:block;height:auto;max-width:100%;font-family:var(--font-sans, 'Helvetica Neue', Arial, sans-serif)">
<title id="ats-c2-t">O ciclo: cada processo alimenta a base, e a base inicia o próximo processo</title>
<defs><marker id="ats-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" style="fill:var(--fg-mute, #6f6e66)"/></marker></defs>
<rect x="30" y="40" width="310" height="74" rx="4" style="fill:var(--bg-2, #ecebe0);stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="48" y="71" style="fill:var(--fg, #1d1d1b);font-size:16px;font-weight:600">Abrir uma vaga</text>
<text x="48" y="94" style="fill:var(--fg-mute, #6f6e66);font-size:13px">Buscar primeiro na base</text>
<rect x="380" y="40" width="310" height="74" rx="4" style="fill:var(--bg-2, #ecebe0);stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="398" y="71" style="fill:var(--fg, #1d1d1b);font-size:16px;font-weight:600">Triagem e entrevista</text>
<text x="398" y="94" style="fill:var(--fg-mute, #6f6e66);font-size:13px">Notas, scores, transcrições</text>
<rect x="380" y="190" width="310" height="74" rx="4" style="fill:var(--bg-2, #ecebe0);stroke:var(--rule, #d8d5c6);stroke-width:1"/>
<text x="398" y="221" style="fill:var(--fg, #1d1d1b);font-size:16px;font-weight:600">Decidir</text>
<text x="398" y="244" style="fill:var(--fg-mute, #6f6e66);font-size:13px">Por que sim, por que não, por que não agora</text>
<rect x="30" y="190" width="310" height="74" rx="4" style="fill:var(--bg-2, #ecebe0);stroke:var(--accent, #ff6600);stroke-width:2"/>
<text x="48" y="221" style="fill:var(--fg, #1d1d1b);font-size:16px;font-weight:600">Base de talentos</text>
<text x="48" y="244" style="fill:var(--fg-mute, #6f6e66);font-size:13px">Com tags, buscável, com datas</text>
<line x1="346" y1="77" x2="374" y2="77" style="stroke:var(--fg-mute, #6f6e66);stroke-width:1.5" marker-end="url(#ats-arrow)"/>
<line x1="535" y1="120" x2="535" y2="184" style="stroke:var(--fg-mute, #6f6e66);stroke-width:1.5" marker-end="url(#ats-arrow)"/>
<line x1="374" y1="227" x2="346" y2="227" style="stroke:var(--fg-mute, #6f6e66);stroke-width:1.5" marker-end="url(#ats-arrow)"/>
<line x1="185" y1="184" x2="185" y2="120" style="stroke:var(--fg-mute, #6f6e66);stroke-width:1.5" marker-end="url(#ats-arrow)"/>
<text x="360" y="292" text-anchor="middle" style="fill:var(--fg-mute, #6f6e66);font-size:12px">Pule a caixa de baixo à esquerda e o ciclo vira uma linha: publicar, esperar, ler CVs, repetir.</text>
</svg>
<figcaption>Cada processo gera sinal. Se esse sinal fica em algum lugar buscável, o próximo processo começa a partir dele em vez de começar por um anúncio de vaga.</figcaption>
</figure>

<h2>O que as ferramentas estão fazendo a respeito</h2>

<p>O mercado percebeu. Redescobrir talentos que você já tem virou uma feature de destaque em todo o setor, e vale a pena olhar como cada produto aborda isso:</p>

<ul>
<li><strong><a href="https://www.ashbyhq.com/product-updates/ai-talent-rediscovery" target="_blank" rel="noopener">Ashby</a></strong> lançou o AI Talent Rediscovery, que avalia as pessoas que já estão na sua base contra os critérios de uma nova vaga, atualiza perfis desatualizados e usa sinais como avanço de etapas e notas de scorecard para ranqueá-las. O CRM agrupa contatos em talent pools que podem ir direto para um pipeline ativo.</li>
<li><strong><a href="https://support.greenhouse.io/hc/en-us/articles/30184390692379-Talent-Rediscovery" target="_blank" rel="noopener">Greenhouse</a></strong> tem Talent Rediscovery e filtragem de talentos sobre candidatos e prospects anteriores, filtrável por scorecard, marco atingido e motivo de rejeição, junto com um CRM para prospects de sourcing.</li>
<li><strong><a href="https://www.manatal.com/features/ai-recommendations" target="_blank" rel="noopener">Manatal</a></strong> lê a descrição da vaga e pontua os candidatos que já estão no seu pool contra ela com matching semântico, então candidatos anteriores aparecem para novas vagas sem busca manual.</li>
<li><strong><a href="https://spott.io/" target="_blank" rel="noopener">Spott</a></strong> é um ATS e CRM AI-native para agências de recrutamento, construído em torno de uma base de recrutamento pensada para continuar útil com o tempo, alimentada por memória de conversas e processamento de transcrições de entrevistas.</li>
<li><strong><a href="https://selenios.com" target="_blank" rel="noopener">Selenios</a></strong>, onde sou CTO, então leia esta com isso em mente. Nossa aposta está no lado de entrada do ciclo: screenings, entrevistas e avaliações viram notas estruturadas vinculadas ao candidato, então a base fica mais densa a cada conversa e não só a cada currículo.</li>
</ul>

<p>Repare do que cada uma dessas features depende. O rediscovery ranqueia pessoas usando os scorecards, os motivos de rejeição, as notas e o histórico de etapas que seu time registrou. Um modelo de matching é tão bom quanto aquilo que ele consegue ler. Aponte o melhor motor de rediscovery do mercado para uma base cheia de cards arquivados sem motivos e sem notas, e ele vai redescobrir currículos. É tudo o que ele tem.</p>

<h2>Hábitos que fazem a base render juros compostos</h2>

<p>A ferramenta é necessária, mas não suficiente. Estes são os hábitos que definem se o seu ATS vira uma base de talentos ou um arquivo morto:</p>

<ol>
<li><strong>Busque antes de publicar.</strong> O primeiro passo de cada nova vaga é uma consulta nas pessoas que você já conhece. Publicar vem depois.</li>
<li><strong>Toda rejeição leva um motivo.</strong> Um motivo curto e estruturado. "Skills", "senioridade", "salário", "localização", "timing", mais uma frase. Leva dez segundos e é o dado mais reutilizável que você vai produzir.</li>
<li><strong>Marque os medalhas de prata.</strong> Quem chegou às etapas finais é um lead para a próxima vaga parecida. Faça com que sejam encontráveis como tal.</li>
<li><strong>Coloque uma data no "agora não".</strong> Se o problema foi timing, anote quando isso pode mudar e deixe o sistema te lembrar.</li>
<li><strong>Registre a conversa.</strong> Notas de entrevista e avaliações pertencem ao perfil do candidato, não a um doc, uma thread de chat ou à memória de alguém. Se valeu uma hora do tempo de um entrevistador, vale a pena guardar.</li>
<li><strong>Meça.</strong> Acompanhe quantas contratações e finalistas vêm de pessoas que já estavam na sua base. Se esse número não cresce ano após ano, a base não está rendendo juros compostos.</li>
</ol>

<h2>Uma base, ou um histórico</h2>

<p>Um ATS usado como quadro te dá um registro organizado do que aconteceu. Um ATS usado como base de talentos te dá vantagem sobre o que vem a seguir. A diferença não é o software que você comprou. É se cada processo deixa algo para trás que o próximo possa usar.</p>

<p>Se, depois de anos contratando, seu próximo processo ainda começa do zero, você não construiu uma base de talentos. Construiu um histórico.</p>
]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Batch inference: o mesmo modelo, pela metade do preço, e a produção nem percebe]]></title>
      <link>https://me.jonymusky.com/pt/blog/batch-inference-half-price</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/batch-inference-half-price</guid>
      <pubDate>Thu, 24 Sep 2026 12:00:00 GMT</pubDate>
      <description><![CDATA[Todos os grandes provedores de LLM vendem um modo batch com 50% de desconto, e a maioria dos times ainda roda seu trabalho em massa on-demand. Como usamos isso para migrar dezenas de milhares de CVs pela metade do custo sem tocar a produção, o que ele cobra em troca, e o checklist que aprendemos do jeito difícil.]]></description>
      <category>llms</category>
      <category>recruiting</category>
      <category>architecture</category>
      <content:encoded><![CDATA[<p>Migrar milhares de candidatos por um modelo de linguagem custa o que custa. Ou a metade. Na Selenios cortamos até 50% da conta de tokens toda vez que processamos dados em bulk, com o mesmo modelo, os mesmos prompts e os mesmos resultados. A técnica se chama batch inference, está na tabela de preços de todos os grandes provedores, e a maioria dos times com quem eu converso ainda não usa para o único tipo de carga em que é dinheiro de graça. Isto é o que ela é, por que custa a metade, o que ela cobra em troca, e o checklist que escrevemos depois de aprender do jeito difícil.</p>

<h2>O que é batch inference</h2>

<p>Em tempo real, cada request espera sua resposta. Em batch não existe conversa. Você escreve um arquivo com um objeto JSON por linha, milhares de prompts, e sobe para um bucket. Você diz ao provedor "processa isso quando der". Algum tempo depois aparece outro arquivo com todas as respostas. Nenhum servidor seu esperando em sockets, nenhuma lógica de retry, nenhuma fila para você operar.</p>

<p>No Amazon Bedrock, que é onde rodamos, o input é um arquivo JSONL no S3 em que cada linha carrega um identificador e o mesmo body que você mandaria para o <code>InvokeModel</code>:</p>

<pre><code>{"recordId":"a1f3c9e2","modelInput":{ …the InvokeModel body for cv 1… }}
{"recordId":"9c02b7d4","modelInput":{ …the InvokeModel body for cv 2… }}
…
{"recordId":"0d41f8a7","modelInput":{ …the InvokeModel body for cv N… }}</code></pre>

<p>Depois, uma única chamada de API cria o job:</p>

<pre><code>import { BedrockClient, CreateModelInvocationJobCommand } from '@aws-sdk/client-bedrock';

const { jobArn } = await bedrock.send(new CreateModelInvocationJobCommand({
  jobName: `cv-parse-${runId}-${shard}`,
  modelId,
  roleArn,
  inputDataConfig: { s3InputDataConfig: { s3Uri: `s3://${bucket}/input/${runId}/${shard}.jsonl` } },
  outputDataConfig: { s3OutputDataConfig: { s3Uri: `s3://${bucket}/output/${runId}/` } },
}));</code></pre>

<p>A partir daí você faz polling do status do job e, quando ele termina, lê os arquivos de output, casa cada linha com seu <code>recordId</code> e segue o seu pipeline como se as chamadas tivessem sido feitas uma a uma.</p>

<h2>Por que custa a metade</h2>

<p>Porque você está vendendo ao provedor a única coisa que sobra para ele: capacidade ociosa. As frotas de inferência são dimensionadas para o pico de tráfego interativo e, fora do pico, as GPUs ficam parcialmente vazias. Quando você abre mão de latência, eles recebem trabalho que podem encaixar nesses buracos, e o preço do token cai 50%.</p>

<p>Não é promoção nem desconto negociado. É o preço de tabela do modo batch no Amazon Bedrock, Anthropic, OpenAI e Gemini, os quatro pela metade da tarifa on-demand. Mesmo modelo, mesmos pesos, mesmo output. A única coisa pela qual você paga menos é a promessa de uma resposta imediata.</p>

<h2>Um caso real: uma migração com anos de histórico</h2>

<p>Recentemente migramos um cliente que vinha de outro sistema de recrutamento. Isso significa dezenas de milhares de CVs diferentes e, junto com eles, as vagas, as notas dos recrutadores, as movimentações entre etapas do pipeline e as avaliações de anos de histórico. Cada CV passou por um modelo de linguagem no Bedrock para extrair sua estrutura no formato que o nosso produto espera.</p>

<p>A conta on-demand teria sido um número. A conta em batch foi exatamente a metade, com o mesmo modelo e os mesmos resultados. Não vou colocar valores absolutos aqui, e não importa: o ponto é a proporção, e ela se mantém em qualquer escala. Leve isso para milhares de candidatos com todo o histórico e a conta continua a mesma. Metade.</p>

<p>Existe uma segunda economia que não tem nada a ver com batch e que você deveria fazer de qualquer jeito: antes de mandar qualquer coisa, fazemos hash de cada arquivo. Num sistema de recrutamento, o mesmo CV aparece anexado a muitas candidaturas. Um candidato que se candidatou a cinco vagas tem cinco cópias do mesmo PDF. Com um MD5 do arquivo como chave, o mesmo CV é parseado uma única vez e o resultado é replicado para cada candidatura que o referencia. O token mais barato é aquele que você nunca manda.</p>

<h2>A vantagem de que ninguém fala: batch não compete com a produção</h2>

<p>O preço é a manchete, mas não é o melhor motivo. O tráfego on-demand sai de um pool compartilhado e, quando esse pool enche, o provedor responde com um 503 e uma mensagem de "insufficient capacity". Já passamos por isso. Agora imagine que, além do seu tráfego normal, você dispara milhares de chamadas de um script de migração pelo mesmo canal, com o mesmo modelo e a mesma conta. Quem paga são seus usuários, em latência e em erros, nas telas que eles realmente usam.</p>

<p>Batch é outra fila, com outra cota. A migração roda de madrugada e a plataforma nem percebe. A própria orientação do Bedrock para erros de capacidade no on-demand diz a mesma coisa: mova as cargas offline para batch. Esse foi o argumento que me convenceu mais do que o desconto. Pagar metade é ótimo. Trabalho em massa que nunca encosta na experiência de quem usa seu produto é o ganho de verdade.</p>

<h2>O que ele cobra em troca</h2>

<p>Nada é de graça, então aqui vai a conta, com os detalhes do Bedrock porque é onde rodamos:</p>

<ol>
<li><strong>Latência.</strong> Minutos ou horas, com o compromisso de terminar em até 24 horas. Você não escolhe quando.</li>
<li><strong>Menos features.</strong> No batch do Bedrock não há tool calling nem structured output. Você pede JSON no prompt e faz o parse você mesmo, com a mesma validação que usaria para qualquer output de modelo não confiável.</li>
<li><strong>Sem caminho de OCR em vários modelos.</strong> Se parte do seu input são imagens escaneadas, elas precisam de uma rota on-demand própria.</li>
<li><strong>A ordem do output não é garantida.</strong> A linha 1 do input não é a linha 1 do output. O <code>recordId</code> é a única coisa que liga uma à outra.</li>
<li><strong>Um mínimo por job.</strong> Por padrão, 100 registros. Com menos que isso, o job é rejeitado.</li>
</ol>

<p>Se o seu caso de uso é interativo, nada disso funciona. Se ninguém está esperando a resposta, tudo isso é aceitável.</p>

<h2>O checklist que aprendemos do jeito difícil</h2>

<p>Nada disso está no guia de getting started. Tudo nos custou algo na primeira vez.</p>

<ul>
<li><strong>Deduplique antes de mandar.</strong> Faça hash do arquivo, não da candidatura. Um parse por CV, não um por candidatura.</li>
<li><strong>Respeite as cotas.</strong> No mínimo 100 registros por job, 1 GB por arquivo de input, 5 GB por job. Nós fazemos shards de 50.000 registros.</li>
<li><strong>Junte o último shard se ele ficar curto.</strong> 50.050 registros divididos ingenuamente em shards de 50.000 geram um shard final de 50, e esse job é rejeitado. O fix são três linhas, e é o tipo de bug que você só encontra com dados reais:
<pre><code>function shard&lt;T&gt;(items: T[], size = 50_000, min = 100): T[][] {
  const shards: T[][] = [];
  for (let i = 0; i &lt; items.length; i += size) shards.push(items.slice(i, i + size));
  const last = shards.at(-1);
  if (shards.length &gt; 1 &amp;&amp; last &amp;&amp; last.length &lt; min) shards.at(-2)!.push(...shards.pop()!);
  return shards;
}</code></pre>
O shard combinado fica um pouco acima do tamanho-alvo, então mantenha o alvo bem abaixo dos limites reais.</li>
<li><strong>Persista o estado de cada job assim que ele for aceito.</strong> Um job é cobrado a partir do segundo em que o provedor o aceita. Se o seu script cair depois de criar o job mas antes de salvar o ARN, e você relançar, paga duas vezes pelo mesmo trabalho. Grave o ARN do job e o shard que ele cobre em armazenamento durável antes de qualquer outra coisa, e faça o launcher pular os shards que já têm um job.</li>
<li><strong>Use identificadores curtos e únicos.</strong> O output volta fora de ordem, então cada linha precisa de um <code>recordId</code> que mapeie de volta para os seus dados sem ambiguidade. Um hash curto funciona melhor que uma chave composta longa que você vai ter que parsear depois.</li>
<li><strong>Valide com o mesmo parser que você usa no on-demand.</strong> O import não deveria conseguir distinguir por qual caminho um resultado veio. Se o output do batch precisa de um parser próprio, agora você tem duas fontes de verdade e uma delas vai divergir.</li>
<li><strong>Apague todos os arquivos do bucket quando terminar.</strong> Inputs e outputs. São CVs, ou seja, dados pessoais, e nada deveria ficar no S3 depois que o import for verificado. Faça a limpeza ser parte do job, não uma nota num runbook.</li>
</ul>

<h2>Quem suporta</h2>

<p>Os quatro grandes provedores oferecem com 50% de desconto, com limites diferentes. Nesta semana:</p>

<table>
<thead><tr><th>Provedor</th><th>Como</th><th>Limites que vale conhecer</th></tr></thead>
<tbody>
<tr><td>Amazon Bedrock</td><td>JSONL no S3, <code>CreateModelInvocationJob</code></td><td>1 GB por arquivo, 5 GB por job, mínimo de 100 registros por job, sem tool calling nem structured output. Disponível para Claude, Nova, Llama, Mistral, DeepSeek, Qwen e outros.</td></tr>
<tr><td>Anthropic</td><td>Message Batches API</td><td>Até 100.000 requests ou 256 MB por batch. A maioria dos batches termina em menos de uma hora. Resultados disponíveis por 29 dias.</td></tr>
<tr><td>OpenAI</td><td>Batch API, arquivo JSONL</td><td>50.000 requests e 200 MB por arquivo, janela de 24 horas, rate limits separados da API síncrona.</td></tr>
<tr><td>Gemini</td><td>Batch Mode, inline ou arquivo</td><td>Arquivos de até 2 GB, meta de 24 horas, o context caching funciona dentro do batch.</td></tr>
</tbody>
</table>

<p>Se você já usa o AI SDK, existe um caminho mais simples para batches pequenos. O AI Gateway da Vercel suporta batch via <code>experimental_startBatch</code>, <code>experimental_getBatchStatus</code> e <code>experimental_getBatchResults</code>, sem S3 envolvido: os requests vão inline, até 1.000 por batch e 4,5 MB, e o mesmo código funciona com modelos da OpenAI e da Anthropic.</p>

<pre><code>import {
  experimental_startBatch as startBatch,
  experimental_getBatchStatus as getBatchStatus,
  experimental_getBatchResults as getBatchResults,
} from 'ai';

const batch = await startBatch({
  requests: cvs.map((cv) =&gt; ({
    id: cv.hash,
    type: 'text',
    model: modelId, // any OpenAI or Anthropic model on the gateway
    prompt: buildParsePrompt(cv.text),
  })),
});
await saveBatchReference(batch); // { version, id, provider }: persist it before anything else

// later, from a cron or a worker
const { status } = await getBatchStatus({ batch });
if (status === 'completed') {
  for await (const item of getBatchResults({ batch })) {
    if (item.status === 'succeeded') await importParsedCv(item.id, parseCv(item.text));
  }
}</code></pre>

<p>Mesmo formato da versão com Bedrock, sem precisar cuidar de um bucket, de uma role e da limpeza. É ideal para batches pequenos e médios. Para dezenas de milhares de CVs, o arquivo no S3 continua sendo a ferramenta certa.</p>

<h2>A regra</h2>

<p>A decisão inteira cabe numa pergunta: <strong>tem alguém esperando por esta resposta?</strong> Se sim, on-demand. Se não, batch. Migrações, re-scoring em massa, enriquecimento de dados, evals, classificação de registros históricos: nenhum deles tem um humano do outro lado olhando um spinner, e todos estão pagando o dobro se rodam on-demand.</p>

<p>Mesmo modelo, metade do preço, e seus usuários nem percebem. E, sinceramente, a metade do preço é o de menos. O que importa é que o trabalho em massa nunca encoste na experiência de quem usa o seu produto.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Jev não gera texto. Essa é a ideia: um loop de QA no browser com o modelo System One da TypeSafe]]></title>
      <link>https://me.jonymusky.com/pt/blog/jev-system-one-browser-qa</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/jev-system-one-browser-qa</guid>
      <pubDate>Sat, 19 Sep 2026 12:00:00 GMT</pubDate>
      <description><![CDATA[Um modelo que devolve decisões calibradas em vez de texto chegou ao AI Gateway da Vercel esta semana e virou o lançamento com a adoção mais rápida da plataforma. Montei um loop de QA com Playwright em que o Jev julga cada passo, medi e comparei com os LLMs que você já paga.]]></description>
      <category>ai-agents</category>
      <category>dev-tools</category>
      <content:encoded><![CDATA[<p>Em 15 de setembro apareceu no AI Gateway da Vercel um modelo que não consegue escrever uma frase. Ele não chama tools, não se explica, e seu output máximo é zero tokens. Três dias depois a Vercel <a href="https://vercel.com/blog/ai-gateway-jev-model-launch">escreveu</a> que ele tinha chegado a quase 13% dos times pagantes nas primeiras 24 horas, o dobro de qualquer lançamento anterior e seis vezes o que o Fable 5.1 conseguiu no primeiro dia. O modelo é o Jev, de uma empresa chamada TypeSafe, e é o primeiro do que eles chamam de modelos System One. Passei esta semana construindo um loop de QA no browser em cima dele. Aqui está o que ele é, por que acho que a empolgação é merecida, o que eu construí e como ficam os números ao lado dos LLMs que você já paga.</p>

<p>Uma nota prática antes do argumento: no momento em que escrevo, a Vercel está com uma promoção de lançamento e o Jev é gratuito no AI Gateway até 25 de setembro. Depois disso custa USD 0.042 por milhão de tokens de input, e o output é gratuito porque não existe output. De qualquer forma custa quase nada, e isso faz parte da história.</p>

<h2>O que é o Jev, em um parágrafo</h2>

<p>Você passa ao Jev um <strong>estado</strong> (uma string, um objeto JSON, até 32k tokens) e um conjunto de <strong>perguntas</strong>. Cada pergunta é uma de três primitivas: um <strong>boolean</strong> ("esta afirmação é verdadeira?"), um <strong>choice</strong> ("qual destas opções?") ou um <strong>score</strong> ("onde fica nesta escala ordenada?"). Ele responde cada pergunta com um valor tipado e uma distribuição de probabilidade, e nada mais. Sem prosa, sem trace de raciocínio, sem JSON para parsear. A TypeSafe treina o modelo com um método que eles chamam de Reinforcement Learning for Calibrated Decisions, então as probabilidades são otimizadas contra resultados, e não contra a preferência de um humano por uma resposta bonita. Um 0.97 deveria acertar cerca de 97% das vezes.</p>

<p>Pelo AI SDK fica assim:</p>

<pre><code>import { experimental_evaluate as evaluate } from 'ai';

const result = await evaluate({
  model: 'typesafe-ai/jev',
  state: { url, title, visibleText },
  questions: {
    signedIn: {
      type: 'boolean',
      instructions: 'Is the user signed in, judging from the page?',
    },
    section: {
      type: 'choice',
      instructions: 'Which section is the page showing?',
      criteria: { dashboard: null, login: null, other: null },
    },
  },
});
// result.answers.signedIn.probability   -> 0.9
// result.answers.section.choice         -> 'dashboard'
// result.answers.section.probabilities  -> { dashboard: 1, login: 0, other: 0 }</code></pre>

<p>Essa é a API inteira. O pacote <code>ai</code> adicionou <code>experimental_evaluate</code> na 7.0.105 exatamente para essa classe de modelo, e o gateway o expõe como um "evaluation model" ao lado dos modelos de linguagem, de embeddings e de imagem. É uma coluna nova no catálogo, não uma linha nova.</p>

<h2>Por que isso é mais importante que um classificador barato</h2>

<p>Olhe onde as chamadas a LLMs realmente estão em um codebase em produção. Uma minoria gera texto que um humano vai ler. A maioria toma uma decisão sobre a qual o código vai agir: rotear este ticket, este CV atende à pergunta eliminatória?, esta mensagem é urgente?, qual destes cinco candidatos é o que o usuário quis dizer?, este passo deu certo? Por três anos, o jeito de obter essa decisão foi pedir a um modelo de chat que, por favor, respondesse em JSON, parsear o JSON, tentar de novo quando não parseia, e tratar a resposta como binária porque o modelo não te dá um número honesto de quão seguro ele está.</p>

<p>Cada uma dessas chamadas paga por geração. Você paga pelos tokens de output, espera o stream deles, e herda um modo de falha (output malformado) que não tem nada a ver com a decisão. Os modos de structured output deixaram o parsing mais confiável, mas não mais rápido nem mais barato, e os logprobs, onde existem, não são calibrados. O Jev elimina a etapa de geração por completo. A decisão <em>é</em> o output. Não há nada para parsear, nada para tentar de novo por questões de formatação, e o número que volta significa o que diz.</p>

<p>A consequência é que decisões ficam baratas o suficiente para você tomar muitas. Faça cinco perguntas sobre o mesmo estado e elas rodam em paralelo em um único request. Faça uma pergunta especulativa que talvez você nem precise. Dê nota a cada candidato em quatro dimensões e deixe o ranking ser uma soma ponderada que você pode mudar sem outra inferência. É um jeito diferente de escrever software em volta de um modelo, e é por isso que a documentação da TypeSafe parece mais um guia de programação do que um guia de prompting. O modelo é uma primitiva. O código é dono do workflow.</p>

<p>O que o Jev não consegue fazer é igualmente importante, porque os limites são o design. Ele não gera, então não consegue escrever um seletor, um resumo, um e-mail ou uma linha de código. Não consegue planejar. Lê texto, não imagens. Se a sua tarefa precisa de alguma dessas coisas, você ainda precisa de um modelo de linguagem. O modelo mental certo não é "um LLM menor", e sim "o julgamento rápido e calibrado que você coloca antes, ao lado ou depois do LLM".</p>

<h2>O que eu construí: um loop de QA em que o Playwright dirige e o Jev julga</h2>

<p>Testes end-to-end no browser são um bom lugar para experimentar isso, porque é exatamente o tipo de carga em que as ferramentas atuais gastam um turno de um modelo de fronteira a cada clique. Ferramentas de QA baseadas em agentes (Expect, browser-use e similares) entregam um browser a um agente de código e deixam ele planejar, clicar, digitar e verificar. Funciona, é lento, e é caro do jeito que vai somando: um flow de 20 passos são 20 ou mais turnos do agente.</p>

<p>A divisão a que cheguei é simples, e eu defenderia cada linha dela:</p>

<table>
<thead><tr><th></th><th>Playwright (código)</th><th>Jev (modelo)</th></tr></thead>
<tbody>
<tr><td>Navegação, digitação, esperas</td><td>sim</td><td></td></tr>
<tr><td>Vídeo, trace, screenshots</td><td>sim</td><td></td></tr>
<tr><td>"Esta afirmação é verdadeira na página?"</td><td></td><td>sim, uma pergunta boolean</td></tr>
<tr><td>"Qual controle visível corresponde a esta intenção?"</td><td></td><td>sim, um choice entre os controles que o código encontrou</td></tr>
<tr><td>"Qual é o próximo clique rumo a este objetivo?"</td><td></td><td>sim, um choice entre as ações observadas ou DONE / BLOCKED / WAIT</td></tr>
</tbody>
</table>

<p>A digitação de texto nunca passa perto do modelo. O código tira um snapshot da página (URL, título, texto visível deduplicado, a árvore aria e cada controle clicável visível marcado com um índice), e o Jev só escolhe entre coisas que o código já encontrou. Ele nunca inventa um seletor. É o mesmo formato do bridge <a href="https://github.com/wy-coliney/jev-browser-use">jev-browser-use</a> que alguém publicou para o Codex esta semana, adaptado para Playwright puro para rodar em qualquer lugar.</p>

<p>Em cima das três primitivas construí o que um time realmente precisa:</p>

<ul>
<li><strong>Uma CLI para agentes.</strong> Um flow é um array JSON de passos. A CLI roda tudo headless, grava em vídeo e imprime um único resultado JSON com pass ou fail por passo, a probabilidade por trás de cada julgamento, o caminho do vídeo e o caminho de um screenshot final. Exit code 0 ou 1. As credenciais são referenciadas como placeholders <code>${QA_EMAIL}</code> e substituídas a partir do ambiente da própria CLI, então um agente que escreve um flow nunca as vê.</li>
<li><strong>Um dashboard.</strong> Um único arquivo HTML servido localmente, em inglês, espanhol ou português. Cada run, cada teste, cada decisão do Jev com sua probabilidade, latência e contagem de tokens, com o vídeo embutido.</li>
<li><strong>Uma skill.</strong> Um <code>SKILL.md</code> que ensina Claude Code, Codex, Cursor ou Grok Build a escrever um flow e a ler o resultado. "Teste este branch no browser" vira algo que um agente consegue fazer sem um humano olhando.</li>
</ul>

<figure>
<img src="/img/jev/dashboard-overview.png" alt="O dashboard do jev-browser-qa: cinco runs na barra lateral, cards de KPI para aprovados, reprovados, duração, chamadas ao Jev, latência média e tokens de input, e um card de teste aprovado" width="1280" height="820" loading="lazy">
<figcaption>O dashboard depois de uma tarde de runs. 545 ms de latência média do Jev nas cinco decisões do último flow.</figcaption>
</figure>

<p>Aqui está um run real contra o nosso produto. O flow faz login de forma determinística, depois passa um objetivo ao Jev e depois pede que ele verifique:</p>

<pre><code>{ "goal": "Open the account or workspace settings screen. Do not change any setting.",
  "maxSteps": 8,
  "denyNames": ["cerrar sesi", "logout", "eliminar", "delete", "guardar", "save"] },
{ "expect": "A settings or configuration screen is displayed." }</code></pre>

<pre><code>[8/9] goal: Open the account or workspace settings screen. Do not change any setting.
    step 1: Click button "Menú de usuario: Olivia Owner" p=0.76
    step 2: Click link "Mi cuenta" (href=/settings) p=0.99
    step 3: WAIT p=0.38
    step 4: DONE p=1.00
    passed (3245 ms)
[9/9] expect: A settings or configuration screen is displayed.
    passed p=0.99 (443 ms)</code></pre>

<p>Quatro decisões, 3.2 segundos incluindo o carregamento das páginas, e a terceira é a minha favorita: a página estava no meio de uma transição depois do clique, e em vez de clicar em outra coisa o Jev escolheu WAIT com uma probabilidade baixa, que é exatamente o que um modelo deveria dizer quando o estado é ambíguo. No flow de login, quando pedi "the button that submits the login form", ele escolheu "Iniciar sesión" com 0.97 em uma página em espanhol que nunca tinha visto. A intenção estava em inglês. Não fez diferença.</p>

<figure>
<img src="/img/jev/dashboard-run.png" alt="Um card de run expandido no dashboard: o vídeo gravado à esquerda e, à direita, o julgamento do Jev com 99% mais os quatro passos do objetivo com suas probabilidades e latências" width="1280" height="600" loading="lazy">
<figcaption>O mesmo run no dashboard. Cada decisão pode ser inspecionada depois, com o vídeo ao lado.</figcaption>
</figure>

<h2>Os números</h2>

<p>O Jev eu medi pessoalmente. Vinte chamadas sequenciais pelo AI Gateway a partir de Buenos Aires, cada uma com uma pergunta boolean e uma choice sobre um estado de página realista de uns 400 tokens:</p>

<table>
<thead><tr><th>Jev via AI Gateway</th><th>Valor</th></tr></thead>
<tbody>
<tr><td>Latência p50 (ida e volta)</td><td>388 ms</td></tr>
<tr><td>Latência p90</td><td>471 ms</td></tr>
<tr><td>mín / máx</td><td>330 ms / 883 ms</td></tr>
<tr><td>Tokens de input por chamada</td><td>395 (julgamentos), ~1,500 (passos de objetivo com a lista de controles)</td></tr>
<tr><td>Custo por julgamento</td><td>USD 0.0000166. Mil assertions por menos de dois centavos.</td></tr>
</tbody>
</table>

<p>A comparação abaixo é a parte para ler com atenção. Eu <strong>não</strong> rodei os outros modelos: peguei os preços de tabela na página de pricing de cada provedor e as latências do Artificial Analysis, e calculei quanto custaria o mesmo julgamento se você pedisse a um modelo de linguagem como um pequeno objeto JSON: 750 tokens de input (o estado mais a pergunta) e 25 de output. O time to first token é o número publicado para a configuração sem reasoning, quando ela existe; o tempo total soma esses 25 tokens na velocidade de output publicada. Trate a coluna de latência como estimativa e a de custo como aritmética.</p>

<table>
<thead><tr><th>Modelo</th><th>Preço in / out (USD por 1M)</th><th>Custo por julgamento</th><th>vs Jev</th><th>Latência estimada</th></tr></thead>
<tbody>
<tr><td><strong>Jev (medido)</strong></td><td>0.042 / 0</td><td>0.0000315</td><td>1x</td><td><strong>0.39 s medido</strong></td></tr>
<tr><td>GPT-5 nano</td><td>0.05 / 0.40</td><td>0.0000475</td><td>1.5x</td><td>~0.5 a 1 s sem reasoning (76 s com reasoning em high, segundo o Artificial Analysis)</td></tr>
<tr><td>GPT-5 mini</td><td>0.25 / 2.00</td><td>0.00024</td><td>7.6x</td><td>~1 s sem reasoning</td></tr>
<tr><td>Gemini 3.5 Flash-Lite</td><td>0.30 / 2.50</td><td>0.00029</td><td>9.2x</td><td>~0.5 s sem thinking (9.4 s com thinking)</td></tr>
<tr><td>Claude Haiku 4.5</td><td>1.00 / 5.00</td><td>0.000875</td><td>28x</td><td>~1.0 s (0.69 s TTFT + 25 tokens a 83 tok/s)</td></tr>
<tr><td>Gemini 3.5 Flash</td><td>1.50 / 9.00</td><td>0.00135</td><td>43x</td><td>~1 s sem thinking (15 s com)</td></tr>
<tr><td>Claude Sonnet 5</td><td>2.00 / 10.00</td><td>0.00175</td><td>56x</td><td>~1.5 a 2 s</td></tr>
<tr><td>Claude Fable 5.1</td><td>10.00 / 50.00</td><td>0.00875</td><td>278x</td><td>vários segundos</td></tr>
</tbody>
</table>

<p>Três coisas chamam a atenção. Primeiro, os LLMs mais baratos ficam a um múltiplo pequeno do Jev em preço, então no tier nano o custo sozinho não é o argumento; latência e calibração são. Segundo, os modelos que as pessoas realmente usam quando querem um julgamento confiável (Haiku, Flash, Sonnet) custam de 30 a 60 vezes mais e têm de duas a cinco vezes a latência, e ainda assim não te dão uma probabilidade. Terceiro, o número de manchete da própria TypeSafe, "194x faster and 445x cheaper", é contra workflows do tipo reasoning, e quando você olha as latências com reasoning naquela tabela (76 segundos para o nano em high) ele deixa de soar como marketing. Para o loop de QA, a comparação que importa é por flow: o run de settings acima tomou cinco decisões por USD 0.0003. Rode isso a cada commit em cada branch e a conta continua zerada.</p>

<h2>O que aprendi em uma semana</h2>

<ul>
<li><strong>Escreva as afirmações do jeito que um QA engineer escreve resultados esperados.</strong> "Uma lista de vagas é exibida, ou um estado vazio convidando a criar a primeira" funciona. "A página está correta" não. Quando uma afirmação é sutil, dê critérios explícitos de verdadeiro e falso; o modelo usa.</li>
<li><strong>Uma probabilidade perto do seu threshold é feedback sobre a afirmação, não sobre o threshold.</strong> Refine a pergunta. Não abaixe a régua.</li>
<li><strong>Deny lists não são opcionais em um loop de ações.</strong> O Jev vai escolher "Cerrar sesión" numa boa se esse for o único controle que parece progresso. O loop só oferece controles únicos e permitidos, e nomes destrutivos nunca são oferecidos.</li>
<li><strong>Texto, não pixels.</strong> O Jev nunca vê o screenshot. Regressões visuais continuam precisando de um modelo de visão ou de um diff de pixels. O screenshot no relatório é para o humano.</li>
<li><strong>Calibração é por população, não por resposta.</strong> A documentação diz isso e dá para ver: um 0.76 em "clicar no menu do usuário" foi o clique certo, e um 0.38 em WAIT foi a hesitação certa. Leia a distribuição, não só o argmax.</li>
<li><strong>Combina com as ferramentas de agentes que você já usa.</strong> O lugar natural disso não é substituir o Expect ou um agente de código, e sim ser a camada mecânica que eles chamam para parar de gastar um turno por clique. É exatamente o que o bridge do Codex faz, e o que a CLI mais a skill daqui fazem para todo o resto.</li>
</ul>

<h2>Experimente</h2>

<p>O projeto é open source em <a href="https://github.com/jonymusky/jev-browser-qa">github.com/jonymusky/jev-browser-qa</a>. Ele é pequeno de propósito: três primitivas, uma CLI, um dashboard, uma skill e um README que diz o que o Jev não vai fazer.</p>

<pre><code>git clone https://github.com/jonymusky/jev-browser-qa &amp;&amp; cd jev-browser-qa
pnpm install &amp;&amp; pnpm exec playwright install chromium
cp .env.example .env          # AI_GATEWAY_API_KEY, BASE_URL, QA_EMAIL, QA_PASSWORD
pnpm jev:smoke                # one call, no browser
pnpm -s qa run flows/login.json
pnpm dashboard                # http://localhost:4310</code></pre>

<p>E para o seu agente: <code>npx skills add https://github.com/jonymusky/jev-browser-qa --skill jev-qa</code>. Tenho rodado a partir do Claude Code e do <a href="https://docs.x.ai/build/overview">Grok Build</a>, o agente de terminal da xAI, que lê Agent Skills e <code>AGENTS.md</code> sem nenhuma configuração: o mesmo prompt, "test this branch with jev-qa and report the verdict with the video path", funciona nos dois, e nenhum dos agentes vê uma credencial, porque os flows levam placeholders <code>${QA_EMAIL}</code> que a CLI resolve. Tem um <a href="https://github.com/jonymusky/jev-browser-qa/blob/main/docs/agents/grok-build.md">walkthrough curto do Grok Build</a> no repo, incluindo a forma headless <code>grok -p</code> para CI. Se quiser testar sem instalar nada, publiquei um bot do Grok com a skill pré-carregada: <a href="https://x.ai/bot/IoOXnqvhpn4i_C1TNq9P_">QA Engineer</a>. Passe a URL do seu app e o flow que você quer verificar, e ele volta com o veredito, o vídeo e o screenshot.</p>

<p>Tomei cuidado neste post para separar o que medi do que inferi, porque as afirmações em torno do Jev são grandes e o modelo tem quatro dias de vida. Mas o design não é um truque. Um modelo que devolve uma decisão calibrada em vez de um parágrafo, em 400 milissegundos, por uma fração de centavo, muda em quais partes do seu sistema faz sentido colocar um modelo. Até agora a resposta era "nas partes que podem pagar por isso". A partir desta semana, a resposta está mais perto de "em qualquer lugar onde, de outro jeito, você escreveria um if e se sentiria mal com isso".</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Next.js 16.3 da cadeira de CTO: cinco apps, um mês, três bugs]]></title>
      <link>https://me.jonymusky.com/pt/blog/nextjs-16-3-from-the-cto-seat</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/nextjs-16-3-from-the-cto-seat</guid>
      <pubDate>Thu, 03 Sep 2026 12:00:00 GMT</pubDate>
      <description><![CDATA[Adotamos o Next.js 16.3 em cinco apps em produção na mesma semana do lançamento. O que valeu a pena, como o Cache Components mudou silenciosamente nossos códigos de status HTTP e por que experimental continua significando experimental.]]></description>
      <category>nextjs</category>
      <category>leadership</category>
      <content:encoded><![CDATA[<p>O Next.js 16.3 saiu no começo de agosto com a manchete "a maior atualização desde o 16.0". Adotamos na mesma semana em cinco apps em produção, depois passamos um mês convivendo com ele, e nesta semana movemos tudo para o último patch e revisamos cada afirmação do release post. Isto é o que se sustentou, o que não, e o que eu diria a outro CTO antes de ele ativar as flags.</p>

<p>Contexto, para que os números signifiquem algo: rodamos cinco apps Next.js. Três ficam em um monorepo pnpm: um produto administrativo de 56 rotas atrás de auth, um site público para candidatos com formulários e entrevistas com IA, e um pequeno portal para funcionários. Os outros dois são repos separados: um dashboard interno de analytics e um site de marketing em três idiomas com um blog em MDX. Os cinco fazem deploy na Vercel. Os cinco agora rodam com React Compiler e Cache Components.</p>

<h2>O que valeu a pena desde o primeiro dia</h2>

<p><strong>O cache de build é real.</strong> O cache em filesystem do Turbopack para <code>next build</code> vem ativado por padrão no 16.3. Medido nesta semana no último patch, em um laptop: o app de 56 rotas compila em 30 segundos a frio e em 0,8 segundo quando nada mudou. No 16.3.0 o mesmo rebuild com cache levava 6,6 segundos, então a linha de patches melhorou isso oito vezes sem a gente mexer em nada. Os apps menores ficam entre 4 e 7 segundos a frio e meio segundo com cache. O tempo total é maior (a coleta de page data e a geração estática não são cacheadas), mas a etapa de compilação era a parte que nos deixava esperando.</p>

<p><strong>A evicção de memória mudou uma decisão, não um número.</strong> Nunca medimos a memória em dev, mas foi por isso que nosso site de marketing finalmente abandonou <code>next dev --webpack</code> como padrão. Sessões longas do Turbopack cresciam até o laptop reclamar; com a evicção ativada por padrão, isso deixou de ser motivo para manter o webpack. O webpack fica como script de fallback, o que traz sua própria lição mais abaixo.</p>

<p><strong><code>catchError</code> é o error boundary que a gente realmente queria.</strong> Um <code>error.tsx</code> clássico engole <code>notFound()</code> e <code>redirect()</code>, e o reset dele só limpa o estado do cliente. O novo boundary de <code>next/error</code> não faz nenhuma das duas coisas: deixa essas duas passarem, e o <code>retry()</code> dele refaz o fetch dos Server Components abaixo dele. Usamos em volta de páginas que fazem fetch no servidor (com o fallback reportando para o Sentry) e, no blog, em volta do corpo MDX, para que um post quebrado vire um bloco de "tente novamente" em vez de uma página em branco.</p>

<p><strong>TypeScript 7 no <code>next build</code>, com uma pegadinha do pnpm.</strong> Quatro dos cinco apps fazem type-check com o <code>tsc</code> nativo. O que não faz é o site de marketing, porque ele usa lint com <code>typescript-eslint</code>, cujo parser quebrava com a API do TypeScript 7 quando testamos. Os apps que usam Biome para lint nunca tiveram esse problema. E com o linker isolado do pnpm, o compilador nativo não inclui automaticamente <code>node_modules/@types</code>, então cada app precisou de um array <code>"types"</code> explícito no tsconfig. Dez minutos, mas dez minutos confusos.</p>

<h2>Cache Components muda a semântica HTTP, não só a velocidade</h2>

<p>Esta é a parte que eu colocaria em negrito em todo guia de migração. Com Cache Components, uma página faz streaming sobre um shell pré-renderizado, e o shell sai com um 200. Se depois sua página chama <code>notFound()</code> enquanto renderiza a parte dinâmica, o status já foi enviado. O usuário vê sua UI de not-found. O Google vê um 200.</p>

<p>Medimos isso no site de marketing antes de corrigir: um slug de blog inexistente, um slug de landing de anúncio inexistente e um slug de página de comparação inexistente respondiam todos 200. Três famílias de rotas inteiras retornando "encontrado" para lixo, justamente na propriedade em que os crawlers são o cliente. A correção segue a orientação do próprio framework: estabelecer a existência <em>antes</em> do primeiro byte. Nosso proxy agora valida os slugs do blog contra um manifest gerado no build (assim ele não fica desatualizado em produção, e carrega as datas de publicação para que posts agendados continuem funcionando), valida os outros dois contra suas tabelas estáticas e reescreve o que não bate para um path que nenhuma rota atende. O router então serve a página de not-found com a nossa marca e um 404 de verdade.</p>

<p>A segunda mudança semântica é que os segment configs acabaram. <code>export const revalidate = 3600</code>, <code>dynamic = "force-static"</code>, <code>dynamic = "force-dynamic"</code>: nenhum é permitido depois que a flag está ativada. Não é uma renomeação. <code>revalidate</code> vira um escopo <code>'use cache'</code> com <code>cacheLife('hours')</code>, e tudo que lê o relógio precisa ir para <em>dentro</em> desse escopo. Nosso blog filtra posts pela data de publicação com <code>new Date()</code>; fora de um escopo de cache isso é dado de request e um erro de prerender na hora, dentro dele o relógio é lido uma vez por entrada de cache e os posts agendados ainda aparecem dentro de uma hora. Feito isso, 204 páginas foram pré-renderizadas no build e um segundo acesso a um post caiu de 129 ms para 29 ms.</p>

<h2>Root params é o que destrava o i18n, e ainda não fizemos isso</h2>

<p>O exemplo de <code>import.meta.glob</code> do release post é um blog lendo arquivos MDX com <code>gray-matter</code>. É literalmente o nosso, então testamos em agosto. Compilou, e transformou as rotas do blog de dinâmicas em totalmente estáticas, o que quebrou o locale em tempo de request do next-intl com 500s em runtime. As leituras síncronas de <code>fs</code> são estruturais até o layout parar de derivar o locale de <code>params</code>.</p>

<p>Esse mesmo layout é o motivo de o site de marketing carregar <code>export const instant = false</code> em toda a árvore de locale: um layout que escolhe <code>&lt;html lang&gt;</code> e as mensagens a partir de <code>params</code> não consegue produzir um shell estático instantâneo. Root params (<code>lang()</code> de <code>next/root-params</code>) mais <code>setRequestLocale</code> é a solução, e é o próximo passo para esse app. Se o seu app é internacionalizado com um segmento <code>[locale]</code>, essa migração é o preço das Instant Navigations, e vale a pena colocá-la no orçamento desde o início em vez de descobri-la por meio de <code>instant = false</code>.</p>

<h2>Experimental significa experimental</h2>

<p>O React Compiler baseado em Rust é a feature que mais me empolgava e a que mais nos custou. Aconteceram duas coisas.</p>

<p>Primeiro, no nosso maior app ele derruba o build por OOM no container padrão da Vercel, uns 25 segundos depois do início de uma compilação a frio. O mesmo app builda bem pelo caminho do Babel (duas vezes mais lento, mas cabe), e os apps menores buildam bem com o port em Rust. Reportamos, um maintainer se envolveu no mesmo dia, e no fim de agosto eles tinham cortado pela metade o pico de alocação do compilador, com um caminho de crescimento quadrático como provável gatilho no nosso código. Esse trabalho está no canary. Rodamos o experimento de novo nesta semana no último patch do 16.3, em um preview deployment, e tivemos o mesmo SIGKILL. Então nossa config mantém um guard por target: port em Rust em todo lugar, exceto nos builds na Vercel daquele app, onde o compilador roda via Babel.</p>

<p>Segundo, o port em Rust perde a classe de escopo <code>jsx-&lt;hash&gt;</code> em elementos renderizados condicionalmente dentro de componentes com estado, então as regras com escopo de <code>&lt;style jsx&gt;</code> param de casar sem aviso. Isso foi para produção com os labels da sidebar invisíveis. Mantivemos um repositório com um repro mínimo (<a href="https://github.com/vercel/next.js/issues/96694">vercel/next.js#96694</a>) e nesta semana rodamos de novo contra três versões: ainda quebrado no último patch do 16.3, corrigido no canary do 16.4. Isso transformou um "talvez já esteja corrigido" em um número de versão, e significa que nossa regra de "nada de styled-jsx com escopo" fica exatamente até o 16.4.</p>

<p>Duas coisas menores da mesma categoria. <code>output: 'standalone'</code> combinado com um adapter quebrava os deploys na Vercel no 16.3.0 (faltava um arquivo de trace); o fix foi mergeado no canary dois dias depois e, até esta semana, ainda não saiu em nenhum patch estável do 16.3. Confira a tag, não o PR. E o guard de config do nosso fallback para webpack originalmente verificava <code>process.argv</code> procurando <code>--webpack</code>, o que nunca bate porque o <code>next.config</code> é avaliado no processo interno de servidor do Next. Um reviewer pegou isso rodando o fallback de verdade. Guard que você nunca exercita é guard que você não tem.</p>

<h2>O bug que não tinha nada a ver com o Next.js</h2>

<p>Toda a adoção do 16.3 no site de marketing foi revisada, aprovada e mergeada em 4 de agosto. Ela foi mergeada na feature branch sobre a qual estava empilhada, e essa branch nunca mais foi mergeada. Durante um mês o site rodou um binário do 16.3 com webpack em dev, sem compilador e com os segment configs antigos. Nada quebrou, e é exatamente por isso que ninguém percebeu; só descobri nesta semana fazendo grep em <code>main</code> atrás de uma flag que deveria estar lá. Reaplicamos a intenção na mão; um cherry-pick tinha dez arquivos em conflito contra um mês de trabalho de design. PRs empilhados são ok. PRs empilhados cuja base não é <code>main</code> precisam de um lembrete na base.</p>

<h2>O que eu diria a outro CTO</h2>

<ul>
<li><strong>Aplique os patches.</strong> O 16.3.3 corrigiu dois advisories críticos de execução remota de código, um deles na otimização de imagens com AVIF, que é o primeiro formato que nosso site de marketing serve. Nada na linha 16.3.x exigiu mudanças de código da nossa parte. Não há motivo para ficar no 16.3.0.</li>
<li><strong>Meça seus 404 depois de ativar Cache Components.</strong> Um <code>curl</code> por família de rotas. Se você vir 200 onde espera 404, mova as verificações de existência para o proxy.</li>
<li><strong>Trate <code>revalidate</code> como uma migração.</strong> Encontre cada segment config, encontre o que lê o relógio, mova para dentro de um escopo de cache.</li>
<li><strong>Dê a cada flag experimental uma saída de emergência por target e um jeito de testá-la de novo.</strong> A nossa é um commit no preview de um pull request e um revert. Seis minutos, e agora sabemos a resposta para este patch.</li>
<li><strong>Mantenha um repo de repro para cada bug que você reportar.</strong> Rodá-lo de novo contra uma versão nova custa dois minutos e responde a pergunta que comentários upstream não conseguem responder.</li>
<li><strong>Um fix no canary não é um fix no seu build.</strong> Confira a release tag.</li>
<li><strong>Se você é internacionalizado, planeje a migração para root params antes de planejar Instant Navigations.</strong> É o mesmo projeto.</li>
</ul>

<p>O 16.3 é um bom release. Só o cache de build e o error boundary já valeram o upgrade, e Cache Components é o modelo certo para os apps que construímos. Mas o release post descreve um destino, e o caminho até lá passa pelo seu proxy, pelo seu layout de i18n e pelo limite de memória do seu container de build. Saber disso antes de começar é a maior parte do trabalho.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Ralph Loop: a metodologia de desenvolvimento com IA que está dominando 2026]]></title>
      <link>https://me.jonymusky.com/pt/blog/ralph-loop-autonomous-ai-development</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/ralph-loop-autonomous-ai-development</guid>
      <pubDate>Thu, 16 Jan 2025 12:00:00 GMT</pubDate>
      <description><![CDATA[Conheça a técnica Ralph Wiggum: um loop de agente de IA autônomo que deixa o Claude Code trabalhando sem parar até o seu projeto estar concluído.]]></description>
      <category>ai-agents</category>
      <category>dev-tools</category>
      <content:encoded><![CDATA[O Ralph Loop (ou técnica Ralph Wiggum) está revolucionando a forma como devs trabalham com assistentes de código com IA. Criada por Geoffrey Huntley, essa metodologia elegantemente simples mantém um agente de IA trabalhando numa tarefa até ela estar realmente pronta.<br /><br /><strong>O que é o Ralph Loop?</strong><br /><br />No fundo, o Ralph é só um loop em Bash. Ele envia repetidamente o mesmo prompt para um agente de código com IA como o Claude Code. O pulo do gato? O progresso não fica na janela de contexto do LLM: ele vive nos seus arquivos e no histórico do git.<br /><br />Enquanto os workflows agênticos tradicionais param quando o LLM termina de chamar tools, o Ralph continua: verifica se terminou, dá feedback e roda outra iteração até a tarefa realmente dar certo.<br /><br /><strong>Como funciona</strong><br /><br />O plugin do Ralph usa um stop hook que é executado quando o Claude tenta encerrar uma sessão. Em vez de deixar o processo terminar, o hook intercepta a saída e procura uma 'palavra de segurança' ou promessa de conclusão. Se o agente tenta sair sem essa palavra específica (por exemplo, 'COMPLETE'), o hook bloqueia a saída e o trabalho continua.<br /><br />Cada iteração começa do zero com um contexto limpo, usando os arquivos e o git como memória em vez do contexto do modelo. Essa mudança de filosofia abraça os recomeços e deixa o git ser a camada de memória.<br /><br /><strong>Por que isso importa</strong><br /><br />Boris Cherny, Head of Claude Code na Anthropic, formalizou isso no plugin oficial ralph-wiggum. A VentureBeat declarou que Ralph Wiggum é 'o maior nome da IA no momento'.<br /><br />Para devs, isso significa que você pode montar ciclos de desenvolvimento autônomos em que o Claude Code melhora seu projeto de forma iterativa até a conclusão, com proteções embutidas para evitar loops infinitos e uso excessivo da API.<br /><br /><strong>Como começar</strong><br /><br />Dê uma olhada nestas implementações:<br />- <a href="https://github.com/snarktank/ralph" target="_blank" rel="noopener noreferrer">snarktank/ralph</a> - Loop de agente de IA autônomo para concluir PRDs<br />- <a href="https://github.com/vercel-labs/ralph-loop-agent" target="_blank" rel="noopener noreferrer">vercel-labs/ralph-loop-agent</a> - Autonomia contínua para o AI SDK<br />- <a href="https://github.com/frankbria/ralph-claude-code" target="_blank" rel="noopener noreferrer">frankbria/ralph-claude-code</a> - Loop de desenvolvimento autônomo com IA para o Claude Code<br /><br />O Ralph Loop representa uma mudança de paradigma no desenvolvimento assistido por IA. Em vez de microgerenciar cada interação, você define o objetivo e deixa o agente trabalhar até terminar. Bem-vindo ao futuro da programação.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[MCP: o protocolo que conecta a IA a tudo]]></title>
      <link>https://me.jonymusky.com/pt/blog/mcp-model-context-protocol</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/mcp-model-context-protocol</guid>
      <pubDate>Fri, 10 Jan 2025 12:00:00 GMT</pubDate>
      <description><![CDATA[Explorando o Model Context Protocol (MCP) da Anthropic e como ele está revolucionando a forma como os modelos de IA interagem com ferramentas e fontes de dados externas.]]></description>
      <category>ai-agents</category>
      <category>llms</category>
      <content:encoded><![CDATA[O Model Context Protocol (MCP) da Anthropic está mudando a forma como construímos aplicações de IA. Em vez de criar integrações customizadas para cada ferramenta, o MCP oferece uma forma padronizada para os modelos de IA se conectarem a bancos de dados, APIs, sistemas de arquivos e muito mais.<br /><br />O que torna o MCP especial é a simplicidade. Você define 'tools' que a sua IA pode usar, e o protocolo cuida da comunicação. Ou seja, você constrói uma vez e conecta em qualquer lugar.<br /><br />Tenho experimentado servidores MCP para vários casos de uso: conectar ao GitHub para code reviews, integrar com o Slack para updates do time e até criar tools customizadas para workflows específicos.<br /><br />O ecossistema está crescendo rápido. Confira a documentação oficial: <a href="https://modelcontextprotocol.io" target="_blank" rel="noopener noreferrer">Documentação do MCP</a>.<br /><br />Se você está construindo aplicações com IA, o MCP com certeza deveria estar no seu radar. É o tipo de protocolo que faz a gente se perguntar por que não tínhamos isso antes.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Claude Code: desenvolvimento com IA direto no seu terminal]]></title>
      <link>https://me.jonymusky.com/pt/blog/claude-code-cli-experience</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/claude-code-cli-experience</guid>
      <pubDate>Wed, 08 Jan 2025 12:00:00 GMT</pubDate>
      <description><![CDATA[Minha experiência usando o CLI do Claude Code nas tarefas de desenvolvimento do dia a dia e por que ele se tornou uma parte essencial do meu workflow.]]></description>
      <category>dev-tools</category>
      <category>ai-agents</category>
      <content:encoded><![CDATA[Depois de semanas usando o Claude Code como meu principal assistente de código, queria compartilhar como ele transformou meu workflow de desenvolvimento.<br /><br />A experiência no CLI é incrivelmente fluida. Você pode fazer perguntas sobre o seu codebase, refatorar código, escrever testes e até debugar problemas, tudo sem sair do terminal. A noção de contexto impressiona: ele entende a estrutura do seu projeto e navega muito bem em codebases complexos.<br /><br />Algumas features que uso todo dia:<br />- Explorar código e entender código legado<br />- Escrever testes unitários para funções existentes<br />- Refatorar com confiança<br />- Prototipar rapidamente novas features<br /><br />A integração com o Git é especialmente útil. Você pode revisar mudanças, escrever mensagens de commit e até criar pull requests direto da conversa.<br /><br />Para devs que vivem no terminal, o Claude Code parece uma extensão natural do seu workflow, e não uma ferramenta à parte.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Por que o Hono é meu framework favorito para edge computing]]></title>
      <link>https://me.jonymusky.com/pt/blog/hono-framework-2025</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/hono-framework-2025</guid>
      <pubDate>Sun, 05 Jan 2025 12:00:00 GMT</pubDate>
      <description><![CDATA[Um mergulho profundo no Hono, o framework web ultrarrápido que funciona sem atrito em Cloudflare Workers, Deno, Bun e Node.js.]]></description>
      <category>edge</category>
      <category>architecture</category>
      <content:encoded><![CDATA[Depois de construir vários projetos com o Hono, posso dizer com tranquilidade que ele virou meu framework favorito para edge computing e aplicações serverless.<br /><br />O que diferencia o Hono:<br />- Zero dependências e um bundle incrivelmente pequeno<br />- Funciona em qualquer lugar: Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda<br />- API no estilo Express, que soa familiar<br />- Middleware embutido para as tarefas mais comuns<br />- TypeScript-first, com uma inferência de tipos excelente<br /><br />A performance é impressionante. No Cloudflare Workers, o Hono praticamente não adiciona overhead aos seus cold starts. Somado à API intuitiva, dá para ir da ideia a uma API em produção em minutos.<br /><br />Gosto especialmente do ecossistema de middleware. Autenticação JWT, CORS, compressão e caching estão disponíveis com um simples import.<br /><br />Se você está construindo APIs para o edge, experimente o Hono: <a href="https://hono.dev" target="_blank" rel="noopener noreferrer">Documentação do Hono</a>.]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Durable execution: sistemas confiáveis com Temporal]]></title>
      <link>https://me.jonymusky.com/pt/blog/temporal-durable-execution</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/temporal-durable-execution</guid>
      <pubDate>Fri, 20 Dec 2024 12:00:00 GMT</pubDate>
      <description><![CDATA[Entendendo os padrões de durable execution e como o Temporal torna acessível a construção de sistemas distribuídos tolerantes a falhas.]]></description>
      <category>architecture</category>
      <content:encoded><![CDATA[Dando sequência ao meu post sobre a Replay Conference, queria me aprofundar no conceito de durable execution e por que ele importa para aplicações modernas.<br /><br />O tratamento de erros tradicional costuma levar a lógicas de retry complexas, dores de cabeça com gerenciamento de estado e falhas difíceis de debugar. A durable execution inverte esse modelo: seu código roda como se as falhas não existissem, e o framework cuida do resto.<br /><br />Com o Temporal, você escreve workflows como funções simples. Se um servidor cair no meio da execução, o workflow retoma automaticamente exatamente de onde parou. Sem checkpoints manuais, sem máquinas de estado complexas.<br /><br />Casos de uso reais que implementei:<br />- Pipelines de processamento de pedidos de longa duração<br />- Fluxos de onboarding de usuários em várias etapas<br />- Jobs batch agendados com dependências complexas<br />- Padrões Saga para transações distribuídas<br /><br />A mudança de modelo mental é grande, mas depois que a ficha cai, você vai se perguntar como conseguia construir sistemas confiáveis sem isso.<br /><br />Comece a explorar: <a href="https://temporal.io" target="_blank" rel="noopener noreferrer">Temporal.io</a>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Destaques da Replay Conference 2024]]></title>
      <link>https://me.jonymusky.com/pt/blog/replay-conference-2024</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/replay-conference-2024</guid>
      <pubDate>Tue, 26 Nov 2024 12:00:00 GMT</pubDate>
      <description><![CDATA[Destaques e recursos da Replay Conference 2024 da Temporal, em Seattle.]]></description>
      <category>events</category>
      <category>architecture</category>
      <content:encoded><![CDATA[Na semana passada, participei da Replay Conference 2024 da Temporal em Seattle, e foi uma experiência incrível! Aqui vão alguns destaques e insights que reuni durante o evento.<br /><br />Compilei um resumo do que venho explorando aqui: <a href="https://gist.github.com/jonymusky/8d1f2bbf2ed3168f37b1383b77fa2cfb" target="_blank" rel="noopener noreferrer">Resumo da Replay Conference</a>.<br /><br />Recomendo muito assistir às palestras: estavam cheias de informações úteis para devs e arquitetos. Você encontra todas no YouTube: <a href="https://www.youtube.com/watch?v=2floQta7GNs&list=PLl9kRkvFJrlR0xieUwBN_nNHW0oijCZa6" target="_blank" rel="noopener noreferrer">Palestras da Replay Conference</a>.<br /><br />A Temporal continua redefinindo a forma como pensamos sobre workflows e sistemas distribuídos, e esta conferência não foi exceção. Seja você iniciante em Temporal ou um usuário experiente, esses recursos valem muito a pena!]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Explorando as joias escondidas da Cloudflare]]></title>
      <link>https://me.jonymusky.com/pt/blog/exploring-cloudflare</link>
      <guid isPermaLink="true">https://me.jonymusky.com/pt/blog/exploring-cloudflare</guid>
      <pubDate>Tue, 26 Nov 2024 12:00:00 GMT</pubDate>
      <description><![CDATA[Descubra como Cloudflare Tunnels, Workers e Email Redirects podem turbinar o desenvolvimento em startups e provas de conceito.]]></description>
      <category>edge</category>
      <category>architecture</category>
      <content:encoded><![CDATA[Neste fim de semana, mergulhei em algumas das features menos conhecidas, mas poderosas, da Cloudflare: Tunnels, Workers (funções serverless), Email Workers e Email Redirects.<br /><br />Embora a maioria das empresas use a Cloudflare para otimização de requests, WAF, proteção contra bots e performance geral do site, tem muito mais para explorar! Muitas dessas funcionalidades costumam ser gratuitas e, na minha opinião, podem acelerar bastante o desenvolvimento.<br /><br />Para startups ou provas de conceito, a Cloudflare pode ser um divisor de águas. Com o Cloudflare Tunnels (50 seats grátis, como o ngrok, mas com mais features), por exemplo, você poderia ter um Raspberry Pi ou um Mac Mini rodando uma API simples em casa, reduzindo os custos iniciais de infraestrutura.<br /><br />Outra coisa empolgante é a possibilidade de se conectar ao Llama 3.1 (<a href="https://lnkd.in/deyeXCA9" target="_blank" rel="noopener noreferrer">Llama 3.1</a>) direto dos Workers, permitindo integrações fáceis com IA, totalmente de graça.<br /><br />Recomendo muito que você experimente! Minha única crítica é que a documentação poderia ser melhor, mas o potencial dessas ferramentas vale muito a pena.<br /><br />Para mais detalhes, confira meu post no LinkedIn: <a href="https://www.linkedin.com/feed/update/urn:li:activity:7264245965698519040/" target="_blank" rel="noopener noreferrer">Cloudflare Insights</a>.]]></content:encoded>
    </item>
  </channel>
</rss>