Gotcha Lembrado é Gotcha Esquecido

A regra de virar script todo passo determinístico saiu de insight pontual e virou hábito permanente. Quatro sinais de alerta, dois exemplos reais — um com nome de arquivo e linha — e o número real de quanto isso pesa em token.

Iago Frota
produtividadeia

Publiquei o post sobre determinismo virar script há poucos dias. E a regra não ficou parada naquele texto.

Virou regra permanente para o Claude Code, em todo projeto que abro — não só no caso isolado que motivou o primeiro post. Guardei essa regra num arquivo que carrego comigo, fora de cada repositório, e ela vale em qualquer ferramenta que eu use.

Nas semanas seguintes apareceu material que aquele texto não tinha. Sinais de alerta que eu não sabia nomear antes. Dois exemplos que doeram mais que o da credencial. E um número real para o argumento do token, que antes ficou só na promessa.

Bora aprofundar.

O teste, rápido

Para quem não leu o post anterior, a régua é essa: se duas execuções corretas do mesmo passo produzem a mesma saída, é script. Se produzem saídas legitimamente diferentes, é inferência.

A regra escrita que uso hoje formaliza isso assim:

“Toda ação reconhecidamente determinística — que tem passos previsíveis, ordem fixa, entrada e saída definidas — deve ser um script, versionado e testado. Não pode depender de o modelo re-derivar o procedimento a cada execução. O modelo fica com o que é irredutivelmente de julgamento: ler contexto ambíguo, decidir escopo, cruzar card × código, redigir. Tudo à volta disso é maquinaria.”

Isso eu já tinha. O que faltava era reconhecer o sintoma antes de virar bug.

Sinais de que faltava um script

Aprendi, na marra, quatro sinais de que um passo determinístico está sendo tratado como se fosse julgamento. Nenhum é óbvio na hora. Só depois que dá errado.

  1. A mesma constante aparece como string literal em vários arquivos. Se um nome, um ID, um valor precisa ser lembrado em N lugares diferentes, “atualizar” vira “lembrar de todos os N”. E o lugar esquecido quebra em silêncio.
  2. A instrução descreve um procedimento fixo em prosa. “Faça X e depois Y, porque na criação o campo não é aceito” — isso é regra determinística escrita como texto corrido. Prosa é reexecutada por interpretação, toda vez. Script é reexecutado por chamada, sempre igual.
  3. O acerto depende de o agente lembrar de um detalhe específico. Um gotcha. E gotcha lembrado é gotcha que, uma hora, não é lembrado.
  4. Existe uma validação possível que só roda se alguém resolver rodar. Validação opcional não é validação. É uma etapa que você torce para não pular.

Os dois primeiros são estruturais — sobre onde a informação mora. Os dois últimos são comportamentais — sobre o que o agente precisa “lembrar” em vez de simplesmente executar. Reconheço o primeiro sinal porque errei nele antes de conseguir nomeá-lo. Vou contar esse exemplo já já.

O caso da minha wiki pessoal, que citei rápido no post anterior, já era o sinal um na prática — só que eu não tinha extraído a regra geral dele ainda.

Contar página não é decisão, é contagem. O script wiki-index-sync.sh sempre devolve o mesmo número para a mesma pasta, e eu não deixo o modelo chutar isso olhando por cima. Só faltava eu perceber que aquilo era instância de uma regra maior, não um hábito isolado daquele projeto.

Do julgamento ao script: como migrar

Antes dos exemplos, vale nomear o caminho, porque ele se repete e não é intuitivo na primeira vez.

  1. Dar a regra em palavras. O que conta como certo, o que conta como errado.
  2. Deixar o agente escrever o script. Ele decide a lógica; quem decide depois é o script, não o agente de novo.
  3. Testar contra um caso real, não um exemplo inventado.
  4. Quando algo passa batido, corrigir o script — nunca a instrução em prosa.
  5. Só então confiar. Daqui em diante, ninguém reprocessa aquele texto via IA de novo. Chama o script e aceita a saída.

O passo 4 é o que mais gente pula. É tentador corrigir só a frase que instrui o agente — “lembre de checar isso também” — em vez de corrigir a lógica que decide. Só que instrução corrigida ainda é prosa. E prosa esquece.

Esse caminho vale tanto para decisão binária — é credencial, sim ou não — quanto para procedimento de vários passos, como criar e depois atualizar. Muda só a forma do script: função, checagem, chamada encadeada. O caminho até lá é sempre o mesmo cinco passos.

O nome que morava em vinte lugares

Esse exemplo é meu, de verdade, sem precisar disfarçar nada — é de um repositório pessoal de skills portáteis (skills-portaveis) que mantenho para o Claude Code e outras ferramentas.

Resolvi renomear uma delas. Nome trocado, pensei — troca simples. Só que fui descobrir, procurando, que o nome antigo estava espalhado como string literal em install.sh:41, em duas linhas de scripts/configlib.py (504 e 554), e em cerca de vinte pontos diferentes de tools/gerar-fixtures-evals.py.

Nenhum desses arquivos era dono do nome. Ele só existia copiado, em todos eles, sem uma fonte única.

“Renomear a skill” não era decisão nenhuma de julgamento. Era um procedimento inteiramente determinístico — achar toda ocorrência da string antiga, trocar pela nova — que eu vinha fazendo por inferência, um arquivo de cada vez, torcendo para não esquecer nenhum.

E esqueci. Pelo menos uma vez, algo passou batido, silenciosamente.

A correção não foi só rodar um find-and-replace uma vez e seguir a vida. Foi perceber que aquele nome precisava morar em um lugar — uma constante, um arquivo de configuração — e ser lido dali por todo o resto. Nunca mais repetido cru em vinte pontos diferentes.

Esse exemplo generaliza melhor do que o da credencial no post anterior. Ele mostra que determinismo virar script não é só decidir “isso é X ou não é X”. É também sobre onde uma informação mora. Nome único vence string espalhada, sempre.

Hoje, antes de dar um rename como terminado, rodo uma busca pela string antiga no repositório inteiro. É o sinal quatro da lista lá em cima, aplicado a mim mesmo: aquela busca era uma validação que dependia de eu lembrar de rodar. Agora é passo obrigatório do procedimento, não gentileza que faço quando lembro.

As duas chamadas que ninguém lembrava de fazer

O segundo exemplo é de um contexto de trabalho. Vou generalizar sem nomear nada, porque o ponto não depende do nome da empresa nem do projeto.

Mexi com a API de um sistema de rastreamento de trabalho baseado em tickets. Dois campos de classificação tinham um comportamento chato: a API REST desse sistema não aceita esses dois campos no payload de criação do item. Eles nem aparecem no schema de metadados de criação. Só dá para setá-los depois, com uma chamada de atualização separada, sobre o item já criado.

Ou seja: criar aquele tipo de item exige sempre duas chamadas HTTP, nessa ordem exata. Criar, depois atualizar. Nunca uma só.

Isso é comportamento determinístico e conhecido. Qualquer dev que já integrou com esse tipo de API reconhece o padrão. Sempre as duas chamadas, sempre nessa ordem, sempre pela mesma limitação de schema.

Só que, antes de virar regra escrita, o agente tinha que lembrar disso a cada vez que criava um item daquele tipo. Às vezes esquecia. Criava só com a primeira chamada, e os dois campos ficavam vazios — sem erro, sem aviso, só um dado faltando que alguém ia notar depois, tarde demais.

Esse é o sinal dois e o sinal três da lista lá em cima, juntos: o comportamento estava descrito em prosa (“lembre de atualizar esses campos depois”), e o acerto dependia de memória. A correção foi documentar como procedimento fixo — melhor ainda, como função que sempre faz as duas chamadas, na ordem certa, sem depender de ninguém lembrar de nada.

Um script assim nem precisa ser sofisticado. Só precisa ser chamado sempre, sem exceção. Porque a exceção é exatamente onde o dado começa a ficar incompleto — e ninguém percebe na hora, só quando alguém vai procurar aquele campo depois e ele não está lá.

Os dois exemplos, juntos, mostram algo que o post anterior não tinha como mostrar sozinho: determinismo escondido aparece tanto num nome de arquivo quanto numa chamada de API. Muda a superfície, não a lógica de identificar.

Quanto isso pesa em token, de verdade

O post anterior falou em “economiza token” sem dar número. Vou dar um agora, porque medi, numa ferramenta de auditoria de contexto que uso.

Numa sessão agêntica, cada request reenvia o histórico inteiro da conversa. O modelo não guarda memória entre chamadas — cada chamada nova carrega tudo que veio antes. Isso faz o custo de uma sessão crescer perto do quadrado do número de mensagens, não linear.

Dissequei um request real, de uma sessão de trabalho comum: 2,1 MB, 231 mensagens. O histórico de conversa sozinho já é cerca de 246 mil tokens — 94,4% do payload inteiro. Definição de ferramenta: 4,8%. System prompt: 0,8%.

E dentro desse histórico, a composição surpreende:

  1. 24,9% é raciocínio que o modelo já fez antes, sendo reenviado de novo.
  2. 22% são avisos automáticos do próprio sistema.
  3. 17,7% são chamadas de ferramenta.
  4. O que o humano escreveu de fato, mais o que o assistente respondeu de fato, é só 18,7% do total.

O resto — mais de 81% — é maquinário do jeito como a conversa funciona. Não é conteúdo.

Aqui está a ponte com o argumento do post anterior: toda vez que você joga para inferência uma decisão que já tinha resposta fixa, você não paga só o raciocínio daquela vez. Você acrescenta mais uma rodada de histórico, e essa rodada vai ser reenviada, inteira, em toda chamada seguinte da sessão — pelo resto dela.

Um script devolve resultado curto: é credencial, sim ou não. Uma rodada de inferência pendurada na conversa carrega raciocínio, chamada de ferramenta e resposta — e isso não some. Fica ali, sendo pago de novo a cada turno posterior. Não é gastar token uma vez. É engordar o histórico que toda mensagem futura vai carregar junto.

Multiplica isso pelas vezes, numa sessão de duas horas, em que você deixa o agente decidir de novo algo que já tinha resposta certa. Cada rodada dessas não é custo isolado. É parcela que se soma a todas as rodadas seguintes, porque o histórico não esquece nada — só o agente esquece, se você deixar por conta da memória dele.

Por fim

Confesso que demorei para nomear os quatro sinais lá de cima. Vivi o problema do nome espalhado antes de conseguir explicá-lo — e só consegui depois que ele já tinha quebrado uma vez, em silêncio.

O que mudou de verdade, desde o primeiro post: parei de tratar isso como insight de uma sessão. Virou regra escrita, que carrego entre projetos, e que releio quando estou prestes a aceitar prosa onde cabia script.

Não virou reflexo automático — ainda preciso parar e perguntar, toda vez. Mas agora sei o que perguntar: essa constante mora em um lugar só? Essa instrução é procedimento ou é prosa disfarçada de procedimento? Essa validação roda sozinha ou só se eu lembrar dela?

Continuo sem saber Python. Continuo pedindo script para quem sabe. A diferença é que agora eu sei apontar, com mais precisão, onde o determinismo estava escondido.

E você, olhando para trás — quantos dos seus próprios gotchas ainda moram só na sua memória, esperando o dia em que você vai esquecer?