O agente que finalmente pensou em loop
TatuEngine·

O agente que finalmente pensou em loop

5 min de leitura← Voltar para timeline

O TatuEngine tinha um agente autopoiético desenhado no papel há meses. Immunosystem, memória associativa, pilha de objetivos, guardião de segurança, supervisor semântico — a arquitetura completa. Mas ela nunca tinha rodado de ponta a ponta. O motivo não era falta de modelo ou de GPU. Era três bugs de integração que se empilhavam e faziam o ciclo morrer silenciosamente antes de fechar.

O ciclo que não fechava

O ciclo M3 é simples na intenção: THINK, EVALUATE, DECIDE, OBSERVE, repetir até o agente decidir que terminou. A função que orquestra isso chama o decisor a cada passo. O problema é que o decisor tinha três defeitos encadeados, cada um sozinho suficiente para travar tudo.

As ferramentas que uso neste projeto ficam na loja de skills — a primeira é grátis.

O primeiro: a avaliação chamava o guardião com uma assinatura que não existia. O código passava tokens, mas a API nativa espera estado oculto, dimensão e passo. A chamada lançava exceção, a exceção era engolida, o alinhamento ficava travado em 0.5 e, como o limiar de resposta é 0.75, o agente nunca declarava DONE. O ciclo rodava para sempre ou abortava sem explicação.

O segundo: o parser de streaming acumula internamente. Quando o ciclo reprocessava o transcript a cada iteração, o mesmo answer era emitido de novo. A correção foi chamar reset antes de cada parse, garantindo uma única resposta por ciclo.

O terceiro era trivial e mortal: o servidor não encontrava o binding nativo, porque ele vive num diretório de testes, não onde o servidor esperava. Três linhas de path resolution resolvem, mas sem elas nada carregava.

Com os três corrigidos, o ciclo fecha em DONE no segundo passo quando o guardião está saudável, e para no breaker de ciclos máximos quando está em drift. Prova com modelo e guardião mockados, sem GPU.

O motor tinha um NameError escondido

Antes mesmo de chegar ao agente, havia um defeito mais básico. O método de decisão usava uma variável que só existia no escopo da avaliação. No caminho de chamada de ferramenta, isso estourava NameError. E no modelo, a função de passo forward usava um parâmetro renomeado mas o corpo ainda referenciava o nome antigo. Qualquer chamada falhava na primeira linha.

Uma auditoria do núcleo mostrou que os módulos mais pesados do projeto, somando mais de mil e seiscentas linhas, não eram sequer importados pelo servidor. O código existia, estava testado, mas não estava no caminho de execução. Foi o tipo de achado que só aparece quando alguém lê o grafo de imports em vez de assumir que o que compila roda.

Scheduled sampling: ensinar o modelo a não se afogar

Paralelamente ao agente, o treino SFT ganhou scheduled sampling. A ideia: uma segunda passagem onde o modelo gera os primeiros tokens da resposta com o próprio argmax e calcula a perda contra o label dourado. Quando o parâmetro de probabilidade é zero, a receita antiga é reproduzida bit a bit. É uma forma de expor o modelo ao próprio erro durante o treino, sem mudar o pipeline.

O segundo problema era de agenda, não de algoritmo. O cosine annealing usava o total de passos como horizonte. Dobrar o orçamento de treino dobrava o trecho de learning rate alto. Um run recente tinha 1.38e-5 no passo 400 contra 3.8e-6 no run anterior, degradou e abortou. A correção desacopla o horizonte: quando definido, o cosine termina no passo dado e o resto fica no piso de learning rate. Ausente, comportamento idêntico ao antigo.

Medir dois runs no mesmo tronco

Validar se um run é melhor que outro exigia comparar checkpoints. O problema: cada checkpoint guarda só os deltas treináveis, não o modelo inteiro. Comparar dois checkpoints em modelos diferentes ou holdouts diferentes não prova nada.

O comparador reconstroi o tronco uma vez, aplica o primeiro checkpoint, mede, zera os deltas e aplica o segundo. Os dois runs são medidos no mesmo modelo e no mesmo holdout. Mede held-out, validação em português e validação em inglês, com o tronco puro como linha de base e o drift do inglês calculado contra ele. Um detalhe de portabilidade: importar o módulo num harness não deve exigir um diretório de mirror que pode não existir. Agora tolera.

O que mudou

O agente deixou de ser arquitetura no papel. O ciclo fecha. O decisor decide. O motor não estoura NameError. O treino não se afoga quando o orçamento cresce. E a validação compara runs de forma justa.

# Verificar o ciclo M3 com modelo e guardião mockados
python3 -m pytest tests/ -k agent -q

# Comparar dois checkpoints no mesmo tronco
python3 scripts/compare_ckpts.py --a run_a.pt --b run_b.pt

Próximo passo: ligar o ciclo M3 a uma interface real e medir se o agente fecha tarefas simples sem intervenção humana. A arquitetura está pronta; falta a prova de uso.

Leia também

Mesmo projeto, mesma trilha — as histórias se conectam.

Próximo passo

Se isto te serviu, tem mais: escrevo sobre o mesmo assunto nos outros projetos, e a loja tem as ferramentas que eu uso aqui.

Sem spam, descadastro de um clique. Também estou aqui: Loja de skills · Portfólio