simakov.space
Задание 2 · Симаков Е. С.

Автономный агент для исследовательских задач веб-инженера

Hermes Agent на собственном VPS: безопасное развёртывание, устройство памяти и навыков, модель стоимости эксплуатации и сравнение моделей на десяти контрольных задачах.

Агент работаетHermes Agent в Docker на сервере сайта, модель deepseek-v4-flash (контекст 1M), 58 навыков + свой
Память, навык, cronпроверены: ответ из MEMORY.md, конспект по ссылке, мониторинг сайта 13:48 → 200, 331 мс
Цена запросапрефикс ≈ 14 500 токенов, кэш 97–99 %; одна задача P4 (T07) — от 1,90 до 6,13 ₽
Изоляцияиз контейнера закрыты API, Postgres, SSH; открыт только сайт по 443

01Развёртывание Hermes Agent

Hermes Agent — опенсорсный (MIT) оркестратор агентов от Nous Research. Он состоит из шлюзов (CLI, Telegram, Discord…), инструментов (терминал, файлы, браузер, планировщик), навыков и подключаемой LLM. За основу взято руководство thecode.media, детали сверены с официальной документацией. Все файлы лежат в репозитории сайта, в каталоге agent-lab/.

Журнал параметров

Фактические значения подставляются из journal.json. Пустые поля ещё не зафиксированы.

Загрузка журнала…

Ход работы

  1. Сервер

    Используется сервер сайта: Ubuntu 26.04.1, 1 vCPU, 891 МБ RAM, 14 ГБ диска, Амстердам. На нём же работают nginx, API стенда и PostgreSQL. Модель облачная, поэтому минимальных требований Hermes (1 vCPU, 1 ГБ) хватает с учётом swap. Провайдер, тариф и цена записываются в журнал.

  2. Подготовка сервера — deploy/agent/setup-agent.sh, запускается один раз
    • swap 1 ГБ: сайт, Postgres и агент вместе не помещаются в 891 МБ;
    • пользователь agent без пароля и sudo; доступ к серверу — только по SSH-ключу;
    • сетевая изоляция контейнера — agent-firewall.sh (сервис systemd, правила восстанавливаются после перезагрузки);
    • агенту не нужны входящие порты: Telegram-шлюз работает через исходящий long polling.

    Из контейнера curl 172.x.0.1:3001 (API) и :5432 (Postgres) не проходят, https://simakov.space открывается.

  3. Docker и Compose

    Пакеты docker.io и docker-compose-v2 из репозитория Ubuntu. Ротация логов задаётся в /etc/docker/daemon.json. Пользователь agent в группу docker не добавляется: группа docker равносильна root, а на сервере сайта это недопустимо. Контейнером управляет администратор.

    docker run --rm hello-world печатает «Hello from Docker!»

  4. LLM-провайдер и ключ

    Основная модель — deepseek-v4-flash через OpenAI-совместимый шлюз llms.dotpoin.com. Это сторонний перепродавец DeepSeek, а не официальный API: запросы агента проходят через него. Шлюз сообщает окно 1 048 576 токенов; вызов инструментов проверен запросом с tools. Hermes отклоняет модели с окном меньше 64 000 токенов, поэтому окно проверяется до запуска. Для P4 дополнительно подключаются GigaChat 2 Max (отечественная, 128K) и Qwen3 8B через Ollama (локальная, контекст поднят до 65 536). Бесплатные :free-модели для работы не годятся: общий лимит запросов, частые HTTP 429.

  5. Лимит расходов

    Лимит ставится до первого запуска планировщика: в кабинете провайдера — лимит на ключ или только предоплаченный баланс без автопополнения. Значения лимитов записываются в журнал. Ошибочная cron-задача раз в 5 минут — это 8 640 запусков в месяц; расчёт ниже, в разделе «Экономика».

  6. Telegram-шлюз

    Бот создаётся в @BotFather (/newbot), числовой ID пользователя берётся у @userinfobot. Токен хранится только в ~/.hermes на VPS, в репозитории его нет. Если токен утёк — /revoke в @BotFather и новый токен.

  7. Мастер настройки в контейнере
    cd /opt/hermes
    docker compose run --rm hermes-agent setup
    ls /home/agent/.hermes   # config.yaml, .env, memories/, skills/ — конфиг записан на хост

    Мастер запускается тем же compose-сервисом, что и шлюз, поэтому пишет ровно в тот каталог, который смонтирован при работе: /home/agent/.hermes на хосте = /opt/data в контейнере. Модель и шлюз задаются в config.yaml (образец — deploy/agent/config.example.yaml), ключи — в .env с правами 600.

  8. compose.yaml — deploy/agent/compose.yaml → /opt/hermes/compose.yaml
    services:
      hermes-agent:
        image: nousresearch/hermes-agent:latest
        container_name: hermes_agent
        restart: unless-stopped
        command: gateway run
        environment:
          HERMES_UID: "${AGENT_UID}"     # процесс — под UID пользователя agent
          HERMES_GID: "${AGENT_GID}"
        volumes:
          - /home/agent/.hermes:/opt/data
        networks: [agent]                # мост br-agent, см. agent-firewall.sh
        security_opt: [ "no-new-privileges:true" ]
        cpus: 0.8
        mem_limit: 700m
        memswap_limit: 1400m
        pids_limit: 256
        logging:
          driver: json-file
          options: { max-size: "10m", max-file: "5" }
    networks:
      agent:
        driver_opts: { com.docker.network.bridge.name: br-agent }

    Портов наружу нет. Лимиты CPU, памяти и числа процессов не дают зациклившемуся агенту положить сайт.

  9. Запуск и логи
    docker compose up -d && docker compose logs -f

    В строке навыков ненулевое число (… 87 total bundled), предупреждения No messaging platforms enabled нет.

  10. Первое сообщение

    Боту отправляется «Кто ты и какие у тебя инструменты?». Время отклика — от отправки до первого ответа, по меткам времени Telegram, медиана трёх попыток. Записывается в журнал.

  11. Ограничение доступа

    В ~/.hermes/.env указан только TELEGRAM_ALLOWED_USERS=<мой ID>; GATEWAY_ALLOW_ALL_USERS не задан. Проверка — сообщение с тестового аккаунта.

    Бот не отвечает постороннему аккаунту. Запись об этом ищется в hermes logs gateway.

  12. Память — agent-lab/server/memories/ → ~/.hermes/memories/

    USER.md (568 из 1 375 символов) хранит предметную область, стиль, формат ответов и язык. MEMORY.md (770 из 2 200) — контекст исследования и договорённости. Агент читает их при старте сессии, так что после правки нужна новая сессия (/new).

    Контрольный вопрос: «Как мы договорились считать стабильный FPS и как называется эталонная сцена?» Правильный ответ («5-й перцентиль за 30 с», «Нива») есть только в MEMORY.md, в интернете его не найти.

  13. Инструменты

    Проверяется каждый набор по отдельности: веб-поиск («найди RFC 9110, раздел про 413»), чтение страницы (https://agent.simakov.space/fixtures/changelog.html), файлы (создать и прочитать /opt/data/research/test.md), терминал (df -h, uname -a), планировщик (/cron list). Отдельно проверяется, что опасная команда (например, rm -rf вне рабочего каталога) запрашивает подтверждение. Эту защиту не отключать.

  14. Первый навык — agent-lab/server/skills/source-digest/SKILL.md

    «Конспект источника». Условие срабатывания: пользователь прислал ссылку и просит конспект, сводку или разбор. Шаги: загрузить страницу, извлечь метаданные, выписать 3–5 утверждений с дословными цитатами, сохранить файл в /opt/data/research/digests/, ответить путём и итоговой строкой. Негативная проверка — вопрос без ссылки: навык срабатывать не должен.

    По сообщению «сделай конспект https://…» появился файл, в ответе есть «Источников: N, из них первичных: M».

  15. Задача по расписанию
    /cron add "0 9 * * *" "Проверь https://simakov.space/ и https://simakov.space/api/benchmarks: код ответа, время ответа, число записей в JSON. Допиши строку в /opt/data/research/monitoring.csv. Если что-то не 200 — начни ответ со слова ALERT, иначе ответь [SILENT]."

    Задания хранятся в ~/.hermes/cron/jobs.json, вывод каждого запуска — в ~/.hermes/cron/output/<job>/<время>.md, журнал попыток — hermes cron runs. [SILENT] подавляет доставку в Telegram, когда всё в порядке.

  16. Логи
    • stdout контейнера: json-file, 5 × 10 МБ (не больше 50 МБ);
    • ~/.hermes/logs/ (agent.log, errors.log, gateway.log) — встроенная ротация Hermes (agent.log.1…), просмотр через hermes logs gateway -f;
    • вывод cron и отчёты P4: срок хранения 90 дней. Пользовательский crontab: find ~/.hermes/cron/output -type f -mtime +90 -delete;
    • сессии в state.db не удаляются: они нужны для анализа шагов и отказов в P4.
  17. Отладка

    Ошибки, гипотезы и решения ведутся в журнале (раздел debug в journal.json) и показываются в таблице ниже. Известные по руководству и документации ситуации:

    СимптомГипотезаРешение
    0 total bundled + No messaging platforms enabledАгент не видит конфиг: смонтирован не тот каталогМонтировать в /opt/data (не /opt/hermes) тот же каталог, куда писал setup
    HTTP 429 от провайдераБесплатная модель, общий лимитПлатная модель с лимитом бюджета на ключе
    Auxiliary title generation failedНе задана вспомогательная модель для заголовков сессийБезвредно; при желании задать дешёвую модель в auxiliary
    Модель отклонена при стартеОкно контекста меньше 64KДругая модель; для Ollama — OLLAMA_CONTEXT_LENGTH=65536
    Контейнер перезапускается, KilledOOM на 1 ГБ RAMswap 1 ГБ, mem_limit, не запускать параллельно второй шлюз на те же данные

T2Память, контекст и навыки

Как агент живёт с ограниченным контекстным окном: что он видит в каждом запросе, что достаёт по требованию и что переносит между сессиями. Описание механики Hermes опирается на документацию разделов Memory, Skills и Cron.

Три уровня: окно, история, память

уровень 1Контекстное окно

Всё, что модель видит в одном запросе: системный промпт, схемы инструментов, снимок памяти, список навыков и сообщения текущей сессии вместе с результатами вызовов инструментов. Окно ограничено (64K–1M токенов), каждый токен в нём оплачивается при каждом запросе. Вне запроса модель ничего не помнит.

уровень 2История сессий

Полные журналы прошлых разговоров в SQLite (~/.hermes/state.db) с полнотекстовым поиском FTS5. В окно они не попадают, агент достаёт нужные фрагменты инструментом session_search. Это архив: он хранит всё дословно, но агент должен сам догадаться, что и где искать. Длинную текущую сессию Hermes сжимает (compaction), и сжатие повторяется по всё более длинной истории. Поэтому документация советует начинать /new на границе задач.

уровень 3Долговременная память

Два небольших файла, MEMORY.md (2 200 символов, около 800 токенов) и USER.md (1 375 символов, около 500 токенов). Они целиком вставляются в системный промпт каждой сессии. Маленький размер выбран сознательно: память — это постоянная надбавка к цене каждого запроса.

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

Механика файловой памяти

ВопросКак устроено в Hermes
Что пишетсяUSER.md — кто пользователь, предпочтения, стиль, язык. MEMORY.md — заметки агента: контекст проектов, договорённости, выводы из решённых задач. Записи короткие, по одному факту.
Кто инициирует(1) Сама модель во время диалога, через инструмент memory с действиями add, patch, remove и целью memory или user. (2) Фоновый self-improvement review после хода: отдельный вызов (можно дешёвой моделью) решает, что стоит запомнить. (3) Пользователь — просьбой «запомни…» или прямой правкой файла. Режим memory.write_approval: true складывает записи в очередь (/memory pending, /memory approve).
КогдаЗапись попадает на диск сразу, но системный промпт текущей сессии собран из снимка, сделанного на старте. Новая запись начнёт влиять на поведение только со следующей сессии. Отсюда практическое правило: после правки памяти — /new.
ПротиворечияАвтоматического механизма разрешения нет. Есть защита от грубых ошибок: точные дубли отклоняются, patch и remove требуют уникальную подстроку (неоднозначное совпадение — ошибка), отложенная правка отклоняется, если запись успела измениться. Решить, что новое сведение заменяет старое, должна сама модель, сделав patch. Если она вместо этого сделала add, в памяти останутся два противоречащих факта, и модель будет выбирать между ними в каждой сессии. Поэтому договорённости стоит формулировать как одну запись с датой и регулярно просматривать файл.
РазрастаниеЖёсткий лимит символов. add сверх лимита возвращает ошибку со списком записей и числом символов, которые нужно освободить. После этого агент одной пачкой операций удаляет или сокращает старое и добавляет новое. Автосжатия нет: при заполнении больше 80 % агенту рекомендуется объединять записи. Плюс — размер памяти и её стоимость ограничены. Минус — при консолидации модель может выбросить важное: решение о забывании принимает модель, а не пользователь.

Сравнение с другими подходами к памяти

ПодходЧто хранит и как достаётСильные стороныСлабые стороны
Файловая память (Hermes, CLAUDE.md, AGENTS.md)Короткий текст целиком в каждом промптеПрозрачна, читается и правится руками, версионируется в git, не нужна инфраструктура, поведение детерминированоОбъём ~1K токенов; нет поиска; противоречия и забывание — на совести модели
Векторная БД + RAGФрагменты документов с эмбеддингами; в окно попадают top-k похожих на запросМасштаб — тысячи документов; поиск по смыслу, а не по словамПохожее ≠ нужное; теряются точные числа и связи между фрагментами; качество зависит от нарезки и модели эмбеддингов; трудно объяснить, почему найдено именно это
Суммаризация истории (compaction)Старая часть диалога заменяется сжатым пересказомДлинная сессия продолжается без переполнения окнаПотери накапливаются от сжатия к сжатию: сначала пропадают детали, потом цитаты и ссылки; ошибка в пересказе становится «фактом»
Граф знанийСущности и связи (автор → статья → метод → результат); запросы по графуМногошаговые вопросы («какие методы из статей 2025 года проверялись на мобильных GPU»), явные связи, видно происхождение фактаДорогое построение (извлечение сущностей — тоже LLM с ошибками); нужна схема; избыточен для мелких задач
Внешняя база фактов (SQL, таблица замеров)Структурированные записи, агент делает запросы инструментомТочность, агрегаты, проверяемость; данные не зависят от моделиПишет туда код или пользователь; агенту нужен инструмент и знание схемы

В Hermes эти слои совмещаются. Файловая память остаётся всегда, история доступна через session_search, а внешние провайдеры памяти (Honcho, Mem0, Supermemory и др., hermes memory setup) работают рядом с файлами, но не вместо них.

Когда файловой памяти достаточно

Когда недостаточно

Навыки: происхождение и риски

Навык — это каталог с SKILL.md: frontmatter с name, description и version, в теле — условие применения, шаги и проверка, рядом могут лежать вспомогательные файлы. Навыки подгружаются постепенно (progressive disclosure). В окне всегда только имя и описание из skills_list(); полный текст загружается через skill_view(), только когда модель решила, что навык подходит. Так сотни навыков почти не занимают контекста.

Где живётКогда действуетЧто задаёт
ПромптСообщение пользователяОдин запросЧто сделать сейчас
Системная инструкцияСистемный промпт (+ USER.md / MEMORY.md)Всегда, в каждой сессииРоль, правила, стиль; поведение по умолчанию
ИнструментКод с JSON-схемой (terminal, web, memory, cronjob…)Когда модель его вызываетНовое действие во внешнем мире. Без инструмента агент только пишет текст
НавыкФайл ~/.hermes/skills/<имя>/SKILL.mdКогда задача подходит под descriptionПроцедуру: какие инструменты, в каком порядке, как проверить результат. Знание «как», а не «что»

Автоматическое порождение. У агента есть инструмент skill_manage (create, patch, delete…). Документация называет поводы создать навык: многошаговый процесс, который удалось выстроить и стоит повторить, и исправление подхода пользователем. Фоновый review после хода тоже может предложить навык. По сути агент пишет себе регламент по следам удачной сессии.

Риски:

Меры, принятые в работе: skills.write_approval: true — новые навыки и правки ждут ручного одобрения (/skills pending); каталог ~/.hermes/skills под git, чтобы каждая правка была видна диффом; у каждого навыка раздел «Проверка» с внешним признаком успеха; на время эксперимента P4 память и навыки откатываются к снимку перед каждым прогоном.

T2Экономика: модель стоимости

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

Модель

Пусть C₀ — постоянная часть запроса (системный промпт, схемы инструментов, память, список навыков, сообщение пользователя), Δ — сколько токенов добавляет один шаг (вызов и результат инструмента), n — число шагов, o — выходных токенов на шаг. Тогда за одну задачу:

входные токены   T_in  = n·C₀ + Δ·n(n−1)/2        # растут квадратично по числу шагов
выходные токены  T_out = n·o
стоимость        S = [ (1−h)·T_in·p_in + h·T_in·p_cache + T_out·p_out ] / 10⁶
месяц            M = 30·(задач_в_день·S(n) + cron_в_день·S(n_cron)) + VPS

Здесь h — доля входных токенов, прочитанных из кэша промпта. Постоянный префикс C₀ одинаков во всех шагах, поэтому почти весь вход попадает в кэш. Значения по умолчанию измерены на этом агенте (отчёты --usage-file и журнал вызовов API): ответ одним словом — 14 188 входных токенов, шаг cron-задачи — 14 943, доля кэша — 97–99 %. Отсюда C₀ = 14 500 и h = 95 % с запасом. Δ зависит от инструмента: вывод команды терминала — сотни токенов, загруженная веб-страница — тысячи. Взято 1 500.

Модель
Задача
Прочее
СценарийЗадач/день × шаговCron/день × шаговТокены/месLLM, ₽/месИтого с VPS
Структура месячных затрат, ₽
интерактивные задачиcronVPS

Что видно из модели

Цифры ниже — при параметрах по умолчанию (C₀ = 14 500, Δ = 1 500, h = 95 %, VPS 400 ₽, 82 ₽/$).

МодельЛёгкийСреднийТяжёлыйЗадача на 8 шагов
DeepSeek V4 Flash · llms.dotpoin.com ($1 / $3, кэш $0,5)≈ 890 ₽≈ 5 700 ₽≈ 43 700 ₽≈ 7,6 ₽
DeepSeek V4 Flash · OpenRouter ($0,14 / $0,28, кэш $0,028)≈ 60 ₽≈ 390 ₽≈ 2 900 ₽≈ 0,5 ₽
GigaChat 2 Lite (65 ₽)≈ 1 200 ₽≈ 7 900 ₽≈ 60 100 ₽≈ 10,5 ₽
GigaChat 2 Max (650 ₽)≈ 12 200 ₽≈ 78 900 ₽≈ 601 300 ₽≈ 105 ₽

Расходы на модель в месяц, без VPS.

Как снизить расход

ПриёмНа что действуетЭффект и цена
Кэширование промптаp_in → p_cache для префиксаСамый сильный рычаг для многошаговых задач: префикс одинаков на всех шагах. Требует стабильного начала промпта: любая правка памяти или списка навыков в середине сессии сбрасывает кэш
Дешёвая модель для вспомогательных задачp_in, p_outЗаголовки сессий, фоновый review памяти, сжатие истории, cron-мониторинг — секция auxiliary в конфиге. Цена — возможный пропуск важного в review
Скрипт вместо агента в cronчисло LLM-вызововhermes cron create … --no-agent --script check.sh: проверку делает скрипт, агент включается только при отклонении. Для мониторинга расход падает почти до нуля
Короткая память и узкий набор инструментовC₀Схемы инструментов — заметная часть префикса. Флаг -t/--toolsets оставляет только нужное (например, для cron — web без браузера и терминала)
Ограничение числа шаговn, квадратичный членЛимит итераций и таймаут обрывают зацикливание. Цена — часть сложных задач не будет доведена до конца
/new на границе задачΔ·n²Не тянуть историю прошлой задачи в новую; нужное агент найдёт через session_search
Обрезка вывода инструментовΔВместо целой страницы или лога — grep/tail; агента об этом стоит попросить в USER.md

P4Сравнительный эксперимент качества

Десять контрольных задач из реальной работы веб-инженера-исследователя, три модели, по три прогона каждой задачи на каждой модели — всего 90 прогонов. Успех проверяет код, а не модель-судья и не автор на глаз. Стенд: agent-lab/p4/.

Контрольные задачи

Веб-фикстуры опубликованы на этом сайте (vps-plans, changelog) и закрыты от индексации. Файловые фикстуры копируются в контейнер агента в /opt/data/p4. Правильные ответы известны заранее или вычисляются из фикстур.

IDКлассКритерий успеха (проверяется кодом)

Модели

МодельКлассКонтекстВход / выход за 1MЦена сверена

Процедура

Результаты

Загрузка…

Итоги и выводы

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

Стоимость одной успешной задачи

МодельT07: времяШагиВход, ток.Доля кэша₽ за успехТо же через OpenRouter
DeepSeek V4 Flash31 с574 39498 %3,28 ₽≈ 0,20 ₽
GLM-5.3 Flash72 с573 52379 %1,90 ₽—
Qwen3.5 9B301 с7371 86916 %6,13 ₽0 ₽ локально

Задача T07 (подсчёт IP и ответов 5xx в журнале nginx) — единственная, которую прошли все три модели. Цены — по прайсу шлюза llms.dotpoin.com; колонка OpenRouter — тот же расход токенов по цене DeepSeek на OpenRouter.

Какая модель для какого класса задач

Класс задачРекомендацияОснование
Обработка файлов и логов командами, регламентные cron-задачи (T02, T04, T07, T09)GLM-5.3 Flash; DeepSeek, когда важна скорость ответаОбе модели решили T07 верно; GLM дешевле на 42 %, DeepSeek быстрее в 2,3 раза. Для фоновых задач время некритично
Поиск по источникам и спецификациям, ссылки (T05, T06, T10)DeepSeek V4 FlashT06 решён с верной ссылкой на раздел RFC и корректной исторической справкой (в RFC 7231 — «Payload Too Large»). Для остальных моделей не проверено
Фоновая обработка без ограничений по времени и при требованиях к приватностиQwen3.5 9B локальноРешает простую задачу верно, но через платный API невыгоден. Оправдан только локально, где токены бесплатны, а 5 минут на задачу приемлемы

Наблюдавшиеся отказы

Оценка полного протокола

По стоимости T07 полный протокол (10 задач × 3 повтора) обошёлся бы примерно в 100 ₽ для DeepSeek, 57 ₽ для GLM и 185 ₽ для Qwen — около 340 ₽ на три модели. Это нижняя граница: задачи с загрузкой веб-страниц (T01, T05, T08) добавляют в контекст тысячи токенов на шаг. Через OpenRouter часть DeepSeek стоила бы около 6 ₽. Стенд готов: node run.mjs --resume досчитает недостающие прогоны, и таблицы на странице обновятся из results.json.

Ограничения эксперимента