Мой harness — обвязка вокруг LLM-агента для ведения ИТ-проектов. Он сам расшифровывает встречи, раскладывает решения и поручения по проектам, следит за задачами в Redmine, держит свежим статус каждого из 10 проектов и раз в день присылает сводку. Решения по задачам принимаю я.
Harness — всё, что превращает «умный чат» в работающую систему. Модель у агента уже менялась, и не раз, а обвязка всё это время только росла.
Встречи, Redmine, заметки — и одно место, где хранится состояние, а не четыре
Узкие MCP-инструменты вместо «полного доступа к файлам и shell»
Расписание и события, а не «когда я вспомнил и открыл чат»
Детерминированный код решает, есть ли что обрабатывать, — модель не тратит токены вхолостую
Агент предлагает, человек подтверждает; каждое изменение оставляет след
Одно сообщение в день и витрина, а не поток уведомлений
Главное свойство harness — гибкость: каждый из шести пунктов — отдельная ручка. В своей системе любой параметр можно добавить, убрать или переделать, не трогая остальные. Дальше — как эти ручки выставлены у меня.
NAS держит инфраструктуру и сам агент — он работает круглосуточно. Ubuntu-сервер берёт GPU-нагрузку. MacBook — место, где я разрабатываю harness. VPS — единственное, что смотрит в интернет.
Контейнеры harness на NAS: hermes (агент) · project-radar-postgres (ядро данных) · project-kanban (API и доска) · project-dashboard (витрина) · qdrant (векторный индекс) · n8n + syncthing (конвейер встреч). Всё, кроме VPS, — домашнее железо; из облака только модели.
Для агента, который работает постоянно, беру подписку, а не оплату за токены. Локально держу то, что должно быть бесплатным и приватным, — эмбеддинги всего хранилища.
gpt-5.6-terra — «мозг» агента Hermes Work; Codex — второй ИИ-разработчик harness
Sonnet 5 в основном, изредка Opus 5 — Claude Code как основной разработчик harness и разовые глубокие задачи
внутри обёртки OpenCode — для рабочих задач внутри контура компании
nomic-embed-text — эмбеддинги для поиска агента по хранилищу; qwen3 / deepseek-r1 / mistral 12–14B для экспериментов
qwen3.7-flash — дешёвая модель для домашнего профиля того же Hermes и для старта новых профилей
Gemini 3.1 Pro
Раньше здесь была сетевая свалка заметок без единой логики. Сейчас агент сам раскладывает информацию по смысловой структуре, а всё хранилище проиндексировано для поиска по смыслу.
Переложить рутину обработки информации с человека на LLM. Вы собираете «сырые» данные (статьи, PDF, записи), а LLM их перерабатывает: пересказывает, выделяет концепты, строит связи, обновляет существующую базу.
Ни одного ручного действия после звонка: запись сама доходит до Obsidian и Telegram, уже разложенная по смыслу.
WaveLink сводит звонок и собственный микрофон в единый аудиопоток — так в записи слышно и меня, и собеседников с одной дорожки. Поток уходит в Recordia, которая пишет его в .wav.
Syncthing решает сразу две задачи: переносит записанный .wav с ноутбука на NAS, и отдельно синхронизирует само Obsidian-хранилище между всеми устройствами — поэтому готовая заметка потом одинаково доступна везде.
n8n триггерится на появление нового файла в папке записей и отправляет его на GPU-сервер с WhisperX. Несколько служебных нод дальше подчищают технический мусор из транскрипта, прежде чем передать текст в LLM — чтобы не жечь токены зря.
Очищенный транскрипт уходит в LLM с фиксированным промптом: роль ассистента для итогов встреч, структура ответа строго по секциям — «Кратко», «Принятые решения», «Действия» в формате «кто → что → дедлайн».
Готовый .md расходится сразу по двум адресам параллельно: летит в Telegram-бот для мгновенного уведомления на телефон, и одновременно пишется прямо в Obsidian-хранилище на NAS.
n8n кладёт саммари в одну папку, а на общей встрече обсуждают пять проектов сразу. Каждые 5 минут агент-разборщик находит новые общие встречи и отдаёт каждый пункт ровно одному адресату: проекту, общей доске IT management или в корзину, если это личное. В папку проекта ложится выдержка со ссылкой на оригинал.
Как только в папке проекта появляется встреча или выдержка, агент извлекает поручения и предлагает карточки: создать, обновить, передвинуть, закрыть. На доску предложение попадает только после моего подтверждения.
WaveLink сводит звонок и микрофон → Recordia пишет .wav
Syncthing: запись → на NAS, хранилище → на все устройства
n8n → WhisperX с диаризацией → очистка мусора
LLM: решения, договорённости, ответственные, сроки
.md в Telegram и в Obsidian на NAS
Агент делит общую встречу на выдержки по проектам
Предложения карточек в инбокс, решаю я
Этапы 1–5 — детерминированный конвейер на n8n, он не менялся с прошлой версии. Этапы 6–7 добавил агент: там нужен смысл, а не правило.
Раньше у меня было 8 скиллов для Claude Code / OpenCode: статус проекта, дельта Redmine, отчёты к планёрке. Они работали, но упирались в одно — в меня.
| Скиллы на MacBook | Hermes Work на NAS | |
|---|---|---|
| Кто запускает | Я, фразой в чате | Расписание и события: новый файл, изменения в Redmine |
| Когда работает | Пока ноутбук открыт и я помню | Круглосуточно: каждые 5 минут, ежедневно, еженедельно |
| Контекст | Заново в каждой сессии, всё зашито в промпт скилла | Состояние в БД, память профиля, роль в SOUL.md |
| Доступ | Широкий: файлы, grep, любые API | 26 узких инструментов, ни одного «общего» доступа к файлам |
| Куда пишет | Прямо в файлы Obsidian | Только через API ядра; задачи — только предложениями |
| Контроль | Я смотрю на экран, пока он работает | Инбокс подтверждений и аудит каждого изменения |
| Стоимость | Полный прогон при каждом запуске | LLM вызывается, только если данные изменились |
Hermes использует тот же формат SKILL.md, и промпты переехали почти без правок. Разница не в инструкции модели, а в четырёх вещах вокруг неё.
cron + monitor-скрипт. Скилл запускается сам, когда в источнике что-то изменилось
Свои MCP-серверы под задачу. Агент физически не может сделать то, что не предусмотрено
Что обработано, что предложено, что принято — в Postgres, а не в frontmatter файла
Результат ложится в ядро и витрину; мне приходит одно утреннее сообщение
Claude Code никуда не делся: на нём я строю harness и решаю разовые глубокие задачи — например первичный разбор нового проекта в Redmine. Регулярное делает агент.
_STATUS.md целикомПриёмка: первый автоматический прогон совпал с ручным 1:1 — 177 закрытых задач, 107 в работе. Правило, которое я вынес: всё детерминированное — коду, смысловое — модели.
Был мета-скилл «подготовь материалы к планёрке»: по очереди запускал отчёт по логистике, дельту Redmine и отчёт по проектам. Перед каждой планёркой — запуск и ожидание.
Снимки и дельты по проектам уже посчитаны
Обновлены там, где что-то изменилось
Недельный отчёт уже лежит в папке планёрки
Оркестрировать нечего — расписание и есть оркестратор
Так же растворился скилл синхронизации роадмапа Google Sheets ↔ Redmine: вместо таблицы теперь вкладка IT management, которая читает Redmine напрямую (раздел 8).
Hermes Agent — open-source фреймворк от Nous Research. На NAS в одном контейнере живут два профиля: ha — умный дом, work — проекты. У каждого свой процесс, свой Telegram-бот, своя модель и свой набор прав.
Кто агент, какие у него инструменты и когда какой брать. Меняется в одном коммите с инструментами
vault 8 · board 7 · portfolio 6 · redmine 5. Все написаны под задачу
синтез статуса, радар Redmine, извлечение задач, разборщик встреч, дайджест, уточнения, выжимки, отчёт по логистике
Большинство с monitor-скриптом: модель запускается только при изменениях
Утренний дайджест, вопросы по неполным карточкам, диалог «что по проекту X»
По подписке. Стартовал на дешёвой qwen-flash — для обкатки обвязки её хватало
С Claude Code я заметил: если у агента есть и grep, и поиск по смыслу, он почти всегда выбирает перебор файлов и жжёт токены. Поэтому встроенный файловый toolset профилю не дан, а вместо него — свой MCP-сервер к хранилищу.
| Инструмент | Что делает | Почему так |
|---|---|---|
| read_file · search_files · ls | выключеноВстроенный доступ Hermes к файлам: читать любой файл, грепать, листать папки | С ним агент перебирает хранилище вслепую и жжёт токены |
| vault_search | найтиИщет заметки по смыслу запроса и отдаёт топ фрагментов; свежие заметки получают небольшой бонус | Основной способ найти что-то. Без бонуса свежести архив перебивал актуальное |
| vault_recent | найтиОтдаёт список заметок за период, новые сверху, — без содержимого | Поиск по смыслу не понимает «последняя встреча» и «сегодня» |
| vault_find | найтиИщет файлы по словам в имени и пути, например «этрн status» | Статус-файлы всех проектов называются одинаково — по смыслу их не различить |
| vault_read | прочитатьЧитает одну заметку, путь к которой уже найден одним из трёх способов выше | Прочитать можно только найденное — бродить по папкам нельзя |
| vault_write | записатьСоздаёт или дополняет обычную заметку — например, недельный отчёт | Статус-файлы так записать нельзя, для них отдельная дверь |
| vault_update_status_blocks | записатьОбновляет в ядре только разрешённые блоки статуса проекта; файл потом пересобирает код | Модель физически не может испортить блоки, которые ей не принадлежат |
Правило: если класс запросов не решается поиском по смыслу, я добавляю ещё один узкий инструмент, а не открываю общий grep.
Почти каждая cron-задача устроена одинаково: сначала дешёвый детерминированный скрипт, потом — по необходимости — модель.
Собирает снимок: новые встречи, дельты Redmine, заметки. Классифицирует источники и считает хеш
0 токенов. Так проходит большинство прогонов
Агент получает готовый JSON: какой проект, какие файлы, какого они типа
Прочитать, извлечь, предложить карточки, обновить блоки статуса — через API ядра
Модель не решает, есть ли работа, — это решает код. Модель решает, что с этой работой делать.
Всё, что имеет формат, — имена файлов, шапки, ссылки, порядок блоков, выборки из трекера — собирает код. Модели остаётся та часть, где нужен смысл. Чем меньше формата доверено модели, тем меньше мест, где она может его сломать.
| Артефакт | Делает код | Делает модель |
|---|---|---|
| Выдержка встречи | Имя файла с датой, frontmatter, ссылка на оригинал, выбор папки проекта | Разделы «Кратко», «Решения», «Поручения», «Факты» |
| Статус проекта | Порядок 15 блоков, шапка «не редактировать», задачи из доски, команда из справочника | Текст живых блоков: суть и фаза, риски, хронология |
| Отчёт по логистике | Выборка из Redmine, дерево инициатив, раскладка по 4 направлениям | Группировка по темам и человеческий текст по образцу |
| Выжимка IT management | Какой задаче нужна выжимка — по хешу описания; куда её сохранить | Одно предложение «зачем эта задача бизнесу» |
Побочный эффект: результат можно проверить кодом. Выдержка без ссылки на оригинал или статус без нужного блока просто не соберутся.
Первая версия хранила «что уже обработано» во frontmatter статус-файлов и в файлах самого агента. Любая переделка грозила потерей памяти. Теперь агент — сменный исполнитель, а всё, что он знает о проектах, лежит в Postgres.
Какие встречи и дельты уже обработаны · карточки и полная история изменений · очередь предложений и решения по ним · снимки Redmine · выжимки · справочник участников · блоки статусов
Роль в SOUL.md · скиллы с инструкциями · расписание · короткая память диалога в Telegram. Ничего, что жалко потерять
Модель меняется одной строкой в конфиге — история обработки и карточки остаются на месте
Обновление образа Hermes не трогает данные: агент стартует и продолжает с того же места
Claude Code или Codex разбирают сбой по базе и журналу событий, а не по логам чата
Менять задачи агенту нельзя — он только предлагает. Но от качества предложения зависит, сколько моего времени уйдёт на разбор. Поэтому у предложения жёсткие правила. Что происходит с ним на моей стороне — в разделе 8.
Сначала смотрит на доску. Перед разбором встречи агент читает текущие карточки проекта и шлёт «обнови», «передвинь», «закрой», а не плодит дубли
Обязательный паспорт. У каждого предложения — файл-источник и уверенность от 0 до 1: без них сервер предложение не примет. Плюс причина одной фразой
Срок — только если прозвучал. Дату агент не придумывает: нет на встрече — поле остаётся пустым
Исполнитель — из справочника. Имя сверяется со списком участников проекта; не нашлось — владельцем становлюсь я, а не случайный человек
Ожидаемый результат — всегда. «Что будет считаться сделанным» агент формулирует из контекста, иначе треть карточек не была готова к работе
Удалённое не возвращается. Если я удалил карточку как галлюцинацию, предложение с тем же заголовком сервер отклонит сам
Дайджест собирает ответы ночных прогонов за 20 часов, движения доски, зависшие предложения и просрочки
Не больше 3 вопросов в день по неполным карточкам. Если вопрос дважды остался без ответа, он затихает на 30 дней
Ответ в чате проходит через узкую дверь: агент может заполнить только исполнителя, срок, ожидаемый результат или колонку — и только у карточки с открытым вопросом
Раньше каждая проектная задача писала в Telegram сама — сообщений стало столько, что я перестал их читать
_STATUS.md больше никто не пишетВ прошлой версии статус-файл был «мотором» всего процесса — его переписывала модель. Оказалось, что у файла, в который пишут двое, в какой-то момент теряется текст.
У каждого проекта есть статус-документ _STATUS.md: паспорт проекта и его текущее состояние. Из него витрина рисует карточку проекта — суть, метрики, задачи, риски (как это выглядит — в разделе 9). Блоки документа поделены по тому, кто их пишет и как часто: это решает, когда модель вообще к ним прикасается.
Задачи в статусе — это проекция доски, а не пересказ модели. Если модель что-то «помнит» про задачу, а на доске этого нет, на карточку проекта это не попадёт.
Летом это была страница с Ганттом из Google Sheets. Каждый следующий шаг появлялся из конкретной боли предыдущего.
Я не готов доверить боту прямую запись в задачи, поэтому у агента нет права менять доску: он кладёт предложения в очередь.
Новый файл или дельта
create / update / move / close + источник, уверенность, причина
Принять, поправить или отклонить
Каждое изменение пишет неизменяемую запись аудита: кто, что, до/после, почему
Если я правил карточку в последние 7 дней, предложение агента не применяется молча: оно показывается первым, с разбором «сейчас → предлагается»
В «Оценку» нельзя без исполнителя и ожидаемого результата, в «Работу» — ещё и без срока. Правила одни для меня и для агента: их проверяет API
«Не актуально» — осознанный отказ, остаётся видимым. Удаление — для галлюцинаций и дублей; повторно такую карточку агент уже не предложит
Цифры в колонках — по всем проектам. Честная метрика шума: 118 из 242 карточек (49%) я отправил в «Не актуально». Агент хорошо находит поручения, но плохо отличает задачу от упоминания.
Вместо таблицы, которую синхронизировал скилл, — снимок Redmine по 5 командам. Redmine остаётся источником фактов, агент добавляет только выжимку «зачем» из описания задачи.
Нужна, чтобы клиентские приложения оставались доступными и не было потерь при оплате.
Перевод звонков курьера в приложение завершит целевой сценарий связи и снизит расходы на сотовые вызовы.
Маркер протухлости считается по всей ветке подзадач, а не по самому эпику.
Выжимка привязана к хешу описания: поменялось описание — выжимка пересчитается, старый ответ модели новую не перезапишет. При сбое Redmine неполный снимок не публикуется — остаётся прошлый.
Сводка из ядра по всем проектам: решения, которые ждут меня, сроки, блокеры. Сюда же попадают сбои разбора встреч — агент не должен молча терять встречу.
Ещё недавно здесь висело больше сотни решений: агент генерировал больше, чем я успевал разобрать. Экран собрал всё в одну очередь — сейчас разобраны все 315 предложений, ждёт одно. Следующая задача — не «умнее извлекать», а «меньше, но точнее».
Ведёт человек, агент сверяет с ним исполнителей. «Имя без отчества» — тот же человек. Блок команды в статусе — проекция справочника
Переключатель «Обзор / Kanban» на каждой вкладке; общая тема и стили с витриной
Проекты не удаляются: история карточек и событий сохраняется целиком
Обязательное поле у каждой карточки: «что будет считаться сделанным». Без него задачи с встреч оставались размытыми
Незавершённый разбор повторяется автоматически, а на экране видно, насколько свежий снимок
Протухшее, просроченное и топ по WSJF подсвечиваются на карточках во всех секциях
Витрина — одностраничное приложение на NAS за nginx с basic auth. Раньше она только читала статус-файлы, теперь это в первую очередь клиент ядра.
Главный экран, доски, IT management читают JSON из API ядра; доска проксируется по пути /kanban/
Статус-плашки по-прежнему из _STATUS.md: папка проектов смонтирована read-only, браузер парсит callout-блоки
У каждого проекта своя вкладка с переключателем «Обзор / Kanban»; программы с фазами дополнительно тянут Гантт из Google Sheets
Кто правит, берётся из basic auth и пишется в аудит: человек и агент различимы в каждой записи
Программы с датами по фазам (например ЭДО и ЭТрН) дополнительно тянут диаграмму Ганта прямо из Google Sheets — поверх тех же статус-плашек.
Мелкие, но полезные детали — набирались по одной, по мере того как чего-то не хватало на практике.
Переключатель темы, выбор хранится в localStorage браузера
Сузить Гантт и задачи до одного человека
Детали задачи всплывают прямо над баром на Гантте
Клик по строке фазы — схлопывает все подзадачи внутри
Один и тот же план — от дневной детализации до годовой
При открытии вкладки и раз в сутки — данные агента всё равно обновляются по утрам
Прошлая версия: оркестратор OpenCode на ноутбуке запускал скиллы по запросу, а Obsidian был общей базой для всего. Листайте вниз — как стало.
Центр сместился: вместо Obsidian и оркестратора на ноутбуке — Project Core (Postgres + HTTP API). У каждой сущности один владелец; Obsidian — вход и витрина.
Пока код жил только на NAS и правился через ssh, каждая доработка ломала что-то соседнее. Переломом стала не новая модель, а обычная инженерная гигиена.
Скрипт гоняет тесты, делает бэкап и кладёт на NAS git bundle со всей историей. Правка руками на сервере теряется при следующем деплое — и это правильно
Архитектура, дорожная карта, грабли, карта «исходник vs производный файл». Агентно-нейтрально: одинаково читают Claude Code и Codex
Владение данными, проекция статуса, одна дверь к данным, разборщик встреч… Новый ADR — когда меняется граница компонента или владелец данных
Подключил хранилище, а бот отвечал «у меня нет доступа к Obsidian». Модель честно следовала старому SOUL.md с этапа бутстрапа.
Свой MCP-сервер назвал kanban — так же, как встроенную подсистему Hermes. Её гейт тихо отключал все мои инструменты, а тест сервера при этом проходил.
«Дай итоги последней встречи» не находилось, а свежий статус проигрывал архиву мартовской планёрки.
Модель писала прозу, код рендерил задачи в тот же файл. Первый рендер стёр блок с вехами проекта.
23.09 Redmine отвечал частично, и монитор едва не опубликовал «пустой» портфель вместо 214 эпиков.
Syncthing при каждой синхронизации ставил права с ноутбука, и агент на NAS не мог перезаписать собственный отчёт.
встречи расшифрованы и саммаризированы с февраля — около 65 в месяц
предложений карточек от агента за 3,5 недели — все разобраны
общих встреч разложено выдержками по проектам с 1 сентября
проектов ведёт агент без моего запуска
фоновых задач, почти все с гейтом по хешу
сообщение в день вместо потока уведомлений
Код из NAS — в репозиторий, выкладка только из закоммиченного
У каждой сущности один владелец; конфликты — человеку
Проверено удалением и пересборкой с совпадением хеша
MCP агента — тонкий HTTP-клиент к API, прямого доступа к БД нет
Google Sheets остаётся способом ввода, но перестаёт быть источником для витрины
Ни одного парсера markdown в браузере; новый проект без правки HTML
Классическая ловушка — продуктизировать личный инструмент до того, как он стабилен. Вернусь, когда появится конкретный второй пользователь
Главный вывод за это лето: модель — самая заменяемая часть. То, что работает, — это данные с одним владельцем, узкие инструменты, гейты перед LLM и человек, который принимает решения.