Documentación

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.

  1. AGENTS.md, entero (comandos, suites congeladas, ubicaciones canónicas, ajustes de Modal).
  2. 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.
  3. 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.
  4. kev/rounds.py (su docstring es el esquema de la especificación), la especificación pasada más cercana en experiments/rounds/, y kev/autoresearch.py (session).
  5. 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 --json y registra metered_cost como 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 es compute_bound(gpu, timeout, trials) (kev/budget.py). El budget de estudio de una especificación debe ser al menos su límite (kev.rounds validate lo comprueba; modal_app.admit_study rechaza 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.

  1. 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.
  2. Escribe experiments/rounds/r<N>.json copiando 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 nombran trained_on para 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> --parents la hace. Elimina cada lectura de una suite retirada (kev.suite.REMOVED_SUITES, con el motivo): evals/external/scienthoon-v1 se 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-v2 y typesafe-v1 se 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. validate y launch rechazan una ronda después de la última ronda de la suite que todavía la nombre.
  3. Los datos nuevos son un directorio nuevo bajo evals/ con un manifest.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.
  4. 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 temperature para 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 que scripts/calibrate_checkpoint.py ajusta en el mismo conjunto. Los elementos reservados de las fuentes de entrenamiento (las particiones calibration / development de 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 de sft-v1 y 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 validate y launch rechazan 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 conjunto temperature. Las rondas <= 20 solo imprimen un !!! warning por brazo entrenado con un corpus de entrenamiento, así que sus especificaciones registradas todavía validan.
    • kev.rounds validate rechaza una lectura de conjunto que (a) sea la suite de entrenamiento de un brazo, un componente de ella (los inputs.components de sft-v1) o la suite data de su plan, (b) agrupe una fuente con la que entrenó algún brazo, o (c) lea la partición calibration o development de 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 nombrar trained_on.
    • La lectura registra el temperature_source de 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.py rechaza los mismos conjuntos de ajuste (comprobado contra la suite de entrenamiento de head.pt); --allow-in-distribution es solo para reproducir un ajuste antiguo, y se registra en head.pt["temperature_fit"].
    • La temperatura dentro del ensayo de un ensayo (result.json calibration_fit) dice role: 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), y validate avisa 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 sources de 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 archivo data fuera de evals/, un manifiesto sin fuentes) es un problema para una ronda nueva.
    • calibrate_checkpoint.py --temperature T (un valor manual, nada ajustado) necesita --reason, registrado en head.pt["temperature_fit"] (p. ej. “copied from the pool fit of runs/r20-readout”).
  5. uv run python -m kev.rounds validate experiments/rounds/r<N>.json (añade --partitions para verificar las particiones) hasta que imprima ok. 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.
  • watch sondea los ensayos lanzados, trae cada estudio terminado (un pull por estudio a la vez), lanza las lecturas de ese brazo una vez (una llamada ::benchmarks por lote por brazo, con 60 s de separación), espera a que terminen y escribe runs/r<N>-readout/round<N>.json y una tabla. Es reiniciable: el estado está en runs/<study>.watch.json y la intención de lanzamiento en runs/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 luego confirm <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 a runs/autoresearch-sessions.jsonl e imprime los comandos de confirmación; nunca los ejecuta. kev.autoresearch leaderboard refresca runs/leaderboard.{jsonl,md} (no comprometido), compare empareja 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, un head.pt publicado), 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 de kev/rounds.py. Un cambio de evaluador necesario es su propio PR, verificado con kev-verify y tests/test_rounds.py, antes de que cualquier ronda dependa de él;
  • pasar --allow-test o ejecutar locked_test fuera 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.rmtree en 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_repo en un plan, o modal_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 como jaredpalmer/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 -f en 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 study puede haber lanzado ensayos igualmente, así que ejecuta modal container list antes 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 nohup y caffeinate; reinicia watch tras 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, watch ejecuta modal_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 en runs/<study>.spawn.json (attempts, como máximo 1 + kev.budget.FULL_FT_RETRIES por 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í que Retries(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-bound lo 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 volumen kev-leases (latido cada minuto); un intento nuevo se rechaza mientras el lease de otro está fresco, y una continuación espera hasta kev.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 (.gitignore conserva 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 run desacoplados en un minuto fallan con “App create rate limit exceeded” y no se ejecuta nada. kev.rounds escalona los lanzamientos con 60 s de separación y agrupa las lecturas de un brazo en una llamada; haz lo mismo a mano.
  • repo@sha en trabajos de benchmark solía desplazar cada campo de run@suite@name@flags; modal_app.parse_jobs ahora 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_study ahora 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 (y watch) 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 --weights copia los shards cuando algo local realmente los necesita.
  • Despliega después de los datos. La imagen copia evals/; el lanzador solo comprueba los hashes de kev/*.py, así que un ensayo cuyo archivo de datos se añadió después del despliegue falla dentro del contenedor. --gpu H200 en study necesita una app desplegada con KEV_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ón locked_args) en una H200 (la GPU viene del gpu de la especificación / la app desplegada, o --gpu H200 a mano). Cada ensayo de pesos bf16 falla la puerta isolation_and_packing dentro 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_TIMEOUTS fija 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 --timeout global infla el límite de admisión de cada trabajo del lote.
  • Admisión de presupuesto. Un lanzamiento por encima de su --budget sale 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 --remote largo y fija --remote-concurrency a 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.json de un ensayo y su resumen bloqueado se puntúan con el ajuste dentro del ensayo; una publicación distribuye la T que scripts/calibrate_checkpoint.py escribió en head.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": true informa 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": true para 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: target suma 1 y la masa de la etiqueta es al menos 0.5 a menos que el registro sea incognoscible (kev.data.none_pair una vez entrenó con masa cero en objetivos suaves, corregido en #60).
  • Redirige la salida de modal run ...::study a un archivo de registro; un filtro puede ocultar el SystemExit que explica por qué no se lanzó nada.