Documentação

Executando uma sessão de pesquisa sem supervisão

Este é o programa de operação de uma sessão de pesquisa que roda sem um humano observando: uma sessão durante a noite, ou uma passagem longa durante o dia. Ele substitui os prompts por noite (os programas da rodada 6 e da noite 3, mantidos na tag git research-archive-2026-09-24 sob docs/prompts/) e incorpora o que deu errado nessas noites. As regras de pesquisa que ele aplica estão no PLAN.md, “Standing rules for every round”; os comandos estão no AGENTS.md.

Uma sessão sobe a encosta e confirma sob regras registradas e deixa um registro com que Jared pode agir. Ela não publica.

1. Início

Gaste os primeiros 30 minutos lendo, não lançando.

  1. AGENTS.md, todo ele (comandos, suítes congeladas, locais canônicos, configurações do Modal).
  2. PLAN.md: onde estamos, o que aprendemos, as regras permanentes, a política de dados e Next. Para um achado sobre o qual você quer construir, leia a sua evidência no arquivo: git show research-archive-2026-09-24:PLAN.md.
  3. As skills em .agents/skills/: kev-modal-study (lançar, observar e puxar trabalho de GPU; leia os seus Gotchas), kev-verify (provar que uma mudança de código não tem regressão), kev-pr-description (antes de qualquer PR), thermonuclear-code-review.
  4. kev/rounds.py (a sua docstring é o esquema da spec), a spec passada mais próxima em experiments/rounds/, e kev/autoresearch.py (session).
  5. A própria passagem de bastão: autorização (dólares do Modal, dólares do AI Gateway), o que está no escopo, o que precisa do Jared.

Depois configure:

  • Trabalhe em uma worktree em um ramo de pesquisa (git worktree add -b research/<session> /tmp/kev-<session> origin/main). Faça push depois de cada commit para que nada se perca se a máquina dormir. Código destinado ao main passa pelo seu próprio PR revisado.
  • Leia uv run modal billing summary --json e registre metered_cost como a linha de base no arquivo de estado (seção 6).
  • Se uma sessão anterior deixou um arquivo de estado, leia-o primeiro e retome a partir dele; trabalhos destacados do Modal continuam rodando sem você.

2. Orçamentos e a regra de gasto

  • A autorização é o total da sessão, contando tudo que ainda está rodando. Antes de todo lançamento, leia o custo medido de novo e não lance se (metered_now - baseline) + sum(admission bounds of everything still running) >= authorization.
  • O limite de admissão de um estudo é impresso no lançamento e salvo em runs/<study>.spawn.json; o limite de uma chamada de benchmark é compute_bound(gpu, timeout, trials) (kev/budget.py). O budget de estudo de uma spec deve ser pelo menos o seu limite (kev.rounds validate confere; modal_app.admit_study recusa um estudo acima do seu orçamento antes de qualquer coisa rodar, e um estudo é limitado a $250 e 28,800 s).
  • Mantenha uma reserva (cerca de 10% da autorização) que nenhuma fase planeja usar: as leituras de faturamento atrasam e são revisadas, e os limites de admissão superestimam muito as leituras (um lote de leitura carrega o timeout do seu trabalho mais lento).
  • Registre toda leitura com o seu horário UTC no arquivo de estado. O gasto do AI Gateway (leituras de referência do Jev, juízes de rótulo) tem o seu próprio teto, aplicado pelo script que o gasta, e é registrado em runs/<name>/usage.json.
  • O limite de gasto do espaço de trabalho do Modal só pode ser aumentado pelo painel; atingi-lo mata contêineres em execução no meio do treinamento.

3. Registre uma rodada

Uma rodada é uma seção do PLAN.md mais uma spec, commitadas juntas antes de qualquer treinamento ou leitura.

  1. Escreva a seção do PLAN.md: o porquê (a lacuna medida e a sua evidência), os dados (congelados primeiro, com manifestos), os braços, a regra (primária, guardas com limiares dimensionados para cada suíte, rank), os estágios de confirmação e o orçamento. Use as regras permanentes; não invente uma estatística nova para uma rodada.
  2. Escreva experiments/rounds/r<N>.json copiando a spec passada mais próxima (r15 para um delta conjunto, r17 para um 27B, r10 para uma rodada de habilidades, r20 para braços post-hoc sem treinamento: um pool de temperatura, checkpoints interpolados; r23 para combinações em direção a outro checkpoint, cujos braços nomeiam trained_on para o treinamento dos dois extremos). Deixe de fora "archive": essa chave marca as rodadas registradas 5-18. Todo arquivo de plano que a spec nomeia, e toda leitura pai que a sua regra precisa, deve existir neste checkout; se faltar uma leitura a um pai, launch-reads <spec> --parents a cria. Descarte toda leitura de uma suíte removida (kev.suite.REMOVED_SUITES, com o motivo): evals/external/scienthoon-v1 foi removida em 2026-09-27, então a partir da rodada 23 a leitura, o painel e o guarda do scienthoon saem; evals/external/wanli-v2 e typesafe-v1 foram removidas em 2026-09-30, então a partir da rodada 27 as suas leituras também saem, e o SemIf é a única leitura externa restante (apenas relatório). Os externos agrupados não são um portão: a regra auditada da rodada 24, que as rodadas 23-26 seguem, reportou SemIf, WANLI-v2 e TypeSafe como painéis opcionais. validate e launch recusam uma rodada depois da última rodada da suíte que ainda a nomeia.
  3. Dados novos são um diretório novo em evals/ com um manifest.json (sha256 por arquivo, hashes das entradas). Sob a política de dados de SFT (PLAN.md) corpora privados mantêm apenas o manifesto no git, com uma entrada "mirror" apontando para o conjunto de dados privado.
  4. OBRIGATÓRIO: toda temperatura servida ou entregue vem de um pool de conjuntos de dados reservados, nunca de uma partição do corpus de treinamento. Uma rodada que lê calibração (ECE, Brier, erros confiantes, cobertura) registra um pool temperature para os seus braços (copie o r20: as oito fontes públicas reservadas da partição de calibração do transfer-r3 + o MMLU-Pro do transfer-v9), e um release entrega a temperatura que o scripts/calibrate_checkpoint.py ajusta no mesmo pool. Os itens reservados das fontes de treinamento (as partições calibration / development de uma suíte de treinamento) estão dentro da distribuição: a rodada 19 serviu os seus braços de SFT em T 0.955 ajustada nas linhas de desenvolvimento do sft-v1 e falhou em todo critério de calibração (breadth-v1 ECE 0.059); o pool de conjuntos de dados reservados da rodada 20 deu 0.0085 no mesmo checkpoint. O que aplica isso:
    • A partir da rodada 21, kev.rounds validate e launch recusam uma rodada cuja regra ou confirmação tenha um critério que a temperatura move (ECE, Brier, NLL, erros confiantes, cobertura; qualquer coisa menos acurácia) e nenhum pool temperature. Rodadas <= 20 apenas imprimem um !!! warning por braço treinado em um corpus de treinamento, então as suas specs registradas ainda validam.
    • kev.rounds validate recusa uma leitura de pool que (a) seja a suíte de treinamento de um braço, um componente dela (inputs.components do sft-v1) ou a suíte data do seu plano, (b) agrupe uma fonte em que algum braço treinou, ou (c) leia a partição calibration ou development de qualquer corpus de treinamento; ele também recusa um pool que não consegue conferir (um braço cujo treinamento é desconhecido, uma suíte sem manifesto ou fontes listadas). Braços de checkpoint sem uma tentativa podem nomear trained_on.
    • O read-out registra o temperature_source de cada braço; a tabela imprime !!! para um braço servido nas linhas de desenvolvimento do trial de um corpus de treinamento (as rodadas 5-19 todas foram; de agora em diante uma temperatura assim é apenas triagem).
    • scripts/calibrate_checkpoint.py recusa os mesmos conjuntos de ajuste (conferidos contra a suíte de treinamento do head.pt); --allow-in-distribution é só para reproduzir um ajuste antigo, e é registrado em head.pt["temperature_fit"].
    • A temperatura dentro do trial de uma tentativa (calibration_fit do result.json) diz role: in-trial screening ... not a served or shipped temperature.
    • Os pais são servidos na temperatura ajustada nas linhas de desenvolvimento do seu trial (para o Kev-27B essa é o seu 1.38 entregue, ajustado nas mesmas linhas); o read-out registra isso e a T do head.pt entregue (parent_temperature_source), e validate avisa quando os dois diferem por mais de 0.05 nas linhas de um corpus de treinamento.
    • A checagem de disjunção é por NOME de fonte (nominal, não semântico): duas suítes carregando o mesmo conjunto de dados sob nomes diferentes passam. Então um pool deve usar fontes que sejam eval-only no Kev por construção, como as oito fontes públicas reservadas do transfer-r3 e o MMLU-Pro do transfer-v9. A allowlist sources de uma leitura de pool deve nomear fontes que a sua suíte lista (um erro de digitação é um problema), e treinamento que o verificador não consegue listar (um arquivo data fora de evals/, um manifesto sem fontes) é um problema para uma rodada nova.
    • calibrate_checkpoint.py --temperature T (um valor manual, nada ajustado) precisa de --reason, registrado em head.pt["temperature_fit"] (por exemplo, “copied from the pool fit of runs/r20-readout”).
  5. uv run python -m kev.rounds validate experiments/rounds/r<N>.json (adicione --partitions para verificar as partições) até imprimir ok. Faça commit da seção do PLAN e da spec em um único commit, e push. Esse horário de commit é o horário de registro.

4. Execute de ponta a ponta

KEV_GPU=H200 uv run modal deploy modal_app.py                     # after any change to kev/*.py or any new file under evals/
uv run python -m kev.rounds launch experiments/rounds/r<N>.json   # one ::study per study, 60 s apart, logs in runs/<study>.log
caffeinate -i nohup uv run python -m kev.rounds watch experiments/rounds/r<N>.json > runs/r<N>.watch.log 2>&1 &
  • Nos primeiros cinco minutos de todo estudo, conte passos do otimizador por minuto em modal container logs <id> e projete o tempo de parede contra o timeout (ep0 step N/M: M é sobre todas as épocas). Um contêiner que estoura o tempo não salva nada; cancele (FunctionCall.from_id(cid).cancel()) e relance sob um nome de estudo novo com menos registros ou um timeout maior.
  • O watch consulta as tentativas geradas, puxa cada estudo terminado (uma puxada por estudo por vez), lança as leituras daquele braço uma vez (uma chamada ::benchmarks em lote por braço, 60 s de intervalo), espera por elas e escreve runs/r<N>-readout/round<N>.json e uma tabela. Ele é reiniciável: o estado está em runs/<study>.watch.json e a intenção de lançamento em runs/r<N>-reads-<arm>.json. Manualmente: launch-reads <spec> [--arms a,b] [--parents] [--dry-run], readout <spec>.
  • Escreva o read-out na seção do PLAN: todo braço, todo critério com o seu intervalo, o veredicto e o que falhou.
  • A confirmação é deliberada, nunca automática. Para o candidato que o read-out nomeia, escreva a escolha no PLAN.md e faça commit, depois por estágio: launch-reads <spec> --stage <stage> --arm <arm>, depois confirm <spec> --stage <stage> --arm <arm> (→ runs/r<N>-verdict/<size>-<stage>.json). Teste painéis antes da leitura bloqueada. Uma leitura cada, sem exceções.
  • Várias rodadas registradas em sequência sob um teto: uv run python -m kev.autoresearch session experiments/rounds/r19.json [...] --spend-start <baseline> --spend-cap <authorization>. Ele valida, lança e observa cada rodada até o seu read-out, para antes de uma rodada cujos orçamentos ultrapassariam o teto, acrescenta a runs/autoresearch-sessions.jsonl e imprime os comandos de confirmação; ele nunca os executa. kev.autoresearch leaderboard atualiza runs/leaderboard.{jsonl,md} (não commitado), compare pareia tentativas contra uma referência na acurácia de transferência, release-check --study <name> tria toda configuração daquele estudo (cada configuração passa apenas se todas as suas sementes passarem nos seus portões).

5. O que uma sessão pode e não pode tocar

Pode: escrever specs, planos e seções do PLAN.md; construir dados congelados novos em diretórios novos; lançar estudos e leituras através do modal_app.py; mudar scripts e constantes de infraestrutura do modal_app.py; abrir PRs para código que pertence ao main.

Não pode, sem o OK explícito do Jared:

  • publicar ou mudar qualquer coisa no Hub (kev.publish, hf upload, hf repos tag, scripts/publish_space.sh, um head.pt publicado), tornar um repositório privado público, ou implantar um endpoint público;
  • commit no main, force-push, ou fazer merge de um PR (o código chega ao main por PRs revisados, com squash-merge, e CI verde);
  • editar qualquer coisa que exista em evals/ (congelado), ou o avaliador: kev/experiment.py: EVALUATOR_FILES, os portões, kev/metrics.py, a leitura pareada do kev/rounds.py. Uma mudança necessária no avaliador é o seu próprio PR, verificado com o kev-verify e o tests/test_rounds.py, antes de qualquer rodada depender dela;
  • passar --allow-test ou rodar locked_test fora de um estágio de confirmação registrado;
  • colocar qualquer saída de Jev, ou qualquer geração de modelo fechado, em dados de treinamento;
  • treinar localmente (um Mac de 32 GB não comporta esses modelos) ou rodar dois processos de treinamento em uma máquina;
  • apagar um checkpoint ou um snapshot do volume de runs (modal volume rm, shutil.rmtree em um contêiner), ou desligar os snapshots de uma tentativa de pesos completos ("snapshot_fractions": "none") em uma spec registrada. Tentativas de pesos completos mantêm snapshots em 0.25, 0.5 e 0.75 dos seus passos (kev.experiment.SNAPSHOT_FRACTIONS) para que uma leitura consiga achar o melhor ponto de uma execução depois que ela termina: a rodada 19 não conseguiu, porque o único estado intermediário era um ponto de retomada, apagado quando a execução terminou, e o melhor checkpoint do AutoJev estava em 0.7 de época. Os snapshots de um 27B são ~154 GB de volume por tentativa; o espaço é decisão do Jared, não da sessão. Os snapshots ficam no volume de runs (primário); um espelho privado no Hub (snapshot_hub_repo em um plano, ou modal_app.py::mirror_snapshots) é armazenamento de longo prazo para um checkpoint que vale a pena guardar, não um substituto: espelhar checkpoints de 27B (~51 GB cada, em um repositório privado como jaredpalmer/kev-snapshots) também é decisão do Jared, e nunca para um repositório público.

Se um braço estiver bloqueado (autenticação, um limite de gasto, um deploy que não vai funcionar em 30 minutos), anote o que aconteceu e passe para o braço seguinte. Não espere por um humano.

6. Resiliência

  • Arquivo de estado runs/<session>-state.json (runs/ é gitignored; faça git add -f no ramo de pesquisa): linha de base e autorização, leituras de gasto com horários UTC, todo estudo com os seus ids de spawn, limite e status, leituras lançadas e puxadas, candidatos, PRs, decisões pendentes. Atualize-o depois de todo lançamento, puxada e leitura, e faça commit dele com a seção do PLAN.
  • Trabalhos destacados. Os estudos geram no app implantado e sobrevivem ao cliente local; um erro local depois do study pode ainda ter gerado tentativas, então rode modal container list antes de relançar, e nunca relance sob o mesmo nome de estudo. Sondas e benchmarks rodam com --detach.
  • Os observadores são processos locais e morrem com a máquina ou a rede. Rode-os sob nohup e caffeinate; reinicie o watch depois de qualquer interrupção (ele retoma a partir do seu estado). Ele tenta de novo erros de DNS e de conexão por conta própria; a exceção de uma tentativa é uma falha e é reportada.
  • Tentativas de pesos completos que estouram o tempo são continuadas pelo observador, não pelo Modal. As tentativas geram com as retentativas do Modal desligadas; quando a chamada de uma tentativa de pesos completos termina pelo seu timeout, o watch roda modal_app.py::resume --trial <label>, que gera a tentativa seguinte (ela continua a partir do último ponto de retomada commitado) com a GPU e o timeout com que o estudo foi admitido e a registra em runs/<study>.spawn.json (attempts, no máximo 1 + kev.budget.FULL_FT_RETRIES por tentativa, a contagem com que o limite de admissão foi calculado; uma tentativa cuja chamada atual ainda está rodando nunca é continuada). Enquanto o observador estiver fora, nada é continuado: reinicie-o e ele retoma o timeout. Porquê: o Modal cobrou cada tentativa que estourou o tempo duas vezes (o timeout, depois a tarefa que ele matou 30 s depois), então Retries(2) deu à tentativa da rodada 22 duas das suas três tentativas, e a retentativa da morte pode iniciar ao lado de uma tentativa em execução (scripts/modal_retry_probe.py). Um estudo gerado antes do livro-razão não tem contagem: resume --trial <label> --beyond-bound o continua à mão, fora de qualquer limite, e diz isso. Duas tentativas nunca compartilham um trial: cada uma é registrada como pendente antes do seu spawn, e cada uma mantém um lease no volume kev-leases (batimento cardíaco a cada minuto); uma tentativa nova recusa enquanto o lease de outra está fresco, e uma continuação espera até kev.budget.LEASE_STALE (15 min) depois do último batimento cardíaco de uma tentativa morta antes de gerar.
  • Quedas de rede matam clientes locais, não o trabalho remoto: uma leitura cujo cliente morreu normalmente já terminou no Modal; puxe o seu diretório do volume (modal volume get kev-runs /<name> runs/<name>) em vez de relançá-la.
  • Um benchmark ou sonda que falha deixa o seu diretório no volume; tente de novo sob um nome novo.

7. Relatório

No fim da sessão (e no arquivo de estado conforme ela avança):

  • A seção do PLAN.md de cada rodada carrega o registo, a tabela de read-out, os resultados de confirmação e o veredicto, negativo ou não, com os caminhos dos relatórios.
  • Atualize o “Where we stand” do PLAN.md (candidatos publicados e confirmados, trabalhos em execução, gasto) e o “What we have learned” se um achado mudou; acrescente cada rodada à tabela Record.
  • Um resumo da sessão no PLAN.md: gasto (linha de base, leitura final, limites em execução), o que está pendente no Modal com os comandos exatos para terminá-lo, incidentes, e no máximo três próximos passos com a sua evidência.
  • Todo número carrega checkpoint, suíte e partição, n e caminho do relatório; faça commit dos read-outs e veredictos de que os números vêm (o .gitignore mantém relatórios, não despejos de previsão; adicione uma regra para diretórios de read-out novos).
  • Carimbos de relógio: os horários de registro e de resultado são horários de commit. Não escreva um horário em um título antes de ele acontecer; o scratchpad da noite 3 o fez, e os seus carimbos não puderam ser usados.

8. Armadilhas conhecidas

  • Limite de criação de app do Modal. Mais de cerca de três modal run destacados em um minuto falham com “App create rate limit exceeded” e nada roda. O kev.rounds escalona os lançamentos com 60 s de intervalo e agrupa as leituras de um braço em uma chamada; faça o mesmo à mão.
  • repo@sha em trabalhos de benchmark costumava deslocar todo campo de run@suite@name@flags; o modal_app.parse_jobs agora analisa da direita, então revisões fixadas do Hub são seguras. Suítes e nomes não podem conter @ ou ,.
  • Uma puxada por estudo. Puxadas concorrentes do mesmo estudo apagavam os diretórios de tentativa umas das outras; o pull_study agora mantém um lock por estudo. Uma puxada enquanto tentativas ainda rodam é segura e atualiza apenas tentativas inacabadas.
  • As puxadas deixam pesos completos no volume. O ::pull (e o watch) pula shards de pesos completos (model*.safetensors, ~51 GB por checkpoint ou snapshot de 27B) e pontos de retomada; todo o resto desce (resultados, linhas, head.pt, configs). Leia um checkpoint ou um snapshot no volume: ::benchmarks --jobs "/runs/<study>/<trial>/snapshots/step-<N>/checkpoint@<suite>@<name>". O ::pull --weights copia os shards quando algo local realmente precisa deles.
  • Implantar depois dos dados. A imagem copia evals/; o lançador só confere hashes de kev/*.py, então uma tentativa cujo arquivo de dados foi adicionado depois do deploy falha dentro do contêiner. --gpu H200 no study precisa de um app implantado com KEV_GPU=H200.
  • 27B. Só H200 (backbone bf16, 55 GB residentes); timeouts de estudo de até 28,800 s (um delta de habilidades de 1 época em lr 2e-5 rodou cerca de 8.8 s por passo do otimizador); leituras em fp32 cerca de três vezes as de um 9B (spec read_timeout: {"27b": 14400}); a leitura bloqueada precisa de --timeout 14400 --memory-mb 131072 (locked_args da spec) em uma H200 (a GPU vem do gpu da spec / do app implantado, ou --gpu H200 à mão). Toda tentativa de pesos bf16 falha no portão isolation_and_packing dentro do trial (uma checagem em fp32); leia os resultados das linhas e meça o isolamento servido em bf16 separadamente.
  • Nome do locked_test. Quando um portão de triagem dentro do trial falha, a ferramenta exige o sufixo -ungated (kev-4b-r8-ungated); o veredicto ainda segue a regra registrada.
  • Timeouts de leitura por suíte. O modal_app.READ_TIMEOUTS define painéis de estado longo em 7,200 s, documentos em 5,400 s, transfer-v9 em 3,600 s, e o resto em 1,800 s. Um --timeout global infla o limite de admissão de todo trabalho do lote.
  • Admissão de orçamento. Um lançamento acima do seu --budget sai antes de qualquer coisa rodar; relance com um orçamento pelo menos o limite impresso.
  • Servidores externos são single-flight. O servidor do AutoJev respondia uma requisição por vez (HTTP 529 enquanto ocupado); sonde um endpoint estrangeiro antes de um longo kev.benchmark --remote e defina --remote-concurrency para o que ele aguentar. Conte as requisições que ele rejeita (por exemplo, um 422 além do seu contexto) como cobertura, nunca as descarte silenciosamente.
  • Temperatura: entregue vs dentro do trial. O result.json de uma tentativa e o seu resumo bloqueado são pontuados no ajuste dentro do trial; um release entrega a T que o scripts/calibrate_checkpoint.py gravou no head.pt. O primeiro confronto direto do AutoJev serviu o Kev-27B na T 1.19 dentro do trial em vez da 1.38 entregue e teve de ser corrigido. Diga qual T cada número usa, e onde ela foi ajustada (seção 3, regra 4: conjuntos de dados reservados, nunca as partições do próprio corpus de treinamento).
  • Calibração de contexto longo. Um painel com "by_length": true reporta acurácia, ECE, Brier e erros confiantes por bucket de tokens de estado (abaixo de 8k até 64k+, e as caudas 8k+/16k+/32k+), e um critério pode portar um (long.ece_16k_plus.candidate <= 0.05). Os tokens são contados a partir dos registros de suíte das leituras, então os dois lados compartilham buckets.
  • Correções de suíte sem uma versão nova de suíte. Um painel pode descartar fontes, tarefas ou uma lista (privada, registrada por hash) de ids ou fontes dos dois lados (exclude_sources, exclude_tasks, exclude_file), e um painel apenas de relatório é marcado "optional": true para que uma leitura de relatório ausente nunca deixe um candidato incompleto. Escolha as exclusões pela validade dos rótulos antes de qualquer read-out sob elas (a rodada 24 as tirou da auditoria de 2026-09-27), e nunca faça commit de uma lista privada.
  • Capacidade do espaço de trabalho. O espaço de trabalho já rodou no máximo cerca de dez contêineres de GPU ao mesmo tempo; contêineres pendentes são capacidade, não um bug, então não os relance.
  • Suítes pequenas. Um portão em 89 ou 144 perguntas não consegue resolver um piso de 2-3 pp; porte-as através de um painel agrupado.
  • Leituras do Jev falham no meio da execução com 503s do gateway; rode a leitura inteira de novo sob um nome novo em vez de costurar linhas parciais.
  • Dados de alvo suave. Ao escrever um construtor, confira alguns registros a olho: o target soma 1 e a massa do rótulo é pelo menos 0.5, a menos que o registro seja impossível de saber (o kev.data.none_pair uma vez treinou zero massa em alvos suaves, corrigido no #60).
  • Redirecione a saída de modal run ...::study para um arquivo de log; um filtro pode esconder o SystemExit que explica por que nada foi lançado.