Статья четвертая из серии. Было исследование, личная история, продуктовый инсайт. Здесь будет продукт. Публикую манифест до того, как написана первая строчка кода — чтобы потом было честно сравнить, где я прав, а где разбился о реальность.
Большинство AI-ботов — это if-else вокруг GPT
Откройте любой «AI-коуч», «AI-психолог» или «AI-ассистент по саморазвитию». Под капотом почти всегда одно и то же: промпт с инструкцией «веди себя как коуч», пара-тройка условий на кнопки, и молитва, что модель не начнёт галлюцинировать диагнозы. Состояние — в лучшем случае в Redis с TTL на сутки. История — последние N сообщений в контексте. Логика — «спроси у Claude, что делать дальше».
Это работает ровно до первого серьёзного вопроса: а откуда взялся этот вывод обо мне? И тут выясняется, что никакого вывода нет — есть только последний ответ модели на последний запрос, сгенерированный заново, с нуля, без памяти о том, что было вчера.
Я строю продукт, где этот вопрос — центральный. Бот в мессенджере MAX, который ведёт адаптивный диалог с пользователем и строит многослойный профиль его личности. Не тест из 20 вопросов с готовым результатом. Не «спроси у ИИ, какой ты архетип». А система, в которой каждый ответ пользователя — это неизменяемое событие, профиль — read model поверх event log, а LLM — не ядро, а один из узлов, причём не самый важный.
В этой статье — спецификация до первого коммита. Почему event sourcing. Почему инварианты. Почему Stability Engine. И почему я начал с YAML-файла на 700 строк, а не с npm init.
Проблема: статичные тесты и амнезия чат-ботов
Человек не понимает своих паттернов. Это базовая неопределённость, которая сидит в нас почти всё время: как я думаю, что меня реально мотивирует, почему я снова и снова попадаю в один и тот же сценарий, где мои скрытые таланты, что мне на самом деле мешает.
Существующие инструменты отвечают на это плохо. MBTI и соционика — статичны: прошёл тест один раз, получил четыре буквы, забыл. Они не учитывают контекст жизни, не обновляются, не показывают динамику. Психотерапия — медленна и дорога, и даже там месяцы уходят на то, чтобы просто нащупать паттерны. А «AI-коучи» в телеграме страдают той самой амнезией: каждая сессия начинается с нуля, предыдущие разговоры — в лучшем случае в виде summary, который модель сама и галлюцинирует.
Что здесь на самом деле нужно — это система, которая:
помнит каждый ответ как факт, а не как токены в контекстном окне;
умеет показать, откуда взялся тот или иной вывод;
меняет профиль только тогда, когда накопилось достаточно сигналов, а не после одной эмоциональной реплики в 3 часа ночи;
различает «пользователь в поиске» и «пользователь в кризисе» — и во втором случае молча отходит в сторону, а не продолжает профилировать.
Всё это — не столько про ML, сколько про архитектуру. Поэтому я и начал со спеки.
Концепция: ÆON Map System
Профиль устроен как карта из семи слоёв. Каждый слой — это группа «карт» (cards), которые накапливают сигналы из диалога и в какой-то момент «назначаются» — когда уверенность достигает порога.
Слой I — Core. Когнитивный стиль и поведенческие паттерны. Как человек думает, как принимает решения, какие сценарии повторяет.
Слой II — Emotional & Motivational. Что реально мотивирует, а не что человек декларирует. Ценности-ядро.
Слой IV — Archetype. Архетипическая матрица — не юнговская поп-версия, а композиция из сигналов предыдущих слоёв.
В MVP — три этих слоя. Остальные четыре — стратегический, динамический, интеграционный и мета-слой — добавляются в v1 и v2.
Каждая завершённая сессия добавляет запись в Book of Consciousness — таймлайн трансформации. Финальный артефакт — глиф: уникальное визуальное изображение профиля, которое перегенерируется по мере того, как профиль уточняется.
Важно, что порядок разблокировки слоёв — линейный (сначала I, потом II, потом IV), но маршрутизация сигналов — сквозная: один ответ на вопрос из слоя II может дать сигнал в карту из слоя IV, если модель-классификатор увидит там релевантный паттерн. Это и есть разница между «пройди тест и получи результат» и «живая система, которая слушает всё».
Архитектурное решение: event sourcing, инварианты, стабильность
Теперь к интересному. Почему такая архитектура, а не очередной endpoint с промптом.
Event Core: ответ — это факт, а не состояние
Центральная сущность — Answer. Когда пользователь отвечает на вопрос, рождается событие answer.given с полями: session_id, question_id, answer_value, answered_at. Оно записывается в таблицу events в PostgreSQL, и это append-only: никаких UPDATE, никаких DELETE. Инвариант INV-02, зафиксированный в спеке:
> Answer неизменяем после записи. Пользователь не может редактировать ответ — только пройти новую сессию.
Почему это важно. Если пользователь вчера ответил «я экстраверт», а сегодня — «я интроверт», это не ошибка данных. Это факт: два разных ответа в двух разных контекстах. Профиль должен уметь это держать — не как противоречие, которое надо «исправить», а как сигнал о том, что человек меняется (или что вопрос был плох). Если разрешить редактирование, мы теряем историю, а вместе с ней — возможность видеть динамику.
Профиль (AeonProfile) — это read model, проекция над event log. Его можно пересобрать из нуля в любой момент, прогнав все события через aeon_engine. Это даёт главное: воспроизводимость. Баг в логике построения профиля — не катастрофа, а патч + пересборка проекции.
Инварианты живут в domain/, а не в контроллерах
В спеке — 10 доменных инвариантов и 2 safety-инварианта. Каждый — с полем enforcement: где именно в коде он держится. Несколько примеров:
INV-03: карта назначается только при
confidence ≥ CARD_CONFIDENCE_THRESHOLD(0.72). Проверка — в Stability Engine, не в LLM. Модель предлагает сигнал, решение принимает детерминированный код.INV-06:
llm.calledevent пишется в базу до отправки ответа пользователю, а не после. Транзакционно. Если мы упали между вызовом LLM и ответом — у нас есть факт вызова, и мы можем разобраться.INV-07: одно сообщение пользователя = не более одного
answer.given. Идемпотентность поmax_update_id. Ретрай вебхука от MAX не должен давать дубль события.INV-09: карта не назначается, пока суммарный вес сквозных сигналов по её типу не перевалит порог. Даже если слой уже разблокирован.
Каждый инвариант покрывается property-based тестом на Vitest + fast-check. Это не unit-тест «проверим один случай», это «сгенерируй 1000 случайных последовательностей событий и проверь, что инвариант держится». Именно такие тесты ловят дыры в логике, которые на примерах не видны.
Stability Engine: где живёт здравый смысл
Отдельный модуль src/stability/, не размазанный по коду. Его работа — контроль порогов, лимитов и safety:
DAILY_SESSION_LIMIT— 3 сессии в день, защита от злоупотреблений и LLM-расходов.CARD_CONFIDENCE_THRESHOLD— нельзя назначать карту на слабых сигналах.MIN_ANSWERS_PER_LAYER— 4 ответа минимум, иначе следующий слой не открывается.Safety Gate 1 — rule-based проверка на кризисные маркеры (суицид, самоповреждение, насилие) до любого дорогого вызова LLM.
Последнее — критично. Многие делают наоборот: отправляют сообщение в LLM, и уже он «решает», кризис это или нет. Но это значит платить за токены на каждом кризисном сообщении и доверять детекцию стохастической модели. Я делаю rule-based (словари маркеров + лёгкий классификатор) в детерминированном коде, с отдельными тестами. Если Gate 1 сработал — бот отвечает тепло и даёт телефон доверия 8-800-2000-122, и никакой LLM не вызывается. Gate 2 — в system prompt основного LLM, как второй рубеж для edge cases.
PDA как методология
Вся спека написана по методологии PDA — Possibility-Driven Architecture. В двух словах: сначала строится карта неопределённости (user-level и system-level), потом граф домена, потом инварианты, константы, события, архитектура, наблюдаемость. Код — в самом конце. Идея в том, что приложение — это машина снижения неопределённости: пользователь приходит с вопросами о себе, уходит с чуть более устойчивой моделью себя. Архитектура проектируется под это, а не под «как быстрее написать CRUD».
Отсюда — все решения выше. Event sourcing — чтобы любая итерация снижения неопределённости была обратимой. Инварианты — чтобы система не ломалась на входных данных, которые проектировщик не предвидел. Stability Engine — потому что есть класс решений, которые LLM принимать не должен никогда.
Почему LLM — периферия, а не ядро
Вот архитектурная схема, которая показывает поток: MAX webhook → адаптер → Stability Engine → Dialog Engine → Event Core. LLM вызывается параллельно, из Dialog Engine, и его вызов логируется как событие до отправки ответа. Aeon Engine работает только с event store — он не имеет доступа к LLM и не должен иметь.

Контринтуитивный тезис: LLM в этой системе — не самый важный компонент. Самые важные — Event Core и Stability Engine. LLM можно заменить (Claude → OpenAI — fallback уже предусмотрен). Event Core и инварианты заменить нельзя — на них держится весь смысл.
Это не про «ИИ плохой» и не про «давайте всё делать на правилах». Это про разделение: что именно должен решать LLM, а что — нет.
LLM решает: как сформулировать следующий вопрос так, чтобы он был живым и адаптивным; как интерпретировать свободный текст ответа; как сгенерировать глиф (через DALL·E); как написать финальный текстовый профиль.
LLM не решает: когда назначать карту; когда прекращать сессию из-за кризиса; когда пользователь исчерпал лимит; какой слой разблокировать следующим; что такое «достаточная уверенность». Всё это — детерминированный код с тестами.
Такое разделение даёт две вещи, которые дороги в продукте. Первая — предсказуемость: поведение системы в edge-кейсах определено правилами, а не настроением модели. Вторая — дешевизна эксплуатации: если половина решений принимается до вызова LLM, расходы на API падают кратно.
Что дальше: трансляция сборки
План такой. Всего итераций — восемь, от нулевой (скелет: Docker, PostgreSQL, «привет» в MAX) до седьмой (safety, глиф, share-карточка). Каждая итерация — отдельная ветка feat/iter-N, закрытие итерации — merge в main и новая статья или короткий апдейт.
Формат трансляции — гибрид: первая из трёх больших статьей здесь, между ними — короткие апдейты со скриншотами и ссылками на коммиты. Репозиторий открытый, GitHub Issues и Projects — публичные.
Что будет во второй статье: итерация 1 и 2 — Event Core в PostgreSQL (append-only таблица с constraint-проверками, которые гарантируют INV-02 на уровне базы), идемпотентность по max_update_id, первый вопрос через Dialog Engine, property-based тесты на инварианты. С рабочим кодом, миграциями и разбором граблей.
Если вам резонирует подход «спека до кода, инварианты до фич» — подписывайтесь, будет подробно. Если считаете, что всё это overengineering для бота в мессенджере — тоже подписывайтесь, через пару итераций проверим.
Спека целиком лежит в репозитории в файле aeon-max-bot.vibepp.yaml — 700 строк машиночитаемого YAML, из которого Cursor-агент в принципе может восстановить половину кода. В репо уже есть что показать. Подписывайтесь, чтобы не пропустить.
Статус

---
Что думаете про такой подход? Если у вас есть опыт event sourcing в AI-продуктах — особенно интересно, где я ещё не вижу подводных камней. Критика приветствуется.
Комментарии (2)

Bratken
23.04.2026 21:03Так и не понял, какую проблему автор пытается решить. Столько текста, который выглядит так, словно пытаются решить какую-то абстрактную проблему без понимания конечного результата (и корня проблем, на самом деле. Речь про сами мозги. Их работу, точнее).
Просто накину, что удается обходиться обычным deepseek онлайн, который даже памяти не имеет. С qwen проще. Можно в одном чате разобрать очень много тем. Важнее понимать, какие вопросы хочешь задать. Коуч, не коуч - неважно.
Такое горожение огородов это как тот мем про синьоров, которые делают скейтерские трюки на граблях, и все равно на них натыкаются. А джун просто наступает на эти грабли.
В итоге пользы от такого инструмента не особо больше.
normal
да, интересно конечно же! и вообще интересно это направление вокруг llm -- программистский подход к достаточно "гуманитарным" задачам (как здесь типирование человека).