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

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

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

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

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

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

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

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

  • ?️ работать с картинками - генерирует и распознаёт: рисует с нуля и понимает, что на присланном фото (с ключом OpenAI);

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

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

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

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

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

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

Готовое решение из коробки: где взять и как поставить

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

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

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

⚠️ Для читателей из России: без ключа OpenAI бот полностью работоспособен - отвалится только работа с картинками (генерация и распознавание), всё остальное на месте. А если картинки нужны: к OpenAI API из России напрямую не подключиться (гео-блок, и российской картой не оплатить) - понадобится VPN с зарубежным IP и иностранная карта либо российский сервис-прокси с оплатой в рублях (у бота меняешь baseURL и ключ на их).

? 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, готовый образ из реестра, облако - или героически объясняете каждому другу про патчи по живому. И где вас ловил на вранье собственный бот - продолжаем народную коллекцию граблей.

Комментарии (0)