The loop that learns|INEMA.CLUB-PRO

Lesson 2 · 20 min

Experiment: keep or discard based on a single metric

The cleanest design for a loop that improves on its own is short: the agent changes one thing, a command gives the score, if it improved it stays with a commit, if it didn't it goes back with git reset, and everything becomes one line in a file. In this lesson you run this cycle with fake agents and then with the real Claude Code.

Start the lesson
Two circuit boards side by side connected to meters; one with a yellow tag, the other going into the discard bin.

In this lesson you will

  1. 1Keep = commit, discard = reset: git as undo
  2. 2A single metric, with a fixed budget per cycle
  3. 3results.tsv: every attempt becomes a line, including the bad ones
  4. 4The same cycle with a fake agent and with Claude Code

Goal: run an autoresearch-style experiment: the agent changes, a single metric decides, git commit if it improved and git reset if not.

01One experiment cycle

The autoresearch design, as described in secondary sources. Click each block.

Equal is not better: a tie goes to discard. Otherwise the system piles up changes that do nothing.

In autoresearch the system is a training script and the metric is validation loss; here, a triage classifier with 40 synthetic cases.

02A queue of experiments

Four ideas, one per cycle. One of them breaks on the first run (the agent is left without an answer). Run it, interrupt it, and resume.

ideas

measuring

results.tsv

erro

Browser simulation. The real rehearsal is right below.

03Rehearsal: three fake agents

The kit creates a triage repo (v1) and runs three cycles: an agent that improves, one that makes things worse, and one that does not change the score. No model is called.

Terminal · cost-free rehearsal
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=%s
Example from the repository
sistema 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

Commit hash column omitted (it changes on every run). The exit code of experimento.sh tells you what happened: 0 keep, 1 discard, 2 error.

04The same cycle with Claude Code

One call through your subscription (uses your subscription quota): claude -p with no tools receives the rules and the wrong cases and returns a new regras.txt. The measurement decides, not the model.

Terminal · real, one call
K=~/projetos/loop-r-eng-aprende-v72/kit
E=${TMPDIR:-/tmp}/ensaio-aprende/a2      # the rehearsal repo above, already with the synonyms keep
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)"
File · kit/agente-claude.sh
#!/usr/bin/env bash
# agente-claude.sh — the REAL agent of the experiment: Claude Code (no tools) proposes a new regras.txt.
# Runs in the repo folder, called by experimento.sh. Uses your subscription quota (one call).
# Empty answer (session limit, error) = ERROR, exit 1: it never becomes "nothing changed".
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 $rc
Example from the repository
ciclo 4 · keep · 0.800 → 0.900 · melhorou
saída: 0
status da última linha: keep

One run. The model added delivery words (pacote, encomenda, transportadora, frete…) and the visible score went from 0.800 to 0.900. Your run may give keep or discard: the "done when" only checks that the line was recorded. In lesson 6, this same change is rejected by the hidden cases.

05Adapt it to your repo

The kit's experimento.sh measures the triage classifier. In your repo, two variables swap the measurer; results.tsv is created on its own and stays out of git. Cost-free rehearsal in a practice repo.

Terminal · rehearsal, a repo of your own
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 --short
Example from the repository
medidor 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.

06Practice

Rehearsal, real run and your own system.

E4 · Three cycles at no cost

10 min

Run the rehearsal.

Done when: tail -n +2 $E/sis/results.tsv | cut -f5 | sort | uniq -c → 1 keep and 2 discard; git -C $E/sis log --oneline | wc -l → 2.

E5 · One real cycle

5 min

Run the real block right after the rehearsal.

Done when: wc -l < $E/sis/results.tsv → 5 (header + 4 cycles) and the last status column is keep, discard or erro, never empty.

E6 · Your meter

30 min

In your system, write the command that prints the metric of your hypothesis (lesson 1) as a number at the start of the line, in the format of medir.py --valor. Plug it into experimento.sh through the MEDIR and MEDIDOR variables, as in the "Adapt it to your repo" block above.

Done when: your measuring command, run twice in a row with no change, prints the same number; and AGENTE="echo '# revisado' >> [a file in the repo]" bash $K/experimento.sh . "comenta"; echo "saída: $?", with MEDIR and MEDIDOR exported, ends in discard and saída: 1.

07Lesson quiz

Question 1 of 4

The change gave exactly the same score as the base. What does the cycle do?skill

08In three sentences

One change, one scoreEach cycle measures the baseline, lets the agent change one thing, and measures again with the same command.
Git undoesImproved is a commit; equal or worse is a reset. Nothing is left half done.
Everything loggedresults.tsv stores keep, discard and erro; an empty answer from the model is an error.

Going further

Previous lessonNext lesson