Обвязка (harness) —
обвязка вокруг модели
Правила, git, перехватчики (hooks), доки, модули, тесты и CI/CD (проверки и выкат): зачем каждый слой, когда код пишут агенты.
Что такое обвязка (harness)
Модель умеет писать код. Без обвязки она может за час сделать то, на что у человека ушла бы неделя — и в том же часе сделать коммит (commit) прямо в ствол, сломать соседний кусок или снести лишнее на сервере, а потом написать «готово».
Обвязка (harness) — всё, что стоит вокруг модели и превращает «умного собеседника» в управляемого исполнителя. Не просьба «будь аккуратнее». А правила, git, перехватчики опасных команд, живые доки, модульный код, автотесты и автоматические проверки на каждый пуш.
Промпт в чате живёт одну сессию. Обвязка (harness) живёт между сессиями, между разными агентами и между людьми. Новый агент открывает проект — и уже не с нуля.
Правила, git и порядок в ветках
На сервере с несколькими продуктами у агентов два уровня инструкций.
Общие — кто главный исполнитель, куда смотреть за картой сервера, что нельзя трогать «заодно».
Проектные — в корне продукта: файл для всех агентов (как работать: ветки, проверки перед словом «готово»), файл эксплуатация (эксплуатация, ops) (деплой, грабли, долг), при необходимости — пошаговые skills (пошаговые процедуры) и узкие роли.
Порядок чтения зафиксирован: сначала «как работать», потом «как устроен прод», потом skill (процедура) по теме. Без этого агент чинит из памяти прошлой сессии.
Зачем git (система версий), когда код пишет агент
Git здесь не «для сеньоров из учебника». Это страховка на пять задач, которые без истории агент решает плохо.
- Откат. Агент за час трогает двадцать файлов. Без снимков «верни как было» = вручную вспоминать. С git — откат ветки или отдельного коммита.
- Просмотр изменений (diff). Владелец часто не читает весь код. Он смотрит, что изменилось. Чат врёт и забывает. Diff — факты по строкам.
- Песочница. Вариант A и вариант B — на разных ветках. Один не затирает другой.
- Несколько агентов. Один чинит оплату, другой — вёрстку. У каждого своя ветка; столкновение видно при слиянии, а не как «кто-то стёр фикс».
- Мост к проверкам и выкату. Автоматические проверки и деплой цепляются не к «файлам у агента на диске», а к пушу, заявке на слияние или метке версии. Без git нет стабильной точки «вот этот набор проверь».
Коротко: git превращает «потрогал файлы» в «предложение изменений с историей».
Ствол нельзя считать рабочей тетрадью
Ствол (обычно ветка main) — общая линия, с которой часто уезжает dev или готовится прод. Перехватчик перед коммитом (pre-commit): попытка сделать коммит (commit) прямо в main / develop падает с текстом «создай свою ветку». Обход только с явного согласия владельца.
Человек иногда стыдится пушить в ствол (main) в пятницу. Агент — нет. Есть только правило и автомат.
Изолированные ветки
- Имя = кто + зачем (пример:
claude/plan-harness-docs). Неtmpи неfix. - Один агент — одна ветка на задачу. Два агента на одной = гонка и затёртый код.
- Ветка до первой правки. Даже «на одну строку» — сначала своя ветка, потом edit.
- Готово = заявка на слияние (заявка на слияние, PR), не «смотри сырую ветку». На заявка на слияние (PR) живут проверки, diff для ревью и след «что когда влито».
- Прод не с черновика. Метка релиза — только с коммита, который уже в стволе. Черновик можно показать на dev (тестовый контур); помечать прод — нет.
Гигиена: чтобы git не стал свалкой
Ветка — указатель, не архив. История живёт в коммитах (commit) и в заявках на слияние (PR). У указателя должно быть конечное состояние, иначе через месяц — кладбище.
| Судьба | Что сделать |
|---|---|
| Влили в ствол | Удалить ветку (часто автоматически после слияния заявки (PR)) |
| Бросили / отвергли | Сохранить коммиты меткой «архив», ветку убрать |
| Ещё в работе | Не плодить вторую ветку на ту же задачу |
| Висит неделями | Попадает в отчёт «санитара» (на одном продукте — старше 14 дней) |
Дополнительно: автономный агент не делает коммит (commit) и не отправляет изменения (push) без явного запроса — оставляет правки, показывает diff, ждёт решения. коммиты (commit) — осмысленными кусками, не одним «wip» (работа ещё идёт) на сорок файлов. Принудительная перезапись истории (force-push) в общие ветки блокируется отдельно.
Перехватчики (hooks): стоп до беды
Правило в тексте агент может «переинтерпретировать». Перехватчик (hook) — скрипт, который срабатывает до действия и может отказать.
| Когда | Что режет |
|---|---|
| Старт сессии | Подсовывает пути к правилам проекта |
| Перед командная строка (shell) | Принудительная перезапись истории (force-push), rm по системным путям, stop nginx/postgres, DROP таблицы, DELETE без условия |
| Перед записью в файл | Секреты, .env |
| Перед commit | Секреты в stage (подготовленное к коммиту) |
| В конце ответа | Ленивые финалы: «давай потом», «и так сойдёт» |
Текст правила учит. Перехватчик (hook) исполняет. После инцидента «агент снёс не то» мало дописать абзац — нужен гейт.
Документация, которая не врёт
Агенты исполняют доки как код. Если в skill (процедуре) написано «максимум 9 разделов», а в схеме уже 15 — агент живёт в мире девятки. Это не гипотеза: на одном продукте skill (процедура) и схема реально разошлись (9 против 15). Второй рассинхрон (drift) — разные счётчики «сколько уроков» в двух файлах одного репо.
Поэтому в зрелых продуктах дока — часть задачи, не «наведём порядок потом». Обновить карту до коммита. Один источник правды; при споре прав код, оба текста привести к нему. После фичи — уборка после фичи (housekeep): типы, тесты, доки, синхронизация правил между агентами (один читает файл эксплуатации (эксплуатация, ops), другой — только общий: инвариант в одном слое для второго уже ложь).
Модули: комнаты, двери и радиус удара
Это не «красивая архитектура для портфолио». Для агента модульная структура — это скорость поиска, узкий контекст и предсказуемый радиус удара (что ещё отвалится, если тронуть вот этот кусок).
Что агент делает с кодом на самом деле
Он не «держит весь проект в голове», как иногда кажется со стороны. Цикл всегда один и тот же:
- Найти — по имени, по поиску текста, по карте, по графу «кто кого зовёт».
- Открыть — столько файлов, сколько влезает в рабочую память сессии.
- Понять границу — где кончается «оплата» и начинается «письма».
- Поправить — и угадать (или проверить тестами), что ещё сломалось.
На складе без полок агент ползает и хватает не то. На складе с подписанными стеллажами — идёт в нужный ряд. Модуль = стеллаж с табличкой и одной дверью снаружи.
Монолит против модулей — одной картинкой
| Шаг | Монолит | Модуль |
|---|---|---|
| Найти место | 5–15 ложных следов по всему репо | 1–2 очевидных пути по имени домена |
| Прочитать | 800+ строк «на всякий случай» | 100–250 строк + публичный вход |
| Оценить риск | «Вроде только тут» | Кто зовёт дверь — перечислимо |
| Написать тест | Чтобы поднять функцию — полмира | Тест на границе модуля |
| Два агента | Драка и ад слияния (merge) в одном файле | Разные каталоги, конфликт реже |
| Выкинуть фичу | Щупальца по половине репо | Удалить папку + правило границ |
Как выглядит «хороший модуль» (не теория)
Не «три файла по двадцать строк с простынёй реэкспорт (re-export)». Модуль — это граница ответственности:
- Свой каталог с именем домена: всё про «бронирование» или «оплату» лежит рядом, а не размазано.
- Свои данные и запросы к ним — не лезть руками в таблицы чужого домена; нужно чужое — зови публичную функцию соседа.
- Узкая дверь снаружи — страницы и API зовут 2–5 функций модуля, а не внутренности на третьем уровне вложенности.
- Внутри можно резать файлы — снаружи всё равно одна дверь.
Типичное дерево (имена условные, смысл с прода):
lib/payments/
index.ts ← дверь наружу: createPayment, refund, getStatus
rules.ts ← когда можно вернуть деньги
queries.ts ← только «свои» таблицы payments_*
format.ts ← мелочь, снаружи не видна
# Снаружи (кто зовёт модуль):
import { createPayment } from '@/lib/payments'
# Не так (дыра в стене):
import { secretHelper } from '@/lib/payments/format' ← дыра в стене
Радиус удара: зачем «дверь» экономит нервы
Когда агент правит внутри модуля за дверью — снаружи ломается только то, что звало публичные функции. Это можно перечислить: «три роута и один воркер». Когда всё в одном файле на тысячу строк — «радиус удара» = неизвестно; агент либо боится трогать, либо кромсает наобум.
На одном крупном продукте это зафиксировали правилом: новый крупный домен = самодостаточный модуль + автопроверка границ (запрет циклов, запрет «логика тянет UI», запрет лазить в чужие запросы к БД). В комментарии к проверке прямо: циклы путают агентов, не только ««чистая архитектура»».
Лимит строк — не эстетика, а давление на декомпозицию
В обвязке (harness)'е обычно так:
| Порог | Смысл для агента |
|---|---|
| ≤ ~250 строк файла кода | Кусок читается целиком, инварианты не теряются |
| > ~400 строк | Жёсткий сигнал: режь до следующего коммита (кроме справочников и документация (docs)) |
| компонент интерфейса (UI) ≤ ~150 (где принято) | Остров интерфейса, не «страница-простыня» |
Легаси всё равно будет: на живом продукте в базовый список долга (baseline) «долга» замораживали порядка десятков файлов длиннее 400 строк и сотен роутов без прямого теста. Смысл не «долга нет», а долг только уменьшается (правило «долг только вниз» (ratchet)): новый файл-переросток или новый роут без теста = красный непрерывная интеграция (CI). Иначе агент под видом «ещё один if» (ещё одно условие) снова склеит монолит.
Параллельные агенты и модули
Ветки git разводят людей и сессии. Модули разводят зоны кода. Вместе: агент A в lib/auth/ на своей ветке, агент B в lib/billing/ на своей — меньше шансов, что оба правят строку 847 одного файл-монолит (god-file) и устраивают ручной слияние (merge) на час.
Как не превратить модульность в религию
- Не тотальный разрез «с нуля» (big-bang) «перережем всё на микросервисы». Обычно хуже: история, карты, открытые заявка на слияние (PR), агенты в полёте. Резать на новых доменах и при касании легаси.
- Правило в доке без гейта протухнет. Нужна автопроверка границ / правило «долг только вниз» (ratchet) — иначе через месяц снова каша.
- Модуль ради модулей — три файла-обёртки без границы ответственности не помогают поиску.
- Карта без модулей — карта болота. Модули без карты — агент блуждает по красивым папкам. Нужны оба.
Итог раздела. Для агента модульность — не чистота ради чистоты. Это навигация, узкий контекст, предсказуемый радиус удара и возможность параллельной работы. Лимит строк и автопроверка «долг только вниз» (ratchet) — способы не дать монолиту вырасти обратно под видом «ещё один if» (ещё одно условие).
Автотесты: «готово» по факту
Модель оптимистична. Она уверена, что правка локальна. Тест — зафиксированное ожидание без самообмана.
- Шлюз проверки (verification gate): перед словом «готово» — прогон: проверка типов (type-check), модульные тесты (unit), сборка (build). Без успешного кода выхода (exit 0) заявление «готово» запрещено.
- Регрессия: починили A, сломали B — тест орёт сразу.
- Контракт на краю: какой ответ API — агенту проще удовлетворить тест, чем угадать неписаное.
Масштаб разный и это нормально: где деньги и вход — сотни модульный тест (unit)ов и гейт «тронул роут — нужен тест»; где контент — сборка как проверка схемы; где крошечный сервис — честный дымовая проверка curl (дымовая проверка (smoke)). Обвязка (harness) требует тормоза там, где цена ошибки высока, не 100% покрытие тестами (покрытие (coverage)) везде.
CI/CD (проверки и выкат) — что это простыми словами
CI (непрерывная интеграция) — на каждую отправку (push) или заявку на слияние автоматом: зависимости, типы, стиль, тесты, иногда границы модулей и правило «долг только вниз» (ratchet). Красное — не мержим.
CD (непрерывная доставка / выкат) — из проверенного ствола можно выкатить одной кнопкой или меткой версии; либо выкат идёт сам по правилам (часто: тег v* только с коммита из ствола (main), не каждый коммит (commit) = прод).
Зачем, если агент «и так прогнал тесты»: мог забыть кусок; окружение (env) другой; на заявке на слияние (PR) висят тяжёлые гейты; владелец смотрит зелёную галку и смысл фичи, а не 40 файлов diff вслепую. На части продуктов перед отправкой (pre-push) режет отправку (push) в ствол (main), если последний непрерывная интеграция (CI) красный.
Шкала: не всё сразу
| Уровень | Что уже есть |
|---|---|
| 0 | Чат с моделью, без правил |
| 1 | Файл правил + запрет коммита (commit) в ствол (main) |
| 2 | + перехватчики (hooks) на опасный bash и секреты |
| 3 | + живые доки, обновление в той же задаче |
| 4 | + модули, лимит строк, границы |
| 5 | + автотесты и «готово» только после прогона |
| 6 | + CI на заявке (PR), CD по тегу или скрипту |
Маленький сервис на одном файле — честные уровни 1–2. Крупный продукт с оплатой без 5–6 — лотерея.
Копипаст
В блоках ниже длинные тире в промптах заменены на запятые (удобнее в рабочих файлах).
Ты аудируешь обвязку (harness) проекта для AI-агентов. Не пиши код, пока не отдашь отчёт.
Проверь и заполни таблицу (есть / нет / частично + путь к файлу):
1. AGENTS.md и CLAUDE.md (или аналоги), порядок чтения
2. Запрет commit/push в main (hook или защита ветки)
3. Правила изолированных веток и гигиена (PR, удаление после merge, архив брошенных)
4. Хуки на опасный bash и секреты
5. Актуальные доки: когда обновлялись, есть ли drift
6. Лимит размера файлов, модульные границы
7. Автотесты: команды, что гоняется на PR
8. CI/CD: workflows, кто деплоит, с какой ветки/тега
9. шлюз проверки (verification gate): что агент обязан прогнать перед словом готово
Для каждого пробела: риск одной фразой и минимальный следующий шаг.
Отдельно: 3 самых толстых файла кода и 3 места, где дока противоречит коду.
Собери минимальную обвязку (harness) для репозитория, где код пишут AI-агенты.
Сделай:
1. AGENTS.md: структура, команды dev/test/build, ветки agent/scope-slug,
HARD STOP на стволе (main), verification gate перед готово, 5-10 инвариантов,
правила: один агент = одна ветка, PR для сдачи, влитое удаляем, брошенное в archive
2. CLAUDE.md: стек, деплой, gotchas, Known Issues (коротко)
3. pre-commit hook: блок commit на main/develop
4. Правило: доки обновлять в той же задаче; файлы кода предпочтительно до 250 строк
Не выдумывай CI, если его нет: опиши минимальный следующий шаг.
Не деплой. Покажи diff и как включить hooksPath.
Развязка
Обвязка (harness) — не недоверие к модели. Это признание: модель сильная, а контекст сессии — нет. Не помнит прошлый инцидент, не стыдится main, не видит прод, оптимистична в «готово».
Правила задают курс. Git делает работу обратимой и разводимой. Изолированные ветки и гигиена не дают истории превратиться в свалку. Перехватчики (hooks) режут катастрофу до выполнения. Доки держат картину между сессиями. Модули делают поиск и правку дешёвыми. Тесты и непрерывная интеграция (CI) не спорят с обаянием ответа в чате — они проверяют изменение.
Можно купить доступ к самой дорогой модели и получить хаос. Можно взять сильную модель и вложить в обвязке (harness) — и тогда скорость агентов работает на продукт.
Обвязка скучнее демо в ленте. На проде выигрывает она.
// Обсуждение
Можно писать анонимно. Укажите email, чтобы получать уведомления об ответах.