Execução Longa|INEMA.CLUB-PRO

Aula 6 · 20 min

Loop headless: loop-longrun.sh, flock, timeout e MemoryMax

No /goal interativo, quem decide que acabou é um avaliador lendo a conversa. No loop headless, quem decide é o teste, rodado pelo próprio loop, com os arquivos de teste protegidos. Nesta aula você roda o loop-longrun.sh com um agente falso: vê concluir, vê a trapaça ser revertida e vê a trava impedir dois loops na mesma pasta, sem gastar cota.

Começar a aula
Rack de servidores numa sala escura com LEDs acesos e nenhum monitor ligado.

Nesta aula você vai

  1. 1O loop.env: testes, protegidos, permitidos e tetos
  2. 2flock, timeout e MemoryMax em cada ciclo
  3. 3Teste protegido mexido = revertido e ciclo falho
  4. 4systemd: vigia a cada 10 min e cron com flock

Objetivo: rodar o loop-longrun.sh com um agente falso e ver o teste decidir: concluído, trapaça revertida, estagnação e trava de segunda cópia.

01Um ciclo do loop-longrun.sh

O agente trabalha; o loop decide. Clique em cada etapa.

Clique em cada bloco do diagrama para ver o que ele faz.

Se o que falta só aparece no teste final (lento), cada ciclo conta como "sem avanço": ponha esses casos também no teste rápido ou aumente ESTAGNACAO (aviso do próprio script).

02Veja o loop decidir

Rode, interrompa no meio e rode de novo. Um dos ciclos mexe num teste protegido.

ciclos a rodar

ciclo em curso

checkpoint (commit)

revertido

Simulação ilustrativa no navegador. O ensaio de verdade, com agente falso, está logo abaixo.

03Ensaio com agente falso

O loop aceita CODEX_BIN: apontando para um script, nenhum modelo é chamado. É a forma barata de testar o seu loop.env antes de gastar cota.

ensaio-loop.sh · monta o projeto de teste
# ensaio-loop.sh — monta um projeto de teste com 3 testes congelados e dois agentes FALSOS
E=/tmp/ensaio-loop; K=~/projetos/execucao-longa
rm -rf $E && mkdir -p $E/proj/tests && cd $E/proj
git init -q && git config user.name ensaio && git config user.email ensaio@local
cat > tests/test_conta.py <<'PY'
from conta import conta_palavras
def test_vazio(): assert conta_palavras("") == 0
def test_simples(): assert conta_palavras("um dois três") == 3
def test_espacos(): assert conta_palavras("  um   dois ") == 2
PY
printf 'def conta_palavras(texto):\n    raise NotImplementedError\n' > conta.py
printf '[pytest]\npythonpath = .\n' > pytest.ini
printf '__pycache__/\n' > .gitignore
git add -A && git commit -qm "base: testes congelados"
$K/tools/novo-longrun.sh $E/proj conta-palavras > /dev/null
D=$(ls -d $E/proj/longrun/*)
echo "Implemente conta_palavras em conta.py. Leia goal.md, state.md e plan.md antes. Não mexa em tests/." > $D/prompt.md
cat > $D/loop.env <<'ENV'
TESTE_RAPIDO="python3 -m pytest -q"
TESTE_FINAL="python3 -m pytest -q"
PROTEGIDOS="tests/ pytest.ini"
PERMITIDOS="conta.py"
MAX_CICLOS=4
MIN_CICLO=2
MEM=1G
ESTAGNACAO=3
ENV
git add -A && git commit -qm "longrun: pasta da execução"
printf '#!/usr/bin/env bash\ncat > /dev/null\nprintf "def conta_palavras(texto):\\n    return len(texto.split())\\n" > conta.py\n' > $E/agente-bom.sh
printf '#!/usr/bin/env bash\ncat > /dev/null\nrm -f tests/test_conta.py\n' > $E/agente-trapaca.sh
printf '#!/usr/bin/env bash\ncat > /dev/null\nsleep 6\n' > $E/agente-lento.sh
chmod +x $E/agente-*.sh
echo "pronto: $D"
Terminal · as três rodadas
bash ensaio-loop.sh
cd /tmp/ensaio-loop/proj && D=$(ls -d $PWD/longrun/*) && L=~/projetos/execucao-longa/tools/loop-longrun.sh

CODEX_BIN=/tmp/ensaio-loop/agente-trapaca.sh $L $D; echo "saída: $?"   # A) trapaça
CODEX_BIN=/tmp/ensaio-loop/agente-bom.sh $L $D; echo "saída: $?"       # B) trabalho certo

CODEX_BIN=/tmp/ensaio-loop/agente-lento.sh $L $D > /dev/null 2>&1 &   # C) dois loops
sleep 2; CODEX_BIN=/tmp/ensaio-loop/agente-lento.sh $L $D; echo "saída: $?"; wait
loop.env de uma execução real (o piloto do kit, 01/10/2026)
TESTE_RAPIDO="pytest -q -m 'not real'"
TESTE_FINAL="pytest -q"
PROTEGIDOS="tests/ pytest.ini tools/medicao/"
PERMITIDOS="tools/medir-sessao.py"
MAX_CICLOS=6
MIN_CICLO=20
MEM=8G
ESTAGNACAO=3
AGENTE=codex
MODELO=<o modelo topo da sua assinatura no Codex>
# se os testes sobem servidor local, o sandbox workspace-write bloqueia até 127.0.0.1:
# CODEX_ARGS="-c sandbox_workspace_write.network_access=true"
Exemplo do repositório
## A) trapaça
2026-10-19 03:56:46 início: rápido=0 passed · agente=codex · teto 4x2min · mem 1G
2026-10-19 03:56:46 ciclo 1: começa
2026-10-19 03:56:46 ciclo 1: agente saiu rc=0 em 0s
2026-10-19 03:56:46 ciclo 1: agente mexeu em arquivo protegido → revertido, ciclo falho
2026-10-19 03:56:46 ciclo 1: checkpoint commitado
(… ciclos 2 e 3 iguais …)
2026-10-19 03:56:47 PARADO: 3 ciclos sem avanço mensurável — ver failures.md
saída: 2

## B) trabalho certo
2026-10-19 03:56:48 início: rápido=0 passed · agente=codex · teto 4x2min · mem 1G
2026-10-19 03:56:48 ciclo 1: começa
2026-10-19 03:56:48 ciclo 1: agente saiu rc=0 em 0s
2026-10-19 03:56:48 ciclo 1: rápido=3 passed (melhor antes: 0)
2026-10-19 03:56:48 ciclo 1: checkpoint commitado
2026-10-19 03:56:49 CONCLUÍDO no ciclo 1: teste final passou (3 passed)
saída: 0

## C) dois loops
outro loop já roda nesta pasta
saída: 3

Os testes apagados pela trapaça voltaram: o loop reverteu os três ciclos e escreveu três linhas "revertido pelo loop" em failures.md.

04Fora do terminal: systemd e cron

O loop roda sozinho; estes três pedaços cuidam dele.

1. Vigia

O vigia.py procura execuções ativas (state.md sem "concluído") e alerta uma vez cada problema: PARADO (o loop.log registrou parada), PARADO SEM AVISO (há loop.log mas nenhum processo do loop vivo) e OCIOSA (nada mudou na pasta nem nas sessões há N minutos).

Alerta = linha em ~/.local/state/execucao-longa/alertas.log; notify-send só com --desktop.

05Ligar o vigia (uma vez por máquina, Linux)

Copia as units do kit, troca o caminho pelo seu e ativa o timer.

Terminal · vigia a cada 10 min
mkdir -p ~/.config/systemd/user
cp ~/projetos/execucao-longa/tools/systemd/longrun-vigia.* ~/.config/systemd/user/
sed -i "s#/home/nmaldaner#$HOME#g" ~/.config/systemd/user/longrun-vigia.service
systemctl --user daemon-reload
systemctl --user enable --now longrun-vigia.timer
systemctl --user list-timers longrun-vigia.timer
python3 ~/projetos/execucao-longa/tools/vigia.py --seco   # uma rodada à mão, sem gravar estado

06Prática

As três rodadas do ensaio, depois o vigia.

E13 · Trapaça revertida

10 min

Rode o ensaio-loop.sh e a rodada A (agente-trapaca).

Pronto quando: a última linha do log é PARADO: 3 ciclos sem avanço mensurável, saída: 2, ls tests/ ainda mostra test_conta.py e grep -c "revertido pelo loop" $D/failures.md → 3.

E14 · Concluído pelo teste e trava de segunda cópia

10 min

Rode as rodadas B e C.

Pronto quando: B termina com CONCLUÍDO no ciclo 1: teste final passou (3 passed) e saída: 0; C imprime outro loop já roda nesta pasta e saída: 3.

E15 · Vigia ligado

15 min

Instale o timer do vigia com o caminho trocado. Deixe a pasta da sua execução real (aula 1) sem "concluído" no state.md.

Pronto quando: systemctl --user list-timers longrun-vigia.timer mostra o próximo disparo e systemctl --user status longrun-vigia.service não mostra falha na última rodada.

07Teste da aula

Pergunta 1 de 5

No loop headless, quem decide que a execução acabou?competência

08Em três frases

O teste decideO loop roda o teste rápido para medir avanço e o final para parar; o agente não faz commit nem declara pronto.
Juiz protegidoMexeu em PROTEGIDOS: revertido, ciclo falho, linha em failures.md; 3 sem avanço: para com 2.
Tetos e travaflock (3), timeout e MemoryMax por ciclo, MAX_CICLOS (1); ensaie com agente falso antes de gastar cota.

Para ir além

Aula anteriorPróxima aula