План: скиллы в админском чате — UX и проверяемость
Дата: 2026-08-01. Основано на разведке боем, не на предположениях: три сценария прогнаны через живой /api/agent-chat под ботом agent@docsbook.io (uid 36) на локальной авторизованной превью, плюс аудит кода чата.
Главный вывод разведки, который меняет постановку. Скилл-флоу в чате уже построен end-to-end и неплохо: полный каталог из 52 скиллов инъецируется в системный промпт, есть
find_skill/read_skill,/skill-nameв композере, галерея выбора черезask_user, follow-up чипы, ведущие по шагам скилла, гейтNEED_SKILL_SELECTION. Строить «интеграцию скиллов в чат» с нуля не нужно — она есть.Ломается не связка, а три вещи вокруг неё: агент не рассказывает о своём арсенале, не доводит запущенный скилл до результата, и не показывает результат так, чтобы ему можно было верить. Всё три подтверждены прогонами ниже.
1. Что показала разведка боем#
Три прогона через /api/agent-chat (admin mode, ws 141 — единственный dev-воркспейс с живым центовым кошельком на момент прогона).
Прогон 1 — discovery. «Что ты умеешь для моей документации?»#
Ожидание из промпта: каталог инъецирован как «your arsenal», в нём 52 скилла с именами и планами.
Факт: 7 общих категорий, ни одного имени скилла, ни одного вызова тула.
«1. Generate Documentation … 3. Content Improvements: Analyze and improve your existing documentation's content for clarity, SEO, and engagement …»
Админ уходит с ответом «умею улучшать контент» вместо «вот /docs-seo, /docs-health-triage, /docs-change-impact — какой запускаем». Арсенал в промпте есть, наружу не выходит.
Прогон 2 — execution. /docs-seo run it on my docs#
Ожидание из промпта (route.ts:308): «If the user's message already contains a /skill-name token, skip find_skill ranking, read_skill that name directly, and execute.»
Факт — цепочка из 9 вызовов:
find_skill → read_skill → get_workspace_info → get_navigation →
get_workspace_info → set_option → get_navigation →
get_workspace_info → get_workspace_info → ERROR, текста ноль
Четыре дефекта в одном прогоне:
find_skillвызван вопреки инструкции. Явный/docs-seoне сработал как shortcut.- Скилл не выполнен.
docs-seo— это аудит SEO: заголовки, мета-описания, структура. Ни одной страницы не прочитано. Вместо работы — цикл из 4×get_workspace_infoи 2×get_navigation. set_option— мутация настроек в audit-скилле. Уdocs-seoв каталогеmode: audit, он обязан только читать и отчитываться. Режим объявлен в метаданных, но ничем не удерживается в рантайме.- Ноль текста пользователю. Поток кончился ошибкой. Админ 26 секунд смотрит на крутилку и получает пустоту.
Прогон 3 — блокировка на пустом кошельке#
Дальнейшие прогоны упёрлись в token_limit_reached — и это корректная работа гейта (runAgentLoop.ts:356-370): у dev-воркспейсов balance_monthly_cents = 0. Экран показывает «$0.00 LEFT» честно.
Побочно подтвердилась известная грабля: grep без -a молча не нашёл строку "Token budget exhausted for this workspace.", которая лежит в runAgentLoop.ts:366 — файл содержит NUL-байт. Диагностика ушла в ложную сторону на несколько шагов.
Что видно на экране (скриншот пустого чата)#
- 9 пресетов написаны на языке задач — «Audit my docs», «Check docs SEO», «Find documentation gaps». За каждым стоит скилл, но связь нигде не показана: админ не узнаёт, что нажал скилл, и не научается, что их 52.
- Весь каталог свёрнут в кнопку «+» без подписи.
- Дрейф реестра, измеренный фактом: 37 записей в
src/components/chat/skill-meta.tsпротив 52 скиллов в каталоге. 15 скиллов — включая все, написанные за две последние сессии (docs-change-impact,docs-trust-audit,docs-pricing-consistency,docs-competitor-gap,docs-health-triage,docs-rank-recovery,docs-title-rewriter,docs-buying-blockers) — идут без иконки и без примеров вопросов. Файл сам себя предупреждает о дрейфе (комментарий на :44-50), но стража нет.
2. Диагноз: три разрыва, а не один#
| # | Разрыв | Где именно | Цена |
|---|---|---|---|
| A | Арсенал не виден. Каталог в промпте, но агент говорит о себе абстракциями; UI прячет 52 скилла за «+» и не связывает пресеты со скиллами | системный промпт buildAdminSystemPrompt; DocsbookChatInput.tsx; skill-meta.ts (дрейф 37/52) |
Админ не знает, что покупает. Платящая аудитория пользуется одной десятой продукта |
| B | Скилл не доводится до результата. /skill-name не работает как shortcut; шаги SKILL.md ничем не удерживаются; циклы; audit-скилл мутирует настройки |
route.ts:308 (инструкция без механики), runAgentLoop.ts (нет step-трекинга, REPEAT_LIMIT=2 не спас) |
Запуск скилла = лотерея. Это хуже, чем отсутствие скилла: доверие тратится один раз |
| C | Результат недоказуем. Нет отчёта, нет «что изменилось», нет замера пользы | нет слоя вывода скилла; get_page_diff_impact (T7) существует, но чат про него не знает |
Нельзя ни продать («вот что мы вам дали»), ни улучшить («вот что не сработало») |
3. Что делаем: пять работ по убыванию отдачи#
Порядок не по сложности, а по тому, сколько боли снимает единица работы.
W1 — Удержать выполнение скилла (разрыв B) ✅ СДЕЛАНО 2026-08-01 (85116d94)#
Все три механики отгружены и проверены боем на живом
/api/agent-chat.
/skill-name— настоящий перехват (src/lib/skills/runtime.ts,route.ts): скилл резолвится по каталогу СЕРВЕРОМ до первого хода модели, тело SKILL.md инъектируется в контекст,find_skill/read_skillфизически убираются из схемы тулов на этот ход. Прогон 2 повторён: ранжирования больше нет.- Режим удерживается в рантайне:
ctx.activeSkill.mode === "audit"→ мутирующие тулы отклоняются с внятным сообщением (список имён + семейства по префиксуupdate_/set_/register_webhook_, чтобы новый мутатор был закрыт по умолчанию). Плюс правило в контекстном блоке: audit-скилл не тянет предложение изменения через отчёт.- Шаги
## Workflowпарсятся в чек-лист хода (extractWorkflowSteps) — источник для W3.Найденное сверх плана — петля не сходилась.
REPEAT_LIMITне спасал, потому что модель чередовала РАЗНЫЕ read-тулы, возвращающие одно и то же: каждый вызов выглядел новым, ход в целом не узнавал ничего и умирал наmax_iterationsс пустым текстом. Добавлено: бюджет бесплодных вызовов (считается и повтор сигнатуры, и повтор РЕЗУЛЬТАТА), по исчерпании — тулы снимаются полностью и ход может только писать текст; один добор на молчаливый ход;max_iterationsотдаёт человеческое сообщение вместо гологоerror.Приёмка выполнена: прогон 2 даёт
✓ без find_skill · ✓ без мутаций · ✓ повтор ≤2 · ✓ текст 1029-1284 симв. · ✓ done. Побочно: стоимость хода упала с ~9.5¢ до ~6.7¢ — меньше холостых round-trip'ов.
Исходная постановка W1
Самая дорогая поломка: скилл запускается и не доходит. Пока это так, улучшать discovery — значит приводить больше людей к сломанному аттракциону.
Три механики, каждая проверяемая:
-
/skill-nameкак настоящий shortcut, а не просьба к модели. Сейчас это строчка в промпте, которую модель проигнорировала. Сделать перехват на сервере: токен/nameв последнем сообщении →fetchSkillBody(name)вызывается кодом до первого хода модели, тело SKILL.md кладётся в контекст,find_skillиз набора тулов на этот ход убирается. Инструкцию, которую модель может не исполнить, заменяем состоянием, которое она не может обойти. -
План шагов как состояние хода. После
read_skillизвлекать нумерованные шаги секции## Workflowи вести по ним явный чек-лист в контексте: какой шаг текущий, какие закрыты. Это же — источник для UI-прогресса (W3). Без него длинный скилл разваливается в свободное блуждание, что прогон 2 и показал. -
Режим скилла удерживается в рантайме. У каждого скилла есть
metadata.mode(audit/refactor/authoring/platform) — и он сейчас чисто декоративный. Пока активенaudit-скилл, мутирующие тулы (set_option,update_*,commit_docs,write_docs) должны отклоняться с внятным «этот скилл только читает; чтобы применить — выйдите из аудита». Прогон 2 показалset_optionвнутриdocs-seo— это не теория.
Приёмка: прогон 2 повторяется и даёт: без find_skill, без повторов одного тула подряд, без мутаций, с непустым финальным текстом.
W2 — Тесты, которые ловят именно это ✅ СДЕЛАНО 2026-08-01 (85116d94)#
Харнесс:
scripts/chat-scenarios/(run.mjs+setup-workspace.mjs+ README).
- 6 сценариев, каждый написан на реально наблюдавшийся дефект, а не воображаемый:
discovery-names-skills,slash-shortcut,audit-readonly,metric-routes-to-tool,skill-reaches-data,no-plan-assumption.- Детерминированные чеки: какие тулы вызваны, каких быть НЕ должно,
no_mutationsпо семействам, максимум повторов подряд, непустой текст, поток кончилсяdone, и — добавлено по факту прогонов —no_trailing_promise(ход не должен кончаться фразой «сейчас соберу данные»).- LLM-судья получает инструкцию ИЗ промпта + фактический ответ и отвечает на закрытый вопрос с цитатой.
- A/B на каждый чек выполнен: подсадка дефекта роняет ровно тот чек, что должен (снял префиксную защиту мутаторов → упал
isMutatingTool; разрешил вложенные шаги → упали 4 чека парсера; удалил запись изskill-meta→ упал страж).- Изолированный воркспейс с бюджетом —
setup-workspace.mjsчасть харнесса: выдаёт планpro_plusи живой кошелёк, и указывает на репо С ДОКУМЕНТАЦИЕЙ (пустой репо заставлял агента метаться междуget_navigation/get_workspace_info— выглядит как баг агента, а им не является).- Стоимость печатается из того же кошелькового леджера, на котором продукт биллит: ~6-10¢ на сценарий, ~40-60¢ полный набор. Решение принято по цифре: детерминированные чеки (
--no-judge) на каждый пуш, судья — по расписанию.Что тесты поймали сверх ожидаемого: ответ, заканчивающийся обещанием вместо результата (устойчиво, 2 прогона подряд) и допущение о плане пользователя («продолжу, предполагая что у вас Pro»). Оба закрыты правилами в промпте.
Урок про сами тесты (риск №1 подтвердился): первая версия
plan-block-degradesсудила НАЛИЧИЕ блокировки — а это свойство фикстуры, не агента, поэтому чек мигал. Переписан вno-plan-assumption: судит то, что обязано держаться при любом плане. Второй случай: судьяaudit-readonlyфлапал на формулировке — вопрос сужен до «заявлено ли изменение сделанным».
Исходная постановка W2
Формулировка Dan: «что действительно ИИ возвращает логичный ответ исходя из ожиданий промпта». То есть проверяем не «не упало», а соответствие поведения тому, что промпт обещает.
Устройство: сценарий = { вход, ожидания }, где ожидания двух сортов.
Детерминированные (без LLM, дёшево, в CI):
- какие тулы вызваны и в каком порядке;
- каких тулов быть НЕ должно (для audit-скилла — весь мутирующий набор);
- нет повторов одного тула подряд больше N;
- финальный текст непустой и длиннее порога;
- поток закончился
done, а неerror.
Это ловит ровно прогон 2 и стоит один прогон чата.
LLM-judge (там, где детерминизм невозможен): судья получает инструкцию из промпта и фактический ответ и отвечает на конкретный вопрос, а не «хорошо ли». Например: «Промпт называет каталог скиллов арсеналом агента. Названы ли в ответе конкретные скиллы по именам? Да/нет + цитата». Это ловит ровно прогон 1.
Три обязательных свойства, иначе тесты станут украшением:
- A/B на каждый чек. Чек засчитывается, только если падает на подсаженном дефекте. Иначе получится набор, который всегда зелёный. (Ровно так проверялась контрольная группа в T7 — подсадка роняла 2 теста.)
- Изолированный воркспейс с живым кошельком. Разведка встала на
balance_monthly_cents = 0у всех dev-воркспейсов. Тестам нужен свой, с бюджетом, и его пополнение — часть сетапа, а не ручная операция. - Стоимость видна. Каждый прогон печатает потраченные центы. Набор из 20 сценариев на каждый пуш может стоить дороже пользы — это решение принимается по цифре, а не на глаз.
Первые 6 сценариев (по одному на найденный дефект + покрытие ключевых режимов):
- discovery: «что ты умеешь» → в ответе ≥3 имени скилла;
/docs-seo→ нетfind_skill, нет мутаций, есть текст;- audit-скилл + просьба «поменяй настройку» → вежливый отказ, а не мутация;
/docs-change-impact→ доходит до вызова аналитики, не выдумывает числа;- вопрос про метрику («сколько визитов вчера») → идёт в
find_tool, а не в скилл (маршрутизация из route.ts:221); - free-план + PRO-скилл → внятная деградация с названием плана, а не падение.
W3 — Показать, что скилл делает и что сделал (разрывы B и C)#
UI сейчас показывает поток тулов строками. Скилл — это не поток тулов, это процедура с шагами и результатом.
- Прогресс по шагам: когда активен скилл, в треде видно «Шаг 2 из 6: собираю заголовки страниц» — данные берутся из плана шагов (W1.2). Это же снимает «26 секунд крутилки».
- Отчёт как артефакт, а не простыня. У audit-скиллов результат структурен по своей природе (находка, доказательство, серьёзность). Отдавать его карточкой с находками и ссылками на страницы, а не абзацем текста. Механика для этого уже есть — admin-cards +
render_widget_card. - Явный конец. Скилл закончился → одна строка «что нашли / что изменили / что дальше». Сейчас конца нет вовсе: поток просто останавливается.
W4 — Сделать арсенал видимым (разрыв A)#
Частично СДЕЛАНО 2026-08-01 (
7d56ccbe): «Try Docsbook» переписан из сетки 9 карточек в листаемую библиотеку из 57 промптов.
- Карточка = результат, не скилл. Один живой вопрос («почему у меня не апгрейдятся?») законно тянет несколько скиллов, и выбирать — работа агента, а не кнопки. Поэтому ни одна карточка скилл не называет; библиотека разложена по бизнес-осям (acquisition / activation / conversion / retention / build).
- Три длины под три задачи: заголовок в 2-3 слова (влезает в карточку), описание одной строкой на ховере (оно же нативный тултип и доступное имя), и длинный промпт под результат, который уходит агенту. Проверено перехватом: карточка из 14 символов отправляет 270.
- Страницы 3×3 со счётчиком «2 / 4», стрелками и свайпом. Перемешивание раз на монтирование с чередованием по осям — каждое открытие показывает другую грань продукта, и страница не оказывается девятью вариациями одного.
- Проверено в браузере на всех 4 страницах: не обрезается ничего (два заголовка ловились переполнением и укорочены), ховер заполняет строку в шапке, листание закольцовано.
Ещё две трети СДЕЛАНО 2026-08-01 (
85116d94):
- Дрейф
skill-meta.tsзакрыт и заперт. Было 37 записей против 52 скиллов — дописаны все 15 недостающих (иконка + короткий лейбл + примеры вопросов), и поставлен тест-стражsrc/components/chat/__tests__/skill-meta-drift.test.ts: каждый скилл каталога обязан иметь запись, устаревшая запись без скилла тоже красная, лейбл ≤30 символов (длиннее — обрезается в меню), ≥2 примера. При недоступной сети тест СКИПАЕТСЯ, а не падает: красный, означающий «нет вайфая», приучает игнорировать красный. A/B: удаление одной записи роняет стража.- Правило «отвечай именами» в промпте. Сначала было вписано в раздел про скиллы — не сработало (прогон дал 0 имён). Перенесено в НАЧАЛО промпта с явным форматом (
- **\/docs-seo`** — строчка про результат) и запретом строк без имени скилла. После переноса: 3 прогона подряд по 3-5 имён, судьяyes`.Осталось по W4: пресеты в «+»-меню называют скилл; каталог как страница внутри чата.
- Пресеты называют скилл. «Audit my docs» → показывать, что это
/docs-analyze. Админ учится языку системы, а не угадывает. - Каталог как страница внутри чата (дешёвая версия: расширенное «+»-меню с группировкой по бизнес-оси — привести людей / довести до ценности / продать / удержать, как в плане каталога). Полноценную галерею — только если после W1–W3 останется, что показывать.
W5 — Замерить пользу (разрыв C, самое медленное)#
Здесь пригождается T7 get_page_diff_impact, написанный в прошлой сессии: он умеет отвечать, помогла ли конкретная правка, сравнивая изменённые страницы с нетронутыми.
Петля: скилл предложил правку → правка закоммичена → через окно измерения чат сам возвращается: «docs-title-rewriter переписал 6 заголовков две недели назад; на этих страницах отказы упали на 9 пунктов против 2 по остальному сайту». Это одновременно доказательство пользы продукта и вход в следующую работу.
Осторожно: T7 честно отказывается судить на малых выборках (проверено боем: 22 и 8 визитов → «not enough visits to judge»). У большинства клиентских воркспейсов трафик именно такой. Значит петля даёт результат не всем и не сразу — обещать «мы измерим эффект» в UI нельзя, можно только показывать, когда измерение получилось.
4. Порядок и почему такой#
W1✅ — без него остальное усиливает поломку.W2✅ — сразу за W1, потому что W1 иначе нечем принять. Тесты пишутся на найденные дефекты, а не абстрактные.- W3 👈 ТЕПЕРЬ ПЕРВЫЙ — делает работу W1 видимой; переиспользует её план шагов (
extractWorkflowStepsуже парсит их и кладёт в контекст, до UI дело не дошло). - W4 — приводит людей к тому, что теперь работает. Дрейф
skill-metaи правило «отвечай именами» уже закрыты; остались пресеты со скиллом и каталог-страница. - W5 — отдельный горизонт, зависит от накопления данных.
W1 + W2 сделаны одним заходом — и это оправдалось: три из пяти правок W1 родились из провалов сценариев, а не из плана (несходимость петли на разных read-тулах, ответ-обещание, допущение о плане). Принимать W1 на глаз означало бы отгрузить перехват /skill-name и не заметить, что ход по-прежнему умирает пустым.
5. Риски#
- Тесты на LLM недетерминированы by design. Один прогон ничего не доказывает. Либо несколько прогонов и порог «k из n», либо чек формулируется так, чтобы шум его не двигал. Иначе красный CI станет фоном, который перестанут читать.
- Стоимость. Полный набор на каждый пуш может оказаться дороже пользы. Решение — по измеренной цифре: дешёвые детерминированные чеки на каждый пуш, LLM-judge — по расписанию.
- Жёсткое удержание шагов может сломать полезную гибкость. Часть скиллов сознательно оставляет агенту свободу. Удерживать надо режим и факт завершения, а не каждый шаг буквально.
- Локальная среда не воспроизводит планы. В dev-БД все воркспейсы бота —
freeс нулевым кошельком. PRO-поведение (а большинство аналитических скиллов — PRO) локально не проверяется вообще. Это та же грабля, что в growth-audit с ролью Free. Тестовому контуру нужен воркспейс с планом и бюджетом, иначе половина сценариев непроверяема. skill-meta.tsразъедется снова. Без стража в CI — гарантированно; он уже разъехался на 15 скиллов при наличии предупреждающего комментария в самом файле.
6. Что уже проверено и не требует повторной разведки#
- Скилл-флоу существует и вызывается:
find_skill→read_skillотработали в прогоне 2. - Каталог тянется в промпт server-side с кешем (
listSkillCatalog,src/lib/skills/find.ts:214). /skill-nameдоезжает до сервера и снимает гейтNEED_SKILL_SELECTION, но не работает как shortcut.- Гейт бюджета в админ-чате есть и корректен (
runAgentLoop.ts:356). - Агент не ходит в свой MCP-сервер: у чата отдельный набор тулов, переиспользуется бизнес-логика. Точка переиспользования для скиллов —
src/lib/skills/find.ts. - Новые тулы MCP (в т.ч.
get_page_diff_impact) в чат-агенте не появляются автоматически — их набор отдельный. Для W5 тул придётся завести и в чате.
Добавлено после реализации W1+W2 (2026-08-01)#
metadata.modeживёт в двух местах: вindex.jsonон на верхнем уровне (mode), в самом SKILL.md — подmetadata.mode. Правда — второе (его правит автор скилла),runtime.tsчитает body и падает на индекс. Распределение: audit 28, platform 11, authoring 8, refactor 5.raw_urlскиллов НЕ содержит категорию единообразно: у однихskills/<category>/<name>/SKILL.md, у другихskills/<name>/SKILL.md. Не строй путь сам — бериraw_urlиз индекса.- Пустой воркспейс делает сценарий непроверяемым и врёт про причину. Свежесозданный воркспейс указывал на несуществующий репо — агент метался между
get_navigation/get_workspace_info14 раз. Выглядит как петля агента, а на деле нечего читать. Фикстура обязана указывать на репо с документацией. - Разведка была права про кошельки, но не про дно: у бота есть воркспейсы с ненулевым балансом (139/143/146/150/151), просто мало.
setup-workspace.mjsподнимает выбранный доpro_plus+ $5. - Дев-логин ботом воспроизводится из curl ровно как в
proxy.ts: GET/api/auth/csrf→ POST/api/auth/callback/agent-bypassсcsrfToken+secret. Это и есть механика харнесса, браузер не нужен. no_trailing_promiseловится дёшево регэкспом — судья для этого не нужен: «let me now gather…», «please hold», «get back to you» в хвосте ответа.