Документация

Запуск исследовательской сессии без присмотра

Это рабочая программа для исследовательской сессии, которая идёт без наблюдения человека: ночная сессия или долгая передача дел в течение дня. Она заменяет ночные промпты (программы раунда 6 и ночи 3, хранятся по git-тегу research-archive-2026-09-24 в docs/prompts/) и вбирает в себя то, что пошло не так в те ночи. Исследовательские правила, которые она применяет, — в PLAN.md, «Standing rules for every round»; команды — в AGENTS.md.

Сессия выполняет hill climbing и подтверждает находки по зарегистрированным правилам и оставляет запись, по которой Jared может действовать. Она ничего не публикует.

1. Начало

Потратьте первые 30 минут на чтение, а не на запуски.

  1. AGENTS.md, целиком (команды, замороженные наборы, канонические места, настройки Modal).
  2. PLAN.md: где мы находимся, что мы узнали, постоянные правила, политика данных и Next. Для находки, на которой вы хотите строить, прочитайте её доказательства в архиве: git show research-archive-2026-09-24:PLAN.md.
  3. Навыки в .agents/skills/: kev-modal-study (запуск, наблюдение и выгрузка работы на GPU; прочитайте его Gotchas), kev-verify (доказательство, что изменение кода не даёт регрессий), kev-pr-description (перед любым PR), thermonuclear-code-review.
  4. kev/rounds.py (его docstring — это схема спецификации), ближайшую прошлую спецификацию в experiments/rounds/ и kev/autoresearch.py (session).
  5. Саму передачу дел: авторизация (доллары Modal, доллары AI Gateway), что входит в охват, что требует Jared.

Затем настройтесь:

  • Работайте в worktree на исследовательской ветке (git worktree add -b research/<session> /tmp/kev-<session> origin/main). Пушите после каждого коммита, чтобы ничего не потерялось, если машина уснёт. Код, предназначенный для main, проходит через собственный проверенный PR.
  • Прочитайте uv run modal billing summary --json и запишите metered_cost как базовую линию в файл состояния (раздел 6).
  • Если прошлая сессия оставила файл состояния, прочитайте его первым и продолжите с него; отсоединённые задания Modal продолжают работать без вас.

2. Бюджеты и правило трат

  • Авторизация — это общая сумма сессии с учётом всего, что ещё работает. Перед каждым запуском снова читайте измеренную стоимость и не запускайте, если (metered_now - baseline) + sum(admission bounds of everything still running) >= authorization.
  • Граница допуска исследования печатается при запуске и сохраняется в runs/<study>.spawn.json; граница вызова бенчмарка — compute_bound(gpu, timeout, trials) (kev/budget.py). budget исследования в спецификации должен быть не меньше его границы (kev.rounds validate это проверяет; modal_app.admit_study отказывает исследованию, превышающему бюджет, прежде чем что-либо запустится, а исследование ограничено $250 и 28 800 с).
  • Держите резерв (около 10 % авторизации), который не планируется ни в одной фазе: показания биллинга запаздывают и пересматриваются, а границы допуска сильно завышают чтения (пакетный запрос на чтение несёт таймаут своей самой медленной задачи).
  • Записывайте каждое показание с его временем UTC в файл состояния. Траты AI Gateway (эталонные чтения Jev, судьи меток) имеют собственный лимит, обеспечиваемый скриптом, который их тратит, и логируются в runs/<name>/usage.json.
  • Лимит трат рабочего пространства Modal можно поднять только из дашборда; его достижение убивает работающие контейнеры посреди обучения.

3. Зарегистрировать раунд

Раунд — это раздел PLAN.md плюс спецификация, закоммиченные вместе до любого обучения или чтения.

  1. Напишите раздел PLAN.md: зачем (измеренный разрыв и его доказательства), данные (сначала замороженные, с манифестами), ветви, правило (основное, охранные с порогами, подобранными под каждый набор, ранг), стадии подтверждения и бюджет. Используйте постоянные правила; не изобретайте новую статистику для одного раунда.
  2. Напишите experiments/rounds/r<N>.json, скопировав ближайшую прошлую спецификацию (r15 для совместной дельты, r17 для 27B, r10 для раунда навыков, r20 для post-hoc-ветвей без обучения: пул температур, интерполированные чекпойнты; r23 для смесей в сторону другого чекпойнта, чьи ветви называют trained_on для обучения обоих концов). Пропустите "archive": этот ключ отмечает записанные раунды 5–18. Каждый файл плана, который называет спецификация, и каждое родительское чтение, которое требует её правило, должны существовать в этом чекауте; если у родителя нет чтения, launch-reads <spec> --parents его создаёт. Уберите каждое чтение удалённого набора (kev.suite.REMOVED_SUITES, с причиной): evals/external/scienthoon-v1 был удалён 2026-09-27, поэтому с раунда 23 чтение, панель и охранник scienthoon уходят; evals/external/wanli-v2 и typesafe-v1 были удалены 2026-09-30, поэтому с раунда 27 их чтения тоже уходят, и SemIf — единственное оставшееся внешнее чтение (только для сведения). Объединённые внешние не являются гейтом: правило с аудитом раунда 24, которому следуют раунды 23–26, докладывало SemIf, WANLI-v2 и TypeSafe как необязательные панели. validate и launch отказывают раунду после последнего раунда набора, который ещё его называет.
  3. Новые данные — это новая папка в evals/ с manifest.json (sha256 по каждому файлу, хеши входов). По политике данных SFT (PLAN.md) закрытые корпуса держат в git только манифест, с записью "mirror", указывающей на закрытый датасет.
  4. ОБЯЗАТЕЛЬНО: каждая обслуживаемая или поставляемая температура берётся из пула отложенных наборов данных, никогда из партиции обучающего корпуса. Раунд, который читает калибровку (ECE, Brier, уверенные ошибки, покрытие), регистрирует пул temperature для своих ветвей (копируйте r20: восемь отложенных публичных источников из калибровочной партиции transfer-r3 + MMLU-Pro из transfer-v9), а релиз поставляет температуру, которую scripts/calibrate_checkpoint.py подгоняет на том же пуле. Отложенные элементы обучающих источников (партиции calibration / development обучающего набора) находятся в распределении: раунд 19 обслуживал свои ветви SFT при T 0.955, подогнанной на девелоперских строках sft-v1, и провалил каждый критерий калибровки (breadth-v1 ECE 0.059); пул отложенных наборов данных раунда 20 дал 0.0085 на том же чекпойнте. Что это обеспечивает:
    • С раунда 21 kev.rounds validate и launch отказывают раунду, чьё правило или подтверждение имеет критерий, на который влияет температура (ECE, Brier, NLL, уверенные ошибки, покрытие; всё, кроме точности), и при этом нет пула temperature. Раунды <= 20 только печатают !!! warning на ветвь, обученную на обучающем корпусе, поэтому их записанные спецификации всё ещё проходят валидацию.
    • kev.rounds validate отказывает чтению пула, которое (a) является обучающим набором ветви, его компонентом (inputs.components у sft-v1) или набором data из его плана, (b) включает в пул источник, на котором обучалась какая-либо ветвь, или (c) читает партицию calibration или development любого обучающего корпуса; оно также отказывает пулу, который не может проверить (ветвь, чьё обучение неизвестно, набор без манифеста или перечисленных источников). Чекпойнт-ветви без прогона могут называть trained_on.
    • Чтение фиксирует temperature_source каждой ветви; таблица печатает !!! для ветви, обслуженной на девелоперских строках обучающего корпуса из её прогона (раунды 5–19 все были такими; с этого момента такая температура — только для скрининга).
    • scripts/calibrate_checkpoint.py отказывает тем же наборам подгонки (проверяется против обучающего набора head.pt); --allow-in-distribution предназначен только для воспроизведения старой подгонки, и это записывается в head.pt["temperature_fit"].
    • Внутрипрогонная температура прогона (result.json calibration_fit) говорит role: in-trial screening ... not a served or shipped temperature.
    • Родители обслуживаются при температуре, подогнанной на девелоперских строках их прогона (для Kev-27B это его поставляемая 1.38, подогнанная на тех же строках); чтение фиксирует это и их поставляемую T из head.pt (parent_temperature_source), а validate предупреждает, когда эти две различаются более чем на 0.05 на строках обучающего корпуса.
    • Проверка непересечения идёт по ИМЕНИ источника (номинально, не семантически): два набора, несущие один и тот же датасет под разными именами, её проходят. Поэтому пул должен использовать источники, которые по построению в Kev только оценочные, — как восемь отложенных публичных источников transfer-r3 и MMLU-Pro из transfer-v9. Список разрешённых sources у чтения пула должен называть источники, которые перечисляет его набор (опечатка — проблема), а обучение, которое проверяющий не может перечислить (файл data вне evals/, манифест без источников), — проблема для нового раунда.
    • calibrate_checkpoint.py --temperature T (ручное значение, ничего не подогнано) требует --reason, записываемую в head.pt["temperature_fit"] (например, “copied from the pool fit of runs/r20-readout”).
  5. uv run python -m kev.rounds validate experiments/rounds/r<N>.json (добавьте --partitions, чтобы проверить партиции), пока не напечатает ok. Закоммитьте раздел PLAN и спецификацию одним коммитом, запушите. Время этого коммита — время регистрации.

4. Прогнать от начала до конца

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 &
  • В первые пять минут каждого исследования считайте шаги оптимизатора в минуту в modal container logs <id> и проецируйте время счёта на таймаут (ep0 step N/M: M — по всем эпохам). Контейнер, у которого вышел таймаут, ничего не сохраняет; отмените (FunctionCall.from_id(cid).cancel()) и перезапустите под новым именем исследования с меньшим числом записей или большим таймаутом.
  • watch опрашивает запущенные прогоны, выгружает каждое завершённое исследование (по одной выгрузке на исследование за раз), один раз запускает чтения этой ветви (один пакетный вызов ::benchmarks на ветвь, с интервалом 60 с), ждёт их и пишет runs/r<N>-readout/round<N>.json и таблицу. Его можно перезапускать: состояние в runs/<study>.watch.json, а намерение запуска — в runs/r<N>-reads-<arm>.json. Вручную: launch-reads <spec> [--arms a,b] [--parents] [--dry-run], readout <spec>.
  • Запишите чтение в раздел PLAN: каждая ветвь, каждый критерий с его интервалом, вердикт и что провалилось.
  • Подтверждение намеренное, никогда не автоматическое. Для кандидата, которого называет чтение, запишите выбор в PLAN.md и закоммитьте его, затем по стадиям: launch-reads <spec> --stage <stage> --arm <arm>, затем confirm <spec> --stage <stage> --arm <arm> (→ runs/r<N>-verdict/<size>-<stage>.json). Тестовые панели перед закрытым чтением. По одному чтению каждое, без исключений.
  • Несколько зарегистрированных раундов подряд под лимитом: uv run python -m kev.autoresearch session experiments/rounds/r19.json [...] --spend-start <baseline> --spend-cap <authorization>. Он валидирует, запускает и наблюдает каждый раунд до его чтения, останавливается перед раундом, чьи бюджеты превысили бы лимит, дописывает в runs/autoresearch-sessions.jsonl и печатает команды подтверждения; он никогда их не запускает. kev.autoresearch leaderboard обновляет runs/leaderboard.{jsonl,md} (не коммитится), compare сопоставляет прогоны с эталоном по точности transfer, release-check --study <name> проверяет каждый конфиг в том исследовании (конфиг проходит, только если все его seed проходят свои гейты).

5. К чему сессия может и не может прикасаться

Может: писать спецификации, планы и разделы PLAN.md; создавать новые замороженные данные в новых папках; запускать исследования и чтения через modal_app.py; менять скрипты и инфраструктурные константы modal_app.py; открывать PR для кода, который принадлежит main.

Не может без явного согласия Jared:

  • публиковать или изменять что-либо на Hub (kev.publish, hf upload, hf repos tag, scripts/publish_space.sh, выпущенный head.pt), делать закрытый репозиторий публичным или разворачивать публичный эндпоинт;
  • коммитить в main, делать force-push или мёржить PR (код попадает в main через проверенные, squash-мёрженные PR с зелёным CI);
  • редактировать что-либо существующее в evals/ (заморожено) или сам оценщик: kev/experiment.py: EVALUATOR_FILES, гейты, kev/metrics.py, парное чтение из kev/rounds.py. Нужное изменение оценщика — это отдельный PR, проверенный с помощью kev-verify и tests/test_rounds.py, прежде чем на него станет полагаться какой-либо раунд;
  • передавать --allow-test или запускать locked_test вне зарегистрированной стадии подтверждения;
  • помещать любой выход Jev или любую генерацию закрытой модели в обучающие данные;
  • обучать локально (32 ГБ Mac не вмещает эти модели) или запускать два процесса обучения на одной машине;
  • удалять чекпойнт или снимок из тома runs (modal volume rm, shutil.rmtree в контейнере) или отключать снимки у полновесного прогона ("snapshot_fractions": "none") в зарегистрированной спецификации. Полновесные прогоны сохраняют снимки на 0.25, 0.5 и 0.75 своих шагов (kev.experiment.SNAPSHOT_FRACTIONS), чтобы чтение могло найти лучшую точку прогона после его завершения: раунд 19 не смог, потому что единственным промежуточным состоянием была точка возобновления, удалённая при завершении прогона, а лучший чекпойнт AutoJev был на 0.7 эпохи. Снимки 27B — это ~154 ГБ тома на прогон; место — решение Jared, а не сессии. Снимки живут на томе runs (основное); закрытое зеркало на Hub (snapshot_hub_repo в плане или modal_app.py::mirror_snapshots) — это долговременное хранилище для чекпойнта, который стоит сохранить, а не замена: зеркалирование чекпойнтов 27B (~51 ГБ каждый, в закрытый репозиторий вроде jaredpalmer/kev-snapshots) — тоже решение Jared, и никогда в публичный репозиторий.

Если ветвь заблокирована (аутентификация, лимит трат, деплой, который не заработает за 30 минут), запишите, что произошло, и переходите к следующей ветви. Не ждите человека.

6. Устойчивость

  • Файл состояния runs/<session>-state.json (runs/ в gitignore; git add -f его в исследовательской ветке): базовая линия и авторизация, показания трат с временами UTC, каждое исследование с его spawn-идентификаторами, границей и статусом, запущенные и выгруженные чтения, кандидаты, PR, ожидающие решения. Обновляйте его после каждого запуска, выгрузки и чтения и коммитьте вместе с разделом PLAN.
  • Отсоединённые задания. Исследования запускаются на развёрнутом приложении и переживают локальный клиент; локальная ошибка после study могла всё же запустить прогоны, поэтому запустите modal container list перед перезапуском и никогда не перезапускайте под тем же именем исследования. Пробы и бенчмарки запускаются с --detach.
  • Наблюдатели — это локальные процессы, и они умирают вместе с машиной или сетью. Запускайте их под nohup и caffeinate; перезапускайте watch после любого прерывания (он возобновляется со своего состояния). Он сам повторяет попытки при ошибках DNS и соединения; собственное исключение прогона — это сбой, и о нём сообщается.
  • Прогоны с истёкшим таймаутом продолжает наблюдатель, а не Modal. Прогоны запускаются с выключенными повторами Modal; когда вызов полновесного прогона завершается по своему таймауту, watch запускает modal_app.py::resume --trial <label>, который порождает следующую попытку (она продолжается с последней закоммиченной точки возобновления) с той GPU и таймаутом, на которые было допущено исследование, и записывает это в runs/<study>.spawn.json (attempts, не более 1 + kev.budget.FULL_FT_RETRIES на прогон, то самое число, с которым вычислялась граница допуска; прогон, чей текущий вызов ещё идёт, никогда не продолжается). Пока наблюдатель не работает, ничего не продолжается: перезапустите его, и он подхватит таймаут. Почему: Modal брал за каждую попытку с таймаутом дважды (сначала таймаут, затем задачу, которую он убил через 30 с), поэтому Retries(2) дал прогону раунда 22 две из трёх попыток, а повтор убитой задачи может стартовать рядом с уже идущей попыткой (scripts/modal_retry_probe.py). У исследования, запущенного до появления реестра, нет счётчика: resume --trial <label> --beyond-bound продолжает его вручную, вне любой границы, и говорит об этом. Две попытки никогда не делят прогон: каждая записывается как ожидающая до своего запуска, и каждая держит аренду на томе kev-leases (heartbeat каждую минуту); новая попытка отказывается, пока аренда другой свежа, а продолжение ждёт до kev.budget.LEASE_STALE (15 мин) после последнего heartbeat убитой попытки, прежде чем запуститься.
  • Обрывы сети убивают локальных клиентов, а не удалённую работу: чтение, чей клиент умер, обычно уже завершилось на Modal; выгрузите его папку с тома (modal volume get kev-runs /<name> runs/<name>) вместо того, чтобы перезапускать его.
  • Неудачный бенчмарк или проба оставляет свою папку на томе; повторите под новым именем.

7. Отчётность

В конце сессии (и в файле состояния по ходу):

  • Раздел PLAN.md каждого раунда несёт его регистрацию, таблицу чтения, результаты подтверждения и вердикт, отрицательный или нет, с путями к отчётам.
  • Обновите PLAN.md «Where we stand» (выпущенные и подтверждённые кандидаты, работающие задания, траты) и «What we have learned», если находка изменилась; добавьте каждый раунд в таблицу Record.
  • Итог сессии в PLAN.md: траты (базовая линия, финальное показание, работающие границы), что ожидает на Modal с точными командами для завершения, инциденты и не более трёх следующих шагов с их доказательствами.
  • Каждое число несёт чекпойнт, набор и партицию, n и путь к отчёту; коммитьте чтения и вердикты, из которых происходят числа (.gitignore держит отчёты, а не дампы предсказаний; добавьте правило для новых папок чтений).
  • Отметки времени: время регистрации и результата — это времена коммитов. Не пишите время в заголовок раньше, чем оно наступило; черновик ночи 3 так сделал, и его отметки нельзя было использовать.

8. Известные подводные камни

  • Лимит частоты создания приложений Modal. Больше примерно трёх отсоединённых modal run в течение минуты падают с “App create rate limit exceeded”, и ничего не запускается. kev.rounds разносит запуски на 60 с и объединяет чтения ветви в один вызов; делайте то же вручную.
  • repo@sha в заданиях бенчмарка раньше сдвигал каждое поле run@suite@name@flags; теперь modal_app.parse_jobs разбирает справа, поэтому закреплённые ревизии Hub безопасны. Наборы и имена не должны содержать @ или ,.
  • Одна выгрузка на исследование. Одновременные выгрузки одного исследования удаляли папки прогонов друг друга; теперь pull_study держит блокировку на исследование. Выгрузка, пока прогоны ещё идут, безопасна и обновляет только незавершённые прогоны.
  • Выгрузки оставляют полные веса на томе. ::pull (и watch) пропускает шарды полных весов (model*.safetensors, ~51 ГБ на чекпойнт или снимок 27B) и точки возобновления; всё остальное скачивается (результаты, строки, head.pt, конфиги). Читайте чекпойнт или снимок на томе: ::benchmarks --jobs "/runs/<study>/<trial>/snapshots/step-<N>/checkpoint@<suite>@<name>". ::pull --weights копирует шарды, когда что-то локальное действительно в них нуждается.
  • Деплой после данных. Образ копирует evals/; лаунчер проверяет только хеши kev/*.py, поэтому прогон, чей файл данных был добавлен после деплоя, падает внутри контейнера. --gpu H200 у study требует приложения, развёрнутого с KEV_GPU=H200.
  • 27B. Только H200 (бэкбон bf16, 55 ГБ резидентно); таймауты исследования до 28 800 с (дельта навыков на 1 эпоху при lr 2e-5 шла около 8.8 с на шаг оптимизатора); чтения fp32 примерно втрое дольше, чем у 9B (спецификация read_timeout: {"27b": 14400}); закрытое чтение требует --timeout 14400 --memory-mb 131072 (спецификация locked_args) на H200 (GPU берётся из gpu спецификации / развёрнутого приложения или --gpu H200 вручную). Каждый прогон с весами bf16 проваливает внутрипрогонный гейт isolation_and_packing (проверка fp32); читайте результаты по строкам и измеряйте обслуживаемую изоляцию в bf16 отдельно.
  • Именование locked_test. Когда внутрипрогонный скрининговый гейт провален, инструмент требует суффикс -ungated (kev-4b-r8-ungated); вердикт всё равно следует зарегистрированному правилу.
  • Таймауты чтения по наборам. modal_app.READ_TIMEOUTS задаёт панелям длинных состояний 7 200 с, документам 5 400 с, transfer-v9 3 600 с, остальным 1 800 с. Один глобальный --timeout раздувает границу допуска каждой задачи в пакете.
  • Допуск по бюджету. Запуск, превышающий свой --budget, выходит до того, как что-либо запустится; перезапустите с бюджетом не меньше напечатанной границы.
  • Внешние серверы однослотовые. Сервер AutoJev отвечал по одному запросу за раз (HTTP 529, пока занят); проверьте чужой эндпоинт перед долгим kev.benchmark --remote и задайте --remote-concurrency по его возможностям. Считайте запросы, которые он отклоняет (например, 422 за пределами его контекста), покрытием, никогда не отбрасывайте их молча.
  • Температура: поставляемая против внутрипрогонной. result.json прогона и его закрытая сводка оцениваются при внутрипрогонной подгонке; релиз поставляет T, которую scripts/calibrate_checkpoint.py записал в head.pt. Первое очное сравнение с AutoJev обслуживало Kev-27B при внутрипрогонной 1.19 вместо поставляемой 1.38, и это пришлось исправлять. Говорите, какую T использует каждое число и где она была подогнана (раздел 3, правило 4: отложенные наборы данных, никогда партиции самого обучающего корпуса).
  • Калибровка длинного контекста. Панель с "by_length": true сообщает точность, ECE, Brier и уверенные ошибки по бинам токенов состояния (от меньше 8k до 64k+ и хвосты 8k+/16k+/32k+), и критерий может гейтить один из них (long.ece_16k_plus.candidate <= 0.05). Токены считаются по записям наборов из чтений, поэтому обе стороны делят бины.
  • Правки набора без новой версии набора. Панель может убрать источники, задачи или (закрытый, зарегистрированный по хешу) список id или источников с обеих сторон (exclude_sources, exclude_tasks, exclude_file), а панель только для отчёта помечается "optional": true, чтобы отсутствующее чтение отчёта никогда не делало кандидата неполным. Выбирайте исключения по достоверности меток до любого чтения под ними (раунд 24 взял их из аудита 2026-09-27) и никогда не коммитьте закрытый список.
  • Ёмкость рабочего пространства. Рабочее пространство запускало не более примерно десяти GPU-контейнеров одновременно; ожидающие контейнеры — это ёмкость, а не баг, поэтому не перезапускайте их.
  • Малые наборы. Охранник на 89 или 144 вопросах не может различить порог в 2–3 п.п.; гейтите их через объединённую панель.
  • Чтения Jev падают на полпути при 503 от шлюза; перезапустите всё чтение под новым именем, а не сшивайте частичные строки.
  • Данные с мягкими целями. Когда пишете сборщик, проверьте несколько записей на глаз: target суммируется в 1, а масса метки не меньше 0.5, если только запись не неотвечаемая (kev.data.none_pair однажды обучился на нулевой массе мягких целей, исправлено в #60).
  • Перенаправляйте вывод modal run ...::study в лог-файл; фильтр может скрыть SystemExit, который объясняет, почему ничего не запустилось.