Запуск исследовательской сессии без присмотра
Это рабочая программа для исследовательской сессии, которая идёт без наблюдения человека: ночная сессия или долгая
передача дел в течение дня. Она заменяет ночные промпты (программы раунда 6 и ночи 3, хранятся по git-тегу
research-archive-2026-09-24 в docs/prompts/) и вбирает в себя то, что пошло не так в те ночи. Исследовательские
правила, которые она применяет, — в PLAN.md, «Standing rules for every round»; команды — в AGENTS.md.
Сессия выполняет hill climbing и подтверждает находки по зарегистрированным правилам и оставляет запись, по которой Jared может действовать. Она ничего не публикует.
1. Начало
Потратьте первые 30 минут на чтение, а не на запуски.
AGENTS.md, целиком (команды, замороженные наборы, канонические места, настройки Modal).PLAN.md: где мы находимся, что мы узнали, постоянные правила, политика данных и Next. Для находки, на которой вы хотите строить, прочитайте её доказательства в архиве:git show research-archive-2026-09-24:PLAN.md.- Навыки в
.agents/skills/:kev-modal-study(запуск, наблюдение и выгрузка работы на GPU; прочитайте его Gotchas),kev-verify(доказательство, что изменение кода не даёт регрессий),kev-pr-description(перед любым PR),thermonuclear-code-review. kev/rounds.py(его docstring — это схема спецификации), ближайшую прошлую спецификацию вexperiments/rounds/иkev/autoresearch.py(session).- Саму передачу дел: авторизация (доллары 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 плюс спецификация, закоммиченные вместе до любого обучения или чтения.
- Напишите раздел PLAN.md: зачем (измеренный разрыв и его доказательства), данные (сначала замороженные, с манифестами), ветви, правило (основное, охранные с порогами, подобранными под каждый набор, ранг), стадии подтверждения и бюджет. Используйте постоянные правила; не изобретайте новую статистику для одного раунда.
- Напишите
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отказывают раунду после последнего раунда набора, который ещё его называет. - Новые данные — это новая папка в
evals/сmanifest.json(sha256 по каждому файлу, хеши входов). По политике данных SFT (PLAN.md) закрытые корпуса держат в git только манифест, с записью"mirror", указывающей на закрытый датасет. - ОБЯЗАТЕЛЬНО: каждая обслуживаемая или поставляемая температура берётся из пула отложенных наборов данных, никогда из партиции
обучающего корпуса. Раунд, который читает калибровку (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.jsoncalibration_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”).
- С раунда 21
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, который объясняет, почему ничего не запустилось.