A base de código é o harness

Antes escrevi sobre o que quero dizer com engenharia de harnesses: o harness do modelo ao redor do LLM e o harness do projeto que você realmente controla. Este é o complemento prático. Então, o que precisa estar na base de código para o agente não perguntar as mesmas coisas toda vez ou, pior, inventar respostas?

Sem harness? As mesmas perguntas em toda sessão. O que ler, o que prova que terminou, como preparar o ambiente, onde ficam os testes, quais comandos são seguros. O agente esquece. A base de código lembra.

Minha visão é simples: a base de código é o harness. Não um prompt. Não Cursor ou Codex. É a camada do próprio projeto que transforma intenção, preparação e verificações em algo que o agente consegue conferir.

O modelo prevê. A base de código faz cumprir.

  • O harness do modelo é o produto: sessões, ferramentas, permissões, MCP. Você configura. Não é seu.
  • O harness do projeto é o que vive na base de código: documentação, comandos, testes, scripts, integração com CI e hábitos que transformam erros repetidos em mecanismos de proteção.

O modelo prevê o próximo token plausível. A base de código determina o que conta como concluído. Mesmo prompt, outra execução, outra resposta. As duas podem parecer certas. Por isso você precisa de verificações que o agente consiga executar sem perguntar.

Prompts são sugestões. Linters são lei.

O harness mínimo que vale a pena

Você pode ampliar isso com o tempo. Estas são as peças que eu não deixaria de fora.

Um mapa, não um livro. O AGENTS.md da raiz deve responder: o que é este projeto, quais são as regras inegociáveis, onde está a intenção do produto, onde está a arquitetura, qual verificação executar para cada tipo de mudança e o que fazer quando ela falha. Referências a arquivos reais. Não um despejo de instruções de 50 páginas.

Intenção do projeto. Um documento focado que diga o que é o produto, para quem é, o que prioriza, o que explicitamente não prioriza e quais suposições são perigosas. Sem isso, o agente basicamente deduz a direção do produto a partir do código ao redor. Aceitável para a cor de um botão. Inaceitável para decidir a quem pertencem os dados, cobrança, privacidade ou o que se pode quebrar na API.

Um comando que signifique concluído. make verify, pnpm verify, npm run validate, o que combinar com seu stack. Documentado no mapa. Amplo o suficiente para detectar regressões entre componentes. A verificação local antes da entrega deve ser pelo menos tão rigorosa quanto o CI no trabalho normal.

Verificações delimitadas para iterar. Mexeu só no pacote da API? Execute a verificação da API. Migração de banco? Execute a do banco. A verificação final define a entrega; as delimitadas mantêm o ciclo rápido.

Verificar e preparar o ambiente local. Uma verificação somente de leitura que diga o que falta. Uma preparação explícita que crie estado local seguro. Segredos reais nunca são inventados. Valores locais não vazios não são sobrescritos. A preparação deve ser um comando, não uma conversa no Slack.

Guia de camadas de testes. Diga ao agente onde a prova deve estar: testes unitários ao lado do código, testes de persistência quando o banco for o foco, smoke tests só quando precisar de transporte real, E2E completo por último. Use por padrão a camada mais barata que comprove a intenção.

Inventário de scripts. Scripts fazem parte da API pública, quer você tenha planejado isso ou não. Documente onde ficam, se apenas leem ou modificam, quem é responsável e qual alvo Make/npm os expõe aos agentes. Nome ruim: setup.sh. Melhor: prepare-local-env, para ninguém destruir produção por acidente.

O CI chama comandos da base de código. GitHub Actions, ou o que você usar, deve ser simples: checkout, instalação e execução do alvo de verificação do repositório. Não duplique a lógica de “mexeram na API?” em três arquivos de workflow. Mantenha isso na base de código para sincronizar o ambiente local e o CI.

Hooks baratos. Formatar e rodar lint no commit é bom. Hooks são higiene, não a definição de concluído. Não pendure a suíte inteira de testes ou as verificações de banco no pre-commit.

PeçaFunção
AGENTS.mdEncaminhar o agente para os documentos e comandos certos
Intenção do projetoLimites do produto antes de escrever código
Verificação finalUma resposta para “terminamos?”
Verificações delimitadasIteração rápida sem pular a entrega
Verificação / preparação do ambientePreparação autônoma
Camadas de testesA prova certa pelo custo certo
Inventário de scriptsSem comandos que alterem coisas de surpresa
CIA mesma lógica do repositório usada localmente

Não despeje tudo isso no agente em toda sessão. Torne fácil de encontrar: um mapa curto no início e documentos focados que ele abra quando a tarefa realmente precisar. O agente deve conseguir encontrar a peça certa sem você colar metade do repositório no chat.

Caminho de adoção

Comece pequeno. Acrescente o resto conforme a dor aparecer. Você não precisa de tudo no primeiro dia.

Mapeie a base de código. Um arquivo curto para agentes na raiz, intenção do projeto, referências de arquitetura e documentos específicos onde algum domínio precise de tratamento especial. Torne a base de código navegável antes de escrever uma enciclopédia.

Defina concluído. Uma verificação final. Executável localmente. Documentada. Determinística o suficiente para que as falhas indiquem o que fazer. O CI invoca a mesma lógica ou um equivalente claramente documentado.

Adicione verificações delimitadas. Acompanhe como a base de código realmente muda: só frontend, só backend, um pacote, contratos, documentação. Iteração rápida; a verificação final continua responsável pela entrega.

Torne o CI reproduzível localmente. Filtros por caminho em um manifesto ou script com testes. Workflows chamam comandos do repositório. Sem seleção de alvos duplicada entre arquivos YAML.

Torne a preparação explícita. Verifique o que falta. Prepare o que for seguro. Documente o que a verificação pode redefinir localmente e por quê.

Documente as camadas de testes e o inventário de scripts. Assim o agente não escolhe sempre o teste mais lento ou um script de shell aleatório.

Transforme revisões repetidas em proteções. Se você ou um revisor dizem a mesma coisa duas vezes, é um sinal: documente, teste, crie uma regra de lint ou um script. Não continue explicando em cada PR.

Modelos que você pode aproveitar

Estruturas independentes do stack. Troque Make por npm, Vitest por go test ou pelo que você usa.

Mapa da raiz (AGENTS.md):

# Project name

Short description. Read `docs/project-intent.md` before product decisions.

## Non-negotiables

- Invariant one
- Invariant two

## Where to read

- Intent: `docs/project-intent.md`
- Architecture: `docs/architecture.md`
- Testing: `docs/testing.md`

## Checks

- Area changed only: `make check-<area>`
- Normal handoff: `make verify`

## When checks fail

Run `make fix` if available, then re-run the scoped gate or `make verify`.

Intenção do projeto (docs/project-intent.md):

# Project intent

## What this is

One paragraph. Present tense. Real behavior today.

## What this is not

Explicit non-goals.

## Optimizes for

## Does not optimize for

## Unsafe assumptions

Things the agent must not infer from nearby code.

## Hard invariants

Violations are bugs, not style preferences.

Matriz de comandos (na documentação do harness ou de contribuição):

ComandoQuandoAltera?
make check-local-envAntes do trabalho pesadoNão
make prepare-local-envFaltam arquivos locaisSim (apenas local)
make check-apiCaminhos da API mudaramNão
make verifyEntrega que muda comportamentoTalvez (documentado)
make fixApós falhas de formato/lintSim

Verificação final (Makefile ou script do pacote):

.PHONY: verify
verify: check fix-if-needed db-check-if-needed
	@echo "verify: ok"

Linha do inventário de scripts:

CaminhoDomínioPúblicoModoNotas
scripts/seed-local.shdevmake prepare-local-envalteraApenas local; nunca CI

Ambiente local:

check-local-env:
	@test -f .env.local || (echo "run: make prepare-local-env"; exit 1)

prepare-local-env:
	@test -f .env.local || cp .env.example .env.local

Workflow mínimo de CI:

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - run: make verify

O ponto não é a sintaxe. É fazer cada camada apontar para os mesmos contratos.

Não é só teoria

Não escrevo sobre isso apenas de forma abstrata. Eu construo isso na maioria dos meus projetos e do meu trabalho. Um repositório que mantenho por fora é o harness: uma CLI, workflows invocáveis, skills empacotadas e o executor que conecta tudo. O repositório do seu produto ainda precisa do próprio mapa, das verificações e da preparação. Harness é o que uso para revisar mudanças, encaminhar trabalho de agentes e manter os ciclos explícitos.

Se os modelos acima ajudam a projetar, esse repositório ajuda a ver exemplos funcionando: integração de workflows, estrutura de skills e documentação para colaboradores. Continua evoluindo. Copie o que servir. Mais contexto: /projects/harness.

O que o harness não faz

Não substitui julgamento. As verificações detectam desvios e erros mecânicos. Não escolhem sua estratégia de produto.

Verificações desatualizadas mentem. Se verify não corresponde ao que o CI executa, ou o guia de testes diz uma coisa e o código faz outra, o agente aprende a lição errada.

Agentes pulam etapas se você permitir. Verificações delimitadas não executam a suíte inteira. Versões erradas de ferramentas ainda estragam sua tarde. Verificações de banco precisam do Docker rodando. O harness reduz a adivinhação. Não substitui seu bom senso.

Chat não é memória. A base de código é.

Essa é toda a aposta. Coloque intenção, preparação e prova onde o agente, e a próxima pessoa, possam executá-las sem depender de você. Construa o harness junto com o produto. Toda vez que você explica a mesma coisa duas vezes, provavelmente falta uma proteção na base de código.