Agentes no CI
O time atualiza o Spring Boot em toda a BoxOffice numa segunda-feira de manhã. Catorze serviços, catorze builds vermelhos: uma propriedade de configuração renomeada aqui, um método depreciado ali, um teste que dependia de um padrão antigo. Cada correção é pequena e tediosa; juntas, tomam dois dias de dois engenheiros. Um agente — um modelo que planeja, usa ferramentas, observa o resultado e tenta de novo — pode fazer a maior parte desse trabalho enquanto os engenheiros revisam. Se for mal projetado, ele também pode fazer push na main, vazar a chave de deploy, ficar uma hora em loop num teste flaky ou "consertar" um build vermelho apagando a asserção. Esta página trata do projeto que obtém o primeiro resultado e torna o segundo impossível.
O que os agentes fazem em um pipeline
O loop do agente — o modelo escolhe uma ferramenta, o harness a executa, o resultado volta para o contexto, repete — vira, no CI, um loop sobre o repositório e o build:
| Workflow agêntico | Gatilho | Verificável por | Raio de impacto típico | Autonomia sensata |
|---|---|---|---|---|
| Consertar o build que falhou | Execução de CI falhou | O build e os testes passam; o diff é pequeno | Uma branch | Supervisionada: PR em rascunho |
| Upgrade de dependência | PR do Renovate/Dependabot está vermelho | Build, testes, canary | Muitos serviços | Supervisionada; merge automático só de versões de patch com CI verde |
Migração (JUnit 4 → 5, javax → jakarta, Java 17 → 21) | Campanha planejada | Build, testes, mais um codemod para a parte mecânica | A base de código inteira, em lotes | Supervisionada, um PR por serviço |
| Da issue ao PR | Issue rotulada ou @mention | Testes escritos com os critérios de aceitação da issue | Uma funcionalidade | Supervisionada; revisão humana como de costume |
| Sincronizar documentação e configuração | API ou schema mudou | Lint, verificação de links, diff do OpenAPI | Documentação | Pode ser autônoma para mudanças só de documentação |
Todas as linhas terminam no mesmo lugar: uma proposta (uma branch, um PR em rascunho) que verificações determinísticas e um humano aprovam. Esse é o nível 3 do espectro de autonomia. O nível 4 — aplicar mudanças sem aprovação a cada mudança — fica reservado para mudanças que são ao mesmo tempo de baixo risco e totalmente verificáveis, e raramente precisa de um modelo.
Anatomia de um agente que conserta o build
sequenceDiagram
autonumber
participant CI as Execução de CI
participant Orch as Workflow (determinístico)
participant Agent as Loop do agente (sandbox)
participant Model as Modelo
participant Repo as Host Git
CI-->>Orch: execução falhou (evento)
Orch->>Orch: dedup pelo SHA do commit, verifica que a branch não é ai-fix/*
Orch->>Agent: log da falha + checkout (sem token de escrita)
loop no máximo N turnos, M dólares
Agent->>Model: contexto + resultados das ferramentas
Model-->>Agent: chamada de ferramenta (ler / editar / rodar build)
Agent->>Agent: executa ferramenta permitida, registra
end
Agent-->>Orch: diff da árvore de trabalho + transcrição
Orch->>Orch: verifica: caminhos permitidos, nenhum teste removido, build verde
Orch->>Repo: job separado com token de escrita: branch + PR em rascunho
Repo-->>Orch: humano revisa, CI roda, merge ou fechamento Leia como dois programas. O agente é não determinístico e não confiável: pode estar errado, confuso ou ser manipulado por texto no log. O orquestrador em volta dele — gatilhos, dedup, verificação, o job do PR — é código determinístico comum, revisado como qualquer outro. Toda a segurança mora no orquestrador.
Rodando agentes headless
Agentes no CI rodam headless: sem terminal, sem um humano para responder "posso rodar este comando?". Toda decisão que uma pessoa tomaria interativamente precisa ser tomada de antemão, como configuração.
claude -p "<task>" roda o Claude Code de forma não interativa. Flags relevantes da sua referência de CLI:
gh run view "$RUN_ID" --log-failed | tail -n 300 | \
claude --bare -p "The CI build failed; the log is on stdin. Fix the root cause with the smallest change. Never delete or weaken a test." \
--allowedTools "Read,Edit,Write,Bash(mvn -B *)" \
--permission-mode dontAsk \
--max-turns 25 --max-budget-usd 1.50 \
--output-format stream-json --verbose > transcript.jsonl
--bare pula a descoberta automática de hooks, plugins, servidores MCP e CLAUDE.md (o mesmo comportamento em toda máquina, e nada carregado do repositório que você não tenha escolhido); --allowedTools usa a sintaxe de regras de permissão (Bash(mvn -B *) permite apenas comandos que começam com mvn -B); dontAsk nega todo o resto; --max-turns e --max-budget-usd limitam o loop; stream-json produz uma transcrição para o log de auditoria, cuja última linha informa o custo.
anthropics/claude-code-action@v1 roda o mesmo motor dentro do GitHub Actions. Sem um prompt, ele responde a menções @claude em PRs e issues (por padrão, só de usuários com acesso de escrita); com um prompt, roda em modo de automação em qualquer evento. As flags da CLI vão em claude_args. A configuração interativa da documentação:
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
claude:
if: contains(github.event.comment.body, '@claude')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
Repare nas permissões: este job pode escrever código, PRs e issues — adequado para um assistente sob demanda que humanos invocam e que faz push de commits que um humano depois revisa. Para um agente sem supervisão que reage a falhas, prefira um job sem acesso de escrita que produza um patch, e um job separado que abra o PR (próxima seção). Consulte a documentação de segurança da action e os inputs atuais antes de usar.
Um agente é um loop que você mesmo pode escrever sobre qualquer API com tool calling — inclusive endpoints compatíveis com OpenAI via LiteLLM ou um modelo local com Ollama. O núcleo, sem tratamento de erros:
for step in range(MAX_STEPS):
reply = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS)
msg = reply.choices[0].message
messages.append(msg.model_dump(exclude_none=True))
if not msg.tool_calls:
break
for call in msg.tool_calls:
result = run_allow_listed(call.function.name, json.loads(call.function.arguments))
audit(step, call, result)
messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
Escrevê-lo você mesmo torna explícita a superfície de ferramentas: aqui o agente tem read_file, write_file (só em src/), run_build e finish — e nenhum shell. O hands-on 13 tem a versão completa, com orçamentos e log de auditoria.
Sandboxing
O agente executa o build, e o build executa o código do repositório: testes, plugins do Maven, scripts. Suponha que esse código — ou o texto que o agente lê — possa ser hostil, e contenha-o em camadas:
| Camada | Controle | Protege contra |
|---|---|---|
| Ambiente efêmero | Runner hospedado ou container novo a cada execução, destruído depois | Persistência entre execuções; caches envenenados (cuidado com caches de build compartilhados) |
| Lista de ferramentas permitidas | Ler/editar arquivos, rodar o build; nenhum shell genérico, nenhum curl, nenhum git push | O agente fazer qualquer coisa que você não previu |
| Escopo do sistema de arquivos | Escrita só em services/*/src/; .github/ e .git/ inacessíveis | Editar o CI para remover verificações; adulterar o histórico |
| Limpeza do ambiente | Build e testes rodam sem variáveis *KEY*, *TOKEN*, *SECRET* | Um teste malicioso lendo o ambiente |
| Saída de rede | Só o registry de pacotes e o endpoint do modelo (proxy ou firewall em runners self-hosted) | Exfiltração para hosts arbitrários |
| Limites de recursos | Timeout do job, limites de CPU/memória | Loops e builds descontrolados |
Nenhuma camada é perfeita. Uma lista de ferramentas permitidas com Bash(mvn *) ainda roda o que quer que os plugins Maven do projeto façam; a limpeza do ambiente não protege a chave da API do modelo mantida pelo próprio processo do agente. É por isso que a chave tem um limite de gasto, o job não tem token de escrita e a saída é verificada fora do agente.
Permissões: o token é o raio de impacto
O que quer que o modelo decida, ele nunca pode fazer mais do que suas credenciais permitem. Projete as credenciais primeiro:
GITHUB_TOKENcom privilégio mínimo: declarepermissions:em todo workflow; o job que roda o agente recebecontents: read(eactions: readpara buscar os logs).- Separe os jobs por privilégio: o job do agente produz um artefato de patch; um job pequeno com
contents: writeepull-requests: writeo aplica e abre um PR em rascunho. O job com acesso de escrita não roda modelo nem código não confiável. - Nenhum segredo no contexto do agente: nunca cole segredos em prompts ou resultados de ferramentas; mantenha chaves de deploy, credenciais de nuvem e tokens de registry totalmente fora do job.
- Somente leitura por padrão: um agente que só diagnostica (explica a falha em um comentário) não precisa de acesso de escrita — comece por aí.
- Proteção de branch e rulesets: a
mainexige PRs, CI e uma aprovação humana; bots não podem contornar; mudanças em.github/workflows/exigem um code owner. - A aprovação humana é estrutural, não uma instrução no prompt: o agente não consegue fazer merge porque seu token não consegue.
No código do BoxOffice: dois jobs, dois privilégios (lab 13)
O Hands-on 13 — O agente de correção divide o workflow exatamente assim. O job fix roda o modelo e o build não confiável com contents: read e actions: read (para buscar o log da falha), e só roda para execuções de CI que falharam em branches do próprio repositório, nunca nas suas próprias branches ai-fix/. O job open-pr tem contents: write e pull-requests: write, não roda modelo nem build, e só aplica o patch como um PR rascunho — depois que um passo independente rodou de novo o mvn verify e rejeitou @Test apagados e arquivos fora de services/<name>/src/.
jobs:
fix:
# Only failed runs, only branches of this repository, never our own fix branches.
if: >-
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
!startsWith(github.event.workflow_run.head_branch, 'ai-fix/') &&
!startsWith(github.event.workflow_run.head_branch, 'ai-tests/')
runs-on: ubuntu-latest
timeout-minutes: ${{ (vars.LLM_PROVIDER || 'ollama') == 'ollama' && 45 || 30 }}
permissions:
contents: read
actions: read # read the logs of the failed run
outputs:
fixed: ${{ steps.verify.outputs.fixed }}
open-pr: # write access, but no model and no untrusted build here
needs: fix
if: needs.fix.outputs.fixed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
Orçamentos: turnos, tokens, dólares, minutos
Cada turno do agente reenvia o contexto inteiro — instruções, definições de ferramentas, o log, cada arquivo lido, cada resultado de ferramenta até ali. O contexto cresce a cada passo, então o total de tokens de entrada cresce aproximadamente de forma quadrática com o número de passos (Sistemas de IA — os números). O prompt caching lê o prefixo repetido por uma fração do preço, o que muda drasticamente a inclinação. Premissas ilustrativas: contexto inicial de 20.000 tokens (instruções, definições de ferramentas, log), 2.500 tokens adicionados por passo (resultado da ferramenta mais a saída do modelo), 400 tokens de saída por passo, preços de tabela de um modelo grande na data em que este texto foi escrito (US$ 4 de entrada, US$ 20 de saída, US$ 0,20 de leitura em cache por milhão de tokens):
Sem cache, 40 turnos custam mais de oito vezes o que custam 10 turnos (cerca de US$ 11 contra US$ 1,3); com cache, a curva é quase linear e cerca de oito vezes mais barata aos 40 turnos. De um jeito ou de outro, o orçamento precisa ser imposto pelo harness, e não pedido no prompt:
| Orçamento | Valor típico para consertar o build | Imposto por |
|---|---|---|
| Turnos | 20–30 | --max-turns, ou o limite do loop |
| Dólares | US$ 1–2 por execução | --max-budget-usd, ou um contador de tokens no loop; limite de gasto do workspace no provedor |
| Tempo de relógio | 20–30 min | timeout-minutes no job |
| Execuções por commit | 1 | Dedup pelo SHA do commit |
| Execuções por dia por repositório | p. ex. 20 | Cota no LLM gateway |
A razão que importa é dólares por correção mergeada, e não por execução: um agente que custa US$ 0,80 por execução, mas produz uma correção mergeável uma vez a cada quatro, custa US$ 3,20 por correção útil — compare isso com o tempo de engenheiro que ele economiza.
No código do BoxOffice: os orçamentos e a área de escrita do agente de correção (lab 13)
O agente do hands-on 13 para em 20 passos, 400.000 tokens ou US$ 1,50, o que vier primeiro; pode ler arquivos do repositório, mas só escreve sob src/, nunca em .git/ ou .github/, e toda chamada de ferramenta vai para um log de auditoria. Uma execução com o qwen2.5-coder-16k local mostra por que o orçamento não é opcional: o modelo nunca abriu o CatalogService.java, reescreveu o teste seis vezes e parou depois dos seus 20 passos, 8½ minutos e 165.075 tokens de entrada com "fixed": false. Nada perigoso aconteceu — toda escrita ficou sob services/catalog/src/, e o mvn verify independente falhou, então nenhum PR foi aberto. As proteções tornam um agente ruim inofensivo, não bom.
ROOT = Path.cwd().resolve()
MAX_STEPS = int(os.environ.get("AGENT_MAX_STEPS", "20"))
MAX_TOKENS = int(os.environ.get("AGENT_MAX_TOKENS", "400000")) # input + output, whole run
MAX_USD = float(os.environ.get("AGENT_MAX_USD", "1.50"))
AUDIT = Path(os.environ.get("AGENT_AUDIT", "agent-audit.jsonl")) # keep it outside the repo
def safe(path: str, write: bool = False) -> Path:
p = (ROOT / path).resolve()
if not p.is_relative_to(ROOT) or {".git", ".github"} & set(p.relative_to(ROOT).parts):
raise PermissionError(f"{path}: outside the allowed area")
if write and "src" not in p.relative_to(ROOT).parts:
raise PermissionError(f"{path}: only files under services/<name>/src/ may be written")
return p
if done:
break
if usage.input_tokens + usage.output_tokens > MAX_TOKENS or usage.usd > MAX_USD:
result = {"fixed": False, "explanation": f"stopped by budget after {step} steps"}
break
Idempotência, retries e loops
Agentes são disparados por eventos, e eventos se repetem: uma execução de CI é reexecutada, dois pushes chegam no mesmo minuto, a própria branch do agente falha no CI. Os padrões de mensageria do curso se aplicam diretamente (Mensageria — semântica de entrega):
- Chave de idempotência: o SHA do commit que falhou. Uma proposta por SHA (branch
ai-fix/<sha8>, grupo deconcurrencyno SHA). Reexecuções encontram a branch e param. - Nada de autodisparo: nunca rode o agente nas próprias branches (
ai-fix/*), e lembre que pushes feitos com oGITHUB_TOKENnão disparam novas execuções de workflow no GitHub — um disjuntor de loops embutido. - Retries são para o transporte, não para a tarefa: os SDKs repetem
429/5xxcom backoff. Reexecutar um agente que falhou é uma nova tentativa com um novo custo — torne isso uma decisão humana, ou no máximo um retry automático com uma estratégia diferente. - Fail open para agentes consultivos: se o agente falhar, o build vermelho original continua lá; nada fica pior do que antes.
Auditoria: quem mudou o quê, e por quê
A mudança de um agente precisa ser explicável meses depois. Guarde:
| Registro | Onde |
|---|---|
| A transcrição completa (prompts, chamadas de ferramentas, resultados) | Artefato do CI com período de retenção |
| Id do modelo, versão do prompt, versão do harness | Na transcrição e no corpo do PR |
| Custo e turnos | Resumo do job; agregado no LLM gateway |
| Que o commit foi gerado por IA | Trailer do commit (AI-assisted: ai-fix (claude-opus-5-5)), rótulo ai-generated no PR |
| Quem aprovou | A revisão do PR — o humano responsável |
É a mesma disciplina de qualquer mudança em produção, e ela alimenta a proveniência da cadeia de suprimentos do software: você consegue responder "que código do inventory foi escrito por um agente, e aprovado por quem?".
Agentes de dependências e de migração
Upgrades de dependências são, na prática, o workflow agêntico mais comum. Bots como o Renovate ou o Dependabot abrem o PR de upgrade de forma determinística; o trabalho do agente começa quando esse PR fica vermelho: ler o changelog e a falha, adaptar o código, manter a mudança mínima. Agrupe atualizações relacionadas (todos os artefatos do Spring juntos), deixe as atualizações de patch com CI verde fazerem merge automático sem IA e mande as versões major para um agente que propõe e um humano que aprova.
Migrações — JUnit 4 → 5, javax.* → jakarta.*, Java 17 → 21 — misturam uma grande parte mecânica com uma cauda longa de casos especiais. Use codemods determinísticos para a parte mecânica (receitas do OpenRewrite são a ferramenta padrão para Java) e o agente para as sobras que a receita não resolve. Divida a campanha em um PR por serviço, pequeno o bastante para revisar, atrás do CI e do canary normais. As mudanças em larga escala no Google seguiam o mesmo princípio muito antes dos LLMs: automatizar a mudança, fragmentá-la em pedaços revisáveis de forma independente e deixar os responsáveis revisarem cada pedaço1.
BoxOffice: o upgrade do Spring Boot, de novo
O Renovate abre um PR agrupado por serviço. Seis ficam verdes e são mergeados após revisão. Para os oito vermelhos, o agente de correção roda uma vez por commit que falhou, com mvn como único comando de shell, um teto de US$ 1,50 e 25 turnos. Ele propõe seis correções como PRs em rascunho (propriedades renomeadas, um padrão alterado em um test slice); nos outros dois, ele para e explica (uma API removida que exige uma decisão de projeto). Dois engenheiros revisam oito PRs em uma manhã, em vez de consertar catorze serviços em dois dias — e os dois casos difíceis recebem toda a atenção deles.
Na entrevista
Para "projete um agente que conserta builds quebrados", desenhe o orquestrador em volta do agente, e não só o loop: gatilho e dedup, sandbox e lista de ferramentas permitidas, orçamentos impostos pelo harness, verificação fora do agente, um passo privilegiado separado que abre um PR em rascunho, e a trilha de auditoria. Quantifique o custo por execução (quadrático nos turnos, atenuado pelo cache) e por correção mergeada. Diga quais falhas ele não deve tocar (testes flaky, infraestrutura, achados de segurança) e como o sistema as detecta.
Boas práticas
- Use o SHA do commit que falhou como chave de idempotência — uma proposta por SHA: o
ai-fix.yamldo lab 13 usa um grupo deconcurrencyno SHA e a branchai-fix/<sha8>. - Guarde a transcrição e o log de auditoria como artefatos do CI — a execução pode ser explicada depois; o lab 13 envia
agent-transcript.jsonleagent-audit.jsonl. - Tire os segredos do build que o agente roda — o lab 13 roda
mvn verifysem as variáveis*KEY*,*TOKEN*e*SECRET*. - Acompanhe dólares por correção mergeada, não por execução — uma correção útil a cada quatro execuções de US$ 0,80 dá US$ 3,20 por correção; compare com o tempo de engenheiro que ela economiza.
- Abra rascunhos, nunca PRs prontos para revisão — um rascunho não sofre merge por acidente e diz que uma pessoa precisa assumir; o lab 13 usa
gh pr create --draft.
Armadilhas comuns
- Um único job com o agente e um token de escrita: toda prompt injection vira um push.
- Orçamentos no prompt ("por favor, pare depois de 10 passos") em vez de no harness.
- Confiar no "consertei" do agente: verifique de forma independente; confira que nenhum teste foi removido ou enfraquecido.
- Agentes disparando agentes: uma branch de correção falha no CI, o que dispara o agente de correção, que…
- Rodar agentes sobre código de forks com segredos (
pull_request_target,workflow_run): veja Segurança e governança.
Como se conecta
flowchart LR
TRIG["red build event<br/>trigger + dedupe"] --> HARN["harness = orchestrator"]
HARN --> SB["sandbox, tool allow-list,<br/>no write token"]
HARN --> BUD["budgets: turns, tokens,<br/>dollars, minutes"]
HARN --> LOOP["agent loop<br/>→ AI systems agents"]
LOOP --> VER["verification<br/>outside the agent"]
VER --> PRIV["separate privileged step<br/>opens a draft PR"]
PRIV --> HUM["human review"]
HARN --> IDEM["idempotency, retries, loops<br/>→ Messaging semantics"]
HARN --> AUD["audit trail:<br/>who changed what, and why"] Componentes da paleta: orchestrator (o harness que impõe orçamentos e permissões) · toolreg (a lista de ferramentas que o agente pode chamar) · llmgw (cotas e custo por execução) · safety (nenhum segredo ou token de escrita ao alcance do agente) · logs (a trilha de auditoria) · metrics (correções aceitas por proposta, custo por correção aceita). Cenários: ai-assistant com o perfil spike (centenas de agentes ao mesmo tempo disputam o gateway com o tráfego dos usuários). Continua em: Sistemas de IA — agentes, Mensageria — semântica (execuções pelo menos uma vez, efeitos idempotentes), Segurança e governança, Cloud-native — entrega, Hands-on 13 — agente de correção.
-
WINTERS, T.; MANSHRECK, T.; WRIGHT, H. Software Engineering at Google, cap. 22 "Large-Scale Changes". O'Reilly, 2020. ↩