Анатомия платформы для AI-приложений, которая доходит до продакшена
Попросите языковую модель сделать React-приложение — и вы его получите. Эта часть работает, и работает уже довольно давно. Дистанцию между этим и продуктом, в который ваши пользователи зайдут завтра — с данными, целыми после вашего следующего деплоя, — не покрыть моделью получше. Её покрывает платформа: изоляция, провижининг, верификация, деплой и восстановление, и каждый из этих слоёв построен так, чтобы агенту было позволено ошибаться, а система всё равно сходилась к результату.
Этот пост — карта. Каждый другой пост в блоге разбирает один из её блоков крупным планом.
Как устроена вся машина
Проект в Adorable рождается из сообщения в чате и живёт как настоящая инфраструктура:
Пять слоёв, пять разных доменов отказа:
- Движок генерации, который планирует и пишет код — и считается ненадёжным по умолчанию.
- Шлюз верификации, который решает, что значит «готово», — и он не LLM.
- Среда исполнения, которая даёт каждому проекту жёсткую изоляцию — отдельный namespace Kubernetes на проект.
- Слой базы данных, рассчитанный на тысячи в основном простаивающих, созданных агентом баз — ветки с копированием при записи, масштабируемые до нуля.
- Модель деплоя, где релиз — это операция над метаданными с обязательным якорем для отката: продвижение ветки, а не копирование.
Остальная часть поста проходит по слоям. Определения всех введённых терминов лежат в глоссарии.
Слой 1: движок генерации сначала планирует, потом печатает
Запрос на новый проект никогда не уходит сразу в код. Стадия системного архитектора читает полный каталог сервисов, текущее состояние проекта и итог прошлой генерации, после чего фиксирует план: какие сервисы нужны приложению (база данных, API-сервер, очередь, CMS), как они интегрируются, и упорядоченный набор задач с объявленными областями файлов и критериями приёмки.
Продюсер — это единственный гибкий цикл вызова инструментов: он сам решает, когда читать контекст, писать код, выполнять SQL и проверять собственную работу, а в его контекст подставляются рабочие примеры интеграции для каждого активного сервиса. Длинную сборку держат связной две дисциплины:
- Сжатие контекста с трекером прогресса. Выпавшая история суммируется, а не теряется, а выполненные действия (записанные файлы, выполненный SQL) переживают усечение — так что сборка в 200 шагов не переделывает шаг 12.
- Защита от спиралей. Диагностические циклы без правок кода прерываются принудительно; раздутые однофайловые выдачи отклоняются ещё до записи на диск.
Архитектура относится к модели как к заменяемой детали: планирование, написание кода и анализ работают на отдельно настроенных моделях, и ни одной из них не доверяется работа следующего слоя.
Слой 2: «готово» решается доказательствами, а не моделью
Продюсер не может сам отметить свою работу завершённой. Заявка «готово» проходит через объективную верификацию — слои, которые порождают доказательства, с которыми агенту не поспорить:
- компилятор TypeScript по каждому объявленному файлу, с проверкой формы импортов и графа модулей;
- контрактная проба: каждая API-процедура, объявленная архитектором, должна отвечать на живом поде без серверных ошибок, и каждая объявленная таблица должна существовать — процедура, которую агент забыл написать, проваливается объективно, потому что контракт — это объявление архитектора, а не воспоминание продюсера;
- собственные юнит-тесты приложения, выполняемые в его поде — каждый скаффолд поставляется тестопригодным с рождения, с раннером тестов и рабочим
npm testс первого коммита; - headless-обход запущенного приложения, когда менялись фронтенд-файлы.
Проваленный слой переоткрывает задачу, помещая доказательство отказа в контекст. В этой цепочке сознательно нет «LLM-критика»: модель, оценивающая модель, сходится к правдоподобному, а не к правильному. Компиляторы, пробы и тесты не торгуются.
Слой 3: среда исполнения — это namespace на проект
Каждый проект работает в собственном namespace Kubernetes — со своими квотами ресурсов, лимитами на ресурсы, деплойментами, сервисами и ingress, сгенерированными из декларативной спецификации. Радиус поражения от любого приложения, в том числе сбойного, ограничен его собственным namespace. Маршрутизация по поддоменам для каждого приложения, wildcard-TLS и веб-интерфейсы для каждого сервиса — всё это выпадает из одного и того же генератора манифестов.
Проект не ограничен одним приложением: он может обрасти соседними приложениями и каталожными сервисами в том же namespace, а платформа сходится к объявленному набору в одной точке-горлышке — fail closed, никогда не скатывается к молчаливому фолбэку.
Экономика простоя — конструктивное ограничение первого класса. Приложения, которые никто не смотрит, замораживаются на уровне cgroup (ноль CPU, почти ноль памяти, мгновенная разморозка); их базы данных масштабируют compute до нуля. Флот выходных проектов стоит ровно столько, сколько должен: ничего, пока он спит.
Слой 4: слой базы данных выбран под агентов, а не под людей
Сущность, создающая здесь базы данных, — это агент, который провижинит сотни короткоживущих, в основном простаивающих баз; такая нагрузка ломает допущения схемы «Postgres на приложение». Ответ — Postgres с разделёнными хранилищем и compute и ветвлением через копирование при записи (self-hosted Neon):
- Ветка на проект, создаётся за O(1) в момент генерации.
- Compute с масштабированием до нуля, с Rust-прокси, который говорит на wire-протоколе Postgres и будит базу прямо посреди соединения — клиенты так и не узнают, что она спала.
- Чекпойнты и форки на момент времени как примитивы — именно это делает две следующие идеи достаточно дешёвыми, чтобы быть значениями по умолчанию.
Дешёвое ветвление — это ещё и то, что даёт агенту кнопку undo для данных: он делает снапшот перед рискованными миграциями и откатывается, когда какая-то из них ломает данные — код И данные вместе, к одному согласованному моменту.
Слой 5: релиз — это продвижение ветки с якорем для отката
Выход в продакшен не двигает никаких данных. Прод — это долговечный таймлайн; дев — его форк с копированием при записи. Каждый релиз закрепляет git-коммит, архивирует выпущенное дерево, ставит тег pre-deploy чекпойнта на таймлайне прода и применяет отложенные миграции — строка в append-only журнале на каждый релиз. Повторные деплои сохраняют таймлайн данных продакшена; замена данных прода на данные дева — это отдельная, явная, снапшотированная операция, никогда не побочный эффект.
Откат следует той же дисциплине: повторно задеплоить закреплённый коммит, опционально восстановить данные к эпохе того релиза — и платформа явно проговаривает, что откату позволено физически удалить: только то, что восстановимо из git или регенерируемо из спецификации.
Соединительная ткань: построено под прерывания
Всё вышесказанное исходит из того, что отказ — нормальный вход. Сборки умирают на полпути (вытеснение подов существует); проход реконсиляции обнаруживает каждую прерванную сборку и восстанавливает согласованную, возобновляемую картину — состояние задания, цепочку задач, локи, поток чата. Секреты идут единственным обязательным путём через Vault в namespace-Secrets, так что перезапуск контейнера не может потерять свои учётные данные. Трекинг ресурсов записывает всё, что создаёт проект, так что удаление получается полным.
Ничего из этого не появляется в демо. Но именно всё это и есть разница между сгенерированным превью и софтом, который можно отдать клиенту — а это и есть тезис следующего поста.
FAQ
Что происходит, когда ИИ пишет сломанный код? Верификация ловит это с доказательством — ошибкой компиляции, проваленной контрактной пробой, красным тестом — и переоткрывает задачу с этим доказательством в контексте. Агент чинит против фактов, а не против собственного мнения о своей работе.
Где на самом деле работает моё приложение? В собственном namespace Kubernetes на платформе: выделенные поды, квоты, ingress и TLS, со своей веткой PostgreSQL. Не в общей песочнице.
Сколько стоит простаивающее приложение? Фактически ничего. Контейнеры замораживаются (ноль CPU), compute базы данных масштабируется до нуля реплик, и оба прозрачно просыпаются на следующем запросе.
Отделены ли данные продакшена от дева? Да — с первого же деплоя у продакшена свой таймлайн данных. Деплои применяют к нему миграции; они никогда не перезаписывают его данными дева. Деструктивное направление существует только как явное, снапшотированное действие.
Можно ли откатить плохой релиз? Каждый релиз закрепляет коммит и ставит тег pre-deploy чекпойнта. Откат восстанавливает код, а опционально и данные, к эпохе того релиза.
Могу ли я забрать своё приложение в другое место?
Проект — это обычный git-репозиторий: Vite + React + tRPC + PostgreSQL, с миграциями в db/migrations. Он синхронизируется с GitHub и запускается везде, где запускается этот стек.