Дисклеймер: в статье упоминается Meta — организация, признанная экстремистской и запрещённая на территории РФ. Статья — не прогрев и не реклама. Все логи, цифры и скрины — из реальных прогонов на тестовом аккаунте.
Все ключи, телефоны и названия в примерах изменены; код, схема, логика и скрины‑ из рабочего проекта заказчика, и строго с его разрешения! Статья не является рекламой/самопиаром и тому подобное.
Пролог: живая встреча, бэклог и бесплатная модель
Всем привет! Немного вступления: ИИ агент заказчика работает в проде уже четвёртый месяц: ночные заявки в НЕЛЬЗЯgram Direct, диалоги, анкеты, сделки в amoCRM. В прошлой статье — разбор Reasoning Lock и анонсы продолжений. Одна из договорённостей с заказчиком — ветка обработки комментариев под постами: человек пишет «+» или «сколько стоит?» под публикацией = агент отвечает ему приватным сообщением в Direct. Обещание висело в бэклоге и ждало своего часа.
Час настал сам. 16 сентября вечером на OpenRouter выкатили новую стелс‑модель: stealth/union‑alpha. Провайдер анонимен, релизный штамп на странице 16 сентября, то есть сутки стелсу к моменту публикации этой статьи. Факты про данную llm: мультимодальность, tool calling, GPQA Diamond 90.9%, и главное — цена ровно ноль (пока что).

Была и цифра, которая меня спугнула: контекст 262K. Вчетверо меньше, чем у прошлого стелса, которому я скормил 60 млн токенов — подробнее в статье https://habr.com/ru/articles/1076084/. Ту прошлую задачу — эксперимент не потянет... а вот задание, и уже реальный бэклог внедрения одного контура пожалуй справиться.
Сеголня с утра я приехал к заказчику в офис — живая встреча по стратегии дальнейшего развития проекта. Вернулся с согласованным бэклогом, сел за работу и решил продолжить метод, обкатанный на прошлом стелсе: бенчмарки не показывают главное, реальная задача — показывает. Не «прогони модель по тестам», а «пусть она сделает настоящую фичу из живого проекта».
Разница с прошлым разом как я сказал ранее, в масштабе и ставках. Работы для модели = одна ветка комментариев, не весь воркфлоу. Но рабочая папка проекта довольно таки полная: живой экспорт воркфлоу, тулзы, патчи, дневники, документы. Открываю Visual Studio Code с Cline — и понеслась.
И ещё одно решение, необходимое на мой взгляд и правильное: не трогать аккаунт и воркфлоу заказчика. Реальный воркфлоу остаётся в проде нетронутым, а эксперимент переезжает на тестовую среду = мой собственный тестовый аккаунт инсты.
Спойлер: именно это решение и подложило мне «палки в колёса», которую я не учёл. Но об этом чуть позже.)
ТЗ‑запрос: 28 200 токенов только на вход — роль, бэклоги и экспорт живого воркфлоу.

Что выдала модель за один заход
Выход llm = семь файлов, и это получается сборочная система:
logic.cjs: тут чистые функции: роутинг событий, расчёт допуска, форматирование ответа, валидация API. Вынесены из нод специально они чтобы тестировались без n8n;
build.cjs — это сам сборщик: читает живой экспорт воркфлоу, вшивает функции в сodeноды, добавляет 15 новых, пишет готовый JSON и держит резервную копию исходника (и отказывается перезаписывать её, если она отличается = защита от затирания);
test.cjs: тут 16 тестов на реальном пейлоаде вебхука;
README.md : тут уже файл «РАЗБОР ВЕТКИ КОММЕНТАРИЕВ» на 38 КБ, где модель сама себе вынесла недостатки.
Итоговый JSON: все исходные ноды без изменения + 15 новых. Импортируется неактивным, пути вебхуков сохранены.
Занятная фишка, за которую я готов простить модели многое = первый раздел её самоанализа называется «Главное и признание недостатков».

«....Вы правы насчёт промпта. Сейчас это копия общего Direct‑промпта с дополнением для комментариев. Полноценного отдельного сценария для „+“, цены и даты рождения я не сделал....» — признание самой LLM
Стелс‑модель, которая сама пишет себе список косяков до того, как её «аудитишь» на мой взгляд не хило подкупает. Осталось проверить, не врёт ли она в остальных шести файлах!
Архитектура за 60 секунд
``` Webhook (Direct и Comments приходят в один поток Meta) └─ IG: Route Events (Code) — раздаёт igEventKind: direct / comment ├─ direct → старая Direct-ветка (не тронута ни строчкой) └─ comment → One At A Time (цикл, пачка по 1) → Prepare (допуск: вкл/выкл, тест-гейт, чёрный список, окно 7 суток) → Eligible? → Claim (Redis INCR — атомарный захват comment_id) → First Claim? → Record Pending (журнал в Redis) → AI Agent (модели и тулзы общие с Direct, память отдельная поднода) → Prepare Reply (чистка тегов, фолбэк, лимит 900 символов) → Send Private Reply → Validate API Result → Record Accepted → назад в цикл за следующим комментарием ```
Хочу отдельно пояснить три решения, которые на мой взгляд являются самыми сильные в данной сборке:
1. Private Reply адресуется по комментарию, а не по пользователю. Тело запроса:
```json { "recipient": { "comment_id": "18120835172314431" }, "message": { "text": "Ответ клиенту" } } ```
Один комментарий = один приватный ответ, окно 7 суток, никакого публичного ответа под постом. Продолжение беседы — только когда человек сам напишет в директ, и это уже отдельное исполнение старой ветки, а не хитрый переход между агентами.
2. Дедупликация через Redis INCR, а не GET‑SET. Если на один комментарий одновременно набегут два исполнения, оба увидят «ключа нет» и оба отправят ответ. INCR атомарен: 1 — мой, 2+ — уже взяли.
3. Философия «не отправим дважды» важнее «обязательно отправим». Ошибка после захвата комментария блокирует автоповтор: ключ в Redis остался — значит второй раз агент не напишет, разбирайся руками. Осознанный обмен гарантии доставки на гарантию отсутствия дублей, так сказать= для агента ателье это правильный обмен.
Кстати приятный бонус, который подтвердился живым прогоном: ID автора комментария и ID отправителя в Direct один и тот же)). Значит, ключ памяти ХХХХ_ig:{userId} у обеих веток общий: комментарий ‑приватный ответ ‑человек пишет в директ — агент помнит, о чём был комментарий. Об этом подробнее в тестовом прогоне 1.1, где я едва не похоронил эту красивую схему собственным выводом.
Аудит: где прятались скрытые «косяки»
Я не поверил ни одному зелёному значку — скепсис или недоверие, да и банально страх:‑) Просмотрел пересборку из исходников (совпала с выданным JSON байт в байт, кроме случайных ID), 16 тестов, синтаксис всех Code‑нод а, главное, прогон функции прямо из JSON с имитацией входов n8n. Результаты: сборка честная, все исходные ноды не тронуты. Но находки были:
Находка (номер) | В чём «подвох» |
1 | Промпт ветки = копия Direct‑промпта на 12 000 символов с тегами VERDICT/TASK/STAGE Отдельный сценарий «+»/цена/дата рождения лежит в РАЗБОРЕ (!), но в JSON не внесён; Агент комментарием отвечал бы «как в директе», включая технические теги |
2 | При выключенном тест‑режиме пользователи из чёрного списка Direct получали бы ответы под постами так как нода «Чёрный список» на новом маршруте не выполняется.. что означало забаненный в директе клиент получает заботу в комментах |
3 | Чистка ответа ловит |
4 | Private Reply выставлен на Graph API v25.0, а в воркфлоу заказчика директ ноды живут на доказанном v22.0 (как бы мелочь, но всё же) |
5 | Захардкоженный ID бизнес‑аккаунта взят из тестовой и не совпал с реальным из живого вебхука = с этой константой молча выкинул бы 100% реальных комментариев |
6 | Allowlist собран из ID ноды «Тест Аккаунт» директа, а реальный тестюзер в него не входил; в фикстуре модель склеила |
Отдельная честность про себя: после прогона 1 я выдвинул одну гипотезу: ID комментатора и ID в директ живут в разных пространствах, общая память не сработает. прогон 1.1 её убил за пять минут: я написал в директ и в комменты с одного аккаунта, и, как итог: ID совпали до последней цифры. Гипотеза отозвана. Мораль серии: константы из фикстур и красивые теории сверяются только живым прогоном и это верно и для модели, и для аудитора.
Код: что в итоге поехало в «бой»
Роутер событий — это самая первая нода после вебхука. Компактный вариант боевой версии:
```js const accountId = String(entry.id); const chatId = String(v.from?.id || ''); const commentId = String(v.id || ''); // Только наш бизнес-аккаунт — РЕАЛЬНЫЙ ID из живого вебхука, а не из фикстуры if (accountId !== '17841479739340657') continue; // Эхо-петля (сам себе не отвечаем) и пустой автор if (!chatId || chatId === accountId) continue; // ID — только строки из цифр: JSON-число > 2^53 тихо округляется if (!/^\d+$/.test(chatId) || !/^\d+$/.test(commentId)) continue; // Живой текст, публикации FEED/REELS, не вложенный ответ, не удаление if (typeof v.text !== 'string' || !v.text.trim()) continue; if (!v.media || !['FEED', 'REELS'].includes(v.media.media_product_type)) continue; if (v.parent_id || v.verb === 'remove' || v.verb === 'delete') continue; ```
Допуск (Prepare, боевая версия)‑ конфиг, который закрыл находки 1, 2, 6 одним движением:
```js const config = { commentsEnabled: true, testMode: false, // боевой режим: отвечаем всем allowedUserIds: ['936850522443774', '826426153486930'], // тест-гейт на будущее blacklistedUserIds: ['1344359104565035', '1686784379107626'], // чёрный список из Direct activePrompt: КОРОТКИЙ_ПРОМПТ_ВЕТКИ // ~1.3K символов вместо копии на ~12K }; ```
А внутри prepare три строчки, которые стоят всей системы допуска:
```js const allowed = config.commentsEnabled === true && (!config.testMode || config.allowedUserIds.includes(j.chatId)) && !(config.blacklistedUserIds || []).includes(j.chatId); ```
Чистка ответа перед отправкой = модель умеет markdown, а уж НЕЛЬЗЯgram обычный текст:
```js text = text.replace(/```[\s\S]*?```/g, '') // блоки кода .replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '$1: $2') // [текст](url) → текст: url .replace(/\[[A-Z_]{2,}(?::[^\]]*)?\]/gi, '') // [TASK:NONE] и [END_DIALOG] .replace(/\*\*(.*?)\*\*/g, '$1').trim(); if (!text || Array.from(text).length > 900) text = ЗАГЛУШКА; // лимит Instagram ```
Да, кстати, последняя строчка является та самая защита, которая выстрелила в прогоне 2: агент упал, а мне в директ всё равно пришла вежливая заглушка вместо тишины или технического мусора.
Три прогона
Прогон 1. Всё зелёное... НО...ноль результата

Импортировал, оставил комментарий под рилсом в тестовом аккаунте. Исполнение зелёное, ноды с галочками, но закончилось всё на Comments: Eligible?. Ответа мне в личку нет.
Ну блин, так бывает то в n8n: исполнение не упало = оно честно отработало замысел, который не совпал с намерением. Читаю вход Eligible?, а там готовый вердикт: skipReason: DISABLED_OR_NOT_ALLOWLISTED. Тест‑гейт модели был собран из ID ноды «Тест Аккаунт» директа = мой реальный тестовый юзер в этом списке не жил — находка номер 6 таблицы выше). Заодно всплыла находка 5 таблицы: захардкоренный ID аккаунта из фикстуры не равен реальный из вебхука = гвард с той константой ронял бы вообще всё.
Лечится не отладкой, а чтением одной строки skipReason и правкой конфига (Prepare v2 выше). Полчаса примерно)
Прогон 1.1. Пять минут, которые убили мою гипотезу
Пишу в директ и в комменты с одного аккаунта. Смотрю аутпут роутера: sender.id в Direct = 1385828923330271, chatId коммента = 1385828923330271. Идеальное совпадение. Гипотеза о «разных ID‑пространствах» мертва, общая память легитимна, архитектура модели оказалась точнее вывода её «аудитора». Супер).
Прогон 2. Bad request — и «бомюа», которую я сам и подложил
Второй комментарий пишу. Гейт пройден, ключи захвачены, агент отработал, отправка...и... вот блин:
```text Failed to send private reply to comment 18120835172314431 for account 17841479739340657: Bad request - please check your parameters ```
Две поломки в одном исполнении.
Первая сам ИИ агент. В коде ветки был включён режим фолбэкмодели, а саму вторую модель никто не создал и соответсвенно поднода не подключена. Агент упал, и сработала защита: в Prepare Reply пустой и нестандартный ответ заменяется заглушкой. Мне в директ ушла вежливая «Здравствуйте!✨ Спасибо за Ваш комментарий...» — вместо пустоты или сырого JSON. Защита сделала ровно то, для чего её писали.
Вторая — отправка. Meta отклонила сам запрос. Community‑нода глотает сырой ответ Graph API, показывая только «Bad request» — даже и диагностировать нечего, иду curl'ом. А причина оказалась смешнее любого бага: в ноде лежал кредитнаил заказчика, а тестировал я на своём тестовом аккаунте. Токен заказчика физически не может отвечать на комментарии моего аккаунта = Meta увидела «чужака» и вежливо послала меня с 400.

Вот она, та самая «безопасная песочница» которую я выбрал в начале, чтобы ничего не сломать у клиента. Решение было правильным, а вот его исполнение — нет: перенеся сборку в изоляцию, я забыл перенести контекст — credential должны были переехать вместе с ней. Первая «бомба» которую я не учёл, заложена моими же руками. ИИ тут ни при чём.
Прогон 3. Победа)
Правильный credential в ноду, фолбэк‑модель подключена — и цепочка прошла насквозь: допуск — захват — журнал — агент — отправка — валидация — запись результата. Приватный ответ пришёл в директ.




Самая шикарная проверка — второй комментарий под другим постом. Новый comment_id, новый claim‑ключ... и память, которая подтянула первый комментарий: агент отвечает уже с учётом истории общения. Логика складывается под бизнес заказчика: один юзер = сколько угодно комментариев (в зависимости скока контекст в redis выставить)= один непрерывный контекст.
Сухой остаток
Стелсу сутки от роду — и он за один заход собрал ветку с дедупликацией, журналированием, фулбэком и честным самоанализом. Контекста 262K хватило: ветка = это не 127 нод, тут важнее точность, чем объём.
Что отдавать стелсу можно: изолированную ветку с чистым контрактом (вебхук на входе, API на выходе), где ошибка не рушит прод. Что нельзя: отдавать константы (ID аккаунтов, allowlistы — они приехали из фикстур и были неверными) и верить зелёным галочкам без живого боя.
Экономика: модель стоит (пока что) 0 у.е.. Весь пайплайн проверки — пересборка, 18 тестов, аудит, три прогона время и внимание. Дешевле любого часа разработчика. «Стелс‑окно» — редкий шанс поймать фронтир бесплатно, пользуйтесь, пока открыто.
Две самые дорогие ошибки дня — человеческие: кредитнеил не оттуда и фолбэк модель без модели. ИИ‑ошибки (промпт‑копия, константы) ловятся аудитом и тестами за минуты. Человеческие — потому и человеческие, что аудитор их изначально оправдывает: «я же тестирую, что может случиться».
Мой главный инструмент этого цикла — второй аудит. Нашлось то, что модель честно скрыть не пыталась, и то, что она скрыть пыталась бы, если бы умела. А его собственные гипотезы умерли об живой вебхук. Проверяющий тоже нуждается в проверке.
В статье про прошлый стелс я закончил фразой: «Заменит не ИИ. Заменит человек, который научился им пользоваться». Сутки спустя у меня прибавилось: и который умеет отвечать за результат — за каждый credential, каждую константу и каждый бой.
Моя прошлая статья осталась с крючком: «один мозг, три входа» то есть. Instagram Direct, Facebook Direct и виджет на сайте. Теперь у этого мозга появился четвёртый вход: комментарии под постами. Он уже отвечает в тестовой среде и помнит каждого человека. Конечно дорабатывать много осталось, и донести его до продакшена заказчика, тесты уже в реальном аккаунте... Но об этом и о другом расскажу в следующих статьях.
Всем добра! И спасибо за внимание:‑)