запись в блоге
Компилировать, а не выращивать
Выращивание — фаза открытия, компиляция — фаза продакшена, а понять, что процедура агента сошлась, можно подсчётом, без модели
Есть один момент, который я подсмотрел, наблюдая за агентами не как разработчик, а как дата-инженер — то есть человек, которого профессионально раздражает, когда одна и та же работа делается заново без причины.
Смотришь, как агент в третий раз за неделю выкатывает один и тот же деплой. И каждый раз он заново соображает: так, сначала прогнать тесты, потом собрать образ, потом накатить миграции, потом переключить трафик. Он думает над этим вслух, тратит токены, иногда путает порядок, иногда решает проявить инициативу не там, где надо. А процедура-то давно сошлась. Она не меняется от запуска к запуску. Ты уже знаешь её наизусть, и агент, по-хорошему, тоже.
В какой-то момент я перестал прикидывать на глаз и посчитал. Написал майнер, который читает мои собственные транскрипты Claude Code — все проекты, все сессии, никакой модели в цикле, — и спросил его, какую работу я переоткрываю чаще всего. Первый ответ: deploy. Три проекта, восемь сессий, и за 75% из них стоит одна и та же последовательность команд. Восемь раз агент рассуждением приходил к процедуре, которая перестала меняться много месяцев назад.
Вот в этом и расхождение: мы построили целую индустрию на том, чтобы выращивать поведение агента заново при каждом запуске — там, где его давно пора было скомпилировать.
Два подхода🔗
Если огрубить, задачу с участием модели можно исполнять двумя способами — и сегодняшний инструментарий сильно перекошен в сторону одного из них.
Первый — выращивание (emergence). Даёшь модели автономию, набор инструментов и память — и правильное поведение проявляется в рантайме. Модель сама решает, что делать дальше, сама строит траекторию, сама себя поправляет. Каноничный предок здесь — BabyAGI и AutoGPT, а вся ветка ReAct-агентов и «автономных» фреймворков — прямые наследники. Демо получается завораживающее: смотри, оно само разобралось. В этом вся продажа.
Очевидное возражение: AutoGPT сегодня никто не деплоит, так с кем я спорю? Справедливо. Фронтир сдвинулся, причём сдвинулся в сторону ограничений — у Claude Code, Cursor, Codex есть хуки, скиллы, слэш-команды, субагенты, режимы доступа. По сравнению с автономным циклом образца 2023-го они прямо-таки дисциплинированные. Но тот deploy в моих транскриптах был переоткрыт восемь раз в 2026 году, одним из этих дисциплинированных агентов, а не AutoGPT. Автономия сузилась. Переоткрытие — нет. Ограничивать то, что агенту позволено делать, оказалось совсем другой задачей, чем замечать, что он уже знает, что делать.
Второй — компиляция. Процедура, которая уже сошлась, должна быть заморожена — в воспроизводимый, аудируемый артефакт. Модель вызывается не как водитель всей траектории, а точечно, в явно обозначенных местах, где действительно нужна мысль. Всё остальное — обычный пайплайн, который прогоняется одинаково каждый раз.
Я склоняюсь ко второму и последние полтора года пишу движок ровно под него (zymi). Но эссе не про движок — про то, почему я думаю, что дефолт индустрии выбран неправильно.
Да, это старое доброе разделение workflow vs agent — те же Anthropic в «Building Effective Agents» описали его давно, и я не переоткрываю таксономию. Мой тезис про время, а не про типы: одна и та же задача мигрирует из выращивания в компиляцию по мере того, как её процедура сходится, — и эту миграцию можно детектировать и автоматизировать.
Недетерминизм — это стоимость, а не фича🔗
Недетерминизм — это цена, которую ты платишь, и платить её надо ровно там, где нужна настоящая нестандартная мысль, — и нигде больше.
«Само разобралось» звучит как достоинство, пока ты смотришь демо. В проде это превращается в «каждый раз разбирается заново» — а значит, каждый раз может разобраться по-другому. Ту же задачу оно сегодня решит за 6 шагов, завтра за 9, послезавтра свернёт не туда на пятом. Ты не можешь это воспроизвести, не можешь толком провести аудит («а почему оно вообще решило накатить миграцию до тестов?»), не можешь дать гарантию. Ты нанял гениального стажёра, который каждое утро приходит с полной амнезией и заново изобретает твой рантайм-плейбук на глазах у прода.
Ключевая проблема выращивания не в том, что модель глупая. Проблема в том, что недетерминизм невозможно локализовать. Он размазан по всей траектории. Раз вся последовательность действий рождается в рантайме, то вся она и ненадёжна — а не только та её часть, где реально требовалось суждение.
А требуется оно, если честно посмотреть на типичный «агентный» воркфлоу, в исчезающе малом числе точек. Деплой — это одно живое решение («данные миграции выглядят опасно, позвать человека?») в обёртке из десяти абсолютно механических шагов. Выращивание заставляет модель заново рождать и опасное решение, и все десять механических шагов, каждый раз, с одинаковым уровнем «а вдруг».
Как выглядит скомпилированный пайплайн🔗
Процедура — это код: скомпилированный, замороженный, воспроизводимый, лежащий в гите, как любой другой DAG. Рассуждение — это данные, которые протекают сквозь неё в явно обозначенных узлах. Ты не выращиваешь граф заново — ты выращиваешь его один раз, а дальше исполняешь. (Дата-инженер узнает в этой конструкции dbt — у этой аналогии есть отдельная статья.)
На практике у меня это выглядит так. Пайплайн — обычный декларативный DAG. Большинство шагов детерминированные: дёрнуть shell, сходить по HTTP, вызвать инструмент. Модель появляется только в двух видах швов:
steps:
- id: run_tests
tool: shell # детерминированный шаг, модель не нужна
run: "pytest -q"
- id: assess_migration
agent: reviewer # шов рассуждения: думаем ровно здесь
task: "Оцени риск миграции ${steps.diff.output}"
- id: promote
ask: "Накатывать миграцию в прод?" # шов делегирования: суждение отдаём наружу
depends_on: [assess_migration]
Шаг run_tests не рассуждает — и не должен. Шаг assess_migration рассуждает, потому что оценка риска — это как раз то место, где нужна модель. А ask вообще выносит суждение из пайплайна — отдаёт его вызывающей стороне (человеку или другому агенту). Недетерминизм здесь не размазан — он собран в две подписанные точки, и ты пальцем показываешь, где именно система «думает».
Кстати, приятный побочный эффект: пайплайн, собранный из одних детерминированных шагов, вообще не требует LLM. Ноль вызовов модели, ноль ключей. При выращивании делать опциональным попросту нечего: там модель и есть рантайм.
И да, я слышу это возражение: «так ты переизобрёл shell-скрипт». Почти. Скомпилированный пайплайн нарочно скучный — скрипт с обозначенными швами, в которых модели или человеку разрешено думать. Суть не в артефакте; артефакт — это пятьдесят строк YAML. Суть в том, откуда он берётся: никто не садится писать такие вхолодную. Они добываются из того, что агент уже сделал, — и это меняет, что́ именно нужно строить. Не формат скриптов получше, а петлю, которая замечает, когда скрипт готов появиться на свет.
Что компиляция даёт, а выращивание — структурно нет🔗
Как только процедура заморожена в артефакт, а не рождается заново, несколько вещей достаются бесплатно:
- Явный поток управления. Основной путь, ветвления, гейты и вызовы модели видны до запуска. Ты читаешь процедуру, а не реконструируешь её потом по транскрипту.
- Аудит. Каждое действие — иммутабельное событие в логе с hash-chain (об этой архитектуре у меня есть отдельный текст), так что ты знаешь не только что агент сделал, но и почему — и можешь проиграть прогон заново, не запуская инструменты повторно.
- Локализованный риск. Опасное — только в явных узлах. «Safe to run up to the dangerous step» становится свойством конструкции, а не удачей.
- Опциональность модели. Нет узла рассуждения — нет и вызова LLM.
Чего я при этом не утверждаю: что внешний мир становится детерминированным. Тесты флапают, API дрейфуют, репозитории уезжают под ногами, у инструментов есть побочные эффекты. «Один и тот же вход — одна и та же траектория» звучало бы красиво и было бы неправдой. Компиляция даёт явный, воспроизводимый поток управления и запись того, что было наблюдено и решено, — а не замороженную вселенную.
Честный контраргумент🔗
Тут полагается сказать: «но некоторые задачи и правда не сошлись». Открытое исследование. Отладка нового, невиданного бага. Разведка боем в незнакомой кодовой базе. Там нет процедуры, которую можно заморозить, — её ещё только предстоит нащупать.
Абсолютно верно. И именно там ты хочешь выращивание. Это и есть шов рассуждения — просто широкий.
Я не против эмерджентности. Я против того, чтобы делать её субстратом по умолчанию. Выращивание — это точечный инструмент, который живёт в обозначенном узле, а не воздух, которым дышит вся система. Ошибка не в том, чтобы к нему прибегать, а в том, чтобы оставлять его включённым на всё — в том числе на процедуры, которые сошлись ещё полгода назад и с тех пор ни разу не поменялись.
И ещё одна уступка — уже не в пользу компиляции. Замороженный артефакт надо сопровождать. Если процедура дрейфует быстрее, чем ты успеваешь её перекомпилировать — деплой, который переписывают каждую неделю, — скомпилированный пайплайн начинает гнить быстрее, чем окупается, и живой агент, который каждый раз перечитывает актуальное состояние, оказывается дешевле в поддержке. Компиляция уверенно выигрывает на сошедшемся и стабильном. На сошедшемся, но быстро дрейфующем — вопрос честно открытый, и готового ответа у меня нет.
И это бьёт по моему же заглавному примеру: deploy как раз из тех вещей, которые дрейфуют. Именно поэтому число рядом с ним важнее счётчика. Восемь сессий — это повторяемость; 75% из них с одной и той же последовательностью команд — это стабильность, и компиляцию заслуживает вторая. По одной частоте промоутнулась бы половина моих транскриптов.
Выращивание — это фаза открытия, а не фаза продакшена🔗
Откуда вообще берутся скомпилированные пайплайны? Не из головы же — никто не пишет их вхолодную. Ты берёшь их из наблюдения. Даёшь выращивающему агенту сделать задачу несколько раз, смотришь, как траектория сходится (я называю это hotpath), и компилируешь сошедшийся путь в пайплайн.
Именно этот цикл я сейчас и достраиваю, и в идеале он замкнутый: агент (у меня это Claude Code) работает как обычно → периодически по его логам прогоняется поиск повторяющихся паттернов → сошедшийся паттерн превращается в пайплайн → тот же агент дальше дёргает этот пайплайн как MCP-инструмент, вместо того чтобы соображать процедуру заново. Разведка боем один раз — дальше исполнение.
Честно про статус, чтобы не продавать вапорвар: цикл уже прошёл end-to-end, и та часть, на которую я замахивался сильнее всего — детекция, — оказалась вообще не требующей модели. Пример из первых рук — релиз самого zymi. Процедура давно сошлась, я прогнал её руками десятки раз, так что в какой-то момент просто скомпилировал. Вот она целиком, без сокращений:
name: release
expose:
mcp: {}
inputs:
- name: version
type: string
required: true
steps:
- id: guard
tool: check_clean
- id: bump
tool: bump_manifests
args: { version: "${inputs.version}" }
depends_on: [guard]
- id: check
tool: run_checks
depends_on: [bump]
- id: commit
tool: commit_release
args: { version: "${inputs.version}" }
depends_on: [check]
- id: ship
tool: tag_and_push
args: { version: "${inputs.version}" }
depends_on: [commit]
Скучно, как и обещано. Пять шагов, модели нигде — швов рассуждения в этом пайплайне нет, поэтому релиз zymi стоит ноль токенов и не требует API-ключа.
В логе событий лежат три реальных end-to-end прогона. Каждый занял 30–40 секунд по стенным часам — но почти всё это cargo clippy и cargo test, настоящая работа, которую придётся сделать независимо от того, кто ведёт релиз. Собственно оркестрация пайплайна — guard, bump, commit, tag-and-push — это 136 миллисекунд на четыре шага и ноль токенов. Компиляция не ускорила работу: тесты — это пол, ниже которого не прыгнешь. Она убрала всё вокруг работы — переоткрытие процедуры, трату токенов, шанс сделать шаги в другом порядке завтра.
Но посмотри, чего в shell-скрипте не бывает. expose: mcp: {} — это вся проводка, которая превращает процедуру в инструмент, вызываемый любым MCP-клиентом. Ни серверного кода, ни адаптера под каждый рантайм.
И approval-гейта в пайплайне нет вообще. Инструмент tag_and_push объявляет requires_approval: true в собственном определении — потому что пуш тега запускает публикующий workflow, и это точка невозврата. Гейт принадлежит опасной операции, а не процедуре, которая её вызывает: он сработает в любом пайплайне, где этот инструмент когда-либо появится, и ни один будущий автор пайплайна не сможет забыть его добавить. Через MCP это рендерится как approve/deny-форма в вызывающем агенте, под zymi run — как терминальный промпт. В обоих случаях прогон паркуется: пауза — это событие в логе, и пайплайн возобновляется с этого места, когда придёт ответ, хоть через десять секунд, хоть завтра утром.
Версия, из которой пишется этот текст, выпущена ровно так. Я сказал агенту «выпусти», он дёрнул пайплайн как инструмент, остановился на ship за подтверждением — и всё.
Майнер существует и открыт: hotpath-miner. Он сам является скомпилированным пайплайном — детерминированный разбор и подсчёт по транскриптам, никакой LLM в цикле детекции, — и всю работу в нём делает один фильтр: повторяющийся запрос считается компилируемым, только если за ним стоит стабильная последовательность команд; работа, которая повторяется, но каждый раз идёт по-новому, остаётся интерпретируемой. Порог повторяемости у меня — дешёвый триггер для ревью, а не доказательство, что процедуру безопасно замораживать.
deploy из первого абзаца — верхняя из именованных процедур его зелёного списка (выше стоят только голые командные комбо — git- и shell-тики, которые скилл сам велит агенту не продавать). Но важнее строка из красного: save-memory — 5 проектов, 11 сессий, шире и горячее самого deploy, и всё равно отвергнут, потому что стабильной последовательности под ним нет. «Запиши это в память» — настоящий повторяющийся интент, у которого каждый раз своя форма; он остаётся интерпретируемым. Майнер, который не умеет говорить «нет», просто переклеивает на всё ярлык «компилируемо», и красный список — единственное доказательство, что фильтр работает, а не украшает счётчик.
Сам майнер теперь устроен по той же схеме. Два его решения никогда не были подсчётом: назвать повторяющуюся тему, для которой у лексикона нет слов, и переспорить фильтр, когда настоящая процедура чуть-чуть не дотянула до планки стабильности. Напрашивающиеся решения — ровно те два дефолта, против которых всё это эссе: захардкодить ещё больше ключевых слов или ввинтить в цикл headless-вызов LLM. Вместо этого assisted-вариант пайплайна собирает эти два решения в два обозначенных ask:-шага: прогон паркуется, отдаёт вопрос тому, кто его запустил, — мне за терминалом или ведущему агенту через MCP — и продолжается с ответом. Детекция остаётся детерминированной и бесключевой; оставшееся суждение не размазано по коду, а собрано в два узла, на которые можно показать пальцем. Те же швы, что в YAML выше, — применённые к инструменту, который за них агитирует.
Первый настоящий улов прошёл весь путь: процедура поднятия сервера с нуля, которую я перелопачивал в пяти отдельных сессиях — диск, файрвол, пакеты, ssh-ключи, харденинг sshd, systemd-юниты, TLS, веб, — найдена в логах, дистиллирована интерактивным агентом по рубрике promote-hotpath, оформлена в пайплайн с помощью zymi-skill, выставлена через MCP и вызвана под человеческим approval-гейтом. Человеческим остаётся суждение в середине: кандидата выбираю я, агент вытаскивает сошедшуюся процедуру из грязной истории (это настоящая работа для шва рассуждения — ровно там, где модель и нужна), а результат я апрувлю до того, как он коснётся прода.
Оговорка, которую я обязан сделать: всё это обкатано на моей собственной повторяющейся операционке. Я пока не показал, что кто-то другой возьмёт свой произвольный грязный трейс и надёжно получит из него полезный пайплайн без меня рядом. Ставка в том, чтобы эту ручную долю сокращать — тогда компилируются не только процедуры, которые я случайно заметил, а все, что реально повторяются. Вот здесь и проходит граница между «делайте разумно» и инструментом: не совет чередовать выращивание с компиляцией, а петля, которая сама переносит работу из фазы открытия в фазу продакшена.
То есть у процесса две фазы. Выращивание — фаза открытия, компиляция — фаза продакшена. Эмерджентность нужна, чтобы найти процедуру, детерминизм — чтобы её эксплуатировать.
Индустрия эти две фазы схлопнула: она гоняет фазу открытия в проде, с полным ценником нестандартной мысли за насквозь стандартную работу. Большая часть того, что сегодня называют «агентами», — это код фазы открытия, случайно оставленный работать в продакшене. И правильный ход — не выкинуть выращивание, а скомпилировать те его части, которые уже сошлись, и оставить живую мысль только там, где ей и место.
Записанные скиллы — тот же инстинкт🔗
В июле 2026-го Anthropic выкатили Record a Skill в Claude Cowork: ты записываешь экран, пока делаешь задачу, проговариваешь вслух свои решения — и Claude упаковывает эту демонстрацию в скилл, который может прогнать заново. Я читаю это как тот же инстинкт, проявившийся с другой стороны: процедура, найденная во взаимодействии, должна оседать в переиспользуемый артефакт, а не переоткрываться вечно.
Разница — в источнике и в том, чем всё заканчивается. Там источник — записанная человеческая сессия: надо самому заметить, что работа повторяема, и не забыть нажать «запись». Здесь источник — собственная история выполнения агента, которая и так лежит на диске, независимо от того, заметил её кто-нибудь или нет. И на выходе там — инструкция для следующего прогона модели, а здесь — пайплайн с явными вызовами инструментов, обозначенными швами рассуждения, approval-гейтами, логом событий и MCP-интерфейсом; его можно ревьюить как код, а если швов рассуждения в нём нет, он вообще исполняется без модели.
Записанные скиллы и скомпилированные пайплайны — соседние идеи. Мне интересен компиляторный цикл между ними.
«Лог — это агент»: тот же субстрат, противоположное направление🔗
За два дня до выхода этого текста Йохеи Накаджима опубликовал эссе о том, что обвязка вокруг замороженной модели — не временные леса, а постоянный орган, и вывел из нейронауки почти в точности тот субстрат, который я описывал в прошлой статье: append-only лог как источник правды, состояние как проекция над ним, replay, форки, консолидация как явная операция. К одному и тому же логу мы пришли через разные двери: он — от памяти и непрерывности, я — от недетерминизма и воспроизводимости. Когда две независимые линии рассуждений упираются в одну и ту же архитектуру, это обычно признак аттрактора, а не вкусовщины.
Но на субстрате согласие и заканчивается, и имя его исследовательской программы отмечает развилку точнее некуда: «The Log is the Agent» (arXiv:2605.21997, рантайм ActiveGraph). В этой школе запись и есть рантайм: поведения подписаны на неё, реагируют на новые события, и траектория рождается из этого реагирования. Моя позиция менее симметрична, чем просит лозунг: лог — это не агент. Лог — это протокол процедуры, которая уже сошлась; агент — это то, что нужно, пока она не сошлась. Выращивание пишет в лог в фазе открытия; компиляция вычитывает из него замороженный путь для фазы продакшена. Запись одна — направление власти противоположное.
А две проблемы, которые его эссе честно оставляет открытыми, — ровно те, на которые у компиляции есть ответ. Отбор — что решает, какой опыт стоит сохранять, — он называет открытой проблемой, на которой держится вся архитектура, и предупреждает: «LLM, рефлексирующий над прошедшим днём и выбирающий, что запомнить, — это функция отбора, причём дорогая и болтливая». Согласен — и hotpath-miner построен ровно в обход этой ловушки: отбор здесь — это подсчёт. Повторяемость плюс стабильная последовательность команд, без модели в цикле детекции. И у него же: скиллам негде жить — «у выученной процедуры нет очевидного дома, кроме, снова, контекстного окна». Про этот дом и есть всё нынешнее эссе: дом выученной процедуры — скомпилированный пайплайн, с подписанными швами и приделанными гейтами.
Побочное следствие: скомпилированный артефакт — это не агент🔗
И последнее, что вытекает из всего этого почти механически.
Если продукт — это скомпилированная процедура, а не автономный агент, то он не претендует на кресло «быть ассистентом» — это инструмент, который втыкается в тот агентский рантайм, который у тебя уже есть.
Инструментарий, построенный вокруг выращивания, делает рантаймы, которые хотят быть центром: собери из наших кубиков своего ассистента. Компиляция делает компоненты, которые может позвать любой центр. Для меня универсальный разъём тут — MCP: пайплайн выставляется наружу как инструмент, и его одинаково зовёт хоть Claude Code, хоть Cursor, хоть чей-то самописный LangGraph. Мне не нужно писать обёртку под каждый рантайм и не нужно строить свой автономный агент — это значило бы снова вернуться к тому самому дефолту, против которого вся затея.
Скомпилированная процедура agent-agnostic по построению. Выращенный ассистент — нет.
Вместо вывода🔗
Это не «агенты — плохо». Это: недетерминизм должен стоять там, где реально происходит мысль, и нигде больше. Выращивание — прекрасный способ найти процедуру и ужасный способ её исполнять.
Компилируй то, что сошлось. Выращивай то, что ещё нет. И знай, глядя на систему, в какой именно точке она думает.
Движок, в котором я это всё обкатываю, — zymi, альфа, Rust + YAML + Python; майнер — hotpath-miner, а скилл, который помогает агенту писать сами пайплайны, — zymi-skill. Вопрос, на который мне правда хочется услышать ответ: где бы вы провели границу между «уже сошлось» и «ещё нет» — и какому сигналу доверили бы проводить её за вас?