Esqueça a DX, foque na AX
Como projetar a experiência do agente: contexto, ferramentas, guardrails e CI para que a IA escreva o código com qualidade
DX significa Developer Experience: o quanto é agradável e produtivo trabalhar em uma codebase. AX significa Agent Experience: o quanto é fácil para um agente entender a codebase, agir sobre ela e saber se acertou.
Hoje não escrevo mais código. Escrevo workflows, skills, plugins e ferramentas para o meu harness (Claude Code e pi.dev) validar o código. O agente escreve e o workflow decide se está correto. Este post reúne as peças desse workflow, uma por seção, com trechos que você pode adaptar ao seu projeto.
Você pode perguntar: por que investir em AX se um modelo melhor resolve o problema? Porque o modelo é só uma variável. Um agente com contexto ruim, sem ferramentas de busca e sem testes erra com qualquer modelo, só que em ritmo diferente.
Contexto é o novo onboarding
Um desenvolvedor novo lê o README, pergunta ao time e aprende a linguagem do domínio. O agente não tem nada disso, e a cada sessão começa do zero. O arquivo de instruções do projeto faz o papel do onboarding.
Esse arquivo se chama CLAUDE.md ou AGENTS.md, dependendo do harness. O nome importa menos que o conteúdo. Um arquivo útil responde a quatro perguntas:
# AGENTS.md
## Comandos
- `npm run check`: lint, typecheck e testes. Rode antes de abrir um PR.
- `npm run test -- <arquivo>`: um arquivo de teste.
## Estrutura
- `src/domain`: regras de negócio, sem dependência de React.
- `src/ui`: componentes. Não acessam a API diretamente.
## Linguagem do domínio
- "Pedido" (`Order`) é o carrinho depois do pagamento. Antes disso é `Cart`.
## Regras
- Não adicione dependências sem perguntar.
- Todo comportamento novo precisa de um cenário em `tests/features`.
Duas regras mantêm esse arquivo útil. Primeira: cada linha deve evitar um erro real que o agente já cometeu. Uma linha que não evita erro custa tokens e atenção sem dar retorno. Segunda: mantenha o arquivo curto e aponte para documentos maiores quando precisar, porque contexto carregado sem necessidade atrapalha a decisão.
Dê ferramentas de busca melhores ao agente
A maior parte do trabalho de um agente é encontrar o código certo. Um grep ingênuo devolve centenas de linhas, e o agente gasta o contexto lendo ruído. Estas ferramentas reduzem esse custo:
- ripgrep (
rg) para busca textual rápida, que respeita o.gitignore. - fzf para filtrar listas grandes de arquivos por nome.
- ast-grep para buscar por estrutura de código, não por texto.
A diferença aparece nas buscas estruturais. Achar todas as chamadas de fetch sem tratamento de erro é difícil com texto e simples com a árvore sintática:
ast-grep run --lang ts --pattern 'await fetch($$$ARGS)' src
Instalar a ferramenta não basta, o agente precisa saber quando usá-la. Uma instrução curta no arquivo de contexto resolve:
## Busca
- Nome de símbolo ou texto: `rg`.
- Padrão de código (chamadas, imports, JSX): `ast-grep run --lang tsx --pattern '...'`.
- Nunca leia um arquivo inteiro para encontrar uma função. Busque primeiro.
Regras determinísticas e não determinísticas
Existem dois tipos de regra, e cada um tem um papel.
Regras determinísticas são verificadas por uma ferramenta. O resultado é sempre o mesmo e o agente não consegue ignorá-las. Um exemplo com oxlint:
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "react"],
"rules": {
"no-console": "error",
"typescript/no-explicit-any": "error",
"react/exhaustive-deps": "error"
}
}
Regras não determinísticas são instruções em linguagem natural, como as do CLAUDE.md ou do .cursorrules. O agente as interpreta, então elas podem falhar. "Prefira composição a herança" e "nomes de funções descrevem a intenção" não têm verificador.
Meu critério é simples. Se uma ferramenta consegue validar a regra, ela vai para o linter ou para o type checker. A linguagem natural fica com o que só o julgamento avalia. Cada regra que sai do texto e vai para uma ferramenta deixa de depender da interpretação do agente.
Skills, plugins e hooks
Quando o mesmo procedimento se repete, ele vira uma skill. Uma skill é uma pasta com um SKILL.md. O frontmatter diz quando ela se aplica e o corpo diz como executar.
---
name: new-feature
description: Cria uma feature nova seguindo o fluxo do projeto. Use quando o usuário pedir uma funcionalidade nova.
---
1. Escreva o cenário em `tests/features/<nome>.feature`.
2. Rode o cenário e confirme que ele falha pelo motivo certo.
3. Implemente o mínimo para passar.
4. Rode `npm run check`.
Skills dependem de o agente lembrar delas. Hooks não dependem, porque o harness os executa em eventos definidos. No Claude Code, este hook roda o oxlint depois de cada edição de arquivo:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx oxlint >&2 || exit 2"
}
]
}
]
}
}
O código de saída 2 é o detalhe que importa. Ele devolve a mensagem de erro ao agente, que corrige o problema na mesma sessão. O erro chega quando o código acabou de ser escrito, e não no CI vinte minutos depois.
Plugins empacotam skills, hooks e comandos para o time inteiro usar o mesmo workflow.
Testes como guardrail e prova de futuro
Testes têm duas funções. A primeira é verificar que o código funciona. A segunda, mais importante para AX, é permitir a mudança. Com uma suíte confiável, o agente altera várias partes da aplicação e sabe na hora se quebrou algo. Sem a suíte, cada mudança é um risco que alguém precisa revisar linha por linha.
Eu escrevo os casos de uso em Gherkin. O formato funciona como uma DSL entre a intenção humana e o código: pessoas leem sem esforço e o agente tem precisão suficiente para implementar.
# language: pt
Funcionalidade: Cupom de desconto
Cenário: Cupom válido reduz o total
Dado um carrinho com total de R$ 100,00
Quando o cliente aplica o cupom "PRIMEIRA10"
Então o total do carrinho é R$ 90,00
Cenário: Cupom expirado é recusado
Dado um carrinho com total de R$ 100,00
Quando o cliente aplica o cupom "NATAL2020"
Então o cliente vê a mensagem "Cupom expirado"
E o total do carrinho continua R$ 100,00
Cada cenário vira um teste. Para a regra de negócio, uso Vitest:
import { describe, expect, it } from "vitest";
import { applyCoupon } from "./cart";
describe("cupom de desconto", () => {
it("cupom válido reduz o total", () => {
const cart = { total: 100 };
expect(applyCoupon(cart, "PRIMEIRA10").total).toBe(90);
});
});
Para o fluxo completo no navegador, uso Playwright:
import { expect, test } from "@playwright/test";
test("cupom expirado é recusado", async ({ page }) => {
await page.goto("/cart");
await page.getByLabel("Cupom").fill("NATAL2020");
await page.getByRole("button", { name: "Aplicar" }).click();
await expect(page.getByText("Cupom expirado")).toBeVisible();
});
Uma regra prática: o agente escreve o cenário primeiro, vê o teste falhar e só depois implementa. Assim não sobram testes que passam sem verificar nada.
Valide o comportamento em vídeo
Teste automatizado responde "passou ou falhou". Em fluxos de interface eu também quero ver o que aconteceu, então uso vídeo como evidência do trabalho do agente.
O testing-agent executa casos de uso escritos em linguagem natural em um navegador real. Cada passo é uma instrução, sem seletores, e as asserções definem o resultado final, não o modelo:
name: Cupom expirado
url: http://localhost:3000/cart
prompt: Aplicar um cupom expirado e verificar a recusa.
steps:
- instruction: Digite NATAL2020 no campo Cupom
value: NATAL2020
- instruction: Clique em Aplicar
expect:
text: Cupom expirado
A execução gera um vídeo. Em vez de ler um diff para saber se o fluxo funciona, eu assisto trinta segundos de gravação. A revisão fica sobre o comportamento, que é o que importa para quem usa o produto.
CI rápido o suficiente para o agente
Um agente só valida o próprio trabalho se a verificação for rápida. Com um CI de vinte minutos, ele abre o PR sem ter certeza e o feedback chega tarde. A pergunta para o seu pipeline é: o agente consegue rodar tudo isso antes de publicar o PR?
O primeiro passo é um único comando local que reproduz o CI:
{
"scripts": {
"check": "oxlint && tsc --noEmit && vitest run",
"check:changed": "oxlint && tsc --noEmit && vitest run --changed origin/main"
}
}
O check:changed roda só os testes afetados pela mudança, e é o que o agente usa durante o trabalho. O CI executa o check completo:
name: ci
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 26
cache: npm
- run: npm ci
- run: npm run check
Para manter o pipeline rápido, faça cache das dependências, rode só os testes afetados no ciclo local, execute lint, tipos e testes em paralelo e trate o limite de tempo como requisito. Um teste instável é pior que um teste ausente, porque o agente aprende a ignorar falhas.
Revise o workflow, não o código
Em alguns casos, uma pessoa já não revisa o código. Mesmo assim, alguém responde pelo resultado. A responsabilidade muda de lugar: o humano passa a revisar o workflow e a validar o comportamento.
O que eu reviso:
- Regras: o linter e o arquivo de contexto cobrem os erros que já aconteceram?
- Testes: os cenários descrevem o comportamento esperado, incluindo os casos de borda?
- Evidência: o vídeo e o resultado do CI mostram que o fluxo funciona?
- Skills e hooks: o procedimento que o agente segue está correto e completo?
Quando um bug passa, a correção tem duas partes. Primeiro corrija o código. Depois pergunte por que o workflow deixou o bug passar. A resposta vira uma regra no linter, um cenário novo ou uma linha no contexto. Assim o mesmo erro não volta e o workflow melhora a cada falha.
Times de agentes
O próximo passo da AX é fazer vários agentes trabalharem juntos. É o que estou construindo no ZettaBot, um projeto open source que roda no seu próprio servidor. Você descreve um resultado e um time de bots faz o trabalho.
Cada bot é um trabalhador configurado, como um Writer ou um Reviewer. Ele roda sobre um agente que você já usa (Claude Code, Codex, Cursor, Pi, OpenCode), recebe skills, plugins e servidores MCP e tem a sua própria forma de trabalhar. Um bot principal, o Zetta, cria os outros bots, distribui as tarefas e mantém as rotinas. As tarefas passam por backlog, em andamento, bloqueada, revisão e concluída, e cada card mostra o bot responsável. Ações sensíveis, como apagar dados ou enviar e-mail, exigem a sua aprovação.
A estrutura repete o que o post descreveu para um único agente: contexto, ferramentas e guardrails. A diferença é que agora existem papéis, e o objetivo do time é entregar com qualidade e velocidade.
Conclusão
AX é dar ao agente contexto, ferramentas e uma forma rápida de saber se acertou. Quanto melhor o workflow, menos o código precisa ser revisado linha por linha. Comece por um passo: pegue o último erro do seu agente e transforme-o em uma regra ou em um teste.
Obrigado pela leitura.