В этой статье хочу рассказать как я разрабатывал агента, который принимает запросы из телеги и создает страницы на WordPress + Elementor, не путает проекты, не трогает прод и не собирает каждый раз дизайн рандомно.
То есть мысль перед созданием была следующая – есть Elementor MCP, есть агент, есть запрос пользователя, а значит, агент может вызвать нужные методы, добавить контейнеры, виджеты, текст, кнопки и готово. Спрашивается, почему бы и не сделать такого агента, удобно же.
Но по факту все, конечно же, оказалось сложнее.
Чтобы это начало работать нормально, пришлось сделать отдельный навык – очередь задач, SQLite, CLI-проверки, правила для телеги, валидаторы, перенос бета в прод и отдельный компилятор на Python.
Так что расскажу все по порядку, возможно, кому то пригодится.
Тут будет по сути хронология работы с агентом, так что идем от начала до сегодняшнего дня.
Началось не с фигмы и не с компилятора
У нас есть несколько сайтов на WordPress + Elementor. Периодически в телегу прилетает обычный рабочий запрос, по типу «сделай страницу», «поправь блок», «создай лендинг», «нужно по документу собрать страницу».
К этому моменту мой агент в OpenClaw уже умел делать соседние задачи – писать тексты, готовить SMM, работать с изображениями, публиковать анонсы. Поэтому первая идея была такой – раз есть Elementor MCP-инструменты, пусть агент просто вызывает их и собирает страницу. Собственно, так он и делал и, естественно, это не работало так как надо.
Когда запрос один все выглядит нормально, но как только появляются реальные пользователи, несколько проектов и риск задеть прод все начинает сыпаться.
Нужно было настроить агента так, чтобы он собирал страницу по определенным правилам – в нужном окружении, по очереди, с проверками и понятным состоянием.
Так появился навык в отдельном пространстве, куда главный агент делегирует именно эти задачи под названием “web42-elementor-builder”

Первая версия
Навык начинался как обычный scaffold:
-
SKILL.md – главный файл с правилами;
-
settings/triggers.yaml и settings/intents.yaml – чтобы понять, какие фразы запускают какой workflow;
-
skills-map.yaml – карта intent → workflow;
-
workflows/ – YAML-файлы для операций со страницами, контейнерами, виджетами, стилями и публикацией;
-
modules/ – отдельные инструкции для окружения, роутинга, резолва страниц, валидации;
-
scripts/ – первые Python CLI для задач, окружений и SQLite.
На этом этапе важнее всего было принять архитектурное решение – агент не должен сам читать и менять состояние, все операции с задачами, окружениями и базой идут только через CLI.
Живая отладка быстро сломала красивую схему
29 июня я проверял навык в телеге с двумя пользователями, оба попросили создать страницы и тут выяснилось, что scaffold не вывозит.
И первый баг был самый неприятный, задача первого пользователя могла тупо потеряться. То есть, пользователь просит страницу, агент спрашивает проект, ждет ответа, а task record создает уже потом. Кроме того, пока первый пользователь думает, второй тоже отправляет запрос и агент создает активную задачу для второго, а первый фактически проваливается между шагами.
Так что сначала надо было настроить порядок операций. Task record нужно создавать до любого вопроса пользователю, даже если проект еще неизвестен, в задаче фиксируется плейсхолдер вроде pending_project, то есть пользователь сразу занимает место в очереди и его запрос больше не теряется.

Второй баг был про дубликаты – один пользователь мог запустить сразу несколько задач. Поэтому, я добавил проверку на уровне db.py: перед созданием новой задачи вызывается get_user_open_task(telegram_user_id). Если у пользователя уже есть active или queued задача, новая не создается.
Третий баг хорошо показал логику поведения агентов – даже когда навык уже существовал, агент все равно пытался делать прямое обращение к МСР
И поэтому жестко зафиксировали правило – для Web / Elementor задач главный агент не строит страницы напрямую, а делегирует ее ИИ-верстальщику внутри которого уже workspace-elementor task routing должен пройти через навык и CLI. Прямые MCP-вызовы до маршрутизации – обход системы.
Один вопрос за раз
Еще один типичный сбой – compound-вопросы.
Агент спрашивал сразу все – какой проект, какой источник, какой стиль, публиковать ли в бету или прод. Вроде бы это правильно для задачи, но в очереди задач это ломает состояние. То есть, пользователь отвечает на три вопроса сразу, агент уже переключился на другую задачу, часть ответа относится к текущему шагу, часть к будущему, часть вообще еще нельзя применять.
Поэтому появилось правило одного ближайшего блокера “если не хватает проекта – спросить только проект; если проект есть, но не выбран источник – спросить только источник”, короче просто не забегать вперед.
Для этого появился workflow_cli.py next-question. Агент больше не сочиняет формулировки сам, он спрашивает CLI, какой вопрос сейчас разрешен и отправляет только его. Это выглядит менее эффектно, зато система становится воспроизводимой.

Агент должен продолжать работу, пока нет настоящего блокера
На той же отладке всплыл обратный перекос.
Агент сделал внутренний шаг – сохранил метаданные, прочитал Google Doc, извлек заголовок и остановился с сообщением вроде “продолжаю”, хотя по факту для пользователя это бесполезный ответ, как и для workflow.
Я разделил состояния на те, которые могут останавливать выполнение, и те, которые обязаны вести дальше. Условно – waiting_*, blocked_*, done_* могут быть причиной остановки, а ready_* – нет. Если задача в активе и следующий шаг можно выполнить без пользователя, агент должен продолжать.
Позже из этого вырос модуль long-running-progress.md, то есть сообщать о долгих операциях можно, но progress-only финальный ответ запрещен. Грубо говоря “если можно идти дальше – надо идти дальше”.
Бета по умолчанию, прод только через перенос
Отдельная тема – окружения.
Есть бета и прод и понятное дело нужно его ограничить, чтобы все тесты были в бете, поэтому – create_page идет в бету по умолчанию, а прод разрешен только через отдельный transfer workflow. Да, да, супер база, но вдруг.
Для этого добавили:
-
env-project-guard.md – проверку проекта и окружения перед операциями;
-
production-transfer.md – порядок переноса;
-
transfer_cli.py – export из beta и import в production;
-
workflow_cli.py guard-production-operation – блокировку прямых правок на production.
Ключевой момент – перенос это не пересборка. Нельзя собрать заново примерно то же самое на рабочем сайте. Нужно взять проверенный результат из беты и перенести его, а перед переносом – обязательно явное подтверждение.

Ошибка с отступами Elementor
Мы проверяли одну из созданных страниц и нашли системную проблему с отступами.
Агент применял padding и margin к контейнерам. В Elementor эти ключи относятся к widgets, а у контейнеров другие настройки – padding, margin, css_classes, flex_gap, grid_gaps. У widgets – padding, margin, cssclasses.
То есть агент использовал неправильный слой API. После этого появился модуль elementor-spacing-builder.md, в нем зафиксировали матрицу container vs widget controls. Модуль стал обязательным перед сохранением Elementor layout.
Здесь был еще один важный урок – агент смотрел в JSON/export и писал, что spacing применен, но браузер мог рендерить иначе из-за темы, каскада CSS или особенностей Elementor. Поэтому проверки только по JSON недостаточно, нужна связка schema-compatible settings + exported Elementor data, а в идеале еще и браузерная проверка computed styles.

Финальный ответ пришлось вынести в отдельный CLI
Через неделю стало понятно, что агент смешивает две разные вещи – workflow questions и final handoff.
Один инструмент должен отвечать на вопрос “что спросить дальше?”, а другой “что показать пользователю, когда задача завершена?” Если смешать это в голове агента, финальный ответ превращается в смесь URL, внутренних статусов, task ID, вопросов и отладочных деталей, поэтому появился result_cli.py.
workflow_cli.py отвечает за шаги и вопросы, result_cli.py render-final собирает user-facing результат из task state и metadata – preview URL, edit URL, применяемые модули, evidence. Внутренности БД и технические трассировки в обычный ответ не попадают.
Разделение получилось небольшим, но полезным – стало меньше путаницы в том, что агент должен делать дальше и что он должен показать пользователю.
Quality gate нельзя доверять словам агента
Еще через неделю добавили отдельный quality_gate_cli.py, потому что агент может сказать “проверено”, не проверив. Иногда он считает, что если шаг был в плане, значит он выполнен, а иногда видит валидный JSON и делает слишком широкий вывод.
Поэтому passing quality gate может поставить только CLI – агент запускает проверку, получает passed или failed, и дальше действует по результату. Self-asserting quality gate запрещён. Quality gate проверяет наличие обязательных артефактов и evidence, а для Figma-сценариев – strict compiler artifacts, asset binding, visual/pixel evidence и repair state, где это требуется от воркфлоу.

Figma оказалась сложнее обычной Elementor-сборки
Изначально создание страниц из гугл дока было основным сценарием – документ дает контент, style audit берет референсную страницу, агент собирает структуру в Elementor.
Style audit и widget/template extraction обязательны для того, чтобы переиспользовать стиль проекта и не изобретать компоненты заново.
Но вот второй сценарий оказался намного сложнее – Figma и Elementor описывают страницу по-разному. В Figma есть дерево нод, координаты, компоненты, фреймы, варианты, vector paths, image fills, а в Elementor – контейнеры, widgets, flexbox/grid, WordPress media, responsive settings, то есть прямой маппинг “нод → виджет” быстро ломается.
Первые версии компилятора шли через упрощенные представления:
В 6 версии был scene.json и из Figma REST JSON делалось упрощенное дерево, потом deterministic compiler с self-heal пытался получить Elementor structure и проблема тут, что при упрощении терялось слишком много информации.
В 7 пробовал гибрид с JavaScript и Elementor MCP widgets.mjs – это добавило второй runtime и усложнило отладку. Python-окружение агента, Node.js-компилятор, validate-readback – все вместе стало слишком хрупким.
А в 8 сделал разворот strict Python compiler. Figma REST JSON напрямую превращается в Elementor JSON через figma_json_to_elementor.py. Ассеты выбираются через figma_selective_assets.py, потом связываются с WordPress media через figma_bind_assets.py.
Короче, главное правило – никакой ручной сборки Figma-страницы агентом. Никаких скриншотов вместо страницы. Никаких HTML/CSS-подстановок чтобы похоже выглядело.
Если компилятор ошибся – исправляем Python и прогоняем заново.
Зачем понадобился semantic compiler
К 14 версии пайплайн стал таким:

Смысл semantic tree в том, чтобы сначала понять, что перед нами, заголовок, текст, кнопка, карточка, изображение или декоративный элемент, а уже потом решать, как это разложить в Elementor flex/grid. Это важное разделение, потому что если сразу превращать Figma-ноды в Elementor-элементы, компилятор путает смысл и раскладку, а если разделить semantic tree и layout tree, можно отдельно улучшать распознавание и отдельно геометрию.
В v14 также закрепил правило “Figma REST JSON – единственный runtime source”. JSON из Figma-to-Elementor плагина можно использовать только в benchmark mode как golden reference, но нельзя копировать его в runtime, использовать как шаблон сборки страницы или строить страницу по нему.
Это архитектурно правильный pipeline, но он еще не означает полной визуальной эквивалентности Figma и Elementor.
Ассеты из Figma нельзя экспортировать без разбора
18 июля нашел еще один практический баг.
Тысячи VECTOR-фрагментов могли экспортироваться как отдельные SVG. WordPress media library быстро засоряется такими файлами, а сборка тормозится.
Поэтому правило изменил:
-
generic VECTOR paths не считаются самостоятельными SVG-ассетами;
-
COMPONENT/INSTANCE экспортируются как SVG только при explicit export или семантическом имени вроде icon, logo, illustration, badge, symbol;
-
raster image fills дедуплицируются по imageRef;
-
декоративные zero-geometry paths не блокируют build.
Это хороший пример того, что большое количество данных не гарантирует лучшее качество работы. Для production-процесса важно экспортировать ровно то, что нужно странице.
Во что это выросло
На момент подготовки статьи наш “web42-elementor-builder” – это уже не один промпт и не один SKILL.md.
В сумме это тысячи строк Python, десятки workflow/config-файлов и большой SKILL.md. То есть полноценный регламент, а не один промпт.
Фактический объем в рабочей директории сейчас такой:
|
Компонент |
Объем |
|
2258 строк, около 90 KB |
|
|
modules/ |
15 файлов, 5309 строк |
|
scripts/*.py |
19 Python-скриптов, 9787 строк |
|
workflows/ |
15 активных YAML workflow плюс backup/recovery copies |
|
settings/ |
4 файла, 489 строк |
Самые крупные Python-файлы:
-
figma_json_to_elementor.py — 3623 строки;
-
db.py — 1764 строки;
-
workflow_cli.py — 657 строк;
-
template_cli.py — 623 строки;
-
task_cli.py — 466 строк;
-
result_cli.py — 384 строки;
-
figma_pipeline_cli.py — 383 строки;
-
figma_preflight.py — 373 строки;
-
transfer_cli.py — 250 строк.
Цифры понятно еще будут меняться, но они хорошо показывают, что подобная вроде простая задачка в реальной работе довольно быстро превращается в систему с состоянием, правилами, проверками и запретами.
Что все еще не идеально
Figma-компилятор пока работает так себе.
В документации по ограничениям зафиксировано, что на ERP golden pair benchmark coverage около 35.6% по количеству элементов – 528 сгенерированных против 1482 в референсе. Это значит, что benchmark mode правильно блокирует заявления об эквивалентности.
Обычный компилятор можно использовать для разработки и тестов, но слабые места остаются:
-
tree alignment нужно улучшать;
-
breakpoint-specific branch generation для tablet/mobile еще не всегда корректен;
-
сложные Figma-компоненты не всегда мапятся 1:1 в Elementor.
В общем, еще есть над чем поработать.
Что по итогу на сегодняшний день
Сейчас “web42-elementor-builder” – это навык, который держит несколько инвариантов:
-
один пользователь – одна открытая задача;
-
task record создается до любого вопроса;
-
вопросы идут только через workflow_cli.py next-question;
-
состояние задач и окружений меняется только через CLI;
-
beta используется по умолчанию;
-
prod не редактируется напрямую;
-
перенос в prod идет через export/import и подтверждение;
-
style audit и spacing builder обязательны для Google Docs сценария;
-
Figma-сценарий идет только через Python compiler;
-
quality gate нельзя проставить словами агента;
-
финальный ответ рендерится отдельно через result_cli.py.
Главный вывод
В принципе, это довольно банально и понятно, что нужно грамотно строить архитектуру, но просто ради галочки зафиксирую.
AI-агенту нужно четко вносить ограничения в правила, состояние, CLI, guard’ы и проверки. Промпт помогает объяснить намерение, но рабочий навык начинается там, где появляется регламент – кто владеет задачей, где хранится состояние, какие шаги нельзя пропустить, какие действия запрещены, чем подтверждается результат и где агент обязан остановиться.
На этом все. Дайте знать, если интересно чем в итоге все закончится – отчитаюсь.
А если есть мысли что и как можно доработать – буду рад видеть в комментах.
Спасибо за внимание!
Автор: RomanOpenclaw


