Финал саги: мой ИИ-ассистент на Hermes улетел на GitHub. ¡Забирайте! А грабли расскажу следом

от автора

Формат: туториал + личный опыт. Сложность: средняя. Время чтения: ~9 минут.

Это третья и финальная часть истории про моего личного ИИ-агента. Предыдущие — «Как я переезжал ИИ-агента с OpenClaw на Hermes и собрал все грабли» и «DeepSeek, бесплатная NVIDIA и Codex: как я научил агента думать дёшево и работать, пока я сплю». Сквозную нумерацию граблей продолжаю с того места, где остановился, — с №26. Читать предыдущие не обязательно: рекап будет. Больше частей не планирую — на этой я историю закругляю.

Кот-космонавт Паспарту за штурвалом корабля GitHub

Тот самый кот-космонавт: в первых двух частях он просто летел в скафандре, а теперь пересел за штурвал корабля с бортом «GitHub», обвешанного всеми навыками — память, поиск, голос, документы, картинки, задачи. Рисовал, как всегда, сам бот

Быстрый рекап, чтобы ты не бегал по прошлым статьям. У меня есть личный ИИ-ассистент — агент на основе Hermes Agent (открытый фреймворк от Nous Research), зовут его Паспарту. Живёт на сервере, отвечает в Telegram, помнит проекты, ходит в интернет, шлёт письма, ведёт календарь. В первой статье я переехал на Hermes с OpenClaw. Во второй — пересадил ассистента на дешёвый DeepSeek и приделал Codex, чтобы сложные задачи доводились до конца сами, без моего участия.

Что в итоге вышло — личный ИИ-ассистент на Hermes, который прямо у тебя на компьютере или на сервере умеет:

  • 💬 общаться и помогать — мозг на DeepSeek, умно и дёшево;

  • 🧠 помнить — банк памяти с поиском, ничего не теряет между сессиями;

  • 🔎 искать в интернете — свежие факты, цены, новости;

  • 🖼️ работать с картинками — рисует и редактирует на Qwen-Image: генерирует с нуля, а пришлёшь фото — подправит, дорисует деталь или склеит несколько кадров в один (лица не трогает);

  • 🎙️ понимать голос — расшифровывает голосовые прямо на месте, локально;

  • 📄 делать документы — DOCX, PDF и презентации .pptx сразу на Google Диск;

  • 📬 жить в Google — почта, календарь, диск, документы, таблицы;

  • вести задачи — трекер с напоминаниями;

  • 🎨 красиво отвечать — таблицы, чек-листы, кнопки по-русски, живой стрим.

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

Сначала — коробка: где взять и как поставить

Паспарту — личный ИИ-ассистент на основе Hermes Agent — живёт в Docker на твоей машине (домашний компьютер или свой VPS — где тебе удобно): ключи и данные остаются у тебя, а общение — обычной перепиской с ботом в Telegram. Полный список возможностей — выше; всё лишнее выключается тумблерами (нет ключа для картинок на Qwen или платного поиска Perplexity — эти функции просто отключатся, без ошибок и истерик). При установке выбираешь профиль (minimal / standard / full) — готовый набор функций под твой аппетит.

GitHub: https://github.com/AML1969/Paspartu

Приготовь заранее три вещи: токен бота от @BotFather, свой Telegram ID от @userinfobot и ключ DeepSeek (это «мозг», он дешёвый). Ещё нужен Docker с плагином compose v2, ~4 ГБ RAM и ~15 ГБ диска.

🐧 Linux / Ubuntu-сервер — ветка linux:

git clone -b linux https://github.com/AML1969/Paspartu.git && cd Paspartu./install.sh

🪟 Windows 11 (через WSL2) — ветка main:

git clone https://github.com/AML1969/Paspartu.git && cd Paspartu./install.sh

Мастер сам задаст вопросы, проверит ключи, соберёт образ и запустит бота — дальше пишешь ему /start в Telegram. Первая сборка идёт несколько минут (качается ~3 ГБ) — это нормально, не пугайся.

📘 Подробная пошаговая инструкция (как с нуля поставить Linux+Docker на Windows, что отвечать мастеру, как проверить и что делать при ошибках) — в репозитории: INSTALL.md.

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


Мышцы: память, фон и Docker

Память, которую наконец можно искать

Самая обидная правда про любого ИИ-агента: он помнит ровно до края контекста, а всё, что «запомнил» в файл памяти, — это маленький блокнот, который читается целиком на каждый запрос. У Hermes этот блокнот — примерно 2200 символов. Полтора года жизни туда не влезают при всём желании.

🔴 Грабля №26. Бот клянётся, что помнит, — а помнит 2200 символов. Спрашиваешь про давнее решение по проекту — бот уверенно отвечает мимо. Не потому что глупый, а потому что нужного факта просто нет в блокноте, его вытеснило новым. И агент не скажет «я не знаю» — он достроит правдоподобный ответ. Уговорами это не лечится, лечится архитектурой: внешняя память, в которую он сам кладёт факты и в которой сам ищет, прежде чем начать сочинять.

Так появился hmem — банк памяти. Идея простая: память — это git-репозиторий из markdown-файлов, поверх которого живёт быстрый полнотекстовый поиск (FTS5 bm25 + триграммы, поэтому кириллица и опечатки прощаются). Каждый долговременный факт — отдельный файл-узел. А сверху — онтология в духе Palantir: у любого знания есть тип, и банк разложен на три полки — entities/ (сущности: сервер, бот, проект), events/ (что случилось: деплой, фикс, решение) и documents/ (что описывает: план, отчёт). У каждого узла есть связи на другие, поэтому память — это не свалка заметок, а граф: «этот инцидент случился на этом сервере в рамках этого проекта».

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

Codex ушёл в фон — и больше не отчитывается

Во второй статье Codex уже умел довести тяжёлую задачу до конца. Но был нюанс: пока он работал, чат был занят. Теперь он работает по-настоящему в фоне: пишешь боту задачу со словом-триггером Codex — мгновенно получаешь квитанцию «✅ Принял задачу №N, ~X минут, можешь давать другие». Чат свободен, короткие вопросы подхватывает дешёвый DeepSeek, тяжёлую задачу молотит Codex. Красота.

И тут — изящное продолжение самой обидной граблы прошлой статьи. Тогда (№25) бот соврал, что запускал Codex, и подсунул мне импровизацию вместо результата.

🔴 Грабля №27. Бот не должен рассказывать о результате, которого у него нет. Если позволить агенту самому «доложить, как там Codex», он снова начнёт сочинять — потому что в момент ответа результата ещё физически нет. Решение архитектурное: за статусы и финал отвечает не бот, а отдельный наблюдатель — системный таймер раз в 3 минуты читает логи запуска и шлёт сообщения в Telegram напрямую, мимо модели. Бот запустил задачу, отчитался квитанцией — и закончил ход. Правда теперь приходит из логов, а не из фантазии агента. Граблю №25 победили не уговорами, а тем, что отобрали у бота возможность врать.

Почему всё это пришлось упаковать в Docker

Заметил общий мотив? Память, русские кнопки, rich-таблицы, фикс стрима — это всё патчи по живому коду, правки прямо в установленный пакет агента.

🔴 Грабля №28. Патчи стираются при каждом обновлении. Любое обновление пакета честно затирает их подчистую. Поэтому рядом жил скрипт, который после каждого апдейта перенакатывал все патчи по порядку (один зависит от другого) и только потом перезапускал бота. Накатываются они по «якорям» — точным строчкам исходника; перепишут авторы этот кусок — якорь не найдётся, перенакат встанет. Я живу с этим техдолгом, но каждое обновление делаю с лёгким холодком под ложечкой.

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


В догонку: грабли, которые вылезли уже после GitHub

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

🔴 Грабля №29. «Не помню» — хотя всё помнит. Пишу боту: «мы недавно обсуждали горные курорты рядом с Вильфраншем». Бот честно лезет в поиск по истории разговоров… и отвечает: «не могу найти ту сессию — ни в памяти, ни в истории». А сессия — есть, двумя неделями раньше, слово «Вильфранш» встречается в ней десятки раз. Стал разбирать по базе — и вот причина: полнотекстовый поиск (SQLite FTS5) склеивает все слова запроса через И и не знает русской морфологии. Бот искал фразой «Вильфранш горные курорты горы» — а в той сессии есть «горах» и «горные», но нет ровно «горы». Одно несогласованное слово — и весь запрос честно даёт ноль. Хуже того: получив ноль, бот не говорил «дай уточню», а сразу выпаливал «не помню». Лечение — правило поиска: сначала ищи одним самым характерным словом (лучше именем собственным, ровно в написании собеседника), и только потом добавляй синонимы через OR и окончания через *. И — категорический запрет говорить «не помню», пока не сделан этот первый шаг. «Вильфранш» одним словом находит ту самую сессию мгновенно.

🔴 Грабля №30. Бот «читает» страницу вслепую — и склеивает двух разных людей в одного. Спрашиваю про конкретного человека — бот выдаёт биографию, в которой перемешаны факты двух разных людей с одинаковым ФИО. Стал копать — и обнаружил вещь похуже одной ошибки: инструмент чтения страниц (web_extract) не работал вообще, ни у кого. В конфиге поле провайдера было пустой строкой, движок тихо откатывался на поисковый бэкенд, который умеет искать, но не умеет читать, — и бот, вместо того чтобы открыть страницу, достраивал её содержимое по памяти и обрывкам поисковой выдачи. Отсюда и склейка тёзок: раз текст реально не читается, разные люди с одним именем сливаются в одну биографию. Лечение из двух частей. Во-первых, свой беcключевой ридер страниц (обычный HTTP-запрос + разбор HTML в текст) — теперь чтение не может «умереть» из-за протухшего чужого ключа. Во-вторых, правило «люди и факты — без склеек»: совпадение ФИО не доказывает, что это один человек (различай по году рождения, должности, региону); один факт — один источник; важное — только из первоисточника; не нашёл — так и скажи. Та самая страница, на которой бот раньше лепил чужую биографию, теперь просто честно читается.

🔴 Грабля №31. Ссылки, которые бот не открывал, — и часть из них ведёт в никуда. Классика, на которой я ловил бота не раз: он вставляет в ответ ссылку — на товар, на страницу, на статью, — взятую из поисковой выдачи или из памяти, ни разу её не открыв. Иногда ссылка верная, а иногда — 404 или чужая устаревшая. И упираешься в это на ровном месте: «ссылки не открываются». Мягкое пожелание «проверяй ссылки, не выдумывай URL» в правилах уже висело — но под нагрузкой не держалось. Пришлось делать жёсткий гейт на каждый ответ: перед тем как вставить любую внешнюю ссылку, бот обязан её физически открыть (запросом с проверкой кода ответа — годится только итоговый 200); URL нельзя сочинять, только из результата инструмента; ссылка битая — не слать её вовсе, а дать проверенную альтернативу и честно сказать «прямую страницу подтвердить не смог».

И один мета-урок, общий для всех троих

Заметил, что все три догоняющие грабли — про одно и то же? Это ровно тот враг, с которого началась статья (№26 — «клянётся, что помнит») и с которым мы бились ещё во второй части (№25 — «соврал, что запускал Codex»): в момент ответа у агента нет нужного факта — и он достраивает правдоподобное вместо честного «не знаю».

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


Итог

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

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

P.S. Кот-космонавт из первых двух частей передаёт привет и сообщает, что теперь он официально упакован — с типом entity, связями, индексом и собственным Dockerfile. Рисовал, как и прежде, сам Паспарту.

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

ссылка на оригинал статьи https://habr.com/ru/articles/1065282/