- BrainTools - https://www.braintools.ru -

Я собрал 33 ИИ-агента на одном движке. Показываю, как устроены пять из них

Один каркас, из которого собраны очень разные вещи: шахматный тренер, инженерный расчёт, помощник по учёбе

Один каркас, из которого собраны очень разные вещи: шахматный тренер, инженерный расчёт, помощник по учёбе

За последний год я собрал 33 ИИ-агента. Шахматный тренер для ребёнка, помощник по учёбе, ассистент для жены, тьюторы образовательных программ, боты для металлургического холдинга и энергетической компании, внутренние агенты отдела продаж и CRM.

Все они работают на одном движке. Один и тот же Docker-образ, одна и та же кодовая база. Ни один из тридцати трёх не потребовал править ядро.

Различаются они одним: где у каждого проходит граница между тем, что считает код, и тем, что говорит модель. За год я понял, что вся работа сводится примерно к этому, а не к подбору промптов или модели.

Разберу пять агентов с обоих концов шкалы — с кодом, замерами и граблями, включая те места, где я провёл границу неправильно.

TL;DR

  • Агент — это том, а не форк. 33 агента = один образ + 33 разных каталога данных. Ни одной правки ядра.

  • Главная переменная — граница «код / модель». В личном агенте модель может посчитать сама. В корпоративном она не выдаёт ни одной итоговой цифры: ни габарит, ни площадь, ни норму, ни рубли. Но входные параметры задаёт она — и это отдельный риск, к которому я вернусь на кейсе с фидерами.

  • Схемы инструментов начинают обгонять промпт после третьего десятка инструментов. У CRM-админа с 331 инструментом 302 КБ JSON-схем ≈ 75 тысяч токенов в префиксе каждого запроса.

  • Три правки конфига дали 15 266 → 1 424 токена промпта и медиану ответа 17 → 7,7 секунды.

  • Тулсет на демо-контуре клиента не был урезан вовсе — модель собрала себе OCR-пайплайн через терминал и доустановку пакетов. Отдельная засада: даже когда терминал отключаешь явно, execute_code остаётся — он в другом наборе.

  • Про провалы тоже будет: правило идемпотентности я написал, а баланс всё равно разошёлся на трёх источниках. 5 863 строки Python для одного агента написаны и в бою не вызваны ни разу.

Каркас: агент — это том, а не форк

В основе — Hermes Agent [1] от NousResearch. Питон, цикл агента, шлюз, платформы, планировщик, скиллы, плагины.

Движок молодой: первый коммит датирован июлем 2025-го. Часть агентов старше него — они начинали на другом каркасе и переехали сюда по ходу дела. Переезд, кстати, оказался лучшей проверкой тезиса про том: у нескольких агентов до сих пор лежат каталоги от прошлого движка и импортированные сессии — у детского 233 из 558 помечены как перенесённые. Личность, история и накопленные данные пережили смену каркаса, потому что никогда ему и не принадлежали.

Устроено просто:

services:
  hermes-<agent>:
    image: hermes-agent:latest
    environment:
      HERMES_HOME: /opt/data
    volumes:
      - hermes-<agent>-data:/opt/data
    command: ["gateway", "run"]

Каталог /opt/hermes внутри контейнера неизменяем. Всё, что делает агента этим агентом, лежит в /opt/data:

  • SOUL⁠.md — личность,

  • config.yaml — модель, поверхности, наборы инструментов,

  • plugins/ — доменные инструменты на Python,

  • skills/ — инструкции, подгружаемые по требованию,

  • state.db — сессии, SQLite с полнотекстовым поиском,

  • cron/jobs.json — расписания.

Новый агент = скопировать каталог home/ из репозитория в новый том. Никакого форка, никакой сборки.

Один неизменяемый образ и том агента: SOUL⁠.md, config.yaml, плагины, скиллы, база сессий и расписания

Один неизменяемый образ и том агента: SOUL⁠.md, config.yaml, плагины, скиллы, база сессий и расписания

Схема 1. Всё, что делает агента этим агентом, лежит в томе. Образ один на всех, ядро не трогается вообще.

Я прошёлся программно по конфигам всех агентов. mcp.servers пуст у каждого — MCP не используется ни одним. Когда инструмент пишешь сам, плагин дешевле — но не по токенам: JSON-схемы у MCP и у плагина одинаковые, и платишь ты именно за них. Дешевле он по эксплуатации: нет второго процесса, есть доступ к идентификатору сессии и к серверным переменным окружения. Обратная сторона — раз секреты лежат в окружении процесса, держать плагины и терминал в одном агенте нельзя. Чем это кончается, будет в разделе про энергетику.

Системный промпт собирается один раз за сессию

Промпт разложен на три яруса по скорости изменения — стабильный, контекстный, изменчивый — и кэшируется. Пересборка только после сжатия контекста.

Отдельно понравилось решение с датой: в промпте она с точностью до дня, а не до минуты. Комментарий в исходнике объясняет прямо — иначе на каждом запросе рвётся префиксный кэш провайдера. Секунда в дате стоит перерасчёта всего префикса.

«Налог движка» — служебные блоки, которые движок подставляет сам. Часть безусловна, часть зависит от конфигурации:

DEFAULT_AGENT_IDENTITY              513
HERMES_AGENT_HELP_GUIDANCE          560
TASK_COMPLETION_GUIDANCE            769
PARALLEL_TOOL_CALL_GUIDANCE         618
MEMORY_GUIDANCE                   1 426
SESSION_SEARCH_GUIDANCE             186
SKILLS_GUIDANCE                     385
STEER_CHANNEL_NOTE                  681
TOOL_USE_ENFORCEMENT_GUIDANCE       824
GOOGLE_MODEL_OPERATIONAL_GUIDANCE   860
OPENAI_MODEL_EXECUTION_GUIDANCE   2 694
--------------------------------------
все блоки списком                 9 516 символов
максимум на одного агента         8 656 символов ≈ 2,2 К токенов

Одновременно все одиннадцать не платит никто. GOOGLE_MODEL_OPERATIONAL_GUIDANCE и OPENAI_MODEL_EXECUTION_GUIDANCE взаимоисключающи: движок выбирает вставку по имени модели, агент на Gemini получает первую, агент на OpenAI-совместимом API — вторую. MEMORY_GUIDANCE уходит вместе с выключенной памятью [2], SKILLS_GUIDANCE — когда скиллов нет. У агента на Gemini без памяти и скиллов налог падает до 5,0 тысячи символов. Поэтому цифры ниже, где промпт ужимается до полутора тысяч токенов, не противоречат этой таблице — там выключена как раз условная часть.

Модели у агентов разные — большинство на gemini-3.6-flash, часть на deepseek-v4-pro, запасной вариант gemini-2.5-pro. Замеры ниже от модели не зависят: считаются символы промпта и байты JSON-схем, а не ответы. В движке для этого есть встроенная измерялка hermes prompt-size — офлайн, без обращения к API:

агент

системный промпт, символов

инструментов

JSON-схем, байт

бот конференции

10 687

3

1 995

тьютор платформы

19 765

36

55 434

корп-агент, металлургия

21 052

36

59 121

контент-цех

35 046

62

77 125

внутренний CRM-админ

35 613

331

302 110

Считать надо аккуратно: слева символы русского текста, справа байты англоязычного JSON, а в UTF-8 кириллица занимает по два байта на символ. То есть реальный разрыв примерно вдвое меньше, чем кажется по таблице. Но направление всё равно видно, и зависит оно не от предмета и не от длины персоны, а от числа инструментов.

У бота с тремя инструментами схемы дают меньше десятой доли промпта — они не проблема вообще. У агентов с тремя-четырьмя десятками уже сопоставимы с ним. А у CRM-админа с 331 инструментом 302 КБ схем — это порядка 75 тысяч токенов, которые лежат в префиксе каждого запроса.

Оговорка: префикс кэшируется, так что полную цену за схемы платишь на прогреве и после каждой правки тулсета, дальше — по ставке чтения кэша. Но окно контекста они занимают всегда.

Вывод обошёлся мне дорого: тулсет — не список фич, а бюджет.

Три правки в конфиге тьютора языкового курса (узкий профиль инструментов, отключённая проба окружения, отключённый reasoning):

Метрика

Было

Стало

prompt_tokens на запрос

15 266

1 424

Медиана ответа

~17 с

7,7 с

Правки я не разделял, и честно: экономию промпта дал узкий тулсет, а ускорение почти целиком — выключенный reasoning. Это разные рычаги, и мерить надо оба. Токены режет тулсет, латентность — режим модели. При этом у соседнего агента в промпте до сих пор висит 50 стоковых навыков на 16 429 символов — «ни один не про энергетику», как я сам написал в заметке. У корпоративного агента энергетики замеренный промпт — 24 тысячи токенов на ход, и на прикладную задачу из них работает около четырёх тысяч.

Чем агенты отличаются друг от друга

Персона при одном и том же образе разлетается в пятнадцать раз:

Агент

Персона целиком

Шахматный тренер

~1,5 К токенов

Корп-агент энергетики

~1,9 К токенов

Корп-агент металлургии

~2,1 К токенов

Агент для дочери

~9 К токенов

Агент для жены

~12 К токенов

Наставник интенсива

~23 К токенов

Порядок неочевидный: у шахматного тренера — самая сложная предметная область в списке — самая тонкая персона. А у наставника интенсива, который «просто общается», — самая толстая.

Промпт толстеет не от предмета, а от числа поверхностей и ролей. У наставника в одном промпте живут личка, форум потока, геймификация, траектория обучения [3], приём домашних заданий, ритуалы по расписанию и рабочий чат команды. У шахматного тренера — одна функция: пересказать посчитанное.

Личный агент против корпоративного:

Личный

Корпоративный

Код и промпт

промпт основной, кода мало

2 100–4 600 строк движка против 80 строк персоны

Что помнит

человека (память включена, досье)

расчёт (память выключена; сегодня говорит один менеджер, завтра другой)

Что является ответом

текст модели

артефакт — лист А3, DXF, спецификация; текст лишь его пересказ

Статусы

«получилось / не получилось»

PASS / FAIL / WARN / INFO с явным «INFO ≠ PASS»

Персона — это

характер

регламент: жёсткие правила, пошаговый алгоритм, формулировки для произнесения вслух

Цена ошибки [4] другая — и граница «модель / код» проходит гораздо левее.

Четыре агента по степени участия модели: от шахматного тренера, где код считает всё, до агента для дочери, где кода почти нет

Четыре агента по степени участия модели: от шахматного тренера, где код считает всё, до агента для дочери, где кода почти нет

Схема 2. Четыре агента из таблиц выше на одной шкале: шахматный тренер, энергетика, агент для жены, агент для дочери. Слева модель только пересказывает посчитанное, справа принимает почти все решения сама.

Шахматный тренер: модели не доверен ни один факт

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

Как это выглядит. Ребёнок вывел ферзя на g4, и тот повис под конём. Вот что посчитал код — это единственное, что видит модель:

{
  "played_move": {"san": "Фg4"},
  "verdict": {
    "label": "blunder", "label_ru": "Зевок", "hybrid_basis": "material_hybrid",
    "win_before_pct": 55.0, "win_after_pct": 4.0, "delta_win_pct": 51.0,
    "eval_words_after": "позиция очень тяжёлая"
  },
  "best_move": {"san": "Фe2", "win_pct": 54.0, "line_san": ["Фe2", "d6", "d3", "Сe6"]},
  "punishment_line_san": ["Кxg4"],
  "tactics_against_student": [
    {"motif": "hanging_piece", "fact_ru": "ферзь на g4 под боем (конь с f6), никто не защищает"}
  ],
  "limits": {"max_sentences": 6, "max_motifs": 2, "eval_language": "wdl_words"}
}

А вот что из этого сделала модель — один и тот же ход в двух регистрах строгости:

Мягче: «{имя}, давай посмотрим на эту позицию, ведь до твоего хода у нас было небольшое преимущество. Казалось бы, активный выпад ферзем на g4 выглядит очень грозно и нацелен на атаку, однако этот ход уводит фигуру под удар. Наш ферзь оказался без защиты, и соперник забирает его конем Кxg4. Сильнее было просто сыграть Фe2, сохраняя отличную игровую позицию. Ты сильный игрок, просто в следующий раз перед активным выпадом обязательно проверяй, не встает ли фигура под бой соперника.»

Строже: «Стоп, {имя}, а зачем туда ферзем на g4? Позиция была примерно равная, а после этого хода стала очень тяжелая. Ты подставил фигуру под бой, ведь у соперника находится простое Кxg4, и наш ферзь потерян. Сильнее было сделать спокойный ход Фe2, сохраняя отличную игру. Ты сильный игрок, просто в этот раз потерял концентрацию. Договорились больше не отдавать фигуры задарма?»

Ни одного нового шахматного факта модель не добавила. Те же Фg4, Кxg4, Фe2 и оценка словами из поля eval_words_after — меняются только тон и порядок изложения. Имя ребёнка в контракт не кладётся, на его месте плейсхолдер.

Здесь граница проведена дальше всего. Формулировка из README проекта: «Движок считает — тренер объясняет». В докстринге модуля анализа жёстче: «факты о тактике добываются кодом, никогда — вопросом к LLM».

Технически это веб-приложение: FastAPI, 11,7 тысячи строк бэкенда, 18 таблиц SQLite, четырнадцать e2e-сценариев. Агент подключён к нему бэкендом одной функции: получает строгий JSON с фактами, отдаёт одну реплику тренера.

Соперник — два независимых инстанса Stockfish. Один играет, второй работает «мозгом тренера» с фиксированным числом узлов ради детерминизма. В комментарии прямо: «никакого open-ended depth».

Сила соперника задана узлами, а не временем:

OPPONENT_LEVELS = {
    1: {"skill": 0,  "nodes": 1500,   "blunder_p": 0.50},
    ...
    8: {"skill": 20, "nodes": 200000, "blunder_p": 0.0},
}

Два вывода записаны прямо в код как результат замеров. Первый: время зависит от железа — «на быстром процессоре Skill 0 за 50 мс досчитает до ~1300 Elo и раздавит новичка». Второй: даже Skill 0 слишком силён для ребёнка. Поэтому слабые уровни с вероятностью blunder_p играют случайный легальный ход — «детский зевок». Только это опускает нижний уровень к 400–800 Elo.

Сила = f(узлы, skill, вероятность зевка). Не f(железо).

Легальность хода не проходит через модель вообще. Ход приходит с фронта, приложение проверяет move in board⁠.legal_moves и отвечает 400. Состояние партии ведёт код: доска в памяти плюс полная сериализация в SQLite после каждого хода, переживает рестарт сервера реплеем из базы. У модели вообще нет диалоговой памяти о партии — каждый вызов stateless.

Классификация хода — формула, а не мнение. Win% из оценки по логистической функции, потеря в пунктах, шкала как у Lichess:

<2 best | <5 good | <10 ok | <20 inaccuracy | <30 mistake | иначе blunder

Симметрия [5] здесь важна до нюанса: позиции «до» и «после» считаются одинаковым бюджетом узлов. Иначе Δwin% прыгает из-за разной глубины, и метка скачет между «лучший ход» и «ошибка» на одном и том же ходу.

Поверх шкалы — три гибридных правила, потому что чистая Δwin% врёт в крайних случаях:

  • Отдал фигуру даром → форсируем «зевок», даже если позиция и так была проиграна (там Δwin% маленькая). С тремя исключениями, и каждое — вычисленный факт, а не догадка: ход совпадает с лучшим по движку (корректная жертва); после хода форсированный мат в пользу ученика; фигура висела уже до хода.

  • После хода появился форсированный мат против ученика.

  • У ученика был мат, и он его не поставил → минимум «ошибка».

Тактику ищут четыре символьных детектора на python-chess: висящие фигуры, вилки, связки, мат в один. Каждый выдаёт готовую русскую фразу, которую модель только пересказывает. Все детекторы pin-aware: абсолютно связанная фигура не считается ни атакующим, ни защитником — иначе факт «под боем» врёт.

Отдельная функция считает честный «выигрыш» вилки: соперник спасает самую дорогую цель, значит забираем вторую по цене. Без этого дешёвая вилка с ферзём в списке вытесняла из объяснения реально висящую ладью.

Детекторы прогоняются дважды — по позиции сразу после хода ученика и после первого хода наказания. Многие вилки видны только там.

Единственный вход модели — строгий JSON. Схема, профиль ученика, позиции до и после, вердикт с оценкой словами («у тебя заметно лучше» — сантипешки детям запрещены), лучший ход, альтернативы, линия наказания, тактика против ученика и лимиты: сколько предложений и мотивов можно.

Имя ребёнка в контракт не кладётся — там плейсхолдер {name}, подстановка на выдаче. Имя не уходит в модель и не оседает в кэше.

Четыре стадии: код считает факты, контракт-JSON с разрешёнными ходами, модель пересказывает, механическая проверка выхода

Четыре стадии: код считает факты, контракт-JSON с разрешёнными ходами, модель пересказывает, механическая проверка выхода

Схема 3. Полный путь одного хода: движок считает, контракт ограничивает, модель пересказывает, проверка ловит выдуманное.

Выход модели проверяется механически. В контракт кладётся allowlist всех ходов, которые тренеру разрешено назвать. После генерации 342 строки разбирают реплику — включая русскую фигурную нотацию Кf3, Фxb2, рокировки и кириллические омоглифы координат — и проверяют каждый токен. Выдуманная координата (e0, Qz9) ловится во всех формах записи, которые я нашёл, — включая русскую нотацию и кириллические омоглифы. Реакция [6]: одна регенерация с напоминанием, если снова грязно — детерминированный шаблон из фактов.

Рабочей, а не косметической, эту защиту делает вот что: половина кода посвящена тому, чтобы НЕ сработать зря. Голое «король на g8» не считается заявленным ходом — это может быть ссылка на поле. При отсутствии доски проверка не заваливается. Второй guard, ловящий «ты выиграл» после поражения, сделан через негативный lookahead, чтобы честное «ты выиграл пешку в дебюте» не срезалось.

В докстринге записана причина: «ложный срез ломает голос тренера — а ради честности голоса вся защита и делается».

Что осталось модели. Ровно три вещи: тон, выбор регистра разбора (по вычисленным полям) и пересказ фактов человеческим языком. Даже на свободный вопрос «есть ли тут вилка?» факты обязана дать функция.

У прямого драйвера принудительно выключен thinking, и мотивировка записана рядом: «факты уже посчитаны движком, тренеру думать нечего — он их пересказывает».

Шахматные инструменты у агента при этом есть. Персона запрещает их звать в разборе партии: там истина уже в контракте.

Агент для дочери: граница проходит по доверию

Что это. У девятилетнего ребёнка в мессенджере есть собеседник, которому она пишет сама: присылает фото домашки, спрашивает про немецкие слова, просит сказку на ночь. Готовых ответов он не даёт принципиально — задаёт наводящие вопросы. Решённые задачи и домашние дела оплачиваются звёздами по таблице наград, звёзды копятся и меняются на поход за мороженым.

Дальше работает расписание. Утром бот сам пишет квест: убрать за кошкой, расчесаться — пять звёзд, до половины девятого бонус. Вечером второй квест. Ребёнок отвечает «готово», агент дописывает начисление в журнал и объявляет новый баланс. Ночью тот же агент пишет уже не ребёнку, а родителю — короткий рассказ на один экран: что учили, какое было настроение, сколько звёзд заработано, был ли тревожный сигнал. По понедельникам — недельная сводка взрослым тоном, без детских эмодзи.

Если день выдался пустым, отчёта не будет вообще — про это ниже.

Здесь пропорция обратная шахматам: персона огромная (SOUL почти 20 КБ плюс девять тысяч символов в конфиге плюс собственный скилл на 361 строку), а своего Python-кода почти нет. И это осознанно — задача не вычислительная. Но три места всё же вынесены в код, и все три про доверие.

(1) Журнал звёзд с идемпотентностью. Правило записано в персону дословно:

{"ts":"...","delta":2,"reason":"...","key":"2026-07-07:learn-de"}
  • баланс = сумма всех delta по журналу; отдельное число не хранить и не «помнить»;

  • начислять только после того, как строка реально дописана;

  • у события есть ключ "ДАТА:тип"; перед начислением проверить, нет ли уже строки с таким ключом;

  • stars.json — только зеркало для быстрых отчётов, источник правды журнал.

Причина названа в персоне прямым текстом: «это ядро доверия, раньше баланс всё время расходился и она злилась».

(2) Протокол тишины. 94 строки Python, которые сами, без модели, решают — есть ли что докладывать родителю. Скрипт сканирует сессии за дату, отфильтровывает крон-сессии (по платформе, по имени файла и по маркеру в первом сообщении), считает пользовательские сообщения, смотрит записи о звёздах и возвращает код возврата: 0 — активности нет, 1 — есть. При нуле промпт обязан вывести ровно [SILENT] и ничего больше — иначе у родителя в канале копится спам «сегодня опять тихо».

(3) Фильтр служебных утечек в адаптере. Фреймворк иногда пишет пользователю сам, мимо модели: баннеры сброса сессии, строка с именем модели, трейсбеки, сообщения о лимитах. Промптом это не перехватить — модель тут вообще не участвует. Поэтому в адаптер мессенджера добавлен подстрочный фильтр.

Всё остальное отдано модели: разбор фото домашнего задания, проверка арифметики, распознавание «битой» детской речи, выбор награды, тон.

Правило написал, дисциплины не хватило

Три источника баланса расходятся:

Источник

Значение

stars.jsonbalance

461

Сумма по массиву внутри того же файла

466

Журнал — объявленный источник правды

457

В журнале одна строка — стартовый перенос. За месяц с лишним после введения правила в него не дописано ни одного события.

Правило, живущее только в промпте, — намерение, а не механизм. Ровно та же задача у языкового тьютора решена базой данных, и в его докстринге написано, почему:

«Стрейки — это арифметика дат. LLM, редактирующая JSON, забывает [7], ошибается в днях и начисляет дважды. Здесь математика [8] точная и идемпотентная.»

Разница между «написал в промпте» и «вынес в код» измеряется этими тремя числами.

Ограничение инструментов режется по каналу, а не по агенту

Я проверил это запуском резолвера прямо в живом контейнере. В переписке с ребёнком терминала и исполнения кода действительно нет. По расписанию — четыре раза в сутки — тому же агенту возвращается полный набор: терминал, исполнение кода, управление компьютером, делегирование.

В исходниках движка это описано прямо: platform_toolsets — механизм мягкий, жёсткая граница только disabled_toolsets. А он у этого агента пуст.

Агент для жены: промпт растёт как журнал инцидентов

Что это. Бытовой помощник в мессенджере. Основной сценарий — надиктовать расходы одной фразой, как они были потрачены, не раскладывая по категориям: «180 кафе 240 такси 95 аптека 60 кофе 320 продукты». В ответ приходит разобранная таблица с предпросмотром:

Проверь перед записью (правь строку по №):

0. 180 → Дом/Кафе        «кафе»
1. 240 → Дом/Транспорт   «такси»
2. 95  → Здоровье/Аптека «аптека»
3. 60  → Прочее/кофе     «кофе» ❓
4. 320 → Дом/Продукты    «продукты»

Итого в батче: 895 (строк: 5).
Ок — «записывай». Правка — «строку N: <категория>».

Знак вопроса в третьей строке — это не ошибка разбора, а честное «категорию подобрал с натяжкой, посмотри сама». В бюджет ничего не попадает, пока человек не подтвердит.

Сюда же — фотография банковской выписки, дневник питания, напоминания. Бюджет, выписки, здоровье, покупки. Пропорция обратная детскому агенту: SOUL почти стоковый, а персона в конфиге — 36 382 символа в 257 строках.

Она не была такой. Стартовый seed — 1,9 КБ. За полгода вырос в тридцать блоков, и почти каждый начинается словами «раньше было так, больше так не делай»:

«Раньше главная проблема была: ты говорила „готово / загрузила / отправила”, а у пользователя результата НЕ БЫЛО. БОЛЬШЕ ТАК НЕ ДЕЛАЙ: ссылки давай ТОЛЬКО те, что вернул реальный ответ API. НИКОГДА не придумывай и не собирай ссылку „по шаблону”».

«Раньше видео ломалось: склеенный ролик выходил на 1 секунду или битым. В его JSON смотри ok и duration_sec: отправляй ТОЛЬКО если ok=true и длительность разумная.»

Промпт здесь работает журналом разбора полётов. И из шести таких блоков потом выросли шесть модулей Python — то есть правило в промпте было промежуточной стадией, а конечной оказался код.

Что именно унесли из модели:

Разбор диктовки расходов. Токенизатор режет свободную фразу «370 кафе 54 такси 6 сладости 300 дети одежда» на пары «сумма — метка»: числа якоря, слова между ними метка. Разбор сумм понимает форматы обеих стран, где живёт семья: 1.234,56, 1 234,56, и даже арифметику 72+108 прямо в строке.

Классификатор — детерминированный роутер: словарь алиасов, нечёткое сравнение с порогом 0,82, и обучаемая таблица: после подтверждённой записи первое слово комментария сохраняется как маппинг, и в следующий раз категория подставляется вообще без модели.

Дедупликация — не «последние N строк», а sha1 от «месяц|сумма|валюта|категория|подкатегория|комментарий». Причём дубль не блокируется молча, а помечается флажком в предпросмотре: переспросить должен человек.

Запись двухфазная: stage() кладёт в черновик и показывает превью, commit() переносит в основную таблицу. В бюджет ничего не попадает мимо предпросмотра.

Банковские PDF. Код писался под конкретные боевые ошибки — и, как выяснится в конце главы, до пользователя не доехал. Разбор ниже про то, каким он должен был быть. Тип файла определяется по magic-bytes %PDF-, а не по метаданным вложения из мессенджера. Разбор сумм отказывается считать 141,288 числом — «центы это ровно два знака», три знака после запятой дают предупреждение, а не сумму-мутанта «минус 141288». Из выписки вытаскиваются заявленные итоги и сверяются с посчитанной суммой транзакций с точностью до двух копеек. Расхождение уходит в предупреждения.

Молчание как возвращаемое значение. Функция напоминания о воде содержит всю логику [9]: активное окно с 8 до 22, цель распределена линейно, молчать, если пил меньше 90 минут назад, молчать, если отставание меньше 400 мл. Иначе вернуть готовый текст.

Пустая строка = «не дёргать человека». Спам-контроль вынесен из промпта в функцию. Ровно так же устроены follow-up по здоровью: возвращается либо текст, либо пустота, означающая «сегодня молчим».

Курс валют — fail-soft без выдумывания. Два источника подряд, суточный кэш. При сбое отдаётся последний кэш с пометкой «курс из кэша». Если и кэша нет — честное «курс недоступен, уточни вручную». Ни одного выдуманного числа ни при каком раскладе.

Астрология через эфемериды. Пример намеренно из области, где проверять некому — тем нагляднее принцип: Swiss Ephemeris, юлианская дата, накшатра как 360/27 с падой. В Human Design момент дизайна ищется методом Ньютона-Рафсона — Солнце должно быть ровно на 88,000° дуги раньше рождения, до 50 итераций с точностью 1e-9. В докстринге: «если расчёт невозможен — модуль ЧЕСТНО кидает ошибку; НИКОГДА не гадай знаки».

Предмет тут дело десятое. Даже там, где никто не проверит, выбран путь «посчитать или честно отказаться» вместо «модель что-нибудь скажет».

Где я сам провалился

5 863 строки собственного Python. Ни одна из трёх баз данных не создана.

Модули, которые пишут в базу, открывают её лениво: раз файлов нет — в бою они не вызывались. Для остальных — курс валют, напоминание о воде, эфемериды — базы и не нужно, там доказательство другое: их импорты встречаются в истории сессий единицы раз, и только в моих приёмочных прогонах.

Я написал отличный детерминированный слой и не довёл до него живого пользователя. Разбор диктовки, сверка выписок, спам-контроль напоминаний — всё работает и не используется. Это ошибка не архитектуры, а продукта: код, который не встроен в путь пользователя, эквивалентен ненаписанному.

Ещё одни грабли, на которые наступил сам. Я закрыл файлы конфигурации от изменения флагом неизменяемости — чтобы агент не переписал себе расписание. Под тот же флаг попал файл заданий планировщика. А планировщик сохраняет состояние через os.replace(). Получилось PermissionError 86 раз за сутки, и расписание не отработало больше недели.

Мера защиты от того, чтобы агент менял себе расписание, молча выключила расписание целиком.

Корпоративный агент, металлургия: домен как код, а не как база знаний

Что это. Завод делает быстровозводимые здания из стандартных блок-контейнеров: общежития, административно-бытовые корпуса, вахтовые городки. Раньше на каждую заявку менеджер шёл к архитектору, тот рисовал план этажа в Revit — от одного до трёх дней, — и только потом можно было считать коммерческое предложение.

Теперь менеджер пишет боту одной фразой: «нужно общежитие на 42 человека, по трое в комнате» — или просто кидает фотографию чужого чертежа. Через пару минут в чат приходит лист формата А3: план этажа с осями, размерными цепочками, экспликацией помещений и чертёжным штампом. Плюс два трёхмерных вида здания и проверка по строительным нормам с пометками «прошло / не прошло». Дальше диалогом меняются параметры: «в два этажа», «санузлы посередине», «сколько стоит».

Вот два листа одного и того же общежития. Сверху — эталонный лист, нарисованный архитектором вручную. Снизу — то, что сгенерировал код из одной фразы:

Сверху лист архитектора, нарисованный в Revit за один-три дня. Снизу тот же план, сгенерированный кодом из текстового брифа за сорок секунд

Сверху лист архитектора, нарисованный в Revit за один-три дня. Снизу тот же план, сгенерированный кодом из текстового брифа за сорок секунд

Оси, шаг модулей, экспликация и площади совпадают: 267,46 м² против эталонных 267,20 на двадцати помещениях. Расхождение в 26 сантиметров набегает из-за округлений в калибровочной таблице, о которой ниже.

Вынесу отдельно: RAG в этом агенте нет вообще.

Я проверил поиском по всему проекту — совпадений ноль. В базе только стоковые таблицы движка. Каталог для базы знаний существует, в админке есть страницы загрузки — но каталог пуст, и ни персона, ни плагин к нему не обращаются. Это задел на будущее, не работающий тракт.

Домен раскидан по четырём местам, и все четыре — код или данные:

Каталог конструктива — 97 строк:

MODULE_CATALOG = {
    "BM-STD": {"len": 6023, "wid": 2448, "h": 2920, "axis_pitch": 2455, "purpose": "жилой/санитарный"},
    "BM-COR": {"len": 2448, "wid": 2015, "h": 2920, "axis_pitch": 2455, "purpose": "коридорный"},
    "BM-L":   {"len": 7350, "wid": 2920, "h": 2920, "axis_pitch": 2925, "purpose": "удлинённый"},
}
WALL_EXT  = 100   # наружная стена (сэндвич)
WALL_PART = 80    # перегородка

Калибровка площадей по эталонному чертежу — самое неочевидное место. Толщина панелей уже «съедена» в чистых площадях готового проекта, поэтому наивная геометрия не сходится с экспликацией. Решение — таблица, снятая с подписей на эталонном листе:

AREA_CALIBRATION = {
    ("living",  "interior"): 12.52,   # рядовой пролёт
    ("living",  "gable"):    12.12,   # торцевой — уже на 0,40 м²
    ("corridor", None):      44.50,
}

с геометрическим fallback’ом, если ключа нет. Результат: сгенерированный лист даёт 267,46 м² против эталонных 267,20 м² на двадцати помещениях.

Ни одна модель не «додумала» бы такую таблицу. Она снимается с бумаги руками.

Реестр норм — полные названия сводов правил, редакции и конкретные пункты: не менее 6 м² на человека; один душ, один умывальник и один унитаз на шестерых; не менее двух эвакуационных выходов, коридор не уже 1400 мм. Отдаётся отдельным инструментом на вопрос «а по каким нормам вы проверяете».

Габариты считает функция, а не модель. Модель отдаёт «сколько людей и по сколько в комнате». Дальше:

def _derive_bays(rooms_needed, reserve, floors=1, stair_bays=0):
    """Пролётов в ряду под нужное число ЖИЛЫХ КОМНАТ (не людей —
    так микс ИТР/рабочие с разной плотностью считается честно)."""
    living_per_floor = math.ceil(rooms_needed / max(floors, 1))
    bays = math.ceil((living_per_floor + SANITARY_BAYS + reserve + stair_bays) / ROWS)
    return max(4, min(bays, MAX_BAYS))

Комментарий в скобках — не украшение. Считать по людям означает потерять смешанный контингент с разной плотностью расселения.

Не влезает — значит не влезает. Если запрошенное не помещается в гребёнку из максимум 14 пролётов, функция не строит молча здание поменьше. Она возвращает fits: False, список вариантов и подсказку модели, что именно сказать человеку.

Персона домена почти не содержит — 80 строк правил поведения [10]. Правило номер один:

НИКОГДА не выдумывай размеры, площади, число модулей и результаты проверок — все цифры бери ТОЛЬКО из результата инструмента.

Правило номер два интереснее, потому что это не запрет, а управляемая уступка: точные цены называть нельзя (их считает менеджер), но и отмалчиваться на «сколько стоит» нельзя — надо вызвать оценщик, назвать вилку с дисклеймерами и предложить связать с менеджером. Формулировка «НЕ отмалчивайся» появилась ровно после того, как на демо бот ушёл от вопроса о цене.

И побочный эффект, ради которого это в итоге продавалось: на эталонном листе, который лёг в основу калибровки, площадь на человека расходится с нормой, процитированной в его же примечаниях — 4,17 м² против шести. Валидатор прототипа ловит это автоматически, и именно этот момент оказался самым убедительным на демонстрации.

Корпоративный агент, энергетика: PASS только там, где посчитано

Что это. Пользователь — инженер производственно-технического отдела подрядчика, который строит подстанции и линии электропередачи. Чтобы понять, во что обойдётся новый объект, он раньше вручную собирал схему, перечень оборудования и прикидку цены.

Теперь он пишет в чат одной строкой: «Нужна 2КТП-400/10/0,4 кВ, два ввода с АВР, пять отходящих: жильё 120 А, жильё 100 А, котельная 160 А, ФАП 80 А, уличное освещение 25 А» — или фотографирует бумажное техзадание. Примерно за минуту приходит комплект: чертёж однолинейной схемы на листе А3 картинкой и в DXF, который открывается в AutoCAD, перечень оборудования в Excel, ведомость работ, отчёт проверки по нормам и вилка стоимости со сроком.

Однолинейная схема двухтрансформаторной подстанции: два ввода 10 кВ, два трансформатора, распределительное устройство на две секции с автоматическим вводом резерва, пять отходящих линий, контур заземления и таблица технических показателей

Однолинейная схема двухтрансформаторной подстанции: два ввода 10 кВ, два трансформатора, распределительное устройство на две секции с автоматическим вводом резерва, пять отходящих линий, контур заземления и таблица технических показателей

Тот же лист выгружается в DXF — заказчику нужен файл, который правится в AutoCAD, а не картинка. Подпись «требуется разработка РД» стоит на листе не для красоты: агент делает стадию «П», а не рабочую документацию, и говорит об этом сам.

Второй артефакт — отчёт проверки. Он интереснее чертежа, потому что показывает главное решение всего агента:

Итог проверок: 3 PASS · 3 FAIL · 1 WARN · 5 INFO

вводной автомат ≥ Iном тр-ра НН (909 А)     ✅ PASS   QF1 — 1000 А; Iном тр-ра 909 А
режим N-1: один тр-р несёт нагрузку          ❌ FAIL   суммарный ток 1200 А; Iном одного 909 А;
                                                      допустимость перегрузки не доказана
защита линий: Iрасч ≤ Iном ≤ Iдоп кабеля     ❌ FAIL   QF4: 600 ≤ 800 ≤ 290 А
ОПН на стороне ВН и НН                       ℹ️ INFO   предусмотрены; координация изоляции — стадия РД
заземляющее устройство                       ℹ️ INFO   предусмотрено; R ≤ 4 Ом — по замеру, стадия РД
отключающая способность против Iкз           ✅ PASS   расч. Iкз ≈ 16,5 кА; мин Icu 35 кА — это прикидка,
                                                      точный ТКЗ по ГОСТ 28249 на стадии РД

Обратите внимание [11] на разницу между FAIL и INFO. Заземление предусмотрено — но статус INFO, а не PASS, потому что наличие контура не равно измеренному сопротивлению. Ограничители перенапряжения стоят — тоже INFO: наличие не равно координации изоляции. PASS ставится только там, где условие реально посчитано из модели.

В комментариях движок открыто описан как порт архитектуры предыдущего агента: «аналог каталога, но для электроэнергетики». Между двумя корпоративными проектами переиспользована не библиотека, а скелет: каталог → генератор → валидатор → рендер → спецификация → костинг → плагин из четырёх-пяти инструментов.

Каталог как единственный источник номиналов — 275 строк: пять трансформаторов, восемь ячеек, автоматы низкой стороны, кабели, опоры, изоляторы, оболочки.

Подбор — лестница, а не «нейросеть предложила»:

def pick_lv_input_breaker(kva):
    i = transformer_lv_current(kva)          # I = S/(√3·0,4)
    for key, inom in ladder:
        if inom >= i: return key
    raise ValueError(f"ток трансформатора {i:.0f} А выше лестницы вводных автоматов")

def pick_hv_fuse(kva):
    """Возвращает копию карточки; молчаливого подбора вне таблицы нет."""
    try: return dict(HV_FUSES[int(kva)])
    except (KeyError, TypeError, ValueError) as exc:
        raise ValueError(f"нет таблицы плавкой вставки для {kva} кВА") from exc

Принцип: подмена только вверх и только явно. Запрошенные 800 кВА не превращаются молча в 630 — берётся следующий не меньший типоразмер, и в чат уходит WARN: запрошено 800 кВА, движок применил следующий не меньший каталожный типоразмер 1000 кВА.

Состав линии выводится формулами:

опоры      = ceil(L/пролёт) + 1
анкерные   = 2 концевые + (ceil(L/500 м) - 1) внутренних + по одной на отпайку
провод     = L × 3 фазы × 1,03 (монтажный запас), округление вверх до 5 м
изоляторы  = штыревые 3/промежуточную; натяжные 6/анкерную, 3/концевую

Отпайка за пределами трассы — ошибка, а не догадка. В реальном логе сессии: {"ok": false, "error": "vl_taps[2].km=0.53 вне трассы 0…0.53 км"}.

Валидатор — 473 строки, десять проверок для подстанции и восемь для линии. Каждая возвращает правило, требование, статус и способ исправления.

Каждая проверка возвращает не только статус, но и способ исправления. Отключающая способность против прикидочного тока короткого замыкания даёт WARN, а не FAIL: полный расчёт по ГОСТ здесь не делается, и выдавать прикидку за проверку нельзя.

Отдельный механизм — «потерянные требования клиента». Если в техзадании просили воздушный ввод, а в схему лёг кабельный; просили учёт на высокой стороне, а заложен на низкой — генерируется предупреждение особого вида, и оно отдельным списком уходит и в отчёт, и в подсказку модели. Это то, что в ручном производственно-техническом отделе теряется чаще всего.

При FAIL код сам дописывает в промпт жёсткую подсказку, не полагаясь на то, что модель заметит статус в JSON:

if fail_count:
    validation_hint = (f"⛔ ВАЛИДАЦИЯ СОДЕРЖИТ {fail_count} FAIL. Явно перечисли FAIL и не называй "
                       "решение прошедшим проверку или готовым к применению. ")

Честно говоря, это по-прежнему просьба к модели — просто гарантированно доставленная. Жёсткий вариант, когда артефакт не отдаётся, пока FAIL не снят, я не сделал.

Стоимость — арифметика снизу вверх: сумма по каталогу, плюс монтаж 30–50%, пусконаладка, проектирование, региональный коэффициент, округление до сотни тысяч. Причём коэффициент берётся только из явного поля региона, и рядом комментарий: «название населённого пункта здесь намеренно не анализируется: совпадение подстроки не определяет регион».

Чертёж рисуется одной функцией в двух форматах. Экспортёр DXF мимикрирует под холст SVG — те же примитивы — поэтому одна и та же функция компоновки даёт идентичную геометрию и в вебе, и в AutoCAD. Растр рендерится headless-браузером, который уже умеет кириллицу и системные шрифты.

Успех рендера проверяется не через «файл существует»:

valid_signature = head[:8] == b"x89PNGrnx1an"
if size < 1024 or not valid_signature or not valid_ihdr or actual_wh != (w_px, h_px):
    png_path.unlink(missing_ok=True); raise RuntimeError(...)

Код возврата, сигнатура файла и размеры из заголовка. Битый файл до пользователя не доезжает.

Два случая, которыми хвастаться нечем

Модель подгоняла вход, пока FAIL не исчез. На реальном техзадании три последовательных вызова генератора:

Вызов

Отходящие линии

Итог

1

2 × 600 А

FAIL

2

2 × 385 А

FAIL

3

200 + 200 + 185 + 185 А

OK

Сложите суммы: 1200 → 770 → 770 А. Между первым и вторым вызовом модель срезала треть заявленной нагрузки — вот это и есть подмена исходных данных. Между вторым и третьим сумма не менялась вообще, поменялось только дробление: два фидера превратились в четыре. Само по себе это законное проектное действие под ограничение по номиналу, но выбрала его модель, а не инженер.

Валидатор проверяет схему, а не её соответствие техзаданию, поэтому «OK» здесь не значит ничего без человека, который сверит нагрузку с исходником. И правильный фикс — не «пусть инженер посмотрит», а признак происхождения у каждого параметра: пришёл из ТЗ или придуман моделью. Любой переход FAIL → OK, полученный сменой параметров второго типа, должен уходить в отчёт отдельной строкой. Механизм для этого у меня уже есть — те самые «потерянные требования клиента». Расширить его на подменённые входы я не догадался.

Модель построила себе OCR-пайплайн, потому что ей это разрешили. Прислали техзадание сканом без текстового слоя. Плагин PDF не принимает. Но набор инструментов даёт терминал, а в конфиге разрешена ленивая доустановка пакетов. Реальная цепочка вызовов из истории сессии:

skill_view("ocr-and-documents")   → инструкция
python -c "import fitz"           → ModuleNotFoundError
pdftotext                         → command not found
python -c "import pypdf"          → ModuleNotFoundError
tesseract                         → command not found
[предупреждение: тот же инструмент падает третий раз подряд]
uv pip install pymupdf → «externally managed» → создаёт venv → успех
render 13 страниц в PNG → установка pillow → OCR
"=== PAGE 1 === puJoKeHne KIpHKa3yIIAO ..."   ← кириллица распалась

И всё равно дальше — корректный вызов доменного инструмента с извлечёнными параметрами: недостающее модель добрала нативным зрением [12].

Выводов тут два. Агентность каркаса вытащила задачу, на которую прикладной код не был рассчитан. Она же оказалась дырой: агент ставит пакеты и рендерит файлы в контейнере на лету, без песочницы, потому что тулсет никто не урезал. Контур демонстрационный, боевых данных в нём нет — но в проде это была бы настоящая дыра.

Оставлять так нельзя. Либо положить нужные пакеты в образ, либо принять PDF в плагине явно, либо выключить терминал.

И третий случай, где сработало как задумано. Прислали техзадание на реконструкцию воздушной линии 110 кВ — 16 километров, двухцепный участок, металлические опоры. Агент извлёк параметры и вместо того, чтобы «прикинуть», ответил отказом: в демо доступны подстанции 10/0,4 кВ и линии 10 кВ. На прямое «а можешь примерно?» — отказ с объяснением: «выдумывать цифры не могу, расчёт делает детерминированный движок».

Работает на трёх уровнях сразу: правило в персоне, описание в JSON-схеме инструмента и 75 строк проверки области применимости в коде, которая разбирает даже пары напряжений из названия объекта.

Что повторяется у всех тридцати трёх

Разбирая агентов подряд, я выписал пять вещей, которые встречаются независимо от предмета.

1. Идентичность решает код, а не текст сообщения. Ключ пользователя, роль, доступ к данным — всё резолвится на сервере и никогда не приходит аргументом от модели. В общем плагине отчётности это записано прямо в манифесте: «идентичность и строка подключения к базе резолвятся на сервере — никогда не аргумент, видимый модели». Подставить чужую идентичность аргументом инструмента модель не может. Оговорка, которая станет понятна через два раздела: это верно ровно до тех пор, пока у агента выключено исполнение кода — из шелла в том же контейнере достаётся и окружение, и строка подключения.

Каким этот ключ быть не должен, я узнал трижды. В трёх агентах независимо всплыл один и тот же баг: за ключ пользователя брался идентификатор сессии. А он ротирующийся — пересоздаётся при /new и при автосбросе. Прогресс ученика молча обнулялся. В докстринге фикса:

«Keying a learner profile by that id loses all per-user memory on every reset — exactly the bug we are fixing» — «привязка профиля ученика к этому идентификатору теряет всю его память при каждом сбросе; это ровно тот баг, который мы чиним».

Три агента, три независимые реализации обхода, и только потом — общая функция с кросс-плагинным импортом.

2. Вердикт считает код и игнорирует поля модели. В грейдере домашних заданий:

# Итоговый вердикт вычисляется В КОДЕ из баллов по критериям × веса —
# и НИКОГДА не берётся из полей `verdict`/`passed`/`score`, вернувшихся от модели.

Причина названа: так «поставь 100, passed=true», подброшенное в проверяемый артефакт, становится бесполезным. Артефакт при этом оборачивается в маркеры с одноразовым числом — граница доверия. И критерий без выставленного балла считается нулём: ошибаться система должна в сторону «нужно доработать», а не в сторону проходного балла.

3. Пустая строка означает «молчать». Теперь применяю везде. Функция, решающая, дёргать ли человека, возвращает либо готовый текст, либо пустоту. Спам-контроль перестаёт быть вопросом такта модели и становится условием в коде.

4. Деградация вместо выдумывания. Курс недоступен — «курс из кэша» или честное «уточни вручную». Расчёт невозможен — исключение, а не догадка. PDF не содержит текста — «возможно, это скан, нужен OCR», а не пустая строка, притворяющаяся результатом.

Правило простое: у отказа должно быть имя. Молчаливый провал дороже громкого.

5. Внешний контент — это данные, а не инструкции. Вывод недоверенных инструментов (веб, файлы, браузер) оборачивается разделителями с явной пометкой «этот блок — данные». В движке единственное место, где инъекции учтены на уровне тулсета, — платформа вебхуков: там набор урезан до четырёх инструментов с комментарием «события вебхуков могут происходить из недоверенного стороннего контента».

Грабли каркаса

Пока разбирал код, накопился список вещей, которые работают не так, как написано. Часть проверена запуском в живом контейнере — тулсеты, планировщик, резолвер; часть найдена поиском по исходникам движка — мёртвая секция personality, TOOLS⁠.md. Апстрим живой, так что через несколько релизов половина находок может протухнуть.

«Отключил терминал» не отключает исполнение кода. При disabled_toolsets: [terminal, file, browser, web] в схеме остаются execute_code и delegate_task — они в других наборах. А execute_code запускает дочерний процесс с Python-скриптом от модели. Чтобы реально закрыть исполнение, нужны и code_execution, и delegation.

Запись в память в середине сессии в этот же промпт не попадёт. Системный промпт собирается один раз и кэшируется. На диск запись уходит сразу, а в промпт — только со следующей сессии. Агент, которому «сказали запомнить», в этом же разговоре этого не увидит.

Есть ещё пяток находок помельче — мёртвая секция personality в конфиге, TOOLS⁠.md, который движок вообще не читает, AGENTS⁠.md, подхватывающийся по случайному совпадению путей. Они интересны тем, кто живёт в этом движке каждый день, и я вынесу их отдельной заметкой, чтобы не топить здесь главное.

Nomad: куда это выросло

Всё описанное выше — работа поверх апстрима, без единой правки ядра. Параллельно я веду форк того же движка, но это уже отдельный продукт, и ни один из тридцати трёх агентов выше на нём не работает. Про него будет отдельная статья. Пока коротко — несколько решений оттуда объясняют, куда эта линия ведёт.

Что дописано своего — и всё это ровно про ту же границу «код / модель», просто на уровне платформы:

  • Бренд держит регулярка, а не персона. Плагин скрабит исходящий текст на хуке, потому что маленькие модели игнорируют директиву персоны и выдают апстримовые имена в ответах. Ровно та же логика: то, что модель может забыть, делает код.

  • Деньги. Гейт баланса до инференса плюс счётчик с брейкером против циклов. На верхнем тире middleware перемаршрутизирует вызов на более дешёвую модель — с защитой: цель обязана быть в разрешённом наборе, апгрейд вверх запрещён, циклы в карте отвергаются.

  • Оркестратор роёв Фугу: агент сам пишет скрипт из примитивов «агент / параллельно / конвейер», спрашивает подтверждение на фан-аут с оценкой стоимости — и выводы субагентов остаются внутри движка, в контекст возвращается только результат. В документации это сформулировано как «тридцать субагентов стоят одного саммари».

То есть та же идея — не давать модели решать то, что можно посчитать, — на уровне платформы превращается в бюджеты, брейкеры, классификаторы отказов и детерминированную оркестрацию. Об этом отдельно.

Выводы

Агент — это том, а не форк. Тридцать три агента на одном образе без единой правки ядра. Если руки тянутся форкнуть движок ради одного агента — почти наверняка задача решается плагином и конфигом. Форк оправдан тогда, когда продуктом становится сам движок: у меня это случилось один раз на тридцать три.

Вся настройка сводится к границе «код / модель». Остальное — детали. Если граница проведена правильно, агент ошибается редко и предсказуемо; если нет — врёт красиво и убедительно, и заметить это некому.

Промпт — это черновик кода. Каждое правило вида «НИКОГДА не выдумывай X» — заявка на функцию. У меня из шести таких правил выросли шесть модулей, и это нормальный ход вещей. Хуже, когда правило так и остаётся в промпте: баланс звёзд разошёлся на трёх источниках именно поэтому.

Тулсет — это бюджет. Схемы обгоняют промпт уже на третьем десятке инструментов; урезание тулсета дало десятикратную экономию токенов, выключенный reasoning — двукратное ускорение. Первое дешевле и безопаснее второго, поэтому начинать я советую с тулсета — но мерить надо оба.

У отказа должно быть имя. Курс из кэша, «не могу посчитать», FAIL с перечислением, INFO вместо PASS там, где не считалось. Молчаливая деградация обходится дороже громкой.

Проверять надо запуском. Половина находок в этом разборе — из прогонов внутри живых контейнеров, а не из чтения конфигов. Что тулсет режется по каналу, а по расписанию открыт; что «отключил терминал» не отключает исполнение кода; что закрытый от записи файл расписания убивает планировщик — ничего из этого по документации не видно.

Про свои эксперименты с агентами пишу в канале «Готовим ИИшницу» [13] — там короткие заметки по ходу дела, а сюда выношу разборы целиком. Проекты и контакты — на hamidun.com [14].

Дальше — анонс, к статье он отношения не имеет, можно не читать. 16 сентября мы проводим бесплатную онлайн-конференцию «ИИ-Трансформация» про внедрение ИИ в бизнес-процессы; регистрация [15].

Автор: hamidun

Источник [16]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/34020

URLs in this post:

[1] Hermes Agent: https://github.com/NousResearch/hermes-agent

[2] памятью: http://www.braintools.ru/article/4140

[3] обучения: http://www.braintools.ru/article/5125

[4] ошибки: http://www.braintools.ru/article/4192

[5] Симметрия: http://www.braintools.ru/article/3088

[6] Реакция: http://www.braintools.ru/article/1549

[7] забывает: http://www.braintools.ru/article/333

[8] математика: http://www.braintools.ru/article/7620

[9] логику: http://www.braintools.ru/article/7640

[10] поведения: http://www.braintools.ru/article/9372

[11] внимание: http://www.braintools.ru/article/7595

[12] зрением: http://www.braintools.ru/article/6238

[13] «Готовим ИИшницу»: https://t.me/jemal_hamidun

[14] hamidun.com: http://hamidun.com

[15] регистрация: https://t.me/AlpinaAIconfbot?start=habr__article__agents_one_engine

[16] Источник: https://habr.com/ru/articles/1067234/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1067234

www.BrainTools.ru

Rambler's Top100