Um agente de IA com acesso ao repositório inteiro ainda escreve código errado. Não por falta de capacidade: ele lê melhor e mais rápido do que eu. Erra porque a parte cara do conhecimento de um sistema com mais de uma década não está no código — está na memória de quem já derrubou a produção.
O sistema em que trabalho todo dia tem mais de dez anos de produção ininterrupta: dezenas de módulos, um data warehouse com procedures agendadas, filas com topologia própria e um front-end acoplado a decisões antigas de API. Quando comecei a usar agentes de código nele, o padrão dos erros ficou óbvio: eram exatamente os mesmos erros que um desenvolvedor novo comete no primeiro mês. Código plausível, convenção correta, e ainda assim uma consequência invisível três camadas adiante.
O que resolveu foi tratar o contexto como artefato de engenharia: versionado no repositório, revisado em pull request e mantido com o mesmo critério de um teste.
A pergunta que decide o que entra
O erro mais comum é transformar o arquivo de contexto em documentação. Descrição de pastas, lista de endpoints, explicação de padrão de projeto. Tudo isso é ruído: o agente já lê o código e deduz melhor do que a sua descrição desatualizada. Pior, cada linha inútil compete por atenção com as linhas que importam. Arquivo grande é arquivo ignorado.
O filtro que uso tem dois critérios, e a regra só entra se passar nos dois:
- Não é derivável do código. Se dá para descobrir lendo o repositório ou o histórico do git, fora.
- Já custou caro. Incidente, retrabalho, hotfix ou bug que chegou no cliente.
O segundo critério é o que dá disciplina ao documento. Ele não cresce por opinião, cresce por post-mortem.
Três regras que existem porque doeu
Um campo de relatório mora em quatro lugares. O dado que o cliente vê num relatório passa por uma view SQL, por uma stored procedure de ETL que roda a cada quinze minutos, por variantes dessa procedure e pelas rotinas noturnas. Alterar a view e esquecer a procedure não quebra nada no deploy — quebra o número do relatório dois dias depois, silenciosamente. Nenhum agente descobre esse acoplamento lendo a view: os quatro arquivos não se referenciam. A regra é uma lista de quatro caminhos e uma frase: mexeu em um, mexa nos quatro.
Migration nova pode precisar de data no passado. Aquela view é reconstruída a
partir do arquivo .sql vivo dentro de uma migration antiga. Ou seja: se a view
passa a referenciar uma coluna nova, a migration que cria essa coluna precisa
rodar antes daquela migration antiga — o que só acontece se ela for datada
retroativamente. Sem isso, tudo funciona no banco que já existe e um migrate
do zero morre.
Foi o que aconteceu comigo. A alteração passou limpa em todos os ambientes que já tinham a coluna, e derrubou o primeiro que subiu do zero: 502 em produção, com a causa numa migration antiga que ninguém tinha motivo para abrir. Os deploys anteriores não tinham sido sortudos — eles nunca chegaram a executar aquele trecho.
Nunca repasse um 4xx de integração para o cliente. O tratador de erro do front-end trata qualquer 400 como 401 e desloga o usuário. O certo seria consertar o front-end — e é o que vai acontecer, no dia em que der para mexer nele sem coordenar release com todos os clientes que consomem a API. Enquanto isso, a regra é operacional: falha de integração vira 502. Um agente lendo apenas o back-end propaga o status original — e a consequência aparece num sistema que ele nem abriu.
As três regras têm em comum o que importa: são curtas, imperativas e impossíveis de inferir do arquivo que está sendo editado.
Tabela ensina melhor que parágrafo
Contexto útil se parece mais com uma tabela de decisão do que com um texto. Para filas, por exemplo, o agente não precisa entender a topologia — precisa acertar o destino:
| Fila | Conexão | Uso |
|---|---|---|
default | redis | Jobs curtos. Padrão para o que não está abaixo |
media | redis-long-running | Processamento de mídia (timeout 1800) |
report-export | redis | Exportações — pod dedicado com autoescalonamento |
Três linhas resolvem uma classe inteira de erro: job pesado despachado na fila padrão, que o worker mata por timeout. A versão em prosa da mesma informação ocuparia um parágrafo e seria seguida com menos precisão.
O mesmo vale para as armadilhas de teste. Elas viram uma lista seca:
- Uma das tabelas centrais não tem
idauto-increment: factory semidexplícito não persiste — oidficaNULL, sem lançar exceção. - Factories randomizam colunas de status. Force o estado que o teste precisa, ou o teste fica intermitente.
- Config em cache sobrepõe o
phpunit.xmle faz o truncate rodar no banco de desenvolvimento em vez do de teste.
Cada item desses custou uma tarde a alguém.
Memória separada do contexto permanente
Contexto permanente e histórico de trabalho são coisas diferentes, e misturar os dois estraga os dois. A regra da fila vale para sempre; o diagnóstico do card da semana passada só importa quando aquele assunto volta.
Por isso o que é permanente fica no arquivo de contexto do repositório, e o que é histórico fica numa memória à parte: um arquivo por fato, com um índice de uma linha por entrada. O agente lê o índice sempre e abre o arquivo inteiro só quando o assunto aparece.
O índice é a peça que faz funcionar. Uma linha por fato, escrita para ser descartada na leitura se o assunto não for aquele:
- [Ordenação de migration da view de relatório](view-migration.md) — coluna nova
referenciada pela view exige migration retroativa, senão o migrate do zero morre
- [Truncate no banco errado](config-cache-testes.md) — config em cache sobrepõe
o phpunit.xmlMEMORY.md
Mesmo princípio de índice de banco: carregar tudo é caro, carregar o ponteiro é barato. E quando o card volta seis meses depois, a investigação já está escrita.
Restringir a capacidade também é design
Peguei do sistema de skills do Matt Pocock a ideia de empacotar fluxos de trabalho como skills: arquivos versionados junto com o código, cada um com escopo declarado, carregados só quando o assunto aparece. O que adaptei foi o recorte. As dele são fluxos genéricos de desenvolvimento; os meus são amarrados ao processo do time, ao board e às regras do próprio sistema. A parte reaproveitável é o formato — o valor está no que só existe aqui dentro.
E a skill que mais mudou meu resultado não ensina o agente a escrever código. Ela cria um fluxo em que ele não escreve.
Lê o card no Jira, investiga o código, faz uma rodada de perguntas sobre as decisões de design e publica um plano de ação como comentário no próprio card. Não edita um único arquivo. O resultado é um plano revisável por humano, com os arquivos afetados e o porquê de cada um, antes de existir qualquer diff para revisar.
Planejar antes de editar não tem nada de novo — as ferramentas já trazem um modo de planejamento embutido. O que muda aqui é onde o plano para: num comentário do card, no fluxo que o time já lê, revisável por quem conhece o domínio e não está com o agente aberto. Plano que vive na sessão morre com a sessão.
E é isso que ataca o gargalo real de usar IA em base legada: a decisão que antecede o código. Revisar um plano de dez linhas custa cinco minutos. Revisar um pull request de trinta arquivos construído sobre a premissa errada custa a tarde inteira — e normalmente termina em “refaz”.
O efeito colateral
As armadilhas que eu explicava de boca para cada pessoa nova — o campo que mora em quatro lugares, a coluna que não é auto-increment, o 400 que desloga o usuário — estavam todas na minha cabeça, e só saíam de lá depois que alguém já tinha tropeçado. Agora estão num arquivo que qualquer um abre antes.
Escrever para a máquina me obrigou a escrever com precisão. Não dá para instruir um agente com “cuidado com os relatórios”: ou são os quatro caminhos, na ordem, ou a regra não existe. É o rigor que a gente cobra de uma mensagem de erro, aplicado ao conhecimento tácito do time.
Como isso se mantém vivo
Documentação de arquitetura morre porque ninguém tem motivo para atualizá-la. Essa não morre, porque o custo de estar desatualizada é imediato: o agente erra hoje, na sua frente, e você corrige a regra em vez de corrigir o mesmo código pela terceira vez.
O ciclo é curto:
- Incidente ou retrabalho acontece.
- Pergunto se a causa era derivável do código. Se era, o problema é outro — falta de teste, nome ruim, abstração vazando.
- Se não era, vira uma regra curta e imperativa, no pull request da própria correção.
- Detalhe que só vale para um módulo vai para o
READMEdaquele módulo. O arquivo raiz carrega apenas o que é transversal.
Esse último ponto é o que segura o crescimento. Sem ele, todo aprendizado quer morar no arquivo principal, e em seis meses você tem um documento de mil linhas que ninguém — nem o agente — lê até o fim.
O que eu levaria para qualquer projeto
Contexto não é prompt. É artefato versionado, com critério de entrada, dono e manutenção:
- Escreva o que não dá para deduzir do código; o resto é ruído caro.
- Toda regra deve ter um incidente atrás dela.
Isso funciona porque quem escreve as regras é quem viveu os incidentes. Num time que rodou inteiro, ou num sistema que ninguém acompanhou desde o começo, esse arquivo não tem de onde nascer — e nenhuma quantidade de agente resolve isso.
Contexto não é o que você configura na ferramenta. É o que sobrou de dez anos prestando atenção no que quebrou.
Se você acha que isso é só documentação com nome novo, eu quero ouvir o argumento. O contato está aberto.