E4 · Três ciclos sem custo
10 minRode o ensaio.
Pronto quando: tail -n +2 $E/sis/results.tsv | cut -f5 | sort | uniq -c → 1 keep e 2 discard; git -C $E/sis log --oneline | wc -l → 2.
Aula 2 · 20 min
O desenho mais limpo de loop que melhora sozinho é curto: o agente muda uma coisa, um comando dá a nota, melhorou fica com commit, não melhorou volta com git reset, e tudo vira uma linha num arquivo. Nesta aula você roda esse ciclo com agentes falsos e depois com o Claude Code de verdade.
Começar a aula
Objetivo: rodar um experimento no estilo autoresearch: o agente muda, uma métrica decide, git commit se melhorou e git reset se não.
O desenho do autoresearch, descrito em fontes secundárias. Clique em cada bloco.
No autoresearch o sistema é um script de treino e a métrica é a perda na validação; aqui, um classificador de triagem com 40 casos sintéticos.
Quatro ideias, uma por ciclo. Uma delas quebra na primeira execução (o agente fica sem resposta). Rode, interrompa e retome.
Simulação no navegador. O ensaio de verdade está logo abaixo.
O kit cria um repo de triagem (v1) e roda três ciclos: um agente que melhora, um que piora e um que não muda a nota. Nenhum modelo é chamado.
K=~/projetos/loop-r-eng-aprende-v72/kit
E=${TMPDIR:-/tmp}/ensaio-aprende/a2
bash $K/novo-sistema.sh $E/sis
for m in sinonimos estraga comenta; do
AGENTE="bash $K/agente-falso.sh $m" bash $K/experimento.sh $E/sis "$m"; echo "saída: $?"
done
echo "## results.tsv"; cut -f1,3- $E/sis/results.tsv
echo "## git log"; git -C $E/sis log --format=%ssistema criado em /tmp/ensaio-aprende/a2/sis (acerto: 0.700 (28/40)) ciclo 1 · keep · 0.700 → 0.800 · melhorou saída: 0 ciclo 2 · discard · 0.800 → 0.700 · não melhorou saída: 1 ciclo 3 · discard · 0.800 → 0.800 · não melhorou saída: 1 ## results.tsv ciclo base novo status descricao 1 0.700 0.800 keep sinonimos 2 0.800 0.700 discard estraga 3 0.800 0.800 discard comenta ## git log keep: sinonimos (0.700 → 0.800) v1: regras de base
Coluna do hash do commit omitida (muda a cada rodada). O código de saída do experimento.sh diz o que aconteceu: 0 keep, 1 discard, 2 erro.
Tratar agente sem resposta como "não melhorou". Quando a assinatura bate no limite, o agente devolve vazio: o ciclo precisa sair como erro (código 2), senão a fila vira uma série de discards falsos e ninguém percebe que nada rodou. O agente-claude.sh sai com 1 quando a resposta não tem regras, e o experimento.sh registra erro.
Uma chamada pela sua assinatura (sai da cota): claude -p sem ferramentas recebe as regras e os casos errados e devolve um regras.txt novo. Quem decide é a medida, não o modelo.
K=~/projetos/loop-r-eng-aprende-v72/kit
E=${TMPDIR:-/tmp}/ensaio-aprende/a2 # o repo do ensaio acima, já com o keep dos sinônimos
AGENTE="bash $K/agente-claude.sh" bash $K/experimento.sh $E/sis "claude: uma mudança"; echo "saída: $?"
echo "status da última linha: $(tail -1 $E/sis/results.tsv | cut -f5)"#!/usr/bin/env bash
# agente-claude.sh — o agente REAL do experimento: o Claude Code (sem ferramentas) propõe um regras.txt novo.
# Roda na pasta do repo, chamado pelo experimento.sh. Sai da cota da sua assinatura (uma chamada).
# Resposta vazia (limite da sessão, erro) = ERRO, saída 1: nunca vira "não mudou nada".
set -uo pipefail
K=$(cd "$(dirname "$0")" && pwd)
PEDIDO="Você melhora as regras de um classificador de mensagens de suporte. Formato de cada linha: 'categoria: palavra, palavra'; a primeira categoria cuja palavra aparece no texto vence; sem nenhuma, 'outros'.
Regras atuais:
$(cat regras.txt)
Casos que o classificador errou:
$(python3 "$K/medir.py" . --erros | tail -n +2)
Proponha UMA mudança que generalize (palavras que pessoas reais usariam), sem copiar frases inteiras dos casos.
Responda SÓ com o conteúdo completo do novo regras.txt, sem explicação e sem cercas de código."
J=$(mktemp)
echo "$PEDIDO" | claude -p --tools "" --output-format json > "$J" 2>/dev/null
python3 - "$J" <<'PY'
import json, os, sys
try:
d = json.load(open(sys.argv[1], encoding="utf-8"))
except Exception:
print("ERRO: resposta do claude não é JSON"); sys.exit(1)
r = (d.get("result") or "").strip()
linhas = [l for l in r.splitlines() if not l.startswith("```")]
if d.get("is_error") or not any(":" in l for l in linhas):
print("ERRO: sem regras na resposta (" + (r[:80] or "vazia") + ")"); sys.exit(1)
u = d.get("usage") or {}
ent = u.get("input_tokens", 0) + u.get("cache_read_input_tokens", 0) + u.get("cache_creation_input_tokens", 0)
if os.environ.get("TOKENS_ARQ"):
open(os.environ["TOKENS_ARQ"], "w").write(f"{ent} {u.get('output_tokens', 0)}\n")
open("regras.txt", "w", encoding="utf-8").write("\n".join(linhas).strip() + "\n")
print(f"regras.txt reescrito ({len(linhas)} linhas)")
PY
rc=$?; rm -f "$J"; exit $rcciclo 4 · keep · 0.800 → 0.900 · melhorou saída: 0 status da última linha: keep
Uma rodada. O modelo acrescentou palavras de entrega (pacote, encomenda, transportadora, frete…) e a nota visível foi de 0.800 a 0.900. A sua rodada pode dar keep ou discard: o "pronto quando" confere só que a linha foi registrada. Na aula 6, esta mesma mudança é reprovada pelos casos escondidos.
O experimento.sh do kit mede o classificador de triagem. No seu repo, duas variáveis trocam o medidor; o results.tsv nasce sozinho e fica fora do git. Ensaio sem custo num repo de treino.
K=~/projetos/loop-r-eng-aprende-v72/kit
E=${TMPDIR:-/tmp}/ensaio-aprende/a2-adaptar; rm -rf $E; mkdir -p $E/repo; cd $E/repo
# um repo "seu": sem results.tsv, sem .gitignore, com o próprio medidor
printf 'def dobro(x):\n return x + 2\n' > calc.py
printf '1 2\n2 4\n5 10\n0 0\n' > casos.txt # entrada e esperado
cat > medir.sh <<'FIM'
# imprime "<acerto> <ok> <total>" (o experimento.sh lê o 1º número)
python3 -c "
import calc
c = [l.split() for l in open('casos.txt') if l.strip()]
ok = sum(calc.dobro(int(a)) == int(b) for a, b in c)
print(f'{ok / len(c):.3f} {ok} {len(c)}')"
FIM
git init -q -b main; git add -A; git -c user.name=treino -c user.email=treino@exemplo.invalid commit -qm "v1"
export MEDIR="bash medir.sh" MEDIDOR="casos.txt medir.sh" # as duas variáveis da adaptação
bash $K/experimento.sh --congelar . $E/medidor.sha256; export GUARDA=$E/medidor.sha256
AGENTE="echo '# revisado' >> calc.py" bash $K/experimento.sh . "comenta"; echo "saída: $?"
AGENTE="printf 'def dobro(x):\n return x * 2\n' > calc.py" bash $K/experimento.sh . "corrige"; echo "saída: $?"
AGENTE="echo '3 7' >> casos.txt" bash $K/experimento.sh . "mexe nos casos"; echo "saída: $?"
echo "## results.tsv (criado pelo experimento.sh)"; cut -f1,3- results.tsv
echo "## git status (vazio = limpo)"; git status --shortmedidor congelado em /tmp/ensaio-aprende/a2-adaptar/medidor.sha256 ciclo 1 · discard · 0.250 → 0.250 · não melhorou saída: 1 ciclo 2 · keep · 0.250 → 1.000 · melhorou saída: 0 ciclo 3 · discard · 1.000 → 1.000 · DESCARTADO: mexeu no medidor (casos.txt medir.sh) saída: 1 ## results.tsv (criado pelo experimento.sh) ciclo base novo status descricao 1 0.250 0.250 discard comenta 2 0.250 1.000 keep corrige 3 1.000 1.000 discard mexe nos casos ## git status (vazio = limpo)
Coluna do hash do commit omitida. O ciclo 3 mostra a guarda da aula 6 valendo também para o medidor seu.
1. A medida. MEDIR="[seu comando]" roda na pasta do repo e imprime o número primeiro, numa linha. Ele recebe --valor e, se você usar ESCONDIDOS (aula 6), também --casos [arquivo]: o seu script precisa medir esse arquivo no lugar dos casos de sempre.
2. O que congelar. MEDIDOR="[arquivos]" lista o que a medida lê (casos e o próprio script). É o que o --congelar grava no manifesto.
3. O registro. Se faltar, o experimento.sh cria o results.tsv com cabeçalho e o põe em .git/info/exclude, sem mexer no seu .gitignore. Rastro e manifesto ficam fora do repo; arquivo novo não ignorado dentro dele faz o ciclo seguinte sair com erro 2 (repo sujo).
Ensaio, real e o seu sistema.
Rode o ensaio.
Pronto quando: tail -n +2 $E/sis/results.tsv | cut -f5 | sort | uniq -c → 1 keep e 2 discard; git -C $E/sis log --oneline | wc -l → 2.
Rode o bloco real logo depois do ensaio.
Pronto quando: wc -l < $E/sis/results.tsv → 5 (cabeçalho + 4 ciclos) e a última coluna de status é keep, discard ou erro, nunca vazia.
No seu sistema, escreva o comando que imprime a métrica da sua hipótese (aula 1) como número no começo da linha, no formato do medir.py --valor. Ligue-o no experimento.sh pelas variáveis MEDIR e MEDIDOR, como no bloco "Adaptar ao seu repo" acima.
Pronto quando: o seu comando de medida, rodado duas vezes seguidas sem mudança, imprime o mesmo número; e AGENTE="echo '# revisado' >> [um arquivo do repo]" bash $K/experimento.sh . "comenta"; echo "saída: $?", com MEDIR e MEDIDOR exportados, termina em discard e saída: 1.
Pergunta 1 de 4
A mudança deu exatamente a mesma nota da base. O que o ciclo faz?competência
Pergunta 2 de 4
Por que o discard também vai para o results.tsv?
Pergunta 3 de 4
O agente ficou sem resposta (limite da sessão). Como o ciclo deve terminar?competência
Pergunta 4 de 4
Sobre os números do autoresearch (experimentos por hora, ganho no treino), o que este curso diz?
Escolha uma alternativa. A explicação aparece depois da sua tentativa.