Ejecutar una sesión de investigación sin supervisión
Este es el programa operativo de una sesión de investigación que se ejecuta sin que nadie la vigile: una sesión nocturna o un
relevo largo durante el día. Sustituye a los prompts por noche (los programas de la ronda 6 y de la noche 3, conservados en la etiqueta de git
research-archive-2026-09-24 bajo docs/prompts/) y recoge lo que salió mal esas noches. Las reglas de investigación que
aplica están en PLAN.md, “Standing rules for every round”; los comandos están en AGENTS.md.
Una sesión hace hill-climbing y confirma bajo reglas registradas y deja un registro sobre el que Jared puede actuar. No publica.
1. Empezar
Dedica los primeros 30 minutos a leer, no a lanzar.
AGENTS.md, entero (comandos, suites congeladas, ubicaciones canónicas, ajustes de Modal).PLAN.md: dónde estamos, qué hemos aprendido, las reglas permanentes, la política de datos y Next. Para un hallazgo sobre el que quieras construir, lee su evidencia en el archivo:git show research-archive-2026-09-24:PLAN.md.- Las skills en
.agents/skills/:kev-modal-study(lanzar, vigilar y traer trabajo de GPU; lee sus Gotchas),kev-verify(demostrar que un cambio de código no tiene regresión),kev-pr-description(antes de cualquier PR),thermonuclear-code-review. kev/rounds.py(su docstring es el esquema de la especificación), la especificación pasada más cercana enexperiments/rounds/, ykev/autoresearch.py(session).- El propio relevo: autorización (dólares de Modal, dólares de AI Gateway), qué está en alcance, qué necesita a Jared.
Luego prepara:
- Trabaja en un worktree en una rama de investigación (
git worktree add -b research/<session> /tmp/kev-<session> origin/main). Haz push después de cada commit para que no se pierda nada si la máquina se duerme. El código destinado a main pasa por su propio PR revisado. - Lee
uv run modal billing summary --jsony registrametered_costcomo la línea base en el archivo de estado (sección 6). - Si una sesión anterior dejó un archivo de estado, léelo primero y reanuda desde él; los trabajos de Modal desacoplados siguen corriendo sin ti.
2. Presupuestos y la regla de gasto
- La autorización es el total de la sesión, contando todo lo que sigue en marcha. Antes de cada lanzamiento, vuelve a leer el coste
medido y no lances si
(metered_now - baseline) + sum(admission bounds of everything still running) >= authorization. - El límite de admisión de un estudio se imprime al lanzar y se guarda en
runs/<study>.spawn.json; el límite de una llamada de benchmark escompute_bound(gpu, timeout, trials)(kev/budget.py). Elbudgetde estudio de una especificación debe ser al menos su límite (kev.rounds validatelo comprueba;modal_app.admit_studyrechaza un estudio por encima de su presupuesto antes de que nada corra, y un estudio está limitado a $250 y 28,800 s). - Guarda una reserva (unos 10 % de la autorización) que ninguna fase planifique: las lecturas de facturación se retrasan y se revisan, y los límites de admisión exageran mucho las lecturas (un lote de lectura lleva el timeout de su trabajo más lento).
- Registra cada lectura con su hora UTC en el archivo de estado. El gasto de AI Gateway (lecturas de referencia de Jev, jueces de etiquetas) tiene su propio
tope, aplicado por el script que lo gasta, y se registra en
runs/<name>/usage.json. - El límite de gasto del espacio de trabajo de Modal solo se puede subir desde el panel; alcanzarlo mata contenedores en marcha a mitad de entrenamiento.
3. Registrar una ronda
Una ronda es una sección de PLAN.md más una especificación, comprometidas juntas antes de cualquier entrenamiento o lectura.
- Escribe la sección de PLAN.md: por qué (la brecha medida y su evidencia), los datos (congelados primero, con manifiestos), los brazos, la regla (primaria, guardas con umbrales dimensionados para cada suite, ranking), las etapas de confirmación y el presupuesto. Usa las reglas permanentes; no inventes un estadístico nuevo para una ronda.
- Escribe
experiments/rounds/r<N>.jsoncopiando la especificación pasada más cercana (r15 para un delta conjunto, r17 para un 27B, r10 para una ronda de habilidades, r20 para brazos post-hoc sin entrenamiento: un conjunto de temperaturas, checkpoints interpolados; r23 para mezclas hacia otro checkpoint, cuyos brazos nombrantrained_onpara el entrenamiento de ambos extremos). Omite"archive": esa clave marca las rondas registradas 5-18. Cada archivo de plan que la especificación nombre, y cada lectura padre que su regla necesite, debe existir en este checkout; si a un padre le falta una lectura,launch-reads <spec> --parentsla hace. Elimina cada lectura de una suite retirada (kev.suite.REMOVED_SUITES, con el motivo):evals/external/scienthoon-v1se retiró el 2026-09-27, así que desde la ronda 23 la lectura, el panel y la guarda de scienthoon se van;evals/external/wanli-v2ytypesafe-v1se retiraron el 2026-09-30, así que desde la ronda 27 sus lecturas también se van, y SemIf es la única lectura externa que queda (solo informe). Los externos agrupados no son una puerta: la regla auditada de la ronda 24, que siguen las rondas 23-26, informó de SemIf, WANLI-v2 y TypeSafe como paneles opcionales.validateylaunchrechazan una ronda después de la última ronda de la suite que todavía la nombre. - Los datos nuevos son un directorio nuevo bajo
evals/con unmanifest.json(sha256 por archivo, hashes de las entradas). Bajo la política de datos SFT (PLAN.md), los corpus privados conservan solo el manifiesto en git, con una entrada"mirror"que apunta al conjunto de datos privado. - OBLIGATORIO: cada temperatura servida o distribuida proviene de un conjunto de conjuntos de datos reservados, nunca de una partición del corpus
de entrenamiento. Una ronda que lee calibración (ECE, Brier, errores con confianza, cobertura) registra un conjunto
temperaturepara sus brazos (copia r20: las ocho fuentes públicas reservadas de la partición de calibración de transfer-r3 + MMLU-Pro de transfer-v9), y una publicación distribuye la temperatura quescripts/calibrate_checkpoint.pyajusta en el mismo conjunto. Los elementos reservados de las fuentes de entrenamiento (las particionescalibration/developmentde una suite de entrenamiento) están en distribución: la ronda 19 sirvió sus brazos SFT a T 0.955 ajustada sobre filas de desarrollo desft-v1y falló cada criterio de calibración (ECE de breadth-v1 0.059); el conjunto de conjuntos de datos reservados de la ronda 20 dio 0.0085 en el mismo checkpoint. Lo que lo aplica:- Desde la ronda 21,
kev.rounds validateylaunchrechazan una ronda cuya regla o confirmación tenga un criterio que la temperatura mueva (ECE, Brier, NLL, errores con confianza, cobertura; cualquier cosa menos precisión) y ningún conjuntotemperature. Las rondas <= 20 solo imprimen un!!! warningpor brazo entrenado con un corpus de entrenamiento, así que sus especificaciones registradas todavía validan. kev.rounds validaterechaza una lectura de conjunto que (a) sea la suite de entrenamiento de un brazo, un componente de ella (losinputs.componentsde sft-v1) o la suitedatade su plan, (b) agrupe una fuente con la que entrenó algún brazo, o (c) lea la particióncalibrationodevelopmentde cualquier corpus de entrenamiento; también rechaza un conjunto que no puede comprobar (un brazo cuyo entrenamiento es desconocido, una suite sin manifiesto o fuentes listadas). Los brazos de checkpoint sin ensayo pueden nombrartrained_on.- La lectura registra el
temperature_sourcede cada brazo; la tabla imprime!!!para un brazo servido con las filas de desarrollo de su ensayo de un corpus de entrenamiento (las rondas 5-19 todas lo hacían; de ahora en adelante una temperatura así es solo de cribado). scripts/calibrate_checkpoint.pyrechaza los mismos conjuntos de ajuste (comprobado contra la suite de entrenamiento de head.pt);--allow-in-distributiones solo para reproducir un ajuste antiguo, y se registra enhead.pt["temperature_fit"].- La temperatura dentro del ensayo de un ensayo (
result.jsoncalibration_fit) dicerole: in-trial screening ... not a served or shipped temperature. - Los padres se sirven a la temperatura ajustada sobre las filas de desarrollo de su ensayo (para Kev-27B esa es su 1.38 distribuida,
ajustada sobre las mismas filas); la lectura registra eso y su head.pt T distribuida (
parent_temperature_source), yvalidateavisa cuando las dos difieren en más de 0.05 en las filas de un corpus de entrenamiento. - La comprobación de disjunción es por NOMBRE de fuente (nominal, no semántica): dos suites que llevan el mismo conjunto de datos bajo nombres distintos
la pasan. Así que un conjunto debe usar fuentes que sean solo de evaluación en Kev por construcción, como las ocho fuentes públicas reservadas de
transfer-r3 y el MMLU-Pro de transfer-v9. La lista de permitidos
sourcesde una lectura de conjunto debe nombrar fuentes que su suite liste (un error tipográfico es un problema), y el entrenamiento que el comprobador no puede listar (un archivodatafuera deevals/, un manifiesto sin fuentes) es un problema para una ronda nueva. calibrate_checkpoint.py --temperature T(un valor manual, nada ajustado) necesita--reason, registrado enhead.pt["temperature_fit"](p. ej. “copied from the pool fit of runs/r20-readout”).
- Desde la ronda 21,
uv run python -m kev.rounds validate experiments/rounds/r<N>.json(añade--partitionspara verificar las particiones) hasta que imprimaok. Compromete la sección de PLAN y la especificación en un commit, haz push. Esa hora de commit es la hora de registro.
4. Ejecutarlo de principio a fin
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 &
- En los primeros cinco minutos de cada estudio, cuenta los pasos de optimizador por minuto en
modal container logs <id>y proyecta el tiempo de reloj contra el timeout (ep0 step N/M: M es sobre todas las épocas). Un contenedor que expira no guarda nada; cancélalo (FunctionCall.from_id(cid).cancel()) y relánzalo bajo un nombre de estudio nuevo con menos registros o un timeout mayor. watchsondea los ensayos lanzados, trae cada estudio terminado (un pull por estudio a la vez), lanza las lecturas de ese brazo una vez (una llamada::benchmarkspor lote por brazo, con 60 s de separación), espera a que terminen y escriberuns/r<N>-readout/round<N>.jsony una tabla. Es reiniciable: el estado está enruns/<study>.watch.jsony la intención de lanzamiento enruns/r<N>-reads-<arm>.json. A mano:launch-reads <spec> [--arms a,b] [--parents] [--dry-run],readout <spec>.- Escribe la lectura en la sección de PLAN: cada brazo, cada criterio con su intervalo, el veredicto y qué falló.
- La confirmación es deliberada, nunca automática. Para el candidato que nombra la lectura, escribe la elección en PLAN.md y
comprométela, luego por etapa:
launch-reads <spec> --stage <stage> --arm <arm>, y luegoconfirm <spec> --stage <stage> --arm <arm>(→runs/r<N>-verdict/<size>-<stage>.json). Los paneles de test antes de la lectura bloqueada. Una lectura cada una, sin excepciones. - Varias rondas registradas en secuencia bajo un tope:
uv run python -m kev.autoresearch session experiments/rounds/r19.json [...] --spend-start <baseline> --spend-cap <authorization>. Valida, lanza y vigila cada ronda hasta su lectura, se detiene antes de una ronda cuyos presupuestos superarían el tope, añade aruns/autoresearch-sessions.jsonle imprime los comandos de confirmación; nunca los ejecuta.kev.autoresearch leaderboardrefrescaruns/leaderboard.{jsonl,md}(no comprometido),compareempareja ensayos contra una referencia en precisión de transferencia,release-check --study <name>criba cada config de ese estudio (cada config pasa solo si todas sus semillas pasan sus puertas).
5. Qué puede y qué no puede tocar una sesión
Puede: escribir especificaciones, planes y secciones de PLAN.md; construir datos congelados nuevos bajo directorios nuevos; lanzar estudios y lecturas mediante
modal_app.py; cambiar scripts y constantes de infraestructura de modal_app.py; abrir PRs para código que pertenece a main.
No puede, sin el OK explícito de Jared:
- publicar o cambiar nada en el Hub (
kev.publish,hf upload,hf repos tag,scripts/publish_space.sh, unhead.ptpublicado), hacer público un repo privado ni desplegar un endpoint público; - comprometer en main, hacer force-push o fusionar un PR (el código llega a main mediante PRs revisados y fusionados con squash y CI en verde);
- editar cualquier cosa que exista bajo
evals/(congelado) o el evaluador:kev/experiment.py: EVALUATOR_FILES, las puertas,kev/metrics.py, la lectura emparejada dekev/rounds.py. Un cambio de evaluador necesario es su propio PR, verificado conkev-verifyytests/test_rounds.py, antes de que cualquier ronda dependa de él; - pasar
--allow-testo ejecutarlocked_testfuera de una etapa de confirmación registrada; - poner cualquier salida de Jev, o cualquier generación de un modelo cerrado, en los datos de entrenamiento;
- entrenar en local (un Mac de 32 GB no puede con estos modelos) o ejecutar dos procesos de entrenamiento en una máquina;
- eliminar un checkpoint o una instantánea del volumen de ejecuciones (
modal volume rm,shutil.rmtreeen un contenedor) o desactivar las instantáneas de un ensayo de pesos completos ("snapshot_fractions": "none") en una especificación registrada. Los ensayos de pesos completos conservan instantáneas en 0.25, 0.5 y 0.75 de sus pasos (kev.experiment.SNAPSHOT_FRACTIONS) para que una lectura pueda encontrar el mejor punto de una ejecución tras terminar: la ronda 19 no pudo, porque el único estado a mitad de ejecución era un punto de reanudación, eliminado al terminar la ejecución, y el mejor checkpoint de AutoJev estaba en 0.7 de época. Las instantáneas de un 27B son ~154 GB de volumen por ensayo; el espacio es decisión de Jared, no de la sesión. Las instantáneas viven en el volumen de ejecuciones (primario); un espejo privado del Hub (snapshot_hub_repoen un plan, omodal_app.py::mirror_snapshots) es almacenamiento a largo plazo para un checkpoint que merece conservarse, no un sustituto: replicar checkpoints de 27B (~51 GB cada uno, en un repo privado comojaredpalmer/kev-snapshots) también es decisión de Jared, y nunca a un repo público.
Si un brazo está bloqueado (autenticación, un límite de gasto, un despliegue que no funcionará en 30 minutos), anota qué pasó y pasa al siguiente brazo. No esperes a un humano.
6. Resiliencia
- Archivo de estado
runs/<session>-state.json(runs/ está en gitignore;git add -fen la rama de investigación): línea base y autorización, lecturas de gasto con horas UTC, cada estudio con sus ids de spawn, límite y estado, lecturas lanzadas y traídas, candidatos, PRs, decisiones pendientes. Actualízalo tras cada lanzamiento, pull y lectura, y compromételo con la sección de PLAN. - Trabajos desacoplados. Los estudios se lanzan en la app desplegada y sobreviven al cliente local; un error local después de
studypuede haber lanzado ensayos igualmente, así que ejecutamodal container listantes de relanzar, y nunca relances bajo el mismo nombre de estudio. Las sondas y los benchmarks se ejecutan con--detach. - Los vigilantes son procesos locales y mueren con la máquina o la red. Ejecútalos bajo
nohupycaffeinate; reiniciawatchtras cualquier interrupción (reanuda desde su estado). Reintenta errores de DNS y de conexión por sí mismo; una excepción propia de un ensayo es un fallo y se informa. - La vigilancia continúa los ensayos de pesos completos que expiran, no Modal. Los ensayos se lanzan con los reintentos de Modal desactivados; cuando la
llamada de un ensayo de pesos completos termina por su timeout,
watchejecutamodal_app.py::resume --trial <label>, que lanza el siguiente intento (continúa desde el último punto de reanudación comprometido) con la GPU y el timeout para los que se admitió el estudio y lo registra enruns/<study>.spawn.json(attempts, como máximo 1 +kev.budget.FULL_FT_RETRIESpor ensayo, la cuenta con la que se calculó el límite de admisión; un ensayo cuya llamada actual sigue en marcha nunca se continúa). Mientras el vigilante está caído no se continúa nada: reinícialo y recoge el timeout. Por qué: Modal cobró cada intento expirado dos veces (el timeout y luego la tarea que mató 30 s después), así queRetries(2)dio al ensayo de la ronda 22 dos de sus tres intentos, y el reintento de la muerte puede empezar junto a un intento en marcha (scripts/modal_retry_probe.py). Un estudio lanzado antes del libro mayor no tiene cuenta:resume --trial <label> --beyond-boundlo continúa a mano, fuera de cualquier límite, y lo dice. Dos intentos nunca comparten un ensayo: cada uno se registra como pendiente antes de su spawn, y cada uno mantiene un lease en el volumenkev-leases(latido cada minuto); un intento nuevo se rechaza mientras el lease de otro está fresco, y una continuación espera hastakev.budget.LEASE_STALE(15 min) tras el último latido de un intento muerto antes de lanzarse. - Las caídas de red matan a los clientes locales, no al trabajo remoto: una lectura cuyo cliente murió normalmente ha terminado en Modal; trae
su directorio del volumen (
modal volume get kev-runs /<name> runs/<name>) en lugar de relanzarla. - Un benchmark o una sonda fallidos dejan su directorio en el volumen; reintenta bajo un nombre nuevo.
7. Informar
Al final de la sesión (y en el archivo de estado a medida que avanza):
- La sección de PLAN.md de cada ronda lleva su registro, la tabla de lectura, los resultados de confirmación y el veredicto, negativo o no, con las rutas de los informes.
- Actualiza “Where we stand” de PLAN.md (candidatos publicados y confirmados, trabajos en marcha, gasto) y “What we have learned” si un hallazgo cambió; añade cada ronda a la tabla Record.
- Un resumen de la sesión en PLAN.md: gasto (línea base, lectura final, límites en marcha), qué está pendiente en Modal con los comandos exactos para terminarlo, incidentes y como máximo tres pasos siguientes con su evidencia.
- Cada número lleva checkpoint, suite y partición, n y ruta del informe; compromete las lecturas y los veredictos de los que
proceden los números (
.gitignoreconserva los informes, no los volcados de predicciones; añade una regla para los directorios de lectura nuevos). - Marcas de tiempo: las horas de registro y de resultado son horas de commit. No escribas una hora en un encabezado antes de que ocurra; el scratchpad de la noche 3 lo hizo, y sus marcas no se pudieron usar.
8. Trampas conocidas
- Límite de creación de apps de Modal. Más de unos tres
modal rundesacoplados en un minuto fallan con “App create rate limit exceeded” y no se ejecuta nada.kev.roundsescalona los lanzamientos con 60 s de separación y agrupa las lecturas de un brazo en una llamada; haz lo mismo a mano. repo@shaen trabajos de benchmark solía desplazar cada campo derun@suite@name@flags;modal_app.parse_jobsahora analiza desde la derecha, así que las revisiones fijadas del Hub son seguras. Las suites y los nombres no deben contener@ni,.- Un pull por estudio. Los pulls concurrentes del mismo estudio borraban los directorios de ensayo unos de otros;
pull_studyahora mantiene un bloqueo por estudio. Un pull mientras los ensayos siguen corriendo es seguro y refresca solo los ensayos sin terminar. - Los pulls dejan los pesos completos en el volumen.
::pull(ywatch) omite los shards de pesos completos (model*.safetensors, ~51 GB por checkpoint o instantánea de 27B) y los puntos de reanudación; todo lo demás baja (resultados, filas,head.pt, configs). Lee un checkpoint o una instantánea en el volumen:::benchmarks --jobs "/runs/<study>/<trial>/snapshots/step-<N>/checkpoint@<suite>@<name>".::pull --weightscopia los shards cuando algo local realmente los necesita. - Despliega después de los datos. La imagen copia
evals/; el lanzador solo comprueba los hashes dekev/*.py, así que un ensayo cuyo archivo de datos se añadió después del despliegue falla dentro del contenedor.--gpu H200enstudynecesita una app desplegada conKEV_GPU=H200. - 27B. Solo H200 (backbone bf16, 55 GB residentes); timeouts de estudio de hasta 28,800 s (un delta de habilidades de 1 época a lr 2e-5 corrió
unos 8.8 s por paso de optimizador); las lecturas fp32 aproximadamente el triple que un 9B (especificación
read_timeout: {"27b": 14400}); la lectura bloqueada necesita--timeout 14400 --memory-mb 131072(especificaciónlocked_args) en una H200 (la GPU viene delgpude la especificación / la app desplegada, o--gpu H200a mano). Cada ensayo de pesos bf16 falla la puertaisolation_and_packingdentro del ensayo (una comprobación fp32); lee los resultados de las filas y mide el aislamiento servido en bf16 por separado. - Nombres de
locked_test. Cuando falla una puerta de cribado dentro del ensayo, la herramienta exige el sufijo-ungated(kev-4b-r8-ungated); el veredicto sigue la regla registrada. - Timeouts de lectura por suite.
modal_app.READ_TIMEOUTSfija los paneles de estado largo en 7,200 s, los documentos en 5,400 s, transfer-v9 en 3,600 s y el resto en 1,800 s. Un--timeoutglobal infla el límite de admisión de cada trabajo del lote. - Admisión de presupuesto. Un lanzamiento por encima de su
--budgetsale antes de que nada corra; relanza con un presupuesto al menos el límite impreso. - Los servidores externos son de un solo vuelo. El servidor de AutoJev respondía a una solicitud a la vez (HTTP 529 mientras estaba ocupado); sondea un
endpoint ajeno antes de un
kev.benchmark --remotelargo y fija--remote-concurrencya lo que pueda aguantar. Cuenta las solicitudes que rechaza (por ejemplo un 422 pasado su contexto) como cobertura, nunca las descartes en silencio. - Temperatura: distribuida frente a la del ensayo. El
result.jsonde un ensayo y su resumen bloqueado se puntúan con el ajuste dentro del ensayo; una publicación distribuye la T quescripts/calibrate_checkpoint.pyescribió enhead.pt. El primer enfrentamiento de AutoJev sirvió Kev-27B con la 1.19 del ensayo en lugar de la 1.38 distribuida y tuvo que corregirse. Di qué T usa cada número, y dónde se ajustó (sección 3, regla 4: conjuntos de datos reservados, nunca las particiones propias del corpus de entrenamiento). - Calibración de contexto largo. Un panel con
"by_length": trueinforma de precisión, ECE, Brier y errores con confianza por cubo de tokens del estado (de menos de 8k a 64k+ y las colas 8k+/16k+/32k+), y un criterio puede hacer de puerta para uno (long.ece_16k_plus.candidate <= 0.05). Los tokens se cuentan de los registros de la suite de las lecturas, así que ambos lados comparten cubos. - Arreglos de suite sin una versión de suite nueva. Un panel puede descartar fuentes, tareas o una lista (privada, registrada por hash) de ids
o fuentes en ambos lados (
exclude_sources,exclude_tasks,exclude_file), y un panel solo de informe se marca"optional": truepara que una lectura de informe ausente nunca deje incompleto a un candidato. Elige las exclusiones por validez de etiquetas antes de cualquier lectura bajo ellas (la ronda 24 las tomó de la auditoría del 2026-09-27), y nunca comprometas una lista privada. - Capacidad del espacio de trabajo. El espacio de trabajo ha ejecutado como máximo unos diez contenedores de GPU a la vez; los contenedores pendientes son capacidad, no un bug, así que no los relances.
- Suites pequeñas. Una guarda sobre 89 o 144 preguntas no puede resolver un suelo de 2-3 pp; ponles una puerta mediante un panel agrupado.
- Las lecturas de Jev fallan a mitad de ejecución por 503 del gateway; vuelve a ejecutar toda la lectura bajo un nombre nuevo en lugar de empalmar filas parciales.
- Datos de objetivo suave. Al escribir un constructor, comprueba unos cuantos registros a ojo:
targetsuma 1 y la masa de la etiqueta es al menos 0.5 a menos que el registro sea incognoscible (kev.data.none_pairuna vez entrenó con masa cero en objetivos suaves, corregido en #60). - Redirige la salida de
modal run ...::studya un archivo de registro; un filtro puede ocultar elSystemExitque explica por qué no se lanzó nada.