Executar uma sessão de investigação sem supervisão
Este é o programa operativo de uma sessão de investigação que corre sem ninguém a vigiar: uma sessão noturna, ou uma longa passagem de testemunho durante o dia. Substitui os prompts por noite (os programas da ronda 6 e da noite 3, mantidos na tag git research-archive-2026-09-24 em docs/prompts/) e incorpora o que correu mal nessas noites. As regras de investigação que aplica estão no PLAN.md, “Standing rules for every round”; os comandos estão no AGENTS.md.
Uma sessão faz hill-climbing e confirma segundo regras registadas e deixa um registo sobre o qual o Jared pode agir. Não publica.
1. Arrancar
Passa os primeiros 30 minutos a ler, não a lançar.
AGENTS.md, todo (comandos, suites congeladas, localizações canónicas, definições do Modal).PLAN.md: onde estamos, o que aprendemos, as regras permanentes, a política de dados e Next. Para um resultado sobre o qual queres construir, lê a sua prova no arquivo:git show research-archive-2026-09-24:PLAN.md.- As skills em
.agents/skills/:kev-modal-study(lançar, vigiar e recolher trabalho de GPU; lê os seus Gotchas),kev-verify(provar que uma alteração de código não tem regressões),kev-pr-description(antes de qualquer PR),thermonuclear-code-review. kev/rounds.py(a sua docstring é o esquema da especificação), a especificação passada mais próxima emexperiments/rounds/, ekev/autoresearch.py(session).- A própria passagem de testemunho: autorização (dólares do Modal, dólares do AI Gateway), o que está no âmbito, o que precisa do Jared.
Depois prepara:
- Trabalha numa worktree num ramo de investigação (
git worktree add -b research/<session> /tmp/kev-<session> origin/main). Faz push após cada commit para que nada se perca se a máquina dormir. O código destinado ao main passa pelo seu próprio PR revisto. - Lê
uv run modal billing summary --jsone registrametered_costcomo a linha de base no ficheiro de estado (secção 6). - Se uma sessão anterior deixou um ficheiro de estado, lê-o primeiro e retoma a partir dele; os trabalhos destacados do Modal continuam a correr sem ti.
2. Orçamentos e a regra de despesa
- A autorização é o total da sessão, contando tudo o que ainda está a correr. Antes de cada lançamento, lê de novo o custo medido e não lances 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 guardado em
runs/<study>.spawn.json; o limite de uma chamada de benchmark écompute_bound(gpu, timeout, trials)(kev/budget.py). Obudgetdo estudo de uma especificação tem de ser pelo menos o seu limite (okev.rounds validateverifica-o; omodal_app.admit_studyrecusa um estudo acima do seu orçamento antes de qualquer coisa correr, e um estudo tem um teto de $250 e 28,800 s). - Mantém uma reserva (cerca de 10 % da autorização) que nenhuma fase inclui no plano: as leituras de faturação atrasam-se e são revistas, e os limites de admissão sobrestimam muito as leituras (um lote de leitura carrega o timeout do seu trabalho mais lento).
- Regista cada leitura com a sua hora UTC no ficheiro de estado. A despesa do AI Gateway (leituras de referência do Jev, juízes de etiquetas) tem o seu próprio teto, imposto pelo script que a gasta, e é registada em
runs/<name>/usage.json. - O limite de despesa do espaço de trabalho do Modal só pode ser aumentado a partir do painel; atingi-lo mata os contentores em execução a meio do treino.
3. Registar uma ronda
Uma ronda é uma secção do PLAN.md mais uma especificação, commitadas em conjunto antes de qualquer treino ou leitura.
- Escreve a secção do PLAN.md: porquê (a lacuna medida e a sua prova), os dados (congelados primeiro, com manifestos), os braços, a regra (primária, guardas com limiares dimensionados a cada suite, classificação), as etapas de confirmação e o orçamento. Usa as regras permanentes; não inventes uma estatística nova para uma ronda.
- Escreve
experiments/rounds/r<N>.jsoncopiando a especificação passada mais próxima (r15 para um delta conjunto, r17 para um 27B, r10 para uma ronda de skills, r20 para braços post-hoc sem treino: um pool de temperaturas, checkpoints interpolados; r23 para misturas para outro checkpoint, cujos braços nomeiamtrained_onpara o treino de ambos os extremos). Omita"archive": essa chave marca as rondas registadas 5-18. Cada ficheiro de plano que a especificação nomeie, e cada leitura pai de que a sua regra precisa, têm de existir neste checkout; se faltar uma leitura a um pai,launch-reads <spec> --parentsfá-la. Elimina cada leitura de uma suite removida (kev.suite.REMOVED_SUITES, com o motivo):evals/external/scienthoon-v1foi removida em 2026-09-27, por isso a partir da ronda 23 a leitura, o painel e a guarda do scienthoon saem;evals/external/wanli-v2etypesafe-v1foram removidas em 2026-09-30, por isso a partir da ronda 27 as suas leituras também saem, e o SemIf é a única leitura externa que resta (apenas para reporte). Os externos agrupados não são uma guarda: a regra auditada da ronda 24, que as rondas 23-26 seguem, reportava o SemIf, o WANLI-v2 e o TypeSafe como painéis opcionais. Ovalidatee olaunchrecusam uma ronda que ainda nomeie uma suite depois da sua última ronda. - Dados novos são um novo diretório em
evals/com ummanifest.json(sha256 por ficheiro, hashes das entradas). Sob a política de dados de SFT (PLAN.md), os corpora privados mantêm apenas o manifesto no git, com uma entrada"mirror"a apontar para o conjunto de dados privado. - OBRIGATÓRIO: cada temperatura servida ou lançada vem de um pool de conjuntos de dados reservados, nunca de uma partição do corpus de treino. Uma ronda que lê calibração (ECE, Brier, erros confiantes, cobertura) registra um pool
temperaturepara os seus braços (copia 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 lançamento distribui a temperatura que oscripts/calibrate_checkpoint.pyajusta no mesmo pool. Os itens reservados das fontes de treino (as partiçõescalibration/developmentde uma suite de treino) estão em distribuição: a ronda 19 serviu os seus braços de SFT a T 0.955 ajustado em linhas de desenvolvimento dosft-v1e falhou todos os critérios de calibração (breadth-v1 ECE 0.059); o pool de conjuntos de dados reservados da ronda 20 deu 0.0085 no mesmo checkpoint. O que o impõe:- A partir da ronda 21, o
kev.rounds validatee olaunchrecusam uma ronda cuja regra ou confirmação tenha um critério que a temperatura move (ECE, Brier, NLL, erros confiantes, cobertura; tudo menos precisão) e nenhum pooltemperature. As rondas <= 20 apenas imprimem um!!! warningpor braço treinado num corpus de treino, por isso as suas especificações registadas ainda validam. - O
kev.rounds validaterecusa uma leitura de pool que (a) seja a suite de treino de um braço, um componente dela (osinputs.componentsdo sft-v1) ou a suitedatado seu plano, (b) agrupe uma fonte em que qualquer braço treinou, ou (c) leia a partiçãocalibrationoudevelopmentde qualquer corpus de treino; também recusa um pool que não consegue verificar (um braço cujo treino é desconhecido, uma suite sem manifesto ou fontes listadas). Os braços de checkpoint sem ensaio podem nomeartrained_on. - A leitura registra o
temperature_sourcede cada braço; a tabela imprime!!!para um braço servido nas linhas de desenvolvimento de um corpus de treino do seu ensaio (as rondas 5-19 foram todas; a partir de agora uma tal temperatura é apenas de triagem). - O
scripts/calibrate_checkpoint.pyrecusa os mesmos conjuntos de ajuste (verificados contra a suite de treino do head.pt); o--allow-in-distributionserve apenas para reproduzir um ajuste antigo, e é registado emhead.pt["temperature_fit"]. - A temperatura dentro do ensaio (o
calibration_fitdoresult.json) dizrole: in-trial screening ... not a served or shipped temperature. - Os pais são servidos à temperatura ajustada nas linhas de desenvolvimento do seu ensaio (para o Kev-27B é a sua 1.38 de distribuição, ajustada nas mesmas linhas); a leitura registra isso e a sua T de distribuição no head.pt (
parent_temperature_source), e ovalidateavisa quando as duas diferem em mais de 0.05 nas linhas de um corpus de treino. - A verificação de disjunção é por NOME de fonte (nominal, não semântica): duas suites que carreguem o mesmo conjunto de dados com nomes diferentes passam-na. Por isso um pool tem de usar fontes que são apenas de avaliação 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 tem de nomear fontes que a sua suite lista (um erro ortográfico é um problema), e um treino que o verificador não consegue listar (um ficheirodatafora deevals/, um manifesto sem fontes) é um problema para uma ronda nova. calibrate_checkpoint.py --temperature T(um valor manual, nada ajustado) precisa de--reason, registado emhead.pt["temperature_fit"](por exemplo, “copied from the pool fit of runs/r20-readout”).
- A partir da ronda 21, o
uv run python -m kev.rounds validate experiments/rounds/r<N>.json(acrescenta--partitionspara verificar as partições) até imprimirok. Commita a secção do PLAN e a especificação num único commit, faz push. Essa hora de commit é a hora de registo.
4. Correr 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 cada estudo, conta os passos do otimizador por minuto em
modal container logs <id>e projeta o tempo real contra o timeout (ep0 step N/M: M é sobre todas as épocas). Um contentor que esgota o tempo não guarda nada; cancela (FunctionCall.from_id(cid).cancel()) e relança com um novo nome de estudo, com menos registos ou um timeout maior. - O
watchconsulta os ensaios lançados, recolhe cada estudo terminado (uma recolha por estudo de cada vez), lança as leituras desse braço uma vez (uma chamada::benchmarksem lote por braço, com 60 s de intervalo), espera por elas e escreveruns/r<N>-readout/round<N>.jsone uma tabela. É reiniciável: o estado está emruns/<study>.watch.jsone a intenção de lançamento emruns/r<N>-reads-<arm>.json. À mão:launch-reads <spec> [--arms a,b] [--parents] [--dry-run],readout <spec>. - Escreve a leitura na secção do PLAN: cada braço, cada critério com o seu intervalo, o veredicto e o que falhou.
- A confirmação é deliberada, nunca automática. Para o candidato que a leitura nomeia, escreve a escolha no PLAN.md e commita-a, depois por etapa:
launch-reads <spec> --stage <stage> --arm <arm>, depoisconfirm <spec> --stage <stage> --arm <arm>(→runs/r<N>-verdict/<size>-<stage>.json). Testa os painéis antes da leitura bloqueada. Uma leitura cada, sem exceções. - Várias rondas registadas em sequência sob um teto:
uv run python -m kev.autoresearch session experiments/rounds/r19.json [...] --spend-start <baseline> --spend-cap <authorization>. Valida, lança e vigia cada ronda até à sua leitura, para antes de uma ronda cujos orçamentos passariam o teto, acrescenta aruns/autoresearch-sessions.jsonle imprime os comandos de confirmação; nunca os corre. Okev.autoresearch leaderboardatualizaruns/leaderboard.{jsonl,md}(não commitado), ocompareemparelha ensaios contra uma referência na precisão de transferência, orelease-check --study <name>tria cada configuração desse estudo (cada configuração passa só se todas as suas sementes passarem as suas guardas).
5. O que uma sessão pode e não pode tocar
Pode: escrever especificações, planos e secções do PLAN.md; construir dados congelados novos em diretórios novos; lançar estudos e leituras através do modal_app.py; alterar 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 alterar seja o que for no Hub (
kev.publish,hf upload,hf repos tag,scripts/publish_space.sh, umhead.ptlançado), tornar público um repositório privado, ou implementar um endpoint público; - fazer commit no main, force-push, ou fundir um PR (o código chega ao main através de PRs revistos e fundidos por squash com CI verde);
- editar seja o que for que exista em
evals/(congelado), ou o avaliador:kev/experiment.py: EVALUATOR_FILES, as guardas,kev/metrics.py, a leitura emparelhada dokev/rounds.py. Uma alteração necessária ao avaliador é o seu próprio PR, verificado com okev-verifye otests/test_rounds.py, antes de qualquer ronda depender dela; - passar
--allow-testou correr olocked_testfora de uma etapa de confirmação registada; - colocar qualquer saída do Jev, ou qualquer geração de modelo fechado, nos dados de treino;
- treinar localmente (um Mac de 32 GB não comporta estes modelos) ou correr dois processos de treino numa máquina;
- apagar um checkpoint ou um snapshot do volume de execuções (
modal volume rm,shutil.rmtreenum contentor), ou desativar os snapshots de um ensaio de pesos completos ("snapshot_fractions": "none") numa especificação registada. Os ensaios de pesos completos mantêm snapshots a 0.25, 0.5 e 0.75 dos seus passos (kev.experiment.SNAPSHOT_FRACTIONS) para que uma leitura possa encontrar o melhor ponto de uma execução depois de ela terminar: a ronda 19 não conseguiu, porque o único estado a meio da execução era um ponto de retoma, apagado quando a execução terminou, e o melhor checkpoint do AutoJev estava a 0.7 de época. Os snapshots de um 27B são ~154 GB de volume por ensaio; o espaço é decisão do Jared, não da sessão. Os snapshots vivem no volume de execuções (primário); um espelho privado no Hub (snapshot_hub_reponum 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, num 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 despesa, uma implementação que não funcionará em 30 minutos), escreve o que aconteceu e passa ao braço seguinte. Não esperes por um humano.
6. Resiliência
- Ficheiro de estado
runs/<session>-state.json(runs/ está no gitignore; fazgit add -fdele no ramo de investigação): linha de base e autorização, leituras de despesa com horas UTC, cada estudo com os seus ids de spawn, limite e estado, leituras lançadas e recolhidas, candidatos, PRs, decisões pendentes. Atualiza-o após cada lançamento, recolha e leitura, e commita-o com a secção do PLAN. - Trabalhos destacados. Os estudos são lançados na aplicação implementada e sobrevivem ao cliente local; um erro local após o
studypode ainda ter lançado ensaios, por isso corremodal container listantes de relançar, e nunca relances com o mesmo nome de estudo. As sondas e os benchmarks correm com--detach. - Os watchers são processos locais e morrem com a máquina ou a rede. Corre-os sob
nohupecaffeinate; reinicia owatchapós qualquer interrupção (retoma a partir do seu estado). Reintenta erros de DNS e de ligação por si próprio; uma exceção do próprio ensaio é uma falha e é reportada. - Os ensaios de pesos completos que esgotam o tempo são continuados pelo watcher, não pelo Modal. Os ensaios são lançados com as retentativas do Modal desligadas; quando a chamada de um ensaio de pesos completos termina pelo seu timeout, o
watchcorremodal_app.py::resume --trial <label>, que lança a tentativa seguinte (continua a partir do último ponto de retoma commitado) com a GPU e o timeout para que o estudo foi admitido e registra-o emruns/<study>.spawn.json(attempts, no máximo 1 +kev.budget.FULL_FT_RETRIESpor ensaio, a contagem com que o limite de admissão foi calculado; um ensaio cuja chamada atual ainda está a correr nunca é continuado). Enquanto o watcher está em baixo nada é continuado: reinicia-o e ele retoma o timeout. Porquê: o Modal cobrava cada tentativa que esgotava o tempo duas vezes (o timeout, e depois a tarefa que matava 30 s mais tarde), por issoRetries(2)deu ao ensaio da ronda 22 duas das suas três tentativas, e a retentativa após o kill pode arrancar ao lado de uma tentativa em execução (scripts/modal_retry_probe.py). Um estudo lançado antes do livro-razão não tem contagem:resume --trial <label> --beyond-boundcontinua-o à mão, fora de qualquer limite, e di-lo. Duas tentativas nunca partilham um ensaio: cada uma é registada como pendente antes do seu spawn, e cada uma detém um lease no volumekev-leases(heartbeat a cada minuto); uma nova tentativa recusa enquanto o lease de outra está fresco, e uma continuação espera atékev.budget.LEASE_STALE(15 min) após o último heartbeat de uma tentativa morta antes de arrancar. - Quedas de rede matam os clientes locais, não o trabalho remoto: uma leitura cujo cliente morreu normalmente já terminou no Modal; recolhe o seu diretório do volume (
modal volume get kev-runs /<name> runs/<name>) em vez de a relançar. - Um benchmark ou sonda falhado deixa o seu diretório no volume; tenta de novo com um nome novo.
7. Reporte
No fim da sessão (e no ficheiro de estado à medida que avança):
- A secção do PLAN.md de cada ronda transporta o seu registo, a tabela de leitura, os resultados de confirmação e o veredicto, negativo ou não, com os caminhos dos relatórios.
- Atualiza o “Where we stand” do PLAN.md (candidatos lançados e confirmados, trabalhos em execução, despesa) e o “What we have learned” se um resultado mudou; acrescenta cada ronda à tabela Record.
- Um resumo da sessão no PLAN.md: despesa (linha de base, leitura final, limites em execução), o que está pendente no Modal com os comandos exatos para o terminar, incidentes, e no máximo três passos seguintes com a sua prova.
- Cada número transporta checkpoint, suite e partição, n e caminho do relatório; commita as leituras e os veredictos de onde vêm os números (o
.gitignoreguarda relatórios, não despejos de previsões; acrescenta uma regra para novos diretórios de leituras). - Marcações temporais: as horas de registo e de resultado são horas de commit. Não escrevas uma hora num cabeçalho antes de ela acontecer; o scratchpad da noite 3 fê-lo, e as suas marcações não puderam ser usadas.
8. Armadilhas conhecidas
- Limite de criação de aplicações do Modal. Mais de cerca de três
modal rundestacados num minuto falham com “App create rate limit exceeded” e nada corre. Okev.roundsespaça os lançamentos 60 s e agrupa as leituras de um braço numa chamada; faz o mesmo à mão. repo@shaem trabalhos de benchmark costumava deslocar cada campo derun@suite@name@flags; omodal_app.parse_jobsagora analisa a partir da direita, por isso as revisões do Hub fixadas são seguras. As suites e os nomes não podem conter@ou,.- Uma recolha por estudo. Recolhas simultâneas do mesmo estudo apagavam os diretórios de ensaio umas das outras; o
pull_studyagora detém um bloqueio por estudo. Uma recolha enquanto os ensaios ainda correm é segura e atualiza apenas os ensaios inacabados. - As recolhas deixam os pesos completos no volume. O
::pull(e owatch) ignoram os shards de pesos completos (model*.safetensors, ~51 GB por checkpoint ou snapshot de 27B) e os pontos de retoma; todo o resto é descarregado (resultados, linhas,head.pt, configurações). Lê 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. - Implementa depois dos dados. A imagem copia
evals/; o lançador só verifica os hashes dekev/*.py, por isso um ensaio cujo ficheiro de dados foi acrescentado depois da implementação falha dentro do contentor.--gpu H200nostudyprecisa de uma aplicação implementada comKEV_GPU=H200. - 27B. Só H200 (backbone bf16, 55 GB residentes); timeouts de estudo até 28,800 s (um delta de skills de 1 época a lr 2e-5 correu cerca de 8.8 s por passo do otimizador); as leituras em fp32 cerca de três vezes as de um 9B (especificação
read_timeout: {"27b": 14400}); a leitura bloqueada precisa de--timeout 14400 --memory-mb 131072(especificaçãolocked_args) numa H200 (a GPU vem dogpuda especificação / da aplicação implementada, ou--gpu H200à mão). Cada ensaio de pesos bf16 falha a guardaisolation_and_packingdentro do ensaio (uma verificação em fp32); lê os resultados a partir das linhas e mede o isolamento servido em bf16 separadamente. - Nomenclatura do
locked_test. Quando uma guarda de triagem dentro do ensaio falhou, a ferramenta exige o sufixo-ungated(kev-4b-r8-ungated); o veredicto segue na mesma a regra registada. - Timeouts de leitura por suite. O
modal_app.READ_TIMEOUTSdefine painéis de estados longos 7,200 s, documentos 5,400 s, transfer-v9 3,600 s, senão 1,800 s. Um--timeoutglobal infla o limite de admissão de cada trabalho do lote. - Admissão de orçamento. Um lançamento acima do seu
--budgetsai antes de qualquer coisa correr; relança com um orçamento pelo menos igual ao limite impresso. - Os servidores externos são de voo único. O servidor do AutoJev respondia a um pedido de cada vez (HTTP 529 enquanto ocupado); sonda um endpoint estrangeiro antes de um longo
kev.benchmark --remotee define--remote-concurrencypara o que ele aguenta. Conta os pedidos que ele rejeita (por exemplo um 422 além do seu contexto) como cobertura, nunca os descartes silenciosamente. - Temperatura: de distribuição vs dentro do ensaio. O
result.jsonde um ensaio e o seu resumo bloqueado são avaliados no ajuste dentro do ensaio; um lançamento distribui a T que oscripts/calibrate_checkpoint.pyescreveu emhead.pt. O primeiro confronto direto com o AutoJev serviu o Kev-27B a 1.19 dentro do ensaio em vez da 1.38 de distribuição e teve de ser corrigido. Diz que T cada número usa, e onde foi ajustada (secção 3, regra 4: conjuntos de dados reservados, nunca as próprias partições do corpus de treino). - Calibração de contexto longo. Um painel com
"by_length": truereporta precisão, ECE, Brier e erros confiantes por intervalo de tokens de estado (abaixo de 8k até 64k+, e as caudas 8k+/16k+/32k+), e um critério pode fazer de guarda a um (long.ece_16k_plus.candidate <= 0.05). Os tokens são contados a partir dos registos da suite das leituras, por isso ambos os lados partilham os intervalos. - Correções de suite sem uma nova versão de suite. Um painel pode excluir fontes, tarefas ou uma lista (privada, registada por hash) de ids ou fontes de ambos os lados (
exclude_sources,exclude_tasks,exclude_file), e um painel apenas de reporte é marcado com"optional": truepara que uma leitura de reporte em falta nunca torne um candidato incompleto. Escolhe as exclusões pela validade das etiquetas antes de qualquer leitura sob elas (a ronda 24 tirou-as da auditoria de 2026-09-27), e nunca commites uma lista privada. - Capacidade do espaço de trabalho. O espaço de trabalho correu no máximo cerca de dez contentores de GPU de uma vez; os contentores pendentes são capacidade, não um bug, por isso não os relances.
- Suites pequenas. Uma guarda em 89 ou 144 perguntas não consegue resolver um piso de 2-3 pp; faz-lhes guarda através de um painel agrupado.
- As leituras do Jev falham a meio da execução em 503s do gateway; corre de novo toda a leitura com um nome novo em vez de remendar linhas parciais.
- Dados de alvo suave. Ao escrever um construtor, verifica alguns registos a olho: o
targetsoma 1 e a massa da etiqueta é pelo menos 0.5 a menos que o registo seja incognoscível (okev.data.none_pairtreinou uma vez massa zero em alvos suaves, corrigido no #60). - Redireciona a saída do
modal run ...::studypara um ficheiro de log; um filtro pode esconder oSystemExitque explica porque nada foi lançado.