d. Agente de Correção
O revisor da parte 1 faz uma chamada ao modelo e posta texto. Um agente é diferente: ele roda um loop — lê um arquivo, edita, roda o build, lê o erro, edita de novo — escolhendo o próprio próximo passo até a tarefa terminar ou um orçamento se esgotar (veja Agentes e ferramentas). Isso o torna muito mais capaz, e muito mais perigoso: ele executa comandos, escreve código e custa mais a cada passo. Esta parte constrói um agente que reage a uma execução de CI com falha propondo uma correção como um pull request em rascunho — e gasta a maior parte das suas linhas mantendo esse agente dentro de uma caixa.
3.1 O fluxo e suas fronteiras de confiança
sequenceDiagram
autonumber
participant CI as Workflow de CI (lab 11)
participant Fix as job "fix"<br/>contents: read, actions: read
participant Agent as Agente<br/>(Claude Code ou fix_agent.py)
participant Open as job "open-pr"<br/>contents: write, pull-requests: write
actor Human as Revisor
CI-->>Fix: workflow_run concluído (falha)
Fix->>Fix: dedup: o branch ai-fix/<sha> existe? para
Fix->>Fix: gh run view --log-failed | tail -300
Fix->>Agent: log + repositório (sem token do GitHub)
Agent->>Agent: lê · edita · mvn verify (≤ 25 turnos, ≤ US$ 1,50)
Agent-->>Fix: alterações na árvore de trabalho
Fix->>Fix: verifica: só src/, nenhum teste removido, mvn verify passa
Fix->>Open: artefato ai-fix.patch (+ transcrição)
Open->>Open: aplica o patch, commit com trailer de IA, push de ai-fix/<sha>
Open->>Human: PR em rascunho, label ai-generated
Human->>Human: revisa, roda o CI, faz merge ou fecha Três separações fazem o trabalho de segurança:
| Separação | O que ela impede |
|---|---|
O agente nunca tem um token do GitHub. Só a ANTHROPIC_API_KEY (ou a chave do LiteLLM) está no ambiente dele | Um agente confuso ou vítima de injection não consegue fazer push, comentar, aprovar nem ler outros repositórios |
A verificação não é feita pelo agente. Um passo de shell simples roda mvn verify de novo, rejeita arquivos fora de services/<name>/src/ e rejeita @Test removidos ou @Disabled adicionados | "Consertei o build" é verificado, não acreditado; o atalho clássico — apagar o teste que falha — é pego |
Só o open-pr pode escrever, e ele não roda modelo nem build: ele aplica um patch | O acesso de escrita fica com dez linhas de shell determinístico |
E um humano faz o merge. Nada neste workflow chega à main sem um review exigido pela proteção de branch.
3.2 Caminho A — Claude Code, headless (pago)
O Claude Code pode rodar de forma não interativa com claude -p. As flags usadas aqui, da sua referência de CLI:
| Flag | Papel neste lab |
|---|---|
--bare | Pula a descoberta automática de hooks, skills, plugins, servidores MCP e CLAUDE.md do repositório — o agente carrega só o que a linha de comando lhe dá, igual em todo runner |
--allowedTools "Read,Edit,Write,Bash(mvn -B *)" | Ferramentas que rodam sem perguntar; o shell fica limitado a comandos que começam com mvn -B |
--permission-mode dontAsk | Tudo o que não é permitido é negado, não perguntado (ninguém consegue responder a uma pergunta no CI) |
--max-turns 25 | Limite de turnos do agente |
--max-budget-usd 1.50 | Para a execução quando o gasto estimado atinge o limite (uma estimativa do lado do cliente — deixe folga) |
--output-format stream-json --verbose | Um evento JSON por linha: a transcrição completa, guardada como log de auditoria; a última linha é o resultado com total_cost_usd |
--model | O modelo, vindo da variável LLM_MODEL |
O log da falha chega pela stdin, então o agente não precisa de permissão para ler arquivos fora do repositório.
O caminho A só roda quando a variável de repositório LLM_PROVIDER é anthropic, e precisa do segredo ANTHROPIC_API_KEY: toda execução é cobrada, com teto de US$ 1,50 pelo --max-budget-usd. O workflow instala uma versão fixada do Claude Code (@anthropic-ai/claude-code@2.1.302); atualize-a de propósito, como qualquer outra dependência. Sem chave, pule para o caminho B — ele é o padrão.
A GitHub Action do Claude Code
A Anthropic também publica a anthropics/claude-code-action@v1, que empacota o mesmo motor para o GitHub: menções @claude em PRs e issues, ou automação com um input prompt, e flags de CLI por meio de claude_args (por exemplo --max-turns 5 --model ...). É o jeito mais rápido de ter um assistente sob demanda nos PRs. Este lab chama a CLI diretamente porque queremos a separação por artefato de patch acima, em que o job que roda o agente não tem token de escrita; consulte a documentação de segurança da action se você a usar para escritas.
3.3 Caminho B — um agente neutro em relação ao provedor em menos de 200 linhas (gratuito com Ollama)
Para ver que um agente é "só um loop", o fix_agent.py implementa um sobre qualquer endpoint compatível com OpenAI com chamada de ferramentas (LiteLLM, ou Ollama com um modelo que suporte ferramentas). Ele tem cinco ferramentas — list_files, read_file, write_file, run_build, finish — e nenhum shell.
| .github/ai/fix_agent.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | |
| Proteção | Onde |
|---|---|
Caminhos resolvidos e mantidos dentro do repositório; .git/ e .github/ inalcançáveis | safe() |
Escreve só em services/<name>/src/ | safe(..., write=True) |
O build roda com um ambiente sem variáveis KEY, TOKEN, SECRET | run_build() |
| Erros de ferramenta voltam ao modelo como texto, nunca derrubam o loop | call_tool() + except |
| Orçamento: passos, tokens e dólares | MAX_STEPS, MAX_TOKENS, MAX_USD |
Toda chamada de ferramenta registrada em agent-audit.jsonl (fora do repositório) | AUDIT |
| Uma chamada de ferramenta escrita como texto JSON (modelos locais pequenos fazem isso) é aceita como chamada, nunca executada como código | text_tool_call() |
run_build("catalog") é lido como services/catalog; um pom.xml ausente vira mensagem de erro, não um crash | run_build() |
3.4 O workflow
| .github/workflows/ai-fix.yaml | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
| Detalhe | Por quê |
|---|---|
on: workflow_run | Roda depois que o CI termina, com a definição do workflow do branch padrão |
head_repository.full_name == github.repository | O workflow_run roda com os segredos deste repositório mesmo quando a execução de CI veio de um fork; fazer checkout e build do código do fork aqui seria um pwn request (parte 6) |
!startsWith(head_branch, 'ai-fix/') | Os próprios branches do agente nunca disparam outro agente: nada de loops |
concurrency no SHA do head + verificação do branch ai-fix/<sha8> | Idempotente: uma proposta por commit com falha, mesmo que a execução seja repetida |
if-no-files-found: ignore + always() | A transcrição é enviada mesmo quando o agente falha — é justamente quando você precisa dela |
| --trailer "AI-assisted: ..." | O commit diz que foi gerado por IA, e com qual modelo | | vars.LLM_PROVIDER == 'anthropic' → caminho A, senão caminho B | Uma variável troca o agente; sem variável nenhuma, o caminho B roda com o Ollama no runner (gratuito, mas um runner só com CPU precisa de muitos minutos para 20 passos — a execução local da 3.5 é bem mais rápida) |
Duas configurações do GitHub que você precisa conhecer
- O
GITHUB_TOKENsó pode criar pull requests se Allow GitHub Actions to create and approve pull requests estiver habilitado — o passo 0.4 do setup fez isso. Este lab nunca aprova. - Eventos causados pelo
GITHUB_TOKEN(o push deai-fix/..., o PR que ele abre) não disparam outros workflows, então o CI não roda sozinho no PR em rascunho. É uma proteção do GitHub contra loops. Para rodar o CI nele, faça push de um commit vazio da sua máquina:gh pr checkout <número do PR em rascunho>, depoisgit commit --allow-empty -m "ci: run CI on the AI fix" && git push(ou use um token de GitHub App se o seu time automatizar isso).
3.5 Quebre o build de propósito
Faça commit do agente e do seu workflow na main antes (workflows workflow_run só rodam a partir do branch padrão):
git switch main && git pull
git add .github/ai/fix_agent.py .github/workflows/ai-fix.yaml
git commit -m "ci(ai): fix agent for failed CI runs (lab 13)"
git push
Agora um bug realista que um teste unitário pega: em deleteEvent do CatalogService, alguém "simplifica" a verificação de existência e tira o !:
public void deleteEvent(Long id) {
- if (!events.existsById(id)) {
+ if (events.existsById(id)) {
throw notFound("event", id);
}
Localmente primeiro (de graça, e minutos em vez de dezenas de minutos). Reproduza o que o CI vai ver, entregue o log ao agente e veja o que ele mudou. O Docker precisa estar rodando: o mvn verify — o seu, o run_build do agente e o passo de verificação no CI — também roda o teste com Testcontainers CatalogApiTest do lab 11:
mvn -B -f services/catalog/pom.xml verify > /tmp/ci-full.log 2>&1; tail -n 300 /tmp/ci-full.log > /tmp/ci-failure.log
grep -E "Tests run: .*(Failures: [1-9]|Errors: [1-9])|BUILD" /tmp/ci-failure.log | head -n 3
AGENT_AUDIT=/tmp/agent-audit.jsonl uv run --no-project --python 3.12 \
--with-requirements .github/ai/requirements.txt python .github/ai/fix_agent.py /tmp/ci-failure.log
git diff # the agent's change, if any
jq -c '{step, tool, args}' /tmp/agent-audit.jsonl # what it did, step by step (or: cat the file)
O grep precisa mostrar a falha que o CI vai ver (o Maven 3.10 imprime [ERROR] BUILD FAILURE; o Maven 3.9, [INFO] BUILD FAILURE):
[ERROR] Tests run: 3, Failures: 1, Errors: 1, Skipped: 0, Time elapsed: 0.618 s <<< FAILURE! -- in boxoffice.catalog.CatalogServiceTest
[ERROR] Tests run: 6, Failures: 1, Errors: 1, Skipped: 0
[ERROR] BUILD FAILURE
O agente imprime uma linha JSON no final: fixed, a sua explanation, os tokens, os passos e o tempo. Duas execuções com o qwen2.5-coder-16k local durante a revisão deste lab deram o mesmo resultado (a explicação está abreviada):
{"fixed": true, "explanation": "The root cause of the issue was that the `deleteEvent` method in `CatalogService` was incorrectly checking if the event exists before deleting it, ... (abridged)", "model": "qwen2.5-coder-16k", "calls": 4, "in": 32012, "cache_read": 0, "cache_write": 0, "out": 954, "seconds": 169.2, "usd": 0.0, "steps": 4}
Leia o log de auditoria ao lado do git diff: o modelo abriu o CatalogService.java, reescreveu-o com o ! de volta (o diff é essa única linha), rodou o build e terminou — quatro passos, cerca de três minutos num notebook:
$ jq -c '{step, tool, args}' /tmp/agent-audit.jsonl
{"step":1,"tool":"read_file","args":{"path":"services/catalog/src/main/java/boxoffice/catalog/CatalogService.java"}}
{"step":2,"tool":"write_file","args":{"path":"services/catalog/src/main/java/boxoffice/catalog/CatalogService.java","content":"<3168 chars>"}}
{"step":3,"tool":"run_build","args":{"service":"catalog"}}
{"step":4,"tool":"finish","args":{"fixed":true,"explanation":"<325 chars>"}}
Não conte com isso. Uma execução anterior do mesmo modelo nunca abriu o CatalogService.java: reescreveu o teste seis vezes — acrescentando, entre outros, um teste que espera um 404 quando o evento existe, o que transforma o bug em especificação (o problema do oráculo da parte 2) — depois quebrou a compilação com um método duplicado e esgotou os seus 20 passos depois de 8 minutos e meio e 165.075 tokens de entrada, terminando com "fixed": false. Nem então algo perigoso aconteceu: toda escrita ficou em services/catalog/src/, e no CI o mvn verify independente do passo de verificação falha com um patch assim, então nenhum PR é aberto. Esse é o objetivo das proteções — elas tornam um agente ruim inofensivo, não bom. Rode de novo (e com um modelo mais forte, se tiver um) e compare.
Seja o que for que ele fez, verifique como o workflow faz: git diff --stat só pode listar arquivos em services/catalog/src/, nenhum @Test pode sumir, e mvn -B -f services/catalog/pom.xml verify precisa passar. Depois desfaça a mudança do agente (git checkout -- services) — a execução de CI abaixo deve partir do commit quebrado.
Depois no CI:
- Faça push do branch e abra um PR:
git push -u origin break-catalog && gh pr create --fill --base main. - O CI falha (
gh pr checksmostraCIem vermelho). Em menos de um minuto o workflowAI fixcomeça:gh run list --workflow ai-fix.yaml --limit 1. Com o modelo gratuito no runner, dê a ele até 45 minutos. - Abra o resumo e baixe o artefato
ai-fix:gh run download <run id> -n ai-fix -D /tmp/ai-fix. Ele temagent-audit.jsonl(caminho B) ouagent-transcript.jsonl(caminho A), eai-fix.patchse a mudança passou na verificação. - Se a verificação passou, aparece um PR em rascunho
AI fix proposal for break-catalog (<sha>):gh pr list --label ai-generated. Leia-o e responda emresults/lab13.md:- O agente corrigiu o código ou dobrou o teste? (A proteção pega testes apagados, não uma asserção enfraquecida — ele tentou?)
- Quantos passos (ou turnos) e quantos dólares ou minutos foram necessários? Compare com o tempo que você levou.
- Você faria o merge? Aprove ou feche o PR com uma justificativa escrita (
gh pr review --approve -b "..."ough pr close -c "...").
- Repita com uma falha que o agente não consegue corrigir só com o repositório — por exemplo, um teste que precisa de um banco que o job de CI não sobe. Um bom agente para e explica (
"fixed": false); um ruim edita arquivos aleatórios. O seu passo de verificação é o que mantém o segundo caso inofensivo.
Quando terminar, feche o PR break-catalog sem merge (gh pr close break-catalog --delete-branch).
Onde agentes no CI compensam
Falhas mecânicas com um sinal claro no log: erros de compilação depois de atualizar uma dependência, um método renomeado, um import quebrado, uma verificação de formatação, um teste que falha porque uma mensagem esperada mudou. Eles compensam menos em testes instáveis e problemas de infraestrutura — classifique esses primeiro (a página da aula sobre testes trata da triagem de testes instáveis).
Solução de problemas
| Sintoma | Causa | Correção |
|---|---|---|
O AI fix nunca começa depois que o CI falha | o workflow não está no branch padrão, ou o seu workflow de CI não se chama CI | faça merge do ai-fix.yaml na main; gh workflow list mostra o nome do CI — use-o em workflows: [...] |
| A execução é pulada | o CI não falhou, o branch começa com ai-fix/ ou ai-tests/, ou a execução de CI veio de um fork | esperado: são as proteções |
fixed: false, explanation: stopped by budget after N steps | o modelo se perdeu (comum em modelos pequenos) | leia o agent-audit.jsonl; aumente AGENT_MAX_STEPS localmente, ou teste um modelo mais forte |
ERROR: ... only files under services/<name>/src/ may be written na auditoria | o agente tentou editar o pom.xml, um workflow ou outro arquivo protegido | esperado: a proteção funcionou |
the agent changed files outside services/<name>/src/ no passo de verificação | o caminho A escreveu fora de src/ | o patch é rejeitado de propósito; leia a transcrição |
openai.BadRequestError ... does not support tools | o modelo do Ollama não tem chamada de ferramentas | use o qwen2.5-coder-16k (feito a partir do qwen2.5-coder:7b, que suporta ferramentas) |
Execução local: agent-audit.jsonl aparece no git status | AGENT_AUDIT não definido, então o log foi para a raiz do repositório | defina AGENT_AUDIT=/tmp/agent-audit.jsonl como no comando acima; apague o arquivo perdido |