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.
AGENTS.md, todo ele (comandos, suítes congeladas, locais canônicos, configurações do Modal).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.- 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. kev/rounds.py(a sua docstring é o esquema da spec), a spec passada mais próxima emexperiments/rounds/, ekev/autoresearch.py(session).- 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 --jsone registremetered_costcomo 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). Obudgetde estudo de uma spec deve ser pelo menos o seu limite (kev.rounds validateconfere;modal_app.admit_studyrecusa 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.
- 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.
- Escreva
experiments/rounds/r<N>.jsoncopiando 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 nomeiamtrained_onpara 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> --parentsa cria. Descarte toda leitura de uma suíte removida (kev.suite.REMOVED_SUITES, com o motivo):evals/external/scienthoon-v1foi 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-v2etypesafe-v1foram 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.validateelaunchrecusam uma rodada depois da última rodada da suíte que ainda a nomeia. - Dados novos são um diretório novo em
evals/com ummanifest.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. - 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
temperaturepara 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 oscripts/calibrate_checkpoint.pyajusta no mesmo pool. Os itens reservados das fontes de treinamento (as partiçõescalibration/developmentde 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 dosft-v1e 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 validateelaunchrecusam 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 pooltemperature. Rodadas <= 20 apenas imprimem um!!! warningpor braço treinado em um corpus de treinamento, então as suas specs registradas ainda validam. kev.rounds validaterecusa uma leitura de pool que (a) seja a suíte de treinamento de um braço, um componente dela (inputs.componentsdo sft-v1) ou a suítedatado seu plano, (b) agrupe uma fonte em que algum braço treinou, ou (c) leia a partiçãocalibrationoudevelopmentde 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 nomeartrained_on.- O read-out registra o
temperature_sourcede 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.pyrecusa 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 emhead.pt["temperature_fit"].- A temperatura dentro do trial de uma tentativa (
calibration_fitdoresult.json) dizrole: 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), evalidateavisa 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
sourcesde 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 arquivodatafora deevals/, um manifesto sem fontes) é um problema para uma rodada nova. calibrate_checkpoint.py --temperature T(um valor manual, nada ajustado) precisa de--reason, registrado emhead.pt["temperature_fit"](por exemplo, “copied from the pool fit of runs/r20-readout”).
- A partir da rodada 21,
uv run python -m kev.rounds validate experiments/rounds/r<N>.json(adicione--partitionspara verificar as partições) até imprimirok. 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
watchconsulta as tentativas geradas, puxa cada estudo terminado (uma puxada por estudo por vez), lança as leituras daquele braço uma vez (uma chamada::benchmarksem lote por braço, 60 s de intervalo), espera por elas e escreveruns/r<N>-readout/round<N>.jsone uma tabela. Ele é reiniciável: o estado está emruns/<study>.watch.jsone a intenção de lançamento emruns/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>, depoisconfirm <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 aruns/autoresearch-sessions.jsonle imprime os comandos de confirmação; ele nunca os executa.kev.autoresearch leaderboardatualizaruns/leaderboard.{jsonl,md}(não commitado),comparepareia 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, umhead.ptpublicado), 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 dokev/rounds.py. Uma mudança necessária no avaliador é o seu próprio PR, verificado com okev-verifye otests/test_rounds.py, antes de qualquer rodada depender dela; - passar
--allow-testou rodarlocked_testfora 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.rmtreeem 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_repoem um plano, oumodal_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 comojaredpalmer/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çagit add -fno 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
studypode ainda ter gerado tentativas, então rodemodal container listantes 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
nohupecaffeinate; reinicie owatchdepois 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
watchrodamodal_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 emruns/<study>.spawn.json(attempts, no máximo 1 +kev.budget.FULL_FT_RETRIESpor 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ãoRetries(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-boundo 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 volumekev-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
.gitignoremanté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 rundestacados em um minuto falham com “App create rate limit exceeded” e nada roda. Okev.roundsescalona 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@shaem trabalhos de benchmark costumava deslocar todo campo derun@suite@name@flags; omodal_app.parse_jobsagora 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_studyagora 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 owatch) 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 --weightscopia os shards quando algo local realmente precisa deles. - Implantar depois dos dados. A imagem copia
evals/; o lançador só confere hashes dekev/*.py, então uma tentativa cujo arquivo de dados foi adicionado depois do deploy falha dentro do contêiner.--gpu H200nostudyprecisa de um app implantado comKEV_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_argsda spec) em uma H200 (a GPU vem dogpuda spec / do app implantado, ou--gpu H200à mão). Toda tentativa de pesos bf16 falha no portãoisolation_and_packingdentro 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_TIMEOUTSdefine 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--timeoutglobal infla o limite de admissão de todo trabalho do lote. - Admissão de orçamento. Um lançamento acima do seu
--budgetsai 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 --remotee defina--remote-concurrencypara 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.jsonde uma tentativa e o seu resumo bloqueado são pontuados no ajuste dentro do trial; um release entrega a T que oscripts/calibrate_checkpoint.pygravou nohead.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": truereporta 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": truepara 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
targetsoma 1 e a massa do rótulo é pelo menos 0.5, a menos que o registro seja impossível de saber (okev.data.none_pairuma vez treinou zero massa em alvos suaves, corrigido no #60). - Redirecione a saída de
modal run ...::studypara um arquivo de log; um filtro pode esconder oSystemExitque explica por que nada foi lançado.