Versão original em inglês: How AI and Agentic Development Are Reshaping Software Engineering.

Durante a maior parte das últimas duas décadas, a habilidade escassa em engenharia era produzir o artefato: escrever o código, ligar os serviços, fazer a coisa funcionar. A implementação era o gargalo, e toda a profissão estava organizada em torno disso: como você era contratado, como era avaliado, como crescia.

A IA moveu esse gargalo. Quando um modelo consegue rascunhar um serviço funcional, um plano de migração ou um diagrama de sistema em segundos, o ato de produzir um artefato plausível deixa de ser um diferencial, porque agora qualquer um consegue produzir um. A restrição não é mais “você consegue construir isso?” A restrição é “você sabe o que construir, e por que isso em vez das alternativas?”

O desenvolvimento agêntico vai além. Um agente não apenas emite um trecho de código; ele executa um plano de múltiplas etapas, chama ferramentas e produz saídas funcionais ao longo de um fluxo de trabalho inteiro. O ser humano não é mais quem executa os passos. O ser humano é quem:

  • formula o problema corretamente,
  • define as restrições dentro das quais o agente deve operar,
  • revisa o resultado de forma crítica,
  • e assume a decisão quando algo dá errado.

Isso muda o que senioridade significa:

  • Um engenheiro júnior costumava ser alguém que precisava de orientação na implementação. Em um mundo agêntico, um engenheiro júnior é alguém que aceita a primeira saída que o modelo produz.
  • Um engenheiro sênior é alguém que sabe quando rejeitá-la, o que está faltando nela, e como direcionar o sistema para uma resposta melhor.
  • Um engenheiro principal é alguém que consegue olhar para três opções geradas por IA e explicar, com precisão, qual delas é a certa para esse contexto, por que as outras ficam aquém, e o que precisaria ser verdade para que cada uma delas se tornasse a resposta correta.

Mas rejeitar e comparar não se trata de identificar falhas que o modelo não consegue ver. Um agente capaz frequentemente vai nomear essas falhas por você: a fila sem limite, o cache silenciosamente servindo dados desatualizados, a migração sem rollback, a chamada síncrona que causa cascata de falhas.

O que o agente não consegue fazer de forma confiável é dizer qual dessas falhas realmente importa aqui, ponderá-la em relação a restrições que ele não conhece, e arcar com as consequências se estiver errado. Seu reconhecimento é real, mas inconsistente e cego ao contexto. Seu trabalho é ser a camada que torna isso confiável:

  • decidir qual risco domina de acordo com os SLAs reais, o time e o orçamento,
  • identificar a mitigação confiante que está sutilmente errada,
  • e assumir o resultado independentemente do que produziu o artefato.

Há uma segunda metade do papel, igualmente importante e muito mais escalável. Um agente é tão bom quanto o ambiente em que opera. Deixado a um prompt vazio, ele improvisa de forma diferente a cada sessão, desvia das convenções do time, e re-deriva decisões que foram tomadas meses atrás. O sênior é a pessoa que elimina essa variância antecipadamente construindo os trilhos nos quais os agentes e o time correm:

  • os harnesses que capturam erros automaticamente: testes, CI, sistemas de tipos, linters e evals, para que uma resposta errada falhe ruidosamente em vez de ser publicada silenciosamente,
  • o conhecimento tribal capturado: architecture decision records (ADRs), convenções, runbooks e arquivos de contexto que codificam como as coisas realmente funcionam aqui,
  • e o contexto compartilhado que viaja junto, para que um agente se comporte de forma consistente entre sessões, cada engenheiro do time obtenha resultados reproduzíveis, e o conhecimento se acumule entre times em vez de ficar na cabeça de uma única pessoa.

É aqui que está a alavancagem. Revisar a saída de um agente é esforço linear. Construir o harness e o contexto que fazem cada agente e cada colega de time produzirem resultados confiáveis é a forma moderna do que engenheiros sêniores sempre fizeram: transformar conhecimento tácito, conquistado a duras penas, em algo do qual toda a organização pode depender, de forma reproduzível.

A habilidade escassa, portanto, migrou da execução para o julgamento:

  • escolher qual opção entre várias, não gerar uma opção,
  • saber o que rejeitar e por quê,
  • nomear o trade-off que a IA negligenciou,
  • e defender a decisão quando alguém questiona.

Isso não é um ajuste temporário. É uma mudança estrutural no que é a engenharia de software. Os engenheiros que prosperam nessa era não são os que resistem à IA ou apenas a toleram, mas os que desenvolvem uma nova camada de ofício sobre ela:

  • a capacidade de raciocinar claramente sobre sistemas,
  • comunicar decisões com precisão,
  • e assumir responsabilidade por resultados que um agente ajudou a produzir, mas que um ser humano deve possuir.

Esse ofício não é inato. É um método. E como qualquer método, pode ser aprendido, praticado e tornado repetível. As seções abaixo o decompõem em hábitos concretos:

  • um template para estruturar uma decisão,
  • uma forma de usar IA que adiciona julgamento em vez de apenas velocidade,
  • um kit de padrões para manter afiados,
  • uma disciplina para defender cada decisão,
  • e um hábito de capturar decisões para que se acumulem.

Um único exemplo em execução — Acme.ai — percorre toda a seção, de modo que cada princípio aparece tanto em abstrato quanto aplicado na prática.


Como o processo de contratação está se adaptando

À medida que a habilidade escassa migra da implementação para o julgamento, a contratação acompanha. Puzzles de live coding e trivia de algoritmos estão cedendo espaço para avaliações baseadas em cenários onde o auxílio da IA não é apenas permitido, mas pressuposto. A pergunta que está sendo feita mudou. Não é mais “você consegue produzir uma solução”, porque um modelo consegue. É “você consegue produzir uma pela qual possa se responsabilizar”.

O artefato que você entrega não é a entrega em si; é a primeira jogada em uma conversa. Alguém vai sondar o raciocínio, questionar as escolhas e levantar alternativas que você não considerou. As pessoas que se saem bem são as que escreveram com isso em mente, da mesma forma que fariam para uma design review real: suposições tornadas explícitas, opções rejeitadas nomeadas, um rastro de julgamento em vez de uma demonstração de output.


O método

Cada parte deste método é mais fácil de entender em movimento do que de forma abstrata. Aqui está o exemplo em execução pelo qual o resto desta seção trabalhará.

Acme.ai é um SaaS B2B multi-tenant de analytics. Clientes constroem dashboards e exportam relatórios. Hoje, quando um usuário clica em “Export report”, o servidor web executa a query, renderiza um PDF ou CSV de forma síncrona e o retorna na resposta HTTP. Isso funcionava no lançamento.

Agora os maiores tenants têm datasets 50x maiores: exportações levam de 30 a 90 segundos, atingem o timeout do gateway de 60 segundos e saturam os web workers durante o horário comercial, degradando o app inteiro para todos os tenants.

O produto quer duas novas capacidades: relatórios agendados (“toda segunda-feira às 8h, me envie o resumo semanal por e-mail”) e grandes exportações (até aproximadamente 1 milhão de linhas) que atualmente falham completamente. Evolua o recurso de relatórios.

O método são cinco hábitos. Apenas um deles é algo que você produz — a decisão estruturada no centro; os outros quatro são disciplinas que a cercam.

Antes de começarMantenha os fundamentos na cabeçaConheça os padrões essenciais de cor, para que o certo venha à mente no momento em que um problema o exige.
Durante todo o processoUse IA como parceira de raciocínio, não como máquina de respostasDeixe o modelo gerar opções e nomear riscos; você poda, rejeita e aprimora o que sobra.
O artefato que você produzEstruture a decisão com um template repetívelO trabalho do engenheiro — a única coisa que você realmente cria. Os outros quatro hábitos existem para tornar este defensável.
Uma passada em cada parteDefenda cada decisão antes que alguém pergunteTeste cada escolha contra a alternativa óbvia antes que um revisor o faça.
Depois — para que se acumuleCapture a decisão para que se acumuleEscreva em ADRs, fitness functions e arquivos de contexto para que a próxima pessoa e o próximo agente a herdem.

Manter os fundamentos na cabeça é uma pré-condição: eles precisam já estar lá quando o problema chega, porque você os recupera no momento em vez de aprendê-los então. Usar IA percorre todo o processo. Defender cada decisão é uma passada por todo o template, aplicada a cada escolha nele — restrições, opções, recomendação, riscos — não uma etapa única executada depois da recomendação. E capturar a decisão acontece depois que a poeira baixa.

Estruture a decisão com um template repetível: UP/DowN

Dê um nome ao template, porque uma estrutura nomeada é aquela que você realmente usa sob pressão. Eu chamo de UP/DowN — Understand, Plan, Decisions, Notes — e as maiúsculas marcam as duas metades. UP é o trabalho de base: a situação em que você está e o plano para mudá-la. DowN é o julgamento e seu resíduo: qual opção você escolheu e por quê, e o que a próxima pessoa precisa observar por causa disso.

  • U — Understand (Entender): reformule o problema, as restrições e as suposições que você está fazendo, e nomeie a área de impacto — o que isso toca e o que não pode quebrar,
  • P — Plan (Planejar): o caminho de migração ou implantação que coloca sua abordagem escolhida em produção sem downtime,
  • D — Decisions (Decisões): as duas ou três abordagens candidatas (incluindo uma deliberadamente conservadora), a matriz de trade-offs e a recomendação — a escolha, as alternativas que ela venceu e por que perderam aqui,
  • N — Notes (Notas): os riscos, o que você mediria ou validaria primeiro, e os efeitos colaterais e ressalvas que não se encaixam em mais lugar nenhum.

São as mesmas quatro seções que você colocaria em um design doc escrito. O exemplo em execução abaixo preenche todas as quatro. Para uma decisão de design como esta, o Plano de implantação naturalmente vem por último — você não pode planejar uma implantação até ter escolhido o que está rodando.

A estrutura permite que você gaste sua atenção no raciocínio em vez de na estrutura, e torna seu raciocínio legível para quem o lê a seguir: um colega de time, um revisor, ou você mesmo daqui a seis meses. O UP sustenta o DowN — sem o trabalho de base, a decisão que você registra é apenas uma opinião com formatação. Mantenha cada movimento enxuto; densidade de sinal supera extensão.

Aplicado à Acme.ai:

Cada parte abaixo emparelha o prompt que a conduz com o que é retornado. Os prompts são turnos em uma única sessão: você entrega ao agente o briefing e o codebase no primeiro turno, e ele carrega esse contexto pelo restante.

U — Problema, restrições e suposições.

Você delega a exploração; você define os limites que ela deve respeitar.

Aqui está o sistema e a mudança: [cole o resumo da Acme.ai acima]. Leia o codebase,
depois mapeie a área de impacto para a mudança de exportação: quais componentes, tabelas
e endpoints ela toca, o que quebra, o que não pode quebrar, e as restrições rígidas
que não posso violar — o timeout do gateway de 60 segundos, o endpoint síncrono ativo
com consumidores reais, e o isolamento por tenant.

O que você confirma na seção — o mapeamento do agente, mais as restrições que só você pode declarar:

O problema central:

  • Geração síncrona, dentro da requisição, não escala.
  • Jobs pesados ocupam web workers compartilhados, causando degradação noisy-neighbor para todos os tenants.

Suposições declaradas:

  • região AWS única,
  • Aurora Postgres como store principal,
  • o endpoint síncrono existente tem consumidores de API ao vivo que não podem ser quebrados,
  • time pequeno, portanto simplicidade operacional importa,
  • sensível a custos.

Metas não funcionais:

  • exportações de até 1 milhão de linhas concluídas em até 5 minutos,
  • latência do app não afetada,
  • sem envios agendados duplicados,
  • artefatos recuperáveis por 7 dias.

D — Abordagens candidatas.

Você delega o espaço de opções, mas reserva a escolha.

Gere três abordagens: uma deliberadamente conservadora e mínima, duas
significativamente diferentes. Para cada uma, me dê: como funciona em algumas
frases; um diagrama Mermaid do fluxo de requisição e dados onde clarifica o design;
as mudanças de schema e API; as estratégias de migração e de rollback; os trade-offs que
carrega; e suas limitações conhecidas. Não recomende nenhuma — eu vou escolher.

O que é gerado — três opções para escolher, não uma recomendação:

  • Opção A (conservadora): manter a geração síncrona, mas aumentar o timeout, mover exportações para um pool de workers dedicado atrás de um endpoint separado, e adicionar uma read replica para que queries pesadas parem de bater na primária. Mudança mínima.
  • Opção B (fila de jobs assíncrona): “Export” enfileira um job; workers geram o artefato, fazem stream para o S3 e notificam o usuário via e-mail e link in-app. Relatórios agendados reutilizam o mesmo job, disparado por um scheduler. Um mecanismo serve para ambos os novos recursos.
  • Opção C (serverless gerenciado): terceirizar a geração para Step Functions e Lambda, artefatos no S3, EventBridge para agendamento. Menos infraestrutura para operar, mas nova superfície operacional e limites de runtime/tamanho para jobs muito grandes.

O núcleo da recomendação é uma mudança no formato do caminho da requisição. O design atual faz tudo inline; a Opção B move a geração completamente para fora dele.

Atual (síncrono):

sequenceDiagram
    actor User
    participant API as Web server
    participant DB as Postgres
    User->>API: Click "Export report"
    API->>DB: Run query (30-90s)
    DB-->>API: Rows
    API->>API: Render PDF / CSV
    API-->>User: File in HTTP response
    Note over User,API: Request blocks for the whole generation — web workers saturate and the app degrades

Proposto (Opção B, assíncrono):

sequenceDiagram
    actor User
    participant API as Web server
    participant Q as Job queue
    participant W as Worker pool
    participant DB as Read replica
    participant S3 as S3
    participant N as Notifier
    participant Sch as Scheduler
    User->>API: Click "Export report"
    API->>Q: Enqueue job
    API-->>User: 202 Accepted (immediate)
    Sch->>Q: Enqueue scheduled job (cron)
    W->>Q: Pull job
    W->>DB: Run query
    W->>S3: Stream artifact in chunks
    W->>N: Job done
    N-->>User: Email + in-app link
    Note over User,API: Request returns immediately — generation runs off the request path

D — Matriz de trade-offs.

Você nomeia as dimensões que importam aqui; ele preenche a grade.

Compare as três abordagens nas dimensões que importam aqui — capacidade de 1M de
linhas, latência do app, suporte a relatórios agendados, esforço de construção,
custo operacional, reversibilidade — como uma tabela.

O que é produzido:

DimensãoA: sync reforçadoB: fila assíncronaC: serverless
Suporta exportação de 1M linhasFracoForteFraco (limites de runtime)
Protege latência do appParcialForteForte
Habilita relatórios agendadosNãoSimSim
Esforço de construçãoBaixoMédioMédio-alto
Custo/complexidade operacionalBaixoMédioBaixo-médio, nova superfície
ReversibilidadeAltaMédiaMédia

D — Recomendação e raciocínio.

Você toma a decisão — depois manda o agente atacá-la.

Estou inclinado para a Opção B porque ela remove a geração do caminho da requisição
e uma abstração de job serve para ambos os recursos. Faça o argumento mais forte
contra B, e o melhor caso para a opção que estou rejeitando. O que estou perdendo?

Sua decisão, após considerar o contraargumento:

  • A Opção B é o núcleo, incorporando a read replica da Opção A como um movimento de suporte:
    • B remove a geração do caminho da requisição, a causa raiz real do problema de noisy-neighbor.
    • A mesma abstração de job serve tanto para relatórios agendados quanto para grandes exportações: um mecanismo para construir e operar, não dois.
    • O esforço de construção é médio: a fila, os workers, o upload para S3 e a lógica de notificação são primitivos bem entendidos com bibliotecas maduras, mas ainda requerem trabalho deliberado em torno de idempotência, tratamento de dead-letter e autoscaling.
  • A Opção C é adiada, não descartada.
    • Na escala atual, adiciona uma nova superfície de falha com benefício marginal.
    • Com 10x de volume, o cálculo muda: frotas de workers ficam caras para dimensionar e operar, a latência de cold-start importa menos quando os jobs são de longa duração, e o retry embutido do Step Functions, gerenciamento de estado e observabilidade começam a se pagar.
    • O esforço de construção é médio-alto: adiciona um novo ambiente de execução para aprender e configurar (empacotamento Lambda, definições de state machine do Step Functions, regras EventBridge), introduz superfície de IAM, e exige que o time raciocine sobre cold starts, limites de runtime do Lambda e rastreamento distribuído entre serviços gerenciados — nada disso vem de graça mesmo quando a infraestrutura é gerenciada.

N — Riscos e o que validar primeiro.

Você solta o agente sobre seu próprio design, uma lente hostil de cada vez.

Teste esse design sob pressão como quatro revisores em sequência — um revisor de
segurança, um SRE, um dono de custos e um futuro mantenedor. Para cada um, nomeie
o modo de falha que eu mais me arrependeria de ignorar.

As lentes revelam o que uma única perspectiva perde:

  • Revisor de segurança: artefatos S3 contêm dados de tenants — URLs assinadas com TTL curto, criptografia em repouso, chaves com escopo por tenant para que o tenant A nunca possa buscar a exportação do tenant B.
  • SRE: fila de dead-letter, alertas sobre profundidade da fila e taxa de falhas, retries com limite, e um job travado que não deve bloquear a fila.
  • Dono de custos: autoscale de workers pela profundidade da fila, uma regra de lifecycle no S3 para expirar artefatos após 7 dias, e a read replica como o principal custo recorrente a ser justificado.
  • Futuro mantenedor: uma abstração de job para ambos os recursos, um contrato de idempotência documentado, e EventBridge ou cron para agendamento em vez de um scheduler artesanal.

A partir daí, os riscos a resolver primeiro — e a forma mais barata de resolvê-los:

  • Envios agendados duplicados (scheduler dispara duas vezes, ou um worker faz retry no meio do envio): chaves de idempotência por (schedule_id, period), entrega at-least-once com deduplicação.
  • Backlog da fila sufocando jobs agendados durante os horários de pico: filas e prioridades separadas para trabalho interativo versus agendado.
  • Explosão de memória na exportação de 1M de linhas: fazer stream para S3 em chunks, nunca bufferizar o conjunto de resultados completo em memória.

Valide primeiro a coisa mais barata: crie um spike com um worker gerando um CSV de 1M de linhas em stream para S3, meça o tempo de parede e a memória de pico, e comprometa-se com o design completo somente depois que esses números estiverem em mãos.

P — Migração e implantação.

Você delega o rascunho; você é dono do gatilho de rollback.

Rascunhe a implantação: uma migração strangler-fig atrás de uma feature flag do caminho
síncrono para o assíncrono, depois canary para cada mudança subsequente. Inclua o
gatilho de rollback e a telemetria que controla cada expansão.

O que é rascunhado — uma migração única, depois releases seguros depois disso:

Strangler-fig (estranguladora) recebe o nome de uma trepadeira que cresce ao redor de uma árvore existente, gradualmente substituindo-a sem nunca cortá-la. Em software, significa introduzir um novo caminho ao lado do antigo, roteando o tráfego para ele incrementalmente via uma feature flag, e descontinuar o caminho antigo somente depois que o novo tiver se provado. Os dois endpoints coexistem durante toda a duração da migração.

flowchart LR
    REQ([Export request]) --> FF{Feature flag}
    FF -->|async criteria met| ASYNC[New async endpoint]
    FF -->|not yet migrated| SYNC[Old sync endpoint]
    ASYNC --> Q[(Queue)] --> W[Workers] --> S3[(S3)] --> N[Notify user]
    SYNC --> DB[(Postgres)] --> R[Render inline] --> HTTP[HTTP response]
    style ASYNC fill:#d4edda,stroke:#28a745
    style SYNC fill:#ffd7d7,stroke:#dc3545

Os critérios da feature flag se expandem ao longo do tempo: primeiro apenas os grandes tenants, depois a maioria, depois todos. Quando a telemetria mostra que o caminho assíncrono cobre todos os casos, o endpoint síncrono é descontinuado e removido. Aí a strangler-fig está completa.

Canary deployment assume a partir desse ponto. Não se trata de substituir um sistema por outro; trata-se de publicar uma nova versão do mesmo sistema com segurança. Uma pequena porcentagem do tráfego vai para a nova versão enquanto o restante permanece na versão estável. Se as taxas de erro e a latência ficarem dentro dos limites, a implantação se expande. Se não, o tráfego é redirecionado de volta antes que o problema atinja a maioria dos usuários.

flowchart LR
    Q[(Job queue)] --> CR{Canary router}
    CR -->|5 percent| NW[v2 workers]
    CR -->|95 percent| SW[v1 workers]
    NW --> M[Monitor errors and latency]
    M -->|healthy| EXP[Expand to 25, 50, 100 percent]
    M -->|unhealthy| RB[Rollback to v1]
    style NW fill:#fff3cd,stroke:#ffc107
    style SW fill:#d4edda,stroke:#28a745
    style RB fill:#ffd7d7,stroke:#dc3545
    style EXP fill:#d4edda,stroke:#28a745

Os dois padrões são sequenciais, não alternativos. Strangler-fig é a migração única para a Opção B. Canary é como cada mudança subsequente à Opção B é publicada depois que ela está no ar.

Aplicado à Acme.ai:

  • Manter o endpoint síncrono existente intacto para exportações pequenas e legadas.
  • Introduzir o caminho assíncrono atrás de um novo endpoint e uma feature flag.
  • Rotear os maiores tenants pelo caminho assíncrono primeiro, onde a dor é maior.
  • Migrar o restante à medida que a telemetria constrói confiança.
  • Descontinuar o caminho síncrono somente quando os dados mostrarem que o assíncrono cobre os casos.
  • A partir desse ponto, todas as mudanças no código dos workers são publicadas via canary.

Use IA como parceira de raciocínio, não como máquina de respostas

Deixe o modelo rascunhar o espaço de opções e trazer à tona coisas dignas de consideração, depois aplique sua própria camada por cima: podando o que não se encaixa no contexto, rejeitando o que parece plausível mas cede sob pressão, e aprimorando o raciocínio por trás do que sobra. É aqui que o trabalho de engenharia real acontece.

Pense em si mesmo como o diretor e no agente como um assistente: um avatar que é tão bom quanto seu harness — as regras, comandos, skills, hooks, verificações automatizadas, linters, testes unitários e ADRs documentados dentro dos quais ele opera. Esse harness precisa carregar o conhecimento tribal do seu time, mais seu próprio conhecimento local, preferências e ferramentas. Quanto melhor equipado o assistente, mais com precisão e eficiência ele lê um codebase inteiro em segundos e rascunha uma dúzia de alternativas em minutos — mesmo que não saiba qual delas importa aqui. Às vezes um bom harness pode até equipá-lo para perceber a diferença.

Deixe-o conduzir o trabalho mecânico — explorar o sistema, enumerar abordagens, rascunhar a implantação, testar sob pressão seu próprio design através de lentes hostis como um revisor de segurança, um SRE, um dono de custos e um futuro mantenedor — e você fica com a parte que nunca foi mecânica: qual abordagem se encaixa, qual risco domina, e o raciocínio em suas próprias palavras. “Porque é uma boa prática” não é uma razão.

Os prompts entremeados no exemplo trabalhado acima mostram essa divisão em ação. Cada um entrega ao agente a geração e reserva o julgamento para você — note o recorrente “não recomende nenhuma; eu vou escolher.” A velocidade do agente não está lá para produzir mais output; está lá para comprimir as partes que nunca foram a parte difícil, para que o tempo que você economiza vá para a única parte que era: possuir a decisão bem o suficiente para defendê-la quando alguém questionar.

Mantenha os fundamentos na cabeça

Esse hábito é sobre você, não sobre seu ferramental. Você não consegue reconhecer o modo de falha que não consegue nomear, então precisa conhecer esses padrões essenciais bem o suficiente para que o certo venha à mente no momento em que um problema o exige:

Isolamento multi-tenant
  • O isolamento deve ser garantido na camada de armazenamento, não na camada de aplicação.
  • Um bug ou violação para na fronteira do tenant por design.
  • Três modelos: schema compartilhado, schema separado, banco de dados separado. Cada um troca custo por força de isolamento.
  • Migrar entre modelos é caro. Escolha cedo.
Particionamento de dados
  • Um único nó tem um limite. Particionamento é como você o ultrapassa.
  • A escolha da chave de partição determina o risco de hotspot.
  • Resharding de um sistema existente é trabalhoso. Escolha a chave desde o início.
  • Joins entre shards são caros. Projete queries para permanecer dentro de um único shard.
Fronteiras de serviço
  • Alinhe fronteiras a capacidades de negócio, não a camadas técnicas.
  • Serviços não compartilham bancos de dados. A propriedade dos dados segue a fronteira.
  • Se um serviço não consegue fazer deploy de forma independente, não é verdadeiramente separado.
  • Uma chamada distribuída tem custos reais. Decomponha com intenção.
Migração strangler-fig
  • Nunca substitua um sistema ao vivo de uma só vez. Roteie o tráfego incrementalmente.
  • Os caminhos antigo e novo coexistem durante a migração.
  • Uma feature flag mantém cada etapa reversível.
  • Descontinue o caminho antigo somente quando a telemetria confirmar que o novo cobre todos os casos.
Idempotência
  • Em um sistema distribuído, mensagens chegam mais de uma vez. Projete para isso.
  • Uma chave de idempotência transforma uma duplicata em no-op.
  • Ordenação estrita é cara. Questione se você realmente precisa dela.
  • Entrega at-least-once com deduplicação alcança o mesmo resultado que exactly-once com custo menor.
Estratégias de caching
  • Cache-aside, write-through e write-behind diferem na forma como lidam com desatualização.
  • Invalidação de cache é o problema difícil: quais entradas ficam incorretas quando os dados mudam?
  • Um cache retornando dados desatualizados com confiança pode ser pior do que nenhum cache.
  • Defina TTLs deliberadamente, não como padrões.
Consistência vs disponibilidade
  • Em uma partição de rede, você não pode ter consistência total e disponibilidade total ao mesmo tempo.
  • A maioria da lógica de negócio tolera consistência eventual se o resultado visível ao usuário for mantido.
  • Consistência forte é cara. Quantifique a necessidade antes de exigi-la.
  • Escolha com base em como a falha parece para o usuário, não no que parece mais seguro no código.

O ponto não é novidade. É a recuperação: alcançar o padrão certo no instante em que ele se aplica, em vez de projetar além dele porque você esqueceu que existia.

Aplicado à Acme.ai, essa decisão se baseou em:

Decomposição de fronteiras de serviço
  • Geração movida do caminho de requisição web para um serviço de worker dedicado.
  • O worker é dono do consumidor de fila, lógica de query e streaming S3.
  • O servidor web agora apenas enfileira e retorna imediatamente.
Idempotência e ordenação de eventos
  • Envios agendados usam chaves de idempotência com chave em (schedule_id, period).
  • Entrega at-least-once assumida; duplicatas absorvidas pelo consumidor.
  • Evita envios duplos quando o scheduler dispara duas vezes ou um worker faz retry.
Caching e desatualização
  • Resultados de relatórios podem ser cacheados com TTL vinculado à atualidade dos dados.
  • Um cache hit evita re-executar a query para os mesmos parâmetros.
  • O TTL deve refletir quão desatualizados os dados exportados podem aceitavelmente estar.
Migração strangler-fig
  • Endpoint síncrono antigo permanece ativo para exportações legadas e pequenas.
  • Caminho assíncrono introduzido atrás de uma feature flag, roteado pelo tamanho do tenant.
  • Descontinuação somente quando a telemetria confirmar que o novo caminho cobre todos os casos.
Isolamento multi-tenant
  • Artefatos S3 com escopo por tenant com URLs assinadas de TTL curto.
  • Chaves com escopo por tenant garantem que o tenant A não consiga buscar a exportação do tenant B.
  • Controle de acesso aplicado na camada de armazenamento, não na camada de aplicação.
Disponibilidade acima de consistência estrita
  • Entrega at-least-once com chaves de idempotência escolhida em vez de exactly-once.
  • Exactly-once entre uma fila e um provedor de e-mail é caro e frágil.
  • Mesmo resultado visível ao usuário alcançado com complexidade significativamente menor.

Mais adiante, no hábito de captura, esses mesmos padrões são codificados em harnesses agênticos: arquivos CLAUDE.md, ADRs e agent skills que consolidam as decisões já tomadas para que um agente não as re-derive ou contradiga entre sessões. Mas isso vem depois. Este hábito é a recuperação na sua própria cabeça que permite julgar o que o agente produz em primeiro lugar. Se você não consegue nomear o padrão, nenhum arquivo de contexto vai dizer a você que o agente escolheu o errado.

Defenda cada decisão antes que alguém pergunte

Isso não é apenas sobre a recomendação final. Aplique a cada escolha no template: as restrições que você assumiu, as opções que você admitiu, a própria recomendação e as mitigações de risco. Para cada uma, responda “por que não a alternativa óbvia?” em voz alta. Se não conseguir, você encontrou uma lacuna. Ou mude a decisão, ou escreva a justificativa. Uma decisão que você não consegue defender é uma que você ainda não entende completamente. Sistemas reais vão interrogar você eventualmente, através de um incidente, uma revisão, ou um sucessor perguntando por que foi construído dessa forma. É mais barato enfrentar a pergunta enquanto ainda é possível mudar a resposta.

Aplicado à Acme.ai:

  • Por que não C (serverless)? Limites de runtime e tamanho no caso de 1M de linhas, além de uma nova superfície operacional com pouco ganho no volume atual. Vale revisitar com 10x de volume ou se o time quisesse zero gerenciamento de workers.
  • Por que não apenas a Opção A conservadora? Ela compra tempo, mas não habilita relatórios agendados e ainda acopla a geração à infraestrutura ao vivo. É uma correção de seis meses, não uma resposta de dois anos. Dito isso, a read replica da Opção A é publicada imediatamente como medida paliativa.
  • Por que at-least-once com deduplicação em vez de exactly-once? Exactly-once entre uma fila e um provedor de e-mail é caro e frágil; chaves de idempotência entregam a mesma garantia visível ao usuário com muito menos custo.

Capture a decisão para que se acumule

Uma decisão que você consegue defender tem pouco valor se ela evapora no momento em que você segue em frente. Uma escolha que vive apenas na sua cabeça, ou em um thread enterrado, precisa ser redescoberta pela próxima pessoa e re-derivada pelo próximo agente. O objetivo é colocá-la onde ela viaja: em artefatos que são versionados, descobríveis e carregáveis por humanos e agentes.

Três tipos de artefatos carregam uma decisão adiante.

Architecture Decision Records (ADRs) capturam o raciocínio por trás de uma escolha, não apenas o veredicto. Um ADR registra o contexto, as opções consideradas, por que a opção escolhida venceu, e as condições sob as quais a decisão deve ser revisitada. Ele responde “por que isso foi construído dessa forma?” no momento em que o raciocínio está mais fresco, para que ninguém precise reconstruí-lo a partir do git blame seis meses depois. A parte mais valiosa é o que foi rejeitado e por quê: evita que engenheiros e agentes re-proponham alternativas que já foram avaliadas.

Uma estrutura mínima de ADR:

  • Título: uma breve descrição no presente do que foi decidido (ex.: “Usar fila de jobs assíncrona para geração de relatórios”)
  • Status: aceito / proposto / obsoleto / substituído pelo ADR-NNN
  • Contexto: o problema, as restrições e as forças em jogo
  • Decisão: o que foi escolhido e o raciocínio central
  • Consequências: o que fica mais fácil, o que fica mais difícil, e sob quais condições isso deve ser revisitado

Fitness functions transformam restrições arquiteturais em verificações automatizadas que falham ruidosamente quando violadas. Onde um teste unitário pergunta “essa função retorna o valor certo?”, uma fitness function pergunta “esse sistema ainda se comporta da forma que decidimos que deveria?” Elas tornam as restrições do ADR aplicáveis em vez de apenas orientativas. Sem elas, o ADR é documentação. Com elas, é arquitetura. Exemplos concretos para a Acme.ai:

  • um teste de CI que falha se qualquer exportação interativa levar mais de 5 minutos,
  • uma regra de linter que rejeita qualquer novo caminho de geração de arquivo síncrona no servidor web,
  • um monitor que alerta quando a profundidade da fila ultrapassa o limite que implica violação de SLA,
  • uma verificação de conformidade que confirma que os artefatos S3 têm a política de lifecycle com TTL correto.

Arquivos de contexto de agente (CLAUDE.md) dizem ao Claude Code como uma parte específica do codebase funciona e quais decisões já foram tomadas. O Claude Code carrega arquivos CLAUDE.md automaticamente no nível raiz e dentro de cada diretório de subsistema, delimitando o contexto onde o agente está trabalhando. Um CLAUDE.md útil codifica:

  • as decisões arquiteturais que governam esse subsistema, com links para os ADRs relevantes,
  • as restrições que o agente jamais deve violar (ex.: “nunca bufferize mais de 10MB antes de fazer stream para S3”),
  • os contratos de idempotência, convenções de nomenclatura e suposições de deploy,
  • uma seção explícita de “não faça” registrando as alternativas que o time já rejeitou.

Isso é o que impede um agente de propor exatamente o design que foi debatido e descartado três sprints atrás.

Organizando esses artefatos em um projeto Claude Code:

acme-ai/
├── CLAUDE.md                           # contexto de agente para todo o projeto
├── docs/
│   └── decisions/
│       ├── ADR-001-async-export-queue.md
│       ├── ADR-002-s3-artifact-storage.md
│       └── ADR-003-strangler-fig-rollout.md
├── src/
│   ├── api/
│   │   └── CLAUDE.md                   # api: apenas enfileirar, retornar 202, sem geração
│   └── workers/
│       ├── CLAUDE.md                   # worker: contrato de idempotência, regras de streaming, limites de tamanho
│       └── export/
└── tests/
    └── fitness/
        ├── test_export_time_budget.py  # falha se qualquer exportação exceder 5 min
        ├── test_queue_depth_alert.py   # valida thresholds de alertas
        └── test_artifact_ttl.py        # confirma que a política de lifecycle S3 está em vigor

O CLAUDE.md raiz carrega as decisões de todo o projeto: a arquitetura assíncrona, quais serviços são responsáveis por quais responsabilidades, e o que o time explicitamente descartou. Cada CLAUDE.md de subsistema vai mais fundo: o arquivo de workers/ diz ao agente para fazer stream para S3 em chunks, usar (schedule_id, period) como chaves de idempotência, e nunca adicionar geração síncrona de volta ao caminho da requisição. Os testes de fitness rodam no CI e tornam as restrições do ADR mensuráveis.

Aplicado à Acme.ai, a decisão deixa para trás:

  • docs/decisions/ADR-001-async-export-queue.md: registra por que a Opção B venceu, o que a Opção C precisaria para se tornar a resposta certa, e por que a Opção A foi rejeitada como solução de longo prazo.
  • tests/fitness/test_export_time_budget.py: falha no CI se qualquer exportação exceder 5 minutos.
  • tests/fitness/test_artifact_ttl.py: confirma que a política de lifecycle S3 expira artefatos após 7 dias.
  • src/workers/CLAUDE.md: diz ao agente para fazer stream em chunks, usar (schedule_id, period) como chaves de idempotência, e nunca rotear geração de volta pelo servidor web.

Resolva uma vez, e você resolveu uma vez. Capture, e você elevou o piso para todos que vêm depois.


O trabalho que permanece

Tire o ferramental e os artigos de tendências e a mudança é simples. O que a IA faz bem é produzir artefatos plausíveis. O que permanece é tudo ao redor deles: decidir o que construir, defender o porquê, e tornar a decisão durável o suficiente para que a próxima pessoa e o próximo agente a herdem em vez de reconstruí-la.

Nenhum dos cinco hábitos é novo. Engenheiros sempre estruturaram decisões, pesaram trade-offs, se apoiaram em fundamentos, defenderam suas escolhas e as escreveram. O que mudou é a alavancagem. Quando o artefato leva segundos para ser gerado, o julgamento ao redor dele deixa de ser uma entrada entre muitas e se torna a entrada. O engenheiro antes valorizado por quanto conseguia construir é agora valorizado por quão bem consegue decidir, e por quão confiavelmente essas decisões se sustentam entre um time e entre sessões.

Esse é um papel mais exigente, não menor. Ele recompensa exatamente as coisas que sempre foram difíceis e nunca automatizáveis: raciocínio claro, trade-offs honestos e a disposição de assumir um resultado que um agente ajudou a produzir. Construa esses hábitos, e construa os trilhos que permitem que agentes trabalhem dentro deles, e a velocidade que a IA traz se torna sua para direcionar em vez de algo para perseguir.